diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_vms.py b/lib/ansible/modules/cloud/ovirt/ovirt_vms.py index ff8fab18d7..838c564921 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_vms.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_vms.py @@ -149,6 +149,12 @@ options: Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB). - C(memory_guaranteed) parameter can't be lower than C(memory) parameter. - Default value is set by engine. + memory_max: + description: + - Upper bound of virtual machine memory up to which memory hot-plug can be performed. + Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB). + - Default value is set by engine. + version_added: "2.5" cpu_shares: description: - Set a CPU shares for this Virtual Machine. @@ -459,6 +465,67 @@ options: - C(storage_domain) - Specifies the target storage domain for converted disks. This is required parameter. version_added: "2.3" + cpu_mode: + description: + - "CPU mode of the virtual machine. It can be some of the following: I(host_passthrough), I(host_model) or I(custom)." + - "For I(host_passthrough) CPU type you need to set C(placement_policy) to I(pinned)." + - "If no value is passed, default value is set by oVirt/RHV engine." + version_added: "2.5" + placement_policy: + description: + - "The configuration of the virtual machine's placement policy." + - "Placement policy can be one of the following values:" + - "C(migratable) - Allow manual and automatic migration." + - "C(pinned) - Do not allow migration." + - "C(user_migratable) - Allow manual migration only." + - "If no value is passed, default value is set by oVirt/RHV engine." + version_added: "2.5" + cpu_pinning: + description: + - "CPU Pinning topology to map virtual machine CPU to host CPU." + - "CPU Pinning topology is a list of dictionary which can have following values:" + - "C(cpu) - Number of the host CPU." + - "C(vcpu) - Number of the virtual machine CPU." + version_added: "2.5" + soundcard_enabled: + description: + - "If I(true), the sound card is added to the virtual machine." + version_added: "2.5" + smartcard_enabled: + description: + - "If I(true), use smart card authentication." + version_added: "2.5" + io_threads_enabled: + description: + - "If I(true), use IO threads." + version_added: "2.5" + ballooning_enabled: + description: + - "If I(true), use memory ballooning." + - "Memory balloon is a guest device, which may be used to re-distribute / reclaim the host memory + based on VM needs in a dynamic way. In this way it's possible to create memory over commitment states." + version_added: "2.5" + rng_device: + description: + - "Random number generator (RNG). You can choose of one the following devices I(urandom), I(random) or I(hwrng)." + - "In order to select I(hwrng), you must have it enabled on cluster first." + - "/dev/urandom is used for cluster version >= 4.1, and /dev/random for cluster version <= 4.0" + version_added: "2.5" + custom_properties: + description: + - "Properties sent to VDSM to configure various hooks." + - "Custom properties is a list of dictionary which can have following values:" + - "C(name) - Name of the custom property. For example: I(hugepages), I(vhost), I(sap_agent), etc." + - "C(regexp) - Regular expression to set for custom property." + - "C(value) - Value to set for custom property." + version_added: "2.5" + watchdog: + description: + - "Assign watchdog device for the virtual machine." + - "Watchdogs is a dictionary which can have following values:" + - "C(model) - Model of the watchdog device. For example: I(i6300esb), I(diag288) or I(null)." + - "C(action) - Watchdog action to be performed when watchdog is triggered. For example: I(none), I(reset), I(poweroff), I(pause) or I(dump)." + version_added: "2.5" notes: - If VM is in I(UNASSIGNED) or I(UNKNOWN) state before any operation, the module will fail. If VM is in I(IMAGE_LOCKED) state before any operation, we try to wait for VM to be I(DOWN). @@ -875,10 +942,24 @@ class VmsModule(BaseModule): cores=self.param('cpu_cores'), sockets=self.param('cpu_sockets'), threads=self.param('cpu_threads'), - ) - ) if ( - any((self.param('cpu_cores'), self.param('cpu_sockets'), self.param('cpu_threads'))) - ) else None, + ) if any(( + self.param('cpu_cores'), + self.param('cpu_sockets'), + self.param('cpu_threads') + )) else None, + cpu_tune=otypes.CpuTune( + vcpu_pins=[ + otypes.VcpuPin(vcpu=int(pin['vcpu']), cpu_set=str(pin['cpu'])) for pin in self.param('cpu_pinning') + ], + ) if self.param('cpu_pinning') else None, + mode=otypes.CpuMode(self.param('cpu_mode')) if self.param('cpu_mode') else None, + ) if any(( + self.param('cpu_cores'), + self.param('cpu_sockets'), + self.param('cpu_threads'), + self.param('cpu_mode'), + self.param('cpu_pinning') + )) else None, cpu_shares=self.param('cpu_shares'), os=otypes.OperatingSystem( type=self.param('operating_system'), @@ -898,7 +979,13 @@ class VmsModule(BaseModule): ) if self.param('memory') else None, memory_policy=otypes.MemoryPolicy( guaranteed=convert_to_bytes(self.param('memory_guaranteed')), - ) if self.param('memory_guaranteed') else None, + ballooning=self.param('ballooning_enabled'), + max=convert_to_bytes(self.param('memory_max')), + ) if any(( + self.param('memory_guaranteed'), + self.param('ballooning_enabled') is not None, + self.param('memory_max') + )) else None, instance_type=otypes.InstanceType( id=get_id_by_name( self._connection.system_service().instance_types_service(), @@ -917,18 +1004,68 @@ class VmsModule(BaseModule): self.param('serial_policy') is not None or self.param('serial_policy_value') is not None ) else None, + placement_policy=otypes.VmPlacementPolicy( + affinity=otypes.VmAffinity(self.param('placement_policy')), + hosts=[ + otypes.Host(name=self.param('host')), + ] if self.param('host') else None, + ) if self.param('placement_policy') else None, + soundcard_enabled=self.param('soundcard_enabled'), + display=otypes.Display( + smartcard_enabled=self.param('smartcard_enabled') + ) if self.param('smartcard_enabled') is not None else None, + io=otypes.Io( + threads=int(self.param('io_threads_enabled')), + ) if self.param('io_threads_enabled') is not None else None, + rng_device=otypes.RngDevice( + source=otypes.RngSource(self.param('rng_device')), + ) if self.param('rng_device') else None, + custom_properties=[ + otypes.CustomProperty( + name=cp.get('name'), + regexp=cp.get('regexp'), + value=str(cp.get('value')), + ) for cp in self.param('custom_properties') + ] if self.param('custom_properties') is not None else None ) def update_check(self, entity): + def check_cpu_pinning(): + if self.param('cpu_pinning'): + current = [] + if entity.cpu.cpu_tune: + current = [(str(pin.cpu_set), int(pin.vcpu)) for pin in entity.cpu.cpu_tune.vcpu_pins] + passed = [(str(pin['cpu']), int(pin['vcpu'])) for pin in self.param('cpu_pinning')] + return sorted(current) == sorted(passed) + return True + + def check_custom_properties(): + if self.param('custom_properties'): + current = [] + if entity.custom_properties: + current = [(cp.name, cp.regexp, str(cp.value)) for cp in entity.custom_properties] + passed = [(cp.get('name'), cp.get('regexp'), str(cp.get('value'))) for cp in self.param('custom_properties')] + return sorted(current) == sorted(passed) + return True + + cpu_mode = getattr(entity.cpu, 'mode') return ( + check_cpu_pinning() and + check_custom_properties() and equal(self.param('cluster'), get_link_name(self._connection, entity.cluster)) and equal(convert_to_bytes(self.param('memory')), entity.memory) and equal(convert_to_bytes(self.param('memory_guaranteed')), entity.memory_policy.guaranteed) and + equal(convert_to_bytes(self.param('memory_max')), entity.memory_policy.max) and equal(self.param('cpu_cores'), entity.cpu.topology.cores) and equal(self.param('cpu_sockets'), entity.cpu.topology.sockets) and equal(self.param('cpu_threads'), entity.cpu.topology.threads) and + equal(self.param('cpu_mode'), str(cpu_mode) if cpu_mode else None) and equal(self.param('type'), str(entity.type)) and equal(self.param('operating_system'), str(entity.os.type)) and equal(self.param('boot_menu'), entity.bios.boot_menu.enabled) and + equal(self.param('soundcard_enabled'), entity.soundcard_enabled) and + equal(self.param('smartcard_enabled'), entity.display.smartcard_enabled) and + equal(self.param('io_threads_enabled'), bool(entity.io.threads)) and + equal(self.param('ballooning_enabled'), entity.memory_policy.ballooning) and equal(self.param('serial_console'), entity.console.enabled) and equal(self.param('usb_support'), entity.usb.enabled) and equal(self.param('sso'), True if entity.sso.methods else False) and @@ -946,7 +1083,10 @@ class VmsModule(BaseModule): equal(self.param('comment'), entity.comment) and equal(self.param('timezone'), getattr(entity.time_zone, 'name', None)) and equal(self.param('serial_policy'), str(getattr(entity.serial_number, 'policy', None))) and - equal(self.param('serial_policy_value'), getattr(entity.serial_number, 'value', None)) + equal(self.param('serial_policy_value'), getattr(entity.serial_number, 'value', None)) and + equal(self.param('placement_policy'), str(entity.placement_policy.affinity)) and + equal(self.param('rng_device'), str(entity.rng_device.source) if entity.rng_device else None) and + self.param('host') in [self._connection.follow_link(host).name for host in entity.placement_policy.hosts] ) def pre_create(self, entity): @@ -956,12 +1096,13 @@ class VmsModule(BaseModule): self._module.params['template'] = 'Blank' def post_update(self, entity): - self.post_create(entity) + self.post_present(entity) - def post_create(self, entity): + def post_present(self, entity): # After creation of the VM, attach disks and NICs: self.changed = self.__attach_disks(entity) self.changed = self.__attach_nics(entity) + self.changed = self.__attach_watchdog(entity) def pre_remove(self, entity): # Forcibly stop the VM, if it's not in DOWN state: @@ -1175,6 +1316,36 @@ class VmsModule(BaseModule): ) ) + def __attach_watchdog(self, entity): + watchdogs_service = self._service.service(entity.id).watchdogs_service() + watchdog = self.param('watchdog') + if watchdog is not None: + current_watchdog = next(iter(watchdogs_service.list()), None) + if watchdog.get('model') is None and current_watchdog: + watchdogs_service.watchdog_service(current_watchdog.id).remove() + return True + elif watchdog.get('model') is not None and current_watchdog is None: + watchdogs_service.add( + otypes.Watchdog( + model=otypes.WatchdogModel(watchdog.get('model').lower()), + action=otypes.WatchdogAction(watchdog.get('action')), + ) + ) + return True + elif current_watchdog is not None: + if ( + str(current_watchdog.model).lower() != watchdog.get('model').lower() or + str(current_watchdog.action).lower() != watchdog.get('action').lower() + ): + watchdogs_service.watchdog_service(current_watchdog.id).update( + otypes.Watchdog( + model=otypes.WatchdogModel(watchdog.get('model')), + action=otypes.WatchdogAction(watchdog.get('action')), + ) + ) + return True + return False + def __attach_nics(self, entity): # Attach NICs to VM, if specified: nics_service = self._service.service(entity.id).nics_service() @@ -1492,6 +1663,7 @@ def main(): disks=dict(type='list', default=[]), memory=dict(type='str'), memory_guaranteed=dict(type='str'), + memory_max=dict(type='str'), cpu_sockets=dict(type='int'), cpu_cores=dict(type='int'), cpu_shares=dict(type='int'), @@ -1550,6 +1722,16 @@ def main(): vmware=dict(type='dict'), xen=dict(type='dict'), kvm=dict(type='dict'), + cpu_mode=dict(type='str'), + placement_policy=dict(type='str'), + cpu_pinning=dict(type='list'), + soundcard_enabled=dict(type='bool', default=None), + smartcard_enabled=dict(type='bool', default=None), + io_threads_enabled=dict(type='bool', default=None), + ballooning_enabled=dict(type='bool', default=None), + rng_device=dict(type='str'), + custom_properties=dict(type='list'), + watchdog=dict(type='dict'), ) module = AnsibleModule( argument_spec=argument_spec, @@ -1590,6 +1772,7 @@ def main(): clone=module.params['clone'], clone_permissions=module.params['clone_permissions'], ) + vms_module.post_present(vm) # Run the VM if it was just created, else don't run it: if state == 'running': @@ -1644,6 +1827,7 @@ def main(): action_condition=lambda vm: vm.status == otypes.VmStatus.UP, wait_condition=lambda vm: vm.status == otypes.VmStatus.UP, ) + ret['changed'] = vms_module.changed elif state == 'stopped': if module.params['xen'] or module.params['kvm'] or module.params['vmware']: vms_module.changed = import_vm(module, connection)