Fix #21112 : Adding support for configuring a CD-rom iso image (#28155)

* Initial CD-ROM support

* create cdrom bugfix

* Improving CDROM change detection and fixing template creation bug
Running MarkAsTemplate on an existing template will fail with an error

* Better change detection for guest ID
Should only mark a change in case it actually changes

* Adding integration tests

* Pep8 compliance fixes

* Adding CDROM support, including iso, client and none types

* Updating added release version for CDROM option
This commit is contained in:
Philippe Dellaert 2017-10-11 14:29:13 +02:00 committed by Dag Wieers
parent c976ac7ed6
commit 9580a6569e
3 changed files with 261 additions and 5 deletions

View file

@ -25,6 +25,7 @@ version_added: '2.2'
author: author:
- James Tanner (@jctanner) <tanner.jc@gmail.com> - James Tanner (@jctanner) <tanner.jc@gmail.com>
- Loic Blot (@nerzhul) <loic.blot@unix-experience.fr> - Loic Blot (@nerzhul) <loic.blot@unix-experience.fr>
- Philippe Dellaert (@pdellaert) <philippe@dellaert.org>
notes: notes:
- Tested on vSphere 5.5 and 6.0 - Tested on vSphere 5.5 and 6.0
requirements: requirements:
@ -64,7 +65,7 @@ options:
version_added: '2.3' version_added: '2.3'
folder: folder:
description: description:
- Destination folder, absolute or relative path to find an existing guest or create the new guest. - Destination folder, absolute path to find an existing guest or create the new guest.
- The folder should include the datacenter. ESX's datacenter is ha-datacenter - The folder should include the datacenter. ESX's datacenter is ha-datacenter
- 'Examples:' - 'Examples:'
- ' folder: /ha-datacenter/vm' - ' folder: /ha-datacenter/vm'
@ -76,8 +77,6 @@ options:
- ' folder: /folder1/datacenter1/vm' - ' folder: /folder1/datacenter1/vm'
- ' folder: folder1/datacenter1/vm' - ' folder: folder1/datacenter1/vm'
- ' folder: /folder1/datacenter1/vm/folder2' - ' folder: /folder1/datacenter1/vm/folder2'
- ' folder: vm/folder2'
- ' folder: folder2'
default: /vm default: /vm
hardware: hardware:
description: description:
@ -102,6 +101,13 @@ options:
- ' - C(type) (string): Valid value is C(thin) (default: None).' - ' - C(type) (string): Valid value is C(thin) (default: None).'
- ' - C(datastore) (string): Datastore to use for the disk. If C(autoselect_datastore) is enabled, filter datastore selection.' - ' - C(datastore) (string): Datastore to use for the disk. If C(autoselect_datastore) is enabled, filter datastore selection.'
- ' - C(autoselect_datastore) (bool): select the less used datastore.' - ' - C(autoselect_datastore) (bool): select the less used datastore.'
cdrom:
description:
- A CD-ROM configuration for the VM.
- 'Valid attributes are:'
- ' - C(type) (string): The type of CD-ROM, valid options are C(none), C(client) or C(iso). With C(none) the CD-ROM will be disconnected but present.'
- ' - C(iso_path) (string): The datastore path to the ISO file to use, in the form of C([datastore1] path/to/file.iso). Required if type is iso.'
version_added: '2.5'
resource_pool: resource_pool:
description: description:
- Affect machine to the given resource pool. - Affect machine to the given resource pool.
@ -209,6 +215,9 @@ EXAMPLES = r'''
memory_mb: 512 memory_mb: 512
num_cpus: 1 num_cpus: 1
scsi: paravirtual scsi: paravirtual
cdrom:
type: iso
iso_path: "[datastore1] livecd.iso"
networks: networks:
- name: VM Network - name: VM Network
mac: aa:bb:dd:aa:00:14 mac: aa:bb:dd:aa:00:14
@ -358,6 +367,52 @@ class PyVmomiDeviceHelper(object):
isinstance(device, vim.vm.device.VirtualBusLogicController) or \ isinstance(device, vim.vm.device.VirtualBusLogicController) or \
isinstance(device, vim.vm.device.VirtualLsiLogicSASController) isinstance(device, vim.vm.device.VirtualLsiLogicSASController)
@staticmethod
def create_ide_controller():
ide_ctl = vim.vm.device.VirtualDeviceSpec()
ide_ctl.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
ide_ctl.device = vim.vm.device.VirtualIDEController()
ide_ctl.device.deviceInfo = vim.Description()
ide_ctl.device.busNumber = 0
return ide_ctl
@staticmethod
def create_cdrom(ide_ctl, cdrom_type, iso_path=None):
cdrom_spec = vim.vm.device.VirtualDeviceSpec()
cdrom_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
cdrom_spec.device = vim.vm.device.VirtualCdrom()
cdrom_spec.device.controllerKey = ide_ctl.device.key
cdrom_spec.device.key = -1
cdrom_spec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
cdrom_spec.device.connectable.allowGuestControl = True
cdrom_spec.device.connectable.startConnected = (cdrom_type != "none")
if cdrom_type in ["none", "client"]:
cdrom_spec.device.backing = vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo()
elif cdrom_type == "iso":
cdrom_spec.device.backing = vim.vm.device.VirtualCdrom.IsoBackingInfo(fileName=iso_path)
return cdrom_spec
@staticmethod
def is_equal_cdrom(vm_obj, cdrom_device, cdrom_type, iso_path):
if cdrom_type == "none":
return (isinstance(cdrom_device.backing, vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo) and
cdrom_device.connectable.allowGuestControl and
not cdrom_device.connectable.startConnected and
(vm_obj.runtime.powerState != vim.VirtualMachinePowerState.poweredOn or not cdrom_device.connectable.connected))
elif cdrom_type == "client":
return (isinstance(cdrom_device.backing, vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo) and
cdrom_device.connectable.allowGuestControl and
cdrom_device.connectable.startConnected and
(vm_obj.runtime.powerState != vim.VirtualMachinePowerState.poweredOn or cdrom_device.connectable.connected))
elif cdrom_type == "iso":
return (isinstance(cdrom_device.backing, vim.vm.device.VirtualCdrom.IsoBackingInfo) and
cdrom_device.backing.fileName == iso_path and
cdrom_device.connectable.allowGuestControl and
cdrom_device.connectable.startConnected and
(vm_obj.runtime.powerState != vim.VirtualMachinePowerState.poweredOn or cdrom_device.connectable.connected))
def create_scsi_disk(self, scsi_ctl, disk_index=None): def create_scsi_disk(self, scsi_ctl, disk_index=None):
diskspec = vim.vm.device.VirtualDeviceSpec() diskspec = vim.vm.device.VirtualDeviceSpec()
diskspec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add diskspec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
@ -527,7 +582,7 @@ class PyVmomiHelper(PyVmomi):
if vm_creation and self.params['guest_id'] is None: if vm_creation and self.params['guest_id'] is None:
self.module.fail_json(msg="guest_id attribute is mandatory for VM creation") self.module.fail_json(msg="guest_id attribute is mandatory for VM creation")
if vm_obj is None or self.params['guest_id'] != vm_obj.summary.config.guestId: if self.params['guest_id'] and (vm_obj is None or self.params['guest_id'] != vm_obj.summary.config.guestId):
self.change_detected = True self.change_detected = True
self.configspec.guestId = self.params['guest_id'] self.configspec.guestId = self.params['guest_id']
@ -550,6 +605,76 @@ class PyVmomiHelper(PyVmomi):
elif vm_creation and not self.params['template']: elif vm_creation and not self.params['template']:
self.module.fail_json(msg="hardware.memory_mb attribute is mandatory for VM creation") self.module.fail_json(msg="hardware.memory_mb attribute is mandatory for VM creation")
def configure_cdrom(self, vm_obj):
# Configure the VM CD-ROM
if "cdrom" in self.params and self.params["cdrom"]:
if "type" not in self.params["cdrom"] or self.params["cdrom"]["type"] not in ["none", "client", "iso"]:
self.module.fail_json(msg="cdrom.type is mandatory")
if self.params["cdrom"]["type"] == "iso" and ("iso_path" not in self.params["cdrom"] or not self.params["cdrom"]["iso_path"]):
self.module.fail_json(msg="cdrom.iso_path is mandatory in case cdrom.type is iso")
if vm_obj and vm_obj.config.template:
# Changing CD-ROM settings on a template is not supported
return
cdrom_spec = None
cdrom_device = self.get_vm_cdrom_device(vm=vm_obj)
iso_path = self.params["cdrom"]["iso_path"] if "iso_path" in self.params["cdrom"] else None
if cdrom_device is None:
# Creating new CD-ROM
ide_device = self.get_vm_ide_device(vm=vm_obj)
if ide_device is None:
# Creating new IDE device
ide_device = self.device_helper.create_ide_controller()
self.change_detected = True
self.configspec.deviceChange.append(ide_device)
elif len(ide_device.device) > 3:
self.module.fail_json(msg="hardware.cdrom specified for a VM or template which already has 4 IDE devices of which none are a cdrom")
cdrom_spec = self.device_helper.create_cdrom(ide_ctl=ide_device, cdrom_type=self.params["cdrom"]["type"], iso_path=iso_path)
if vm_obj and vm_obj.runtime.powerState == vim.VirtualMachinePowerState.poweredOn:
cdrom_spec.device.connectable.connected = (self.params["cdrom"]["type"] != "none")
elif not self.device_helper.is_equal_cdrom(vm_obj=vm_obj, cdrom_device=cdrom_device, cdrom_type=self.params["cdrom"]["type"], iso_path=iso_path):
# Updating an existing CD-ROM
if self.params["cdrom"]["type"] in ["client", "none"]:
cdrom_device.backing = vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo()
elif self.params["cdrom"]["type"] == "iso":
cdrom_device.backing = vim.vm.device.VirtualCdrom.IsoBackingInfo(fileName=iso_path)
cdrom_device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
cdrom_device.connectable.allowGuestControl = True
cdrom_device.connectable.startConnected = (self.params["cdrom"]["type"] != "none")
if vm_obj and vm_obj.runtime.powerState == vim.VirtualMachinePowerState.poweredOn:
cdrom_device.connectable.connected = (self.params["cdrom"]["type"] != "none")
cdrom_spec = vim.vm.device.VirtualDeviceSpec()
cdrom_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
cdrom_spec.device = cdrom_device
if cdrom_spec:
self.change_detected = True
self.configspec.deviceChange.append(cdrom_spec)
def get_vm_cdrom_device(self, vm=None):
if vm is None:
return None
for device in vm.config.hardware.device:
if isinstance(device, vim.vm.device.VirtualCdrom):
return device
return None
def get_vm_ide_device(self, vm=None):
if vm is None:
return None
for device in vm.config.hardware.device:
if isinstance(device, vim.vm.device.VirtualIDEController):
return device
return None
def get_vm_network_interfaces(self, vm=None): def get_vm_network_interfaces(self, vm=None):
if vm is None: if vm is None:
return [] return []
@ -1148,6 +1273,7 @@ class PyVmomiHelper(PyVmomi):
self.configure_cpu_and_memory(vm_obj=vm_obj, vm_creation=True) self.configure_cpu_and_memory(vm_obj=vm_obj, vm_creation=True)
self.configure_disks(vm_obj=vm_obj) self.configure_disks(vm_obj=vm_obj)
self.configure_network(vm_obj=vm_obj) self.configure_network(vm_obj=vm_obj)
self.configure_cdrom(vm_obj=vm_obj)
# Find if we need network customizations (find keys in dictionary that requires customizations) # Find if we need network customizations (find keys in dictionary that requires customizations)
network_changes = False network_changes = False
@ -1265,6 +1391,7 @@ class PyVmomiHelper(PyVmomi):
self.configure_cpu_and_memory(vm_obj=self.current_vm_obj) self.configure_cpu_and_memory(vm_obj=self.current_vm_obj)
self.configure_disks(vm_obj=self.current_vm_obj) self.configure_disks(vm_obj=self.current_vm_obj)
self.configure_network(vm_obj=self.current_vm_obj) self.configure_network(vm_obj=self.current_vm_obj)
self.configure_cdrom(vm_obj=self.current_vm_obj)
self.customize_customvalues(vm_obj=self.current_vm_obj) self.customize_customvalues(vm_obj=self.current_vm_obj)
if self.params['annotation'] and self.current_vm_obj.config.annotation != self.params['annotation']: if self.params['annotation'] and self.current_vm_obj.config.annotation != self.params['annotation']:
@ -1306,7 +1433,7 @@ class PyVmomiHelper(PyVmomi):
return {'changed': change_applied, 'failed': True, 'msg': task.info.error.msg} return {'changed': change_applied, 'failed': True, 'msg': task.info.error.msg}
# Mark VM as Template # Mark VM as Template
if self.params['is_template']: if self.params['is_template'] and not self.current_vm_obj.config.template:
self.current_vm_obj.MarkAsTemplate() self.current_vm_obj.MarkAsTemplate()
change_applied = True change_applied = True
@ -1352,6 +1479,7 @@ def main():
folder=dict(type='str', default='/vm'), folder=dict(type='str', default='/vm'),
guest_id=dict(type='str'), guest_id=dict(type='str'),
disk=dict(type='list', default=[]), disk=dict(type='list', default=[]),
cdrom=dict(type='dict', default={}),
hardware=dict(type='dict', default={}), hardware=dict(type='dict', default={}),
force=dict(type='bool', default=False), force=dict(type='bool', default=False),
datacenter=dict(type='str', default='ha-datacenter'), datacenter=dict(type='str', default='ha-datacenter'),

View file

@ -0,0 +1,127 @@
- name: Wait for Flask controller to come up online
wait_for:
host: "{{ vcsim }}"
port: 5000
state: started
- name: kill vcsim
uri:
url: "{{ 'http://' + vcsim + ':5000/killall' }}"
- name: start vcsim with no folders
uri:
url: "{{ 'http://' + vcsim + ':5000/spawn?datacenter=1&cluster=1&folder=0' }}"
register: vcsim_instance
- name: Wait for Flask controller to come up online
wait_for:
host: "{{ vcsim }}"
port: 443
state: started
- name: get a list of Clusters from vcsim
uri:
url: "{{ 'http://' + vcsim + ':5000/govc_find?filter=CCR' }}"
register: clusterlist
- debug: var=vcsim_instance
- debug: var=clusterlist
- name: Create VM with CDROM
vmware_guest:
validate_certs: False
hostname: "{{ vcsim }}"
username: "{{ vcsim_instance['json']['username'] }}"
password: "{{ vcsim_instance['json']['password'] }}"
folder: "/{{ (clusterlist['json'][0]|basename).split('_')[0] }}/vm"
name: CDROM-Test
datacenter: "{{ (clusterlist['json'][0]|basename).split('_')[0] }}"
cluster: "{{ clusterlist['json'][0] }}"
resource_pool: Resources
guest_id: centos64Guest
hardware:
memory_mb: 512
num_cpus: 1
scsi: paravirtual
disk:
- size_mb: 128
type: thin
datastore: LocalDS_0
cdrom:
type: iso
iso_path: "[LocalDS_0] base.iso"
register: cdrom_vm
- debug: var=cdrom_vm
- name: assert the VM was created
assert:
that:
- "cdrom_vm.failed == false"
- "cdrom_vm.changed == true"
- name: Update CDROM to iso for the new VM
vmware_guest:
validate_certs: False
hostname: "{{ vcsim }}"
username: "{{ vcsim_instance['json']['username'] }}"
password: "{{ vcsim_instance['json']['password'] }}"
folder: "/{{ (clusterlist['json'][0]|basename).split('_')[0] }}/vm"
name: CDROM-Test
datacenter: "{{ (clusterlist['json'][0]|basename).split('_')[0] }}"
cdrom:
type: iso
iso_path: "[LocalDS_0] base_new.iso"
state: present
register: cdrom_vm
- debug: var=cdrom_vm
- name: assert the VM was changed
assert:
that:
- "cdrom_vm.failed == false"
- "cdrom_vm.changed == true"
- name: Update CDROM to client for the new VM
vmware_guest:
validate_certs: False
hostname: "{{ vcsim }}"
username: "{{ vcsim_instance['json']['username'] }}"
password: "{{ vcsim_instance['json']['password'] }}"
folder: "/{{ (clusterlist['json'][0]|basename).split('_')[0] }}/vm"
name: CDROM-Test
datacenter: "{{ (clusterlist['json'][0]|basename).split('_')[0] }}"
cdrom:
type: client
state: present
register: cdrom_vm
- debug: var=cdrom_vm
- name: assert the VM was changed
assert:
that:
- "cdrom_vm.failed == false"
- "cdrom_vm.changed == true"
- name: Update CDROM to none for the new VM
vmware_guest:
validate_certs: False
hostname: "{{ vcsim }}"
username: "{{ vcsim_instance['json']['username'] }}"
password: "{{ vcsim_instance['json']['password'] }}"
folder: "/{{ (clusterlist['json'][0]|basename).split('_')[0] }}/vm"
name: CDROM-Test
datacenter: "{{ (clusterlist['json'][0]|basename).split('_')[0] }}"
cdrom:
type: none
state: present
register: cdrom_vm
- debug: var=cdrom_vm
- name: assert the VM was changed
assert:
that:
- "cdrom_vm.failed == false"
- "cdrom_vm.changed == true"

View file

@ -13,3 +13,4 @@
- include: poweroff_d1_c1_f1.yml - include: poweroff_d1_c1_f1.yml
- include: clone_d1_c1_f0.yml - include: clone_d1_c1_f0.yml
- include: create_d1_c1_f0.yml - include: create_d1_c1_f0.yml
- include: cdrom_d1_c1_f0.yml