1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2017, Ansible Project 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7ANSIBLE_METADATA = {'metadata_version': '1.1', 8 'status': ['preview'], 9 'supported_by': 'community'} 10 11DOCUMENTATION = ''' 12--- 13module: ovirt_instance_type 14short_description: Module to manage Instance Types in oVirt/RHV 15version_added: "2.8" 16author: 17- Martin Necas (@mnecas) 18- Ondra Machacek (@machacekondra) 19description: 20 - This module manages whole lifecycle of the Instance Type in oVirt/RHV. 21options: 22 name: 23 description: 24 - Name of the Instance Type to manage. 25 - If instance type don't exists C(name) is required. Otherwise C(id) or C(name) can be used. 26 id: 27 description: 28 - ID of the Instance Type to manage. 29 state: 30 description: 31 - Should the Instance Type be present/absent. 32 - I(present) state will create/update instance type and don't change its state if it already exists. 33 choices: [ absent, present ] 34 default: present 35 memory: 36 description: 37 - Amount of memory of the Instance Type. Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB). 38 - Default value is set by engine. 39 memory_guaranteed: 40 description: 41 - Amount of minimal guaranteed memory of the Instance Type. 42 Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB). 43 - C(memory_guaranteed) parameter can't be lower than C(memory) parameter. 44 - Default value is set by engine. 45 nics: 46 description: 47 - List of NICs, which should be attached to Virtual Machine. NIC is described by following dictionary. 48 - C(name) - Name of the NIC. 49 - C(profile_name) - Profile name where NIC should be attached. 50 - C(interface) - Type of the network interface. One of following I(virtio), I(e1000), I(rtl8139), default is I(virtio). 51 - C(mac_address) - Custom MAC address of the network interface, by default it's obtained from MAC pool. 52 - NOTE - This parameter is used only when C(state) is I(running) or I(present) and is able to only create NICs. 53 To manage NICs of the instance type in more depth please use M(ovirt_nic) module instead. 54 memory_max: 55 description: 56 - Upper bound of instance type memory up to which memory hot-plug can be performed. 57 Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB). 58 - Default value is set by engine. 59 cpu_cores: 60 description: 61 - Number of virtual CPUs cores of the Instance Type. 62 - Default value is set by oVirt/RHV engine. 63 cpu_sockets: 64 description: 65 - Number of virtual CPUs sockets of the Instance Type. 66 - Default value is set by oVirt/RHV engine. 67 cpu_threads: 68 description: 69 - Number of virtual CPUs sockets of the Instance Type. 70 - Default value is set by oVirt/RHV engine. 71 operating_system: 72 description: 73 - Operating system of the Instance Type. 74 - Default value is set by oVirt/RHV engine. 75 - "Possible values: debian_7, freebsd, freebsdx64, other, other_linux, other_linux_kernel_4, 76 other_linux_ppc64, other_linux_s390x, other_ppc64, other_s390x, rhcos_x64, rhel_3, 77 rhel_3x64, rhel_4, rhel_4x64, rhel_5, rhel_5x64, rhel_6, rhel_6_9_plus_ppc64, 78 rhel_6_ppc64, rhel_6x64, rhel_7_ppc64, rhel_7_s390x, rhel_7x64, rhel_8x64, 79 rhel_atomic7x64, sles_11, sles_11_ppc64, sles_12_s390x, ubuntu_12_04, ubuntu_12_10, 80 ubuntu_13_04, ubuntu_13_10, ubuntu_14_04, ubuntu_14_04_ppc64, ubuntu_16_04_s390x, 81 windows_10, windows_10x64, windows_2003, windows_2003x64, windows_2008, 82 windows_2008R2x64, windows_2008x64, windows_2012R2x64, windows_2012x64, windows_2016x64, 83 windows_2019x64, windows_7, windows_7x64, windows_8, windows_8x64, windows_xp" 84 boot_devices: 85 description: 86 - List of boot devices which should be used to boot. For example C([ cdrom, hd ]). 87 - Default value is set by oVirt/RHV engine. 88 choices: [ cdrom, hd, network ] 89 serial_console: 90 description: 91 - "I(True) enable VirtIO serial console, I(False) to disable it. By default is chosen by oVirt/RHV engine." 92 type: bool 93 usb_support: 94 description: 95 - "I(True) enable USB support, I(False) to disable it. By default is chosen by oVirt/RHV engine." 96 type: bool 97 high_availability: 98 description: 99 - If I(yes) Instance Type will be set as highly available. 100 - If I(no) Instance Type won't be set as highly available. 101 - If no value is passed, default value is set by oVirt/RHV engine. 102 type: bool 103 high_availability_priority: 104 description: 105 - Indicates the priority of the instance type inside the run and migration queues. 106 Instance Type with higher priorities will be started and migrated before instance types with lower 107 priorities. The value is an integer between 0 and 100. The higher the value, the higher the priority. 108 - If no value is passed, default value is set by oVirt/RHV engine. 109 watchdog: 110 description: 111 - "Assign watchdog device for the instance type." 112 - "Watchdogs is a dictionary which can have following values:" 113 - "C(model) - Model of the watchdog device. For example: I(i6300esb), I(diag288) or I(null)." 114 - "C(action) - Watchdog action to be performed when watchdog is triggered. For example: I(none), I(reset), I(poweroff), I(pause) or I(dump)." 115 host: 116 description: 117 - Specify host where Instance Type should be running. By default the host is chosen by engine scheduler. 118 - This parameter is used only when C(state) is I(running) or I(present). 119 graphical_console: 120 description: 121 - "Assign graphical console to the instance type." 122 - "Graphical console is a dictionary which can have following values:" 123 - "C(headless_mode) - If I(true) disable the graphics console for this instance type." 124 - "C(protocol) - Graphical protocol, a list of I(spice), I(vnc), or both." 125 description: 126 description: 127 - "Description of the instance type." 128 cpu_mode: 129 description: 130 - "CPU mode of the instance type. It can be some of the following: I(host_passthrough), I(host_model) or I(custom)." 131 - "For I(host_passthrough) CPU type you need to set C(placement_policy) to I(pinned)." 132 - "If no value is passed, default value is set by oVirt/RHV engine." 133 rng_device: 134 description: 135 - "Random number generator (RNG). You can choose of one the following devices I(urandom), I(random) or I(hwrng)." 136 - "In order to select I(hwrng), you must have it enabled on cluster first." 137 - "/dev/urandom is used for cluster version >= 4.1, and /dev/random for cluster version <= 4.0" 138 rng_bytes: 139 description: 140 - "Number of bytes allowed to consume per period." 141 rng_period: 142 description: 143 - "Duration of one period in milliseconds." 144 placement_policy: 145 description: 146 - "The configuration of the instance type's placement policy." 147 - "Placement policy can be one of the following values:" 148 - "C(migratable) - Allow manual and automatic migration." 149 - "C(pinned) - Do not allow migration." 150 - "C(user_migratable) - Allow manual migration only." 151 - "If no value is passed, default value is set by oVirt/RHV engine." 152 cpu_pinning: 153 description: 154 - "CPU Pinning topology to map instance type CPU to host CPU." 155 - "CPU Pinning topology is a list of dictionary which can have following values:" 156 - "C(cpu) - Number of the host CPU." 157 - "C(vcpu) - Number of the instance type CPU." 158 soundcard_enabled: 159 description: 160 - "If I(true), the sound card is added to the instance type." 161 type: bool 162 smartcard_enabled: 163 description: 164 - "If I(true), use smart card authentication." 165 type: bool 166 virtio_scsi: 167 description: 168 - "If I(true), virtio scsi will be enabled." 169 type: bool 170 io_threads: 171 description: 172 - "Number of IO threads used by instance type. I(0) means IO threading disabled." 173 ballooning_enabled: 174 description: 175 - "If I(true), use memory ballooning." 176 - "Memory balloon is a guest device, which may be used to re-distribute / reclaim the host memory 177 based on instance type needs in a dynamic way. In this way it's possible to create memory over commitment states." 178 type: bool 179extends_documentation_fragment: ovirt 180''' 181 182EXAMPLES = ''' 183# Examples don't contain auth parameter for simplicity, 184# look at ovirt_auth module to see how to reuse authentication: 185 186# Create instance type 187- name: Create instance type 188 ovirt_instance_type: 189 state: present 190 name: myit 191 rng_device: hwrng 192 rng_bytes: 200 193 rng_period: 200 194 soundcard_enabled: true 195 virtio_scsi: true 196 boot_devices: 197 - network 198 199# Remove instance type 200- ovirt_instance_type: 201 state: absent 202 name: myit 203 204 205# Create instance type with predefined memory and cpu limits. 206- ovirt_instance_type: 207 state: present 208 name: myit 209 memory: 2GiB 210 cpu_cores: 2 211 cpu_sockets: 2 212 nics: 213 - name: nic1 214 215# Enable usb support and serial console 216- ovirt_instance_type: 217 name: myit 218 usb_support: True 219 serial_console: True 220 221# Use graphical console with spice and vnc 222- name: Create a instance type that has the console configured for both Spice and VNC 223 ovirt_instance_type: 224 name: myit 225 graphical_console: 226 protocol: 227 - spice 228 - vnc 229''' 230 231 232RETURN = ''' 233 234id: 235 description: ID of the instance type which is managed 236 returned: On success if instance type is found. 237 type: str 238 sample: 7de90f31-222c-436c-a1ca-7e655bd5b60c 239instancetype: 240 description: "Dictionary of all the instance type attributes. instance type attributes can be found on your oVirt/RHV instance 241 at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/instance_type." 242 returned: On success if instance type is found. 243 type: dict 244''' 245 246from ansible.module_utils.basic import AnsibleModule 247import traceback 248 249from ansible.module_utils.ovirt import ( 250 BaseModule, 251 check_params, 252 check_sdk, 253 convert_to_bytes, 254 create_connection, 255 equal, 256 get_dict_of_struct, 257 get_entity, 258 get_link_name, 259 get_id_by_name, 260 ovirt_full_argument_spec, 261 search_by_attributes, 262 search_by_name, 263 wait, 264) 265 266try: 267 import ovirtsdk4.types as otypes 268except ImportError: 269 pass 270 271 272class InstanceTypeModule(BaseModule): 273 def build_entity(self): 274 return otypes.InstanceType( 275 id=self.param('id'), 276 name=self.param('name'), 277 console=( 278 otypes.Console(enabled=self.param('serial_console')) 279 ) if self.param('serial_console') is not None else None, 280 usb=( 281 otypes.Usb(enabled=self.param('usb_support')) 282 ) if self.param('usb_support') is not None else None, 283 high_availability=otypes.HighAvailability( 284 enabled=self.param('high_availability'), 285 priority=self.param('high_availability_priority'), 286 ) if self.param('high_availability') is not None or self.param('high_availability_priority') else None, 287 cpu=otypes.Cpu( 288 topology=otypes.CpuTopology( 289 cores=self.param('cpu_cores'), 290 sockets=self.param('cpu_sockets'), 291 threads=self.param('cpu_threads'), 292 ) if any(( 293 self.param('cpu_cores'), 294 self.param('cpu_sockets'), 295 self.param('cpu_threads') 296 )) else None, 297 cpu_tune=otypes.CpuTune( 298 vcpu_pins=[ 299 otypes.VcpuPin(vcpu=int(pin['vcpu']), cpu_set=str(pin['cpu'])) for pin in self.param('cpu_pinning') 300 ], 301 ) if self.param('cpu_pinning') else None, 302 mode=otypes.CpuMode(self.param('cpu_mode')) if self.param( 303 'cpu_mode') else None, 304 ) if any(( 305 self.param('cpu_cores'), 306 self.param('cpu_sockets'), 307 self.param('cpu_threads'), 308 self.param('cpu_mode'), 309 self.param('cpu_pinning') 310 )) else None, 311 os=otypes.OperatingSystem( 312 type=self.param('operating_system'), 313 boot=otypes.Boot( 314 devices=[ 315 otypes.BootDevice(dev) for dev in self.param('boot_devices') 316 ], 317 ) if self.param('boot_devices') else None 318 ), 319 rng_device=otypes.RngDevice( 320 source=otypes.RngSource(self.param('rng_device')), 321 rate=otypes.Rate( 322 bytes=self.param('rng_bytes'), 323 period=self.param('rng_period') 324 ) 325 ) if self.param('rng_device') else None, 326 memory=convert_to_bytes( 327 self.param('memory') 328 ) if self.param('memory') else None, 329 virtio_scsi=otypes.VirtioScsi( 330 enabled=self.param('virtio_scsi') 331 ) if self.param('virtio_scsi') else None, 332 memory_policy=otypes.MemoryPolicy( 333 guaranteed=convert_to_bytes(self.param('memory_guaranteed')), 334 ballooning=self.param('ballooning_enabled'), 335 max=convert_to_bytes(self.param('memory_max')), 336 ) if any(( 337 self.param('memory_guaranteed'), 338 self.param('ballooning_enabled') is not None, 339 self.param('memory_max') 340 )) else None, 341 description=self.param('description'), 342 placement_policy=otypes.VmPlacementPolicy( 343 affinity=otypes.VmAffinity(self.param('placement_policy')), 344 hosts=[ 345 otypes.Host(name=self.param('host')), 346 ] if self.param('host') else None, 347 ) if self.param('placement_policy') else None, 348 soundcard_enabled=self.param('soundcard_enabled'), 349 display=otypes.Display( 350 smartcard_enabled=self.param('smartcard_enabled') 351 ) if self.param('smartcard_enabled') is not None else None, 352 io=otypes.Io( 353 threads=self.param('io_threads'), 354 ) if self.param('io_threads') is not None else None, 355 ) 356 357 def __attach_watchdog(self, entity): 358 watchdogs_service = self._service.service(entity.id).watchdogs_service() 359 watchdog = self.param('watchdog') 360 if watchdog is not None: 361 current_watchdog = next(iter(watchdogs_service.list()), None) 362 if watchdog.get('model') is None and current_watchdog: 363 watchdogs_service.watchdog_service(current_watchdog.id).remove() 364 return True 365 elif watchdog.get('model') is not None and current_watchdog is None: 366 watchdogs_service.add( 367 otypes.Watchdog( 368 model=otypes.WatchdogModel(watchdog.get('model').lower()), 369 action=otypes.WatchdogAction(watchdog.get('action')), 370 ) 371 ) 372 return True 373 elif current_watchdog is not None: 374 if ( 375 str(current_watchdog.model).lower() != watchdog.get('model').lower() or 376 str(current_watchdog.action).lower() != watchdog.get('action').lower() 377 ): 378 watchdogs_service.watchdog_service(current_watchdog.id).update( 379 otypes.Watchdog( 380 model=otypes.WatchdogModel(watchdog.get('model')), 381 action=otypes.WatchdogAction(watchdog.get('action')), 382 ) 383 ) 384 return True 385 return False 386 387 def __get_vnic_profile_id(self, nic): 388 """ 389 Return VNIC profile ID looked up by it's name, because there can be 390 more VNIC profiles with same name, other criteria of filter is cluster. 391 """ 392 vnics_service = self._connection.system_service().vnic_profiles_service() 393 clusters_service = self._connection.system_service().clusters_service() 394 cluster = search_by_name(clusters_service, self.param('cluster')) 395 profiles = [ 396 profile for profile in vnics_service.list() 397 if profile.name == nic.get('profile_name') 398 ] 399 cluster_networks = [ 400 net.id for net in self._connection.follow_link(cluster.networks) 401 ] 402 try: 403 return next( 404 profile.id for profile in profiles 405 if profile.network.id in cluster_networks 406 ) 407 except StopIteration: 408 raise Exception( 409 "Profile '%s' was not found in cluster '%s'" % ( 410 nic.get('profile_name'), 411 self.param('cluster') 412 ) 413 ) 414 415 def __attach_nics(self, entity): 416 # Attach NICs to instance type, if specified: 417 nics_service = self._service.service(entity.id).nics_service() 418 for nic in self.param('nics'): 419 if search_by_name(nics_service, nic.get('name')) is None: 420 if not self._module.check_mode: 421 nics_service.add( 422 otypes.Nic( 423 name=nic.get('name'), 424 interface=otypes.NicInterface( 425 nic.get('interface', 'virtio') 426 ), 427 vnic_profile=otypes.VnicProfile( 428 id=self.__get_vnic_profile_id(nic), 429 ) if nic.get('profile_name') else None, 430 mac=otypes.Mac( 431 address=nic.get('mac_address') 432 ) if nic.get('mac_address') else None, 433 ) 434 ) 435 self.changed = True 436 437 def __attach_graphical_console(self, entity): 438 graphical_console = self.param('graphical_console') 439 if not graphical_console: 440 return False 441 442 it_service = self._service.instance_type_service(entity.id) 443 gcs_service = it_service.graphics_consoles_service() 444 graphical_consoles = gcs_service.list() 445 # Remove all graphical consoles if there are any: 446 if bool(graphical_console.get('headless_mode')): 447 if not self._module.check_mode: 448 for gc in graphical_consoles: 449 gcs_service.console_service(gc.id).remove() 450 return len(graphical_consoles) > 0 451 452 # If there are not gc add any gc to be added: 453 protocol = graphical_console.get('protocol') 454 if isinstance(protocol, str): 455 protocol = [protocol] 456 457 current_protocols = [str(gc.protocol) for gc in graphical_consoles] 458 if not current_protocols: 459 if not self._module.check_mode: 460 for p in protocol: 461 gcs_service.add( 462 otypes.GraphicsConsole( 463 protocol=otypes.GraphicsType(p), 464 ) 465 ) 466 return True 467 468 # Update consoles: 469 if sorted(protocol) != sorted(current_protocols): 470 if not self._module.check_mode: 471 for gc in graphical_consoles: 472 gcs_service.console_service(gc.id).remove() 473 for p in protocol: 474 gcs_service.add( 475 otypes.GraphicsConsole( 476 protocol=otypes.GraphicsType(p), 477 ) 478 ) 479 return True 480 481 def post_update(self, entity): 482 self.post_present(entity.id) 483 484 def post_present(self, entity_id): 485 entity = self._service.service(entity_id).get() 486 self.changed = self.__attach_nics(entity) 487 self.changed = self.__attach_watchdog(entity) 488 self.changed = self.__attach_graphical_console(entity) 489 490 def update_check(self, entity): 491 cpu_mode = getattr(entity.cpu, 'mode') 492 it_display = entity.display 493 return ( 494 not self.param('kernel_params_persist') and 495 equal(convert_to_bytes(self.param('memory_guaranteed')), entity.memory_policy.guaranteed) and 496 equal(convert_to_bytes(self.param('memory_max')), entity.memory_policy.max) and 497 equal(self.param('cpu_cores'), entity.cpu.topology.cores) and 498 equal(self.param('cpu_sockets'), entity.cpu.topology.sockets) and 499 equal(self.param('cpu_threads'), entity.cpu.topology.threads) and 500 equal(self.param('cpu_mode'), str(cpu_mode) if cpu_mode else None) and 501 equal(self.param('type'), str(entity.type)) and 502 equal(self.param('name'), str(entity.name)) and 503 equal(self.param('operating_system'), str(entity.os.type)) and 504 equal(self.param('soundcard_enabled'), entity.soundcard_enabled) and 505 equal(self.param('smartcard_enabled'), getattr(it_display, 'smartcard_enabled', False)) and 506 equal(self.param('io_threads'), entity.io.threads) and 507 equal(self.param('ballooning_enabled'), entity.memory_policy.ballooning) and 508 equal(self.param('serial_console'), getattr(entity.console, 'enabled', None)) and 509 equal(self.param('usb_support'), entity.usb.enabled) and 510 equal(self.param('virtio_scsi'), getattr(entity, 'smartcard_enabled', False)) and 511 equal(self.param('high_availability'), entity.high_availability.enabled) and 512 equal(self.param('high_availability_priority'), entity.high_availability.priority) and 513 equal(self.param('boot_devices'), [str(dev) for dev in getattr(entity.os.boot, 'devices', [])]) and 514 equal(self.param('description'), entity.description) and 515 equal(self.param('rng_device'), str(entity.rng_device.source) if entity.rng_device else None) and 516 equal(self.param('rng_bytes'), entity.rng_device.rate.bytes if entity.rng_device else None) and 517 equal(self.param('rng_period'), entity.rng_device.rate.period if entity.rng_device else None) and 518 equal(self.param('placement_policy'), str(entity.placement_policy.affinity) if entity.placement_policy else None) 519 ) 520 521 522def main(): 523 argument_spec = ovirt_full_argument_spec( 524 state=dict(type='str', default='present', 525 choices=['absent', 'present']), 526 name=dict(type='str'), 527 id=dict(type='str'), 528 memory=dict(type='str'), 529 memory_guaranteed=dict(type='str'), 530 memory_max=dict(type='str'), 531 cpu_sockets=dict(type='int'), 532 cpu_cores=dict(type='int'), 533 cpu_threads=dict(type='int'), 534 operating_system=dict(type='str'), 535 boot_devices=dict(type='list', choices=['cdrom', 'hd', 'network']), 536 serial_console=dict(type='bool'), 537 usb_support=dict(type='bool'), 538 high_availability=dict(type='bool'), 539 high_availability_priority=dict(type='int'), 540 watchdog=dict(type='dict'), 541 host=dict(type='str'), 542 graphical_console=dict(type='dict'), 543 description=dict(type='str'), 544 cpu_mode=dict(type='str'), 545 rng_device=dict(type='str'), 546 rng_bytes=dict(type='int', default=None), 547 rng_period=dict(type='int', default=None), 548 placement_policy=dict(type='str'), 549 cpu_pinning=dict(type='list'), 550 soundcard_enabled=dict(type='bool', default=None), 551 virtio_scsi=dict(type='bool', default=None), 552 smartcard_enabled=dict(type='bool', default=None), 553 io_threads=dict(type='int', default=None), 554 nics=dict(type='list', default=[]), 555 ballooning_enabled=dict(type='bool', default=None), 556 ) 557 module = AnsibleModule( 558 argument_spec=argument_spec, 559 supports_check_mode=True, 560 required_one_of=[['id', 'name']], 561 ) 562 563 check_sdk(module) 564 check_params(module) 565 566 try: 567 state = module.params['state'] 568 auth = module.params.pop('auth') 569 connection = create_connection(auth) 570 its_service = connection.system_service().instance_types_service() 571 its_module = InstanceTypeModule( 572 connection=connection, 573 module=module, 574 service=its_service, 575 ) 576 it = its_module.search_entity() 577 578 if state == 'present': 579 ret = its_module.create( 580 entity=it 581 ) 582 its_module.post_present(ret['id']) 583 ret['changed'] = its_module.changed 584 elif state == 'absent': 585 ret = its_module.remove() 586 module.exit_json(**ret) 587 except Exception as e: 588 module.fail_json(msg=str(e), exception=traceback.format_exc()) 589 finally: 590 connection.close(logout=auth.get('token') is None) 591 592 593if __name__ == "__main__": 594 main() 595