#!/usr/bin/python # -*- coding: utf-8 -*- # 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 . # TODO: # Ability to set CPU/Memory reservations try: import json except ImportError: import simplejson as json HAS_PYSPHERE = False 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 HAS_PYSPHERE = True except ImportError: pass import ssl DOCUMENTATION = ''' --- module: vsphere_guest short_description: Create/delete/manage a guest VM through VMware vSphere. description: - Create/delete/reconfigure a guest VM through VMware vSphere. This module has a dependency on pysphere >= 1.7 version_added: "1.6" options: vcenter_hostname: description: - The hostname of the vcenter server the module will connect to, to create the guest. required: true default: null aliases: [] validate_certs: description: - Validate SSL certs. Note, if running on python without SSLContext support (typically, python < 2.7.9) you will have to set this to C(no) as pysphere does not support validating certificates on older python. Prior to 2.1, this module would always validate on python >= 2.7.9 and never validate on python <= 2.7.8. required: false default: yes choices: ['yes', 'no'] version_added: 2.1 guest: description: - The virtual server name you wish to manage. required: true username: description: - Username 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 esxi: description: - Dictionary which includes datacenter and hostname on which the VM should be created. For standalone ESXi hosts, ha-datacenter should be used as the datacenter name required: false default: null state: description: - Indicate desired state of the vm. 'reconfigured' only applies changes to 'vm_cdrom', 'memory_mb', and 'num_cpus' in vm_hardware parameter. The 'memory_mb' and 'num_cpus' changes are applied to powered-on vms when hot-plugging is enabled for the guest. default: present choices: ['present', 'powered_off', 'absent', 'powered_on', 'restarted', 'reconfigured'] from_template: version_added: "1.9" description: - Specifies if the VM should be deployed from a template (mutually exclusive with 'state' parameter). No guest customization changes to hardware such as CPU, RAM, NICs or Disks can be applied when launching from template. default: no choices: ['yes', 'no'] template_src: version_added: "1.9" description: - Name of the source template to deploy from default: None snapshot_to_clone: description: - A string that when specified, will create a linked clone copy of the VM. Snapshot must already be taken in vCenter. version_added: "2.0" required: false default: none power_on_after_clone: description: - Specifies if the VM should be powered on after the clone. required: false default: yes choices: ['yes', 'no'] vm_disk: description: - A key, value list of disks and their sizes and which datastore to keep it in. required: false default: null vm_hardware: description: - A key, value list of VM config settings. Must include ['memory_mb', 'num_cpus', 'osid', 'scsi']. 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 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 vm_hw_version: description: - Desired hardware version identifier (for example, "vmx-08" for vms that needs to be managed with vSphere Client). Note that changing hardware version of existing vm is not supported. required: false default: null version_added: "1.7" vmware_guest_facts: description: - Gather facts from vCenter on a particular VM required: false default: null force: description: - Boolean. Allows you to run commands which may alter the running state of a guest. Also used to reconfigure and destroy. default: "no" choices: [ "yes", "no" ] notes: - This module should run from a system that can access vSphere directly. Either by using local_action, or using delegate_to. author: "Richard Hoop (@rhoop) " requirements: - "python >= 2.6" - pysphere ''' EXAMPLES = ''' # Create a new VM on an ESX server # Returns changed = False when the VM already exists # Returns changed = True and a adds ansible_facts from the new VM # State will set the power status of a guest upon creation. Use powered_on to create and boot. # Options ['state', 'vm_extra_config', 'vm_disk', 'vm_nic', 'vm_hardware', 'esxi'] are required together # Note: vm_floppy support added in 2.0 - vsphere_guest: vcenter_hostname: vcenter.mydomain.local username: myuser password: mypass guest: newvm001 state: powered_on vm_extra_config: vcpu.hotadd: yes mem.hotadd: yes notes: This is a test VM folder: MyFolder vm_disk: disk1: size_gb: 10 type: thin datastore: storage001 # VMs can be put into folders. The value given here is either the full path # to the folder (e.g. production/customerA/lamp) or just the last component # of the path (e.g. lamp): folder: production/customerA/lamp vm_nic: nic1: type: vmxnet3 network: VM Network network_type: standard nic2: type: vmxnet3 network: dvSwitch Network network_type: dvs vm_hardware: memory_mb: 2048 num_cpus: 2 osid: centos64Guest scsi: paravirtual vm_cdrom: type: "iso" iso_path: "DatastoreName/cd-image.iso" vm_floppy: type: "image" image_path: "DatastoreName/floppy-image.flp" esxi: datacenter: MyDatacenter hostname: esx001.mydomain.local # Reconfigure the CPU and Memory on the newly created VM # Will return the changes made - vsphere_guest: vcenter_hostname: vcenter.mydomain.local username: myuser password: mypass guest: newvm001 state: reconfigured vm_extra_config: vcpu.hotadd: yes mem.hotadd: yes notes: This is a test VM vm_disk: disk1: size_gb: 10 type: thin datastore: storage001 vm_nic: nic1: type: vmxnet3 network: VM Network network_type: standard vm_hardware: memory_mb: 4096 num_cpus: 4 osid: centos64Guest scsi: paravirtual esxi: datacenter: MyDatacenter hostname: esx001.mydomain.local # Deploy a guest from a template - vsphere_guest: vcenter_hostname: vcenter.mydomain.local username: myuser password: mypass guest: newvm001 from_template: yes template_src: centosTemplate cluster: MainCluster resource_pool: "/Resources" vm_extra_config: folder: MyFolder # Task to gather facts from a vSphere cluster only if the system is a VMWare guest - vsphere_guest: vcenter_hostname: vcenter.mydomain.local username: myuser password: mypass guest: newvm001 vmware_guest_facts: yes # Typical output of a vsphere_facts run on a guest # If vmware tools is not installed, ipadresses with return None - hw_eth0: - addresstype: "assigned" label: "Network adapter 1" macaddress: "00:22:33:33:44:55" macaddress_dash: "00-22-33-33-44-55" ipaddresses: ['192.0.2.100', '2001:DB8:56ff:feac:4d8a'] summary: "VM Network" hw_guest_full_name: "newvm001" hw_guest_id: "rhel6_64Guest" hw_memtotal_mb: 2048 hw_name: "centos64Guest" hw_power_status: "POWERED ON", hw_processor_count: 2 hw_product_uuid: "ef50bac8-2845-40ff-81d9-675315501dac" hw_power_status will be one of the following values: - POWERED ON - POWERED OFF - SUSPENDED - POWERING ON - POWERING OFF - SUSPENDING - RESETTING - BLOCKED ON MSG - REVERTING TO SNAPSHOT - UNKNOWN as seen in the VMPowerState-Class of PySphere: http://git.io/vlwOq # Remove a vm from vSphere # The VM must be powered_off or you need to use force to force a shutdown - vsphere_guest: vcenter_hostname: vcenter.mydomain.local username: myuser password: mypass guest: newvm001 state: absent force: yes ''' def add_scsi_controller(module, s, config, devices, type="paravirtual", bus_num=0, disk_ctrl_key=1): # add a scsi controller scsi_ctrl_spec = config.new_deviceChange() scsi_ctrl_spec.set_element_operation('add') if type == "lsi": # For RHEL5 scsi_ctrl = VI.ns0.VirtualLsiLogicController_Def("scsi_ctrl").pyclass() elif type == "paravirtual": # For RHEL6 scsi_ctrl = VI.ns0.ParaVirtualSCSIController_Def("scsi_ctrl").pyclass() elif type == "lsi_sas": scsi_ctrl = VI.ns0.VirtualLsiLogicSASController_Def( "scsi_ctrl").pyclass() elif type == "bus_logic": scsi_ctrl = VI.ns0.VirtualBusLogicController_Def("scsi_ctrl").pyclass() else: s.disconnect() module.fail_json( msg="Error adding scsi controller to vm spec. No scsi controller" " type of: %s" % (type)) scsi_ctrl.set_element_busNumber(int(bus_num)) scsi_ctrl.set_element_key(int(disk_ctrl_key)) scsi_ctrl.set_element_sharedBus("noSharing") scsi_ctrl_spec.set_element_device(scsi_ctrl) # Add the scsi controller to the VM spec. devices.append(scsi_ctrl_spec) return disk_ctrl_key def add_disk(module, s, config_target, config, devices, datastore, type="thin", size=200000, disk_ctrl_key=1, disk_number=0, key=0): # add a vmdk disk # Verify the datastore exists datastore_name, ds = find_datastore(module, s, datastore, config_target) # create a new disk - file based - for the vm disk_spec = config.new_deviceChange() disk_spec.set_element_fileOperation("create") disk_spec.set_element_operation("add") disk_ctlr = VI.ns0.VirtualDisk_Def("disk_ctlr").pyclass() disk_backing = VI.ns0.VirtualDiskFlatVer2BackingInfo_Def( "disk_backing").pyclass() disk_backing.set_element_fileName(datastore_name) disk_backing.set_element_diskMode("persistent") if type != "thick": disk_backing.set_element_thinProvisioned(1) disk_ctlr.set_element_key(key) disk_ctlr.set_element_controllerKey(int(disk_ctrl_key)) disk_ctlr.set_element_unitNumber(int(disk_number)) disk_ctlr.set_element_backing(disk_backing) disk_ctlr.set_element_capacityInKB(int(size)) disk_spec.set_element_device(disk_ctlr) devices.append(disk_spec) def add_cdrom(module, s, config_target, config, devices, default_devs, type="client", vm_cd_iso_path=None): # Add a cd-rom # Make sure the datastore exists. if vm_cd_iso_path: iso_location = vm_cd_iso_path.split('/', 1) datastore, ds = find_datastore( module, s, iso_location[0], config_target) iso_path = iso_location[1] # find ide controller ide_ctlr = None for dev in default_devs: if dev.typecode.type[1] == "VirtualIDEController": ide_ctlr = dev # add a cdrom based on a physical device if ide_ctlr: cd_spec = config.new_deviceChange() cd_spec.set_element_operation('add') cd_ctrl = VI.ns0.VirtualCdrom_Def("cd_ctrl").pyclass() if type == "iso": iso = VI.ns0.VirtualCdromIsoBackingInfo_Def("iso").pyclass() ds_ref = iso.new_datastore(ds) ds_ref.set_attribute_type(ds.get_attribute_type()) iso.set_element_datastore(ds_ref) iso.set_element_fileName("%s %s" % (datastore, iso_path)) cd_ctrl.set_element_backing(iso) cd_ctrl.set_element_key(20) cd_ctrl.set_element_controllerKey(ide_ctlr.get_element_key()) cd_ctrl.set_element_unitNumber(0) cd_spec.set_element_device(cd_ctrl) elif type == "client": client = VI.ns0.VirtualCdromRemoteAtapiBackingInfo_Def( "client").pyclass() client.set_element_deviceName("") cd_ctrl.set_element_backing(client) cd_ctrl.set_element_key(20) cd_ctrl.set_element_controllerKey(ide_ctlr.get_element_key()) cd_ctrl.set_element_unitNumber(0) cd_spec.set_element_device(cd_ctrl) else: s.disconnect() module.fail_json( msg="Error adding cdrom of type %s to vm spec. " " cdrom type can either be iso or client" % (type)) devices.append(cd_spec) def add_floppy(module, s, config_target, config, devices, default_devs, type="image", vm_floppy_image_path=None): # Add a floppy # Make sure the datastore exists. if vm_floppy_image_path: image_location = vm_floppy_image_path.split('/', 1) datastore, ds = find_datastore( module, s, image_location[0], config_target) image_path = image_location[1] floppy_spec = config.new_deviceChange() floppy_spec.set_element_operation('add') floppy_ctrl = VI.ns0.VirtualFloppy_Def("floppy_ctrl").pyclass() if type == "image": image = VI.ns0.VirtualFloppyImageBackingInfo_Def("image").pyclass() ds_ref = image.new_datastore(ds) ds_ref.set_attribute_type(ds.get_attribute_type()) image.set_element_datastore(ds_ref) image.set_element_fileName("%s %s" % (datastore, image_path)) floppy_ctrl.set_element_backing(image) floppy_ctrl.set_element_key(3) floppy_spec.set_element_device(floppy_ctrl) elif type == "client": client = VI.ns0.VirtualFloppyRemoteDeviceBackingInfo_Def( "client").pyclass() client.set_element_deviceName("/dev/fd0") floppy_ctrl.set_element_backing(client) floppy_ctrl.set_element_key(3) floppy_spec.set_element_device(floppy_ctrl) else: s.disconnect() module.fail_json( msg="Error adding floppy of type %s to vm spec. " " floppy type can either be image or client" % (type)) devices.append(floppy_spec) def add_nic(module, s, nfmor, config, devices, nic_type="vmxnet3", network_name="VM Network", network_type="standard"): # add a NIC # Different network card types are: "VirtualE1000", # "VirtualE1000e","VirtualPCNet32", "VirtualVmxnet", "VirtualNmxnet2", # "VirtualVmxnet3" nic_spec = config.new_deviceChange() nic_spec.set_element_operation("add") if nic_type == "e1000": nic_ctlr = VI.ns0.VirtualE1000_Def("nic_ctlr").pyclass() elif nic_type == "e1000e": nic_ctlr = VI.ns0.VirtualE1000e_Def("nic_ctlr").pyclass() elif nic_type == "pcnet32": nic_ctlr = VI.ns0.VirtualPCNet32_Def("nic_ctlr").pyclass() elif nic_type == "vmxnet": nic_ctlr = VI.ns0.VirtualVmxnet_Def("nic_ctlr").pyclass() elif nic_type == "vmxnet2": nic_ctlr = VI.ns0.VirtualVmxnet2_Def("nic_ctlr").pyclass() elif nic_type == "vmxnet3": nic_ctlr = VI.ns0.VirtualVmxnet3_Def("nic_ctlr").pyclass() else: s.disconnect() module.fail_json( msg="Error adding nic to vm spec. No nic type of: %s" % (nic_type)) if network_type == "standard": nic_backing = VI.ns0.VirtualEthernetCardNetworkBackingInfo_Def( "nic_backing").pyclass() nic_backing.set_element_deviceName(network_name) elif network_type == "dvs": # Get the portgroup key portgroupKey = find_portgroup_key(module, s, nfmor, network_name) # Get the dvswitch uuid dvswitch_uuid = find_dvswitch_uuid(module, s, nfmor, portgroupKey) nic_backing_port = VI.ns0.DistributedVirtualSwitchPortConnection_Def( "nic_backing_port").pyclass() nic_backing_port.set_element_switchUuid(dvswitch_uuid) nic_backing_port.set_element_portgroupKey(portgroupKey) nic_backing = VI.ns0.VirtualEthernetCardDistributedVirtualPortBackingInfo_Def( "nic_backing").pyclass() nic_backing.set_element_port(nic_backing_port) else: s.disconnect() module.fail_json( msg="Error adding nic backing to vm spec. No network type of:" " %s" % (network_type)) nic_ctlr.set_element_addressType("generated") nic_ctlr.set_element_backing(nic_backing) nic_ctlr.set_element_key(4) nic_spec.set_element_device(nic_ctlr) devices.append(nic_spec) def find_datastore(module, s, datastore, config_target): # Verify the datastore exists and put it in brackets if it does. ds = None if config_target: for d in config_target.Datastore: if (d.Datastore.Accessible and (datastore and d.Datastore.Name == datastore) or (not datastore)): ds = d.Datastore.Datastore datastore = d.Datastore.Name break else: for ds_mor, ds_name in s.get_datastores().items(): ds_props = VIProperty(s, ds_mor) if (ds_props.summary.accessible and (datastore and ds_name == datastore) or (not datastore)): ds = ds_mor datastore = ds_name if not ds: s.disconnect() module.fail_json(msg="Datastore: %s does not appear to exist" % (datastore)) datastore_name = "[%s]" % datastore return datastore_name, ds def find_portgroup_key(module, s, nfmor, network_name): # Find a portgroups key given the portgroup name. # Grab all the distributed virtual portgroup's names and key's. dvpg_mors = s._retrieve_properties_traversal( property_names=['name', 'key'], from_node=nfmor, obj_type='DistributedVirtualPortgroup') # Get the correct portgroup managed object. dvpg_mor = None for dvpg in dvpg_mors: if dvpg_mor: break for p in dvpg.PropSet: if p.Name == "name" and p.Val == network_name: dvpg_mor = dvpg if dvpg_mor: break # If dvpg_mor is empty we didn't find the named portgroup. if dvpg_mor is None: s.disconnect() module.fail_json( msg="Could not find the distributed virtual portgroup named" " %s" % network_name) # Get the portgroup key portgroupKey = None for p in dvpg_mor.PropSet: if p.Name == "key": portgroupKey = p.Val return portgroupKey def find_dvswitch_uuid(module, s, nfmor, portgroupKey): # Find a dvswitch's uuid given a portgroup key. # Function searches all dvswitches in the datacenter to find the switch # that has the portgroup key. # Grab the dvswitch uuid and portgroup properties dvswitch_mors = s._retrieve_properties_traversal( property_names=['uuid', 'portgroup'], from_node=nfmor, obj_type='DistributedVirtualSwitch') dvswitch_mor = None # Get the dvswitches managed object for dvswitch in dvswitch_mors: if dvswitch_mor: break for p in dvswitch.PropSet: if p.Name == "portgroup": pg_mors = p.Val.ManagedObjectReference for pg_mor in pg_mors: if dvswitch_mor: break key_mor = s._get_object_properties( pg_mor, property_names=['key']) for key in key_mor.PropSet: if key.Val == portgroupKey: dvswitch_mor = dvswitch # Get the switches uuid dvswitch_uuid = None for p in dvswitch_mor.PropSet: if p.Name == "uuid": dvswitch_uuid = p.Val return dvswitch_uuid def spec_singleton(spec, request, vm): if not spec: _this = request.new__this(vm._mor) _this.set_attribute_type(vm._mor.get_attribute_type()) request.set_element__this(_this) spec = request.new_spec() return spec def get_cdrom_params(module, s, vm_cdrom): cdrom_type = None cdrom_iso_path = None try: cdrom_type = vm_cdrom['type'] except KeyError: s.disconnect() module.fail_json( msg="Error on %s definition. cdrom type needs to be" " specified." % vm_cdrom) if cdrom_type == 'iso': try: cdrom_iso_path = vm_cdrom['iso_path'] except KeyError: s.disconnect() module.fail_json( msg="Error on %s definition. cdrom iso_path needs" " to be specified." % vm_cdrom) return cdrom_type, cdrom_iso_path def vmdisk_id(vm, current_datastore_name): id_list = [] for vm_disk in vm._disks: if current_datastore_name in vm_disk['descriptor']: id_list.append(vm_disk['device']['key']) return id_list def deploy_template(vsphere_client, guest, resource_pool, template_src, esxi, module, cluster_name, snapshot_to_clone, power_on_after_clone, vm_extra_config): vmTemplate = vsphere_client.get_vm_by_name(template_src) vmTarget = None if esxi: datacenter = esxi['datacenter'] esxi_hostname = esxi['hostname'] # Datacenter managed object reference dclist = [k for k, v in vsphere_client.get_datacenters().items() if v == datacenter] if dclist: dcmor=dclist[0] else: vsphere_client.disconnect() module.fail_json(msg="Cannot find datacenter named: %s" % datacenter) dcprops = VIProperty(vsphere_client, dcmor) # hostFolder managed reference hfmor = dcprops.hostFolder._obj # Grab the computerResource name and host properties crmors = vsphere_client._retrieve_properties_traversal( property_names=['name', 'host'], from_node=hfmor, obj_type='ComputeResource') # Grab the host managed object reference of the esxi_hostname try: hostmor = [k for k, v in vsphere_client.get_hosts().items() if v == esxi_hostname][0] except IndexError: vsphere_client.disconnect() module.fail_json(msg="Cannot find esx host named: %s" % esxi_hostname) # Grab the computeResource managed object reference of the host we are # creating the VM on. crmor = None for cr in crmors: if crmor: break for p in cr.PropSet: if p.Name == "host": for h in p.Val.get_element_ManagedObjectReference(): if h == hostmor: crmor = cr.Obj break if crmor: break crprops = VIProperty(vsphere_client, crmor) rpmor = crprops.resourcePool._obj elif resource_pool: try: cluster = [k for k, v in vsphere_client.get_clusters().items() if v == cluster_name][0] if cluster_name else None except IndexError: vsphere_client.disconnect() module.fail_json(msg="Cannot find Cluster named: %s" % cluster_name) try: rpmor = [k for k, v in vsphere_client.get_resource_pools( from_mor=cluster).items() if v == resource_pool][0] except IndexError: vsphere_client.disconnect() module.fail_json(msg="Cannot find Resource Pool named: %s" % resource_pool) else: module.fail_json(msg="You need to specify either esxi:[datacenter,hostname] or [cluster,resource_pool]") try: vmTarget = vsphere_client.get_vm_by_name(guest) except Exception: pass if not vmTemplate.is_powered_off(): module.fail_json( msg="Source %s must be powered off" % template_src ) try: if not vmTarget: cloneArgs = dict(resourcepool=rpmor, power_on=power_on_after_clone) if snapshot_to_clone is not None: #check if snapshot_to_clone is specified, Create a Linked Clone instead of a full clone. cloneArgs["linked"] = True cloneArgs["snapshot"] = snapshot_to_clone if vm_extra_config.get("folder") is not None: # if a folder is specified, clone the VM into it cloneArgs["folder"] = vm_extra_config.get("folder") vmTemplate.clone(guest, **cloneArgs) changed = True else: changed = False vsphere_client.disconnect() module.exit_json(changed=changed) except Exception as e: module.fail_json( msg="Could not clone selected machine: %s" % e ) # example from https://github.com/kalazzerx/pysphere/blob/master/examples/pysphere_create_disk_and_add_to_vm.py # was used. def update_disks(vsphere_client, vm, module, vm_disk, changes): request = VI.ReconfigVM_TaskRequestMsg() changed = False for cnf_disk in vm_disk: disk_id = re.sub("disk", "", cnf_disk) found = False for dev_key in vm._devices: if vm._devices[dev_key]['type'] == 'VirtualDisk': hdd_id = vm._devices[dev_key]['label'].split()[2] if disk_id == hdd_id: found = True continue if not found: it = VI.ReconfigVM_TaskRequestMsg() _this = request.new__this(vm._mor) _this.set_attribute_type(vm._mor.get_attribute_type()) request.set_element__this(_this) spec = request.new_spec() dc = spec.new_deviceChange() dc.Operation = "add" dc.FileOperation = "create" hd = VI.ns0.VirtualDisk_Def("hd").pyclass() hd.Key = -100 hd.UnitNumber = int(disk_id) hd.CapacityInKB = int(vm_disk[cnf_disk]['size_gb']) * 1024 * 1024 hd.ControllerKey = 1000 # module.fail_json(msg="peos : %s" % vm_disk[cnf_disk]) backing = VI.ns0.VirtualDiskFlatVer2BackingInfo_Def("backing").pyclass() backing.FileName = "[%s]" % vm_disk[cnf_disk]['datastore'] backing.DiskMode = "persistent" backing.Split = False backing.WriteThrough = False backing.ThinProvisioned = False backing.EagerlyScrub = False hd.Backing = backing dc.Device = hd spec.DeviceChange = [dc] request.set_element_spec(spec) ret = vsphere_client._proxy.ReconfigVM_Task(request)._returnval # Wait for the task to finish task = VITask(ret, vsphere_client) status = task.wait_for_state([task.STATE_SUCCESS, task.STATE_ERROR]) if status == task.STATE_SUCCESS: changed = True changes[cnf_disk] = vm_disk[cnf_disk] elif status == task.STATE_ERROR: module.fail_json( msg="Error reconfiguring vm: %s, [%s]" % ( task.get_error_message(), vm_disk[cnf_disk])) return changed, changes def reconfigure_vm(vsphere_client, vm, module, esxi, resource_pool, cluster_name, guest, vm_extra_config, vm_hardware, vm_disk, vm_nic, state, force): spec = None changed = False changes = {} request = None shutdown = False poweron = vm.is_powered_on() devices = [] memoryHotAddEnabled = bool(vm.properties.config.memoryHotAddEnabled) cpuHotAddEnabled = bool(vm.properties.config.cpuHotAddEnabled) cpuHotRemoveEnabled = bool(vm.properties.config.cpuHotRemoveEnabled) changed, changes = update_disks(vsphere_client, vm, module, vm_disk, changes) request = VI.ReconfigVM_TaskRequestMsg() # Change extra config if vm_extra_config: spec = spec_singleton(spec, request, vm) extra_config = [] for k,v in vm_extra_config.iteritems(): ec = spec.new_extraConfig() ec.set_element_key(str(k)) ec.set_element_value(str(v)) extra_config.append(ec) spec.set_element_extraConfig(extra_config) changes["extra_config"] = vm_extra_config # Change Memory if 'memory_mb' in vm_hardware: if int(vm_hardware['memory_mb']) != vm.properties.config.hardware.memoryMB: spec = spec_singleton(spec, request, vm) if vm.is_powered_on(): if force: # No hot add but force if not memoryHotAddEnabled: shutdown = True elif int(vm_hardware['memory_mb']) < vm.properties.config.hardware.memoryMB: shutdown = True else: # Fail on no hot add and no force if not memoryHotAddEnabled: module.fail_json( msg="memoryHotAdd is not enabled. force is " "required for shutdown") # Fail on no force and memory shrink elif int(vm_hardware['memory_mb']) < vm.properties.config.hardware.memoryMB: module.fail_json( msg="Cannot lower memory on a live VM. force is " "required for shutdown") # set the new RAM size spec.set_element_memoryMB(int(vm_hardware['memory_mb'])) changes['memory'] = vm_hardware['memory_mb'] # ===( Reconfigure Network )====# if vm_nic: changed = reconfigure_net(vsphere_client, vm, module, esxi, resource_pool, guest, vm_nic, cluster_name) # Change Num CPUs if 'num_cpus' in vm_hardware: if int(vm_hardware['num_cpus']) != vm.properties.config.hardware.numCPU: spec = spec_singleton(spec, request, vm) if vm.is_powered_on(): if force: # No hot add but force if not cpuHotAddEnabled: shutdown = True elif int(vm_hardware['num_cpus']) < vm.properties.config.hardware.numCPU: if not cpuHotRemoveEnabled: shutdown = True else: # Fail on no hot add and no force if not cpuHotAddEnabled: module.fail_json( msg="cpuHotAdd is not enabled. force is " "required for shutdown") # Fail on no force and cpu shrink without hot remove elif int(vm_hardware['num_cpus']) < vm.properties.config.hardware.numCPU: if not cpuHotRemoveEnabled: module.fail_json( msg="Cannot lower CPU on a live VM without " "cpuHotRemove. force is required for shutdown") spec.set_element_numCPUs(int(vm_hardware['num_cpus'])) changes['cpu'] = vm_hardware['num_cpus'] # Change CDROM if 'vm_cdrom' in vm_hardware: spec = spec_singleton(spec, request, vm) cdrom_type, cdrom_iso_path = get_cdrom_params(module, vsphere_client, vm_hardware['vm_cdrom']) cdrom = None current_devices = vm.properties.config.hardware.device for dev in current_devices: if dev._type == 'VirtualCdrom': cdrom = dev._obj break if cdrom_type == 'iso': iso_location = cdrom_iso_path.split('/', 1) datastore, ds = find_datastore( module, vsphere_client, iso_location[0], None) iso_path = iso_location[1] iso = VI.ns0.VirtualCdromIsoBackingInfo_Def('iso').pyclass() iso.set_element_fileName('%s %s' % (datastore, iso_path)) cdrom.set_element_backing(iso) cdrom.Connectable.set_element_connected(True) cdrom.Connectable.set_element_startConnected(True) elif cdrom_type == 'client': client = VI.ns0.VirtualCdromRemoteAtapiBackingInfo_Def('client').pyclass() client.set_element_deviceName("") cdrom.set_element_backing(client) cdrom.Connectable.set_element_connected(True) cdrom.Connectable.set_element_startConnected(True) else: vsphere_client.disconnect() module.fail_json( msg="Error adding cdrom of type %s to vm spec. " " cdrom type can either be iso or client" % (cdrom_type)) dev_change = spec.new_deviceChange() dev_change.set_element_device(cdrom) dev_change.set_element_operation('edit') devices.append(dev_change) changes['cdrom'] = vm_hardware['vm_cdrom'] # Resize hard drives if vm_disk: spec = spec_singleton(spec, request, vm) # Get a list of the VM's hard drives dev_list = [d for d in vm.properties.config.hardware.device if d._type=='VirtualDisk'] if len(vm_disk) > len(dev_list): vsphere_client.disconnect() module.fail_json(msg="Error in vm_disk definition. Too many disks defined in comparison to the VM's disk profile.") disk_num = 0 dev_changes = [] disks_changed = {} for disk in sorted(vm_disk.iterkeys()): try: disksize = int(vm_disk[disk]['size_gb']) # Convert the disk size to kilobytes disksize = disksize * 1024 * 1024 except (KeyError, ValueError): vsphere_client.disconnect() module.fail_json(msg="Error in '%s' definition. Size needs to be specified as an integer." % disk) # Make sure the new disk size is higher than the current value dev = dev_list[disk_num] if disksize < int(dev.capacityInKB): vsphere_client.disconnect() module.fail_json(msg="Error in '%s' definition. New size needs to be higher than the current value (%s GB)." % (disk, int(dev.capacityInKB) / 1024 / 1024)) # Set the new disk size elif disksize > int(dev.capacityInKB): dev_obj = dev._obj dev_obj.set_element_capacityInKB(disksize) dev_change = spec.new_deviceChange() dev_change.set_element_operation("edit") dev_change.set_element_device(dev_obj) dev_changes.append(dev_change) disks_changed[disk] = {'size_gb': int(vm_disk[disk]['size_gb'])} disk_num = disk_num + 1 if dev_changes: spec.set_element_deviceChange(dev_changes) changes['disks'] = disks_changed if len(changes): if shutdown and vm.is_powered_on(): try: vm.power_off(sync_run=True) vm.get_status() except Exception: e = get_exception() module.fail_json( msg='Failed to shutdown vm %s: %s' % (guest, e) ) if len(devices): spec.set_element_deviceChange(devices) request.set_element_spec(spec) ret = vsphere_client._proxy.ReconfigVM_Task(request)._returnval # Wait for the task to finish task = VITask(ret, vsphere_client) status = task.wait_for_state([task.STATE_SUCCESS, task.STATE_ERROR]) if status == task.STATE_SUCCESS: changed = True elif status == task.STATE_ERROR: module.fail_json( msg="Error reconfiguring vm: %s" % task.get_error_message()) if vm.is_powered_off() and poweron: try: vm.power_on(sync_run=True) except Exception: e = get_exception() module.fail_json( msg='Failed to power on vm %s : %s' % (guest, e) ) vsphere_client.disconnect() if changed: module.exit_json(changed=True, changes=changes) module.exit_json(changed=False) def reconfigure_net(vsphere_client, vm, module, esxi, resource_pool, guest, vm_nic, cluster_name=None): s = vsphere_client nics = {} request = VI.ReconfigVM_TaskRequestMsg() _this = request.new__this(vm._mor) _this.set_attribute_type(vm._mor.get_attribute_type()) request.set_element__this(_this) nic_changes = [] datacenter = esxi['datacenter'] # Datacenter managed object reference dclist = [k for k, v in vsphere_client.get_datacenters().items() if v == datacenter] if dclist: dcmor=dclist[0] else: vsphere_client.disconnect() module.fail_json(msg="Cannot find datacenter named: %s" % datacenter) dcprops = VIProperty(vsphere_client, dcmor) nfmor = dcprops.networkFolder._obj for k,v in vm_nic.iteritems(): nicNum = k[len(k) -1] if vm_nic[k]['network_type'] == 'dvs': portgroupKey = find_portgroup_key(module, s, nfmor, vm_nic[k]['network']) todvs = True elif vm_nic[k]['network_type'] == 'standard': todvs = False # Detect cards that need to be changed and network type (and act accordingly) for dev in vm.properties.config.hardware.device: if dev._type in ["VirtualE1000", "VirtualE1000e", "VirtualPCNet32", "VirtualVmxnet", "VirtualNmxnet2", "VirtualVmxnet3"]: devNum = dev.deviceInfo.label[len(dev.deviceInfo.label) - 1] if devNum == nicNum: fromdvs = dev.deviceInfo.summary.split(':')[0] == 'DVSwitch' if todvs and fromdvs: if dev.backing.port._obj.get_element_portgroupKey() != portgroupKey: nics[k] = (dev, portgroupKey, 1) elif fromdvs and not todvs: nics[k] = (dev, '', 2) elif not fromdvs and todvs: nics[k] = (dev, portgroupKey, 3) elif not fromdvs and not todvs: if dev.backing._obj.get_element_deviceName() != vm_nic[k]['network']: nics[k] = (dev, '', 2) else: pass else: module.exit_json() if len(nics) > 0: for nic, obj in nics.iteritems(): """ 1,2 and 3 are used to mark which action should be taken 1 = from a distributed switch to a distributed switch 2 = to a standard switch 3 = to a distributed switch """ dev = obj[0] pgKey = obj[1] dvsKey = obj[2] if dvsKey == 1: dev.backing.port._obj.set_element_portgroupKey(pgKey) dev.backing.port._obj.set_element_portKey('') if dvsKey == 3: dvswitch_uuid = find_dvswitch_uuid(module, s, nfmor, pgKey) nic_backing_port = VI.ns0.DistributedVirtualSwitchPortConnection_Def( "nic_backing_port").pyclass() nic_backing_port.set_element_switchUuid(dvswitch_uuid) nic_backing_port.set_element_portgroupKey(pgKey) nic_backing_port.set_element_portKey('') nic_backing = VI.ns0.VirtualEthernetCardDistributedVirtualPortBackingInfo_Def( "nic_backing").pyclass() nic_backing.set_element_port(nic_backing_port) dev._obj.set_element_backing(nic_backing) if dvsKey == 2: nic_backing = VI.ns0.VirtualEthernetCardNetworkBackingInfo_Def( "nic_backing").pyclass() nic_backing.set_element_deviceName(vm_nic[nic]['network']) dev._obj.set_element_backing(nic_backing) for nic, obj in nics.iteritems(): dev = obj[0] spec = request.new_spec() nic_change = spec.new_deviceChange() nic_change.set_element_device(dev._obj) nic_change.set_element_operation("edit") nic_changes.append(nic_change) spec.set_element_deviceChange(nic_changes) request.set_element_spec(spec) ret = vsphere_client._proxy.ReconfigVM_Task(request)._returnval task = VITask(ret, vsphere_client) status = task.wait_for_state([task.STATE_SUCCESS, task.STATE_ERROR]) if status == task.STATE_SUCCESS: return(True) elif status == task.STATE_ERROR: module.fail_json(msg="Could not change network %s" % task.get_error_message()) elif len(nics) == 0: return(False) def _build_folder_tree(nodes, parent): tree = {} for node in nodes: if node['parent'] == parent: tree[node['name']] = dict.copy(node) tree[node['name']]['subfolders'] = _build_folder_tree(nodes, node['id']) del tree[node['name']]['parent'] return tree def _find_path_in_tree(tree, path): for name, o in tree.iteritems(): if name == path[0]: if len(path) == 1: return o else: return _find_path_in_tree(o['subfolders'], path[1:]) return None def _get_folderid_for_path(vsphere_client, datacenter, path): content = vsphere_client._retrieve_properties_traversal(property_names=['name', 'parent'], obj_type=MORTypes.Folder) if not content: return {} node_list = [ { 'id': o.Obj, 'name': o.PropSet[0].Val, 'parent': (o.PropSet[1].Val if len(o.PropSet) > 1 else None) } for o in content ] tree = _build_folder_tree(node_list, datacenter) tree = _find_path_in_tree(tree, ['vm'])['subfolders'] folder = _find_path_in_tree(tree, path.split('/')) return folder['id'] if folder else None def create_vm(vsphere_client, module, esxi, resource_pool, cluster_name, guest, vm_extra_config, vm_hardware, vm_disk, vm_nic, vm_hw_version, state): datacenter = esxi['datacenter'] esxi_hostname = esxi['hostname'] # Datacenter managed object reference dclist = [k for k, v in vsphere_client.get_datacenters().items() if v == datacenter] if dclist: dcmor=dclist[0] else: vsphere_client.disconnect() module.fail_json(msg="Cannot find datacenter named: %s" % datacenter) dcprops = VIProperty(vsphere_client, dcmor) # hostFolder managed reference hfmor = dcprops.hostFolder._obj # virtualmachineFolder managed object reference if vm_extra_config.get('folder'): # try to find the folder by its full path, e.g. 'production/customerA/lamp' vmfmor = _get_folderid_for_path(vsphere_client, dcmor, vm_extra_config.get('folder')) # try the legacy behaviour of just matching the folder name, so 'lamp' alone matches 'production/customerA/lamp' if vmfmor is None: for mor, name in vsphere_client._get_managed_objects(MORTypes.Folder).iteritems(): if name == vm_extra_config['folder']: vmfmor = mor # if neither of strategies worked, bail out if vmfmor is None: vsphere_client.disconnect() module.fail_json(msg="Cannot find folder named: %s" % vm_extra_config['folder']) else: vmfmor = dcprops.vmFolder._obj # networkFolder managed object reference nfmor = dcprops.networkFolder._obj # Grab the computerResource name and host properties crmors = vsphere_client._retrieve_properties_traversal( property_names=['name', 'host'], from_node=hfmor, obj_type='ComputeResource') # Grab the host managed object reference of the esxi_hostname try: hostmor = [k for k, v in vsphere_client.get_hosts().items() if v == esxi_hostname][0] except IndexError: vsphere_client.disconnect() module.fail_json(msg="Cannot find esx host named: %s" % esxi_hostname) # Grab the computerResource managed object reference of the host we are # creating the VM on. crmor = None for cr in crmors: if crmor: break for p in cr.PropSet: if p.Name == "host": for h in p.Val.get_element_ManagedObjectReference(): if h == hostmor: crmor = cr.Obj break if crmor: break crprops = VIProperty(vsphere_client, crmor) # Get resource pool managed reference # Requires that a cluster name be specified. if resource_pool: try: cluster = [k for k, v in vsphere_client.get_clusters().items() if v == cluster_name][0] if cluster_name else None except IndexError: vsphere_client.disconnect() module.fail_json(msg="Cannot find Cluster named: %s" % cluster_name) try: rpmor = [k for k, v in vsphere_client.get_resource_pools( from_mor=cluster).items() if v == resource_pool][0] except IndexError: vsphere_client.disconnect() module.fail_json(msg="Cannot find Resource Pool named: %s" % resource_pool) else: rpmor = crprops.resourcePool._obj # CREATE VM CONFIGURATION # get config target request = VI.QueryConfigTargetRequestMsg() _this = request.new__this(crprops.environmentBrowser._obj) _this.set_attribute_type( crprops.environmentBrowser._obj.get_attribute_type()) request.set_element__this(_this) h = request.new_host(hostmor) h.set_attribute_type(hostmor.get_attribute_type()) request.set_element_host(h) config_target = vsphere_client._proxy.QueryConfigTarget(request)._returnval # get default devices request = VI.QueryConfigOptionRequestMsg() _this = request.new__this(crprops.environmentBrowser._obj) _this.set_attribute_type( crprops.environmentBrowser._obj.get_attribute_type()) request.set_element__this(_this) h = request.new_host(hostmor) h.set_attribute_type(hostmor.get_attribute_type()) request.set_element_host(h) config_option = vsphere_client._proxy.QueryConfigOption(request)._returnval default_devs = config_option.DefaultDevice # add parameters to the create vm task create_vm_request = VI.CreateVM_TaskRequestMsg() config = create_vm_request.new_config() if vm_hw_version: config.set_element_version(vm_hw_version) vmfiles = config.new_files() datastore_name, ds = find_datastore( module, vsphere_client, vm_disk['disk1']['datastore'], config_target) vmfiles.set_element_vmPathName(datastore_name) config.set_element_files(vmfiles) config.set_element_name(guest) if 'notes' in vm_extra_config: config.set_element_annotation(vm_extra_config['notes']) config.set_element_memoryMB(int(vm_hardware['memory_mb'])) config.set_element_numCPUs(int(vm_hardware['num_cpus'])) config.set_element_guestId(vm_hardware['osid']) devices = [] # Attach all the hardware we want to the VM spec. # Add a scsi controller to the VM spec. disk_ctrl_key = add_scsi_controller( module, vsphere_client, config, devices, vm_hardware['scsi']) if vm_disk: disk_num = 0 disk_key = 0 for disk in sorted(vm_disk.iterkeys()): try: datastore = vm_disk[disk]['datastore'] except KeyError: vsphere_client.disconnect() module.fail_json( msg="Error on %s definition. datastore needs to be" " specified." % disk) try: disksize = int(vm_disk[disk]['size_gb']) # Convert the disk size to kiloboytes disksize = disksize * 1024 * 1024 except (KeyError, ValueError): vsphere_client.disconnect() module.fail_json(msg="Error on %s definition. size needs to be specified as an integer." % disk) try: disktype = vm_disk[disk]['type'] except KeyError: vsphere_client.disconnect() module.fail_json( msg="Error on %s definition. type needs to be" " specified." % disk) # Add the disk to the VM spec. add_disk( module, vsphere_client, config_target, config, devices, datastore, disktype, disksize, disk_ctrl_key, disk_num, disk_key) disk_num = disk_num + 1 disk_key = disk_key + 1 if 'vm_cdrom' in vm_hardware: cdrom_type, cdrom_iso_path = get_cdrom_params(module, vsphere_client, vm_hardware['vm_cdrom']) # Add a CD-ROM device to the VM. add_cdrom(module, vsphere_client, config_target, config, devices, default_devs, cdrom_type, cdrom_iso_path) if 'vm_floppy' in vm_hardware: floppy_image_path = None floppy_type = None try: floppy_type = vm_hardware['vm_floppy']['type'] except KeyError: vsphere_client.disconnect() module.fail_json( msg="Error on %s definition. floppy type needs to be" " specified." % vm_hardware['vm_floppy']) if floppy_type == 'image': try: floppy_image_path = vm_hardware['vm_floppy']['image_path'] except KeyError: vsphere_client.disconnect() module.fail_json( msg="Error on %s definition. floppy image_path needs" " to be specified." % vm_hardware['vm_floppy']) # Add a floppy to the VM. add_floppy(module, vsphere_client, config_target, config, devices, default_devs, floppy_type, floppy_image_path) if vm_nic: for nic in sorted(vm_nic.iterkeys()): try: nictype = vm_nic[nic]['type'] except KeyError: vsphere_client.disconnect() module.fail_json( msg="Error on %s definition. type needs to be " " specified." % nic) try: network = vm_nic[nic]['network'] except KeyError: vsphere_client.disconnect() module.fail_json( msg="Error on %s definition. network needs to be " " specified." % nic) try: network_type = vm_nic[nic]['network_type'] except KeyError: vsphere_client.disconnect() module.fail_json( msg="Error on %s definition. network_type needs to be " " specified." % nic) # Add the nic to the VM spec. add_nic(module, vsphere_client, nfmor, config, devices, nictype, network, network_type) config.set_element_deviceChange(devices) create_vm_request.set_element_config(config) folder_mor = create_vm_request.new__this(vmfmor) folder_mor.set_attribute_type(vmfmor.get_attribute_type()) create_vm_request.set_element__this(folder_mor) rp_mor = create_vm_request.new_pool(rpmor) rp_mor.set_attribute_type(rpmor.get_attribute_type()) create_vm_request.set_element_pool(rp_mor) host_mor = create_vm_request.new_host(hostmor) host_mor.set_attribute_type(hostmor.get_attribute_type()) create_vm_request.set_element_host(host_mor) # CREATE THE VM taskmor = vsphere_client._proxy.CreateVM_Task(create_vm_request)._returnval task = VITask(taskmor, vsphere_client) task.wait_for_state([task.STATE_SUCCESS, task.STATE_ERROR]) if task.get_state() == task.STATE_ERROR: vsphere_client.disconnect() module.fail_json(msg="Error creating vm: %s" % task.get_error_message()) else: # We always need to get the vm because we are going to gather facts vm = vsphere_client.get_vm_by_name(guest) # VM was created. If there is any extra config options specified, set # them here , disconnect from vcenter, then exit. if vm_extra_config: vm.set_extra_config(vm_extra_config) # Power on the VM if it was requested power_state(vm, state, True) vmfacts=gather_facts(vm) vsphere_client.disconnect() module.exit_json( ansible_facts=vmfacts, changed=True, changes="Created VM %s" % guest) def delete_vm(vsphere_client, module, guest, vm, force): try: if vm.is_powered_on(): if force: try: vm.power_off(sync_run=True) vm.get_status() except Exception: e = get_exception() module.fail_json( msg='Failed to shutdown vm %s: %s' % (guest, e)) else: module.fail_json( msg='You must use either shut the vm down first or ' 'use force ') # Invoke Destroy_Task request = VI.Destroy_TaskRequestMsg() _this = request.new__this(vm._mor) _this.set_attribute_type(vm._mor.get_attribute_type()) request.set_element__this(_this) ret = vsphere_client._proxy.Destroy_Task(request)._returnval task = VITask(ret, vsphere_client) # Wait for the task to finish status = task.wait_for_state( [task.STATE_SUCCESS, task.STATE_ERROR]) if status == task.STATE_ERROR: vsphere_client.disconnect() module.fail_json(msg="Error removing vm: %s %s" % task.get_error_message()) module.exit_json(changed=True, changes="VM %s deleted" % guest) except Exception: e = get_exception() module.fail_json( msg='Failed to delete vm %s : %s' % (guest, e)) def power_state(vm, state, force): """ Correctly set the power status for a VM determined by the current and requested states. force is forceful """ 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: return get_exception() 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_power_status': vm.get_status(), '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, 'hw_interfaces':[], } netInfo = vm.get_property('net') netDict = {} if netInfo: for net in netInfo: netDict[net['mac_address']] = net['ip_addresses'] 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, 'ipaddresses': netDict.get(entry.macAddress, None), 'macaddress_dash': entry.macAddress.replace(':', '-'), 'summary': entry.deviceInfo.summary, } facts['hw_interfaces'].append('eth'+str(ifidx)) ifidx += 1 return facts class DefaultVMConfig(object): """ Shallow and deep dict comparison for interfaces """ 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): try: if v == int: self.check_dict[key][k] = int(self.check_dict[key][k]) elif v == basestring: self.check_dict[key][k] = str(self.check_dict[key][k]) else: raise ValueError except ValueError: self.recursive_missing.append((k, v)) else: self.recursive_missing.append((k, v)) return self.recursive_missing def config_check(name, passed, default, module): """ Checks that the dict passed for VM configuration matches the required interface declared at the top of __main__ """ 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, 'scsi': basestring, 'osid': basestring } proto_vm_disk = { 'disk1': { 'datastore': basestring, 'size_gb': int, 'type': basestring } } proto_vm_nic = { 'nic1': { 'type': basestring, 'network': basestring, 'network_type': basestring } } proto_esxi = { 'datacenter': basestring, 'hostname': basestring } module = AnsibleModule( argument_spec=dict( vcenter_hostname=dict( type='str', default=os.environ.get('VMWARE_HOST') ), username=dict( type='str', default=os.environ.get('VMWARE_USER') ), password=dict( type='str', no_log=True, default=os.environ.get('VMWARE_PASSWORD') ), state=dict( required=False, choices=[ 'powered_on', 'powered_off', 'present', 'absent', 'restarted', 'reconfigured' ], default='present'), vmware_guest_facts=dict(required=False, type='bool'), from_template=dict(required=False, type='bool'), template_src=dict(required=False, type='str'), snapshot_to_clone=dict(required=False, default=None, type='str'), guest=dict(required=True, type='str'), vm_disk=dict(required=False, type='dict', default={}), 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={}), vm_hw_version=dict(required=False, default=None, type='str'), resource_pool=dict(required=False, default=None, type='str'), cluster=dict(required=False, default=None, type='str'), force=dict(required=False, type='bool', default=False), esxi=dict(required=False, type='dict', default={}), validate_certs=dict(required=False, type='bool', default=True), power_on_after_clone=dict(required=False, type='bool', default=True) ), supports_check_mode=False, mutually_exclusive=[['state', 'vmware_guest_facts'],['state', 'from_template']], required_together=[ ['state', 'force'], [ 'state', 'vm_disk', 'vm_nic', 'vm_hardware', 'esxi' ], ['from_template', 'template_src'], ], ) if not HAS_PYSPHERE: 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_nic = module.params['vm_nic'] vm_hardware = module.params['vm_hardware'] vm_extra_config = module.params['vm_extra_config'] vm_hw_version = module.params['vm_hw_version'] esxi = module.params['esxi'] resource_pool = module.params['resource_pool'] cluster = module.params['cluster'] template_src = module.params['template_src'] from_template = module.params['from_template'] snapshot_to_clone = module.params['snapshot_to_clone'] power_on_after_clone = module.params['power_on_after_clone'] validate_certs = module.params['validate_certs'] # CONNECT TO THE SERVER viserver = VIServer() if validate_certs and not hasattr(ssl, 'SSLContext') and not vcenter_hostname.startswith('http://'): module.fail_json(msg='pysphere does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task') try: viserver.connect(vcenter_hostname, username, password) except ssl.SSLError as sslerr: if '[SSL: CERTIFICATE_VERIFY_FAILED]' in sslerr.strerror: if not validate_certs: default_context = ssl._create_default_https_context ssl._create_default_https_context = ssl._create_unverified_context viserver.connect(vcenter_hostname, username, password) else: module.fail_json(msg='Unable to validate the certificate of the vcenter host %s' % vcenter_hostname) else: raise except VIApiException: err = get_exception() 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) except Exception: pass if vm: # Run for facts only if vmware_guest_facts: try: module.exit_json(ansible_facts=gather_facts(vm)) except Exception: e = get_exception() 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': reconfigure_vm( vsphere_client=viserver, vm=vm, module=module, esxi=esxi, resource_pool=resource_pool, cluster_name=cluster, guest=guest, vm_extra_config=vm_extra_config, vm_hardware=vm_hardware, vm_disk=vm_disk, vm_nic=vm_nic, state=state, force=force ) elif state == 'absent': delete_vm( vsphere_client=viserver, module=module, guest=guest, vm=vm, force=force) # VM doesn't exist else: # Fail for fact gather task if vmware_guest_facts: module.fail_json( msg="No such VM %s. Fact gathering requires an existing vm" % guest) elif from_template: deploy_template( vsphere_client=viserver, esxi=esxi, resource_pool=resource_pool, guest=guest, template_src=template_src, module=module, cluster_name=cluster, snapshot_to_clone=snapshot_to_clone, power_on_after_clone=power_on_after_clone, vm_extra_config=vm_extra_config ) if state in ['restarted', 'reconfigured']: module.fail_json( msg="No such VM %s. States [" "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 in ['present', 'powered_off', 'powered_on']: # 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) create_vm( vsphere_client=viserver, module=module, esxi=esxi, resource_pool=resource_pool, cluster_name=cluster, guest=guest, vm_extra_config=vm_extra_config, vm_hardware=vm_hardware, vm_disk=vm_disk, vm_nic=vm_nic, vm_hw_version=vm_hw_version, state=state ) viserver.disconnect() module.exit_json( changed=False, vcenter=vcenter_hostname) # this is magic, see lib/ansible/module_common.py from ansible.module_utils.basic import * if __name__ == '__main__': main()