2015-10-01 18:29:21 +02:00
|
|
|
#!/usr/bin/env python2
|
2014-04-21 22:38:31 +02:00
|
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
2015-07-04 05:57:53 +02:00
|
|
|
# This file is part of Ansible
|
|
|
|
#
|
|
|
|
# Ansible is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# Ansible is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
2014-04-21 22:38:31 +02:00
|
|
|
|
|
|
|
# TODO:
|
|
|
|
# Ability to set CPU/Memory reservations
|
|
|
|
|
|
|
|
try:
|
|
|
|
import json
|
|
|
|
except ImportError:
|
|
|
|
import simplejson as json
|
|
|
|
|
2014-04-22 23:12:23 +02:00
|
|
|
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
|
|
|
|
|
2014-04-23 22:44:49 +02:00
|
|
|
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
|
2014-04-24 05:46:14 +02:00
|
|
|
version_added: "1.6"
|
2014-04-23 22:44:49 +02:00
|
|
|
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
|
2014-10-31 21:13:41 +01:00
|
|
|
username:
|
2014-04-23 22:44:49 +02:00
|
|
|
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:
|
2014-09-07 00:04:06 +02:00
|
|
|
- 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
|
2014-04-23 22:44:49 +02:00
|
|
|
required: false
|
|
|
|
default: null
|
|
|
|
state:
|
|
|
|
description:
|
2015-05-22 19:21:56 +02:00
|
|
|
- Indicate desired state of the vm. 'reconfigured' only applies changes to 'memory_mb' and 'num_cpus' in vm_hardware parameter, and only when hot-plugging is enabled for the guest.
|
2014-04-23 22:44:49 +02:00
|
|
|
default: present
|
2014-11-13 22:01:26 +01:00
|
|
|
choices: ['present', 'powered_off', 'absent', 'powered_on', 'restarted', 'reconfigured']
|
2014-09-30 11:20:59 +02:00
|
|
|
from_template:
|
2015-01-06 22:06:45 +01:00
|
|
|
version_added: "1.9"
|
2014-09-30 11:20:59 +02:00
|
|
|
description:
|
2015-07-07 21:30:33 +02:00
|
|
|
- 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.
|
2014-12-16 20:50:41 +01:00
|
|
|
default: no
|
|
|
|
choices: ['yes', 'no']
|
2014-09-30 11:20:59 +02:00
|
|
|
template_src:
|
2015-01-06 22:06:45 +01:00
|
|
|
version_added: "1.9"
|
2014-09-30 11:20:59 +02:00
|
|
|
description:
|
2014-12-16 20:50:41 +01:00
|
|
|
- Name of the source template to deploy from
|
|
|
|
default: None
|
2015-07-09 22:46:39 +02:00
|
|
|
snapshot_to_clone:
|
2015-01-31 16:24:44 +01:00
|
|
|
description:
|
2015-07-14 23:03:21 +02:00
|
|
|
- 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"
|
2015-01-31 16:24:44 +01:00
|
|
|
required: false
|
|
|
|
default: none
|
2015-07-26 01:07:36 +02:00
|
|
|
power_on_after_clone:
|
|
|
|
description:
|
|
|
|
- Specifies if the VM should be powered on after the clone.
|
|
|
|
required: false
|
|
|
|
default: yes
|
|
|
|
choices: ['yes', 'no']
|
2014-04-23 22:44:49 +02:00
|
|
|
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
|
2014-05-29 23:57:41 +02:00
|
|
|
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
|
2014-07-10 15:53:56 +02:00
|
|
|
version_added: "1.7"
|
2014-04-23 22:44:49 +02:00
|
|
|
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.
|
2015-06-15 20:41:22 +02:00
|
|
|
author: "Richard Hoop (@rhoop) <wrhoop@gmail.com>"
|
2015-05-11 21:15:53 +02:00
|
|
|
requirements:
|
|
|
|
- "python >= 2.6"
|
|
|
|
- pysphere
|
2014-04-23 22:44:49 +02:00
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2015-09-01 21:53:11 +02:00
|
|
|
# Note: vm_floppy support added in 2.0
|
2014-04-23 22:44:49 +02:00
|
|
|
|
|
|
|
- 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
|
2015-10-03 20:31:22 +02:00
|
|
|
folder: MyFolder
|
2014-04-23 22:44:49 +02:00
|
|
|
vm_disk:
|
|
|
|
disk1:
|
|
|
|
size_gb: 10
|
|
|
|
type: thin
|
|
|
|
datastore: storage001
|
2015-11-26 13:32:27 +01:00
|
|
|
# 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
|
2014-04-23 22:44:49 +02:00
|
|
|
vm_nic:
|
|
|
|
nic1:
|
|
|
|
type: vmxnet3
|
|
|
|
network: VM Network
|
|
|
|
network_type: standard
|
2015-05-20 17:01:29 +02:00
|
|
|
nic2:
|
|
|
|
type: vmxnet3
|
|
|
|
network: dvSwitch Network
|
|
|
|
network_type: dvs
|
2014-04-23 22:44:49 +02:00
|
|
|
vm_hardware:
|
|
|
|
memory_mb: 2048
|
|
|
|
num_cpus: 2
|
|
|
|
osid: centos64Guest
|
|
|
|
scsi: paravirtual
|
2015-05-21 13:52:52 +02:00
|
|
|
vm_cdrom:
|
|
|
|
type: "iso"
|
|
|
|
iso_path: "DatastoreName/cd-image.iso"
|
2015-09-01 17:55:10 +02:00
|
|
|
vm_floppy:
|
|
|
|
type: "image"
|
|
|
|
image_path: "DatastoreName/floppy-image.flp"
|
2014-04-23 22:44:49 +02:00
|
|
|
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
|
|
|
|
|
2014-09-30 11:20:59 +02:00
|
|
|
# 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"
|
2015-10-03 20:31:22 +02:00
|
|
|
vm_extra_config:
|
|
|
|
folder: MyFolder
|
2014-09-30 11:20:59 +02:00
|
|
|
|
2014-04-23 22:44:49 +02:00
|
|
|
# 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
|
2014-09-30 11:20:59 +02:00
|
|
|
# If vmware tools is not installed, ipadresses with return None
|
2014-04-23 22:44:49 +02:00
|
|
|
|
|
|
|
- hw_eth0:
|
|
|
|
- addresstype: "assigned"
|
|
|
|
label: "Network adapter 1"
|
|
|
|
macaddress: "00:22:33:33:44:55"
|
|
|
|
macaddress_dash: "00-22-33-33-44-55"
|
2014-09-30 11:20:59 +02:00
|
|
|
ipaddresses: ['192.0.2.100', '2001:DB8:56ff:feac:4d8a']
|
2014-04-23 22:44:49 +02:00
|
|
|
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
|
2014-11-08 00:20:49 +01:00
|
|
|
# The VM must be powered_off or you need to use force to force a shutdown
|
2014-04-23 22:44:49 +02:00
|
|
|
|
|
|
|
- vsphere_guest:
|
|
|
|
vcenter_hostname: vcenter.mydomain.local
|
|
|
|
username: myuser
|
|
|
|
password: mypass
|
|
|
|
guest: newvm001
|
|
|
|
state: absent
|
|
|
|
force: yes
|
|
|
|
'''
|
2014-04-22 23:12:23 +02:00
|
|
|
|
|
|
|
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))
|
|
|
|
|
2014-04-28 18:40:00 +02:00
|
|
|
scsi_ctrl.set_element_busNumber(int(bus_num))
|
|
|
|
scsi_ctrl.set_element_key(int(disk_ctrl_key))
|
2014-04-22 23:12:23 +02:00
|
|
|
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)
|
2014-04-28 18:40:00 +02:00
|
|
|
disk_ctlr.set_element_controllerKey(int(disk_ctrl_key))
|
|
|
|
disk_ctlr.set_element_unitNumber(int(disk_number))
|
2014-04-22 23:12:23 +02:00
|
|
|
disk_ctlr.set_element_backing(disk_backing)
|
2014-04-28 18:40:00 +02:00
|
|
|
disk_ctlr.set_element_capacityInKB(int(size))
|
2014-04-22 23:12:23 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2015-09-01 17:55:10 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2014-04-22 23:12:23 +02:00
|
|
|
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
|
2015-06-19 12:04:19 +02:00
|
|
|
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 server.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
|
2014-04-22 23:12:23 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2014-04-23 15:53:12 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2015-10-03 20:31:22 +02:00
|
|
|
def deploy_template(vsphere_client, guest, resource_pool, template_src, esxi, module, cluster_name, snapshot_to_clone, power_on_after_clone, vm_extra_config):
|
2014-09-30 11:20:59 +02:00
|
|
|
vmTemplate = vsphere_client.get_vm_by_name(template_src)
|
|
|
|
vmTarget = None
|
|
|
|
|
2015-06-18 12:55:54 +02:00
|
|
|
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, e:
|
|
|
|
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,
|
2015-12-15 12:45:51 +01:00
|
|
|
v in vsphere_client.get_clusters().items() if v == cluster_name][0] if cluster_name else None
|
2015-06-18 12:55:54 +02:00
|
|
|
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)
|
|
|
|
else:
|
|
|
|
module.fail_json(msg="You need to specify either esxi:[datacenter,hostname] or [cluster,resource_pool]")
|
2014-09-30 11:20:59 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
vmTarget = vsphere_client.get_vm_by_name(guest)
|
|
|
|
except Exception:
|
|
|
|
pass
|
2015-07-26 01:07:36 +02:00
|
|
|
|
|
|
|
if not vmTemplate.is_powered_off():
|
2014-09-30 11:20:59 +02:00
|
|
|
module.fail_json(
|
2015-07-26 01:07:36 +02:00
|
|
|
msg="Source %s must be powered off" % template_src
|
2014-09-30 11:20:59 +02:00
|
|
|
)
|
2015-07-26 01:07:36 +02:00
|
|
|
|
2014-09-30 11:20:59 +02:00
|
|
|
try:
|
2015-07-26 01:07:36 +02:00
|
|
|
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
|
|
|
|
|
2015-10-03 20:31:22 +02:00
|
|
|
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")
|
|
|
|
|
2015-07-26 01:07:36 +02:00
|
|
|
vmTemplate.clone(guest, **cloneArgs)
|
2015-01-31 16:24:44 +01:00
|
|
|
changed = True
|
2014-09-30 11:20:59 +02:00
|
|
|
else:
|
2015-07-26 01:07:36 +02:00
|
|
|
changed = False
|
2015-01-31 16:24:44 +01:00
|
|
|
|
2014-09-30 11:20:59 +02:00
|
|
|
vsphere_client.disconnect()
|
|
|
|
module.exit_json(changed=changed)
|
|
|
|
except Exception as e:
|
|
|
|
module.fail_json(
|
|
|
|
msg="Could not clone selected machine: %s" % e
|
|
|
|
)
|
|
|
|
|
2015-10-01 18:38:46 +02:00
|
|
|
# example from https://github.com/kalazzerx/pysphere/blob/master/examples/pysphere_create_disk_and_add_to_vm.py
|
|
|
|
# was used.
|
2015-10-01 18:29:21 +02:00
|
|
|
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
|
|
|
|
|
2014-09-30 11:20:59 +02:00
|
|
|
|
2014-04-23 21:26:03 +02:00
|
|
|
def reconfigure_vm(vsphere_client, vm, module, esxi, resource_pool, cluster_name, guest, vm_extra_config, vm_hardware, vm_disk, vm_nic, state, force):
|
2014-04-23 15:53:12 +02:00
|
|
|
spec = None
|
|
|
|
changed = False
|
|
|
|
changes = {}
|
2015-10-01 18:29:21 +02:00
|
|
|
request = None
|
2014-04-23 21:26:03 +02:00
|
|
|
shutdown = False
|
2015-06-18 20:33:16 +02:00
|
|
|
poweron = vm.is_powered_on()
|
2014-04-23 15:53:12 +02:00
|
|
|
|
2014-04-23 21:26:03 +02:00
|
|
|
memoryHotAddEnabled = bool(vm.properties.config.memoryHotAddEnabled)
|
|
|
|
cpuHotAddEnabled = bool(vm.properties.config.cpuHotAddEnabled)
|
|
|
|
cpuHotRemoveEnabled = bool(vm.properties.config.cpuHotRemoveEnabled)
|
2014-04-23 15:53:12 +02:00
|
|
|
|
2015-10-01 18:29:21 +02:00
|
|
|
changed, changes = update_disks(vsphere_client, vm,
|
|
|
|
module, vm_disk, changes)
|
|
|
|
request = VI.ReconfigVM_TaskRequestMsg()
|
|
|
|
|
2014-04-23 21:26:03 +02:00
|
|
|
# Change Memory
|
2015-06-18 13:23:43 +02:00
|
|
|
if 'memory_mb' in vm_hardware:
|
2014-04-23 21:26:03 +02:00
|
|
|
|
2014-06-23 17:16:03 +02:00
|
|
|
if int(vm_hardware['memory_mb']) != vm.properties.config.hardware.memoryMB:
|
2014-04-23 15:53:12 +02:00
|
|
|
spec = spec_singleton(spec, request, vm)
|
|
|
|
|
2014-04-23 21:26:03 +02:00
|
|
|
if vm.is_powered_on():
|
|
|
|
if force:
|
|
|
|
# No hot add but force
|
|
|
|
if not memoryHotAddEnabled:
|
|
|
|
shutdown = True
|
2014-06-23 17:16:03 +02:00
|
|
|
elif int(vm_hardware['memory_mb']) < vm.properties.config.hardware.memoryMB:
|
2014-04-23 21:26:03 +02:00
|
|
|
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
|
2014-06-23 17:16:03 +02:00
|
|
|
elif int(vm_hardware['memory_mb']) < vm.properties.config.hardware.memoryMB:
|
2014-04-23 21:26:03 +02:00
|
|
|
module.fail_json(
|
|
|
|
msg="Cannot lower memory on a live VM. force is "
|
|
|
|
"required for shutdown")
|
|
|
|
|
2014-04-23 15:53:12 +02:00
|
|
|
# set the new RAM size
|
2014-04-28 18:40:00 +02:00
|
|
|
spec.set_element_memoryMB(int(vm_hardware['memory_mb']))
|
2014-04-23 15:53:12 +02:00
|
|
|
changes['memory'] = vm_hardware['memory_mb']
|
2015-11-04 14:22:08 +01:00
|
|
|
# ===( Reconfigure Network )====#
|
|
|
|
if vm_nic:
|
|
|
|
changed = reconfigure_net(vsphere_client, vm, module, esxi, resource_pool, guest, vm_nic, cluster_name)
|
2014-04-23 15:53:12 +02:00
|
|
|
|
|
|
|
# ====( Config Memory )====#
|
2015-06-18 13:23:43 +02:00
|
|
|
if 'num_cpus' in vm_hardware:
|
2014-06-23 17:16:03 +02:00
|
|
|
if int(vm_hardware['num_cpus']) != vm.properties.config.hardware.numCPU:
|
2014-04-23 15:53:12 +02:00
|
|
|
spec = spec_singleton(spec, request, vm)
|
|
|
|
|
2014-04-23 21:26:03 +02:00
|
|
|
if vm.is_powered_on():
|
|
|
|
if force:
|
|
|
|
# No hot add but force
|
|
|
|
if not cpuHotAddEnabled:
|
|
|
|
shutdown = True
|
2014-06-23 17:16:03 +02:00
|
|
|
elif int(vm_hardware['num_cpus']) < vm.properties.config.hardware.numCPU:
|
2014-04-23 21:26:03 +02:00
|
|
|
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
|
2014-06-23 17:16:03 +02:00
|
|
|
elif int(vm_hardware['num_cpus']) < vm.properties.config.hardware.numCPU:
|
2014-04-23 21:26:03 +02:00
|
|
|
if not cpuHotRemoveEnabled:
|
|
|
|
module.fail_json(
|
|
|
|
msg="Cannot lower CPU on a live VM without "
|
|
|
|
"cpuHotRemove. force is required for shutdown")
|
|
|
|
|
2014-04-28 18:40:00 +02:00
|
|
|
spec.set_element_numCPUs(int(vm_hardware['num_cpus']))
|
2014-04-23 15:53:12 +02:00
|
|
|
|
|
|
|
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())
|
|
|
|
|
2015-06-18 20:33:16 +02:00
|
|
|
if vm.is_powered_off() and poweron:
|
2014-04-23 15:53:12 +02:00
|
|
|
try:
|
|
|
|
vm.power_on(sync_run=True)
|
|
|
|
except Exception, e:
|
|
|
|
module.fail_json(
|
|
|
|
msg='Failed to power on vm %s : %s' % (guest, e)
|
|
|
|
)
|
2014-04-23 21:26:03 +02:00
|
|
|
|
2014-04-23 15:53:12 +02:00
|
|
|
vsphere_client.disconnect()
|
|
|
|
if changed:
|
|
|
|
module.exit_json(changed=True, changes=changes)
|
|
|
|
|
|
|
|
module.exit_json(changed=False)
|
|
|
|
|
|
|
|
|
2015-11-04 14:22:08 +01:00
|
|
|
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)
|
|
|
|
|
2015-11-26 13:32:27 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2014-05-29 23:57:41 +02:00
|
|
|
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):
|
2014-04-22 23:12:23 +02:00
|
|
|
|
|
|
|
datacenter = esxi['datacenter']
|
|
|
|
esxi_hostname = esxi['hostname']
|
|
|
|
# Datacenter managed object reference
|
2014-07-11 05:05:23 +02:00
|
|
|
dclist = [k for k,
|
|
|
|
v in vsphere_client.get_datacenters().items() if v == datacenter]
|
|
|
|
if dclist:
|
|
|
|
dcmor=dclist[0]
|
|
|
|
else:
|
2014-04-22 23:12:23 +02:00
|
|
|
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
|
2014-12-24 13:39:48 +01:00
|
|
|
if vm_extra_config.get('folder'):
|
2015-11-26 13:32:27 +01:00
|
|
|
# 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:
|
2014-09-30 11:20:59 +02:00
|
|
|
vsphere_client.disconnect()
|
|
|
|
module.fail_json(msg="Cannot find folder named: %s" % vm_extra_config['folder'])
|
|
|
|
else:
|
|
|
|
vmfmor = dcprops.vmFolder._obj
|
2014-04-22 23:12:23 +02:00
|
|
|
|
|
|
|
# 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,
|
2015-12-15 12:45:51 +01:00
|
|
|
v in vsphere_client.get_clusters().items() if v == cluster_name][0] if cluster_name else None
|
2014-04-22 23:12:23 +02:00
|
|
|
except IndexError, e:
|
|
|
|
vsphere_client.disconnect()
|
|
|
|
module.fail_json(msg="Cannot find Cluster named: %s" %
|
|
|
|
cluster_name)
|
2014-04-21 22:38:31 +02:00
|
|
|
|
2014-04-22 23:12:23 +02:00
|
|
|
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)
|
2014-04-21 22:38:31 +02:00
|
|
|
|
2014-04-22 23:12:23 +02:00
|
|
|
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()
|
2014-05-29 23:57:41 +02:00
|
|
|
if vm_hw_version:
|
|
|
|
config.set_element_version(vm_hw_version)
|
2014-04-22 23:12:23 +02:00
|
|
|
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)
|
2014-07-11 05:05:23 +02:00
|
|
|
if 'notes' in vm_extra_config:
|
2014-04-22 23:12:23 +02:00
|
|
|
config.set_element_annotation(vm_extra_config['notes'])
|
2014-04-28 18:40:00 +02:00
|
|
|
config.set_element_memoryMB(int(vm_hardware['memory_mb']))
|
|
|
|
config.set_element_numCPUs(int(vm_hardware['num_cpus']))
|
2014-04-22 23:12:23 +02:00
|
|
|
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:
|
2014-05-13 22:13:13 +02:00
|
|
|
disksize = int(vm_disk[disk]['size_gb'])
|
2014-04-22 23:12:23 +02:00
|
|
|
# Convert the disk size to kiloboytes
|
|
|
|
disksize = disksize * 1024 * 1024
|
2014-05-13 22:13:13 +02:00
|
|
|
except (KeyError, ValueError):
|
2014-04-22 23:12:23 +02:00
|
|
|
vsphere_client.disconnect()
|
2014-05-13 22:13:13 +02:00
|
|
|
module.fail_json(msg="Error on %s definition. size needs to be specified as an integer." % disk)
|
2014-04-22 23:12:23 +02:00
|
|
|
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)
|
2015-09-01 17:55:10 +02:00
|
|
|
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)
|
2014-04-22 23:12:23 +02:00
|
|
|
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:
|
2014-07-11 05:05:23 +02:00
|
|
|
# We always need to get the vm because we are going to gather facts
|
|
|
|
vm = vsphere_client.get_vm_by_name(guest)
|
2014-04-21 22:38:31 +02:00
|
|
|
|
2014-04-22 23:12:23 +02:00
|
|
|
# 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)
|
|
|
|
|
2014-04-23 21:26:03 +02:00
|
|
|
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))
|
|
|
|
|
2014-04-22 23:12:23 +02:00
|
|
|
|
|
|
|
def power_state(vm, state, force):
|
|
|
|
"""
|
|
|
|
Correctly set the power status for a VM determined by the current and
|
|
|
|
requested states. force is forceful
|
|
|
|
"""
|
2014-04-21 22:38:31 +02:00
|
|
|
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,
|
2015-07-20 23:39:31 +02:00
|
|
|
'hw_interfaces':[],
|
2014-04-21 22:38:31 +02:00
|
|
|
}
|
2014-09-30 11:20:59 +02:00
|
|
|
netInfo = vm.get_property('net')
|
|
|
|
netDict = {}
|
|
|
|
if netInfo:
|
|
|
|
for net in netInfo:
|
|
|
|
netDict[net['mac_address']] = net['ip_addresses']
|
2014-04-21 22:38:31 +02:00
|
|
|
|
|
|
|
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,
|
2014-09-30 11:20:59 +02:00
|
|
|
'ipaddresses': netDict.get(entry.macAddress, None),
|
2014-04-21 22:38:31 +02:00
|
|
|
'macaddress_dash': entry.macAddress.replace(':', '-'),
|
|
|
|
'summary': entry.deviceInfo.summary,
|
|
|
|
}
|
2015-07-20 23:39:31 +02:00
|
|
|
facts['hw_interfaces'].append('eth'+str(ifidx))
|
2014-04-21 22:38:31 +02:00
|
|
|
|
|
|
|
ifidx += 1
|
|
|
|
|
|
|
|
return facts
|
|
|
|
|
|
|
|
|
2014-04-22 22:20:04 +02:00
|
|
|
class DefaultVMConfig(object):
|
|
|
|
|
2014-04-22 23:12:23 +02:00
|
|
|
"""
|
|
|
|
Shallow and deep dict comparison for interfaces
|
|
|
|
"""
|
|
|
|
|
2014-04-22 22:20:04 +02:00
|
|
|
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):
|
2014-05-14 22:56:08 +02:00
|
|
|
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))
|
2014-04-22 22:20:04 +02:00
|
|
|
else:
|
|
|
|
self.recursive_missing.append((k, v))
|
|
|
|
|
|
|
|
return self.recursive_missing
|
|
|
|
|
|
|
|
|
|
|
|
def config_check(name, passed, default, module):
|
2014-04-22 23:12:23 +02:00
|
|
|
"""
|
|
|
|
Checks that the dict passed for VM configuration matches the required
|
|
|
|
interface declared at the top of __main__
|
|
|
|
"""
|
2014-04-22 22:20:04 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2014-04-21 22:38:31 +02:00
|
|
|
def main():
|
|
|
|
|
|
|
|
vm = None
|
|
|
|
|
2014-04-22 22:20:04 +02:00
|
|
|
proto_vm_hardware = {
|
|
|
|
'memory_mb': int,
|
2014-04-22 23:12:23 +02:00
|
|
|
'num_cpus': int,
|
|
|
|
'scsi': basestring,
|
|
|
|
'osid': basestring
|
2014-04-22 22:20:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
proto_vm_disk = {
|
|
|
|
'disk1': {
|
2014-04-22 23:12:23 +02:00
|
|
|
'datastore': basestring,
|
2014-04-22 22:20:04 +02:00
|
|
|
'size_gb': int,
|
|
|
|
'type': basestring
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
proto_vm_nic = {
|
|
|
|
'nic1': {
|
|
|
|
'type': basestring,
|
|
|
|
'network': basestring,
|
|
|
|
'network_type': basestring
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
proto_esxi = {
|
|
|
|
'datacenter': basestring,
|
|
|
|
'hostname': basestring
|
|
|
|
}
|
|
|
|
|
2014-04-21 22:38:31 +02:00
|
|
|
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'),
|
2015-01-31 16:24:44 +01:00
|
|
|
vmware_guest_facts=dict(required=False, type='bool'),
|
|
|
|
from_template=dict(required=False, type='bool'),
|
2014-09-30 11:20:59 +02:00
|
|
|
template_src=dict(required=False, type='str'),
|
2015-07-09 22:46:39 +02:00
|
|
|
snapshot_to_clone=dict(required=False, default=None, type='str'),
|
2014-04-21 22:38:31 +02:00
|
|
|
guest=dict(required=True, type='str'),
|
2014-04-22 22:20:04 +02:00
|
|
|
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={}),
|
2014-05-29 23:57:41 +02:00
|
|
|
vm_hw_version=dict(required=False, default=None, type='str'),
|
2014-04-22 23:12:23 +02:00
|
|
|
resource_pool=dict(required=False, default=None, type='str'),
|
|
|
|
cluster=dict(required=False, default=None, type='str'),
|
2015-01-31 16:24:44 +01:00
|
|
|
force=dict(required=False, type='bool', default=False),
|
2014-04-22 22:20:04 +02:00
|
|
|
esxi=dict(required=False, type='dict', default={}),
|
2015-07-26 01:07:36 +02:00
|
|
|
power_on_after_clone=dict(required=False, type='bool', default=True)
|
2014-04-22 22:20:04 +02:00
|
|
|
|
2014-04-21 22:38:31 +02:00
|
|
|
|
|
|
|
),
|
|
|
|
supports_check_mode=False,
|
2014-09-30 11:20:59 +02:00
|
|
|
mutually_exclusive=[['state', 'vmware_guest_facts'],['state', 'from_template']],
|
2014-04-21 22:38:31 +02:00
|
|
|
required_together=[
|
|
|
|
['state', 'force'],
|
2014-04-22 22:20:04 +02:00
|
|
|
[
|
|
|
|
'state',
|
|
|
|
'vm_disk',
|
|
|
|
'vm_nic',
|
|
|
|
'vm_hardware',
|
|
|
|
'esxi'
|
2014-04-22 23:12:23 +02:00
|
|
|
],
|
2015-07-15 00:56:14 +02:00
|
|
|
['from_template', 'template_src'],
|
2014-04-21 22:38:31 +02:00
|
|
|
],
|
|
|
|
)
|
|
|
|
|
2014-04-22 23:12:23 +02:00
|
|
|
if not HAS_PYSPHERE:
|
2014-04-21 22:38:31 +02:00
|
|
|
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']
|
2014-04-22 22:20:04 +02:00
|
|
|
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']
|
2014-05-29 23:57:41 +02:00
|
|
|
vm_hw_version = module.params['vm_hw_version']
|
2014-04-22 22:20:04 +02:00
|
|
|
esxi = module.params['esxi']
|
2014-04-22 23:12:23 +02:00
|
|
|
resource_pool = module.params['resource_pool']
|
|
|
|
cluster = module.params['cluster']
|
2014-09-30 11:20:59 +02:00
|
|
|
template_src = module.params['template_src']
|
|
|
|
from_template = module.params['from_template']
|
2015-07-09 22:46:39 +02:00
|
|
|
snapshot_to_clone = module.params['snapshot_to_clone']
|
2015-07-26 01:07:36 +02:00
|
|
|
power_on_after_clone = module.params['power_on_after_clone']
|
2015-01-31 16:24:44 +01:00
|
|
|
|
2014-04-21 22:38:31 +02:00
|
|
|
|
|
|
|
# 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)
|
2014-04-23 21:26:03 +02:00
|
|
|
except Exception:
|
|
|
|
pass
|
2014-04-21 22:38:31 +02:00
|
|
|
|
2014-04-23 21:26:03 +02:00
|
|
|
if vm:
|
2014-04-21 22:38:31 +02:00
|
|
|
# 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
|
2014-04-22 22:20:04 +02:00
|
|
|
elif state == 'present':
|
2014-04-21 22:38:31 +02:00
|
|
|
module.exit_json(changed=False)
|
|
|
|
|
|
|
|
# Fail on reconfig without params
|
|
|
|
elif state == 'reconfigured':
|
2014-04-23 15:53:12 +02:00
|
|
|
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,
|
2014-04-23 21:26:03 +02:00
|
|
|
state=state,
|
|
|
|
force=force
|
2014-04-23 15:53:12 +02:00
|
|
|
)
|
2014-04-23 21:26:03 +02:00
|
|
|
elif state == 'absent':
|
|
|
|
delete_vm(
|
|
|
|
vsphere_client=viserver,
|
|
|
|
module=module,
|
|
|
|
guest=guest,
|
|
|
|
vm=vm,
|
|
|
|
force=force)
|
2014-04-21 22:38:31 +02:00
|
|
|
|
|
|
|
# VM doesn't exist
|
2014-04-23 21:26:03 +02:00
|
|
|
else:
|
2014-04-21 22:38:31 +02:00
|
|
|
|
|
|
|
# Fail for fact gather task
|
|
|
|
if vmware_guest_facts:
|
|
|
|
module.fail_json(
|
|
|
|
msg="No such VM %s. Fact gathering requires an existing vm"
|
|
|
|
% guest)
|
2014-09-30 11:20:59 +02:00
|
|
|
|
|
|
|
elif from_template:
|
|
|
|
deploy_template(
|
|
|
|
vsphere_client=viserver,
|
|
|
|
esxi=esxi,
|
|
|
|
resource_pool=resource_pool,
|
|
|
|
guest=guest,
|
|
|
|
template_src=template_src,
|
|
|
|
module=module,
|
2015-01-31 16:24:44 +01:00
|
|
|
cluster_name=cluster,
|
2015-07-26 01:07:36 +02:00
|
|
|
snapshot_to_clone=snapshot_to_clone,
|
2015-10-03 20:31:22 +02:00
|
|
|
power_on_after_clone=power_on_after_clone,
|
|
|
|
vm_extra_config=vm_extra_config
|
2014-09-30 11:20:59 +02:00
|
|
|
)
|
2015-07-26 01:07:36 +02:00
|
|
|
|
2014-04-22 23:12:23 +02:00
|
|
|
if state in ['restarted', 'reconfigured']:
|
2014-04-21 22:38:31 +02:00
|
|
|
module.fail_json(
|
2014-04-22 23:12:23 +02:00
|
|
|
msg="No such VM %s. States ["
|
2014-04-21 22:38:31 +02:00
|
|
|
"restarted, reconfigured] required an existing VM" % guest)
|
|
|
|
elif state == 'absent':
|
|
|
|
module.exit_json(changed=False, msg="vm %s not present" % guest)
|
|
|
|
|
|
|
|
# Create the VM
|
2014-04-22 23:12:23 +02:00
|
|
|
elif state in ['present', 'powered_off', 'powered_on']:
|
2014-04-22 22:20:04 +02:00
|
|
|
|
|
|
|
# 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)
|
2014-04-21 22:38:31 +02:00
|
|
|
|
2014-04-22 23:12:23 +02:00
|
|
|
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,
|
2014-05-29 23:57:41 +02:00
|
|
|
vm_hw_version=vm_hw_version,
|
2014-04-22 23:12:23 +02:00
|
|
|
state=state
|
|
|
|
)
|
2014-04-23 21:26:03 +02:00
|
|
|
|
|
|
|
viserver.disconnect()
|
2014-04-21 22:38:31 +02:00
|
|
|
module.exit_json(
|
|
|
|
changed=False,
|
|
|
|
vcenter=vcenter_hostname)
|
|
|
|
|
|
|
|
|
|
|
|
# this is magic, see lib/ansible/module_common.py
|
2015-05-18 20:59:20 +02:00
|
|
|
from ansible.module_utils.basic import *
|
2015-05-11 21:15:53 +02:00
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|