ansible/cloud/vsphere_guest

422 lines
12 KiB
Python

#!/usr/bin/python2
# -*- coding: utf-8 -*-
# TODO:
# Ability to set CPU/Memory reservations
try:
import json
except ImportError:
import simplejson as json
DOCUMENTATION = '''
---
module: vsphere_client
short_description: Creates a virtual guest on vsphere.
description:
- Communicates with vsphere, creating a new virtual guest OS based on
the specifications you specify to the module.
version_added: "1.1"
options:
vcenter_hostname:
description:
- The hostname of the vcenter server the module will connect to, to create the guest.
required: true
default: null
aliases: []
user:
description:
- username of the user to connect to vcenter as.
required: true
default: null
password:
description:
- password of the user to connect to vcenter as.
required: true
default: null
resource_pool:
description:
- The name of the resource_pool to create the VM in.
required: false
default: None
cluster:
description:
- The name of the cluster to create the VM in. By default this is derived from the host you tell the module to build the guest on.
required: false
default: None
datacenter:
description:
- The name of the datacenter to create the VM in.
required: true
default: null
datastore:
description:
- The datastore to store the VMs config files in. (Hard-disk locations are specified separately.)
required: true
default: null
esxi_hostname:
description:
- The hostname of the esxi host you want the VM to be created on.
required: true
default: null
power_on:
description:
- Whether or not to power on the VM after creation.
required: false
default: no
choices: [yes, no]
vm_name:
description:
- The name you want to call the VM.
required: true
default: null
vm_memory_mb:
description:
- How much memory in MB to give the VM.
required: false
default: 1024
vm_num_cpus:
description:
- How many vCPUs to give the VM.
required: false
default: 1
vm_scsi:
description:
- The type of scsi controller to add to the VM.
required: false
default: "paravirtual"
choices: [paravirtual, lsi, lsi_sas, bus_logic]
vm_disk:
description:
- A key, value list of disks and their sizes and which datastore to keep it in.
required: false
default: null
vm_nic:
description:
- A key, value list of nics, their types and what network to put them on.
required: false
default: null
choices: [vmxnet3, vmxnet2, vmxnet, e1000, e1000e, pcnet32]
vm_notes:
description:
- Any notes that you want to show up in the VMs Annotations field.
required: false
default: null
vm_cdrom:
description:
- A path, including datastore, to an ISO you want the CDROM device on the VM to have.
required: false
default: null
vm_extra_config:
description:
- A key, value pair of any extra values you want set or changed in the vmx file of the VM. Useful to set advanced options on the VM.
required: false
default: null
guestosid:
description:
- "A vmware guest needs to have a specific OS identifier set on it
during creation. You can find your os guestosid at the following URL:
http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html"
required: true
default: null
# informational: requirements for nodes
requirements: [ pysphere ]
author: Romeo Theriault
'''
def power_state(vm, state, force):
power_status = vm.get_status()
check_status = ' '.join(state.split("_")).upper()
# Need Force
if not force and power_status in [
'SUSPENDED', 'POWERING ON',
'RESETTING', 'BLOCKED ON MSG'
]:
return "VM is in %s power state. Force is required!" % power_status
# State is already true
if power_status == check_status:
return False
else:
try:
if state == 'powered_off':
vm.power_off(sync_run=True)
elif state == 'powered_on':
vm.power_on(sync_run=True)
elif state == 'restarted':
if power_status in ('POWERED ON', 'POWERING ON', 'RESETTING'):
vm.reset(sync_run=False)
else:
return "Cannot restart VM in the current state %s" \
% power_status
return True
except Exception, e:
return e
return False
def gather_facts(vm):
"""
Gather facts for VM directly from vsphere.
"""
vm.get_properties()
facts = {
'module_hw': True,
'hw_name': vm.properties.name,
'hw_guest_full_name': vm.properties.config.guestFullName,
'hw_guest_id': vm.properties.config.guestId,
'hw_product_uuid': vm.properties.config.uuid,
'hw_processor_count': vm.properties.config.hardware.numCPU,
'hw_memtotal_mb': vm.properties.config.hardware.memoryMB,
}
ifidx = 0
for entry in vm.properties.config.hardware.device:
if not hasattr(entry, 'macAddress'):
continue
factname = 'hw_eth' + str(ifidx)
facts[factname] = {
'addresstype': entry.addressType,
'label': entry.deviceInfo.label,
'macaddress': entry.macAddress,
'macaddress_dash': entry.macAddress.replace(':', '-'),
'summary': entry.deviceInfo.summary,
}
ifidx += 1
return facts
class DefaultVMConfig(object):
def __init__(self, check_dict, interface_dict):
self.check_dict, self.interface_dict = check_dict, interface_dict
self.set_current, self.set_past = set(
check_dict.keys()), set(interface_dict.keys())
self.intersect = self.set_current.intersection(self.set_past)
self.recursive_missing = None
def shallow_diff(self):
return self.set_past - self.intersect
def recursive_diff(self):
if not self.recursive_missing:
self.recursive_missing = []
for key, value in self.interface_dict.items():
if isinstance(value, dict):
for k, v in value.items():
if k in self.check_dict[key]:
if not isinstance(self.check_dict[key][k], v):
self.recursive_missing.append((k, v))
else:
self.recursive_missing.append((k, v))
return self.recursive_missing
def config_check(name, passed, default, module):
diff = DefaultVMConfig(passed, default)
if len(diff.shallow_diff()):
module.fail_json(
msg="Missing required key/pair [%s]. %s must contain %s" %
(', '.join(diff.shallow_diff()), name, default))
if diff.recursive_diff():
module.fail_json(
msg="Config mismatch for %s on %s" %
(name, diff.recursive_diff()))
return True
def main():
vm = None
proto_vm_hardware = {
'memory_mb': int,
'num_cpus': int
}
proto_vm_disk = {
'disk1': {
'size_gb': int,
'type': basestring
}
}
proto_vm_nic = {
'nic1': {
'type': basestring,
'network': basestring,
'network_type': basestring
}
}
proto_esxi = {
'datastore': basestring,
'datacenter': basestring,
'hostname': basestring
}
module = AnsibleModule(
argument_spec=dict(
vcenter_hostname=dict(required=True, type='str'),
username=dict(required=True, type='str'),
password=dict(required=True, type='str'),
state=dict(
required=False,
choices=[
'powered_on',
'powered_off',
'present',
'absent',
'restarted',
'reconfigured'
],
default='present'),
vmware_guest_facts=dict(required=False, choices=BOOLEANS),
guest=dict(required=True, type='str'),
vm_disk=dict(required=False, type='dict', default={}),
vm_boot_state=dict(
required=False,
choices=[
'powered_on',
'powered_off',
],
default='powered_on'),
vm_nic=dict(required=False, type='dict', default={}),
vm_hardware=dict(required=False, type='dict', default={}),
vm_extra_config=dict(required=False, type='dict', default={}),
force=dict(required=False, choices=BOOLEANS, default=False),
esxi=dict(required=False, type='dict', default={}),
),
supports_check_mode=False,
mutually_exclusive=[['state', 'vmware_guest_facts']],
required_together=[
['state', 'force'],
[
'state',
'vm_disk',
'vm_boot_state',
'vm_nic',
'vm_hardware',
'esxi'
]
],
)
try:
from pysphere import VIServer, VIProperty, MORTypes
from pysphere.resources import VimService_services as VI
from pysphere.vi_task import VITask
from pysphere import VIException, VIApiException, FaultTypes
except ImportError, e:
module.fail_json(msg='pysphere module required')
vcenter_hostname = module.params['vcenter_hostname']
username = module.params['username']
password = module.params['password']
vmware_guest_facts = module.params['vmware_guest_facts']
state = module.params['state']
guest = module.params['guest']
force = module.params['force']
vm_disk = module.params['vm_disk']
vm_boot_state = module.params['vm_boot_state']
vm_nic = module.params['vm_nic']
vm_hardware = module.params['vm_hardware']
vm_extra_config = module.params['vm_extra_config']
esxi = module.params['esxi']
# CONNECT TO THE SERVER
viserver = VIServer()
try:
viserver.connect(vcenter_hostname, username, password)
except VIApiException, err:
module.fail_json(msg="Cannot connect to %s: %s" %
(vcenter_hostname, err))
# Check if the VM exists before continuing
try:
vm = viserver.get_vm_by_name(guest)
# Run for facts only
if vmware_guest_facts:
try:
module.exit_json(ansible_facts=gather_facts(vm))
except Exception, e:
module.fail_json(
msg="Fact gather failed with exception %s" % e)
# Power Changes
elif state in ['powered_on', 'powered_off', 'restarted']:
state_result = power_state(vm, state, force)
# Failure
if isinstance(state_result, basestring):
module.fail_json(msg=state_result)
else:
module.exit_json(changed=state_result)
# Just check if there
elif state == 'present':
module.exit_json(changed=False)
# Fail on reconfig without params
elif state == 'reconfigured':
pass
# VM doesn't exist
except Exception:
# Fail for fact gather task
if vmware_guest_facts:
module.fail_json(
msg="No such VM %s. Fact gathering requires an existing vm"
% guest)
if state not in ['absent', 'present']:
module.fail_json(
msg="No such VM %s. States [powered_on, powered_off, "
"restarted, reconfigured] required an existing VM" % guest)
elif state == 'absent':
module.exit_json(changed=False, msg="vm %s not present" % guest)
# Create the VM
elif state == 'present':
# Check the guest_config
config_check("vm_disk", vm_disk, proto_vm_disk, module)
config_check("vm_nic", vm_nic, proto_vm_nic, module)
config_check("vm_hardware", vm_hardware, proto_vm_hardware, module)
config_check("esxi", esxi, proto_esxi, module)
if vm:
# If the vm already exists, lets get some info from it, pass back the
# vm's vmware_guest_facts and then exit.
viserver.disconnect()
module.exit_json(
changed=False,
vcenter=vcenter_hostname)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()