1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3# 4# Copyright (c) 2016 Red Hat, Inc. 5# 6# This file is part of Ansible 7# 8# Ansible is free software: you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation, either version 3 of the License, or 11# (at your option) any later version. 12# 13# Ansible is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 20# 21 22ANSIBLE_METADATA = {'metadata_version': '1.1', 23 'status': ['preview'], 24 'supported_by': 'community'} 25 26 27DOCUMENTATION = ''' 28--- 29module: ovirt_vmpool 30short_description: Module to manage VM pools in oVirt/RHV 31version_added: "2.3" 32author: "Ondra Machacek (@machacekondra)" 33description: 34 - "Module to manage VM pools in oVirt/RHV." 35options: 36 id: 37 description: 38 - "ID of the vmpool to manage." 39 version_added: "2.8" 40 name: 41 description: 42 - "Name of the VM pool to manage." 43 required: true 44 comment: 45 description: 46 - Comment of the Virtual Machine pool. 47 state: 48 description: 49 - "Should the VM pool be present/absent." 50 - "Note that when C(state) is I(absent) all VMs in VM pool are stopped and removed." 51 choices: ['present', 'absent'] 52 default: present 53 template: 54 description: 55 - "Name of the template, which will be used to create VM pool." 56 description: 57 description: 58 - "Description of the VM pool." 59 cluster: 60 description: 61 - "Name of the cluster, where VM pool should be created." 62 type: 63 description: 64 - "Type of the VM pool. Either manual or automatic." 65 - "C(manual) - The administrator is responsible for explicitly returning the virtual machine to the pool. 66 The virtual machine reverts to the original base image after the administrator returns it to the pool." 67 - "C(Automatic) - When the virtual machine is shut down, it automatically reverts to its base image and 68 is returned to the virtual machine pool." 69 - "Default value is set by engine." 70 choices: ['manual', 'automatic'] 71 vm_per_user: 72 description: 73 - "Maximum number of VMs a single user can attach to from this pool." 74 - "Default value is set by engine." 75 prestarted: 76 description: 77 - "Number of pre-started VMs defines the number of VMs in run state, that are waiting 78 to be attached to Users." 79 - "Default value is set by engine." 80 vm_count: 81 description: 82 - "Number of VMs in the pool." 83 - "Default value is set by engine." 84 vm: 85 description: 86 - "For creating vm pool without editing template." 87 - "Note: You can use C(vm) only for creating vm pool." 88 type: dict 89 suboptions: 90 comment: 91 description: 92 - Comment of the Virtual Machine. 93 timezone: 94 description: 95 - Sets time zone offset of the guest hardware clock. 96 - For example C(Etc/GMT) 97 memory: 98 description: 99 - Amount of memory of the Virtual Machine. Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB). 100 - Default value is set by engine. 101 memory_guaranteed: 102 description: 103 - Amount of minimal guaranteed memory of the Virtual Machine. 104 Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB). 105 - C(memory_guaranteed) parameter can't be lower than C(memory) parameter. 106 - Default value is set by engine. 107 memory_max: 108 description: 109 - Upper bound of virtual machine memory up to which memory hot-plug can be performed. 110 Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB). 111 - Default value is set by engine. 112 cloud_init: 113 description: 114 - Dictionary with values for Unix-like Virtual Machine initialization using cloud init. 115 - C(host_name) - Hostname to be set to Virtual Machine when deployed. 116 - C(timezone) - Timezone to be set to Virtual Machine when deployed. 117 - C(user_name) - Username to be used to set password to Virtual Machine when deployed. 118 - C(root_password) - Password to be set for user specified by C(user_name) parameter. 119 - C(authorized_ssh_keys) - Use this SSH keys to login to Virtual Machine. 120 - C(regenerate_ssh_keys) - If I(True) SSH keys will be regenerated on Virtual Machine. 121 - C(custom_script) - Cloud-init script which will be executed on Virtual Machine when deployed. This is appended to the end of the 122 cloud-init script generated by any other options. 123 - C(dns_servers) - DNS servers to be configured on Virtual Machine. 124 - C(dns_search) - DNS search domains to be configured on Virtual Machine. 125 - C(nic_boot_protocol) - Set boot protocol of the network interface of Virtual Machine. Can be one of C(none), C(dhcp) or C(static). 126 - C(nic_ip_address) - If boot protocol is static, set this IP address to network interface of Virtual Machine. 127 - C(nic_netmask) - If boot protocol is static, set this netmask to network interface of Virtual Machine. 128 - C(nic_gateway) - If boot protocol is static, set this gateway to network interface of Virtual Machine. 129 - C(nic_name) - Set name to network interface of Virtual Machine. 130 - C(nic_on_boot) - If I(True) network interface will be set to start on boot. 131 sso: 132 description: 133 - "I(True) enable Single Sign On by Guest Agent, I(False) to disable it. By default is chosen by oVirt/RHV engine." 134 type: bool 135 smartcard_enabled: 136 description: 137 - "If I(true), use smart card authentication." 138 type: bool 139 nics: 140 description: 141 - List of NICs, which should be attached to Virtual Machine. NIC is described by following dictionary. 142 - C(name) - Name of the NIC. 143 - C(profile_name) - Profile name where NIC should be attached. 144 - C(interface) - Type of the network interface. One of following I(virtio), I(e1000), I(rtl8139), default is I(virtio). 145 - C(mac_address) - Custom MAC address of the network interface, by default it's obtained from MAC pool. 146 - NOTE - This parameter is used only when C(state) is I(running) or I(present) and is able to only create NICs. 147 - To manage NICs of the VM in more depth please use M(ovirt_nics) module instead. 148 version_added: "2.9" 149extends_documentation_fragment: ovirt 150''' 151 152EXAMPLES = ''' 153# Examples don't contain auth parameter for simplicity, 154# look at ovirt_auth module to see how to reuse authentication: 155 156- name: Create VM pool from template 157 ovirt_vmpool: 158 cluster: mycluster 159 name: myvmpool 160 template: rhel7 161 vm_count: 2 162 prestarted: 2 163 vm_per_user: 1 164 165- name: Remove vmpool, note that all VMs in pool will be stopped and removed 166 ovirt_vmpool: 167 state: absent 168 name: myvmpool 169 170- name: Change Pool Name 171 ovirt_vmpool: 172 id: 00000000-0000-0000-0000-000000000000 173 name: "new_pool_name" 174 175- name: Create vm pool and override the pool values 176 ovirt_vmpool: 177 cluster: mycluster 178 name: vmpool 179 template: blank 180 vm_count: 2 181 prestarted: 1 182 vm_per_user: 1 183 vm: 184 memory: 4GiB 185 memory_guaranteed: 4GiB 186 memory_max: 10GiB 187 comment: vncomment 188 cloud_init: 189 nic_boot_protocol: static 190 nic_ip_address: 10.34.60.86 191 nic_netmask: 255.255.252.0 192 nic_gateway: 10.34.63.254 193 nic_name: eth1 194 nic_on_boot: true 195 host_name: example.com 196 custom_script: | 197 write_files: 198 - content: | 199 Hello, world! 200 path: /tmp/greeting.txt 201 permissions: '0644' 202 user_name: root 203 root_password: super_password 204 nics: 205 - name: nicname 206 interface: virtio 207 profile_name: network 208 209''' 210 211RETURN = ''' 212id: 213 description: ID of the VM pool which is managed 214 returned: On success if VM pool is found. 215 type: str 216 sample: 7de90f31-222c-436c-a1ca-7e655bd5b60c 217vm_pool: 218 description: "Dictionary of all the VM pool attributes. VM pool attributes can be found on your oVirt/RHV instance 219 at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/vm_pool." 220 returned: On success if VM pool is found. 221 type: dict 222''' 223 224try: 225 import ovirtsdk4.types as otypes 226except ImportError: 227 pass 228 229import traceback 230 231from ansible.module_utils.basic import AnsibleModule 232from ansible.module_utils.ovirt import ( 233 BaseModule, 234 check_params, 235 check_sdk, 236 create_connection, 237 equal, 238 get_link_name, 239 ovirt_full_argument_spec, 240 wait, 241 convert_to_bytes, 242 search_by_name, 243) 244 245 246class VmPoolsModule(BaseModule): 247 def __init__(self, *args, **kwargs): 248 super(VmPoolsModule, self).__init__(*args, **kwargs) 249 self._initialization = None 250 251 def build_entity(self): 252 vm = self.param('vm') 253 return otypes.VmPool( 254 id=self._module.params['id'], 255 name=self._module.params['name'], 256 description=self._module.params['description'], 257 comment=self._module.params['comment'], 258 cluster=otypes.Cluster( 259 name=self._module.params['cluster'] 260 ) if self._module.params['cluster'] else None, 261 template=otypes.Template( 262 name=self._module.params['template'] 263 ) if self._module.params['template'] else None, 264 max_user_vms=self._module.params['vm_per_user'], 265 prestarted_vms=self._module.params['prestarted'], 266 size=self._module.params['vm_count'], 267 type=otypes.VmPoolType( 268 self._module.params['type'] 269 ) if self._module.params['type'] else None, 270 vm=self.build_vm(vm) if self._module.params['vm'] else None, 271 ) 272 273 def build_vm(self, vm): 274 return otypes.Vm( 275 comment=vm.get('comment'), 276 memory=convert_to_bytes( 277 vm.get('memory') 278 ) if vm.get('memory') else None, 279 memory_policy=otypes.MemoryPolicy( 280 guaranteed=convert_to_bytes(vm.get('memory_guaranteed')), 281 max=convert_to_bytes(vm.get('memory_max')), 282 ) if any(( 283 vm.get('memory_guaranteed'), 284 vm.get('memory_max') 285 )) else None, 286 initialization=self.get_initialization(vm), 287 display=otypes.Display( 288 smartcard_enabled=vm.get('smartcard_enabled') 289 ) if vm.get('smartcard_enabled') is not None else None, 290 sso=( 291 otypes.Sso( 292 methods=[otypes.Method(id=otypes.SsoMethod.GUEST_AGENT)] if vm.get('sso') else [] 293 ) 294 ) if vm.get('sso') is not None else None, 295 time_zone=otypes.TimeZone( 296 name=vm.get('timezone'), 297 ) if vm.get('timezone') else None, 298 ) 299 300 def get_initialization(self, vm): 301 if self._initialization is not None: 302 return self._initialization 303 304 sysprep = vm.get('sysprep') 305 cloud_init = vm.get('cloud_init') 306 cloud_init_nics = vm.get('cloud_init_nics') or [] 307 if cloud_init is not None: 308 cloud_init_nics.append(cloud_init) 309 310 if cloud_init or cloud_init_nics: 311 self._initialization = otypes.Initialization( 312 nic_configurations=[ 313 otypes.NicConfiguration( 314 boot_protocol=otypes.BootProtocol( 315 nic.pop('nic_boot_protocol').lower() 316 ) if nic.get('nic_boot_protocol') else None, 317 name=nic.pop('nic_name', None), 318 on_boot=nic.pop('nic_on_boot', None), 319 ip=otypes.Ip( 320 address=nic.pop('nic_ip_address', None), 321 netmask=nic.pop('nic_netmask', None), 322 gateway=nic.pop('nic_gateway', None), 323 ) if ( 324 nic.get('nic_gateway') is not None or 325 nic.get('nic_netmask') is not None or 326 nic.get('nic_ip_address') is not None 327 ) else None, 328 ) 329 for nic in cloud_init_nics 330 if ( 331 nic.get('nic_gateway') is not None or 332 nic.get('nic_netmask') is not None or 333 nic.get('nic_ip_address') is not None or 334 nic.get('nic_boot_protocol') is not None or 335 nic.get('nic_on_boot') is not None 336 ) 337 ] if cloud_init_nics else None, 338 **cloud_init 339 ) 340 elif sysprep: 341 self._initialization = otypes.Initialization( 342 **sysprep 343 ) 344 return self._initialization 345 346 def get_vms(self, entity): 347 vms = self._connection.system_service().vms_service().list() 348 resp = [] 349 for vm in vms: 350 if vm.vm_pool is not None and vm.vm_pool.id == entity.id: 351 resp.append(vm) 352 return resp 353 354 def post_create(self, entity): 355 vm_param = self.param('vm') 356 if vm_param is not None and vm_param.get('nics') is not None: 357 vms = self.get_vms(entity) 358 for vm in vms: 359 self.__attach_nics(vm, vm_param) 360 361 def __attach_nics(self, entity, vm_param): 362 # Attach NICs to VM, if specified: 363 vms_service = self._connection.system_service().vms_service() 364 nics_service = vms_service.service(entity.id).nics_service() 365 for nic in vm_param.get('nics'): 366 if search_by_name(nics_service, nic.get('name')) is None: 367 if not self._module.check_mode: 368 nics_service.add( 369 otypes.Nic( 370 name=nic.get('name'), 371 interface=otypes.NicInterface( 372 nic.get('interface', 'virtio') 373 ), 374 vnic_profile=otypes.VnicProfile( 375 id=self.__get_vnic_profile_id(nic), 376 ) if nic.get('profile_name') else None, 377 mac=otypes.Mac( 378 address=nic.get('mac_address') 379 ) if nic.get('mac_address') else None, 380 ) 381 ) 382 self.changed = True 383 384 def __get_vnic_profile_id(self, nic): 385 """ 386 Return VNIC profile ID looked up by it's name, because there can be 387 more VNIC profiles with same name, other criteria of filter is cluster. 388 """ 389 vnics_service = self._connection.system_service().vnic_profiles_service() 390 clusters_service = self._connection.system_service().clusters_service() 391 cluster = search_by_name(clusters_service, self.param('cluster')) 392 profiles = [ 393 profile for profile in vnics_service.list() 394 if profile.name == nic.get('profile_name') 395 ] 396 cluster_networks = [ 397 net.id for net in self._connection.follow_link(cluster.networks) 398 ] 399 try: 400 return next( 401 profile.id for profile in profiles 402 if profile.network.id in cluster_networks 403 ) 404 except StopIteration: 405 raise Exception( 406 "Profile '%s' was not found in cluster '%s'" % ( 407 nic.get('profile_name'), 408 self.param('cluster') 409 ) 410 ) 411 412 def update_check(self, entity): 413 return ( 414 equal(self._module.params.get('name'), entity.name) and 415 equal(self._module.params.get('cluster'), get_link_name(self._connection, entity.cluster)) and 416 equal(self._module.params.get('description'), entity.description) and 417 equal(self._module.params.get('comment'), entity.comment) and 418 equal(self._module.params.get('vm_per_user'), entity.max_user_vms) and 419 equal(self._module.params.get('prestarted'), entity.prestarted_vms) and 420 equal(self._module.params.get('vm_count'), entity.size) 421 ) 422 423 424def main(): 425 argument_spec = ovirt_full_argument_spec( 426 id=dict(default=None), 427 state=dict( 428 choices=['present', 'absent'], 429 default='present', 430 ), 431 name=dict(required=True), 432 template=dict(default=None), 433 cluster=dict(default=None), 434 description=dict(default=None), 435 vm=dict(default=None, type='dict'), 436 comment=dict(default=None), 437 vm_per_user=dict(default=None, type='int'), 438 prestarted=dict(default=None, type='int'), 439 vm_count=dict(default=None, type='int'), 440 type=dict(default=None, choices=['automatic', 'manual']), 441 ) 442 module = AnsibleModule( 443 argument_spec=argument_spec, 444 supports_check_mode=True, 445 ) 446 447 check_sdk(module) 448 check_params(module) 449 450 try: 451 auth = module.params.pop('auth') 452 connection = create_connection(auth) 453 vm_pools_service = connection.system_service().vm_pools_service() 454 vm_pools_module = VmPoolsModule( 455 connection=connection, 456 module=module, 457 service=vm_pools_service, 458 ) 459 460 state = module.params['state'] 461 if state == 'present': 462 ret = vm_pools_module.create() 463 464 # Wait for all VM pool VMs to be created: 465 if module.params['wait']: 466 vms_service = connection.system_service().vms_service() 467 for vm in vms_service.list(search='pool=%s' % module.params['name']): 468 wait( 469 service=vms_service.service(vm.id), 470 condition=lambda vm: vm.status in [otypes.VmStatus.DOWN, otypes.VmStatus.UP], 471 timeout=module.params['timeout'], 472 ) 473 474 elif state == 'absent': 475 ret = vm_pools_module.remove() 476 477 module.exit_json(**ret) 478 except Exception as e: 479 module.fail_json(msg=str(e), exception=traceback.format_exc()) 480 finally: 481 connection.close(logout=auth.get('token') is None) 482 483 484if __name__ == "__main__": 485 main() 486