From e5d42c6313ba051f22f83cbde3da9410fd7fc3b9 Mon Sep 17 00:00:00 2001 From: Cedric Bosdonnat Date: Fri, 13 Mar 2020 16:38:08 +0100 Subject: [PATCH] openSUSE-3000 virt-defined-states (#222) * Create virt.pool_defined state out of virt.pool_running Users may want to use states to ensure a virtual storage pool is defined and not enforce it to be running. Extract the code that performs the pool definition / update from virt.pool_running state into a virt.pool_defined. Obviously the virt.pool_running state calls the virt.pool_defined one. In such a case no additionnal test is needed for virt.pool_defined since this is already tested with virt.pool_running. * Add virt.update test parameter In order to allow running dry-runs of virt.update module add a test parameter. This will later be used by the virt states. * Extract virt.defined state from virt.running In order to ensure a virtual guest is defined independently of its status, extract the corresponding code from the virt.running state. This commit also handles the __opts__['test'] for the running state. Since the update call only performs changes if needed, deprecate the update parameter. * Extract virt.network_defined from virt.network_running Just like domains and storage pools, users may want to ensure a network is defined without influencing it's status. Extract the code from network_running state defining the network into a network_defined state. While at it, support __opt__['test'] == True in these states. Updating the network definition in the pool_defined state will come in a future PR. * Fix virt.update to handle None mem and cpu virt.running state now may call virt.update with None mem and cpu parameters. This was not handled in _gen_xml(). Also add some more tests cases matching this for virt.update. --- salt/modules/virt.py | 16 +- salt/states/virt.py | 673 +++++++++++++++----- tests/unit/modules/test_virt.py | 26 + tests/unit/states/test_virt.py | 1346 ++++++++++++++++++++++++++++++++------- 4 files changed, 1665 insertions(+), 396 deletions(-) diff --git a/salt/modules/virt.py b/salt/modules/virt.py index 3889238ecd..f0820e8825 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py @@ -1783,6 +1783,7 @@ def update(name, graphics=None, live=True, boot=None, + test=False, **kwargs): ''' Update the definition of an existing domain. @@ -1835,6 +1836,10 @@ def update(name, .. versionadded:: 3000 + :param test: run in dry-run mode if set to True + + .. versionadded:: sodium + :return: Returns a dictionary indicating the status of what has been done. It is structured in @@ -1880,8 +1885,8 @@ def update(name, boot = _handle_remote_boot_params(boot) new_desc = ElementTree.fromstring(_gen_xml(name, - cpu, - mem, + cpu or 0, + mem or 0, all_disks, _get_merged_nics(hypervisor, nic_profile, interfaces), hypervisor, @@ -1973,11 +1978,12 @@ def update(name, if changes['disk']: for idx, item in enumerate(changes['disk']['sorted']): source_file = all_disks[idx]['source_file'] - if item in changes['disk']['new'] and source_file and not os.path.isfile(source_file): + if item in changes['disk']['new'] and source_file and not os.path.isfile(source_file) and not test: _qemu_image_create(all_disks[idx]) try: - conn.defineXML(salt.utils.stringutils.to_str(ElementTree.tostring(desc))) + if not test: + conn.defineXML(salt.utils.stringutils.to_str(ElementTree.tostring(desc))) status['definition'] = True except libvirt.libvirtError as err: conn.close() @@ -2010,7 +2016,7 @@ def update(name, for cmd in commands: try: - ret = getattr(domain, cmd['cmd'])(*cmd['args']) + ret = getattr(domain, cmd['cmd'])(*cmd['args']) if not test else 0 device_type = cmd['device'] if device_type in ['cpu', 'mem']: status[device_type] = not bool(ret) diff --git a/salt/states/virt.py b/salt/states/virt.py index 55a9ad2616..819776d707 100644 --- a/salt/states/virt.py +++ b/salt/states/virt.py @@ -14,6 +14,7 @@ for the generation and signing of certificates for systems running libvirt: # Import Python libs from __future__ import absolute_import, print_function, unicode_literals +import copy import fnmatch import os @@ -245,6 +246,187 @@ def powered_off(name, connection=None, username=None, password=None): connection=connection, username=username, password=password) +def defined(name, + cpu=None, + mem=None, + vm_type=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + seed=True, + install=True, + pub_key=None, + priv_key=None, + connection=None, + username=None, + password=None, + os_type=None, + arch=None, + boot=None, + update=True): + ''' + Starts an existing guest, or defines and starts a new VM with specified arguments. + + .. versionadded:: sodium + + :param name: name of the virtual machine to run + :param cpu: number of CPUs for the virtual machine to create + :param mem: amount of memory in MiB for the new virtual machine + :param vm_type: force virtual machine type for the new VM. The default value is taken from + the host capabilities. This could be useful for example to use ``'qemu'`` type instead + of the ``'kvm'`` one. + :param disk_profile: + Name of the disk profile to use for the new virtual machine + :param disks: + List of disk to create for the new virtual machine. + See :ref:`init-disk-def` for more details on the items on this list. + :param nic_profile: + Name of the network interfaces profile to use for the new virtual machine + :param interfaces: + List of network interfaces to create for the new virtual machine. + See :ref:`init-nic-def` for more details on the items on this list. + :param graphics: + Graphics device to create for the new virtual machine. + See :ref:`init-graphics-def` for more details on this dictionary + :param saltenv: + Fileserver environment (Default: ``'base'``). + See :mod:`cp module for more details ` + :param seed: ``True`` to seed the disk image. Only used when the ``image`` parameter is provided. + (Default: ``True``) + :param install: install salt minion if absent (Default: ``True``) + :param pub_key: public key to seed with (Default: ``None``) + :param priv_key: public key to seed with (Default: ``None``) + :param seed_cmd: Salt command to execute to seed the image. (Default: ``'seed.apply'``) + :param connection: libvirt connection URI, overriding defaults + :param username: username to connect with, overriding defaults + :param password: password to connect with, overriding defaults + :param os_type: + type of virtualization as found in the ``//os/type`` element of the libvirt definition. + The default value is taken from the host capabilities, with a preference for ``hvm``. + Only used when creating a new virtual machine. + :param arch: + architecture of the virtual machine. The default value is taken from the host capabilities, + but ``x86_64`` is prefed over ``i686``. Only used when creating a new virtual machine. + + :param boot: + Specifies kernel for the virtual machine, as well as boot parameters + for the virtual machine. This is an optionl parameter, and all of the + keys are optional within the dictionary. If a remote path is provided + to kernel or initrd, salt will handle the downloading of the specified + remote fild, and will modify the XML accordingly. + + .. code-block:: python + + { + 'kernel': '/root/f8-i386-vmlinuz', + 'initrd': '/root/f8-i386-initrd', + 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' + } + + :param update: set to ``False`` to prevent updating a defined domain. (Default: ``True``) + + .. deprecated:: sodium + + .. rubric:: Example States + + Make sure a virtual machine called ``domain_name`` is defined: + + .. code-block:: yaml + + domain_name: + virt.defined: + - cpu: 2 + - mem: 2048 + - disk_profile: prod + - disks: + - name: system + size: 8192 + overlay_image: True + pool: default + image: /path/to/image.qcow2 + - name: data + size: 16834 + - nic_profile: prod + - interfaces: + - name: eth0 + mac: 01:23:45:67:89:AB + - name: eth1 + type: network + source: admin + - graphics: + type: spice + listen: + type: address + address: 192.168.0.125 + + ''' + + ret = {'name': name, + 'changes': {}, + 'result': True if not __opts__['test'] else None, + 'comment': '' + } + + try: + if name in __salt__['virt.list_domains'](connection=connection, username=username, password=password): + status = {} + if update: + status = __salt__['virt.update'](name, + cpu=cpu, + mem=mem, + disk_profile=disk_profile, + disks=disks, + nic_profile=nic_profile, + interfaces=interfaces, + graphics=graphics, + live=True, + connection=connection, + username=username, + password=password, + boot=boot, + test=__opts__['test']) + ret['changes'][name] = status + if not status.get('definition'): + ret['comment'] = 'Domain {0} unchanged'.format(name) + ret['result'] = True + elif status.get('errors'): + ret['comment'] = 'Domain {0} updated with live update(s) failures'.format(name) + else: + ret['comment'] = 'Domain {0} updated'.format(name) + else: + if not __opts__['test']: + __salt__['virt.init'](name, + cpu=cpu, + mem=mem, + os_type=os_type, + arch=arch, + hypervisor=vm_type, + disk=disk_profile, + disks=disks, + nic=nic_profile, + interfaces=interfaces, + graphics=graphics, + seed=seed, + install=install, + pub_key=pub_key, + priv_key=priv_key, + connection=connection, + username=username, + password=password, + boot=boot, + start=False) + ret['changes'][name] = {'definition': True} + ret['comment'] = 'Domain {0} defined'.format(name) + except libvirt.libvirtError as err: + # Something bad happened when defining / updating the VM, report it + ret['comment'] = six.text_type(err) + ret['result'] = False + + return ret + + def running(name, cpu=None, mem=None, @@ -326,9 +508,10 @@ def running(name, :param seed_cmd: Salt command to execute to seed the image. (Default: ``'seed.apply'``) .. versionadded:: 2019.2.0 - :param update: set to ``True`` to update a defined module. (Default: ``False``) + :param update: set to ``True`` to update a defined domain. (Default: ``False``) .. versionadded:: 2019.2.0 + .. deprecated:: sodium :param connection: libvirt connection URI, overriding defaults .. versionadded:: 2019.2.0 @@ -424,93 +607,74 @@ def running(name, address: 192.168.0.125 ''' - - ret = {'name': name, - 'changes': {}, - 'result': True, - 'comment': '{0} is running'.format(name) - } - - try: + merged_disks = disks + if image: + default_disks = [{'system': {}}] + disknames = ['system'] + if disk_profile: + disklist = copy.deepcopy( + __salt__['config.get']('virt:disk', {}).get(disk_profile, default_disks)) + disknames = disklist.keys() + disk = {'name': disknames[0], 'image': image} + if merged_disks: + first_disk = [d for d in merged_disks if d.get('name') == disknames[0]] + if first_disk and 'image' not in first_disk[0]: + first_disk[0]['image'] = image + else: + merged_disks.append(disk) + else: + merged_disks = [disk] + salt.utils.versions.warn_until( + 'Sodium', + '\'image\' parameter has been deprecated. Rather use the \'disks\' parameter ' + 'to override or define the image. \'image\' will be removed in {version}.' + ) + + if not update: + salt.utils.versions.warn_until('Magnesium', + '\'update\' parameter has been deprecated. Future behavior will be the one of update=True' + 'It will be removed in {version}.') + ret = defined(name, + cpu=cpu, + mem=mem, + vm_type=vm_type, + disk_profile=disk_profile, + disks=merged_disks, + nic_profile=nic_profile, + interfaces=interfaces, + graphics=graphics, + seed=seed, + install=install, + pub_key=pub_key, + priv_key=priv_key, + os_type=os_type, + arch=arch, + boot=boot, + update=update, + connection=connection, + username=username, + password=password) + + result = True if not __opts__['test'] else None + if ret['result'] is None or ret['result']: + changed = ret['changes'][name].get('definition', False) try: domain_state = __salt__['virt.vm_state'](name) if domain_state.get(name) != 'running': - action_msg = 'started' - if update: - status = __salt__['virt.update'](name, - cpu=cpu, - mem=mem, - disk_profile=disk_profile, - disks=disks, - nic_profile=nic_profile, - interfaces=interfaces, - graphics=graphics, - live=False, - connection=connection, - username=username, - password=password, - boot=boot) - if status['definition']: - action_msg = 'updated and started' - __salt__['virt.start'](name) - ret['changes'][name] = 'Domain {0}'.format(action_msg) - ret['comment'] = 'Domain {0} {1}'.format(name, action_msg) - else: - if update: - status = __salt__['virt.update'](name, - cpu=cpu, - mem=mem, - disk_profile=disk_profile, - disks=disks, - nic_profile=nic_profile, - interfaces=interfaces, - graphics=graphics, - connection=connection, - username=username, - password=password, - boot=boot) - ret['changes'][name] = status - if status.get('errors', None): - ret['comment'] = 'Domain {0} updated, but some live update(s) failed'.format(name) - elif not status['definition']: - ret['comment'] = 'Domain {0} exists and is running'.format(name) - else: - ret['comment'] = 'Domain {0} updated, restart to fully apply the changes'.format(name) - else: - ret['comment'] = 'Domain {0} exists and is running'.format(name) - except CommandExecutionError: - if image: - salt.utils.versions.warn_until( - 'Sodium', - '\'image\' parameter has been deprecated. Rather use the \'disks\' parameter ' - 'to override or define the image. \'image\' will be removed in {version}.' - ) - __salt__['virt.init'](name, - cpu=cpu, - mem=mem, - os_type=os_type, - arch=arch, - image=image, - hypervisor=vm_type, - disk=disk_profile, - disks=disks, - nic=nic_profile, - interfaces=interfaces, - graphics=graphics, - seed=seed, - install=install, - pub_key=pub_key, - priv_key=priv_key, - connection=connection, - username=username, - password=password, - boot=boot) - ret['changes'][name] = 'Domain defined and started' - ret['comment'] = 'Domain {0} defined and started'.format(name) - except libvirt.libvirtError as err: - # Something bad happened when starting / updating the VM, report it - ret['comment'] = six.text_type(err) - ret['result'] = False + if not __opts__['test']: + __salt__['virt.start'](name, connection=connection, username=username, password=password) + comment = 'Domain {} started'.format(name) + if not ret['comment'].endswith('unchanged'): + comment = '{} and started'.format(ret['comment']) + ret['comment'] = comment + ret['changes'][name]['started'] = True + elif not changed: + ret['comment'] = 'Domain {0} exists and is running'.format(name) + + except libvirt.libvirtError as err: + # Something bad happened when starting / updating the VM, report it + ret['comment'] = six.text_type(err) + ret['result'] = False return ret @@ -670,6 +834,106 @@ def reverted(name, snapshot=None, cleanup=False): # pylint: disable=redefined-o return ret +def network_defined(name, + bridge, + forward, + vport=None, + tag=None, + ipv4_config=None, + ipv6_config=None, + autostart=True, + connection=None, + username=None, + password=None): + ''' + Defines a new network with specified arguments. + + :param bridge: Bridge name + :param forward: Forward mode(bridge, router, nat) + :param vport: Virtualport type (Default: ``'None'``) + :param tag: Vlan tag (Default: ``'None'``) + :param ipv4_config: + IPv4 network configuration. See the :py:func`virt.network_define + ` function corresponding parameter documentation + for more details on this dictionary. + (Default: None). + :param ipv6_config: + IPv6 network configuration. See the :py:func`virt.network_define + ` function corresponding parameter documentation + for more details on this dictionary. + (Default: None). + :param autostart: Network autostart (default ``'True'``) + :param connection: libvirt connection URI, overriding defaults + :param username: username to connect with, overriding defaults + :param password: password to connect with, overriding defaults + + .. versionadded:: sodium + + .. code-block:: yaml + + network_name: + virt.network_defined + + .. code-block:: yaml + + network_name: + virt.network_defined: + - bridge: main + - forward: bridge + - vport: openvswitch + - tag: 180 + - autostart: True + + .. code-block:: yaml + + network_name: + virt.network_defined: + - bridge: natted + - forward: nat + - ipv4_config: + cidr: 192.168.42.0/24 + dhcp_ranges: + - start: 192.168.42.10 + end: 192.168.42.25 + - start: 192.168.42.100 + end: 192.168.42.150 + - autostart: True + + ''' + ret = {'name': name, + 'changes': {}, + 'result': True if not __opts__['test'] else None, + 'comment': '' + } + + try: + info = __salt__['virt.network_info'](name, connection=connection, username=username, password=password) + if info and info[name]: + ret['comment'] = 'Network {0} exists'.format(name) + ret['result'] = True + else: + if not __opts__['test']: + __salt__['virt.network_define'](name, + bridge, + forward, + vport=vport, + tag=tag, + ipv4_config=ipv4_config, + ipv6_config=ipv6_config, + autostart=autostart, + start=False, + connection=connection, + username=username, + password=password) + ret['changes'][name] = 'Network defined' + ret['comment'] = 'Network {0} defined'.format(name) + except libvirt.libvirtError as err: + ret['result'] = False + ret['comment'] = err.get_error_message() + + return ret + + def network_running(name, bridge, forward, @@ -715,13 +979,13 @@ def network_running(name, .. code-block:: yaml - domain_name: - virt.network_define + network_name: + virt.network_running .. code-block:: yaml network_name: - virt.network_define: + virt.network_running: - bridge: main - forward: bridge - vport: openvswitch @@ -731,7 +995,7 @@ def network_running(name, .. code-block:: yaml network_name: - virt.network_define: + virt.network_running: - bridge: natted - forward: nat - ipv4_config: @@ -744,44 +1008,46 @@ def network_running(name, - autostart: True ''' - ret = {'name': name, - 'changes': {}, - 'result': True, - 'comment': '' - } - - try: - info = __salt__['virt.network_info'](name, connection=connection, username=username, password=password) - if info: - if info[name]['active']: - ret['comment'] = 'Network {0} exists and is running'.format(name) + ret = network_defined(name, + bridge, + forward, + vport=vport, + tag=tag, + ipv4_config=ipv4_config, + ipv6_config=ipv6_config, + autostart=autostart, + connection=connection, + username=username, + password=password) + + defined = name in ret['changes'] and ret['changes'][name].startswith('Network defined') + + result = True if not __opts__['test'] else None + if ret['result'] is None or ret['result']: + try: + info = __salt__['virt.network_info'](name, connection=connection, username=username, password=password) + # In the corner case where test=True and the network wasn't defined + # we may not get the network in the info dict and that is normal. + if info.get(name, {}).get('active', False): + ret['comment'] = '{} and is running'.format(ret['comment']) else: - __salt__['virt.network_start'](name, connection=connection, username=username, password=password) - ret['changes'][name] = 'Network started' - ret['comment'] = 'Network {0} started'.format(name) - else: - __salt__['virt.network_define'](name, - bridge, - forward, - vport=vport, - tag=tag, - ipv4_config=ipv4_config, - ipv6_config=ipv6_config, - autostart=autostart, - start=True, - connection=connection, - username=username, - password=password) - ret['changes'][name] = 'Network defined and started' - ret['comment'] = 'Network {0} defined and started'.format(name) - except libvirt.libvirtError as err: - ret['result'] = False - ret['comment'] = err.get_error_message() + if not __opts__['test']: + __salt__['virt.network_start'](name, connection=connection, username=username, password=password) + change = 'Network started' + if name in ret['changes']: + change = '{} and started'.format(ret['changes'][name]) + ret['changes'][name] = change + ret['comment'] = '{} and started'.format(ret['comment']) + ret['result'] = result + + except libvirt.libvirtError as err: + ret['result'] = False + ret['comment'] = err.get_error_message() return ret -def pool_running(name, +def pool_defined(name, ptype=None, target=None, permissions=None, @@ -792,9 +1058,9 @@ def pool_running(name, username=None, password=None): ''' - Defines and starts a new pool with specified arguments. + Defines a new pool with specified arguments. - .. versionadded:: 2019.2.0 + .. versionadded:: sodium :param ptype: libvirt pool type :param target: full path to the target device or folder. (Default: ``None``) @@ -816,12 +1082,7 @@ def pool_running(name, .. code-block:: yaml pool_name: - virt.pool_define - - .. code-block:: yaml - - pool_name: - virt.pool_define: + virt.pool_defined: - ptype: netfs - target: /mnt/cifs - permissions: @@ -884,29 +1145,19 @@ def pool_running(name, username=username, password=password) - action = "started" - if info[name]['state'] == 'running': - action = "restarted" + action = '' + if info[name]['state'] != 'running': if not __opts__['test']: - __salt__['virt.pool_stop'](name, connection=connection, username=username, password=password) - - if not __opts__['test']: - __salt__['virt.pool_build'](name, connection=connection, username=username, password=password) - __salt__['virt.pool_start'](name, connection=connection, username=username, password=password) + __salt__['virt.pool_build'](name, connection=connection, username=username, password=password) + action = ', built' - autostart_str = ', autostart flag changed' if needs_autostart else '' - ret['changes'][name] = 'Pool updated, built{0} and {1}'.format(autostart_str, action) - ret['comment'] = 'Pool {0} updated, built{1} and {2}'.format(name, autostart_str, action) + action = '{}, autostart flag changed'.format(action) if needs_autostart else action + ret['changes'][name] = 'Pool updated{0}'.format(action) + ret['comment'] = 'Pool {0} updated{1}'.format(name, action) else: - if info[name]['state'] == 'running': - ret['comment'] = 'Pool {0} unchanged and is running'.format(name) - ret['result'] = True - else: - ret['changes'][name] = 'Pool started' - ret['comment'] = 'Pool {0} started'.format(name) - if not __opts__['test']: - __salt__['virt.pool_start'](name, connection=connection, username=username, password=password) + ret['comment'] = 'Pool {0} unchanged'.format(name) + ret['result'] = True else: needs_autostart = autostart if not __opts__['test']: @@ -932,17 +1183,12 @@ def pool_running(name, connection=connection, username=username, password=password) - - __salt__['virt.pool_start'](name, - connection=connection, - username=username, - password=password) if needs_autostart: - ret['changes'][name] = 'Pool defined, started and marked for autostart' - ret['comment'] = 'Pool {0} defined, started and marked for autostart'.format(name) + ret['changes'][name] = 'Pool defined, marked for autostart' + ret['comment'] = 'Pool {0} defined, marked for autostart'.format(name) else: - ret['changes'][name] = 'Pool defined and started' - ret['comment'] = 'Pool {0} defined and started'.format(name) + ret['changes'][name] = 'Pool defined' + ret['comment'] = 'Pool {0} defined'.format(name) if needs_autostart: if not __opts__['test']: @@ -958,6 +1204,117 @@ def pool_running(name, return ret +def pool_running(name, + ptype=None, + target=None, + permissions=None, + source=None, + transient=False, + autostart=True, + connection=None, + username=None, + password=None): + ''' + Defines and starts a new pool with specified arguments. + + .. versionadded:: 2019.2.0 + + :param ptype: libvirt pool type + :param target: full path to the target device or folder. (Default: ``None``) + :param permissions: + target permissions. See :ref:`pool-define-permissions` for more details on this structure. + :param source: + dictionary containing keys matching the ``source_*`` parameters in function + :func:`salt.modules.virt.pool_define`. + :param transient: + when set to ``True``, the pool will be automatically undefined after being stopped. (Default: ``False``) + :param autostart: + Whether to start the pool when booting the host. (Default: ``True``) + :param start: + When ``True``, define and start the pool, otherwise the pool will be left stopped. + :param connection: libvirt connection URI, overriding defaults + :param username: username to connect with, overriding defaults + :param password: password to connect with, overriding defaults + + .. code-block:: yaml + + pool_name: + virt.pool_running + + .. code-block:: yaml + + pool_name: + virt.pool_running: + - ptype: netfs + - target: /mnt/cifs + - permissions: + - mode: 0770 + - owner: 1000 + - group: 100 + - source: + dir: samba_share + hosts: + - one.example.com + - two.example.com + format: cifs + - autostart: True + + ''' + ret = pool_defined(name, + ptype=ptype, + target=target, + permissions=permissions, + source=source, + transient=transient, + autostart=autostart, + connection=connection, + username=username, + password=password) + defined = name in ret['changes'] and ret['changes'][name].startswith('Pool defined') + updated = name in ret['changes'] and ret['changes'][name].startswith('Pool updated') + + result = True if not __opts__['test'] else None + if ret['result'] is None or ret['result']: + try: + info = __salt__['virt.pool_info'](name, connection=connection, username=username, password=password) + action = 'started' + # In the corner case where test=True and the pool wasn't defined + # we may get not get our pool in the info dict and that is normal. + is_running = info.get(name, {}).get('state', 'stopped') == 'running' + if is_running: + if updated: + action = 'built, restarted' + if not __opts__['test']: + __salt__['virt.pool_stop'](name, connection=connection, username=username, password=password) + if not __opts__['test']: + __salt__['virt.pool_build'](name, connection=connection, username=username, password=password) + else: + action = 'already running' + result = True + + if not is_running or updated or defined: + if not __opts__['test']: + __salt__['virt.pool_start'](name, connection=connection, username=username, password=password) + + comment = 'Pool {0}'.format(name) + change = 'Pool' + if name in ret['changes']: + comment = '{0},'.format(ret['comment']) + change = '{0},'.format(ret['changes'][name]) + + if action != 'already running': + ret['changes'][name] = '{0} {1}'.format(change, action) + + ret['comment'] = '{0} {1}'.format(comment, action) + ret['result'] = result + + except libvirt.libvirtError as err: + ret['comment'] = err.get_error_message() + ret['result'] = False + + return ret + + def pool_deleted(name, purge=False, connection=None, diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py index d762dcc479..8690154662 100644 --- a/tests/unit/modules/test_virt.py +++ b/tests/unit/modules/test_virt.py @@ -1272,6 +1272,32 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): define_mock = MagicMock(return_value=True) self.mock_conn.defineXML = define_mock + # No parameter passed case + self.assertEqual({ + 'definition': False, + 'disk': {'attached': [], 'detached': []}, + 'interface': {'attached': [], 'detached': []} + }, virt.update('my vm')) + + # Same parameters passed than in default virt.defined state case + self.assertEqual({ + 'definition': False, + 'disk': {'attached': [], 'detached': []}, + 'interface': {'attached': [], 'detached': []} + }, virt.update('my vm', + cpu=None, + mem=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + live=True, + connection=None, + username=None, + password=None, + boot=None)) + # Update vcpus case setvcpus_mock = MagicMock(return_value=0) domain_mock.setVcpusFlags = setvcpus_mock diff --git a/tests/unit/states/test_virt.py b/tests/unit/states/test_virt.py index c50c04b8ab..6727704494 100644 --- a/tests/unit/states/test_virt.py +++ b/tests/unit/states/test_virt.py @@ -217,6 +217,243 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): organization='SaltStack', expiration_days=700), ret) + def test_defined(self): + ''' + defined state test cases. + ''' + ret = {'name': 'myvm', + 'changes': {}, + 'result': True, + 'comment': 'myvm is running'} + with patch.dict(virt.__opts__, {'test': False}): + # no change test + init_mock = MagicMock(return_value=True) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm']), + 'virt.update': MagicMock(return_value={'definition': False}), + }): + ret.update({'changes': {'myvm': {'definition': False}}, + 'comment': 'Domain myvm unchanged'}) + self.assertDictEqual(virt.defined('myvm'), ret) + + # Test defining a guest with connection details + init_mock.reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=[]), + 'virt.init': init_mock, + 'virt.update': MagicMock(side_effect=CommandExecutionError('not found')), + }): + ret.update({'changes': {'myvm': {'definition': True}}, + 'comment': 'Domain myvm defined'}) + disks = [{ + 'name': 'system', + 'size': 8192, + 'overlay_image': True, + 'pool': 'default', + 'image': '/path/to/image.qcow2' + }, + { + 'name': 'data', + 'size': 16834 + }] + ifaces = [{ + 'name': 'eth0', + 'mac': '01:23:45:67:89:AB' + }, + { + 'name': 'eth1', + 'type': 'network', + 'source': 'admin' + }] + graphics = {'type': 'spice', 'listen': {'type': 'address', 'address': '192.168.0.1'}} + self.assertDictEqual(virt.defined('myvm', + cpu=2, + mem=2048, + os_type='linux', + arch='i686', + vm_type='qemu', + disk_profile='prod', + disks=disks, + nic_profile='prod', + interfaces=ifaces, + graphics=graphics, + seed=False, + install=False, + pub_key='/path/to/key.pub', + priv_key='/path/to/key', + connection='someconnection', + username='libvirtuser', + password='supersecret'), ret) + init_mock.assert_called_with('myvm', + cpu=2, + mem=2048, + os_type='linux', + arch='i686', + disk='prod', + disks=disks, + nic='prod', + interfaces=ifaces, + graphics=graphics, + hypervisor='qemu', + seed=False, + boot=None, + install=False, + start=False, + pub_key='/path/to/key.pub', + priv_key='/path/to/key', + connection='someconnection', + username='libvirtuser', + password='supersecret') + + # Working update case when running + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm']), + 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True}) + }): + ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}}, + 'result': True, + 'comment': 'Domain myvm updated'}) + self.assertDictEqual(virt.defined('myvm', cpu=2), ret) + + # Working update case when running with boot params + boot = { + 'kernel': '/root/f8-i386-vmlinuz', + 'initrd': '/root/f8-i386-initrd', + 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' + } + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm']), + 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True}) + }): + ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}}, + 'result': True, + 'comment': 'Domain myvm updated'}) + self.assertDictEqual(virt.defined('myvm', boot=boot), ret) + + # Working update case when stopped + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm']), + 'virt.update': MagicMock(return_value={'definition': True}) + }): + ret.update({'changes': {'myvm': {'definition': True}}, + 'result': True, + 'comment': 'Domain myvm updated'}) + self.assertDictEqual(virt.defined('myvm', cpu=2), ret) + + # Failed live update case + update_mock = MagicMock(return_value={'definition': True, 'cpu': False, 'errors': ['some error']}) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm']), + 'virt.update': update_mock, + }): + ret.update({'changes': {'myvm': {'definition': True, 'cpu': False, 'errors': ['some error']}}, + 'result': True, + 'comment': 'Domain myvm updated with live update(s) failures'}) + self.assertDictEqual(virt.defined('myvm', cpu=2), ret) + update_mock.assert_called_with('myvm', cpu=2, mem=None, + disk_profile=None, disks=None, nic_profile=None, interfaces=None, + graphics=None, live=True, + connection=None, username=None, password=None, + boot=None, test=False) + + # Failed definition update case + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm']), + 'virt.update': MagicMock(side_effect=[self.mock_libvirt.libvirtError('error message')]) + }): + ret.update({'changes': {}, + 'result': False, + 'comment': 'error message'}) + self.assertDictEqual(virt.defined('myvm', cpu=2), ret) + + # Test dry-run mode + with patch.dict(virt.__opts__, {'test': True}): + # Guest defined case + init_mock = MagicMock(return_value=True) + update_mock = MagicMock(side_effect=CommandExecutionError('not found')) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=[]), + 'virt.init': init_mock, + 'virt.update': update_mock, + }): + ret.update({'changes': {'myvm': {'definition': True}}, + 'result': None, + 'comment': 'Domain myvm defined'}) + disks = [{ + 'name': 'system', + 'size': 8192, + 'overlay_image': True, + 'pool': 'default', + 'image': '/path/to/image.qcow2' + }, + { + 'name': 'data', + 'size': 16834 + }] + ifaces = [{ + 'name': 'eth0', + 'mac': '01:23:45:67:89:AB' + }, + { + 'name': 'eth1', + 'type': 'network', + 'source': 'admin' + }] + graphics = {'type': 'spice', 'listen': {'type': 'address', 'address': '192.168.0.1'}} + self.assertDictEqual(virt.defined('myvm', + cpu=2, + mem=2048, + os_type='linux', + arch='i686', + vm_type='qemu', + disk_profile='prod', + disks=disks, + nic_profile='prod', + interfaces=ifaces, + graphics=graphics, + seed=False, + install=False, + pub_key='/path/to/key.pub', + priv_key='/path/to/key', + connection='someconnection', + username='libvirtuser', + password='supersecret'), ret) + init_mock.assert_not_called() + update_mock.assert_not_called() + + # Guest update case + update_mock = MagicMock(return_value={'definition': True}) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm']), + 'virt.update': update_mock + }): + ret.update({'changes': {'myvm': {'definition': True}}, + 'result': None, + 'comment': 'Domain myvm updated'}) + self.assertDictEqual(virt.defined('myvm', cpu=2), ret) + update_mock.assert_called_with('myvm', cpu=2, mem=None, + disk_profile=None, disks=None, nic_profile=None, interfaces=None, + graphics=None, live=True, + connection=None, username=None, password=None, + boot=None, test=True) + + # No changes case + update_mock = MagicMock(return_value={'definition': False}) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm']), + 'virt.update': update_mock, + }): + ret.update({'changes': {'myvm': {'definition': False}}, + 'result': True, + 'comment': 'Domain myvm unchanged'}) + self.assertDictEqual(virt.defined('myvm'), ret) + update_mock.assert_called_with('myvm', cpu=None, mem=None, + disk_profile=None, disks=None, nic_profile=None, interfaces=None, + graphics=None, live=True, + connection=None, username=None, password=None, + boot=None, test=True) + def test_running(self): ''' running state test cases. @@ -225,163 +462,369 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): 'changes': {}, 'result': True, 'comment': 'myvm is running'} - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.start': MagicMock(return_value=0), - }): - ret.update({'changes': {'myvm': 'Domain started'}, - 'comment': 'Domain myvm started'}) - self.assertDictEqual(virt.running('myvm'), ret) - - init_mock = MagicMock(return_value=True) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(side_effect=CommandExecutionError('not found')), - 'virt.init': init_mock, - 'virt.start': MagicMock(return_value=0) - }): - ret.update({'changes': {'myvm': 'Domain defined and started'}, - 'comment': 'Domain myvm defined and started'}) - self.assertDictEqual(virt.running('myvm', - cpu=2, - mem=2048, - image='/path/to/img.qcow2'), ret) - init_mock.assert_called_with('myvm', cpu=2, mem=2048, image='/path/to/img.qcow2', - os_type=None, arch=None, boot=None, - disk=None, disks=None, nic=None, interfaces=None, - graphics=None, hypervisor=None, - seed=True, install=True, pub_key=None, priv_key=None, - connection=None, username=None, password=None) + with patch.dict(virt.__opts__, {'test': False}): + # Test starting an existing guest without changing it + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), + 'virt.start': MagicMock(return_value=0), + 'virt.update': MagicMock(return_value={'definition': False}), + 'virt.list_domains': MagicMock(return_value=['myvm']), + }): + ret.update({'changes': {'myvm': {'started': True}}, + 'comment': 'Domain myvm started'}) + self.assertDictEqual(virt.running('myvm'), ret) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(side_effect=CommandExecutionError('not found')), - 'virt.init': init_mock, - 'virt.start': MagicMock(return_value=0) - }): - ret.update({'changes': {'myvm': 'Domain defined and started'}, - 'comment': 'Domain myvm defined and started'}) - disks = [{ - 'name': 'system', - 'size': 8192, - 'overlay_image': True, - 'pool': 'default', - 'image': '/path/to/image.qcow2' - }, - { - 'name': 'data', - 'size': 16834 - }] - ifaces = [{ - 'name': 'eth0', - 'mac': '01:23:45:67:89:AB' - }, - { - 'name': 'eth1', - 'type': 'network', - 'source': 'admin' - }] - graphics = {'type': 'spice', 'listen': {'type': 'address', 'address': '192.168.0.1'}} - self.assertDictEqual(virt.running('myvm', - cpu=2, - mem=2048, - os_type='linux', - arch='i686', - vm_type='qemu', - disk_profile='prod', - disks=disks, - nic_profile='prod', - interfaces=ifaces, - graphics=graphics, - seed=False, - install=False, - pub_key='/path/to/key.pub', - priv_key='/path/to/key', + # Test defining and starting a guest the old way + init_mock = MagicMock(return_value=True) + start_mock = MagicMock(return_value=0) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), + 'virt.init': init_mock, + 'virt.start': start_mock, + 'virt.list_domains': MagicMock(return_value=[]), + }): + ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, + 'comment': 'Domain myvm defined and started'}) + self.assertDictEqual(virt.running('myvm', + cpu=2, + mem=2048, + image='/path/to/img.qcow2'), ret) + init_mock.assert_called_with('myvm', cpu=2, mem=2048, + os_type=None, arch=None, boot=None, + disk=None, disks=[{'name': 'system', 'image': '/path/to/img.qcow2'}], nic=None, interfaces=None, + graphics=None, hypervisor=None, start=False, + seed=True, install=True, pub_key=None, priv_key=None, + connection=None, username=None, password=None,) + start_mock.assert_called_with('myvm', connection=None, username=None, password=None) + + # Test image parameter with disks with defined image + init_mock = MagicMock(return_value=True) + start_mock = MagicMock(return_value=0) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), + 'virt.init': init_mock, + 'virt.start': start_mock, + 'virt.list_domains': MagicMock(return_value=[]), + }): + ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, + 'comment': 'Domain myvm defined and started'}) + disks = [{ + 'name': 'system', + 'size': 8192, + 'overlay_image': True, + 'pool': 'default', + 'image': '/path/to/image.qcow2' + }, + { + 'name': 'data', + 'size': 16834 + }] + self.assertDictEqual(virt.running('myvm', + cpu=2, + mem=2048, + disks=disks, + image='/path/to/img.qcow2'), ret) + init_mock.assert_called_with('myvm', cpu=2, mem=2048, + os_type=None, arch=None, boot=None, + disk=None, disks=disks, nic=None, interfaces=None, + graphics=None, hypervisor=None, start=False, + seed=True, install=True, pub_key=None, priv_key=None, + connection=None, username=None, password=None,) + start_mock.assert_called_with('myvm', connection=None, username=None, password=None) + + # Test image parameter with disks without defined image + init_mock = MagicMock(return_value=True) + start_mock = MagicMock(return_value=0) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), + 'virt.init': init_mock, + 'virt.start': start_mock, + 'virt.list_domains': MagicMock(return_value=[]), + }): + ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, + 'comment': 'Domain myvm defined and started'}) + disks = [{ + 'name': 'system', + 'size': 8192, + 'overlay_image': True, + 'pool': 'default', + }, + { + 'name': 'data', + 'size': 16834 + }] + self.assertDictEqual(virt.running('myvm', + cpu=2, + mem=2048, + disks=disks, + image='/path/to/img.qcow2'), ret) + init_mock.assert_called_with('myvm', cpu=2, mem=2048, + os_type=None, arch=None, boot=None, + disk=None, + disks=[{ + 'name': 'system', + 'size': 8192, + 'overlay_image': True, + 'pool': 'default', + 'image': '/path/to/img.qcow2', + }, + { + 'name': 'data', + 'size': 16834 + }], + nic=None, interfaces=None, + graphics=None, hypervisor=None, start=False, + seed=True, install=True, pub_key=None, priv_key=None, + connection=None, username=None, password=None,) + start_mock.assert_called_with('myvm', connection=None, username=None, password=None) + + # Test defining and starting a guest the new way with connection details + init_mock.reset_mock() + start_mock.reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), + 'virt.init': init_mock, + 'virt.start': start_mock, + 'virt.list_domains': MagicMock(return_value=[]), + }): + ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, + 'comment': 'Domain myvm defined and started'}) + disks = [{ + 'name': 'system', + 'size': 8192, + 'overlay_image': True, + 'pool': 'default', + 'image': '/path/to/image.qcow2' + }, + { + 'name': 'data', + 'size': 16834 + }] + ifaces = [{ + 'name': 'eth0', + 'mac': '01:23:45:67:89:AB' + }, + { + 'name': 'eth1', + 'type': 'network', + 'source': 'admin' + }] + graphics = {'type': 'spice', 'listen': {'type': 'address', 'address': '192.168.0.1'}} + self.assertDictEqual(virt.running('myvm', + cpu=2, + mem=2048, + os_type='linux', + arch='i686', + vm_type='qemu', + disk_profile='prod', + disks=disks, + nic_profile='prod', + interfaces=ifaces, + graphics=graphics, + seed=False, + install=False, + pub_key='/path/to/key.pub', + priv_key='/path/to/key', + connection='someconnection', + username='libvirtuser', + password='supersecret'), ret) + init_mock.assert_called_with('myvm', + cpu=2, + mem=2048, + os_type='linux', + arch='i686', + disk='prod', + disks=disks, + nic='prod', + interfaces=ifaces, + graphics=graphics, + hypervisor='qemu', + seed=False, + boot=None, + install=False, + start=False, + pub_key='/path/to/key.pub', + priv_key='/path/to/key', + connection='someconnection', + username='libvirtuser', + password='supersecret') + start_mock.assert_called_with('myvm', connection='someconnection', username='libvirtuser', - password='supersecret'), ret) - init_mock.assert_called_with('myvm', - cpu=2, - mem=2048, - os_type='linux', - arch='i686', - image=None, - disk='prod', - disks=disks, - nic='prod', - interfaces=ifaces, - graphics=graphics, - hypervisor='qemu', - seed=False, - boot=None, - install=False, - pub_key='/path/to/key.pub', - priv_key='/path/to/key', - connection='someconnection', - username='libvirtuser', - password='supersecret') + password='supersecret') - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.start': MagicMock(side_effect=[self.mock_libvirt.libvirtError('libvirt error msg')]) - }): - ret.update({'changes': {}, 'result': False, 'comment': 'libvirt error msg'}) - self.assertDictEqual(virt.running('myvm'), ret) + # Test with existing guest, but start raising an error + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), + 'virt.update': MagicMock(return_value={'definition': False}), + 'virt.start': MagicMock(side_effect=[self.mock_libvirt.libvirtError('libvirt error msg')]), + 'virt.list_domains': MagicMock(return_value=['myvm']), + }): + ret.update({'changes': {'myvm': {}}, 'result': False, 'comment': 'libvirt error msg'}) + self.assertDictEqual(virt.running('myvm'), ret) - # Working update case when running - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True}) - }): - ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}}, - 'result': True, - 'comment': 'Domain myvm updated, restart to fully apply the changes'}) - self.assertDictEqual(virt.running('myvm', update=True, cpu=2), ret) - - # Working update case when running with boot params - boot = { - 'kernel': '/root/f8-i386-vmlinuz', - 'initrd': '/root/f8-i386-initrd', - 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' - } + # Working update case when running + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), + 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True}), + 'virt.list_domains': MagicMock(return_value=['myvm']), + }): + ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}}, + 'result': True, + 'comment': 'Domain myvm updated'}) + self.assertDictEqual(virt.running('myvm', cpu=2, update=True), ret) + + # Working update case when running with boot params + boot = { + 'kernel': '/root/f8-i386-vmlinuz', + 'initrd': '/root/f8-i386-initrd', + 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' + } - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True}) - }): - ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}}, - 'result': True, - 'comment': 'Domain myvm updated, restart to fully apply the changes'}) - self.assertDictEqual(virt.running('myvm', update=True, boot=boot), ret) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), + 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True}), + 'virt.list_domains': MagicMock(return_value=['myvm']), + }): + ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}}, + 'result': True, + 'comment': 'Domain myvm updated'}) + self.assertDictEqual(virt.running('myvm', boot=boot, update=True), ret) - # Working update case when stopped - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.start': MagicMock(return_value=0), - 'virt.update': MagicMock(return_value={'definition': True}) - }): - ret.update({'changes': {'myvm': 'Domain updated and started'}, - 'result': True, - 'comment': 'Domain myvm updated and started'}) - self.assertDictEqual(virt.running('myvm', update=True, cpu=2), ret) + # Working update case when stopped + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), + 'virt.start': MagicMock(return_value=0), + 'virt.update': MagicMock(return_value={'definition': True}), + 'virt.list_domains': MagicMock(return_value=['myvm']), + }): + ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, + 'result': True, + 'comment': 'Domain myvm updated and started'}) + self.assertDictEqual(virt.running('myvm', cpu=2, update=True), ret) - # Failed live update case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.update': MagicMock(return_value={'definition': True, 'cpu': False, 'errors': ['some error']}) - }): - ret.update({'changes': {'myvm': {'definition': True, 'cpu': False, 'errors': ['some error']}}, - 'result': True, - 'comment': 'Domain myvm updated, but some live update(s) failed'}) - self.assertDictEqual(virt.running('myvm', update=True, cpu=2), ret) + # Failed live update case + update_mock = MagicMock(return_value={'definition': True, 'cpu': False, 'errors': ['some error']}) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), + 'virt.update': update_mock, + 'virt.list_domains': MagicMock(return_value=['myvm']), + }): + ret.update({'changes': {'myvm': {'definition': True, 'cpu': False, 'errors': ['some error']}}, + 'result': True, + 'comment': 'Domain myvm updated with live update(s) failures'}) + self.assertDictEqual(virt.running('myvm', cpu=2, update=True), ret) + update_mock.assert_called_with('myvm', cpu=2, mem=None, + disk_profile=None, disks=None, nic_profile=None, interfaces=None, + graphics=None, live=True, + connection=None, username=None, password=None, + boot=None, test=False) + + # Failed definition update case + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), + 'virt.update': MagicMock(side_effect=[self.mock_libvirt.libvirtError('error message')]), + 'virt.list_domains': MagicMock(return_value=['myvm']), + }): + ret.update({'changes': {}, + 'result': False, + 'comment': 'error message'}) + self.assertDictEqual(virt.running('myvm', cpu=2, update=True), ret) - # Failed definition update case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.update': MagicMock(side_effect=[self.mock_libvirt.libvirtError('error message')]) - }): - ret.update({'changes': {}, - 'result': False, - 'comment': 'error message'}) - self.assertDictEqual(virt.running('myvm', update=True, cpu=2), ret) + # Test dry-run mode + with patch.dict(virt.__opts__, {'test': True}): + # Guest defined case + init_mock = MagicMock(return_value=True) + start_mock = MagicMock(return_value=0) + list_mock = MagicMock(return_value=[]) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), + 'virt.init': init_mock, + 'virt.start': start_mock, + 'virt.list_domains': list_mock, + }): + ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, + 'result': None, + 'comment': 'Domain myvm defined and started'}) + disks = [{ + 'name': 'system', + 'size': 8192, + 'overlay_image': True, + 'pool': 'default', + 'image': '/path/to/image.qcow2' + }, + { + 'name': 'data', + 'size': 16834 + }] + ifaces = [{ + 'name': 'eth0', + 'mac': '01:23:45:67:89:AB' + }, + { + 'name': 'eth1', + 'type': 'network', + 'source': 'admin' + }] + graphics = {'type': 'spice', 'listen': {'type': 'address', 'address': '192.168.0.1'}} + self.assertDictEqual(virt.running('myvm', + cpu=2, + mem=2048, + os_type='linux', + arch='i686', + vm_type='qemu', + disk_profile='prod', + disks=disks, + nic_profile='prod', + interfaces=ifaces, + graphics=graphics, + seed=False, + install=False, + pub_key='/path/to/key.pub', + priv_key='/path/to/key', + connection='someconnection', + username='libvirtuser', + password='supersecret'), ret) + init_mock.assert_not_called() + start_mock.assert_not_called() + + # Guest update case + update_mock = MagicMock(return_value={'definition': True}) + start_mock = MagicMock(return_value=0) + list_mock = MagicMock(return_value=['myvm']) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), + 'virt.start': start_mock, + 'virt.update': update_mock, + 'virt.list_domains': list_mock, + }): + ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, + 'result': None, + 'comment': 'Domain myvm updated and started'}) + self.assertDictEqual(virt.running('myvm', cpu=2, update=True), ret) + update_mock.assert_called_with('myvm', cpu=2, mem=None, + disk_profile=None, disks=None, nic_profile=None, interfaces=None, + graphics=None, live=True, + connection=None, username=None, password=None, + boot=None, test=True) + start_mock.assert_not_called() + + # No changes case + update_mock = MagicMock(return_value={'definition': False}) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), + 'virt.update': update_mock, + 'virt.list_domains': list_mock, + }): + ret.update({'changes': {'myvm': {'definition': False}}, + 'result': True, + 'comment': 'Domain myvm exists and is running'}) + self.assertDictEqual(virt.running('myvm', update=True), ret) + update_mock.assert_called_with('myvm', cpu=None, mem=None, + disk_profile=None, disks=None, nic_profile=None, interfaces=None, + graphics=None, live=True, + connection=None, username=None, password=None, + boot=None, test=True) def test_stopped(self): ''' @@ -599,92 +1042,506 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): ret.update({'changes': {}, 'result': False, 'comment': 'No changes had happened'}) self.assertDictEqual(virt.rebooted('myvm'), ret) + def test_network_defined(self): + ''' + network_defined state test cases. + ''' + ret = {'name': 'mynet', 'changes': {}, 'result': True, 'comment': ''} + with patch.dict(virt.__opts__, {'test': False}): + define_mock = MagicMock(return_value=True) + # Non-existing network case + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(side_effect=[{}, {'mynet': {'active': False}}]), + 'virt.network_define': define_mock, + }): + ret.update({'changes': {'mynet': 'Network defined'}, + 'comment': 'Network mynet defined'}) + self.assertDictEqual(virt.network_defined('mynet', + 'br2', + 'bridge', + vport='openvswitch', + tag=180, + ipv4_config={ + 'cidr': '192.168.2.0/24', + 'dhcp_ranges': [ + {'start': '192.168.2.10', 'end': '192.168.2.25'}, + {'start': '192.168.2.110', 'end': '192.168.2.125'}, + ] + }, + ipv6_config={ + 'cidr': '2001:db8:ca2:2::1/64', + 'dhcp_ranges': [ + {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, + ] + }, + autostart=False, + connection='myconnection', + username='user', + password='secret'), ret) + define_mock.assert_called_with('mynet', + 'br2', + 'bridge', + vport='openvswitch', + tag=180, + autostart=False, + start=False, + ipv4_config={ + 'cidr': '192.168.2.0/24', + 'dhcp_ranges': [ + {'start': '192.168.2.10', 'end': '192.168.2.25'}, + {'start': '192.168.2.110', 'end': '192.168.2.125'}, + ] + }, + ipv6_config={ + 'cidr': '2001:db8:ca2:2::1/64', + 'dhcp_ranges': [ + {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, + ] + }, + connection='myconnection', + username='user', + password='secret') + + # Case where there is nothing to be done + define_mock.reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={'mynet': {'active': True}}), + 'virt.network_define': define_mock, + }): + ret.update({'changes': {}, 'comment': 'Network mynet exists'}) + self.assertDictEqual(virt.network_defined('mynet', 'br2', 'bridge'), ret) + + # Error case + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={}), + 'virt.network_define': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) + }): + ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) + self.assertDictEqual(virt.network_defined('mynet', 'br2', 'bridge'), ret) + + # Test cases with __opt__['test'] set to True + with patch.dict(virt.__opts__, {'test': True}): + ret.update({'result': None}) + + # Non-existing network case + define_mock.reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={}), + 'virt.network_define': define_mock, + }): + ret.update({'changes': {'mynet': 'Network defined'}, + 'comment': 'Network mynet defined'}) + self.assertDictEqual(virt.network_defined('mynet', + 'br2', + 'bridge', + vport='openvswitch', + tag=180, + ipv4_config={ + 'cidr': '192.168.2.0/24', + 'dhcp_ranges': [ + {'start': '192.168.2.10', 'end': '192.168.2.25'}, + {'start': '192.168.2.110', 'end': '192.168.2.125'}, + ] + }, + ipv6_config={ + 'cidr': '2001:db8:ca2:2::1/64', + 'dhcp_ranges': [ + {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, + ] + }, + autostart=False, + connection='myconnection', + username='user', + password='secret'), ret) + define_mock.assert_not_called() + + # Case where there is nothing to be done + define_mock.reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={'mynet': {'active': True}}), + 'virt.network_define': define_mock, + }): + ret.update({'changes': {}, 'comment': 'Network mynet exists', 'result': True}) + self.assertDictEqual(virt.network_defined('mynet', 'br2', 'bridge'), ret) + + # Error case + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) + }): + ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) + self.assertDictEqual(virt.network_defined('mynet', 'br2', 'bridge'), ret) + def test_network_running(self): ''' network_running state test cases. ''' ret = {'name': 'mynet', 'changes': {}, 'result': True, 'comment': ''} - define_mock = MagicMock(return_value=True) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={}), - 'virt.network_define': define_mock - }): - ret.update({'changes': {'mynet': 'Network defined and started'}, - 'comment': 'Network mynet defined and started'}) - self.assertDictEqual(virt.network_running('mynet', - 'br2', - 'bridge', - vport='openvswitch', - tag=180, - ipv4_config={ - 'cidr': '192.168.2.0/24', - 'dhcp_ranges': [ - {'start': '192.168.2.10', 'end': '192.168.2.25'}, - {'start': '192.168.2.110', 'end': '192.168.2.125'}, - ] - }, - ipv6_config={ - 'cidr': '2001:db8:ca2:2::1/64', - 'dhcp_ranges': [ - {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, - ] - }, - autostart=False, - connection='myconnection', - username='user', - password='secret'), ret) - define_mock.assert_called_with('mynet', - 'br2', - 'bridge', - vport='openvswitch', - tag=180, - autostart=False, - start=True, - ipv4_config={ - 'cidr': '192.168.2.0/24', - 'dhcp_ranges': [ - {'start': '192.168.2.10', 'end': '192.168.2.25'}, - {'start': '192.168.2.110', 'end': '192.168.2.125'}, - ] - }, - ipv6_config={ - 'cidr': '2001:db8:ca2:2::1/64', - 'dhcp_ranges': [ - {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, - ] - }, - connection='myconnection', - username='user', - password='secret') + with patch.dict(virt.__opts__, {'test': False}): + define_mock = MagicMock(return_value=True) + start_mock = MagicMock(return_value=True) + # Non-existing network case + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(side_effect=[{}, {'mynet': {'active': False}}]), + 'virt.network_define': define_mock, + 'virt.network_start': start_mock, + }): + ret.update({'changes': {'mynet': 'Network defined and started'}, + 'comment': 'Network mynet defined and started'}) + self.assertDictEqual(virt.network_running('mynet', + 'br2', + 'bridge', + vport='openvswitch', + tag=180, + ipv4_config={ + 'cidr': '192.168.2.0/24', + 'dhcp_ranges': [ + {'start': '192.168.2.10', 'end': '192.168.2.25'}, + {'start': '192.168.2.110', 'end': '192.168.2.125'}, + ] + }, + ipv6_config={ + 'cidr': '2001:db8:ca2:2::1/64', + 'dhcp_ranges': [ + {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, + ] + }, + autostart=False, + connection='myconnection', + username='user', + password='secret'), ret) + define_mock.assert_called_with('mynet', + 'br2', + 'bridge', + vport='openvswitch', + tag=180, + autostart=False, + start=False, + ipv4_config={ + 'cidr': '192.168.2.0/24', + 'dhcp_ranges': [ + {'start': '192.168.2.10', 'end': '192.168.2.25'}, + {'start': '192.168.2.110', 'end': '192.168.2.125'}, + ] + }, + ipv6_config={ + 'cidr': '2001:db8:ca2:2::1/64', + 'dhcp_ranges': [ + {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, + ] + }, + connection='myconnection', + username='user', + password='secret') + start_mock.assert_called_with('mynet', + connection='myconnection', + username='user', + password='secret') - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={'mynet': {'active': True}}), - 'virt.network_define': define_mock, - }): - ret.update({'changes': {}, 'comment': 'Network mynet exists and is running'}) - self.assertDictEqual(virt.network_running('mynet', 'br2', 'bridge'), ret) + # Case where there is nothing to be done + define_mock.reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={'mynet': {'active': True}}), + 'virt.network_define': define_mock, + }): + ret.update({'changes': {}, 'comment': 'Network mynet exists and is running'}) + self.assertDictEqual(virt.network_running('mynet', 'br2', 'bridge'), ret) - start_mock = MagicMock(return_value=True) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={'mynet': {'active': False}}), - 'virt.network_start': start_mock, - 'virt.network_define': define_mock, - }): - ret.update({'changes': {'mynet': 'Network started'}, 'comment': 'Network mynet started'}) - self.assertDictEqual(virt.network_running('mynet', - 'br2', - 'bridge', + # Network existing and stopped case + start_mock = MagicMock(return_value=True) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={'mynet': {'active': False}}), + 'virt.network_start': start_mock, + 'virt.network_define': define_mock, + }): + ret.update({'changes': {'mynet': 'Network started'}, 'comment': 'Network mynet exists and started'}) + self.assertDictEqual(virt.network_running('mynet', + 'br2', + 'bridge', + connection='myconnection', + username='user', + password='secret'), ret) + start_mock.assert_called_with('mynet', connection='myconnection', username='user', password='secret') + + # Error case + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={}), + 'virt.network_define': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) + }): + ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) + self.assertDictEqual(virt.network_running('mynet', 'br2', 'bridge'), ret) + + # Test cases with __opt__['test'] set to True + with patch.dict(virt.__opts__, {'test': True}): + ret.update({'result': None}) + + # Non-existing network case + define_mock.reset_mock() + start_mock.reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={}), + 'virt.network_define': define_mock, + 'virt.network_start': start_mock, + }): + ret.update({'changes': {'mynet': 'Network defined and started'}, + 'comment': 'Network mynet defined and started'}) + self.assertDictEqual(virt.network_running('mynet', + 'br2', + 'bridge', + vport='openvswitch', + tag=180, + ipv4_config={ + 'cidr': '192.168.2.0/24', + 'dhcp_ranges': [ + {'start': '192.168.2.10', 'end': '192.168.2.25'}, + {'start': '192.168.2.110', 'end': '192.168.2.125'}, + ] + }, + ipv6_config={ + 'cidr': '2001:db8:ca2:2::1/64', + 'dhcp_ranges': [ + {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, + ] + }, + autostart=False, + connection='myconnection', + username='user', + password='secret'), ret) + define_mock.assert_not_called() + start_mock.assert_not_called() + + # Case where there is nothing to be done + define_mock.reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={'mynet': {'active': True}}), + 'virt.network_define': define_mock, + }): + ret.update({'changes': {}, 'comment': 'Network mynet exists and is running'}) + self.assertDictEqual(virt.network_running('mynet', 'br2', 'bridge'), ret) + + # Network existing and stopped case + start_mock = MagicMock(return_value=True) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={'mynet': {'active': False}}), + 'virt.network_start': start_mock, + 'virt.network_define': define_mock, + }): + ret.update({'changes': {'mynet': 'Network started'}, 'comment': 'Network mynet exists and started'}) + self.assertDictEqual(virt.network_running('mynet', + 'br2', + 'bridge', + connection='myconnection', + username='user', + password='secret'), ret) + start_mock.assert_not_called() + + # Error case + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) + }): + ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) + self.assertDictEqual(virt.network_running('mynet', 'br2', 'bridge'), ret) + + def test_pool_defined(self): + ''' + pool_defined state test cases. + ''' + ret = {'name': 'mypool', 'changes': {}, 'result': True, 'comment': ''} + mocks = {mock: MagicMock(return_value=True) for mock in ['define', 'autostart', 'build']} + with patch.dict(virt.__opts__, {'test': False}): + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(side_effect=[{}, {'mypool': {'state': 'stopped', 'autostart': True}}]), + 'virt.pool_define': mocks['define'], + 'virt.pool_build': mocks['build'], + 'virt.pool_set_autostart': mocks['autostart'] + }): + ret.update({'changes': {'mypool': 'Pool defined, marked for autostart'}, + 'comment': 'Pool mypool defined, marked for autostart'}) + self.assertDictEqual(virt.pool_defined('mypool', + ptype='logical', + target='/dev/base', + permissions={'mode': '0770', + 'owner': 1000, + 'group': 100, + 'label': 'seclabel'}, + source={'devices': [{'path': '/dev/sda'}]}, + transient=True, + autostart=True, + connection='myconnection', + username='user', + password='secret'), ret) + mocks['define'].assert_called_with('mypool', + ptype='logical', + target='/dev/base', + permissions={'mode': '0770', + 'owner': 1000, + 'group': 100, + 'label': 'seclabel'}, + source_devices=[{'path': '/dev/sda'}], + source_dir=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + source_initiator=None, + transient=True, + start=False, + connection='myconnection', + username='user', + password='secret') + mocks['autostart'].assert_called_with('mypool', + state='on', connection='myconnection', username='user', - password='secret'), ret) - start_mock.assert_called_with('mynet', connection='myconnection', username='user', password='secret') + password='secret') + mocks['build'].assert_called_with('mypool', + connection='myconnection', + username='user', + password='secret') - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={}), - 'virt.network_define': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) - self.assertDictEqual(virt.network_running('mynet', 'br2', 'bridge'), ret) + mocks['update'] = MagicMock(return_value=False) + for mock in mocks: + mocks[mock].reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'stopped', 'autostart': True}}), + 'virt.pool_update': mocks['update'], + 'virt.pool_build': mocks['build'], + }): + ret.update({'changes': {}, 'comment': 'Pool mypool unchanged'}) + self.assertDictEqual(virt.pool_defined('mypool', + ptype='logical', + target='/dev/base', + source={'devices': [{'path': '/dev/sda'}]}), ret) + mocks['build'].assert_not_called() + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(return_value={}), + 'virt.pool_define': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) + }): + ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) + self.assertDictEqual(virt.pool_defined('mypool', + ptype='logical', + target='/dev/base', + source={'devices': [{'path': '/dev/sda'}]}), ret) + + # Test case with update and autostart change on stopped pool + for mock in mocks: + mocks[mock].reset_mock() + mocks['update'] = MagicMock(return_value=True) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'stopped', 'autostart': True}}), + 'virt.pool_update': mocks['update'], + 'virt.pool_set_autostart': mocks['autostart'], + 'virt.pool_build': mocks['build'], + }): + ret.update({'changes': {'mypool': 'Pool updated, built, autostart flag changed'}, + 'comment': 'Pool mypool updated, built, autostart flag changed', + 'result': True}) + self.assertDictEqual(virt.pool_defined('mypool', + ptype='logical', + target='/dev/base', + autostart=False, + permissions={'mode': '0770', + 'owner': 1000, + 'group': 100, + 'label': 'seclabel'}, + source={'devices': [{'path': '/dev/sda'}]}), ret) + mocks['build'].assert_called_with('mypool', connection=None, username=None, password=None) + mocks['autostart'].assert_called_with('mypool', state='off', + connection=None, username=None, password=None) + mocks['update'].assert_called_with('mypool', + ptype='logical', + target='/dev/base', + permissions={'mode': '0770', + 'owner': 1000, + 'group': 100, + 'label': 'seclabel'}, + source_devices=[{'path': '/dev/sda'}], + source_dir=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + source_initiator=None, + connection=None, + username=None, + password=None) + + # test case with update and no autostart change on running pool + for mock in mocks: + mocks[mock].reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'running', 'autostart': False}}), + 'virt.pool_update': mocks['update'], + 'virt.pool_build': mocks['build'], + }): + ret.update({'changes': {'mypool': 'Pool updated'}, + 'comment': 'Pool mypool updated', + 'result': True}) + self.assertDictEqual(virt.pool_defined('mypool', + ptype='logical', + target='/dev/base', + autostart=False, + permissions={'mode': '0770', + 'owner': 1000, + 'group': 100, + 'label': 'seclabel'}, + source={'devices': [{'path': '/dev/sda'}]}), ret) + mocks['update'].assert_called_with('mypool', + ptype='logical', + target='/dev/base', + permissions={'mode': '0770', + 'owner': 1000, + 'group': 100, + 'label': 'seclabel'}, + source_devices=[{'path': '/dev/sda'}], + source_dir=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + source_initiator=None, + connection=None, + username=None, + password=None) + + with patch.dict(virt.__opts__, {'test': True}): + # test case with test=True and no change + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'running', 'autostart': True}}), + 'virt.pool_update': MagicMock(return_value=False), + }): + ret.update({'changes': {}, 'comment': 'Pool mypool unchanged', + 'result': True}) + self.assertDictEqual(virt.pool_defined('mypool', + ptype='logical', + target='/dev/base', + source={'devices': [{'path': '/dev/sda'}]}), ret) + + # test case with test=True and pool to be defined + for mock in mocks: + mocks[mock].reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(return_value={}), + }): + ret.update({'changes': {'mypool': 'Pool defined, marked for autostart'}, + 'comment': 'Pool mypool defined, marked for autostart', + 'result': None}) + self.assertDictEqual(virt.pool_defined('mypool', + ptype='logical', + target='/dev/base', + permissions={'mode': '0770', + 'owner': 1000, + 'group': 100, + 'label': 'seclabel'}, + source={'devices': [{'path': '/dev/sda'}]}, + transient=True, + autostart=True, + connection='myconnection', + username='user', + password='secret'), ret) def test_pool_running(self): ''' @@ -694,14 +1551,14 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): mocks = {mock: MagicMock(return_value=True) for mock in ['define', 'autostart', 'build', 'start', 'stop']} with patch.dict(virt.__opts__, {'test': False}): with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={}), + 'virt.pool_info': MagicMock(side_effect=[{}, {'mypool': {'state': 'stopped', 'autostart': True}}]), 'virt.pool_define': mocks['define'], 'virt.pool_build': mocks['build'], 'virt.pool_start': mocks['start'], 'virt.pool_set_autostart': mocks['autostart'] }): - ret.update({'changes': {'mypool': 'Pool defined, started and marked for autostart'}, - 'comment': 'Pool mypool defined, started and marked for autostart'}) + ret.update({'changes': {'mypool': 'Pool defined, marked for autostart, started'}, + 'comment': 'Pool mypool defined, marked for autostart, started'}) self.assertDictEqual(virt.pool_running('mypool', ptype='logical', target='/dev/base', @@ -754,7 +1611,7 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'running', 'autostart': True}}), 'virt.pool_update': MagicMock(return_value=False), }): - ret.update({'changes': {}, 'comment': 'Pool mypool unchanged and is running'}) + ret.update({'changes': {}, 'comment': 'Pool mypool already running'}) self.assertDictEqual(virt.pool_running('mypool', ptype='logical', target='/dev/base', @@ -797,8 +1654,8 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): 'virt.pool_build': mocks['build'], 'virt.pool_start': mocks['start'] }): - ret.update({'changes': {'mypool': 'Pool updated, built, autostart flag changed and started'}, - 'comment': 'Pool mypool updated, built, autostart flag changed and started', + ret.update({'changes': {'mypool': 'Pool updated, built, autostart flag changed, started'}, + 'comment': 'Pool mypool updated, built, autostart flag changed, started', 'result': True}) self.assertDictEqual(virt.pool_running('mypool', ptype='logical', @@ -842,8 +1699,8 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): 'virt.pool_start': mocks['start'], 'virt.pool_stop': mocks['stop'] }): - ret.update({'changes': {'mypool': 'Pool updated, built and restarted'}, - 'comment': 'Pool mypool updated, built and restarted', + ret.update({'changes': {'mypool': 'Pool updated, built, restarted'}, + 'comment': 'Pool mypool updated, built, restarted', 'result': True}) self.assertDictEqual(virt.pool_running('mypool', ptype='logical', @@ -882,7 +1739,7 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'running', 'autostart': True}}), 'virt.pool_update': MagicMock(return_value=False), }): - ret.update({'changes': {}, 'comment': 'Pool mypool unchanged and is running', + ret.update({'changes': {}, 'comment': 'Pool mypool already running', 'result': True}) self.assertDictEqual(virt.pool_running('mypool', ptype='logical', @@ -905,6 +1762,29 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): target='/dev/base', source={'devices': [{'path': '/dev/sda'}]}), ret) + # test case with test=True and pool to be defined + for mock in mocks: + mocks[mock].reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(return_value={}), + }): + ret.update({'changes': {'mypool': 'Pool defined, marked for autostart, started'}, + 'comment': 'Pool mypool defined, marked for autostart, started', + 'result': None}) + self.assertDictEqual(virt.pool_running('mypool', + ptype='logical', + target='/dev/base', + permissions={'mode': '0770', + 'owner': 1000, + 'group': 100, + 'label': 'seclabel'}, + source={'devices': [{'path': '/dev/sda'}]}, + transient=True, + autostart=True, + connection='myconnection', + username='user', + password='secret'), ret) + def test_pool_deleted(self): ''' Test the pool_deleted state -- 2.16.4