1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2016, Timothy Vandenbrande <timothy.vandenbrande@gmail.com> 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10DOCUMENTATION = r''' 11--- 12module: rhevm 13short_description: RHEV/oVirt automation 14description: 15 - This module only supports oVirt/RHEV version 3. 16 - A newer module M(ovirt.ovirt.ovirt_vm) supports oVirt/RHV version 4. 17 - Allows you to create/remove/update or powermanage virtual machines on a RHEV/oVirt platform. 18requirements: 19 - ovirtsdk 20author: 21- Timothy Vandenbrande (@TimothyVandenbrande) 22options: 23 user: 24 description: 25 - The user to authenticate with. 26 type: str 27 default: admin@internal 28 password: 29 description: 30 - The password for user authentication. 31 type: str 32 required: true 33 server: 34 description: 35 - The name/IP of your RHEV-m/oVirt instance. 36 type: str 37 default: 127.0.0.1 38 port: 39 description: 40 - The port on which the API is reachable. 41 type: int 42 default: 443 43 insecure_api: 44 description: 45 - A boolean switch to make a secure or insecure connection to the server. 46 type: bool 47 default: no 48 name: 49 description: 50 - The name of the VM. 51 type: str 52 cluster: 53 description: 54 - The RHEV/oVirt cluster in which you want you VM to start. 55 type: str 56 datacenter: 57 description: 58 - The RHEV/oVirt datacenter in which you want you VM to start. 59 type: str 60 default: Default 61 state: 62 description: 63 - This serves to create/remove/update or powermanage your VM. 64 type: str 65 choices: [ absent, cd, down, info, ping, present, restarted, up ] 66 default: present 67 image: 68 description: 69 - The template to use for the VM. 70 type: str 71 type: 72 description: 73 - To define if the VM is a server or desktop. 74 type: str 75 choices: [ desktop, host, server ] 76 default: server 77 vmhost: 78 description: 79 - The host you wish your VM to run on. 80 type: str 81 vmcpu: 82 description: 83 - The number of CPUs you want in your VM. 84 type: int 85 default: 2 86 cpu_share: 87 description: 88 - This parameter is used to configure the CPU share. 89 type: int 90 default: 0 91 vmmem: 92 description: 93 - The amount of memory you want your VM to use (in GB). 94 type: int 95 default: 1 96 osver: 97 description: 98 - The operating system option in RHEV/oVirt. 99 type: str 100 default: rhel_6x64 101 mempol: 102 description: 103 - The minimum amount of memory you wish to reserve for this system. 104 type: int 105 default: 1 106 vm_ha: 107 description: 108 - To make your VM High Available. 109 type: bool 110 default: yes 111 disks: 112 description: 113 - This option uses complex arguments and is a list of disks with the options name, size and domain. 114 type: list 115 elements: str 116 ifaces: 117 description: 118 - This option uses complex arguments and is a list of interfaces with the options name and vlan. 119 type: list 120 elements: str 121 aliases: [ interfaces, nics ] 122 boot_order: 123 description: 124 - This option uses complex arguments and is a list of items that specify the bootorder. 125 type: list 126 elements: str 127 default: [ hd, network ] 128 del_prot: 129 description: 130 - This option sets the delete protection checkbox. 131 type: bool 132 default: yes 133 cd_drive: 134 description: 135 - The CD you wish to have mounted on the VM when I(state = 'CD'). 136 type: str 137 timeout: 138 description: 139 - The timeout you wish to define for power actions. 140 - When I(state = 'up'). 141 - When I(state = 'down'). 142 - When I(state = 'restarted'). 143 type: int 144''' 145 146RETURN = r''' 147vm: 148 description: Returns all of the VMs variables and execution. 149 returned: always 150 type: dict 151 sample: '{ 152 "boot_order": [ 153 "hd", 154 "network" 155 ], 156 "changed": true, 157 "changes": [ 158 "Delete Protection" 159 ], 160 "cluster": "C1", 161 "cpu_share": "0", 162 "created": false, 163 "datacenter": "Default", 164 "del_prot": true, 165 "disks": [ 166 { 167 "domain": "ssd-san", 168 "name": "OS", 169 "size": 40 170 } 171 ], 172 "eth0": "00:00:5E:00:53:00", 173 "eth1": "00:00:5E:00:53:01", 174 "eth2": "00:00:5E:00:53:02", 175 "exists": true, 176 "failed": false, 177 "ifaces": [ 178 { 179 "name": "eth0", 180 "vlan": "Management" 181 }, 182 { 183 "name": "eth1", 184 "vlan": "Internal" 185 }, 186 { 187 "name": "eth2", 188 "vlan": "External" 189 } 190 ], 191 "image": false, 192 "mempol": "0", 193 "msg": [ 194 "VM exists", 195 "cpu_share was already set to 0", 196 "VM high availability was already set to True", 197 "The boot order has already been set", 198 "VM delete protection has been set to True", 199 "Disk web2_Disk0_OS already exists", 200 "The VM starting host was already set to host416" 201 ], 202 "name": "web2", 203 "type": "server", 204 "uuid": "4ba5a1be-e60b-4368-9533-920f156c817b", 205 "vm_ha": true, 206 "vmcpu": "4", 207 "vmhost": "host416", 208 "vmmem": "16" 209 }' 210''' 211 212EXAMPLES = r''' 213- name: Basic get info from VM 214 community.general.rhevm: 215 server: rhevm01 216 user: '{{ rhev.admin.name }}' 217 password: '{{ rhev.admin.pass }}' 218 name: demo 219 state: info 220 221- name: Basic create example from image 222 community.general.rhevm: 223 server: rhevm01 224 user: '{{ rhev.admin.name }}' 225 password: '{{ rhev.admin.pass }}' 226 name: demo 227 cluster: centos 228 image: centos7_x64 229 state: present 230 231- name: Power management 232 community.general.rhevm: 233 server: rhevm01 234 user: '{{ rhev.admin.name }}' 235 password: '{{ rhev.admin.pass }}' 236 cluster: RH 237 name: uptime_server 238 image: centos7_x64 239 state: down 240 241- name: Multi disk, multi nic create example 242 community.general.rhevm: 243 server: rhevm01 244 user: '{{ rhev.admin.name }}' 245 password: '{{ rhev.admin.pass }}' 246 cluster: RH 247 name: server007 248 type: server 249 vmcpu: 4 250 vmmem: 2 251 ifaces: 252 - name: eth0 253 vlan: vlan2202 254 - name: eth1 255 vlan: vlan36 256 - name: eth2 257 vlan: vlan38 258 - name: eth3 259 vlan: vlan2202 260 disks: 261 - name: root 262 size: 10 263 domain: ssd-san 264 - name: swap 265 size: 10 266 domain: 15kiscsi-san 267 - name: opt 268 size: 10 269 domain: 15kiscsi-san 270 - name: var 271 size: 10 272 domain: 10kiscsi-san 273 - name: home 274 size: 10 275 domain: sata-san 276 boot_order: 277 - network 278 - hd 279 state: present 280 281- name: Add a CD to the disk cd_drive 282 community.general.rhevm: 283 user: '{{ rhev.admin.name }}' 284 password: '{{ rhev.admin.pass }}' 285 name: server007 286 cd_drive: rhev-tools-setup.iso 287 state: cd 288 289- name: New host deployment + host network configuration 290 community.general.rhevm: 291 password: '{{ rhevm.admin.pass }}' 292 name: ovirt_node007 293 type: host 294 cluster: rhevm01 295 ifaces: 296 - name: em1 297 - name: em2 298 - name: p3p1 299 ip: 172.31.224.200 300 netmask: 255.255.254.0 301 - name: p3p2 302 ip: 172.31.225.200 303 netmask: 255.255.254.0 304 - name: bond0 305 bond: 306 - em1 307 - em2 308 network: rhevm 309 ip: 172.31.222.200 310 netmask: 255.255.255.0 311 management: yes 312 - name: bond0.36 313 network: vlan36 314 ip: 10.2.36.200 315 netmask: 255.255.254.0 316 gateway: 10.2.36.254 317 - name: bond0.2202 318 network: vlan2202 319 - name: bond0.38 320 network: vlan38 321 state: present 322''' 323 324import time 325 326try: 327 from ovirtsdk.api import API 328 from ovirtsdk.xml import params 329 HAS_SDK = True 330except ImportError: 331 HAS_SDK = False 332 333from ansible.module_utils.basic import AnsibleModule 334 335 336RHEV_FAILED = 1 337RHEV_SUCCESS = 0 338RHEV_UNAVAILABLE = 2 339 340RHEV_TYPE_OPTS = ['desktop', 'host', 'server'] 341STATE_OPTS = ['absent', 'cd', 'down', 'info', 'ping', 'present', 'restart', 'up'] 342 343msg = [] 344changed = False 345failed = False 346 347 348class RHEVConn(object): 349 'Connection to RHEV-M' 350 351 def __init__(self, module): 352 self.module = module 353 354 user = module.params.get('user') 355 password = module.params.get('password') 356 server = module.params.get('server') 357 port = module.params.get('port') 358 insecure_api = module.params.get('insecure_api') 359 360 url = "https://%s:%s" % (server, port) 361 362 try: 363 api = API(url=url, username=user, password=password, insecure=str(insecure_api)) 364 api.test() 365 self.conn = api 366 except Exception: 367 raise Exception("Failed to connect to RHEV-M.") 368 369 def __del__(self): 370 self.conn.disconnect() 371 372 def createVMimage(self, name, cluster, template): 373 try: 374 vmparams = params.VM( 375 name=name, 376 cluster=self.conn.clusters.get(name=cluster), 377 template=self.conn.templates.get(name=template), 378 disks=params.Disks(clone=True) 379 ) 380 self.conn.vms.add(vmparams) 381 setMsg("VM is created") 382 setChanged() 383 return True 384 except Exception as e: 385 setMsg("Failed to create VM") 386 setMsg(str(e)) 387 setFailed() 388 return False 389 390 def createVM(self, name, cluster, os, actiontype): 391 try: 392 vmparams = params.VM( 393 name=name, 394 cluster=self.conn.clusters.get(name=cluster), 395 os=params.OperatingSystem(type_=os), 396 template=self.conn.templates.get(name="Blank"), 397 type_=actiontype 398 ) 399 self.conn.vms.add(vmparams) 400 setMsg("VM is created") 401 setChanged() 402 return True 403 except Exception as e: 404 setMsg("Failed to create VM") 405 setMsg(str(e)) 406 setFailed() 407 return False 408 409 def createDisk(self, vmname, diskname, disksize, diskdomain, diskinterface, diskformat, diskallocationtype, diskboot): 410 VM = self.get_VM(vmname) 411 412 newdisk = params.Disk( 413 name=diskname, 414 size=1024 * 1024 * 1024 * int(disksize), 415 wipe_after_delete=True, 416 sparse=diskallocationtype, 417 interface=diskinterface, 418 format=diskformat, 419 bootable=diskboot, 420 storage_domains=params.StorageDomains( 421 storage_domain=[self.get_domain(diskdomain)] 422 ) 423 ) 424 425 try: 426 VM.disks.add(newdisk) 427 VM.update() 428 setMsg("Successfully added disk " + diskname) 429 setChanged() 430 except Exception as e: 431 setFailed() 432 setMsg("Error attaching " + diskname + "disk, please recheck and remove any leftover configuration.") 433 setMsg(str(e)) 434 return False 435 436 try: 437 currentdisk = VM.disks.get(name=diskname) 438 attempt = 1 439 while currentdisk.status.state != 'ok': 440 currentdisk = VM.disks.get(name=diskname) 441 if attempt == 100: 442 setMsg("Error, disk %s, state %s" % (diskname, str(currentdisk.status.state))) 443 raise Exception() 444 else: 445 attempt += 1 446 time.sleep(2) 447 setMsg("The disk " + diskname + " is ready.") 448 except Exception as e: 449 setFailed() 450 setMsg("Error getting the state of " + diskname + ".") 451 setMsg(str(e)) 452 return False 453 return True 454 455 def createNIC(self, vmname, nicname, vlan, interface): 456 VM = self.get_VM(vmname) 457 CLUSTER = self.get_cluster_byid(VM.cluster.id) 458 DC = self.get_DC_byid(CLUSTER.data_center.id) 459 newnic = params.NIC( 460 name=nicname, 461 network=DC.networks.get(name=vlan), 462 interface=interface 463 ) 464 465 try: 466 VM.nics.add(newnic) 467 VM.update() 468 setMsg("Successfully added iface " + nicname) 469 setChanged() 470 except Exception as e: 471 setFailed() 472 setMsg("Error attaching " + nicname + " iface, please recheck and remove any leftover configuration.") 473 setMsg(str(e)) 474 return False 475 476 try: 477 currentnic = VM.nics.get(name=nicname) 478 attempt = 1 479 while currentnic.active is not True: 480 currentnic = VM.nics.get(name=nicname) 481 if attempt == 100: 482 setMsg("Error, iface %s, state %s" % (nicname, str(currentnic.active))) 483 raise Exception() 484 else: 485 attempt += 1 486 time.sleep(2) 487 setMsg("The iface " + nicname + " is ready.") 488 except Exception as e: 489 setFailed() 490 setMsg("Error getting the state of " + nicname + ".") 491 setMsg(str(e)) 492 return False 493 return True 494 495 def get_DC(self, dc_name): 496 return self.conn.datacenters.get(name=dc_name) 497 498 def get_DC_byid(self, dc_id): 499 return self.conn.datacenters.get(id=dc_id) 500 501 def get_VM(self, vm_name): 502 return self.conn.vms.get(name=vm_name) 503 504 def get_cluster_byid(self, cluster_id): 505 return self.conn.clusters.get(id=cluster_id) 506 507 def get_cluster(self, cluster_name): 508 return self.conn.clusters.get(name=cluster_name) 509 510 def get_domain_byid(self, dom_id): 511 return self.conn.storagedomains.get(id=dom_id) 512 513 def get_domain(self, domain_name): 514 return self.conn.storagedomains.get(name=domain_name) 515 516 def get_disk(self, disk): 517 return self.conn.disks.get(disk) 518 519 def get_network(self, dc_name, network_name): 520 return self.get_DC(dc_name).networks.get(network_name) 521 522 def get_network_byid(self, network_id): 523 return self.conn.networks.get(id=network_id) 524 525 def get_NIC(self, vm_name, nic_name): 526 return self.get_VM(vm_name).nics.get(nic_name) 527 528 def get_Host(self, host_name): 529 return self.conn.hosts.get(name=host_name) 530 531 def get_Host_byid(self, host_id): 532 return self.conn.hosts.get(id=host_id) 533 534 def set_Memory(self, name, memory): 535 VM = self.get_VM(name) 536 VM.memory = int(int(memory) * 1024 * 1024 * 1024) 537 try: 538 VM.update() 539 setMsg("The Memory has been updated.") 540 setChanged() 541 return True 542 except Exception as e: 543 setMsg("Failed to update memory.") 544 setMsg(str(e)) 545 setFailed() 546 return False 547 548 def set_Memory_Policy(self, name, memory_policy): 549 VM = self.get_VM(name) 550 VM.memory_policy.guaranteed = int(memory_policy) * 1024 * 1024 * 1024 551 try: 552 VM.update() 553 setMsg("The memory policy has been updated.") 554 setChanged() 555 return True 556 except Exception as e: 557 setMsg("Failed to update memory policy.") 558 setMsg(str(e)) 559 setFailed() 560 return False 561 562 def set_CPU(self, name, cpu): 563 VM = self.get_VM(name) 564 VM.cpu.topology.cores = int(cpu) 565 try: 566 VM.update() 567 setMsg("The number of CPUs has been updated.") 568 setChanged() 569 return True 570 except Exception as e: 571 setMsg("Failed to update the number of CPUs.") 572 setMsg(str(e)) 573 setFailed() 574 return False 575 576 def set_CPU_share(self, name, cpu_share): 577 VM = self.get_VM(name) 578 VM.cpu_shares = int(cpu_share) 579 try: 580 VM.update() 581 setMsg("The CPU share has been updated.") 582 setChanged() 583 return True 584 except Exception as e: 585 setMsg("Failed to update the CPU share.") 586 setMsg(str(e)) 587 setFailed() 588 return False 589 590 def set_Disk(self, diskname, disksize, diskinterface, diskboot): 591 DISK = self.get_disk(diskname) 592 setMsg("Checking disk " + diskname) 593 if DISK.get_bootable() != diskboot: 594 try: 595 DISK.set_bootable(diskboot) 596 setMsg("Updated the boot option on the disk.") 597 setChanged() 598 except Exception as e: 599 setMsg("Failed to set the boot option on the disk.") 600 setMsg(str(e)) 601 setFailed() 602 return False 603 else: 604 setMsg("The boot option of the disk is correct") 605 if int(DISK.size) < (1024 * 1024 * 1024 * int(disksize)): 606 try: 607 DISK.size = (1024 * 1024 * 1024 * int(disksize)) 608 setMsg("Updated the size of the disk.") 609 setChanged() 610 except Exception as e: 611 setMsg("Failed to update the size of the disk.") 612 setMsg(str(e)) 613 setFailed() 614 return False 615 elif int(DISK.size) > (1024 * 1024 * 1024 * int(disksize)): 616 setMsg("Shrinking disks is not supported") 617 setFailed() 618 return False 619 else: 620 setMsg("The size of the disk is correct") 621 if str(DISK.interface) != str(diskinterface): 622 try: 623 DISK.interface = diskinterface 624 setMsg("Updated the interface of the disk.") 625 setChanged() 626 except Exception as e: 627 setMsg("Failed to update the interface of the disk.") 628 setMsg(str(e)) 629 setFailed() 630 return False 631 else: 632 setMsg("The interface of the disk is correct") 633 return True 634 635 def set_NIC(self, vmname, nicname, newname, vlan, interface): 636 NIC = self.get_NIC(vmname, nicname) 637 VM = self.get_VM(vmname) 638 CLUSTER = self.get_cluster_byid(VM.cluster.id) 639 DC = self.get_DC_byid(CLUSTER.data_center.id) 640 NETWORK = self.get_network(str(DC.name), vlan) 641 checkFail() 642 if NIC.name != newname: 643 NIC.name = newname 644 setMsg('Updating iface name to ' + newname) 645 setChanged() 646 if str(NIC.network.id) != str(NETWORK.id): 647 NIC.set_network(NETWORK) 648 setMsg('Updating iface network to ' + vlan) 649 setChanged() 650 if NIC.interface != interface: 651 NIC.interface = interface 652 setMsg('Updating iface interface to ' + interface) 653 setChanged() 654 try: 655 NIC.update() 656 setMsg('iface has successfully been updated.') 657 except Exception as e: 658 setMsg("Failed to update the iface.") 659 setMsg(str(e)) 660 setFailed() 661 return False 662 return True 663 664 def set_DeleteProtection(self, vmname, del_prot): 665 VM = self.get_VM(vmname) 666 VM.delete_protected = del_prot 667 try: 668 VM.update() 669 setChanged() 670 except Exception as e: 671 setMsg("Failed to update delete protection.") 672 setMsg(str(e)) 673 setFailed() 674 return False 675 return True 676 677 def set_BootOrder(self, vmname, boot_order): 678 VM = self.get_VM(vmname) 679 bootorder = [] 680 for device in boot_order: 681 bootorder.append(params.Boot(dev=device)) 682 VM.os.boot = bootorder 683 684 try: 685 VM.update() 686 setChanged() 687 except Exception as e: 688 setMsg("Failed to update the boot order.") 689 setMsg(str(e)) 690 setFailed() 691 return False 692 return True 693 694 def set_Host(self, host_name, cluster, ifaces): 695 HOST = self.get_Host(host_name) 696 CLUSTER = self.get_cluster(cluster) 697 698 if HOST is None: 699 setMsg("Host does not exist.") 700 ifacelist = dict() 701 networklist = [] 702 manageip = '' 703 704 try: 705 for iface in ifaces: 706 try: 707 setMsg('creating host interface ' + iface['name']) 708 if 'management' in iface: 709 manageip = iface['ip'] 710 if 'boot_protocol' not in iface: 711 if 'ip' in iface: 712 iface['boot_protocol'] = 'static' 713 else: 714 iface['boot_protocol'] = 'none' 715 if 'ip' not in iface: 716 iface['ip'] = '' 717 if 'netmask' not in iface: 718 iface['netmask'] = '' 719 if 'gateway' not in iface: 720 iface['gateway'] = '' 721 722 if 'network' in iface: 723 if 'bond' in iface: 724 bond = [] 725 for slave in iface['bond']: 726 bond.append(ifacelist[slave]) 727 try: 728 tmpiface = params.Bonding( 729 slaves=params.Slaves(host_nic=bond), 730 options=params.Options( 731 option=[ 732 params.Option(name='miimon', value='100'), 733 params.Option(name='mode', value='4') 734 ] 735 ) 736 ) 737 except Exception as e: 738 setMsg('Failed to create the bond for ' + iface['name']) 739 setFailed() 740 setMsg(str(e)) 741 return False 742 try: 743 tmpnetwork = params.HostNIC( 744 network=params.Network(name=iface['network']), 745 name=iface['name'], 746 boot_protocol=iface['boot_protocol'], 747 ip=params.IP( 748 address=iface['ip'], 749 netmask=iface['netmask'], 750 gateway=iface['gateway'] 751 ), 752 override_configuration=True, 753 bonding=tmpiface) 754 networklist.append(tmpnetwork) 755 setMsg('Applying network ' + iface['name']) 756 except Exception as e: 757 setMsg('Failed to set' + iface['name'] + ' as network interface') 758 setFailed() 759 setMsg(str(e)) 760 return False 761 else: 762 tmpnetwork = params.HostNIC( 763 network=params.Network(name=iface['network']), 764 name=iface['name'], 765 boot_protocol=iface['boot_protocol'], 766 ip=params.IP( 767 address=iface['ip'], 768 netmask=iface['netmask'], 769 gateway=iface['gateway'] 770 )) 771 networklist.append(tmpnetwork) 772 setMsg('Applying network ' + iface['name']) 773 else: 774 tmpiface = params.HostNIC( 775 name=iface['name'], 776 network=params.Network(), 777 boot_protocol=iface['boot_protocol'], 778 ip=params.IP( 779 address=iface['ip'], 780 netmask=iface['netmask'], 781 gateway=iface['gateway'] 782 )) 783 ifacelist[iface['name']] = tmpiface 784 except Exception as e: 785 setMsg('Failed to set ' + iface['name']) 786 setFailed() 787 setMsg(str(e)) 788 return False 789 except Exception as e: 790 setMsg('Failed to set networks') 791 setMsg(str(e)) 792 setFailed() 793 return False 794 795 if manageip == '': 796 setMsg('No management network is defined') 797 setFailed() 798 return False 799 800 try: 801 HOST = params.Host(name=host_name, address=manageip, cluster=CLUSTER, ssh=params.SSH(authentication_method='publickey')) 802 if self.conn.hosts.add(HOST): 803 setChanged() 804 HOST = self.get_Host(host_name) 805 state = HOST.status.state 806 while (state != 'non_operational' and state != 'up'): 807 HOST = self.get_Host(host_name) 808 state = HOST.status.state 809 time.sleep(1) 810 if state == 'non_responsive': 811 setMsg('Failed to add host to RHEVM') 812 setFailed() 813 return False 814 815 setMsg('status host: up') 816 time.sleep(5) 817 818 HOST = self.get_Host(host_name) 819 state = HOST.status.state 820 setMsg('State before setting to maintenance: ' + str(state)) 821 HOST.deactivate() 822 while state != 'maintenance': 823 HOST = self.get_Host(host_name) 824 state = HOST.status.state 825 time.sleep(1) 826 setMsg('status host: maintenance') 827 828 try: 829 HOST.nics.setupnetworks(params.Action( 830 force=True, 831 check_connectivity=False, 832 host_nics=params.HostNics(host_nic=networklist) 833 )) 834 setMsg('nics are set') 835 except Exception as e: 836 setMsg('Failed to apply networkconfig') 837 setFailed() 838 setMsg(str(e)) 839 return False 840 841 try: 842 HOST.commitnetconfig() 843 setMsg('Network config is saved') 844 except Exception as e: 845 setMsg('Failed to save networkconfig') 846 setFailed() 847 setMsg(str(e)) 848 return False 849 except Exception as e: 850 if 'The Host name is already in use' in str(e): 851 setMsg("Host already exists") 852 else: 853 setMsg("Failed to add host") 854 setFailed() 855 setMsg(str(e)) 856 return False 857 858 HOST.activate() 859 while state != 'up': 860 HOST = self.get_Host(host_name) 861 state = HOST.status.state 862 time.sleep(1) 863 if state == 'non_responsive': 864 setMsg('Failed to apply networkconfig.') 865 setFailed() 866 return False 867 setMsg('status host: up') 868 else: 869 setMsg("Host exists.") 870 871 return True 872 873 def del_NIC(self, vmname, nicname): 874 return self.get_NIC(vmname, nicname).delete() 875 876 def remove_VM(self, vmname): 877 VM = self.get_VM(vmname) 878 try: 879 VM.delete() 880 except Exception as e: 881 setMsg("Failed to remove VM.") 882 setMsg(str(e)) 883 setFailed() 884 return False 885 return True 886 887 def start_VM(self, vmname, timeout): 888 VM = self.get_VM(vmname) 889 try: 890 VM.start() 891 except Exception as e: 892 setMsg("Failed to start VM.") 893 setMsg(str(e)) 894 setFailed() 895 return False 896 return self.wait_VM(vmname, "up", timeout) 897 898 def wait_VM(self, vmname, state, timeout): 899 VM = self.get_VM(vmname) 900 while VM.status.state != state: 901 VM = self.get_VM(vmname) 902 time.sleep(10) 903 if timeout is not False: 904 timeout -= 10 905 if timeout <= 0: 906 setMsg("Timeout expired") 907 setFailed() 908 return False 909 return True 910 911 def stop_VM(self, vmname, timeout): 912 VM = self.get_VM(vmname) 913 try: 914 VM.stop() 915 except Exception as e: 916 setMsg("Failed to stop VM.") 917 setMsg(str(e)) 918 setFailed() 919 return False 920 return self.wait_VM(vmname, "down", timeout) 921 922 def set_CD(self, vmname, cd_drive): 923 VM = self.get_VM(vmname) 924 try: 925 if str(VM.status.state) == 'down': 926 cdrom = params.CdRom(file=cd_drive) 927 VM.cdroms.add(cdrom) 928 setMsg("Attached the image.") 929 setChanged() 930 else: 931 cdrom = VM.cdroms.get(id="00000000-0000-0000-0000-000000000000") 932 cdrom.set_file(cd_drive) 933 cdrom.update(current=True) 934 setMsg("Attached the image.") 935 setChanged() 936 except Exception as e: 937 setMsg("Failed to attach image.") 938 setMsg(str(e)) 939 setFailed() 940 return False 941 return True 942 943 def set_VM_Host(self, vmname, vmhost): 944 VM = self.get_VM(vmname) 945 HOST = self.get_Host(vmhost) 946 try: 947 VM.placement_policy.host = HOST 948 VM.update() 949 setMsg("Set startup host to " + vmhost) 950 setChanged() 951 except Exception as e: 952 setMsg("Failed to set startup host.") 953 setMsg(str(e)) 954 setFailed() 955 return False 956 return True 957 958 def migrate_VM(self, vmname, vmhost): 959 VM = self.get_VM(vmname) 960 961 HOST = self.get_Host_byid(VM.host.id) 962 if str(HOST.name) != vmhost: 963 try: 964 VM.migrate( 965 action=params.Action( 966 host=params.Host( 967 name=vmhost, 968 ) 969 ), 970 ) 971 setChanged() 972 setMsg("VM migrated to " + vmhost) 973 except Exception as e: 974 setMsg("Failed to set startup host.") 975 setMsg(str(e)) 976 setFailed() 977 return False 978 return True 979 980 def remove_CD(self, vmname): 981 VM = self.get_VM(vmname) 982 try: 983 VM.cdroms.get(id="00000000-0000-0000-0000-000000000000").delete() 984 setMsg("Removed the image.") 985 setChanged() 986 except Exception as e: 987 setMsg("Failed to remove the image.") 988 setMsg(str(e)) 989 setFailed() 990 return False 991 return True 992 993 994class RHEV(object): 995 def __init__(self, module): 996 self.module = module 997 998 def __get_conn(self): 999 self.conn = RHEVConn(self.module) 1000 return self.conn 1001 1002 def test(self): 1003 self.__get_conn() 1004 return "OK" 1005 1006 def getVM(self, name): 1007 self.__get_conn() 1008 VM = self.conn.get_VM(name) 1009 if VM: 1010 vminfo = dict() 1011 vminfo['uuid'] = VM.id 1012 vminfo['name'] = VM.name 1013 vminfo['status'] = VM.status.state 1014 vminfo['cpu_cores'] = VM.cpu.topology.cores 1015 vminfo['cpu_sockets'] = VM.cpu.topology.sockets 1016 vminfo['cpu_shares'] = VM.cpu_shares 1017 vminfo['memory'] = (int(VM.memory) // 1024 // 1024 // 1024) 1018 vminfo['mem_pol'] = (int(VM.memory_policy.guaranteed) // 1024 // 1024 // 1024) 1019 vminfo['os'] = VM.get_os().type_ 1020 vminfo['del_prot'] = VM.delete_protected 1021 try: 1022 vminfo['host'] = str(self.conn.get_Host_byid(str(VM.host.id)).name) 1023 except Exception: 1024 vminfo['host'] = None 1025 vminfo['boot_order'] = [] 1026 for boot_dev in VM.os.get_boot(): 1027 vminfo['boot_order'].append(str(boot_dev.dev)) 1028 vminfo['disks'] = [] 1029 for DISK in VM.disks.list(): 1030 disk = dict() 1031 disk['name'] = DISK.name 1032 disk['size'] = (int(DISK.size) // 1024 // 1024 // 1024) 1033 disk['domain'] = str((self.conn.get_domain_byid(DISK.get_storage_domains().get_storage_domain()[0].id)).name) 1034 disk['interface'] = DISK.interface 1035 vminfo['disks'].append(disk) 1036 vminfo['ifaces'] = [] 1037 for NIC in VM.nics.list(): 1038 iface = dict() 1039 iface['name'] = str(NIC.name) 1040 iface['vlan'] = str(self.conn.get_network_byid(NIC.get_network().id).name) 1041 iface['interface'] = NIC.interface 1042 iface['mac'] = NIC.mac.address 1043 vminfo['ifaces'].append(iface) 1044 vminfo[str(NIC.name)] = NIC.mac.address 1045 CLUSTER = self.conn.get_cluster_byid(VM.cluster.id) 1046 if CLUSTER: 1047 vminfo['cluster'] = CLUSTER.name 1048 else: 1049 vminfo = False 1050 return vminfo 1051 1052 def createVMimage(self, name, cluster, template, disks): 1053 self.__get_conn() 1054 return self.conn.createVMimage(name, cluster, template, disks) 1055 1056 def createVM(self, name, cluster, os, actiontype): 1057 self.__get_conn() 1058 return self.conn.createVM(name, cluster, os, actiontype) 1059 1060 def setMemory(self, name, memory): 1061 self.__get_conn() 1062 return self.conn.set_Memory(name, memory) 1063 1064 def setMemoryPolicy(self, name, memory_policy): 1065 self.__get_conn() 1066 return self.conn.set_Memory_Policy(name, memory_policy) 1067 1068 def setCPU(self, name, cpu): 1069 self.__get_conn() 1070 return self.conn.set_CPU(name, cpu) 1071 1072 def setCPUShare(self, name, cpu_share): 1073 self.__get_conn() 1074 return self.conn.set_CPU_share(name, cpu_share) 1075 1076 def setDisks(self, name, disks): 1077 self.__get_conn() 1078 counter = 0 1079 bootselect = False 1080 for disk in disks: 1081 if 'bootable' in disk: 1082 if disk['bootable'] is True: 1083 bootselect = True 1084 1085 for disk in disks: 1086 diskname = name + "_Disk" + str(counter) + "_" + disk.get('name', '').replace('/', '_') 1087 disksize = disk.get('size', 1) 1088 diskdomain = disk.get('domain', None) 1089 if diskdomain is None: 1090 setMsg("`domain` is a required disk key.") 1091 setFailed() 1092 return False 1093 diskinterface = disk.get('interface', 'virtio') 1094 diskformat = disk.get('format', 'raw') 1095 diskallocationtype = disk.get('thin', False) 1096 diskboot = disk.get('bootable', False) 1097 1098 if bootselect is False and counter == 0: 1099 diskboot = True 1100 1101 DISK = self.conn.get_disk(diskname) 1102 1103 if DISK is None: 1104 self.conn.createDisk(name, diskname, disksize, diskdomain, diskinterface, diskformat, diskallocationtype, diskboot) 1105 else: 1106 self.conn.set_Disk(diskname, disksize, diskinterface, diskboot) 1107 checkFail() 1108 counter += 1 1109 1110 return True 1111 1112 def setNetworks(self, vmname, ifaces): 1113 self.__get_conn() 1114 VM = self.conn.get_VM(vmname) 1115 1116 counter = 0 1117 length = len(ifaces) 1118 1119 for NIC in VM.nics.list(): 1120 if counter < length: 1121 iface = ifaces[counter] 1122 name = iface.get('name', None) 1123 if name is None: 1124 setMsg("`name` is a required iface key.") 1125 setFailed() 1126 elif str(name) != str(NIC.name): 1127 setMsg("ifaces are in the wrong order, rebuilding everything.") 1128 for NIC in VM.nics.list(): 1129 self.conn.del_NIC(vmname, NIC.name) 1130 self.setNetworks(vmname, ifaces) 1131 checkFail() 1132 return True 1133 vlan = iface.get('vlan', None) 1134 if vlan is None: 1135 setMsg("`vlan` is a required iface key.") 1136 setFailed() 1137 checkFail() 1138 interface = iface.get('interface', 'virtio') 1139 self.conn.set_NIC(vmname, str(NIC.name), name, vlan, interface) 1140 else: 1141 self.conn.del_NIC(vmname, NIC.name) 1142 counter += 1 1143 checkFail() 1144 1145 while counter < length: 1146 iface = ifaces[counter] 1147 name = iface.get('name', None) 1148 if name is None: 1149 setMsg("`name` is a required iface key.") 1150 setFailed() 1151 vlan = iface.get('vlan', None) 1152 if vlan is None: 1153 setMsg("`vlan` is a required iface key.") 1154 setFailed() 1155 if failed is True: 1156 return False 1157 interface = iface.get('interface', 'virtio') 1158 self.conn.createNIC(vmname, name, vlan, interface) 1159 1160 counter += 1 1161 checkFail() 1162 return True 1163 1164 def setDeleteProtection(self, vmname, del_prot): 1165 self.__get_conn() 1166 VM = self.conn.get_VM(vmname) 1167 if bool(VM.delete_protected) != bool(del_prot): 1168 self.conn.set_DeleteProtection(vmname, del_prot) 1169 checkFail() 1170 setMsg("`delete protection` has been updated.") 1171 else: 1172 setMsg("`delete protection` already has the right value.") 1173 return True 1174 1175 def setBootOrder(self, vmname, boot_order): 1176 self.__get_conn() 1177 VM = self.conn.get_VM(vmname) 1178 bootorder = [] 1179 for boot_dev in VM.os.get_boot(): 1180 bootorder.append(str(boot_dev.dev)) 1181 1182 if boot_order != bootorder: 1183 self.conn.set_BootOrder(vmname, boot_order) 1184 setMsg('The boot order has been set') 1185 else: 1186 setMsg('The boot order has already been set') 1187 return True 1188 1189 def removeVM(self, vmname): 1190 self.__get_conn() 1191 self.setPower(vmname, "down", 300) 1192 return self.conn.remove_VM(vmname) 1193 1194 def setPower(self, vmname, state, timeout): 1195 self.__get_conn() 1196 VM = self.conn.get_VM(vmname) 1197 if VM is None: 1198 setMsg("VM does not exist.") 1199 setFailed() 1200 return False 1201 1202 if state == VM.status.state: 1203 setMsg("VM state was already " + state) 1204 else: 1205 if state == "up": 1206 setMsg("VM is going to start") 1207 self.conn.start_VM(vmname, timeout) 1208 setChanged() 1209 elif state == "down": 1210 setMsg("VM is going to stop") 1211 self.conn.stop_VM(vmname, timeout) 1212 setChanged() 1213 elif state == "restarted": 1214 self.setPower(vmname, "down", timeout) 1215 checkFail() 1216 self.setPower(vmname, "up", timeout) 1217 checkFail() 1218 setMsg("the vm state is set to " + state) 1219 return True 1220 1221 def setCD(self, vmname, cd_drive): 1222 self.__get_conn() 1223 if cd_drive: 1224 return self.conn.set_CD(vmname, cd_drive) 1225 else: 1226 return self.conn.remove_CD(vmname) 1227 1228 def setVMHost(self, vmname, vmhost): 1229 self.__get_conn() 1230 return self.conn.set_VM_Host(vmname, vmhost) 1231 1232 def setHost(self, hostname, cluster, ifaces): 1233 self.__get_conn() 1234 return self.conn.set_Host(hostname, cluster, ifaces) 1235 1236 1237def checkFail(): 1238 if failed: 1239 module.fail_json(msg=msg) 1240 else: 1241 return True 1242 1243 1244def setFailed(): 1245 global failed 1246 failed = True 1247 1248 1249def setChanged(): 1250 global changed 1251 changed = True 1252 1253 1254def setMsg(message): 1255 global failed 1256 msg.append(message) 1257 1258 1259def core(module): 1260 1261 r = RHEV(module) 1262 1263 state = module.params.get('state') 1264 1265 if state == 'ping': 1266 r.test() 1267 return RHEV_SUCCESS, {"ping": "pong"} 1268 elif state == 'info': 1269 name = module.params.get('name') 1270 if not name: 1271 setMsg("`name` is a required argument.") 1272 return RHEV_FAILED, msg 1273 vminfo = r.getVM(name) 1274 return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} 1275 elif state == 'present': 1276 created = False 1277 name = module.params.get('name') 1278 if not name: 1279 setMsg("`name` is a required argument.") 1280 return RHEV_FAILED, msg 1281 actiontype = module.params.get('type') 1282 if actiontype == 'server' or actiontype == 'desktop': 1283 vminfo = r.getVM(name) 1284 if vminfo: 1285 setMsg('VM exists') 1286 else: 1287 # Create VM 1288 cluster = module.params.get('cluster') 1289 if cluster is None: 1290 setMsg("cluster is a required argument.") 1291 setFailed() 1292 template = module.params.get('image') 1293 if template: 1294 disks = module.params.get('disks') 1295 if disks is None: 1296 setMsg("disks is a required argument.") 1297 setFailed() 1298 checkFail() 1299 if r.createVMimage(name, cluster, template, disks) is False: 1300 return RHEV_FAILED, vminfo 1301 else: 1302 os = module.params.get('osver') 1303 if os is None: 1304 setMsg("osver is a required argument.") 1305 setFailed() 1306 checkFail() 1307 if r.createVM(name, cluster, os, actiontype) is False: 1308 return RHEV_FAILED, vminfo 1309 created = True 1310 1311 # Set MEMORY and MEMORY POLICY 1312 vminfo = r.getVM(name) 1313 memory = module.params.get('vmmem') 1314 if memory is not None: 1315 memory_policy = module.params.get('mempol') 1316 if memory_policy == 0: 1317 memory_policy = memory 1318 mem_pol_nok = True 1319 if int(vminfo['mem_pol']) == memory_policy: 1320 setMsg("Memory is correct") 1321 mem_pol_nok = False 1322 1323 mem_nok = True 1324 if int(vminfo['memory']) == memory: 1325 setMsg("Memory is correct") 1326 mem_nok = False 1327 1328 if memory_policy > memory: 1329 setMsg('memory_policy cannot have a higher value than memory.') 1330 return RHEV_FAILED, msg 1331 1332 if mem_nok and mem_pol_nok: 1333 if memory_policy > int(vminfo['memory']): 1334 r.setMemory(vminfo['name'], memory) 1335 r.setMemoryPolicy(vminfo['name'], memory_policy) 1336 else: 1337 r.setMemoryPolicy(vminfo['name'], memory_policy) 1338 r.setMemory(vminfo['name'], memory) 1339 elif mem_nok: 1340 r.setMemory(vminfo['name'], memory) 1341 elif mem_pol_nok: 1342 r.setMemoryPolicy(vminfo['name'], memory_policy) 1343 checkFail() 1344 1345 # Set CPU 1346 cpu = module.params.get('vmcpu') 1347 if int(vminfo['cpu_cores']) == cpu: 1348 setMsg("Number of CPUs is correct") 1349 else: 1350 if r.setCPU(vminfo['name'], cpu) is False: 1351 return RHEV_FAILED, msg 1352 1353 # Set CPU SHARE 1354 cpu_share = module.params.get('cpu_share') 1355 if cpu_share is not None: 1356 if int(vminfo['cpu_shares']) == cpu_share: 1357 setMsg("CPU share is correct.") 1358 else: 1359 if r.setCPUShare(vminfo['name'], cpu_share) is False: 1360 return RHEV_FAILED, msg 1361 1362 # Set DISKS 1363 disks = module.params.get('disks') 1364 if disks is not None: 1365 if r.setDisks(vminfo['name'], disks) is False: 1366 return RHEV_FAILED, msg 1367 1368 # Set NETWORKS 1369 ifaces = module.params.get('ifaces', None) 1370 if ifaces is not None: 1371 if r.setNetworks(vminfo['name'], ifaces) is False: 1372 return RHEV_FAILED, msg 1373 1374 # Set Delete Protection 1375 del_prot = module.params.get('del_prot') 1376 if r.setDeleteProtection(vminfo['name'], del_prot) is False: 1377 return RHEV_FAILED, msg 1378 1379 # Set Boot Order 1380 boot_order = module.params.get('boot_order') 1381 if r.setBootOrder(vminfo['name'], boot_order) is False: 1382 return RHEV_FAILED, msg 1383 1384 # Set VM Host 1385 vmhost = module.params.get('vmhost') 1386 if vmhost: 1387 if r.setVMHost(vminfo['name'], vmhost) is False: 1388 return RHEV_FAILED, msg 1389 1390 vminfo = r.getVM(name) 1391 vminfo['created'] = created 1392 return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} 1393 1394 if actiontype == 'host': 1395 cluster = module.params.get('cluster') 1396 if cluster is None: 1397 setMsg("cluster is a required argument.") 1398 setFailed() 1399 ifaces = module.params.get('ifaces') 1400 if ifaces is None: 1401 setMsg("ifaces is a required argument.") 1402 setFailed() 1403 if r.setHost(name, cluster, ifaces) is False: 1404 return RHEV_FAILED, msg 1405 return RHEV_SUCCESS, {'changed': changed, 'msg': msg} 1406 1407 elif state == 'absent': 1408 name = module.params.get('name') 1409 if not name: 1410 setMsg("`name` is a required argument.") 1411 return RHEV_FAILED, msg 1412 actiontype = module.params.get('type') 1413 if actiontype == 'server' or actiontype == 'desktop': 1414 vminfo = r.getVM(name) 1415 if vminfo: 1416 setMsg('VM exists') 1417 1418 # Set Delete Protection 1419 del_prot = module.params.get('del_prot') 1420 if r.setDeleteProtection(vminfo['name'], del_prot) is False: 1421 return RHEV_FAILED, msg 1422 1423 # Remove VM 1424 if r.removeVM(vminfo['name']) is False: 1425 return RHEV_FAILED, msg 1426 setMsg('VM has been removed.') 1427 vminfo['state'] = 'DELETED' 1428 else: 1429 setMsg('VM was already removed.') 1430 return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} 1431 1432 elif state == 'up' or state == 'down' or state == 'restarted': 1433 name = module.params.get('name') 1434 if not name: 1435 setMsg("`name` is a required argument.") 1436 return RHEV_FAILED, msg 1437 timeout = module.params.get('timeout') 1438 if r.setPower(name, state, timeout) is False: 1439 return RHEV_FAILED, msg 1440 vminfo = r.getVM(name) 1441 return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} 1442 1443 elif state == 'cd': 1444 name = module.params.get('name') 1445 cd_drive = module.params.get('cd_drive') 1446 if r.setCD(name, cd_drive) is False: 1447 return RHEV_FAILED, msg 1448 return RHEV_SUCCESS, {'changed': changed, 'msg': msg} 1449 1450 1451def main(): 1452 global module 1453 module = AnsibleModule( 1454 argument_spec=dict( 1455 state=dict(type='str', default='present', choices=['absent', 'cd', 'down', 'info', 'ping', 'present', 'restarted', 'up']), 1456 user=dict(type='str', default='admin@internal'), 1457 password=dict(type='str', required=True, no_log=True), 1458 server=dict(type='str', default='127.0.0.1'), 1459 port=dict(type='int', default=443), 1460 insecure_api=dict(type='bool', default=False), 1461 name=dict(type='str'), 1462 image=dict(type='str'), 1463 datacenter=dict(type='str', default="Default"), 1464 type=dict(type='str', default='server', choices=['desktop', 'host', 'server']), 1465 cluster=dict(type='str', default=''), 1466 vmhost=dict(type='str'), 1467 vmcpu=dict(type='int', default=2), 1468 vmmem=dict(type='int', default=1), 1469 disks=dict(type='list', elements='str'), 1470 osver=dict(type='str', default="rhel_6x64"), 1471 ifaces=dict(type='list', elements='str', aliases=['interfaces', 'nics']), 1472 timeout=dict(type='int'), 1473 mempol=dict(type='int', default=1), 1474 vm_ha=dict(type='bool', default=True), 1475 cpu_share=dict(type='int', default=0), 1476 boot_order=dict(type='list', elements='str', default=['hd', 'network']), 1477 del_prot=dict(type='bool', default=True), 1478 cd_drive=dict(type='str'), 1479 ), 1480 ) 1481 1482 if not HAS_SDK: 1483 module.fail_json(msg="The 'ovirtsdk' module is not importable. Check the requirements.") 1484 1485 rc = RHEV_SUCCESS 1486 try: 1487 rc, result = core(module) 1488 except Exception as e: 1489 module.fail_json(msg=str(e)) 1490 1491 if rc != 0: # something went wrong emit the msg 1492 module.fail_json(rc=rc, msg=result) 1493 1494 module.exit_json(**result) 1495 1496 1497if __name__ == '__main__': 1498 main() 1499