0c1257b0c1
Before this change, an instance must be present for make use of state=stopped/started. Now we are deploying an instance in the desire state if it does not exist. In this case all args needed to deploy the instance must be passed. However the short form for stopping/starting an _existing_ instance still works as before.
877 lines
31 KiB
Python
877 lines
31 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# (c) 2015, René Moser <mail@renemoser.net>
|
|
#
|
|
# 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/>.
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: cs_instance
|
|
short_description: Manages instances and virtual machines on Apache CloudStack based clouds.
|
|
description:
|
|
- Deploy, start, update, scale, restart, stop and destroy instances.
|
|
version_added: '2.0'
|
|
author: "René Moser (@resmo)"
|
|
options:
|
|
name:
|
|
description:
|
|
- Host name of the instance. C(name) can only contain ASCII letters.
|
|
required: true
|
|
display_name:
|
|
description:
|
|
- Custom display name of the instances.
|
|
required: false
|
|
default: null
|
|
group:
|
|
description:
|
|
- Group in where the new instance should be in.
|
|
required: false
|
|
default: null
|
|
state:
|
|
description:
|
|
- State of the instance.
|
|
required: false
|
|
default: 'present'
|
|
choices: [ 'deployed', 'started', 'stopped', 'restarted', 'destroyed', 'expunged', 'present', 'absent' ]
|
|
service_offering:
|
|
description:
|
|
- Name or id of the service offering of the new instance.
|
|
- If not set, first found service offering is used.
|
|
required: false
|
|
default: null
|
|
template:
|
|
description:
|
|
- Name or id of the template to be used for creating the new instance.
|
|
- Required when using C(state=present).
|
|
- Mutually exclusive with C(ISO) option.
|
|
required: false
|
|
default: null
|
|
iso:
|
|
description:
|
|
- Name or id of the ISO to be used for creating the new instance.
|
|
- Required when using C(state=present).
|
|
- Mutually exclusive with C(template) option.
|
|
required: false
|
|
default: null
|
|
hypervisor:
|
|
description:
|
|
- Name the hypervisor to be used for creating the new instance.
|
|
- Relevant when using C(state=present), but only considered if not set on ISO/template.
|
|
- If not set or found on ISO/template, first found hypervisor will be used.
|
|
required: false
|
|
default: null
|
|
choices: [ 'KVM', 'VMware', 'BareMetal', 'XenServer', 'LXC', 'HyperV', 'UCS', 'OVM' ]
|
|
keyboard:
|
|
description:
|
|
- Keyboard device type for the instance.
|
|
required: false
|
|
default: null
|
|
choices: [ 'de', 'de-ch', 'es', 'fi', 'fr', 'fr-be', 'fr-ch', 'is', 'it', 'jp', 'nl-be', 'no', 'pt', 'uk', 'us' ]
|
|
networks:
|
|
description:
|
|
- List of networks to use for the new instance.
|
|
required: false
|
|
default: []
|
|
aliases: [ 'network' ]
|
|
ip_address:
|
|
description:
|
|
- IPv4 address for default instance's network during creation.
|
|
required: false
|
|
default: null
|
|
ip6_address:
|
|
description:
|
|
- IPv6 address for default instance's network.
|
|
required: false
|
|
default: null
|
|
ip_to_networks:
|
|
description:
|
|
- "List of mappings in the form {'network': NetworkName, 'ip': 1.2.3.4}"
|
|
- Mutually exclusive with C(networks) option.
|
|
required: false
|
|
default: null
|
|
aliases: [ 'ip_to_network' ]
|
|
disk_offering:
|
|
description:
|
|
- Name of the disk offering to be used.
|
|
required: false
|
|
default: null
|
|
disk_size:
|
|
description:
|
|
- Disk size in GByte required if deploying instance from ISO.
|
|
required: false
|
|
default: null
|
|
root_disk_size:
|
|
description:
|
|
- Root disk size in GByte required if deploying instance with KVM hypervisor and want resize the root disk size at startup (need CloudStack >= 4.4, cloud-initramfs-growroot installed and enabled in the template)
|
|
required: false
|
|
default: null
|
|
security_groups:
|
|
description:
|
|
- List of security groups the instance to be applied to.
|
|
required: false
|
|
default: []
|
|
aliases: [ 'security_group' ]
|
|
domain:
|
|
description:
|
|
- Domain the instance is related to.
|
|
required: false
|
|
default: null
|
|
account:
|
|
description:
|
|
- Account the instance is related to.
|
|
required: false
|
|
default: null
|
|
project:
|
|
description:
|
|
- Name of the project the instance to be deployed in.
|
|
required: false
|
|
default: null
|
|
zone:
|
|
description:
|
|
- Name of the zone in which the instance shoud be deployed.
|
|
- If not set, default zone is used.
|
|
required: false
|
|
default: null
|
|
ssh_key:
|
|
description:
|
|
- Name of the SSH key to be deployed on the new instance.
|
|
required: false
|
|
default: null
|
|
affinity_groups:
|
|
description:
|
|
- Affinity groups names to be applied to the new instance.
|
|
required: false
|
|
default: []
|
|
aliases: [ 'affinity_group' ]
|
|
user_data:
|
|
description:
|
|
- Optional data (ASCII) that can be sent to the instance upon a successful deployment.
|
|
- The data will be automatically base64 encoded.
|
|
- Consider switching to HTTP_POST by using C(CLOUDSTACK_METHOD=post) to increase the HTTP_GET size limit of 2KB to 32 KB.
|
|
required: false
|
|
default: null
|
|
force:
|
|
description:
|
|
- Force stop/start the instance if required to apply changes, otherwise a running instance will not be changed.
|
|
required: false
|
|
default: false
|
|
tags:
|
|
description:
|
|
- List of tags. Tags are a list of dictionaries having keys C(key) and C(value).
|
|
- "If you want to delete all tags, set a empty list e.g. C(tags: [])."
|
|
required: false
|
|
default: null
|
|
poll_async:
|
|
description:
|
|
- Poll async jobs until job has finished.
|
|
required: false
|
|
default: true
|
|
extends_documentation_fragment: cloudstack
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
# Create a instance from an ISO
|
|
# NOTE: Names of offerings and ISOs depending on the CloudStack configuration.
|
|
- local_action:
|
|
module: cs_instance
|
|
name: web-vm-1
|
|
iso: Linux Debian 7 64-bit
|
|
hypervisor: VMware
|
|
project: Integration
|
|
zone: ch-zrh-ix-01
|
|
service_offering: 1cpu_1gb
|
|
disk_offering: PerfPlus Storage
|
|
disk_size: 20
|
|
networks:
|
|
- Server Integration
|
|
- Sync Integration
|
|
- Storage Integration
|
|
|
|
# For changing a running instance, use the 'force' parameter
|
|
- local_action:
|
|
module: cs_instance
|
|
name: web-vm-1
|
|
display_name: web-vm-01.example.com
|
|
iso: Linux Debian 7 64-bit
|
|
service_offering: 2cpu_2gb
|
|
force: yes
|
|
|
|
# Create or update a instance on Exoscale's public cloud
|
|
- local_action:
|
|
module: cs_instance
|
|
name: web-vm-1
|
|
template: Linux Debian 7 64-bit
|
|
service_offering: Tiny
|
|
ssh_key: john@example.com
|
|
tags:
|
|
- { key: admin, value: john }
|
|
- { key: foo, value: bar }
|
|
|
|
# Create an instance with multiple interfaces specifying the IP addresses
|
|
- local_action:
|
|
module: cs_instance
|
|
name: web-vm-1
|
|
template: Linux Debian 7 64-bit
|
|
service_offering: Tiny
|
|
ip_to_networks:
|
|
- {'network': NetworkA, 'ip': '10.1.1.1'}
|
|
- {'network': NetworkB, 'ip': '192.168.1.1'}
|
|
|
|
# Ensure a instance has stopped
|
|
- local_action: cs_instance name=web-vm-1 state=stopped
|
|
|
|
# Ensure a instance is running
|
|
- local_action: cs_instance name=web-vm-1 state=started
|
|
|
|
# Remove a instance
|
|
- local_action: cs_instance name=web-vm-1 state=absent
|
|
'''
|
|
|
|
RETURN = '''
|
|
---
|
|
id:
|
|
description: UUID of the instance.
|
|
returned: success
|
|
type: string
|
|
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
|
name:
|
|
description: Name of the instance.
|
|
returned: success
|
|
type: string
|
|
sample: web-01
|
|
display_name:
|
|
description: Display name of the instance.
|
|
returned: success
|
|
type: string
|
|
sample: web-01
|
|
group:
|
|
description: Group name of the instance is related.
|
|
returned: success
|
|
type: string
|
|
sample: web
|
|
created:
|
|
description: Date of the instance was created.
|
|
returned: success
|
|
type: string
|
|
sample: 2014-12-01T14:57:57+0100
|
|
password_enabled:
|
|
description: True if password setting is enabled.
|
|
returned: success
|
|
type: boolean
|
|
sample: true
|
|
password:
|
|
description: The password of the instance if exists.
|
|
returned: success
|
|
type: string
|
|
sample: Ge2oe7Do
|
|
ssh_key:
|
|
description: Name of SSH key deployed to instance.
|
|
returned: success
|
|
type: string
|
|
sample: key@work
|
|
domain:
|
|
description: Domain the instance is related to.
|
|
returned: success
|
|
type: string
|
|
sample: example domain
|
|
account:
|
|
description: Account the instance is related to.
|
|
returned: success
|
|
type: string
|
|
sample: example account
|
|
project:
|
|
description: Name of project the instance is related to.
|
|
returned: success
|
|
type: string
|
|
sample: Production
|
|
default_ip:
|
|
description: Default IP address of the instance.
|
|
returned: success
|
|
type: string
|
|
sample: 10.23.37.42
|
|
public_ip:
|
|
description: Public IP address with instance via static NAT rule.
|
|
returned: success
|
|
type: string
|
|
sample: 1.2.3.4
|
|
iso:
|
|
description: Name of ISO the instance was deployed with.
|
|
returned: success
|
|
type: string
|
|
sample: Debian-8-64bit
|
|
template:
|
|
description: Name of template the instance was deployed with.
|
|
returned: success
|
|
type: string
|
|
sample: Debian-8-64bit
|
|
service_offering:
|
|
description: Name of the service offering the instance has.
|
|
returned: success
|
|
type: string
|
|
sample: 2cpu_2gb
|
|
zone:
|
|
description: Name of zone the instance is in.
|
|
returned: success
|
|
type: string
|
|
sample: ch-gva-2
|
|
state:
|
|
description: State of the instance.
|
|
returned: success
|
|
type: string
|
|
sample: Running
|
|
security_groups:
|
|
description: Security groups the instance is in.
|
|
returned: success
|
|
type: list
|
|
sample: '[ "default" ]'
|
|
affinity_groups:
|
|
description: Affinity groups the instance is in.
|
|
returned: success
|
|
type: list
|
|
sample: '[ "webservers" ]'
|
|
tags:
|
|
description: List of resource tags associated with the instance.
|
|
returned: success
|
|
type: dict
|
|
sample: '[ { "key": "foo", "value": "bar" } ]'
|
|
hypervisor:
|
|
description: Hypervisor related to this instance.
|
|
returned: success
|
|
type: string
|
|
sample: KVM
|
|
instance_name:
|
|
description: Internal name of the instance (ROOT admin only).
|
|
returned: success
|
|
type: string
|
|
sample: i-44-3992-VM
|
|
'''
|
|
|
|
import base64
|
|
|
|
try:
|
|
from cs import CloudStack, CloudStackException, read_config
|
|
has_lib_cs = True
|
|
except ImportError:
|
|
has_lib_cs = False
|
|
|
|
# import cloudstack common
|
|
from ansible.module_utils.cloudstack import *
|
|
|
|
|
|
class AnsibleCloudStackInstance(AnsibleCloudStack):
|
|
|
|
def __init__(self, module):
|
|
super(AnsibleCloudStackInstance, self).__init__(module)
|
|
self.returns = {
|
|
'group': 'group',
|
|
'hypervisor': 'hypervisor',
|
|
'instancename': 'instance_name',
|
|
'publicip': 'public_ip',
|
|
'passwordenabled': 'password_enabled',
|
|
'password': 'password',
|
|
'serviceofferingname': 'service_offering',
|
|
'isoname': 'iso',
|
|
'templatename': 'template',
|
|
'keypair': 'ssh_key',
|
|
'securitygroup': 'security_group',
|
|
}
|
|
self.instance = None
|
|
self.template = None
|
|
self.iso = None
|
|
|
|
|
|
def get_service_offering_id(self):
|
|
service_offering = self.module.params.get('service_offering')
|
|
|
|
service_offerings = self.cs.listServiceOfferings()
|
|
if service_offerings:
|
|
if not service_offering:
|
|
return service_offerings['serviceoffering'][0]['id']
|
|
|
|
for s in service_offerings['serviceoffering']:
|
|
if service_offering in [ s['name'], s['id'] ]:
|
|
return s['id']
|
|
self.module.fail_json(msg="Service offering '%s' not found" % service_offering)
|
|
|
|
|
|
def get_template_or_iso(self, key=None):
|
|
template = self.module.params.get('template')
|
|
iso = self.module.params.get('iso')
|
|
|
|
if not template and not iso:
|
|
self.module.fail_json(msg="Template or ISO is required.")
|
|
|
|
args = {}
|
|
args['account'] = self.get_account(key='name')
|
|
args['domainid'] = self.get_domain(key='id')
|
|
args['projectid'] = self.get_project(key='id')
|
|
args['zoneid'] = self.get_zone(key='id')
|
|
args['isrecursive'] = True
|
|
|
|
if template:
|
|
if self.template:
|
|
return self._get_by_key(key, self.template)
|
|
|
|
args['templatefilter'] = 'executable'
|
|
templates = self.cs.listTemplates(**args)
|
|
if templates:
|
|
for t in templates['template']:
|
|
if template in [ t['displaytext'], t['name'], t['id'] ]:
|
|
self.template = t
|
|
return self._get_by_key(key, self.template)
|
|
self.module.fail_json(msg="Template '%s' not found" % template)
|
|
|
|
elif iso:
|
|
if self.iso:
|
|
return self._get_by_key(key, self.iso)
|
|
args['isofilter'] = 'executable'
|
|
isos = self.cs.listIsos(**args)
|
|
if isos:
|
|
for i in isos['iso']:
|
|
if iso in [ i['displaytext'], i['name'], i['id'] ]:
|
|
self.iso = i
|
|
return self._get_by_key(key, self.iso)
|
|
self.module.fail_json(msg="ISO '%s' not found" % iso)
|
|
|
|
|
|
def get_disk_offering_id(self):
|
|
disk_offering = self.module.params.get('disk_offering')
|
|
|
|
if not disk_offering:
|
|
return None
|
|
|
|
disk_offerings = self.cs.listDiskOfferings()
|
|
if disk_offerings:
|
|
for d in disk_offerings['diskoffering']:
|
|
if disk_offering in [ d['displaytext'], d['name'], d['id'] ]:
|
|
return d['id']
|
|
self.module.fail_json(msg="Disk offering '%s' not found" % disk_offering)
|
|
|
|
|
|
def get_instance(self):
|
|
instance = self.instance
|
|
if not instance:
|
|
instance_name = self.module.params.get('name')
|
|
|
|
args = {}
|
|
args['account'] = self.get_account(key='name')
|
|
args['domainid'] = self.get_domain(key='id')
|
|
args['projectid'] = self.get_project(key='id')
|
|
# Do not pass zoneid, as the instance name must be unique across zones.
|
|
instances = self.cs.listVirtualMachines(**args)
|
|
if instances:
|
|
for v in instances['virtualmachine']:
|
|
if instance_name in [ v['name'], v['displayname'], v['id'] ]:
|
|
self.instance = v
|
|
break
|
|
return self.instance
|
|
|
|
def get_iptonetwork_mappings(self):
|
|
network_mappings = self.module.params.get('ip_to_networks')
|
|
if network_mappings is None:
|
|
return
|
|
|
|
if network_mappings and self.module.params.get('networks'):
|
|
self.module.fail_json(msg="networks and ip_to_networks are mutually exclusive.")
|
|
|
|
network_names = [n['network'] for n in network_mappings]
|
|
ids = self.get_network_ids(network_names)
|
|
res = []
|
|
for i, data in enumerate(network_mappings):
|
|
res.append({'networkid': ids[i], 'ip': data['ip']})
|
|
return res
|
|
|
|
def get_network_ids(self, network_names=None):
|
|
if network_names is None:
|
|
network_names = self.module.params.get('networks')
|
|
|
|
if not network_names:
|
|
return None
|
|
|
|
args = {}
|
|
args['account'] = self.get_account(key='name')
|
|
args['domainid'] = self.get_domain(key='id')
|
|
args['projectid'] = self.get_project(key='id')
|
|
args['zoneid'] = self.get_zone(key='id')
|
|
|
|
networks = self.cs.listNetworks(**args)
|
|
if not networks:
|
|
self.module.fail_json(msg="No networks available")
|
|
|
|
network_ids = []
|
|
network_displaytexts = []
|
|
for network_name in network_names:
|
|
for n in networks['network']:
|
|
if network_name in [ n['displaytext'], n['name'], n['id'] ]:
|
|
network_ids.append(n['id'])
|
|
network_displaytexts.append(n['name'])
|
|
break
|
|
|
|
if len(network_ids) != len(network_names):
|
|
self.module.fail_json(msg="Could not find all networks, networks list found: %s" % network_displaytexts)
|
|
|
|
return network_ids
|
|
|
|
|
|
def present_instance(self):
|
|
instance = self.get_instance()
|
|
if not instance:
|
|
instance = self.deploy_instance()
|
|
else:
|
|
instance = self.update_instance(instance)
|
|
|
|
# In check mode, we do not necessarely have an instance
|
|
if instance:
|
|
instance = self.ensure_tags(resource=instance, resource_type='UserVm')
|
|
|
|
return instance
|
|
|
|
|
|
def get_user_data(self):
|
|
user_data = self.module.params.get('user_data')
|
|
if user_data:
|
|
user_data = base64.b64encode(user_data)
|
|
return user_data
|
|
|
|
|
|
def deploy_instance(self, start_vm=True):
|
|
self.result['changed'] = True
|
|
networkids = self.get_network_ids()
|
|
if networkids is not None:
|
|
networkids = ','.join(networkids)
|
|
|
|
args = {}
|
|
args['templateid'] = self.get_template_or_iso(key='id')
|
|
args['zoneid'] = self.get_zone(key='id')
|
|
args['serviceofferingid'] = self.get_service_offering_id()
|
|
args['account'] = self.get_account(key='name')
|
|
args['domainid'] = self.get_domain(key='id')
|
|
args['projectid'] = self.get_project(key='id')
|
|
args['diskofferingid'] = self.get_disk_offering_id()
|
|
args['networkids'] = networkids
|
|
args['iptonetworklist'] = self.get_iptonetwork_mappings()
|
|
args['userdata'] = self.get_user_data()
|
|
args['keyboard'] = self.module.params.get('keyboard')
|
|
args['ipaddress'] = self.module.params.get('ip_address')
|
|
args['ip6address'] = self.module.params.get('ip6_address')
|
|
args['name'] = self.module.params.get('name')
|
|
args['displayname'] = self.get_or_fallback('display_name', 'name')
|
|
args['group'] = self.module.params.get('group')
|
|
args['keypair'] = self.module.params.get('ssh_key')
|
|
args['size'] = self.module.params.get('disk_size')
|
|
args['startvm'] = start_vm
|
|
args['rootdisksize'] = self.module.params.get('root_disk_size')
|
|
args['securitygroupnames'] = ','.join(self.module.params.get('security_groups'))
|
|
args['affinitygroupnames'] = ','.join(self.module.params.get('affinity_groups'))
|
|
|
|
template_iso = self.get_template_or_iso()
|
|
if 'hypervisor' not in template_iso:
|
|
args['hypervisor'] = self.get_hypervisor()
|
|
|
|
instance = None
|
|
if not self.module.check_mode:
|
|
instance = self.cs.deployVirtualMachine(**args)
|
|
|
|
if 'errortext' in instance:
|
|
self.module.fail_json(msg="Failed: '%s'" % instance['errortext'])
|
|
|
|
poll_async = self.module.params.get('poll_async')
|
|
if poll_async:
|
|
instance = self._poll_job(instance, 'virtualmachine')
|
|
return instance
|
|
|
|
|
|
def update_instance(self, instance):
|
|
args_service_offering = {}
|
|
args_service_offering['id'] = instance['id']
|
|
args_service_offering['serviceofferingid'] = self.get_service_offering_id()
|
|
|
|
args_instance_update = {}
|
|
args_instance_update['id'] = instance['id']
|
|
args_instance_update['group'] = self.module.params.get('group')
|
|
args_instance_update['displayname'] = self.get_or_fallback('display_name', 'name')
|
|
args_instance_update['userdata'] = self.get_user_data()
|
|
args_instance_update['ostypeid'] = self.get_os_type(key='id')
|
|
|
|
args_ssh_key = {}
|
|
args_ssh_key['id'] = instance['id']
|
|
args_ssh_key['keypair'] = self.module.params.get('ssh_key')
|
|
args_ssh_key['projectid'] = self.get_project(key='id')
|
|
|
|
if self._has_changed(args_service_offering, instance) or \
|
|
self._has_changed(args_instance_update, instance) or \
|
|
self._has_changed(args_ssh_key, instance):
|
|
|
|
force = self.module.params.get('force')
|
|
instance_state = instance['state'].lower()
|
|
|
|
if instance_state == 'stopped' or force:
|
|
self.result['changed'] = True
|
|
if not self.module.check_mode:
|
|
|
|
# Ensure VM has stopped
|
|
instance = self.stop_instance()
|
|
instance = self._poll_job(instance, 'virtualmachine')
|
|
self.instance = instance
|
|
|
|
# Change service offering
|
|
if self._has_changed(args_service_offering, instance):
|
|
res = self.cs.changeServiceForVirtualMachine(**args_service_offering)
|
|
if 'errortext' in res:
|
|
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
|
instance = res['virtualmachine']
|
|
self.instance = instance
|
|
|
|
# Update VM
|
|
if self._has_changed(args_instance_update, instance):
|
|
res = self.cs.updateVirtualMachine(**args_instance_update)
|
|
if 'errortext' in res:
|
|
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
|
instance = res['virtualmachine']
|
|
self.instance = instance
|
|
|
|
# Reset SSH key
|
|
if self._has_changed(args_ssh_key, instance):
|
|
instance = self.cs.resetSSHKeyForVirtualMachine(**args_ssh_key)
|
|
if 'errortext' in instance:
|
|
self.module.fail_json(msg="Failed: '%s'" % instance['errortext'])
|
|
|
|
instance = self._poll_job(instance, 'virtualmachine')
|
|
self.instance = instance
|
|
|
|
# Start VM again if it was running before
|
|
if instance_state == 'running':
|
|
instance = self.start_instance()
|
|
return instance
|
|
|
|
|
|
def absent_instance(self):
|
|
instance = self.get_instance()
|
|
if instance:
|
|
if instance['state'].lower() not in ['expunging', 'destroying', 'destroyed']:
|
|
self.result['changed'] = True
|
|
if not self.module.check_mode:
|
|
res = self.cs.destroyVirtualMachine(id=instance['id'])
|
|
|
|
if 'errortext' in res:
|
|
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
|
|
|
poll_async = self.module.params.get('poll_async')
|
|
if poll_async:
|
|
instance = self._poll_job(res, 'virtualmachine')
|
|
return instance
|
|
|
|
|
|
def expunge_instance(self):
|
|
instance = self.get_instance()
|
|
if instance:
|
|
res = {}
|
|
if instance['state'].lower() in [ 'destroying', 'destroyed' ]:
|
|
self.result['changed'] = True
|
|
if not self.module.check_mode:
|
|
res = self.cs.destroyVirtualMachine(id=instance['id'], expunge=True)
|
|
|
|
elif instance['state'].lower() not in [ 'expunging' ]:
|
|
self.result['changed'] = True
|
|
if not self.module.check_mode:
|
|
res = self.cs.destroyVirtualMachine(id=instance['id'], expunge=True)
|
|
|
|
if res and 'errortext' in res:
|
|
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
|
|
|
poll_async = self.module.params.get('poll_async')
|
|
if poll_async:
|
|
res = self._poll_job(res, 'virtualmachine')
|
|
return instance
|
|
|
|
|
|
def stop_instance(self):
|
|
instance = self.get_instance()
|
|
|
|
if not instance:
|
|
instance = self.deploy_instance(start_vm=False)
|
|
return instance
|
|
|
|
elif instance['state'].lower() in ['stopping', 'stopped']:
|
|
return instance
|
|
|
|
if instance['state'].lower() in ['starting', 'running']:
|
|
self.result['changed'] = True
|
|
if not self.module.check_mode:
|
|
instance = self.cs.stopVirtualMachine(id=instance['id'])
|
|
|
|
if 'errortext' in instance:
|
|
self.module.fail_json(msg="Failed: '%s'" % instance['errortext'])
|
|
|
|
poll_async = self.module.params.get('poll_async')
|
|
if poll_async:
|
|
instance = self._poll_job(instance, 'virtualmachine')
|
|
return instance
|
|
|
|
|
|
def start_instance(self):
|
|
instance = self.get_instance()
|
|
|
|
if not instance:
|
|
instance = self.deploy_instance()
|
|
return instance
|
|
|
|
elif instance['state'].lower() in ['starting', 'running']:
|
|
return instance
|
|
|
|
if instance['state'].lower() in ['stopped', 'stopping']:
|
|
self.result['changed'] = True
|
|
if not self.module.check_mode:
|
|
instance = self.cs.startVirtualMachine(id=instance['id'])
|
|
|
|
if 'errortext' in instance:
|
|
self.module.fail_json(msg="Failed: '%s'" % instance['errortext'])
|
|
|
|
poll_async = self.module.params.get('poll_async')
|
|
if poll_async:
|
|
instance = self._poll_job(instance, 'virtualmachine')
|
|
return instance
|
|
|
|
|
|
def restart_instance(self):
|
|
instance = self.get_instance()
|
|
|
|
if not instance:
|
|
instance = self.deploy_instance()
|
|
return instance
|
|
|
|
elif instance['state'].lower() in [ 'running', 'starting' ]:
|
|
self.result['changed'] = True
|
|
if not self.module.check_mode:
|
|
instance = self.cs.rebootVirtualMachine(id=instance['id'])
|
|
|
|
if 'errortext' in instance:
|
|
self.module.fail_json(msg="Failed: '%s'" % instance['errortext'])
|
|
|
|
poll_async = self.module.params.get('poll_async')
|
|
if poll_async:
|
|
instance = self._poll_job(instance, 'virtualmachine')
|
|
|
|
elif instance['state'].lower() in [ 'stopping', 'stopped' ]:
|
|
instance = self.start_instance()
|
|
return instance
|
|
|
|
|
|
def get_result(self, instance):
|
|
super(AnsibleCloudStackInstance, self).get_result(instance)
|
|
if instance:
|
|
if 'securitygroup' in instance:
|
|
security_groups = []
|
|
for securitygroup in instance['securitygroup']:
|
|
security_groups.append(securitygroup['name'])
|
|
self.result['security_groups'] = security_groups
|
|
if 'affinitygroup' in instance:
|
|
affinity_groups = []
|
|
for affinitygroup in instance['affinitygroup']:
|
|
affinity_groups.append(affinitygroup['name'])
|
|
self.result['affinity_groups'] = affinity_groups
|
|
if 'nic' in instance:
|
|
for nic in instance['nic']:
|
|
if nic['isdefault'] and 'ipaddress' in nic:
|
|
self.result['default_ip'] = nic['ipaddress']
|
|
return self.result
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec = dict(
|
|
name = dict(required=True),
|
|
display_name = dict(default=None),
|
|
group = dict(default=None),
|
|
state = dict(choices=['present', 'deployed', 'started', 'stopped', 'restarted', 'absent', 'destroyed', 'expunged'], default='present'),
|
|
service_offering = dict(default=None),
|
|
template = dict(default=None),
|
|
iso = dict(default=None),
|
|
networks = dict(type='list', aliases=[ 'network' ], default=None),
|
|
ip_to_networks = dict(type='list', aliases=['ip_to_network'], default=None),
|
|
ip_address = dict(defaul=None),
|
|
ip6_address = dict(defaul=None),
|
|
disk_offering = dict(default=None),
|
|
disk_size = dict(type='int', default=None),
|
|
root_disk_size = dict(type='int', default=None),
|
|
keyboard = dict(choices=['de', 'de-ch', 'es', 'fi', 'fr', 'fr-be', 'fr-ch', 'is', 'it', 'jp', 'nl-be', 'no', 'pt', 'uk', 'us'], default=None),
|
|
hypervisor = dict(choices=['KVM', 'VMware', 'BareMetal', 'XenServer', 'LXC', 'HyperV', 'UCS', 'OVM', 'Simulator'], default=None),
|
|
security_groups = dict(type='list', aliases=[ 'security_group' ], default=[]),
|
|
affinity_groups = dict(type='list', aliases=[ 'affinity_group' ], default=[]),
|
|
domain = dict(default=None),
|
|
account = dict(default=None),
|
|
project = dict(default=None),
|
|
user_data = dict(default=None),
|
|
zone = dict(default=None),
|
|
ssh_key = dict(default=None),
|
|
force = dict(choices=BOOLEANS, default=False),
|
|
tags = dict(type='list', aliases=[ 'tag' ], default=None),
|
|
poll_async = dict(choices=BOOLEANS, default=True),
|
|
api_key = dict(default=None),
|
|
api_secret = dict(default=None, no_log=True),
|
|
api_url = dict(default=None),
|
|
api_http_method = dict(choices=['get', 'post'], default='get'),
|
|
api_timeout = dict(type='int', default=10),
|
|
),
|
|
mutually_exclusive = (
|
|
['template', 'iso'],
|
|
),
|
|
required_together = (
|
|
['api_key', 'api_secret', 'api_url'],
|
|
),
|
|
supports_check_mode=True
|
|
)
|
|
|
|
if not has_lib_cs:
|
|
module.fail_json(msg="python library cs required: pip install cs")
|
|
|
|
try:
|
|
acs_instance = AnsibleCloudStackInstance(module)
|
|
|
|
state = module.params.get('state')
|
|
|
|
if state in ['absent', 'destroyed']:
|
|
instance = acs_instance.absent_instance()
|
|
|
|
elif state in ['expunged']:
|
|
instance = acs_instance.expunge_instance()
|
|
|
|
elif state in ['present', 'deployed']:
|
|
instance = acs_instance.present_instance()
|
|
|
|
elif state in ['stopped']:
|
|
instance = acs_instance.stop_instance()
|
|
|
|
elif state in ['started']:
|
|
instance = acs_instance.start_instance()
|
|
|
|
elif state in ['restarted']:
|
|
instance = acs_instance.restart_instance()
|
|
|
|
if instance and 'state' in instance and instance['state'].lower() == 'error':
|
|
module.fail_json(msg="Instance named '%s' in error state." % module.params.get('name'))
|
|
|
|
result = acs_instance.get_result(instance)
|
|
|
|
except CloudStackException, e:
|
|
module.fail_json(msg='CloudStackException: %s' % str(e))
|
|
|
|
module.exit_json(**result)
|
|
|
|
# import module snippets
|
|
from ansible.module_utils.basic import *
|
|
if __name__ == '__main__':
|
|
main()
|