Hello community, here is the log from the commit of package salt for openSUSE:Factory checked in at 2019-06-18 14:55:12 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/salt (Old) and /work/SRC/openSUSE:Factory/.salt.new.4811 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "salt" Tue Jun 18 14:55:12 2019 rev:87 rq:709997 version:2019.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/salt/salt.changes 2019-06-12 12:58:20.985340065 +0200 +++ /work/SRC/openSUSE:Factory/.salt.new.4811/salt.changes 2019-06-18 14:55:13.677446197 +0200 @@ -1,0 +2,51 @@ +Fri Jun 14 14:09:29 UTC 2019 - Pablo Suárez Hernández <pablo.suarezhernan...@suse.com> + +- Fix zypper pkg.list_pkgs test expectation and dpkg mocking + +- Added: + * fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch + +------------------------------------------------------------------- +Fri Jun 14 12:15:43 UTC 2019 - Pablo Suárez Hernández <pablo.suarezhernan...@suse.com> + +- Set 'salt' group for files and directories created by + salt-standalone-formulas-configuration package +- Various fixes for virt module +- Fix virt.volume_infos raising an exception when there is only virtual machine on the minion. +- Fix virt.purge() on all non-KVM hypervisors. For instance on Xen, virt.purge would simply throw an exception about unsupported flag +- Building a libvirt pool starts it. When defining a new pool, we need to +let build start it or we will get libvirt errors. +- Fix handling of Virtual Machines with white space in their name. + +- Added: + * virt.pool_running-fix-pool-start.patch + * virt-handle-whitespaces-in-vm-names.patch + * virt.volume_infos-fix-for-single-vm.patch + * try-except-undefineflags-as-this-operation-is-not-su.patch + +------------------------------------------------------------------- +Wed Jun 5 14:26:29 UTC 2019 - Pablo Suárez Hernández <pablo.suarezhernan...@suse.com> + +- avoid batch.py exception when minion does not respond (bsc#1135507) + +- Added: + * batch.py-avoid-exception-when-minion-does-not-respon.patch + +------------------------------------------------------------------- +Mon Jun 3 11:01:57 UTC 2019 - psuarezhernan...@suse.com + +- Preserve already defined DESTRUCTIVE_TESTS and EXPENSIVE_TESTS + env variables + +- Added: + * preserve-already-defined-destructive_tests-and-expen.patch + +------------------------------------------------------------------- +Wed May 29 10:54:42 UTC 2019 - psuarezhernan...@suse.com + +- Do not break repo files with multiple line values on yumpkg (bsc#1135360) + +- Added: + * do-not-break-repo-files-with-multiple-line-values-on.patch + +------------------------------------------------------------------- New: ---- batch.py-avoid-exception-when-minion-does-not-respon.patch do-not-break-repo-files-with-multiple-line-values-on.patch fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch preserve-already-defined-destructive_tests-and-expen.patch try-except-undefineflags-as-this-operation-is-not-su.patch virt-handle-whitespaces-in-vm-names.patch virt.pool_running-fix-pool-start.patch virt.volume_infos-fix-for-single-vm.patch ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ salt.spec ++++++ --- /var/tmp/diff_new_pack.QvbGtG/_old 2019-06-18 14:55:15.229445189 +0200 +++ /var/tmp/diff_new_pack.QvbGtG/_new 2019-06-18 14:55:15.233445185 +0200 @@ -180,6 +180,22 @@ Patch56: add-standalone-configuration-file-for-enabling-packa.patch # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/53237 Patch57: add-ppc64le-as-a-valid-rpm-package-architecture.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/53293 +Patch58: do-not-break-repo-files-with-multiple-line-values-on.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/53343 +Patch59: preserve-already-defined-destructive_tests-and-expen.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/53159 +Patch60: batch.py-avoid-exception-when-minion-does-not-respon.patch +# PATCH_FIX_UPSTREAM: https://github.com/saltstack/salt/pull/53471 +Patch61: fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch +# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/142 +Patch62: try-except-undefineflags-as-this-operation-is-not-su.patch +# PATCH_FIX_UPSTREAM: https://github.com/saltstack/salt/pull/52160 +Patch63: virt-handle-whitespaces-in-vm-names.patch +# PATCH_FIX_UPSTREAM: https://github.com/saltstack/salt/pull/52341 +Patch64: virt.pool_running-fix-pool-start.patch +# PATCH_FIX_UPSTREAM: https://github.com/saltstack/salt/pull/52414 +Patch65: virt.volume_infos-fix-for-single-vm.patch # BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -707,6 +723,14 @@ %patch55 -p1 %patch56 -p1 %patch57 -p1 +%patch58 -p1 +%patch59 -p1 +%patch60 -p1 +%patch61 -p1 +%patch62 -p1 +%patch63 -p1 +%patch64 -p1 +%patch65 -p1 %build %if 0%{?build_py2} @@ -1426,9 +1450,9 @@ %files standalone-formulas-configuration %defattr(-,root,root) -%config(noreplace) %attr(0640, root, root) %{_sysconfdir}/salt/master.d/standalone-formulas-configuration.conf -%dir %attr(0750, root, root) %{_prefix}/share/salt-formulas/ -%dir %attr(0750, root, root) %{_prefix}/share/salt-formulas/states/ -%dir %attr(0750, root, root) %{_prefix}/share/salt-formulas/metadata/ +%config(noreplace) %attr(0640, root, salt) %{_sysconfdir}/salt/master.d/standalone-formulas-configuration.conf +%dir %attr(0750, root, salt) %{_prefix}/share/salt-formulas/ +%dir %attr(0750, root, salt) %{_prefix}/share/salt-formulas/states/ +%dir %attr(0750, root, salt) %{_prefix}/share/salt-formulas/metadata/ %changelog ++++++ _lastrevision ++++++ --- /var/tmp/diff_new_pack.QvbGtG/_old 2019-06-18 14:55:15.313445133 +0200 +++ /var/tmp/diff_new_pack.QvbGtG/_new 2019-06-18 14:55:15.313445133 +0200 @@ -1 +1 @@ -6ea034113af0bc6f97110175d633bdf951af0fcd \ No newline at end of file +1d301081a6e8a705499eb861b24c46ab17120691 \ No newline at end of file ++++++ batch.py-avoid-exception-when-minion-does-not-respon.patch ++++++ >From 50377852ca989ffa141fcf32d5ca57d120b455b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Guilherme=20Vanz?= <jv...@jvanz.com> Date: Tue, 21 May 2019 16:13:18 -0300 Subject: [PATCH] batch.py: avoid exception when minion does not respond (bsc#1135507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have several issues reporting that salt is throwing exception when the minion does not respond. This change avoid the exception adding a default data to the minion when it fails to respond. This patch based on the patch suggested by @roskens. Issues #46876 #48509 #50238 bsc#1135507 Signed-off-by: José Guilherme Vanz <jguilhermev...@suse.com> --- salt/cli/batch.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/salt/cli/batch.py b/salt/cli/batch.py index ce239215cb..1623fc5be8 100644 --- a/salt/cli/batch.py +++ b/salt/cli/batch.py @@ -315,6 +315,11 @@ class Batch(object): if self.opts.get('failhard') and data['ret']['retcode'] > 0: failhard = True + # avoid an exception if the minion does not respond. + if data.get("failed") is True: + log.debug('Minion %s failed to respond: data=%s', minion, data) + data = {'ret': 'Minion did not return. [Failed]', 'retcode': salt.defaults.exitcodes.EX_GENERIC} + if self.opts.get('raw'): ret[minion] = data yield data -- 2.21.0 ++++++ do-not-break-repo-files-with-multiple-line-values-on.patch ++++++ >From b99e55aab52d086315d54cf44af68f40dcf79dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= <psuarezhernan...@suse.com> Date: Wed, 29 May 2019 11:03:16 +0100 Subject: [PATCH] Do not break repo files with multiple line values on yumpkg (bsc#1135360) --- salt/modules/yumpkg.py | 16 ++++++--- tests/integration/modules/test_pkg.py | 48 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py index 5ec3835574..3a4fe47a45 100644 --- a/salt/modules/yumpkg.py +++ b/salt/modules/yumpkg.py @@ -2763,7 +2763,12 @@ def del_repo(repo, basedir=None, **kwargs): # pylint: disable=W0613 del filerepos[stanza]['comments'] content += '\n[{0}]'.format(stanza) for line in filerepos[stanza]: - content += '\n{0}={1}'.format(line, filerepos[stanza][line]) + # A whitespace is needed at the begining of the new line in order + # to avoid breaking multiple line values allowed on repo files. + value = filerepos[stanza][line] + if isinstance(value, six.string_types) and '\n' in value: + value = '\n '.join(value.split('\n')) + content += '\n{0}={1}'.format(line, value) content += '\n{0}\n'.format(comments) with salt.utils.files.fopen(repofile, 'w') as fileout: @@ -2898,11 +2903,14 @@ def mod_repo(repo, basedir=None, **kwargs): ) content += '[{0}]\n'.format(stanza) for line in six.iterkeys(filerepos[stanza]): + # A whitespace is needed at the begining of the new line in order + # to avoid breaking multiple line values allowed on repo files. + value = filerepos[stanza][line] + if isinstance(value, six.string_types) and '\n' in value: + value = '\n '.join(value.split('\n')) content += '{0}={1}\n'.format( line, - filerepos[stanza][line] - if not isinstance(filerepos[stanza][line], bool) - else _bool_to_str(filerepos[stanza][line]) + value if not isinstance(value, bool) else _bool_to_str(value) ) content += comments + '\n' diff --git a/tests/integration/modules/test_pkg.py b/tests/integration/modules/test_pkg.py index 0271cea81f..a82c9662c7 100644 --- a/tests/integration/modules/test_pkg.py +++ b/tests/integration/modules/test_pkg.py @@ -123,6 +123,54 @@ class PkgModuleTest(ModuleCase, SaltReturnAssertsMixin): if repo is not None: self.run_function('pkg.del_repo', [repo]) + def test_mod_del_repo_multiline_values(self): + ''' + test modifying and deleting a software repository defined with multiline values + ''' + os_grain = self.run_function('grains.item', ['os'])['os'] + repo = None + try: + if os_grain in ['CentOS', 'RedHat', 'SUSE']: + my_baseurl = 'http://my.fake.repo/foo/bar/\n http://my.fake.repo.alt/foo/bar/' + expected_get_repo_baseurl = 'http://my.fake.repo/foo/bar/\nhttp://my.fake.repo.alt/foo/bar/' + major_release = int( + self.run_function( + 'grains.item', + ['osmajorrelease'] + )['osmajorrelease'] + ) + repo = 'fakerepo' + name = 'Fake repo for RHEL/CentOS/SUSE' + baseurl = my_baseurl + gpgkey = 'https://my.fake.repo/foo/bar/MY-GPG-KEY.pub' + failovermethod = 'priority' + gpgcheck = 1 + enabled = 1 + ret = self.run_function( + 'pkg.mod_repo', + [repo], + name=name, + baseurl=baseurl, + gpgkey=gpgkey, + gpgcheck=gpgcheck, + enabled=enabled, + failovermethod=failovermethod, + ) + # return data from pkg.mod_repo contains the file modified at + # the top level, so use next(iter(ret)) to get that key + self.assertNotEqual(ret, {}) + repo_info = ret[next(iter(ret))] + self.assertIn(repo, repo_info) + self.assertEqual(repo_info[repo]['baseurl'], my_baseurl) + ret = self.run_function('pkg.get_repo', [repo]) + self.assertEqual(ret['baseurl'], expected_get_repo_baseurl) + self.run_function('pkg.mod_repo', [repo]) + ret = self.run_function('pkg.get_repo', [repo]) + self.assertEqual(ret['baseurl'], expected_get_repo_baseurl) + finally: + if repo is not None: + self.run_function('pkg.del_repo', [repo]) + @requires_salt_modules('pkg.owner') def test_owner(self): ''' -- 2.21.0 ++++++ fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch ++++++ >From 8c4066c668147b1180c56f39722d2ade78ffd41c Mon Sep 17 00:00:00 2001 From: Mihai Dinca <mdi...@suse.de> Date: Thu, 13 Jun 2019 17:48:55 +0200 Subject: [PATCH] Fix zypper pkg.list_pkgs expectation and dpkg mocking --- tests/unit/modules/test_dpkg_lowpkg.py | 12 ++++++------ tests/unit/modules/test_zypperpkg.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/unit/modules/test_dpkg_lowpkg.py b/tests/unit/modules/test_dpkg_lowpkg.py index d16ce3cc1a..98557a1d10 100644 --- a/tests/unit/modules/test_dpkg_lowpkg.py +++ b/tests/unit/modules/test_dpkg_lowpkg.py @@ -127,9 +127,9 @@ class DpkgTestCase(TestCase, LoaderModuleMockMixin): with patch.dict(dpkg.__salt__, {'cmd.run_all': mock}): self.assertEqual(dpkg.file_dict('httpd'), 'Error: error') - @patch('salt.modules.dpkg._get_pkg_ds_avail', MagicMock(return_value=dselect_pkg)) - @patch('salt.modules.dpkg._get_pkg_info', MagicMock(return_value=pkgs_info)) - @patch('salt.modules.dpkg._get_pkg_license', MagicMock(return_value='BSD v3')) + @patch('salt.modules.dpkg_lowpkg._get_pkg_ds_avail', MagicMock(return_value=dselect_pkg)) + @patch('salt.modules.dpkg_lowpkg._get_pkg_info', MagicMock(return_value=pkgs_info)) + @patch('salt.modules.dpkg_lowpkg._get_pkg_license', MagicMock(return_value='BSD v3')) def test_info(self): ''' Test info @@ -154,9 +154,9 @@ class DpkgTestCase(TestCase, LoaderModuleMockMixin): assert pkg_data['maintainer'] == 'Simpsons Developers <simpsons-devel-disc...@lists.springfield.org>' assert pkg_data['license'] == 'BSD v3' - @patch('salt.modules.dpkg._get_pkg_ds_avail', MagicMock(return_value=dselect_pkg)) - @patch('salt.modules.dpkg._get_pkg_info', MagicMock(return_value=pkgs_info)) - @patch('salt.modules.dpkg._get_pkg_license', MagicMock(return_value='BSD v3')) + @patch('salt.modules.dpkg_lowpkg._get_pkg_ds_avail', MagicMock(return_value=dselect_pkg)) + @patch('salt.modules.dpkg_lowpkg._get_pkg_info', MagicMock(return_value=pkgs_info)) + @patch('salt.modules.dpkg_lowpkg._get_pkg_license', MagicMock(return_value='BSD v3')) def test_info_attr(self): ''' Test info with 'attr' parameter diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py index 5c5091a570..a7063e47c6 100644 --- a/tests/unit/modules/test_zypperpkg.py +++ b/tests/unit/modules/test_zypperpkg.py @@ -659,7 +659,7 @@ Repository 'DUMMY' not found by its alias, number, or URI. 'install_date_time_t': 1503572639, 'epoch': None, }], - 'perseus-dummy.i586': [{ + 'perseus-dummy': [{ 'version': '1.1', 'release': '1.1', 'arch': 'i586', -- 2.21.0 ++++++ preserve-already-defined-destructive_tests-and-expen.patch ++++++ >From 5a1e0b7b8eab900e03fa800cc7a0a2b59bf2ff55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= <psuarezhernan...@suse.com> Date: Mon, 3 Jun 2019 11:38:36 +0100 Subject: [PATCH] Preserve already defined DESTRUCTIVE_TESTS and EXPENSIVE_TESTS env variables --- tests/support/parser/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/support/parser/__init__.py b/tests/support/parser/__init__.py index ed262d46c0..f269457670 100644 --- a/tests/support/parser/__init__.py +++ b/tests/support/parser/__init__.py @@ -574,12 +574,12 @@ class SaltTestingParser(optparse.OptionParser): self.validate_options() - if self.support_destructive_tests_selection: + if self.support_destructive_tests_selection and not os.environ.get('DESTRUCTIVE_TESTS', None): # Set the required environment variable in order to know if # destructive tests should be executed or not. os.environ['DESTRUCTIVE_TESTS'] = str(self.options.run_destructive) - if self.support_expensive_tests_selection: + if self.support_expensive_tests_selection and not os.environ.get('EXPENSIVE_TESTS', None): # Set the required environment variable in order to know if # expensive tests should be executed or not. os.environ['EXPENSIVE_TESTS'] = str(self.options.run_expensive) -- 2.17.1 ++++++ try-except-undefineflags-as-this-operation-is-not-su.patch ++++++ >From e0bded83fa691c3b972fa4c22b14c5ac0a7a3f13 Mon Sep 17 00:00:00 2001 From: Jeroen Schutrup <jeroenschut...@hotmail.nl> Date: Sun, 12 Aug 2018 19:43:22 +0200 Subject: [PATCH] Try/except undefineFlags() as this operation is not supported on bhyve (cherry picked from commit 29a44aceb1a73347ac07dd241b4a64a4a38cef6e) --- salt/modules/virt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/salt/modules/virt.py b/salt/modules/virt.py index a3f625909d..423016cd90 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py @@ -3189,7 +3189,10 @@ def purge(vm_, dirs=False, removables=None, **kwargs): shutil.rmtree(dir_) if getattr(libvirt, 'VIR_DOMAIN_UNDEFINE_NVRAM', False): # This one is only in 1.2.8+ - dom.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_NVRAM) + try: + dom.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_NVRAM) + except Exception: + dom.undefine() else: dom.undefine() conn.close() -- 2.21.0 ++++++ virt-handle-whitespaces-in-vm-names.patch ++++++ >From fbad82a38b4460260726cb3b9456cad7986eb4cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= <cbosdon...@suse.com> Date: Wed, 13 Mar 2019 09:43:51 +0100 Subject: [PATCH] virt: handle whitespaces in VM names The disk creation code is now ready to handle whitespaces in virtual machine name. --- salt/modules/virt.py | 8 +++--- tests/unit/modules/test_virt.py | 46 ++++++++++++++++----------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/salt/modules/virt.py b/salt/modules/virt.py index 423016cd90..d160f0905f 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py @@ -760,14 +760,14 @@ def _qemu_image_create(disk, create_overlay=False, saltenv='base'): qcow2 = False if salt.utils.path.which('qemu-img'): - res = __salt__['cmd.run']('qemu-img info {}'.format(sfn)) + res = __salt__['cmd.run']('qemu-img info "{}"'.format(sfn)) imageinfo = salt.utils.yaml.safe_load(res) qcow2 = imageinfo['file format'] == 'qcow2' try: if create_overlay and qcow2: log.info('Cloning qcow2 image %s using copy on write', sfn) __salt__['cmd.run']( - 'qemu-img create -f qcow2 -o backing_file={0} {1}' + 'qemu-img create -f qcow2 -o backing_file="{0}" "{1}"' .format(sfn, img_dest).split()) else: log.debug('Copying %s to %s', sfn, img_dest) @@ -778,7 +778,7 @@ def _qemu_image_create(disk, create_overlay=False, saltenv='base'): if disk_size and qcow2: log.debug('Resize qcow2 image to %sM', disk_size) __salt__['cmd.run']( - 'qemu-img resize {0} {1}M' + 'qemu-img resize "{0}" {1}M' .format(img_dest, disk_size) ) @@ -800,7 +800,7 @@ def _qemu_image_create(disk, create_overlay=False, saltenv='base'): if disk_size: log.debug('Create empty image with size %sM', disk_size) __salt__['cmd.run']( - 'qemu-img create -f {0} {1} {2}M' + 'qemu-img create -f {0} "{1}" {2}M' .format(disk.get('format', 'qcow2'), img_dest, disk_size) ) else: diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py index bbe8d813d7..cc62b67918 100644 --- a/tests/unit/modules/test_virt.py +++ b/tests/unit/modules/test_virt.py @@ -1106,7 +1106,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): with patch.dict(virt.__salt__, {'cmd.run': mock_run}): # pylint: disable=no-member # Ensure the init() function allows creating VM without NIC and disk - virt.init('testvm', + virt.init('test vm', 2, 1234, nic=None, @@ -1120,7 +1120,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): # Test case creating disks defineMock.reset_mock() mock_run.reset_mock() - virt.init('testvm', + virt.init('test vm', 2, 1234, nic=None, @@ -1134,10 +1134,10 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): definition = ET.fromstring(defineMock.call_args_list[0][0][0]) disk_sources = [disk.find('source').get('file') if disk.find('source') is not None else None for disk in definition.findall('./devices/disk')] - expected_disk_path = os.path.join(root_dir, 'testvm_system.qcow2') + expected_disk_path = os.path.join(root_dir, 'test vm_system.qcow2') self.assertEqual(disk_sources, [expected_disk_path, None]) self.assertEqual(mock_run.call_args[0][0], - 'qemu-img create -f qcow2 {0} 10240M'.format(expected_disk_path)) + 'qemu-img create -f qcow2 "{0}" 10240M'.format(expected_disk_path)) self.assertEqual(mock_chmod.call_args[0][0], expected_disk_path) def test_update(self): @@ -1147,7 +1147,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): root_dir = os.path.join(salt.syspaths.ROOT_DIR, 'srv', 'salt-images') xml = ''' <domain type='kvm' id='7'> - <name>myvm</name> + <name>my vm</name> <memory unit='KiB'>1048576</memory> <currentMemory unit='KiB'>1048576</currentMemory> <vcpu placement='auto'>1</vcpu> @@ -1157,7 +1157,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <devices> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> - <source file='{0}{1}myvm_system.qcow2'/> + <source file='{0}{1}my vm_system.qcow2'/> <backingStore/> <target dev='vda' bus='virtio'/> <alias name='virtio-disk0'/> @@ -1165,7 +1165,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): </disk> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> - <source file='{0}{1}myvm_data.qcow2'/> + <source file='{0}{1}my vm_data.qcow2'/> <backingStore/> <target dev='vdb' bus='virtio'/> <alias name='virtio-disk1'/> @@ -1198,7 +1198,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): </devices> </domain> '''.format(root_dir, os.sep) - domain_mock = self.set_mock_vm('myvm', xml) + domain_mock = self.set_mock_vm('my vm', xml) domain_mock.OSType = MagicMock(return_value='hvm') define_mock = MagicMock(return_value=True) self.mock_conn.defineXML = define_mock @@ -1211,7 +1211,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): 'cpu': True, 'disk': {'attached': [], 'detached': []}, 'interface': {'attached': [], 'detached': []} - }, virt.update('myvm', cpu=2)) + }, virt.update('my vm', cpu=2)) setxml = ET.fromstring(define_mock.call_args[0][0]) self.assertEqual(setxml.find('vcpu').text, '2') self.assertEqual(setvcpus_mock.call_args[0][0], 2) @@ -1225,7 +1225,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): 'mem': True, 'disk': {'attached': [], 'detached': []}, 'interface': {'attached': [], 'detached': []} - }, virt.update('myvm', mem=2048)) + }, virt.update('my vm', mem=2048)) setxml = ET.fromstring(define_mock.call_args[0][0]) self.assertEqual(setxml.find('memory').text, '2048') self.assertEqual(setxml.find('memory').get('unit'), 'MiB') @@ -1240,21 +1240,21 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): mock_run = MagicMock() with patch.dict(os.__dict__, {'chmod': mock_chmod, 'makedirs': MagicMock()}): # pylint: disable=no-member with patch.dict(virt.__salt__, {'cmd.run': mock_run}): # pylint: disable=no-member - ret = virt.update('myvm', disk_profile='default', disks=[ + ret = virt.update('my vm', disk_profile='default', disks=[ {'name': 'cddrive', 'device': 'cdrom', 'source_file': None, 'model': 'ide'}, {'name': 'added', 'size': 2048}]) added_disk_path = os.path.join( - virt.__salt__['config.get']('virt:images'), 'myvm_added.qcow2') # pylint: disable=no-member + virt.__salt__['config.get']('virt:images'), 'my vm_added.qcow2') # pylint: disable=no-member self.assertEqual(mock_run.call_args[0][0], - 'qemu-img create -f qcow2 {0} 2048M'.format(added_disk_path)) + 'qemu-img create -f qcow2 "{0}" 2048M'.format(added_disk_path)) self.assertEqual(mock_chmod.call_args[0][0], added_disk_path) self.assertListEqual( - [None, os.path.join(root_dir, 'myvm_added.qcow2')], + [None, os.path.join(root_dir, 'my vm_added.qcow2')], [ET.fromstring(disk).find('source').get('file') if str(disk).find('<source') > -1 else None for disk in ret['disk']['attached']]) self.assertListEqual( - [os.path.join(root_dir, 'myvm_data.qcow2')], + [os.path.join(root_dir, 'my vm_data.qcow2')], [ET.fromstring(disk).find('source').get('file') for disk in ret['disk']['detached']]) self.assertEqual(devattach_mock.call_count, 2) devdetach_mock.assert_called_once() @@ -1271,7 +1271,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): devattach_mock.reset_mock() devdetach_mock.reset_mock() with patch.dict(salt.modules.config.__opts__, mock_config): # pylint: disable=no-member - ret = virt.update('myvm', nic_profile='myprofile', + ret = virt.update('my vm', nic_profile='myprofile', interfaces=[{'name': 'eth0', 'type': 'network', 'source': 'default', 'mac': '52:54:00:39:02:b1'}, {'name': 'eth1', 'type': 'network', 'source': 'newnet'}]) @@ -1285,7 +1285,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): # Remove nics case devattach_mock.reset_mock() devdetach_mock.reset_mock() - ret = virt.update('myvm', nic_profile=None, interfaces=[]) + ret = virt.update('my vm', nic_profile=None, interfaces=[]) self.assertEqual([], ret['interface']['attached']) self.assertEqual(2, len(ret['interface']['detached'])) devattach_mock.assert_not_called() @@ -1294,7 +1294,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): # Remove disks case (yeah, it surely is silly) devattach_mock.reset_mock() devdetach_mock.reset_mock() - ret = virt.update('myvm', disk_profile=None, disks=[]) + ret = virt.update('my vm', disk_profile=None, disks=[]) self.assertEqual([], ret['disk']['attached']) self.assertEqual(2, len(ret['disk']['detached'])) devattach_mock.assert_not_called() @@ -1305,7 +1305,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): 'definition': True, 'disk': {'attached': [], 'detached': []}, 'interface': {'attached': [], 'detached': []} - }, virt.update('myvm', graphics={'type': 'vnc'})) + }, virt.update('my vm', graphics={'type': 'vnc'})) setxml = ET.fromstring(define_mock.call_args[0][0]) self.assertEqual('vnc', setxml.find('devices/graphics').get('type')) @@ -1314,7 +1314,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): 'definition': False, 'disk': {'attached': [], 'detached': []}, 'interface': {'attached': [], 'detached': []} - }, virt.update('myvm', cpu=1, mem=1024, + }, virt.update('my vm', cpu=1, mem=1024, disk_profile='default', disks=[{'name': 'data', 'size': 2048}], nic_profile='myprofile', interfaces=[{'name': 'eth0', 'type': 'network', 'source': 'default', @@ -1328,7 +1328,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): self.mock_conn.defineXML.side_effect = self.mock_libvirt.libvirtError("Test error") setmem_mock.reset_mock() with self.assertRaises(self.mock_libvirt.libvirtError): - virt.update('myvm', mem=2048) + virt.update('my vm', mem=2048) # Failed single update failure case self.mock_conn.defineXML = MagicMock(return_value=True) @@ -1338,7 +1338,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): 'errors': ['Failed to live change memory'], 'disk': {'attached': [], 'detached': []}, 'interface': {'attached': [], 'detached': []} - }, virt.update('myvm', mem=2048)) + }, virt.update('my vm', mem=2048)) # Failed multiple updates failure case self.assertEqual({ @@ -1347,7 +1347,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): 'cpu': True, 'disk': {'attached': [], 'detached': []}, 'interface': {'attached': [], 'detached': []} - }, virt.update('myvm', cpu=4, mem=2048)) + }, virt.update('my vm', cpu=4, mem=2048)) def test_mixed_dict_and_list_as_profile_objects(self): ''' -- 2.21.0 ++++++ virt.pool_running-fix-pool-start.patch ++++++ >From 946dd98e911e62c7bc3bcdd8adc8a170645c981c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= <cbosdon...@suse.com> Date: Wed, 6 Jun 2018 09:49:36 +0200 Subject: [PATCH] virt.pool_running: fix pool start Building a libvirt pool starts it. When defining a new pool, we need to let build start it or we will get libvirt errors. Also backport virt states test to add test for the bug: cherry picked from commits: - 451e7da55bd232546c4d30ec36d432de2d5a14ec - 495db345a570cb14cd9b0ae96e1bb0f3fad6aef0 - cb00a5f9b4c9a2a863da3c1107ca6458a4092c3d - fc75872fb63e254eecc782168ff8b37157d9e514 - 2a5f6ae5d69be71daeab6c9cbe4dd642255ff3c6 - 2463ebe5a82b1a017004e8e0e390535485dc703e - c7c5d6ee88fbc74d0ee0aeab41beb421d8625f05 --- salt/states/virt.py | 7 +- tests/unit/states/test_virt.py | 508 ++++++++++++++++++++++++++++++++- 2 files changed, 508 insertions(+), 7 deletions(-) diff --git a/salt/states/virt.py b/salt/states/virt.py index 90693880df..d411f864cd 100644 --- a/salt/states/virt.py +++ b/salt/states/virt.py @@ -780,7 +780,7 @@ def pool_running(name, source_name=(source or {}).get('name', None), source_format=(source or {}).get('format', None), transient=transient, - start=True, + start=False, connection=connection, username=username, password=password) @@ -795,11 +795,6 @@ def pool_running(name, connection=connection, username=username, password=password) - - __salt__['virt.pool_start'](name, - connection=connection, - username=username, - password=password) ret['changes'][name] = 'Pool defined and started' ret['comment'] = 'Pool {0} defined and started'.format(name) except libvirt.libvirtError as err: diff --git a/tests/unit/states/test_virt.py b/tests/unit/states/test_virt.py index 2e421319ad..8022989937 100644 --- a/tests/unit/states/test_virt.py +++ b/tests/unit/states/test_virt.py @@ -21,6 +21,25 @@ from tests.support.mock import ( # Import Salt Libs import salt.states.virt as virt import salt.utils.files +from salt.exceptions import CommandExecutionError + +# Import 3rd-party libs +from salt.ext import six + + +class LibvirtMock(MagicMock): # pylint: disable=too-many-ancestors + ''' + libvirt library mockup + ''' + class libvirtError(Exception): # pylint: disable=invalid-name + ''' + libvirt error mockup + ''' + def get_error_message(self): + ''' + Fake function return error message + ''' + return six.text_type(self) @skipIf(NO_MOCK, NO_MOCK_REASON) @@ -29,7 +48,12 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): Test cases for salt.states.libvirt ''' def setup_loader_modules(self): - return {virt: {}} + self.mock_libvirt = LibvirtMock() # pylint: disable=attribute-defined-outside-init + self.addCleanup(delattr, self, 'mock_libvirt') + loader_globals = { + 'libvirt': self.mock_libvirt + } + return {virt: loader_globals} @classmethod def setUpClass(cls): @@ -195,3 +219,485 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): locality='Los_Angeles', organization='SaltStack', expiration_days=700), ret) + + def test_running(self): + ''' + running state test cases. + ''' + ret = {'name': 'myvm', + 'changes': {}, + 'result': True, + 'comment': 'myvm is running'} + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value='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, + 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.__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', + 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, + install=False, + pub_key='/path/to/key.pub', + priv_key='/path/to/key', + connection='someconnection', + username='libvirtuser', + password='supersecret') + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value='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) + + # Working update case when running + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value='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 stopped + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value='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) + + # Failed live update case + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value='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 definition update case + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.vm_state': MagicMock(return_value='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) + + def test_stopped(self): + ''' + stopped state test cases. + ''' + ret = {'name': 'myvm', + 'changes': {}, + 'result': True} + + shutdown_mock = MagicMock(return_value=True) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.shutdown': shutdown_mock + }): + ret.update({'changes': { + 'stopped': [{'domain': 'myvm', 'shutdown': True}] + }, + 'comment': 'Machine has been shut down'}) + self.assertDictEqual(virt.stopped('myvm'), ret) + shutdown_mock.assert_called_with('myvm', connection=None, username=None, password=None) + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.shutdown': shutdown_mock, + }): + self.assertDictEqual(virt.stopped('myvm', + connection='myconnection', + username='user', + password='secret'), ret) + shutdown_mock.assert_called_with('myvm', connection='myconnection', username='user', password='secret') + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.shutdown': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) + }): + ret.update({'changes': {'ignored': [{'domain': 'myvm', 'issue': 'Some error'}]}, + 'result': False, + 'comment': 'No changes had happened'}) + self.assertDictEqual(virt.stopped('myvm'), ret) + + with patch.dict(virt.__salt__, {'virt.list_domains': MagicMock(return_value=[])}): # pylint: disable=no-member + ret.update({'changes': {}, 'result': False, 'comment': 'No changes had happened'}) + self.assertDictEqual(virt.stopped('myvm'), ret) + + def test_powered_off(self): + ''' + powered_off state test cases. + ''' + ret = {'name': 'myvm', + 'changes': {}, + 'result': True} + + stop_mock = MagicMock(return_value=True) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.stop': stop_mock + }): + ret.update({'changes': { + 'unpowered': [{'domain': 'myvm', 'stop': True}] + }, + 'comment': 'Machine has been powered off'}) + self.assertDictEqual(virt.powered_off('myvm'), ret) + stop_mock.assert_called_with('myvm', connection=None, username=None, password=None) + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.stop': stop_mock, + }): + self.assertDictEqual(virt.powered_off('myvm', + connection='myconnection', + username='user', + password='secret'), ret) + stop_mock.assert_called_with('myvm', connection='myconnection', username='user', password='secret') + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.stop': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) + }): + ret.update({'changes': {'ignored': [{'domain': 'myvm', 'issue': 'Some error'}]}, + 'result': False, + 'comment': 'No changes had happened'}) + self.assertDictEqual(virt.powered_off('myvm'), ret) + + with patch.dict(virt.__salt__, {'virt.list_domains': MagicMock(return_value=[])}): # pylint: disable=no-member + ret.update({'changes': {}, 'result': False, 'comment': 'No changes had happened'}) + self.assertDictEqual(virt.powered_off('myvm'), ret) + + def test_snapshot(self): + ''' + snapshot state test cases. + ''' + ret = {'name': 'myvm', + 'changes': {}, + 'result': True} + + snapshot_mock = MagicMock(return_value=True) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.snapshot': snapshot_mock + }): + ret.update({'changes': { + 'saved': [{'domain': 'myvm', 'snapshot': True}] + }, + 'comment': 'Snapshot has been taken'}) + self.assertDictEqual(virt.snapshot('myvm'), ret) + snapshot_mock.assert_called_with('myvm', suffix=None, connection=None, username=None, password=None) + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.snapshot': snapshot_mock, + }): + self.assertDictEqual(virt.snapshot('myvm', + suffix='snap', + connection='myconnection', + username='user', + password='secret'), ret) + snapshot_mock.assert_called_with('myvm', + suffix='snap', + connection='myconnection', + username='user', + password='secret') + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.snapshot': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) + }): + ret.update({'changes': {'ignored': [{'domain': 'myvm', 'issue': 'Some error'}]}, + 'result': False, + 'comment': 'No changes had happened'}) + self.assertDictEqual(virt.snapshot('myvm'), ret) + + with patch.dict(virt.__salt__, {'virt.list_domains': MagicMock(return_value=[])}): # pylint: disable=no-member + ret.update({'changes': {}, 'result': False, 'comment': 'No changes had happened'}) + self.assertDictEqual(virt.snapshot('myvm'), ret) + + def test_rebooted(self): + ''' + rebooted state test cases. + ''' + ret = {'name': 'myvm', + 'changes': {}, + 'result': True} + + reboot_mock = MagicMock(return_value=True) + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.reboot': reboot_mock + }): + ret.update({'changes': { + 'rebooted': [{'domain': 'myvm', 'reboot': True}] + }, + 'comment': 'Machine has been rebooted'}) + self.assertDictEqual(virt.rebooted('myvm'), ret) + reboot_mock.assert_called_with('myvm', connection=None, username=None, password=None) + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.reboot': reboot_mock, + }): + self.assertDictEqual(virt.rebooted('myvm', + connection='myconnection', + username='user', + password='secret'), ret) + reboot_mock.assert_called_with('myvm', + connection='myconnection', + username='user', + password='secret') + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), + 'virt.reboot': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) + }): + ret.update({'changes': {'ignored': [{'domain': 'myvm', 'issue': 'Some error'}]}, + 'result': False, + 'comment': 'No changes had happened'}) + self.assertDictEqual(virt.rebooted('myvm'), ret) + + with patch.dict(virt.__salt__, {'virt.list_domains': MagicMock(return_value=[])}): # pylint: disable=no-member + ret.update({'changes': {}, 'result': False, 'comment': 'No changes had happened'}) + self.assertDictEqual(virt.rebooted('myvm'), 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, + autostart=False, + connection='myconnection', + username='user', + password='secret'), ret) + define_mock.assert_called_with('mynet', + 'br2', + 'bridge', + 'openvswitch', + tag=180, + autostart=False, + start=True, + connection='myconnection', + username='user', + password='secret') + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.network_info': MagicMock(return_value={'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={'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', + connection='myconnection', + username='user', + password='secret'), ret) + 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={}), + '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) + + def test_pool_running(self): + ''' + pool_running state test cases. + ''' + ret = {'name': 'mypool', 'changes': {}, 'result': True, 'comment': ''} + mocks = {mock: MagicMock(return_value=True) for mock in ['define', 'autostart', 'build', 'start']} + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(return_value={}), + '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 and started'}, + 'comment': 'Pool mypool defined and started'}) + 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) + 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, + transient=True, + start=False, + connection='myconnection', + username='user', + password='secret') + mocks['autostart'].assert_called_with('mypool', + state='on', + connection='myconnection', + username='user', + password='secret') + mocks['build'].assert_called_with('mypool', + connection='myconnection', + username='user', + password='secret') + mocks['start'].assert_not_called() + + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(return_value={'state': 'running'}), + }): + ret.update({'changes': {}, 'comment': 'Pool mypool exists and is running'}) + self.assertDictEqual(virt.pool_running('mypool', + ptype='logical', + target='/dev/base', + source={'devices': [{'path': '/dev/sda'}]}), ret) + + for mock in mocks: + mocks[mock].reset_mock() + with patch.dict(virt.__salt__, { # pylint: disable=no-member + 'virt.pool_info': MagicMock(return_value={'state': 'stopped'}), + 'virt.pool_build': mocks['build'], + 'virt.pool_start': mocks['start'] + }): + ret.update({'changes': {'mypool': 'Pool started'}, 'comment': 'Pool mypool started'}) + self.assertDictEqual(virt.pool_running('mypool', + ptype='logical', + target='/dev/base', + source={'devices': [{'path': '/dev/sda'}]}), ret) + mocks['start'].assert_called_with('mypool', connection=None, username=None, password=None) + 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_running('mypool', + ptype='logical', + target='/dev/base', + source={'devices': [{'path': '/dev/sda'}]}), ret) -- 2.21.0 ++++++ virt.volume_infos-fix-for-single-vm.patch ++++++ >From b0b5a78a463f7587be4f81074b182d1f4b4461be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= <cbosdon...@suse.com> Date: Thu, 4 Apr 2019 16:18:58 +0200 Subject: [PATCH] virt.volume_infos fix for single VM _get_domain returns a domain object when only one VM has been found. virt.volume_infos needs to take care of it or it will fail to list the volumes informations if the host in such a case. --- salt/modules/virt.py | 4 ++- tests/unit/modules/test_virt.py | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/salt/modules/virt.py b/salt/modules/virt.py index 17039444c4..a3f625909d 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py @@ -5047,10 +5047,12 @@ def volume_infos(pool=None, volume=None, **kwargs): conn = __get_conn(**kwargs) try: backing_stores = _get_all_volumes_paths(conn) + domains = _get_domain(conn) + domains_list = domains if isinstance(domains, list) else [domains] disks = {domain.name(): {node.get('file') for node in ElementTree.fromstring(domain.XMLDesc(0)).findall('.//disk/source/[@file]')} - for domain in _get_domain(conn)} + for domain in domains_list} def _volume_extract_infos(vol): ''' diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py index 14e51e1e2a..bbe8d813d7 100644 --- a/tests/unit/modules/test_virt.py +++ b/tests/unit/modules/test_virt.py @@ -2864,6 +2864,52 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): } }) + # Single VM test + with patch('salt.modules.virt._get_domain', MagicMock(return_value=mock_vms[0])): + actual = virt.volume_infos('pool0', 'vol0') + self.assertEqual(1, len(actual.keys())) + self.assertEqual(1, len(actual['pool0'].keys())) + self.assertEqual(['vm0'], sorted(actual['pool0']['vol0']['used_by'])) + self.assertEqual('/path/to/vol0.qcow2', actual['pool0']['vol0']['path']) + self.assertEqual('file', actual['pool0']['vol0']['type']) + self.assertEqual('/key/of/vol0', actual['pool0']['vol0']['key']) + self.assertEqual(123456789, actual['pool0']['vol0']['capacity']) + self.assertEqual(123456, actual['pool0']['vol0']['allocation']) + + self.assertEqual(virt.volume_infos('pool1', None), { + 'pool1': { + 'vol1': { + 'type': 'file', + 'key': '/key/of/vol1', + 'path': '/path/to/vol1.qcow2', + 'capacity': 12345, + 'allocation': 1234, + 'used_by': [], + }, + 'vol2': { + 'type': 'file', + 'key': '/key/of/vol2', + 'path': '/path/to/vol2.qcow2', + 'capacity': 12345, + 'allocation': 1234, + 'used_by': [], + } + } + }) + + self.assertEqual(virt.volume_infos(None, 'vol2'), { + 'pool1': { + 'vol2': { + 'type': 'file', + 'key': '/key/of/vol2', + 'path': '/path/to/vol2.qcow2', + 'capacity': 12345, + 'allocation': 1234, + 'used_by': [], + } + } + }) + def test_volume_delete(self): ''' Test virt.volume_delete -- 2.21.0