From 99b0017700e3d93d068650989b0b3cfa2d04698ba979eebd2d256702547640d4 Mon Sep 17 00:00:00 2001 From: Jochen Breuer Date: Mon, 16 Mar 2020 15:06:32 +0000 Subject: [PATCH 1/2] osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:327 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=163 --- _lastrevision | 2 +- ...te-the-import-cachefor-extra-modules.patch | 52 + ...use-2019.2.3-virt-defined-states-219.patch | 2378 +++++++++++++++++ salt.changes | 14 + salt.spec | 7 + ...name-instead-of-undocumented-abbrevi.patch | 39 +- 6 files changed, 2488 insertions(+), 4 deletions(-) create mode 100644 loader-invalidate-the-import-cachefor-extra-modules.patch create mode 100644 open-suse-2019.2.3-virt-defined-states-219.patch diff --git a/_lastrevision b/_lastrevision index b23f976..8c71fd2 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -d9f16c8fe9224267baa7b315699270521dda6162 \ No newline at end of file +0f35901e836e26f224b8fe278679334f6ea6281d \ No newline at end of file diff --git a/loader-invalidate-the-import-cachefor-extra-modules.patch b/loader-invalidate-the-import-cachefor-extra-modules.patch new file mode 100644 index 0000000..33f2b2d --- /dev/null +++ b/loader-invalidate-the-import-cachefor-extra-modules.patch @@ -0,0 +1,52 @@ +From 3d92c4e096dca27b95e485b70594186151e40092 Mon Sep 17 00:00:00 2001 +From: Alberto Planas +Date: Thu, 12 Mar 2020 16:39:42 +0100 +Subject: [PATCH] loader: invalidate the import cachefor extra modules + +Because we are mangling with importlib, we can find from time to +time an invalidation issue with sys.path_importer_cache, that +requires the removal of FileFinder that remain None for the +extra_module_dirs + +(cherry picked from commit 0fb8e707a45d5caf40759e8b4943590d6fce5046) +--- + salt/loader.py | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/salt/loader.py b/salt/loader.py +index 52cb4cfcb5..26b44de511 100644 +--- a/salt/loader.py ++++ b/salt/loader.py +@@ -1506,9 +1506,11 @@ class LazyLoader(salt.utils.lazy.LazyDict): + self._clean_module_dirs.append(directory) + + def __clean_sys_path(self): ++ invalidate_path_importer_cache = False + for directory in self._clean_module_dirs: + if directory in sys.path: + sys.path.remove(directory) ++ invalidate_path_importer_cache = True + self._clean_module_dirs = [] + + # Be sure that sys.path_importer_cache do not contains any +@@ -1516,6 +1518,16 @@ class LazyLoader(salt.utils.lazy.LazyDict): + if USE_IMPORTLIB: + importlib.invalidate_caches() + ++ # Because we are mangling with importlib, we can find from ++ # time to time an invalidation issue with ++ # sys.path_importer_cache, that requires the removal of ++ # FileFinder that remain None for the extra_module_dirs ++ if invalidate_path_importer_cache: ++ for directory in self.extra_module_dirs: ++ if directory in sys.path_importer_cache \ ++ and sys.path_importer_cache[directory] is None: ++ del sys.path_importer_cache[directory] ++ + def _load_module(self, name): + mod = None + fpath, suffix = self.file_mapping[name][:2] +-- +2.16.4 + + diff --git a/open-suse-2019.2.3-virt-defined-states-219.patch b/open-suse-2019.2.3-virt-defined-states-219.patch new file mode 100644 index 0000000..2dc062a --- /dev/null +++ b/open-suse-2019.2.3-virt-defined-states-219.patch @@ -0,0 +1,2378 @@ +From 66e635ecf9643053c0fdb54718f63782ed998f2f Mon Sep 17 00:00:00 2001 +From: Cedric Bosdonnat +Date: Fri, 13 Mar 2020 16:38:46 +0100 +Subject: [PATCH] Open suse 2019.2.3 virt defined states (#219) + +* 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 | 675 +++++++++++++++----- + tests/unit/modules/test_virt.py | 26 + + tests/unit/states/test_virt.py | 1346 ++++++++++++++++++++++++++++++++------- + 4 files changed, 1666 insertions(+), 397 deletions(-) + +diff --git a/salt/modules/virt.py b/salt/modules/virt.py +index 339760ead4..b44d1a65bf 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:: neon + ++ :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 d1c9191a29..8932496b76 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 +@@ -407,93 +590,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, None) != '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 domain_state.get(name) != 'running': ++ 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 + +@@ -653,6 +817,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, +@@ -698,13 +962,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 +@@ -714,7 +978,7 @@ def network_running(name, + .. code-block:: yaml + + network_name: +- virt.network_define: ++ virt.network_running: + - bridge: natted + - forward: nat + - ipv4_config: +@@ -727,44 +991,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, +@@ -775,9 +1041,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``) +@@ -799,12 +1065,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: +@@ -867,29 +1128,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']: +@@ -915,17 +1166,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']: +@@ -941,6 +1187,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 3079657a9b..2d3417ce91 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 334c33b7d0..38a732b50c 100644 +--- a/tests/unit/states/test_virt.py ++++ b/tests/unit/states/test_virt.py +@@ -220,6 +220,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. +@@ -228,163 +465,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): + ''' +@@ -602,92 +1045,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): + ''' +@@ -697,14 +1554,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', +@@ -757,7 +1614,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', +@@ -800,8 +1657,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', +@@ -845,8 +1702,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', +@@ -885,7 +1742,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', +@@ -908,6 +1765,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 + + diff --git a/salt.changes b/salt.changes index f51e369..9c14dd8 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,17 @@ +------------------------------------------------------------------- +Mon Mar 16 13:40:30 UTC 2020 - Jochen Breuer + +- Adds test for zypper abbreviation fix +- Improved storage pool or network handling +- Better import cache handline + +- Added: + * loader-invalidate-the-import-cachefor-extra-modules.patch + * open-suse-2019.2.3-virt-defined-states-219.patch + +- Modified: + * use-full-option-name-instead-of-undocumented-abbrevi.patch + ------------------------------------------------------------------- Thu Mar 5 12:12:35 UTC 2020 - Jochen Breuer diff --git a/salt.spec b/salt.spec index 9f6b949..8058e3b 100644 --- a/salt.spec +++ b/salt.spec @@ -304,6 +304,11 @@ Patch110: batch-async-catch-exceptions-and-safety-unregister-a.patch Patch111: fix-unit-tests-for-batch-async-after-refactor.patch # PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/218 Patch112: use-full-option-name-instead-of-undocumented-abbrevi.patch +# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/221 +Patch113: loader-invalidate-the-import-cachefor-extra-modules.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/55814 +Patch114: open-suse-2019.2.3-virt-defined-states-219.patch + BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: logrotate @@ -942,6 +947,8 @@ cp %{S:5} ./.travis.yml %patch110 -p1 %patch111 -p1 %patch112 -p1 +%patch113 -p1 +%patch114 -p1 %build %if 0%{?build_py2} diff --git a/use-full-option-name-instead-of-undocumented-abbrevi.patch b/use-full-option-name-instead-of-undocumented-abbrevi.patch index 00344fd..fed150d 100644 --- a/use-full-option-name-instead-of-undocumented-abbrevi.patch +++ b/use-full-option-name-instead-of-undocumented-abbrevi.patch @@ -1,12 +1,13 @@ -From fb82c59e6de2a31f60c9f8a23f1eed4e24009dcf Mon Sep 17 00:00:00 2001 +From b06d2882f4e89011b1f5eeb442620b4543694140 Mon Sep 17 00:00:00 2001 From: Michael Calmer Date: Sun, 1 Mar 2020 16:22:54 +0100 Subject: [PATCH] use full option name instead of undocumented abbreviation --- - salt/modules/zypperpkg.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) + salt/modules/zypperpkg.py | 2 +- + tests/unit/modules/test_zypperpkg.py | 14 +++++++++++++- + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py index 8c1e05c21c..19d1fd96c7 100644 @@ -21,6 +22,38 @@ index 8c1e05c21c..19d1fd96c7 100644 cmd.append('products') if not all: cmd.append('-i') +diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py +index 7617113401..ae85152d30 100644 +--- a/tests/unit/modules/test_zypperpkg.py ++++ b/tests/unit/modules/test_zypperpkg.py +@@ -241,7 +241,18 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + 'stdout': get_test_data(filename) + } + +- with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}): ++ cmd_run_all = MagicMock(return_value=ref_out) ++ mock_call = call(['zypper', ++ '--non-interactive', ++ '--xmlout', ++ '--no-refresh', ++ '--disable-repositories', ++ 'products', u'-i'], ++ env={'ZYPP_READONLY_HACK': '1'}, ++ output_loglevel='trace', ++ python_shell=False) ++ ++ with patch.dict(zypper.__salt__, {'cmd.run_all': cmd_run_all}): + products = zypper.list_products() + self.assertEqual(len(products), 7) + self.assertIn(test_data['vendor'], [product['vendor'] for product in products]) +@@ -250,6 +261,7 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + self.assertCountEqual(test_data[kwd], [prod.get(kwd) for prod in products]) + else: + self.assertEqual(test_data[kwd], sorted([prod.get(kwd) for prod in products])) ++ cmd_run_all.assert_has_calls([mock_call]) + + def test_refresh_db(self): + ''' -- 2.16.4 From 1de38d19c0e63c16af447783b39fa1393bec5c00f4ad111018812bdf212b3936 Mon Sep 17 00:00:00 2001 From: Jochen Breuer Date: Tue, 17 Mar 2020 18:14:09 +0000 Subject: [PATCH 2/2] osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:328 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=164 --- _lastrevision | 2 +- salt.changes | 8 ++ salt.spec | 3 + ...-don-t-raise-an-exception-if-there-i.patch | 79 +++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 virt._get_domain-don-t-raise-an-exception-if-there-i.patch diff --git a/_lastrevision b/_lastrevision index 8c71fd2..fda2b82 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -0f35901e836e26f224b8fe278679334f6ea6281d \ No newline at end of file +6c4669ed512eb32e435e4bfd2cebdcba315841bb \ No newline at end of file diff --git a/salt.changes b/salt.changes index 9c14dd8..de30994 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,11 @@ +------------------------------------------------------------------- +Tue Mar 17 10:35:25 UTC 2020 - Pablo Suárez Hernández + +- virt._get_domain: don't raise an exception if there is no VM + +- Added: + * virt._get_domain-don-t-raise-an-exception-if-there-i.patch + ------------------------------------------------------------------- Mon Mar 16 13:40:30 UTC 2020 - Jochen Breuer diff --git a/salt.spec b/salt.spec index 8058e3b..62d1c80 100644 --- a/salt.spec +++ b/salt.spec @@ -308,6 +308,8 @@ Patch112: use-full-option-name-instead-of-undocumented-abbrevi.patch Patch113: loader-invalidate-the-import-cachefor-extra-modules.patch # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/55814 Patch114: open-suse-2019.2.3-virt-defined-states-219.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/56392 +Patch115: virt._get_domain-don-t-raise-an-exception-if-there-i.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -949,6 +951,7 @@ cp %{S:5} ./.travis.yml %patch112 -p1 %patch113 -p1 %patch114 -p1 +%patch115 -p1 %build %if 0%{?build_py2} diff --git a/virt._get_domain-don-t-raise-an-exception-if-there-i.patch b/virt._get_domain-don-t-raise-an-exception-if-there-i.patch new file mode 100644 index 0000000..46b552e --- /dev/null +++ b/virt._get_domain-don-t-raise-an-exception-if-there-i.patch @@ -0,0 +1,79 @@ +From 9496d01c4ff7f20bfbff4902ae5c33c147a0343e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= +Date: Tue, 17 Mar 2020 11:01:48 +0100 +Subject: [PATCH] virt._get_domain: don't raise an exception if there + is no VM + +Raising an exception if there is no VM in _get_domain makes sense if +looking for some VMs, but not when listing all VMs. +--- + salt/modules/virt.py | 2 +- + tests/unit/modules/test_virt.py | 41 +++++++++++++++++++++++++++++++++ + 2 files changed, 42 insertions(+), 1 deletion(-) + +diff --git a/salt/modules/virt.py b/salt/modules/virt.py +index b44d1a65bf97a363c2fe7a871e090cb9ca957e03..46a349ef98aa78483291dcafb38b4541bf0ac247 100644 +--- a/salt/modules/virt.py ++++ b/salt/modules/virt.py +@@ -268,7 +268,7 @@ def _get_domain(conn, *vms, **kwargs): + for id_ in conn.listDefinedDomains(): + all_vms.append(id_) + +- if not all_vms: ++ if vms and not all_vms: + raise CommandExecutionError('No virtual machines found.') + + if vms: +diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py +index 2d3417ce91506a1819dbe03045a89887c9d19721..2696ed9d1bb892ee539b385e32393f8f98d23a30 100644 +--- a/tests/unit/modules/test_virt.py ++++ b/tests/unit/modules/test_virt.py +@@ -3634,3 +3634,44 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): + } + }, + [backend for backend in backends if backend['name'] == 'netfs'][0]['options']) ++ ++ def test_get_domain(self): ++ ''' ++ Test the virt._get_domain function ++ ''' ++ # Tests with no VM ++ self.mock_conn.listDomainsID.return_value = [] ++ self.mock_conn.listDefinedDomains.return_value = [] ++ self.assertEqual([], virt._get_domain(self.mock_conn)) ++ self.assertRaisesRegex(CommandExecutionError, 'No virtual machines found.', ++ virt._get_domain, self.mock_conn, 'vm2') ++ ++ # Test with active and inactive VMs ++ self.mock_conn.listDomainsID.return_value = [1] ++ ++ def create_mock_vm(idx): ++ mock_vm = MagicMock() ++ mock_vm.name.return_value = 'vm{0}'.format(idx) ++ return mock_vm ++ ++ mock_vms = [create_mock_vm(idx) for idx in range(3)] ++ self.mock_conn.lookupByID.return_value = mock_vms[0] ++ self.mock_conn.listDefinedDomains.return_value = ['vm1', 'vm2'] ++ ++ self.mock_conn.lookupByName.side_effect = mock_vms ++ self.assertEqual(mock_vms, virt._get_domain(self.mock_conn)) ++ ++ self.mock_conn.lookupByName.side_effect = None ++ self.mock_conn.lookupByName.return_value = mock_vms[0] ++ self.assertEqual(mock_vms[0], virt._get_domain(self.mock_conn, inactive=False)) ++ ++ self.mock_conn.lookupByName.return_value = None ++ self.mock_conn.lookupByName.side_effect = [mock_vms[1], mock_vms[2]] ++ self.assertEqual([mock_vms[1], mock_vms[2]], virt._get_domain(self.mock_conn, active=False)) ++ ++ self.mock_conn.reset_mock() ++ self.mock_conn.lookupByName.return_value = None ++ self.mock_conn.lookupByName.side_effect = [mock_vms[1], mock_vms[2]] ++ self.assertEqual([mock_vms[1], mock_vms[2]], virt._get_domain(self.mock_conn, 'vm1', 'vm2')) ++ self.assertRaisesRegex(CommandExecutionError, 'The VM "vm2" is not present', ++ virt._get_domain, self.mock_conn, 'vm2', inactive=False) +-- +2.23.0 + +