From 3dca5d510a195304e7e4b1d362575c8206e08a73 Mon Sep 17 00:00:00 2001 From: Richard Hoop Date: Mon, 21 Apr 2014 16:38:31 -0400 Subject: [PATCH 1/7] Large cleanups. Reintegrating all modules together for a guest --- cloud/vsphere_guest | 328 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 cloud/vsphere_guest diff --git a/cloud/vsphere_guest b/cloud/vsphere_guest new file mode 100644 index 00000000000..cf75e3946e3 --- /dev/null +++ b/cloud/vsphere_guest @@ -0,0 +1,328 @@ +#!/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 + + +def main(): + + vm = None + + 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'), + guest_config=dict(required=False, type='dict', default={}), + force=dict(required=False, choices=BOOLEANS, default=False), + + ), + supports_check_mode=False, + mutually_exclusive=[['state', 'vmware_guest_facts']], + required_together=[ + ['state', 'force'], + ['state', 'guest_config'] + ], + ) + + 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'] + guest_config = module.params['guest_config'] + + # 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' and not len(guest_config): + module.exit_json(changed=False) + + # Fail on reconfig without params + elif state == 'reconfigured': + if not len(guest_config): + module.fail_json( + msg="guest_config is required to reconfigure a VM") + # do it + else: + 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': + pass + + 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 +#<> +main() From a956522363952a84f70f257cecf94ea56854f4e8 Mon Sep 17 00:00:00 2001 From: Richard Hoop Date: Tue, 22 Apr 2014 16:20:04 -0400 Subject: [PATCH 2/7] More config checks and clean up for vm_config settings --- cloud/vsphere_guest | 116 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 11 deletions(-) diff --git a/cloud/vsphere_guest b/cloud/vsphere_guest index cf75e3946e3..4bc0ded2e83 100644 --- a/cloud/vsphere_guest +++ b/cloud/vsphere_guest @@ -201,10 +201,80 @@ def gather_facts(vm): 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'), @@ -223,15 +293,34 @@ def main(): default='present'), vmware_guest_facts=dict(required=False, choices=BOOLEANS), guest=dict(required=True, type='str'), - guest_config=dict(required=False, type='dict', default={}), + 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', 'guest_config'] + [ + 'state', + 'vm_disk', + 'vm_boot_state', + 'vm_nic', + 'vm_hardware', + 'esxi' + ] ], ) @@ -250,7 +339,12 @@ def main(): state = module.params['state'] guest = module.params['guest'] force = module.params['force'] - guest_config = module.params['guest_config'] + 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() @@ -283,17 +377,12 @@ def main(): module.exit_json(changed=state_result) # Just check if there - elif state == 'present' and not len(guest_config): + elif state == 'present': module.exit_json(changed=False) # Fail on reconfig without params elif state == 'reconfigured': - if not len(guest_config): - module.fail_json( - msg="guest_config is required to reconfigure a VM") - # do it - else: - pass + pass # VM doesn't exist except Exception: @@ -312,7 +401,12 @@ def main(): # Create the VM elif state == 'present': - pass + + # 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 From b81d5d532efbe9632b13d31789032dc1eb9999cb Mon Sep 17 00:00:00 2001 From: Richard Hoop Date: Tue, 22 Apr 2014 17:12:23 -0400 Subject: [PATCH 3/7] Creation complete --- cloud/vsphere_guest | 682 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 546 insertions(+), 136 deletions(-) diff --git a/cloud/vsphere_guest b/cloud/vsphere_guest index 4bc0ded2e83..f39810cf53c 100644 --- a/cloud/vsphere_guest +++ b/cloud/vsphere_guest @@ -10,124 +10,520 @@ try: 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 -''' +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 + + +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(bus_num) + scsi_ctrl.set_element_key(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(disk_ctrl_key) + disk_ctlr.set_element_unitNumber(disk_number) + disk_ctlr.set_element_backing(disk_backing) + disk_ctlr.set_element_capacityInKB(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_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 + 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 + 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 create_vm(vsphere_client, + module, + esxi, + resource_pool, + cluster_name, + guest, + vm_extra_config, + vm_hardware, + vm_disk, + vm_nic, + state): + + datacenter = esxi['datacenter'] + esxi_hostname = esxi['hostname'] + # Datacenter managed object reference + dcmor = [k for k, + v in vsphere_client.get_datacenters().items() if v == datacenter][0] + + if dcmor is None: + 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 + 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, e: + 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] + except IndexError, e: + 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, e: + vsphere_client.disconnect() + module.fail_json(msg="Cannot find Resource Pool named: %s" % + resource_pool) -def power_state(vm, state, force): + 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() + 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 vm_extra_config['notes'] is not None: + config.set_element_annotation(vm_extra_config['notes']) + config.set_element_memoryMB(vm_hardware['memory_mb']) + config.set_element_numCPUs(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 = vm_disk[disk]['size_gb'] + # Convert the disk size to kiloboytes + disksize = disksize * 1024 * 1024 + except KeyError: + vsphere_client.disconnect() + module.fail_json( + msg="Error on %s definition. size needs to be" + " specified." % 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_iso_path = None + cdrom_type = None + try: + cdrom_type = vm_hardware['vm_cdrom']['type'] + except KeyError: + vsphere_client.disconnect() + module.fail_json( + msg="Error on %s definition. cdrom type needs to be" + " specified." % vm_hardware['vm_cdrom']) + if cdrom_type == 'iso': + try: + cdrom_iso_path = vm_hardware['vm_cdrom']['iso_path'] + except KeyError: + vsphere_client.disconnect() + module.fail_json( + msg="Error on %s definition. cdrom iso_path needs" + " to be specified." % 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_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: + vm = None + if vm_extra_config or state in ['powered_on', 'powered_off']: + 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) + + +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() @@ -203,6 +599,10 @@ def gather_facts(vm): 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( @@ -230,6 +630,10 @@ class DefaultVMConfig(object): 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()): @@ -251,11 +655,14 @@ def main(): proto_vm_hardware = { 'memory_mb': int, - 'num_cpus': int + 'num_cpus': int, + 'scsi': basestring, + 'osid': basestring } proto_vm_disk = { 'disk1': { + 'datastore': basestring, 'size_gb': int, 'type': basestring } @@ -270,7 +677,6 @@ def main(): } proto_esxi = { - 'datastore': basestring, 'datacenter': basestring, 'hostname': basestring } @@ -294,16 +700,11 @@ def main(): 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={}), + resource_pool=dict(required=False, default=None, type='str'), + cluster=dict(required=False, default=None, type='str'), force=dict(required=False, choices=BOOLEANS, default=False), esxi=dict(required=False, type='dict', default={}), @@ -316,20 +717,15 @@ def main(): [ 'state', 'vm_disk', - 'vm_boot_state', 'vm_nic', 'vm_hardware', 'esxi' - ] + ], + ['resource_pool', 'cluster'] ], ) - 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: + if not HAS_PYSPHERE: module.fail_json(msg='pysphere module required') vcenter_hostname = module.params['vcenter_hostname'] @@ -340,11 +736,12 @@ def main(): 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'] + resource_pool = module.params['resource_pool'] + cluster = module.params['cluster'] # CONNECT TO THE SERVER viserver = VIServer() @@ -392,15 +789,15 @@ def main(): module.fail_json( msg="No such VM %s. Fact gathering requires an existing vm" % guest) - if state not in ['absent', 'present']: + if state in ['restarted', 'reconfigured']: module.fail_json( - msg="No such VM %s. States [powered_on, powered_off, " + 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 == 'present': + elif state in ['present', 'powered_off', 'powered_on']: # Check the guest_config config_check("vm_disk", vm_disk, proto_vm_disk, module) @@ -408,6 +805,19 @@ def main(): 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, + state=state + ) if vm: # If the vm already exists, lets get some info from it, pass back the # vm's vmware_guest_facts and then exit. From 149a740a1ca6dccda864a2a9f23a2a7c052a1146 Mon Sep 17 00:00:00 2001 From: Richard Hoop Date: Wed, 23 Apr 2014 09:53:12 -0400 Subject: [PATCH 4/7] Started reconfigured --- cloud/vsphere_guest | 114 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 101 insertions(+), 13 deletions(-) diff --git a/cloud/vsphere_guest b/cloud/vsphere_guest index f39810cf53c..0223cb12cdb 100644 --- a/cloud/vsphere_guest +++ b/cloud/vsphere_guest @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python # -*- coding: utf-8 -*- @@ -273,17 +273,92 @@ def find_dvswitch_uuid(module, s, nfmor, portgroupKey): return dvswitch_uuid -def create_vm(vsphere_client, - module, - esxi, - resource_pool, - cluster_name, - guest, - vm_extra_config, - vm_hardware, - vm_disk, - vm_nic, - state): +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 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 reconfigure_vm(vsphere_client, vm, module, esxi, resource_pool, cluster_name, guest, vm_extra_config, vm_hardware, vm_disk, vm_nic, state): + module.fail_json(msg=vm.properties.config) + spec = None + changed = False + changes = {} + request = VI.ReconfigVM_TaskRequestMsg() + + + if vm_hardware['memory_mb']: + if vm_hardware['memory_mb'] != vm.properties.config.hardware.memoryMB: + spec = spec_singleton(spec, request, vm) + + shutdown = True + # set the new RAM size + spec.set_element_memoryMB(vm_hardware['memory_mb']) + changes['memory'] = vm_hardware['memory_mb'] + + # ====( Config Memory )====# + if vm_hardware['num_cpus']: + if vm_hardware['num_cpus'] != vm.properties.config.hardware.numCPU: + spec = spec_singleton(spec, request, vm) + + shutdown = True + spec.set_element_numCPUs(vm_hardware['num_cpus']) + spec.set_element_numCoresPerSocket(vm_hardware['num_cpus']) + + changes['cpu'] = vm_hardware['num_cpus'] + + if len(changes): + + if shutdown and vm.is_powered_on(): + try: + vm.power_off(sync_run=True) + vm.get_status() + + except Exception, e: + module.fail_json( + msg='Failed to shutdown vm %s: %s' % (guest, e) + ) + + 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(): + try: + vm.power_on(sync_run=True) + except Exception, e: + module.fail_json( + msg='Failed to power on vm %s : %s' % (guest, e) + ) + # ====( Done )====# + vsphere_client.disconnect() + if changed: + module.exit_json(changed=True, changes=changes) + + module.exit_json(changed=False) + + +def create_vm(vsphere_client, module, esxi, resource_pool, cluster_name, guest, vm_extra_config, vm_hardware, vm_disk, vm_nic, state): datacenter = esxi['datacenter'] esxi_hostname = esxi['hostname'] @@ -779,7 +854,20 @@ def main(): # Fail on reconfig without params elif state == 'reconfigured': - pass + 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 + ) # VM doesn't exist except Exception: From f09ecf223963d339f79ad53f04846ef7dd9fc0be Mon Sep 17 00:00:00 2001 From: Richard Hoop Date: Wed, 23 Apr 2014 15:26:03 -0400 Subject: [PATCH 5/7] Core complete --- cloud/vsphere_guest | 121 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 12 deletions(-) diff --git a/cloud/vsphere_guest b/cloud/vsphere_guest index 0223cb12cdb..25367890973 100644 --- a/cloud/vsphere_guest +++ b/cloud/vsphere_guest @@ -291,19 +291,43 @@ def vmdisk_id(vm, current_datastore_name): return id_list -def reconfigure_vm(vsphere_client, vm, module, esxi, resource_pool, cluster_name, guest, vm_extra_config, vm_hardware, vm_disk, vm_nic, state): - module.fail_json(msg=vm.properties.config) +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 = VI.ReconfigVM_TaskRequestMsg() + shutdown = False + memoryHotAddEnabled = bool(vm.properties.config.memoryHotAddEnabled) + cpuHotAddEnabled = bool(vm.properties.config.cpuHotAddEnabled) + cpuHotRemoveEnabled = bool(vm.properties.config.cpuHotRemoveEnabled) + # Change Memory if vm_hardware['memory_mb']: + if vm_hardware['memory_mb'] != vm.properties.config.hardware.memoryMB: spec = spec_singleton(spec, request, vm) - shutdown = True + if vm.is_powered_on(): + if force: + # No hot add but force + if not memoryHotAddEnabled: + shutdown = True + elif 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 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(vm_hardware['memory_mb']) changes['memory'] = vm_hardware['memory_mb'] @@ -313,9 +337,29 @@ def reconfigure_vm(vsphere_client, vm, module, esxi, resource_pool, cluster_name if vm_hardware['num_cpus'] != vm.properties.config.hardware.numCPU: spec = spec_singleton(spec, request, vm) - shutdown = True + if vm.is_powered_on(): + if force: + # No hot add but force + if not cpuHotAddEnabled: + shutdown = True + elif 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 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(vm_hardware['num_cpus']) - spec.set_element_numCoresPerSocket(vm_hardware['num_cpus']) changes['cpu'] = vm_hardware['num_cpus'] @@ -350,7 +394,7 @@ def reconfigure_vm(vsphere_client, vm, module, esxi, resource_pool, cluster_name module.fail_json( msg='Failed to power on vm %s : %s' % (guest, e) ) - # ====( Done )====# + vsphere_client.disconnect() if changed: module.exit_json(changed=True, changes=changes) @@ -593,6 +637,50 @@ def create_vm(vsphere_client, module, esxi, resource_pool, cluster_name, guest, # Power on the VM if it was requested power_state(vm, state, True) + vsphere_client.disconnect() + module.exit_json( + ansible_facts=gather_facts(vm), + 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: + 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: + module.fail_json( + msg='Failed to delete vm %s : %s' % (guest, e)) + def power_state(vm, state, force): """ @@ -829,7 +917,10 @@ def main(): # 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: @@ -866,11 +957,19 @@ def main(): vm_hardware=vm_hardware, vm_disk=vm_disk, vm_nic=vm_nic, - state=state + state=state, + force=force ) + elif state == 'absent': + delete_vm( + vsphere_client=viserver, + module=module, + guest=guest, + vm=vm, + force=force) # VM doesn't exist - except Exception: + else: # Fail for fact gather task if vmware_guest_facts: @@ -906,10 +1005,8 @@ def main(): vm_nic=vm_nic, state=state ) - 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() + + viserver.disconnect() module.exit_json( changed=False, vcenter=vcenter_hostname) From f6e6b313b7e338fc1bc2388b9011090537d8a5b9 Mon Sep 17 00:00:00 2001 From: Richard Hoop Date: Wed, 23 Apr 2014 16:44:49 -0400 Subject: [PATCH 6/7] Ready for pull request --- cloud/vsphere_guest | 190 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/cloud/vsphere_guest b/cloud/vsphere_guest index 25367890973..37082da97db 100644 --- a/cloud/vsphere_guest +++ b/cloud/vsphere_guest @@ -20,6 +20,196 @@ try: except ImportError: pass +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.5" +options: + vcenter_hostname: + description: + - The hostname of the vcenter server the module will connect to, to create the guest. + required: true + default: null + aliases: [] + guest: + description: + - The virtual server name you wish to manage. + required: true + user: + 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. + required: false + default: null + state: + description: + - Indicate desired state of the vm. + default: present + choices: ['present', 'powered_on', 'absent', 'powered_on', 'restarted', 'reconfigured'] + 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 + 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. +requirements: [ 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 + +- 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 + 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: 2048 + num_cpus: 2 + osid: centos64Guest + scsi: paravirtual + 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 + +# 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 + +- hw_eth0: + - addresstype: "assigned" + label: "Network adapter 1" + macaddress: "00:22:33:33:44:55" + macaddress_dash: "00-22-33-33-44-55" + summary: "VM Network" + hw_guest_full_name: "newvm001" + hw_guest_id: "rhel6_64Guest" + hw_memtotal_mb: 2048 + hw_name: "centos64Guest" + hw_processor_count: 2 + hw_product_uuid: "ef50bac8-2845-40ff-81d9-675315501dac" + +# Remove a vm from vSphere +# The VM must be powered_off of 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 From 2364ede3fcd8d64f9236d32aeef71d2ed67fd3cd Mon Sep 17 00:00:00 2001 From: Richard Hoop Date: Wed, 23 Apr 2014 16:53:12 -0400 Subject: [PATCH 7/7] Author --- cloud/vsphere_guest | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/vsphere_guest b/cloud/vsphere_guest index 37082da97db..fbb8f008fd2 100644 --- a/cloud/vsphere_guest +++ b/cloud/vsphere_guest @@ -102,6 +102,7 @@ options: 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 requirements: [ pysphere ] '''