mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
628 lines
18 KiB
Python
628 lines
18 KiB
Python
|
#!/usr/bin/python
|
||
|
# -*- coding: utf-8 -*-
|
||
|
#
|
||
|
# (c) 2016, René Moser <mail@renemoser.net>
|
||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||
|
|
||
|
from __future__ import absolute_import, division, print_function
|
||
|
__metaclass__ = type
|
||
|
|
||
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||
|
'status': ['preview'],
|
||
|
'supported_by': 'community'}
|
||
|
|
||
|
|
||
|
DOCUMENTATION = '''
|
||
|
---
|
||
|
module: cs_host
|
||
|
short_description: Manages hosts on Apache CloudStack based clouds.
|
||
|
description:
|
||
|
- Create, update and remove hosts.
|
||
|
author: René Moser (@resmo)
|
||
|
options:
|
||
|
name:
|
||
|
description:
|
||
|
- Name of the host.
|
||
|
type: str
|
||
|
required: true
|
||
|
aliases: [ ip_address ]
|
||
|
url:
|
||
|
description:
|
||
|
- Url of the host used to create a host.
|
||
|
- If not provided, C(http://) and param I(name) is used as url.
|
||
|
- Only considered if I(state=present) and host does not yet exist.
|
||
|
type: str
|
||
|
username:
|
||
|
description:
|
||
|
- Username for the host.
|
||
|
- Required if I(state=present) and host does not yet exist.
|
||
|
type: str
|
||
|
password:
|
||
|
description:
|
||
|
- Password for the host.
|
||
|
- Required if I(state=present) and host does not yet exist.
|
||
|
type: str
|
||
|
pod:
|
||
|
description:
|
||
|
- Name of the pod.
|
||
|
- Required if I(state=present) and host does not yet exist.
|
||
|
type: str
|
||
|
cluster:
|
||
|
description:
|
||
|
- Name of the cluster.
|
||
|
type: str
|
||
|
hypervisor:
|
||
|
description:
|
||
|
- Name of the cluster.
|
||
|
- Required if I(state=present) and host does not yet exist.
|
||
|
- Possible values are C(KVM), C(VMware), C(BareMetal), C(XenServer), C(LXC), C(HyperV), C(UCS), C(OVM), C(Simulator).
|
||
|
type: str
|
||
|
allocation_state:
|
||
|
description:
|
||
|
- Allocation state of the host.
|
||
|
type: str
|
||
|
choices: [ enabled, disabled, maintenance ]
|
||
|
host_tags:
|
||
|
description:
|
||
|
- Tags of the host.
|
||
|
type: list
|
||
|
aliases: [ host_tag ]
|
||
|
state:
|
||
|
description:
|
||
|
- State of the host.
|
||
|
type: str
|
||
|
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.
|
||
|
type: str
|
||
|
extends_documentation_fragment:
|
||
|
- community.general.cloudstack
|
||
|
|
||
|
'''
|
||
|
|
||
|
EXAMPLES = '''
|
||
|
- name: Ensure a host is present but disabled
|
||
|
cs_host:
|
||
|
name: pod01.zone01.example.com
|
||
|
cluster: vcenter.example.com/zone01/cluster01
|
||
|
pod: pod01
|
||
|
zone: zone01
|
||
|
hypervisor: VMware
|
||
|
allocation_state: disabled
|
||
|
host_tags:
|
||
|
- perf
|
||
|
- gpu
|
||
|
delegate_to: localhost
|
||
|
|
||
|
- name: Ensure an existing host is disabled
|
||
|
cs_host:
|
||
|
name: pod01.zone01.example.com
|
||
|
zone: zone01
|
||
|
allocation_state: disabled
|
||
|
delegate_to: localhost
|
||
|
|
||
|
- name: Ensure an existing host is enabled
|
||
|
cs_host:
|
||
|
name: pod01.zone01.example.com
|
||
|
zone: zone01
|
||
|
allocation_state: enabled
|
||
|
delegate_to: localhost
|
||
|
|
||
|
- name: Ensure a host is absent
|
||
|
cs_host:
|
||
|
name: pod01.zone01.example.com
|
||
|
zone: zone01
|
||
|
state: absent
|
||
|
delegate_to: localhost
|
||
|
'''
|
||
|
|
||
|
RETURN = '''
|
||
|
---
|
||
|
capabilities:
|
||
|
description: Capabilities of the host.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: hvm
|
||
|
cluster:
|
||
|
description: Cluster of the host.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: vcenter.example.com/zone/cluster01
|
||
|
cluster_type:
|
||
|
description: Type of the cluster of the host.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: ExternalManaged
|
||
|
cpu_allocated:
|
||
|
description: Amount in percent of the host's CPU currently allocated.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: 166.25%
|
||
|
cpu_number:
|
||
|
description: Number of CPUs of the host.
|
||
|
returned: success
|
||
|
type: str
|
||
|
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: str
|
||
|
sample: 33.6%
|
||
|
cpu_with_overprovisioning:
|
||
|
description: Amount of the host's CPU after applying the cpu.overprovisioning.factor.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: 959520.0
|
||
|
created:
|
||
|
description: Date when the host was created.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: 2015-05-03T15:05:51+0200
|
||
|
disconnected:
|
||
|
description: Date when the host was disconnected.
|
||
|
returned: success
|
||
|
type: str
|
||
|
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: str
|
||
|
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: str
|
||
|
sample: "perf"
|
||
|
hypervisor:
|
||
|
description: Host's hypervisor.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: VMware
|
||
|
hypervisor_version:
|
||
|
description: Hypervisor version.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: 5.1
|
||
|
ip_address:
|
||
|
description: IP address of the host
|
||
|
returned: success
|
||
|
type: str
|
||
|
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: str
|
||
|
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: str
|
||
|
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: str
|
||
|
sample: ...
|
||
|
out_of_band_management:
|
||
|
description: Host out-of-band management information.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: ...
|
||
|
pod:
|
||
|
description: Pod name of the host.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: Pod01
|
||
|
removed:
|
||
|
description: Date and time the host was removed.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: "1970-01-17T17:27:32+0100"
|
||
|
resource_state:
|
||
|
description: Resource state of the host.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: Enabled
|
||
|
allocation_state::
|
||
|
description: Allocation state of the host.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: enabled
|
||
|
state:
|
||
|
description: State of the host.
|
||
|
returned: success
|
||
|
type: str
|
||
|
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: str
|
||
|
sample: true
|
||
|
host_type:
|
||
|
description: Type of the host.
|
||
|
returned: success
|
||
|
type: str
|
||
|
sample: Routing
|
||
|
host_version:
|
||
|
description: Version of the host.
|
||
|
returned: success
|
||
|
type: str
|
||
|
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: str
|
||
|
sample: zone01
|
||
|
'''
|
||
|
|
||
|
from ansible.module_utils.basic import AnsibleModule
|
||
|
from ansible_collections.community.general.plugins.module_utils.cloudstack import (
|
||
|
AnsibleCloudStack,
|
||
|
cs_argument_spec,
|
||
|
cs_required_together,
|
||
|
)
|
||
|
import time
|
||
|
|
||
|
|
||
|
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',
|
||
|
'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',
|
||
|
}
|
||
|
# States only usable by the updateHost API
|
||
|
self.allocation_states_for_update = {
|
||
|
'enabled': 'Enable',
|
||
|
'disabled': 'Disable',
|
||
|
}
|
||
|
self.host = None
|
||
|
|
||
|
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.query_api('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.query_api('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_host(self, refresh=False):
|
||
|
if self.host is not None and not refresh:
|
||
|
return self.host
|
||
|
|
||
|
name = self.module.params.get('name')
|
||
|
args = {
|
||
|
'zoneid': self.get_zone(key='id'),
|
||
|
'fetch_list': True,
|
||
|
}
|
||
|
res = self.query_api('listHosts', **args)
|
||
|
if res:
|
||
|
for h in res:
|
||
|
if name in [h['ipaddress'], h['name']]:
|
||
|
self.host = h
|
||
|
return self.host
|
||
|
|
||
|
def _handle_allocation_state(self, host):
|
||
|
allocation_state = self.module.params.get('allocation_state')
|
||
|
if not allocation_state:
|
||
|
return host
|
||
|
|
||
|
host = self._set_host_allocation_state(host)
|
||
|
|
||
|
# In case host in maintenance and target is maintenance
|
||
|
if host['allocationstate'].lower() == allocation_state and allocation_state == 'maintenance':
|
||
|
return host
|
||
|
|
||
|
# Cancel maintenance if target state is enabled/disabled
|
||
|
elif allocation_state in list(self.allocation_states_for_update.keys()):
|
||
|
host = self.disable_maintenance(host)
|
||
|
host = self._update_host(host, self.allocation_states_for_update[allocation_state])
|
||
|
|
||
|
# Only an enabled host can put in maintenance
|
||
|
elif allocation_state == 'maintenance':
|
||
|
host = self._update_host(host, 'Enable')
|
||
|
host = self.enable_maintenance(host)
|
||
|
|
||
|
return host
|
||
|
|
||
|
def _set_host_allocation_state(self, host):
|
||
|
if host is None:
|
||
|
host['allocationstate'] = 'Enable'
|
||
|
|
||
|
# Set host allocationstate to be disabled/enabled
|
||
|
elif host['resourcestate'].lower() in list(self.allocation_states_for_update.keys()):
|
||
|
host['allocationstate'] = self.allocation_states_for_update[host['resourcestate'].lower()]
|
||
|
|
||
|
else:
|
||
|
host['allocationstate'] = host['resourcestate']
|
||
|
|
||
|
return host
|
||
|
|
||
|
def present_host(self):
|
||
|
host = self.get_host()
|
||
|
|
||
|
if not host:
|
||
|
host = self._create_host(host)
|
||
|
else:
|
||
|
host = self._update_host(host)
|
||
|
|
||
|
if host:
|
||
|
host = self._handle_allocation_state(host)
|
||
|
|
||
|
return host
|
||
|
|
||
|
def _get_url(self):
|
||
|
url = self.module.params.get('url')
|
||
|
if url:
|
||
|
return url
|
||
|
else:
|
||
|
return "http://%s" % self.module.params.get('name')
|
||
|
|
||
|
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._get_url(),
|
||
|
'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'),
|
||
|
'hosttags': self.get_host_tags(),
|
||
|
}
|
||
|
if not self.module.check_mode:
|
||
|
host = self.query_api('addHost', **args)
|
||
|
host = host['host'][0]
|
||
|
return host
|
||
|
|
||
|
def _update_host(self, host, allocation_state=None):
|
||
|
args = {
|
||
|
'id': host['id'],
|
||
|
'hosttags': self.get_host_tags(),
|
||
|
'allocationstate': allocation_state,
|
||
|
}
|
||
|
|
||
|
if allocation_state is not None:
|
||
|
host = self._set_host_allocation_state(host)
|
||
|
|
||
|
if self.has_changed(args, host):
|
||
|
self.result['changed'] = True
|
||
|
if not self.module.check_mode:
|
||
|
host = self.query_api('updateHost', **args)
|
||
|
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.enable_maintenance(host)
|
||
|
if res:
|
||
|
res = self.query_api('deleteHost', **args)
|
||
|
return host
|
||
|
|
||
|
def enable_maintenance(self, host):
|
||
|
if host['resourcestate'] not in ['PrepareForMaintenance', 'Maintenance']:
|
||
|
self.result['changed'] = True
|
||
|
args = {
|
||
|
'id': host['id'],
|
||
|
}
|
||
|
if not self.module.check_mode:
|
||
|
res = self.query_api('prepareHostForMaintenance', **args)
|
||
|
self.poll_job(res, 'host')
|
||
|
host = self._poll_for_maintenance()
|
||
|
return host
|
||
|
|
||
|
def disable_maintenance(self, host):
|
||
|
if host['resourcestate'] in ['PrepareForMaintenance', 'Maintenance']:
|
||
|
self.result['changed'] = True
|
||
|
args = {
|
||
|
'id': host['id'],
|
||
|
}
|
||
|
if not self.module.check_mode:
|
||
|
res = self.query_api('cancelHostMaintenance', **args)
|
||
|
host = self.poll_job(res, 'host')
|
||
|
return host
|
||
|
|
||
|
def _poll_for_maintenance(self):
|
||
|
for i in range(0, 300):
|
||
|
time.sleep(2)
|
||
|
host = self.get_host(refresh=True)
|
||
|
if not host:
|
||
|
return None
|
||
|
elif host['resourcestate'] != 'PrepareForMaintenance':
|
||
|
return host
|
||
|
self.fail_json(msg="Polling for maintenance timed out")
|
||
|
|
||
|
def get_result(self, host):
|
||
|
super(AnsibleCloudStackHost, self).get_result(host)
|
||
|
if host:
|
||
|
self.result['allocation_state'] = host['resourcestate'].lower()
|
||
|
self.result['host_tags'] = host['hosttags'].split(',') if host.get('hosttags') else []
|
||
|
return self.result
|
||
|
|
||
|
|
||
|
def main():
|
||
|
argument_spec = cs_argument_spec()
|
||
|
argument_spec.update(dict(
|
||
|
name=dict(required=True, aliases=['ip_address']),
|
||
|
url=dict(),
|
||
|
password=dict(no_log=True),
|
||
|
username=dict(),
|
||
|
hypervisor=dict(),
|
||
|
allocation_state=dict(choices=['enabled', 'disabled', 'maintenance']),
|
||
|
pod=dict(),
|
||
|
cluster=dict(),
|
||
|
host_tags=dict(type='list', aliases=['host_tag']),
|
||
|
zone=dict(),
|
||
|
state=dict(choices=['present', 'absent'], default='present'),
|
||
|
))
|
||
|
|
||
|
module = AnsibleModule(
|
||
|
argument_spec=argument_spec,
|
||
|
required_together=cs_required_together(),
|
||
|
supports_check_mode=True
|
||
|
)
|
||
|
|
||
|
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)
|
||
|
|
||
|
module.exit_json(**result)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|