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