diff --git a/lib/ansible/modules/cloud/cloudstack/cs_host.py b/lib/ansible/modules/cloud/cloudstack/cs_host.py new file mode 100644 index 00000000000..550e0af1b5a --- /dev/null +++ b/lib/ansible/modules/cloud/cloudstack/cs_host.py @@ -0,0 +1,546 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2016, René Moser +# +# 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 . + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'version': '1.0'} + +DOCUMENTATION = ''' +--- +module: cs_host +short_description: Manages hosts on Apache CloudStack based clouds. +description: + - Create, update and remove hosts. +version_added: "2.3" +author: "René Moser (@resmo)" +options: + name: + description: + - Name of the host. + required: true + aliases: [ 'url' ] + username: + description: + - Username for the host. + - Required if C(state=present) and host does not yet exist. + required: false + default: null + password: + description: + - Password for the host. + - Required if C(state=present) and host does not yet exist. + required: false + default: null + pod: + description: + - Name of the pod. + - Required if C(state=present) and host does not yet exist. + required: false + default: null + cluster: + description: + - Name of the cluster. + required: false + default: null + hypervisor: + description: + - Name of the cluster. + - Required if C(state=present) and host does not yet exist. + choices: [ 'KVM', 'VMware', 'BareMetal', 'XenServer', 'LXC', 'HyperV', 'UCS', 'OVM', 'Simulator' ] + required: false + default: null + allocation_state: + description: + - Allocation state of the host. + choices: [ 'enabled', 'disabled' ] + required: false + default: null + host_tags: + description: + - Tags of the host. + required: false + default: null + state: + description: + - State of the host. + required: false + default: 'present' + choices: [ 'present', 'absent' ] + zone: + description: + - Name of the zone in which the host should be deployed. + - If not set, default zone is used. + required: false + default: null +extends_documentation_fragment: cloudstack +''' + +EXAMPLES = ''' +# Ensure a host is present but disabled +- local_action: + module: cs_host + name: ix-pod01-esx01.example.com + cluster: vcenter.example.com/ch-zrh-ix/pod01-cluster01 + pod: pod01 + zone: ch-zrh-ix-01 + hypervisor: VMware + allocation_state: disabled + host_tags: + - perf + - gpu + +# Ensure an existing host is disabled +- local_action: + module: cs_host + name: ix-pod01-esx01.example.com + zone: ch-zrh-ix-01 + allocation_state: disabled + +# Ensure an existing host is disabled +- local_action: + module: cs_host + name: ix-pod01-esx01.example.com + zone: ch-zrh-ix-01 + allocation_state: enabled + +# Ensure a host is absent +- local_action: + module: cs_host + name: ix-pod01-esx01.example.com + zone: ch-zrh-ix-01 + state: absent +''' + +RETURN = ''' +--- +capabilities: + description: Capabilities of the host. + returned: success + type: string + sample: hvm +cluster: + description: Cluster of the host. + returned: success + type: string + sample: vcenter.example.com/zone/cluster01 +cluster_type: + description: Type of the cluster of the host. + returned: success + type: string + sample: ExternalManaged +cpu_allocated: + description: Amount in percent of the host's CPU currently allocated. + returned: success + type: string + sample: 166.25% +cpu_number: + description: Number of CPUs of the host. + returned: success + type: string + sample: 24 +cpu_sockets: + description: Number of CPU sockets of the host. + returned: success + type: int + sample: 2 +cpu_speed: + description: CPU speed in Mhz + returned: success + type: int + sample: 1999 +cpu_used: + description: Amount of the host's CPU currently used. + returned: success + type: string + sample: 33.6% +cpu_with_overprovisioning: + description: Amount of the host's CPU after applying the cpu.overprovisioning.factor. + returned: success + type: string + sample: 959520.0 +created: + description: Date when the host was created. + returned: success + type: string + sample: 2015-05-03T15:05:51+0200 +disconnected: + description: Date when the host was disconnected. + returned: success + type: string + sample: 2015-05-03T15:05:51+0200 +disk_size_allocated: + description: Host's currently allocated disk size. + returned: success + type: int + sample: 2593 +disk_size_total: + description: Total disk size of the host + returned: success + type: int + sample: 259300 +events: + description: Events available for the host + returned: success + type: string + sample: "Ping; HostDown; AgentConnected; AgentDisconnected; PingTimeout; ShutdownRequested; Remove; StartAgentRebalance; ManagementServerDown" +ha_host: + description: Whether the host is a HA host. + returned: success + type: bool + sample: false +has_enough_capacity: + description: Whether the host has enough CPU and RAM capacity to migrate a VM to it. + returned: success + type: bool + sample: true +host_tags: + description: Comma-separated list of tags for the host. + returned: success + type: string + sample: "perf" +hypervisor: + description: Host's hypervisor. + returned: success + type: string + sample: VMware +hypervisor_version: + description: Hypervisor version. + returned: success + type: string + sample: 5.1 +ip_address: + description: IP address of the host + returned: success + type: string + sample: 10.10.10.1 +is_local_storage_active: + description: Whether the local storage is available or not. + returned: success + type: bool + sample: false +last_pinged: + description: Date and time the host was last pinged. + returned: success + type: string + sample: "1970-01-17T17:27:32+0100" +management_server_id: + description: Management server ID of the host. + returned: success + type: int + sample: 345050593418 +memory_allocated: + description: Amount of the host's memory currently allocated. + returned: success + type: int + sample: 69793218560 +memory_total: + description: Total of memory of the host. + returned: success + type: int + sample: 206085263360 +memory_used: + description: Amount of the host's memory currently used. + returned: success + type: int + sample: 65504776192 +name: + description: Name of the host. + returned: success + type: string + sample: esx32.example.com +network_kbs_read: + description: Incoming network traffic on the host. + returned: success + type: int + sample: 0 +network_kbs_write: + description: Outgoing network traffic on the host. + returned: success + type: int + sample: 0 +os_category: + description: OS category name of the host. + returned: success + type: string + sample: ... +out_of_band_management: + description: Host out-of-band management information. + returned: success + type: string + sample: ... +pod: + description: Pod name of the host. + returned: success + type: string + sample: Pod01 +removed: + description: Date and time the host was removed. + returned: success + type: string + sample: "1970-01-17T17:27:32+0100" +resource_state: + description: Resource state of the host. + returned: success + type: string + sample: Enabled +state: + description: State of the host. + returned: success + type: string + sample: Up +suitable_for_migration: + description: | + Whether this host is suitable (has enough capacity and satisfies all + conditions like hosttags, max guests VM limit, etc) to migrate a VM to it + or not. + returned: success + type: string + sample: true +host_type: + description: Type of the host. + returned: success + type: string + sample: Routing +host_version: + description: Version of the host. + returned: success + type: string + sample: 4.5.2 +gpu_group: + description: GPU cards present in the host. + returned: success + type: list + sample: [] +zone: + description: Zone of the host. + returned: success + type: string + sample: zone01 +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.cloudstack import AnsibleCloudStack, CloudStackException, cs_argument_spec, cs_required_together, CS_HYPERVISORS + + +class AnsibleCloudStackHost(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackHost, self).__init__(module) + self.returns = { + 'averageload': 'average_load', + 'capabilities': 'capabilities', + 'clustername': 'cluster', + 'clustertype': 'cluster_type', + 'cpuallocated': 'cpu_allocated', + 'cpunumber': 'cpu_number', + 'cpusockets': 'cpu_sockets', + 'cpuspeed': 'cpu_speed', + 'cpuused': 'cpu_used', + 'cpuwithoverprovisioning': 'cpu_with_overprovisioning', + 'disconnected': 'disconnected', + 'details': 'details', + 'disksizeallocated': 'disk_size_allocated', + 'disksizetotal': 'disk_size_total', + 'events': 'events', + 'hahost': 'ha_host', + 'hasenoughcapacity': 'has_enough_capacity', + 'hosttags': 'host_tags', + 'hypervisor': 'hypervisor', + 'hypervisorversion': 'hypervisor_version', + 'ipaddress': 'ip_address', + 'islocalstorageactive': 'is_local_storage_active', + 'lastpinged': 'last_pinged', + 'managementserverid': 'management_server_id', + 'memoryallocated': 'memory_allocated', + 'memorytotal': 'memory_total', + 'memoryused': 'memory_used', + 'networkkbsread': 'network_kbs_read', + 'networkkbswrite': 'network_kbs_write', + 'oscategoryname': 'os_category', + 'outofbandmanagement': 'out_of_band_management', + 'podname': 'pod', + 'removed': 'removed', + 'resourcestate': 'resource_state', + 'suitableformigration': 'suitable_for_migration', + 'type': 'host_type', + 'version': 'host_version', + 'gpugroup': 'gpu_group', + + } + self.allocation_states = { + 'enabled': 'enable', + 'disabled': 'disable', + } + + def get_pod(self, key=None): + pod_name = self.module.params.get('pod') + if not pod_name: + return None + args = { + 'name': pod_name, + 'zoneid': self.get_zone(key='id'), + } + pods = self.cs.listPods(**args) + if pods: + return self._get_by_key(key, pods['pod'][0]) + self.module.fail_json(msg="Pod %s not found" % pod_name) + + def get_cluster(self, key=None): + cluster_name = self.module.params.get('cluster') + if not cluster_name: + return None + args = { + 'name': cluster_name, + 'zoneid': self.get_zone(key='id'), + } + clusters = self.cs.listClusters(**args) + if clusters: + return self._get_by_key(key, clusters['cluster'][0]) + self.module.fail_json(msg="Cluster %s not found" % cluster_name) + + def get_host_tags(self): + host_tags = self.module.params.get('host_tags') + if host_tags is None: + return None + return ','.join(host_tags) + + def get_allocation_state(self): + allocation_state = self.module.params.get('allocation_state') + if allocation_state is None: + return None + return self.allocation_states[allocation_state] + + def get_host(self): + host = None + args = { + 'zoneid': self.get_zone(key='id'), + 'name': self.module.params.get('name'), + } + hosts = self.cs.listHosts(**args) + if hosts: + host = hosts['host'][0] + return host + + def present_host(self): + host = self.get_host() + if not host: + host = self._create_host(host) + else: + host = self._update_host(host) + return host + + def _create_host(self, host): + required_params = [ + 'password', + 'username', + 'hypervisor', + 'pod', + ] + self.module.fail_on_missing_params(required_params=required_params) + self.result['changed'] = True + args = { + 'hypervisor': self.module.params.get('hypervisor'), + 'url': self.module.params.get('name'), + 'username': self.module.params.get('username'), + 'password': self.module.params.get('password'), + 'podid': self.get_pod(key='id'), + 'zoneid': self.get_zone(key='id'), + 'clusterid': self.get_cluster(key='id'), + 'allocationstate': self.get_allocation_state(), + 'hosttags': self.get_host_tags(), + } + if not self.module.check_mode: + host = self.cs.addHost(**args) + if 'errortext' in host: + self.module.fail_json(msg="Failed: '%s'" % host['errortext']) + host = host['host'] + return host + + def _update_host(self, host): + args = { + 'id': host['id'], + 'hosttags': self.get_host_tags(), + 'allocationstate': self.module.params.get('allocation_state'), + } + host['allocationstate'] = host['resourcestate'].lower() + if self.has_changed(args, host): + args['allocationstate'] = self.get_allocation_state() + self.result['changed'] = True + if not self.module.check_mode: + host = self.cs.updateHost(**args) + if 'errortext' in host: + self.module.fail_json(msg="Failed: '%s'" % host['errortext']) + host = host['host'] + return host + + def absent_host(self): + host = self.get_host() + if host: + self.result['changed'] = True + args = { + 'id': host['id'], + } + if not self.module.check_mode: + res = self.cs.deleteHost(**args) + if 'errortext' in res: + self.module.fail_json(msg="Failed: '%s'" % res['errortext']) + return host + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True, aliases=['url']), + password=dict(default=None, not_log=True), + username=dict(default=None), + hypervisor=dict(choices=CS_HYPERVISORS, default=None), + allocation_state=dict(default=None), + pod=dict(default=None), + cluster=dict(default=None), + host_tags=dict(default=None, type='list'), + zone=dict(default=None), + state=dict(choices=['present', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + try: + acs_host = AnsibleCloudStackHost(module) + + state = module.params.get('state') + if state == 'absent': + host = acs_host.absent_host() + else: + host = acs_host.present_host() + + result = acs_host.get_result(host) + + except CloudStackException as e: + module.fail_json(msg='CloudStackException: %s' % str(e)) + + module.exit_json(**result) + + +if __name__ == '__main__': + main()