VMware: add support for multiple CD-ROMs (#51120)

Add support for create or re-configure VM with multiple CD-ROMs attaching to IDE controller now, will implement SATA controller support later.
parameters can be set as below:

    cdrom:
    - controller_type: ide
      controller_number: 0
      unit_number: 0
      type: client
This commit is contained in:
Diane Wang 2019-08-29 07:56:33 -07:00 committed by Gonéri Le Bouder
parent e41f1a4d7d
commit 54b108175b
2 changed files with 282 additions and 60 deletions

View file

@ -198,9 +198,21 @@ options:
cdrom: cdrom:
description: description:
- A CD-ROM configuration for the virtual machine. - A CD-ROM configuration for the virtual machine.
- Or a list of CD-ROMs configuration for the virtual machine. Added in version 2.9.
- 'Parameters C(controller_type), C(controller_number), C(unit_number), C(state) are added for a list of CD-ROMs
configuration support.'
- 'Valid attributes are:' - '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(type) (string): The type of CD-ROM, valid options are C(none), C(client) or C(iso). With C(none) the CD-ROM
- ' - 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 set C(iso).' 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 set C(iso).'
- ' - C(controller_type) (string): Default value is C(ide). Only C(ide) controller type for CD-ROM is supported for
now, will add SATA controller type in the future.'
- ' - C(controller_number) (int): For C(ide) controller, valid value is 0 or 1.'
- ' - C(unit_number) (int): For CD-ROM device attach to C(ide) controller, valid value is 0 or 1.
C(controller_number) and C(unit_number) are mandatory attributes.'
- ' - C(state) (string): Valid value is C(present) or C(absent). Default is C(present). If set to C(absent), then
the specified CD-ROM will be removed. For C(ide) controller, hot-add or hot-remove CD-ROM is not supported.'
version_added: '2.5' version_added: '2.5'
resource_pool: resource_pool:
description: description:
@ -598,6 +610,13 @@ from ansible.module_utils.vmware import (find_obj, gather_vm_facts, get_all_objs
wait_for_task, TaskError) wait_for_task, TaskError)
def list_or_dict(value):
if isinstance(value, list) or isinstance(value, dict):
return value
else:
raise ValueError("'%s' is not valid, valid type is 'list' or 'dict'." % value)
class PyVmomiDeviceHelper(object): class PyVmomiDeviceHelper(object):
""" This class is a helper to create easily VMware Objects for PyVmomiHelper """ """ This class is a helper to create easily VMware Objects for PyVmomiHelper """
@ -630,7 +649,7 @@ class PyVmomiDeviceHelper(object):
return isinstance(device, tuple(self.scsi_device_type.values())) return isinstance(device, tuple(self.scsi_device_type.values()))
@staticmethod @staticmethod
def create_ide_controller(): def create_ide_controller(bus_number=0):
ide_ctl = vim.vm.device.VirtualDeviceSpec() ide_ctl = vim.vm.device.VirtualDeviceSpec()
ide_ctl.operation = vim.vm.device.VirtualDeviceSpec.Operation.add ide_ctl.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
ide_ctl.device = vim.vm.device.VirtualIDEController() ide_ctl.device = vim.vm.device.VirtualIDEController()
@ -638,17 +657,18 @@ class PyVmomiDeviceHelper(object):
# While creating a new IDE controller, temporary key value # While creating a new IDE controller, temporary key value
# should be unique negative integers # should be unique negative integers
ide_ctl.device.key = -randint(200, 299) ide_ctl.device.key = -randint(200, 299)
ide_ctl.device.busNumber = 0 ide_ctl.device.busNumber = bus_number
return ide_ctl return ide_ctl
@staticmethod @staticmethod
def create_cdrom(ide_ctl, cdrom_type, iso_path=None): def create_cdrom(ide_device, cdrom_type, iso_path=None, unit_number=0):
cdrom_spec = vim.vm.device.VirtualDeviceSpec() cdrom_spec = vim.vm.device.VirtualDeviceSpec()
cdrom_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add cdrom_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
cdrom_spec.device = vim.vm.device.VirtualCdrom() cdrom_spec.device = vim.vm.device.VirtualCdrom()
cdrom_spec.device.controllerKey = ide_ctl.device.key cdrom_spec.device.controllerKey = ide_device.key
cdrom_spec.device.key = -1 cdrom_spec.device.key = -randint(3000, 3999)
cdrom_spec.device.unitNumber = unit_number
cdrom_spec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo() cdrom_spec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
cdrom_spec.device.connectable.allowGuestControl = True cdrom_spec.device.connectable.allowGuestControl = True
cdrom_spec.device.connectable.startConnected = (cdrom_type != "none") cdrom_spec.device.connectable.startConnected = (cdrom_type != "none")
@ -678,6 +698,26 @@ class PyVmomiDeviceHelper(object):
cdrom_device.connectable.startConnected and cdrom_device.connectable.startConnected and
(vm_obj.runtime.powerState != vim.VirtualMachinePowerState.poweredOn or cdrom_device.connectable.connected)) (vm_obj.runtime.powerState != vim.VirtualMachinePowerState.poweredOn or cdrom_device.connectable.connected))
@staticmethod
def update_cdrom_config(vm_obj, cdrom_spec, cdrom_device, iso_path=None):
# Updating an existing CD-ROM
if cdrom_spec["type"] in ["client", "none"]:
cdrom_device.backing = vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo()
elif cdrom_spec["type"] == "iso" and iso_path is not None:
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 = (cdrom_spec["type"] != "none")
if vm_obj and vm_obj.runtime.powerState == vim.VirtualMachinePowerState.poweredOn:
cdrom_device.connectable.connected = (cdrom_spec["type"] != "none")
def remove_cdrom(self, cdrom_device):
cdrom_spec = vim.vm.device.VirtualDeviceSpec()
cdrom_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.remove
cdrom_spec.device = cdrom_device
return cdrom_spec
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
@ -1030,56 +1070,161 @@ class PyVmomiHelper(PyVmomi):
self.configspec.firmware = boot_firmware self.configspec.firmware = boot_firmware
self.change_detected = True self.change_detected = True
def sanitize_cdrom_params(self):
# cdroms {'ide': [{num: 0, cdrom: []}, {}], 'sata': [{num: 0, cdrom: []}, {}, ...]}
cdroms = {'ide': [], 'sata': []}
expected_cdrom_spec = self.params.get('cdrom')
if expected_cdrom_spec:
for cdrom_spec in expected_cdrom_spec:
cdrom_spec['controller_type'] = cdrom_spec.get('controller_type', 'ide').lower()
if cdrom_spec['controller_type'] not in ['ide', 'sata']:
self.module.fail_json(msg="Invalid cdrom.controller_type: %s, valid value is 'ide' or 'sata'."
% cdrom_spec['controller_type'])
cdrom_spec['state'] = cdrom_spec.get('state', 'present').lower()
if cdrom_spec['state'] not in ['present', 'absent']:
self.module.fail_json(msg="Invalid cdrom.state: %s, valid value is 'present', 'absent'."
% cdrom_spec['state'])
if cdrom_spec['state'] == 'present':
if 'type' in cdrom_spec and cdrom_spec.get('type') not in ['none', 'client', 'iso']:
self.module.fail_json(msg="Invalid cdrom.type: %s, valid value is 'none', 'client' or 'iso'."
% cdrom_spec.get('type'))
if cdrom_spec.get('type') == 'iso' and not cdrom_spec.get('iso_path'):
self.module.fail_json(msg="cdrom.iso_path is mandatory when cdrom.type is set to iso.")
if cdrom_spec['controller_type'] == 'ide' and \
(cdrom_spec.get('controller_number') not in [0, 1] or cdrom_spec.get('unit_number') not in [0, 1]):
self.module.fail_json(msg="Invalid cdrom.controller_number: %s or cdrom.unit_number: %s, valid"
" values are 0 or 1 for IDE controller." % (cdrom_spec.get('controller_number'), cdrom_spec.get('unit_number')))
if cdrom_spec['controller_type'] == 'sata' and \
(cdrom_spec.get('controller_number') not in range(0, 4) or cdrom_spec.get('unit_number') not in range(0, 30)):
self.module.fail_json(msg="Invalid cdrom.controller_number: %s or cdrom.unit_number: %s,"
" valid controller_number value is 0-3, valid unit_number is 0-29"
" for SATA controller." % (cdrom_spec.get('controller_number'), cdrom_spec.get('unit_number')))
ctl_exist = False
for exist_spec in cdroms.get(cdrom_spec['controller_type']):
if exist_spec['num'] == cdrom_spec['controller_number']:
ctl_exist = True
exist_spec['cdrom'].append(cdrom_spec)
break
if not ctl_exist:
cdroms.get(cdrom_spec['controller_type']).append({'num': cdrom_spec['controller_number'], 'cdrom': [cdrom_spec]})
return cdroms
def configure_cdrom(self, vm_obj): def configure_cdrom(self, vm_obj):
# Configure the VM CD-ROM # Configure the VM CD-ROM
if "cdrom" in self.params and self.params["cdrom"]: if self.params.get('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: if vm_obj and vm_obj.config.template:
# Changing CD-ROM settings on a template is not supported # Changing CD-ROM settings on a template is not supported
return return
cdrom_spec = None if isinstance(self.params.get('cdrom'), dict):
cdrom_device = self.get_vm_cdrom_device(vm=vm_obj) self.configure_cdrom_dict(vm_obj)
iso_path = self.params["cdrom"]["iso_path"] if "iso_path" in self.params["cdrom"] else None elif isinstance(self.params.get('cdrom'), list):
if cdrom_device is None: self.configure_cdrom_list(vm_obj)
# 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) def configure_cdrom_dict(self, vm_obj):
if self.params["cdrom"].get('type') not in ['none', 'client', 'iso']:
self.module.fail_json(msg="cdrom.type is mandatory. Options are 'none', 'client', and 'iso'.")
if self.params["cdrom"]['type'] == 'iso' and not self.params["cdrom"].get('iso_path'):
self.module.fail_json(msg="cdrom.iso_path is mandatory when cdrom.type is set to iso.")
cdrom_spec = None
cdrom_devices = self.get_vm_cdrom_devices(vm=vm_obj)
iso_path = self.params["cdrom"].get("iso_path")
if len(cdrom_devices) == 0:
# Creating new CD-ROM
ide_devices = self.get_vm_ide_devices(vm=vm_obj)
if len(ide_devices) == 0:
# Creating new IDE device
ide_ctl = self.device_helper.create_ide_controller()
ide_device = ide_ctl.device
self.change_detected = True
self.configspec.deviceChange.append(ide_ctl)
else:
ide_device = ide_devices[0]
if 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_device=ide_device, cdrom_type=self.params["cdrom"]["type"],
iso_path=iso_path)
if vm_obj and vm_obj.runtime.powerState == vim.VirtualMachinePowerState.poweredOn: if vm_obj and vm_obj.runtime.powerState == vim.VirtualMachinePowerState.poweredOn:
cdrom_spec.device.connectable.connected = (self.params["cdrom"]["type"] != "none") 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): elif not self.device_helper.is_equal_cdrom(vm_obj=vm_obj, cdrom_device=cdrom_devices[0],
# Updating an existing CD-ROM cdrom_type=self.params["cdrom"]["type"], iso_path=iso_path):
if self.params["cdrom"]["type"] in ["client", "none"]: self.device_helper.update_cdrom_config(vm_obj, self.params["cdrom"], cdrom_devices[0], iso_path=iso_path)
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 = vim.vm.device.VirtualDeviceSpec()
cdrom_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit cdrom_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
cdrom_spec.device = cdrom_device cdrom_spec.device = cdrom_devices[0]
if cdrom_spec: if cdrom_spec:
self.change_detected = True self.change_detected = True
self.configspec.deviceChange.append(cdrom_spec) self.configspec.deviceChange.append(cdrom_spec)
def configure_cdrom_list(self, vm_obj):
configured_cdroms = self.sanitize_cdrom_params()
cdrom_devices = self.get_vm_cdrom_devices(vm=vm_obj)
# configure IDE CD-ROMs
if configured_cdroms['ide']:
ide_devices = self.get_vm_ide_devices(vm=vm_obj)
for expected_cdrom_spec in configured_cdroms['ide']:
ide_device = None
for device in ide_devices:
if device.busNumber == expected_cdrom_spec['num']:
ide_device = device
break
# if not find the matched ide controller or no existing ide controller
if not ide_device:
ide_ctl = self.device_helper.create_ide_controller(bus_number=expected_cdrom_spec['num'])
ide_device = ide_ctl.device
self.change_detected = True
self.configspec.deviceChange.append(ide_ctl)
for cdrom in expected_cdrom_spec['cdrom']:
cdrom_device = None
iso_path = cdrom.get('iso_path')
unit_number = cdrom.get('unit_number')
for target_cdrom in cdrom_devices:
if target_cdrom.controllerKey == ide_device.key and target_cdrom.unitNumber == unit_number:
cdrom_device = target_cdrom
break
# create new CD-ROM
if not cdrom_device and cdrom.get('state') != 'absent':
if vm_obj and vm_obj.runtime.powerState == vim.VirtualMachinePowerState.poweredOn:
self.module.fail_json(msg='CD-ROM attach to IDE controller not support hot-add.')
if len(ide_device.device) == 2:
self.module.fail_json(msg='Maximum number of CD-ROMs attached to IDE controller is 2.')
cdrom_spec = self.device_helper.create_cdrom(ide_device=ide_device, cdrom_type=cdrom['type'],
iso_path=iso_path, unit_number=unit_number)
self.change_detected = True
self.configspec.deviceChange.append(cdrom_spec)
# re-configure CD-ROM
elif cdrom_device and cdrom.get('state') != 'absent' and \
not self.device_helper.is_equal_cdrom(vm_obj=vm_obj, cdrom_device=cdrom_device,
cdrom_type=cdrom['type'], iso_path=iso_path):
self.device_helper.update_cdrom_config(vm_obj, cdrom, cdrom_device, iso_path=iso_path)
cdrom_spec = vim.vm.device.VirtualDeviceSpec()
cdrom_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
cdrom_spec.device = cdrom_device
self.change_detected = True
self.configspec.deviceChange.append(cdrom_spec)
# delete CD-ROM
elif cdrom_device and cdrom.get('state') == 'absent':
if vm_obj and vm_obj.runtime.powerState != vim.VirtualMachinePowerState.poweredOff:
self.module.fail_json(msg='CD-ROM attach to IDE controller not support hot-remove.')
cdrom_spec = self.device_helper.remove_cdrom(cdrom_device)
self.change_detected = True
self.configspec.deviceChange.append(cdrom_spec)
# configure SATA CD-ROMs is not supported yet
if configured_cdroms['sata']:
pass
def configure_hardware_params(self, vm_obj): def configure_hardware_params(self, vm_obj):
""" """
Function to configure hardware related configuration of virtual machine Function to configure hardware related configuration of virtual machine
@ -1166,19 +1311,19 @@ class PyVmomiHelper(PyVmomi):
self.change_detected = True self.change_detected = True
def get_device_by_type(self, vm=None, type=None): def get_device_by_type(self, vm=None, type=None):
device_list = []
if vm is None or type is None: if vm is None or type is None:
return None return device_list
for device in vm.config.hardware.device: for device in vm.config.hardware.device:
if isinstance(device, type): if isinstance(device, type):
return device device_list.append(device)
return None return device_list
def get_vm_cdrom_device(self, vm=None): def get_vm_cdrom_devices(self, vm=None):
return self.get_device_by_type(vm=vm, type=vim.vm.device.VirtualCdrom) return self.get_device_by_type(vm=vm, type=vim.vm.device.VirtualCdrom)
def get_vm_ide_device(self, vm=None): def get_vm_ide_devices(self, vm=None):
return self.get_device_by_type(vm=vm, type=vim.vm.device.VirtualIDEController) return self.get_device_by_type(vm=vm, type=vim.vm.device.VirtualIDEController)
def get_vm_network_interfaces(self, vm=None): def get_vm_network_interfaces(self, vm=None):
@ -2570,7 +2715,7 @@ def main():
folder=dict(type='str'), folder=dict(type='str'),
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={}), cdrom=dict(type=list_or_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

@ -28,7 +28,6 @@
- name: assert the VM was created - name: assert the VM was created
assert: assert:
that: that:
- "cdrom_vm.failed == false"
- "cdrom_vm.changed == true" - "cdrom_vm.changed == true"
- name: Update CDROM to iso for the new VM - name: Update CDROM to iso for the new VM
@ -52,7 +51,6 @@
- name: assert the VM was changed - name: assert the VM was changed
assert: assert:
that: that:
- "cdrom_vm.failed == false"
- "cdrom_vm.changed == true" - "cdrom_vm.changed == true"
- name: Update CDROM to client for the new VM - name: Update CDROM to client for the new VM
@ -74,7 +72,6 @@
- name: assert the VM was changed - name: assert the VM was changed
assert: assert:
that: that:
- "cdrom_vm.failed == false"
- "cdrom_vm.changed == true" - "cdrom_vm.changed == true"
- name: clone vm - name: clone vm
@ -97,7 +94,7 @@
username: "{{ vcenter_username }}" username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}" password: "{{ vcenter_password }}"
folder: vm folder: vm
name: test_vm1 name: test_vm2
datacenter: "{{ dc1 }}" datacenter: "{{ dc1 }}"
cdrom: cdrom:
type: none type: none
@ -109,7 +106,6 @@
- name: assert the VM was changed - name: assert the VM was changed
assert: assert:
that: that:
- "cdrom_vm.failed == false"
- "cdrom_vm.changed == true" - "cdrom_vm.changed == true"
- name: Create VM with multiple disks and a CDROM - GitHub issue 38679 - name: Create VM with multiple disks and a CDROM - GitHub issue 38679
@ -148,7 +144,88 @@
- name: assert the VM was created - name: assert the VM was created
assert: assert:
that: that:
- "cdrom_vm.failed == false" - "cdrom_vm.changed == true"
- name: Create VM with multiple CDROMs
vmware_guest:
validate_certs: False
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
folder: vm
name: test_vm1
datacenter: "{{ dc1 }}"
cluster: "{{ ccr1 }}"
resource_pool: Resources
guest_id: centos64Guest
hardware:
memory_mb: 512
num_cpus: 1
scsi: paravirtual
disk:
- size_mb: 128
type: thin
datastore: "{{ ds2 }}"
cdrom:
- controller_type: ide
controller_number: 0
unit_number: 0
type: iso
iso_path: "[{{ ds1 }}] centos.iso"
- controller_type: ide
controller_number: 0
unit_number: 1
type: client
- controller_number: 1
unit_number: 0
type: none
- controller_number: 1
unit_number: 1
type: client
register: cdrom_vm
- debug: var=cdrom_vm
- name: assert the VM was created
assert:
that:
- "cdrom_vm.changed == true"
- name: Remove the last 2 CDROMs and update the first 2 for the new VM
vmware_guest:
validate_certs: False
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
folder: vm
name: test_vm1
datacenter: "{{ dc1 }}"
cdrom:
- controller_type: ide
controller_number: 0
unit_number: 0
type: client
- controller_type: ide
controller_number: 0
unit_number: 1
type: iso
iso_path: "[{{ ds1 }}] fedora.iso"
- controller_type: ide
controller_number: 1
unit_number: 0
state: absent
- controller_type: ide
controller_number: 1
unit_number: 1
state: absent
state: present
register: cdrom_vm
- debug: var=cdrom_vm
- name: assert the VM was changed
assert:
that:
- "cdrom_vm.changed == true" - "cdrom_vm.changed == true"
# VCSIM fails with invalidspec exception but real vCenter PASS testcase # VCSIM fails with invalidspec exception but real vCenter PASS testcase