#!/usr/bin/python
#
#  Licensed to the Apache Software Foundation (ASF) under one or more
#  contributor license agreements.  See the NOTICE file distributed with
#  this work for additional information regarding copyright ownership.
#  The ASF licenses this file to You under the Apache License, Version 2.0
#  (the "License"); you may not use this file except in compliance with
#  the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#

DOCUMENTATION = '''
---
module: ambari_cluster_state
version_added: "2.1"
author: Mark Bittmann (https://github.com/mbittmann)
short_description: Create, delete, start or stop an ambari cluster
description:
    - Create, delete, start or stop an ambari cluster
options:
  host:
    description:
      The hostname for the ambari web server
  port:
    description:
      The port for the ambari web server
  username:
    description:
      The username for the ambari web server
  password:
    description:
      The name of the cluster in web server
    required: yes
  cluster_name:
    description:
      The name of the cluster in ambari
    required: yes
  cluster_state:
    description:
      The desired state for the ambari cluster ['present', 'absent', 'started', 'stopped']. Setting the cluster
      state to absent will first stop the cluster.
    required: yes
  blueprint_var:
    description:
      The path to the file defining the cluster blueprint and host mapping. Required when state == 'present'
    required: no
  blueprint_name:
    description:
      The name of the blueprint. Required when state == 'present'
    required: no
  wait_for_complete:
    description:
      Whether to wait for the request to complete before returning. Default is False.
    required: no
  requirements: [ 'requests']
'''

EXAMPLES = '''
# must use full relative path to any files in stored in roles/role_name/files/
- name: Create a new ambari cluster
    ambari_cluster_state:
      host: localhost
      port: 8080
      username: admin
      password: admin
      cluster_name: my_cluster
      cluster_state: present
      blueprint_var: roles/my_role/files/blueprint.yml
      blueprint_name: hadoop
      wait_for_complete: True
- name: Start the ambari cluster
  ambari_cluster_state:
    host: localhost
    port: 8080
    username: admin
    password: admin
    cluster_name: my_cluster
    cluster_state: started
    wait_for_complete: True
- name: Stop the ambari cluster
  ambari_cluster_state:
    host: localhost
    port: 8080
    username: admin
    password: admin
    cluster_name: my_cluster
    cluster_state: stopped
    wait_for_complete: True
- name: Delete the ambari cluster
  ambari_cluster_state:
    host: localhost
    port: 8080
    username: admin
    password: admin
    cluster_name: my_cluster
    cluster_state: absent
'''

RETURN = '''
results:
    description: The content of the requests object returned from the RESTful call
    returned: success
    type: string
created_blueprint:
    description: Whether a blueprint was created
    returned: success
    type: boolean
status:
    description: The status of the blueprint creation process
    returned: success
    type: string
'''

__author__ = 'mbittmann'

import json
try:
    import requests
except ImportError:
    REQUESTS_FOUND = False
else:
    REQUESTS_FOUND = True


def main():

    argument_spec = dict(
        host=dict(type='str', default=None, required=True),
        port=dict(type='int', default=None, required=True),
        username=dict(type='str', default=None, required=True),
        password=dict(type='str', default=None, required=True),
        cluster_name=dict(type='str', default=None, required=True),
        cluster_state=dict(type='str', default=None, required=True,
                           choices=['present', 'absent', 'started', 'stopped']),
        blueprint_var=dict(type='dict', required=False),
        blueprint_name=dict(type='str', default=None, required=False),
        configurations=dict(type='list', default=None, required=False),
        wait_for_complete=dict(default=False, required=False, type='bool'),
    )

    required_together = ['blueprint_var', 'blueprint_name']

    module = AnsibleModule(
        argument_spec=argument_spec,
        required_together=required_together
    )

    if not REQUESTS_FOUND:
        module.fail_json(
            msg='requests library is required for this module')

    p = module.params

    host = p.get('host')
    port = p.get('port')
    username = p.get('password')
    password = p.get('password')
    cluster_name = p.get('cluster_name')
    cluster_state = p.get('cluster_state')
    blueprint_name = p.get('blueprint_name')
    wait_for_complete = p.get('wait_for_complete')

    ambari_url = 'http://{0}:{1}'.format(host, port)

    try:
        if cluster_state in ['started', 'stopped']:
            if not cluster_exists(ambari_url, username, password, cluster_name):
                module.fail_json(msg="Cluster name {0} does not exist".format(cluster_name))
            state = ''
            if cluster_state == 'started':
                state = 'STARTED'
            elif cluster_state == 'stopped':
                state = 'INSTALLED'

            request = set_cluster_state(ambari_url, username, password, cluster_name, state)
            if wait_for_complete:
                try:
                    request_id = json.loads(request.content)['Requests']['id']
                except ValueError:
                    module.exit_json(changed=True, results=request.content)
                status = wait_for_request_complete(ambari_url, username, password, cluster_name, request_id, 2)
                if status != 'COMPLETED':
                    module.fail_json(msg="Request failed with status {0}".format(status))
            module.exit_json(changed=True, results=request.content)
        elif cluster_state == 'absent':
            if not cluster_exists(ambari_url, username, password, cluster_name):
                module.exit_json(changed=False, msg='Skipping. Cluster does not exist')
            if not can_delete_cluster(ambari_url, username, password, cluster_name):
                request = set_cluster_state(ambari_url, username, password, cluster_name, 'INSTALLED')
                request_id = json.loads(request.content)['Requests']['id']
                status = wait_for_request_complete(ambari_url, username, password, cluster_name, request_id, 2)
                if status != 'COMPLETED':
                    module.fail_json(msg="Request failed with status {0}".format(status))
            request = delete_cluster(ambari_url, username, password, cluster_name)
            module.exit_json(changed=True, results=request.content)
        elif cluster_state == 'present':
            if not p.get('blueprint_var') or not blueprint_name:  # have neither name nor file
                module.fail_json(msg="Must provide blueprint_var and blueprint_name when cluster_state=='present'")

            blueprint_var = p.get('blueprint_var')
            blueprint, host_map = blueprint_var_to_ambari_converter(blueprint_var)
            created_blueprint = False

            if not blueprint_exists(ambari_url, username, password, blueprint_name):
                create_blueprint(ambari_url, username, password, blueprint_name, blueprint)
                created_blueprint = True

            if cluster_exists(ambari_url, username, password, cluster_name):
                module.exit_json(changed=False, msg='Cluster {0} already exists'.format(cluster_name),
                                 created_blueprint=created_blueprint)

            configurations = p.get('configurations')
            request = create_cluster(ambari_url, username, password, cluster_name, blueprint_name, configurations, host_map)
            request_id = json.loads(request.content)['Requests']['id']
            if wait_for_complete:
                status = wait_for_request_complete(ambari_url, username, password, cluster_name, request_id, 2)
                if status != 'COMPLETED':
                    module.fail_json(msg="Request failed with status {0}".format(status))
            request_status = get_request_status(ambari_url, username, password, cluster_name, request_id)
            module.exit_json(changed=True, results=request.content,
                             created_blueprint=created_blueprint, status=request_status)

    except requests.ConnectionError, e:
        module.fail_json(msg="Could not connect to Ambari client: " + str(e.message))
    except Exception, e:
        module.fail_json(msg="Ambari client exception occurred: " + str(e.message))


def get_clusters(ambari_url, user, password):
    r = get(ambari_url, user, password, '/api/v1/clusters')
    if r.status_code != 200:
        msg = 'Could not get cluster list: request code {0}, \
                    request message {1}'.format(r.status_code, r.content)
        raise Exception(msg)
    clusters = json.loads(r.content)
    return clusters['items']


def cluster_exists(ambari_url, user, password, cluster_name):
    clusters = get_clusters(ambari_url, user, password)
    return cluster_name in [item['Clusters']['cluster_name'] for item in clusters]


def set_cluster_state(ambari_url, user, password, cluster_name, cluster_state):
    path = '/api/v1/clusters/{0}/services'.format(cluster_name)
    request = {"RequestInfo": {"context": "Setting cluster state"},
               "Body": {"ServiceInfo": {"state": "{0}".format(cluster_state)}}}
    payload = json.dumps(request)
    r = put(ambari_url, user, password, path, payload)
    if r.status_code not in [202, 200]:
        msg = 'Could not set cluster state: request code {0}, \
                    request message {1}'.format(r.status_code, r.content)
        raise Exception(msg)
    return r


def create_cluster(ambari_url, user, password, cluster_name, blueprint_name, configurations, hosts_json):
    path = '/api/v1/clusters/{0}'.format(cluster_name)
    data = json.dumps({'blueprint': blueprint_name, 'configurations': configurations, 'host_groups': hosts_json})
    f = open('cluster.log', 'w')
    f.write(data)
    f.close()
    r = post(ambari_url, user, password, path, data)
    if r.status_code != 202:
        msg = 'Could not create cluster: request code {0}, \
                    request message {1}'.format(r.status_code, r.content)
        raise Exception(msg)
    return r


def get_request_status(ambari_url, user, password, cluster_name, request_id):
    path = '/api/v1/clusters/{0}/requests/{1}'.format(cluster_name, request_id)
    r = get(ambari_url, user, password, path)
    if r.status_code != 200:
        msg = 'Could not get cluster request status: request code {0}, \
                    request message {1}'.format(r.status_code, r.content)
        raise Exception(msg)
    service = json.loads(r.content)
    return service['Requests']['request_status']


def wait_for_request_complete(ambari_url, user, password, cluster_name, request_id, sleep_time):
    while True:
        status = get_request_status(ambari_url, user, password, cluster_name, request_id)
        if status == 'COMPLETED':
            return status
        elif status in ['FAILED', 'TIMEDOUT', 'ABORTED', 'SKIPPED_FAILED']:
            return status
        else:
            time.sleep(sleep_time)


def can_delete_cluster(ambari_url, user, password, cluster_name):
    path = '/api/v1/clusters/{0}/services?ServiceInfo/state=STARTED'.format(cluster_name)
    r = get(ambari_url, user, password, path)
    items = json.loads(r.content)['items']
    return len(items) > 0


def get_blueprints(ambari_url, user, password):
    path = '/api/v1/blueprints'
    r = get(ambari_url, user, password, path)
    if r.status_code != 200:
        msg = 'Could not get blueprint list: request code {0}, \
                    request message {1}'.format(r.status_code, r.content)
        raise Exception(msg)

    services = json.loads(r.content)
    return services['items']


def create_blueprint(ambari_url, user, password, blueprint_name, blueprint_data):
    data = json.dumps(blueprint_data)
    f = open('blueprint.log', 'w')
    f.write(data)
    f.close()
    path = "/api/v1/blueprints/" + blueprint_name
    r = post(ambari_url, user, password, path, data)
    if r.status_code != 201:
        msg = 'Could not create blueprint: request code {0}, \
                    request message {1}'.format(r.status_code, r.content)
        raise Exception(msg)
    return r


def blueprint_exists(ambari_url, user, password, blueprint_name):
    blueprints = get_blueprints(ambari_url, user, password)
    return blueprint_name in [item['Blueprints']['blueprint_name'] for item in blueprints]


def delete_cluster(ambari_url, user, password, cluster_name):
    path = '/api/v1/clusters/{0}'.format(cluster_name)
    r = delete(ambari_url, user, password, path)
    if r.status_code != 200:
        msg = 'Could not delete cluster: request code {0}, \
                    request message {1}'.format(r.status_code, r.content)
        raise Exception(msg)
    return r


def get(ambari_url, user, password, path):
    r = requests.get(ambari_url + path, auth=(user, password))
    return r


def put(ambari_url, user, password, path, data):
    headers = {'X-Requested-By': 'ambari'}
    r = requests.put(ambari_url + path, data=data, auth=(user, password), headers=headers)
    return r


def post(ambari_url, user, password, path, data):
    headers = {'X-Requested-By': 'ambari'}
    r = requests.post(ambari_url + path, data=data, auth=(user, password), headers=headers)
    return r


def delete(ambari_url, user, password, path):
    headers = {'X-Requested-By': 'ambari'}
    r = requests.delete(ambari_url + path, auth=(user, password), headers=headers)
    return r


def blueprint_var_to_ambari_converter(blueprint_var):
    groups = blueprint_var['groups']
    new_groups = []
    host_map = []
    for group in groups:
        components = []
        for component in group['components']:
            components.append({'name': component})
        group['components'] = components
        hosts = group.pop('hosts')
        new_groups.append(group)
        this_host_map = dict()
        this_host_map['name'] = group['name']
        this_host_list = [{'fqdn': host} for host in hosts]
        this_host_map['hosts'] = this_host_list
        host_map.append(this_host_map)
    blueprint = dict()
    blueprint['configurations'] = blueprint_var['required_configurations']
    blueprint['host_groups'] = new_groups
    blueprint['Blueprints'] = {'stack_name': blueprint_var['stack_name'], 'stack_version': blueprint_var['stack_version']}
    return blueprint, host_map

from ansible.module_utils.basic import *
if __name__ == '__main__':
    main()
