#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 Red Hat, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
#

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


DOCUMENTATION = '''
---
module: ovirt_cluster
short_description: Module to manage clusters in oVirt/RHV
version_added: "2.3"
author: "Ondra Machacek (@machacekondra)"
description:
    - "Module to manage clusters in oVirt/RHV"
options:
    name:
        description:
            - "Name of the cluster to manage."
        required: true
    state:
        description:
            - "Should the cluster be present or absent"
        choices: ['present', 'absent']
        default: present
    data_center:
        description:
            - "Datacenter name where cluster reside."
    description:
        description:
            - "Description of the cluster."
    comment:
        description:
            - "Comment of the cluster."
    network:
        description:
            - "Management network of cluster to access cluster hosts."
    ballooning:
        description:
            - "If I(True) enable memory balloon optimization. Memory balloon is used to
               re-distribute / reclaim the host memory based on VM needs
               in a dynamic way."
    virt:
        description:
            - "If I(True), hosts in this cluster will be used to run virtual machines."
    gluster:
        description:
            - "If I(True), hosts in this cluster will be used as Gluster Storage
               server nodes, and not for running virtual machines."
            - "By default the cluster is created for virtual machine hosts."
    threads_as_cores:
        description:
            - "If I(True) the exposed host threads would be treated as cores
               which can be utilized by virtual machines."
    ksm:
        description:
            - "I I(True) MoM enables to run Kernel Same-page Merging I(KSM) when
               necessary and when it can yield a memory saving benefit that
               outweighs its CPU cost."
    ksm_numa:
        description:
            - "If I(True) enables KSM C(ksm) for best berformance inside NUMA nodes."
    ha_reservation:
        description:
            - "If I(True) enable the oVirt/RHV to monitor cluster capacity for highly
               available virtual machines."
    trusted_service:
        description:
            - "If (True) enable integration with an OpenAttestation server."
    vm_reason:
        description:
            - "If I(True) enable an optional reason field when a virtual machine
               is shut down from the Manager, allowing the administrator to
               provide an explanation for the maintenance."
    host_reason:
        description:
            - "If I(True) enable an optional reason field when a host is placed
               into maintenance mode from the Manager, allowing the administrator
               to provide an explanation for the maintenance."
    memory_policy:
        description:
            - "I(disabled) - Disables memory page sharing."
            - "I(server) - Sets the memory page sharing threshold to 150% of the system memory on each host."
            - "I(desktop) - Sets the memory page sharing threshold to 200% of the system memory on each host."
        choices: ['disabled', 'server', 'desktop']
    rng_sources:
        description:
            - "List that specify the random number generator devices that all hosts in the cluster will use."
            - "Supported generators are: I(hwrng) and I(random)."
    spice_proxy:
        description:
            - "The proxy by which the SPICE client will connect to virtual machines."
            - "The address must be in the following format: I(protocol://[host]:[port])"
    fence_enabled:
        description:
            - "If I(True) enables fencing on the cluster."
            - "Fencing is enabled by default."
    fence_skip_if_sd_active:
        description:
            - "If I(True) any hosts in the cluster that are Non Responsive
               and still connected to storage will not be fenced."
    fence_skip_if_connectivity_broken:
        description:
            - "If I(True) fencing will be temporarily disabled if the percentage
               of hosts in the cluster that are experiencing connectivity issues
               is greater than or equal to the defined threshold."
            - "The threshold can be specified by C(fence_connectivity_threshold)."
    fence_connectivity_threshold:
        description:
            - "The threshold used by C(fence_skip_if_connectivity_broken)."
    resilience_policy:
        description:
            - "The resilience policy defines how the virtual machines are prioritized in the migration."
            - "Following values are supported:"
            - "C(do_not_migrate) -  Prevents virtual machines from being migrated. "
            - "C(migrate) - Migrates all virtual machines in order of their defined priority."
            - "C(migrate_highly_available) - Migrates only highly available virtual machines to prevent overloading other hosts."
        choices: ['do_not_migrate', 'migrate', 'migrate_highly_available']
    migration_bandwidth:
        description:
            - "The bandwidth settings define the maximum bandwidth of both outgoing and incoming migrations per host."
            - "Following bandwidth options are supported:"
            - "C(auto) - Bandwidth is copied from the I(rate limit) [Mbps] setting in the data center host network QoS."
            - "C(hypervisor_default) - Bandwidth is controlled by local VDSM setting on sending host."
            - "C(custom) - Defined by user (in Mbps)."
        choices: ['auto', 'hypervisor_default', 'custom']
    migration_bandwidth_limit:
        description:
            - "Set the I(custom) migration bandwidth limit."
            - "This parameter is used only when C(migration_bandwidth) is I(custom)."
    migration_auto_converge:
        description:
            - "If I(True) auto-convergence is used during live migration of virtual machines."
            - "Used only when C(migration_policy) is set to I(legacy)."
            - "Following options are supported:"
            - "C(true) - Override the global setting to I(true)."
            - "C(false) - Override the global setting to I(false)."
            - "C(inherit) - Use value which is set globally."
        choices: ['true', 'false', 'inherit']
    migration_compressed:
        description:
            - "If I(True) compression is used during live migration of the virtual machine."
            - "Used only when C(migration_policy) is set to I(legacy)."
            - "Following options are supported:"
            - "C(true) - Override the global setting to I(true)."
            - "C(false) - Override the global setting to I(false)."
            - "C(inherit) - Use value which is set globally."
        choices: ['true', 'false', 'inherit']
    migration_policy:
        description:
            - "A migration policy defines the conditions for live migrating
               virtual machines in the event of host failure."
            - "Following policies are supported:"
            - "C(legacy) - Legacy behavior of 3.6 version."
            - "C(minimal_downtime) - Virtual machines should not experience any significant downtime."
            - "C(suspend_workload) - Virtual machines may experience a more significant downtime."
            - "C(post_copy) - Virtual machines should not experience any significant downtime.
               If the VM migration is not converging for a long time, the migration will be switched to post-copy.
               Added in version I(2.4)."
        choices: ['legacy', 'minimal_downtime', 'suspend_workload', 'post_copy']
    serial_policy:
        description:
            - "Specify a serial number policy for the virtual machines in the cluster."
            - "Following options are supported:"
            - "C(vm) - Sets the virtual machine's UUID as its serial number."
            - "C(host) - Sets the host's UUID as the virtual machine's serial number."
            - "C(custom) - Allows you to specify a custom serial number in C(serial_policy_value)."
    serial_policy_value:
        description:
            - "Allows you to specify a custom serial number."
            - "This parameter is used only when C(serial_policy) is I(custom)."
    scheduling_policy:
        description:
            - "Name of the scheduling policy to be used for cluster."
    cpu_arch:
        description:
            - "CPU architecture of cluster."
        choices: ['x86_64', 'ppc64', 'undefined']
    cpu_type:
        description:
            - "CPU codename. For example I(Intel SandyBridge Family)."
    switch_type:
        description:
            - "Type of switch to be used by all networks in given cluster.
               Either I(legacy) which is using linux brigde or I(ovs) using
               Open vSwitch."
        choices: ['legacy', 'ovs']
    compatibility_version:
        description:
            - "The compatibility version of the cluster. All hosts in this
               cluster must support at least this compatibility version."
    mac_pool:
        description:
            - "MAC pool to be used by this cluster."
            - "C(Note:)"
            - "This is supported since oVirt version 4.1."
        version_added: 2.4
    external_network_providers:
        description:
            - "List of references to the external network providers available
               in the cluster. If the automatic deployment of the external
               network provider is supported, the networks of the referenced
               network provider are available on every host in the cluster."
            - "External network provider is described by following dictionary:"
            - "C(name) - Name of the external network provider. Either C(name)
               or C(id) is required."
            - "C(id) - ID of the external network provider. Either C(name) or
               C(id) is required."
            - "This is supported since oVirt version 4.2."
        version_added: 2.5
extends_documentation_fragment: ovirt
'''

EXAMPLES = '''
# Examples don't contain auth parameter for simplicity,
# look at ovirt_auth module to see how to reuse authentication:

# Create cluster
- ovirt_cluster:
    data_center: mydatacenter
    name: mycluster
    cpu_type: Intel SandyBridge Family
    description: mycluster
    compatibility_version: 4.0

# Create virt service cluster:
- ovirt_cluster:
    data_center: mydatacenter
    name: mycluster
    cpu_type: Intel Nehalem Family
    description: mycluster
    switch_type: legacy
    compatibility_version: 4.0
    ballooning: true
    gluster: false
    threads_as_cores: true
    ha_reservation: true
    trusted_service: false
    host_reason: false
    vm_reason: true
    ksm_numa: true
    memory_policy: server
    rng_sources:
      - hwrng
      - random

# Create cluster with default network provider
- ovirt_cluster:
    name: mycluster
    data_center: Default
    cpu_type: Intel SandyBridge Family
    external_network_providers:
      - name: ovirt-provider-ovn

# Remove cluster
- ovirt_cluster:
    state: absent
    name: mycluster
'''

RETURN = '''
id:
    description: ID of the cluster which is managed
    returned: On success if cluster is found.
    type: str
    sample: 7de90f31-222c-436c-a1ca-7e655bd5b60c
cluster:
    description: "Dictionary of all the cluster attributes. Cluster attributes can be found on your oVirt/RHV instance
                  at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/cluster."
    type: dict
    returned: On success if cluster is found.
'''

import traceback

try:
    import ovirtsdk4.types as otypes
except ImportError:
    pass

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ovirt import (
    BaseModule,
    check_sdk,
    create_connection,
    equal,
    ovirt_full_argument_spec,
    search_by_name,
    get_id_by_name,
)


class ClustersModule(BaseModule):

    def __get_major(self, full_version):
        if full_version is None:
            return None
        if isinstance(full_version, otypes.Version):
            return full_version.major
        return int(full_version.split('.')[0])

    def __get_minor(self, full_version):
        if full_version is None:
            return None
        if isinstance(full_version, otypes.Version):
            return full_version.minor
        return int(full_version.split('.')[1])

    def param(self, name, default=None):
        return self._module.params.get(name, default)

    def _get_memory_policy(self):
        memory_policy = self.param('memory_policy')
        if memory_policy == 'desktop':
            return 200
        elif memory_policy == 'server':
            return 150
        elif memory_policy == 'disabled':
            return 100

    def _get_policy_id(self):
        # These are hardcoded IDs, once there is API, please fix this.
        # legacy - 00000000-0000-0000-0000-000000000000
        # minimal downtime - 80554327-0569-496b-bdeb-fcbbf52b827b
        # suspend workload if needed - 80554327-0569-496b-bdeb-fcbbf52b827c
        # post copy - a7aeedb2-8d66-4e51-bb22-32595027ce71
        migration_policy = self.param('migration_policy')
        if migration_policy == 'legacy':
            return '00000000-0000-0000-0000-000000000000'
        elif migration_policy == 'minimal_downtime':
            return '80554327-0569-496b-bdeb-fcbbf52b827b'
        elif migration_policy == 'suspend_workload':
            return '80554327-0569-496b-bdeb-fcbbf52b827c'
        elif migration_policy == 'post_copy':
            return 'a7aeedb2-8d66-4e51-bb22-32595027ce71'

    def _get_sched_policy(self):
        sched_policy = None
        if self.param('scheduling_policy'):
            sched_policies_service = self._connection.system_service().scheduling_policies_service()
            sched_policy = search_by_name(sched_policies_service, self.param('scheduling_policy'))
            if not sched_policy:
                raise Exception("Scheduling policy '%s' was not found" % self.param('scheduling_policy'))

        return sched_policy

    def _get_mac_pool(self):
        mac_pool = None
        if self._module.params.get('mac_pool'):
            mac_pool = search_by_name(
                self._connection.system_service().mac_pools_service(),
                self._module.params.get('mac_pool'),
            )

        return mac_pool

    def _get_external_network_providers(self):
        return self.param('external_network_providers') or []

    def _get_external_network_provider_id(self, external_provider):
        return external_provider.get('id') or get_id_by_name(
            self._connection.system_service().openstack_network_providers_service(),
            external_provider.get('name')
        )

    def _get_external_network_providers_entity(self):
        if self.param('external_network_providers') is not None:
            return [otypes.ExternalProvider(id=self._get_external_network_provider_id(external_provider))
                    for external_provider in self.param('external_network_providers')]

    def build_entity(self):
        sched_policy = self._get_sched_policy()
        return otypes.Cluster(
            name=self.param('name'),
            comment=self.param('comment'),
            description=self.param('description'),
            ballooning_enabled=self.param('ballooning'),
            gluster_service=self.param('gluster'),
            virt_service=self.param('virt'),
            threads_as_cores=self.param('threads_as_cores'),
            ha_reservation=self.param('ha_reservation'),
            trusted_service=self.param('trusted_service'),
            optional_reason=self.param('vm_reason'),
            maintenance_reason_required=self.param('host_reason'),
            scheduling_policy=otypes.SchedulingPolicy(
                id=sched_policy.id,
            ) if sched_policy else None,
            serial_number=otypes.SerialNumber(
                policy=otypes.SerialNumberPolicy(self.param('serial_policy')),
                value=self.param('serial_policy_value'),
            ) if (
                self.param('serial_policy') is not None or
                self.param('serial_policy_value') is not None
            ) else None,
            migration=otypes.MigrationOptions(
                auto_converge=otypes.InheritableBoolean(
                    self.param('migration_auto_converge'),
                ) if self.param('migration_auto_converge') else None,
                bandwidth=otypes.MigrationBandwidth(
                    assignment_method=otypes.MigrationBandwidthAssignmentMethod(
                        self.param('migration_bandwidth'),
                    ) if self.param('migration_bandwidth') else None,
                    custom_value=self.param('migration_bandwidth_limit'),
                ) if (
                    self.param('migration_bandwidth') or
                    self.param('migration_bandwidth_limit')
                ) else None,
                compressed=otypes.InheritableBoolean(
                    self.param('migration_compressed'),
                ) if self.param('migration_compressed') else None,
                policy=otypes.MigrationPolicy(
                    id=self._get_policy_id()
                ) if self.param('migration_policy') else None,
            ) if (
                self.param('migration_bandwidth') is not None or
                self.param('migration_bandwidth_limit') is not None or
                self.param('migration_auto_converge') is not None or
                self.param('migration_compressed') is not None or
                self.param('migration_policy') is not None
            ) else None,
            error_handling=otypes.ErrorHandling(
                on_error=otypes.MigrateOnError(
                    self.param('resilience_policy')
                ),
            ) if self.param('resilience_policy') else None,
            fencing_policy=otypes.FencingPolicy(
                enabled=self.param('fence_enabled'),
                skip_if_connectivity_broken=otypes.SkipIfConnectivityBroken(
                    enabled=self.param('fence_skip_if_connectivity_broken'),
                    threshold=self.param('fence_connectivity_threshold'),
                ) if (
                    self.param('fence_skip_if_connectivity_broken') is not None or
                    self.param('fence_connectivity_threshold') is not None
                ) else None,
                skip_if_sd_active=otypes.SkipIfSdActive(
                    enabled=self.param('fence_skip_if_sd_active'),
                ) if self.param('fence_skip_if_sd_active') is not None else None,
            ) if (
                self.param('fence_enabled') is not None or
                self.param('fence_skip_if_sd_active') is not None or
                self.param('fence_skip_if_connectivity_broken') is not None or
                self.param('fence_connectivity_threshold') is not None
            ) else None,
            display=otypes.Display(
                proxy=self.param('spice_proxy'),
            ) if self.param('spice_proxy') else None,
            required_rng_sources=[
                otypes.RngSource(rng) for rng in self.param('rng_sources')
            ] if self.param('rng_sources') else None,
            memory_policy=otypes.MemoryPolicy(
                over_commit=otypes.MemoryOverCommit(
                    percent=self._get_memory_policy(),
                ),
            ) if self.param('memory_policy') else None,
            ksm=otypes.Ksm(
                enabled=self.param('ksm'),
                merge_across_nodes=not self.param('ksm_numa'),
            ) if (
                self.param('ksm_numa') is not None or
                self.param('ksm') is not None
            ) else None,
            data_center=otypes.DataCenter(
                name=self.param('data_center'),
            ) if self.param('data_center') else None,
            management_network=otypes.Network(
                name=self.param('network'),
            ) if self.param('network') else None,
            cpu=otypes.Cpu(
                architecture=otypes.Architecture(
                    self.param('cpu_arch')
                ) if self.param('cpu_arch') else None,
                type=self.param('cpu_type'),
            ) if (
                self.param('cpu_arch') or self.param('cpu_type')
            ) else None,
            version=otypes.Version(
                major=self.__get_major(self.param('compatibility_version')),
                minor=self.__get_minor(self.param('compatibility_version')),
            ) if self.param('compatibility_version') else None,
            switch_type=otypes.SwitchType(
                self.param('switch_type')
            ) if self.param('switch_type') else None,
            mac_pool=otypes.MacPool(
                id=get_id_by_name(self._connection.system_service().mac_pools_service(), self.param('mac_pool'))
            ) if self.param('mac_pool') else None,
            external_network_providers=self._get_external_network_providers_entity(),
        )

    def _matches_entity(self, item, entity):
        return equal(item.get('id'), entity.id) and equal(item.get('name'), entity.name)

    def _update_check_external_network_providers(self, entity):
        if self.param('external_network_providers') is None:
            return True
        if entity.external_network_providers is None:
            return not self.param('external_network_providers')
        entity_providers = self._connection.follow_link(entity.external_network_providers)
        entity_provider_ids = [provider.id for provider in entity_providers]
        entity_provider_names = [provider.name for provider in entity_providers]
        for provider in self._get_external_network_providers():
            if provider.get('id'):
                if provider.get('id') not in entity_provider_ids:
                    return False
            elif provider.get('name') and provider.get('name') not in entity_provider_names:
                return False
        for entity_provider in entity_providers:
            if not any([self._matches_entity(provider, entity_provider)
                        for provider in self._get_external_network_providers()]):
                return False
        return True

    def update_check(self, entity):
        sched_policy = self._get_sched_policy()
        migration_policy = getattr(entity.migration, 'policy', None)
        cluster_cpu = getattr(entity, 'cpu', dict())
        return (
            equal(self.param('comment'), entity.comment) and
            equal(self.param('description'), entity.description) and
            equal(self.param('switch_type'), str(entity.switch_type)) and
            equal(self.param('cpu_arch'), str(getattr(cluster_cpu, 'architecture', None))) and
            equal(self.param('cpu_type'), getattr(cluster_cpu, 'type', None)) and
            equal(self.param('ballooning'), entity.ballooning_enabled) and
            equal(self.param('gluster'), entity.gluster_service) and
            equal(self.param('virt'), entity.virt_service) and
            equal(self.param('threads_as_cores'), entity.threads_as_cores) and
            equal(self.param('ksm_numa'), not entity.ksm.merge_across_nodes) and
            equal(self.param('ksm'), entity.ksm.enabled) and
            equal(self.param('ha_reservation'), entity.ha_reservation) and
            equal(self.param('trusted_service'), entity.trusted_service) and
            equal(self.param('host_reason'), entity.maintenance_reason_required) and
            equal(self.param('vm_reason'), entity.optional_reason) and
            equal(self.param('spice_proxy'), getattr(entity.display, 'proxy', None)) and
            equal(self.param('fence_enabled'), entity.fencing_policy.enabled) and
            equal(self.param('fence_skip_if_sd_active'), entity.fencing_policy.skip_if_sd_active.enabled) and
            equal(self.param('fence_skip_if_connectivity_broken'), entity.fencing_policy.skip_if_connectivity_broken.enabled) and
            equal(self.param('fence_connectivity_threshold'), entity.fencing_policy.skip_if_connectivity_broken.threshold) and
            equal(self.param('resilience_policy'), str(entity.error_handling.on_error)) and
            equal(self.param('migration_bandwidth'), str(entity.migration.bandwidth.assignment_method)) and
            equal(self.param('migration_auto_converge'), str(entity.migration.auto_converge)) and
            equal(self.param('migration_compressed'), str(entity.migration.compressed)) and
            equal(self.param('serial_policy'), str(getattr(entity.serial_number, 'policy', None))) and
            equal(self.param('serial_policy_value'), getattr(entity.serial_number, 'value', None)) and
            equal(self.param('scheduling_policy'), getattr(self._connection.follow_link(entity.scheduling_policy), 'name', None)) and
            equal(self._get_policy_id(), getattr(migration_policy, 'id', None)) and
            equal(self._get_memory_policy(), entity.memory_policy.over_commit.percent) and
            equal(self.__get_minor(self.param('compatibility_version')), self.__get_minor(entity.version)) and
            equal(self.__get_major(self.param('compatibility_version')), self.__get_major(entity.version)) and
            equal(
                self.param('migration_bandwidth_limit') if self.param('migration_bandwidth') == 'custom' else None,
                entity.migration.bandwidth.custom_value
            ) and
            equal(
                sorted(self.param('rng_sources')) if self.param('rng_sources') else None,
                sorted([
                    str(source) for source in entity.required_rng_sources
                ])
            ) and
            equal(
                get_id_by_name(self._connection.system_service().mac_pools_service(), self.param('mac_pool'), raise_error=False),
                entity.mac_pool.id
            ) and
            self._update_check_external_network_providers(entity)
        )


def main():
    argument_spec = ovirt_full_argument_spec(
        state=dict(
            choices=['present', 'absent'],
            default='present',
        ),
        name=dict(default=None, required=True),
        ballooning=dict(default=None, type='bool', aliases=['balloon']),
        gluster=dict(default=None, type='bool'),
        virt=dict(default=None, type='bool'),
        threads_as_cores=dict(default=None, type='bool'),
        ksm_numa=dict(default=None, type='bool'),
        ksm=dict(default=None, type='bool'),
        ha_reservation=dict(default=None, type='bool'),
        trusted_service=dict(default=None, type='bool'),
        vm_reason=dict(default=None, type='bool'),
        host_reason=dict(default=None, type='bool'),
        memory_policy=dict(default=None, choices=['disabled', 'server', 'desktop'], aliases=['performance_preset']),
        rng_sources=dict(default=None, type='list'),
        spice_proxy=dict(default=None),
        fence_enabled=dict(default=None, type='bool'),
        fence_skip_if_sd_active=dict(default=None, type='bool'),
        fence_skip_if_connectivity_broken=dict(default=None, type='bool'),
        fence_connectivity_threshold=dict(default=None, type='int'),
        resilience_policy=dict(default=None, choices=['migrate_highly_available', 'migrate', 'do_not_migrate']),
        migration_bandwidth=dict(default=None, choices=['auto', 'hypervisor_default', 'custom']),
        migration_bandwidth_limit=dict(default=None, type='int'),
        migration_auto_converge=dict(default=None, choices=['true', 'false', 'inherit']),
        migration_compressed=dict(default=None, choices=['true', 'false', 'inherit']),
        migration_policy=dict(
            default=None,
            choices=['legacy', 'minimal_downtime', 'suspend_workload', 'post_copy']
        ),
        serial_policy=dict(default=None, choices=['vm', 'host', 'custom']),
        serial_policy_value=dict(default=None),
        scheduling_policy=dict(default=None),
        data_center=dict(default=None),
        description=dict(default=None),
        comment=dict(default=None),
        network=dict(default=None),
        cpu_arch=dict(default=None, choices=['ppc64', 'undefined', 'x86_64']),
        cpu_type=dict(default=None),
        switch_type=dict(default=None, choices=['legacy', 'ovs']),
        compatibility_version=dict(default=None),
        mac_pool=dict(default=None),
        external_network_providers=dict(default=None, type='list'),
    )
    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=True,
    )

    if module._name == 'ovirt_clusters':
        module.deprecate("The 'ovirt_clusters' module is being renamed 'ovirt_cluster'", version=2.8)

    check_sdk(module)

    try:
        auth = module.params.pop('auth')
        connection = create_connection(auth)
        clusters_service = connection.system_service().clusters_service()
        clusters_module = ClustersModule(
            connection=connection,
            module=module,
            service=clusters_service,
        )

        state = module.params['state']
        if state == 'present':
            ret = clusters_module.create()
        elif state == 'absent':
            ret = clusters_module.remove()

        module.exit_json(**ret)
    except Exception as e:
        module.fail_json(msg=str(e), exception=traceback.format_exc())
    finally:
        connection.close(logout=auth.get('token') is None)


if __name__ == "__main__":
    main()
