#!/usr/bin/python

# Copyright (c) 2018 Red Hat, Inc.
#
# GNU General Public License v3.0+

from __future__ import absolute_import, division, print_function
__metaclass__ = type

from ansible.module_utils.basic import AnsibleModule
try:
    from ansible.module_utils.ca_common import fatal
except ImportError:
    from module_utils.ca_common import fatal
import datetime

ANSIBLE_METADATA = {
    'metadata_version': '1.1',
    'status': ['preview'],
    'supported_by': 'community'
}

DOCUMENTATION = '''
---
module: ceph_crush

author: Sebastien Han <seb@redhat.com>

short_description: Create Ceph CRUSH hierarchy

version_added: "2.6"

description:
    - By using the hostvar variable 'osd_crush_location'
    ceph_crush creates buckets and places them in the right CRUSH hierarchy

options:
    cluster:
        description:
            - The ceph cluster name.
        required: false
        default: ceph
    location:
        description:
            - osd_crush_location dict from the inventory file. It contains
            the placement of each host in the CRUSH map.
        required: true
    containerized:
        description:
            - Weither or not this is a containerized cluster. The value is
            assigned or not depending on how the playbook runs.
        required: false
        default: None
'''

EXAMPLES = '''
- name: configure crush hierarchy
  ceph_crush:
    cluster: "{{ cluster }}"
    location: "{{ hostvars[item]['osd_crush_location'] }}"
    containerized: "{{ container_exec_cmd }}"
  with_items: "{{ groups[osd_group_name] }}"
  when: crush_rule_config | bool
'''

RETURN = '''#  '''


def generate_cmd(cluster, subcommand, bucket, bucket_type, containerized=None):
    '''
    Generate command line to execute
    '''
    cmd = [
        'ceph',
        '--cluster',
        cluster,
        'osd',
        'crush',
        subcommand,
        bucket,
        bucket_type,
    ]
    if containerized:
        cmd = containerized.split() + cmd
    return cmd


def sort_osd_crush_location(location, module):
    '''
    Sort location tuple
    '''
    if len(location) < 2:
        fatal("You must specify at least 2 buckets.", module)

    if not any(item for item in location if item[0] == "host"):
        fatal("You must specify a 'host' bucket.", module)

    try:
        crush_bucket_types = [
            "host",
            "chassis",
            "rack",
            "row",
            "pdu",
            "pod",
            "room",
            "datacenter",
            "region",
            "root",
        ]
        return sorted(location, key=lambda crush: crush_bucket_types.index(crush[0]))  # noqa: E501
    except ValueError as error:
        fatal("{} is not a valid CRUSH bucket, valid bucket types are {}".format(error.args[0].split()[0], crush_bucket_types), module)  # noqa: E501


def create_and_move_buckets_list(cluster, location, containerized=None):
    '''
    Creates Ceph CRUSH buckets and arrange the hierarchy
    '''
    previous_bucket = None
    cmd_list = []
    for item in location:
        bucket_type, bucket_name = item
        # ceph osd crush add-bucket maroot root
        cmd_list.append(generate_cmd(cluster, "add-bucket", bucket_name, bucket_type, containerized))  # noqa: E501
        if previous_bucket:
            # ceph osd crush move monrack root=maroot
            cmd_list.append(generate_cmd(cluster, "move", previous_bucket, "%s=%s" % (bucket_type, bucket_name), containerized))  # noqa: E501
        previous_bucket = item[1]
    return cmd_list


def exec_commands(module, cmd_list):
    '''
    Creates Ceph commands
    '''
    for cmd in cmd_list:
        rc, out, err = module.run_command(cmd)
    return rc, cmd, out, err


def main():
    module = AnsibleModule(
        argument_spec=dict(
            cluster=dict(type='str', required=False, default='ceph'),
            location=dict(type='dict', required=True),
            containerized=dict(type='str', required=False, default=None),
        ),
        supports_check_mode=True,
    )

    cluster = module.params['cluster']
    location_dict = module.params['location']
    location = sort_osd_crush_location(tuple(location_dict.items()), module)
    containerized = module.params['containerized']

    result = dict(
        changed=False,
        stdout='',
        stderr='',
        rc=0,
        start='',
        end='',
        delta='',
    )

    if module.check_mode:
        module.exit_json(**result)

    startd = datetime.datetime.now()

    # run the Ceph command to add buckets
    rc, cmd, out, err = exec_commands(module, create_and_move_buckets_list(cluster, location, containerized))  # noqa: E501

    endd = datetime.datetime.now()
    delta = endd - startd

    result = dict(
        cmd=cmd,
        start=str(startd),
        end=str(endd),
        delta=str(delta),
        rc=rc,
        stdout=out.rstrip("\r\n"),
        stderr=err.rstrip("\r\n"),
        changed=True,
    )

    if rc != 0:
        module.fail_json(msg='non-zero return code', **result)

    module.exit_json(**result)


if __name__ == '__main__':
    main()
