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