From 83d86fbdce3fd439602f933119546785bedc2818415c30b5adee7f0a6ea7b6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Fri, 14 Jun 2019 12:50:01 +0000 Subject: [PATCH 1/2] osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:274 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=144 --- _lastrevision | 2 +- ...xception-when-minion-does-not-respon.patch | 42 ++ ...o-files-with-multiple-line-values-on.patch | 111 ++++ ...ist_pkgs-expectation-and-dpkg-mockin.patch | 57 ++ ...-defined-destructive_tests-and-expen.patch | 34 + salt.changes | 43 ++ salt.spec | 32 +- ...ineflags-as-this-operation-is-not-su.patch | 31 + virt-handle-whitespaces-in-vm-names.patch | 243 ++++++++ virt.pool_running-fix-pool-start.patch | 582 ++++++++++++++++++ virt.volume_infos-fix-for-single-vm.patch | 92 +++ 11 files changed, 1264 insertions(+), 5 deletions(-) create mode 100644 batch.py-avoid-exception-when-minion-does-not-respon.patch create mode 100644 do-not-break-repo-files-with-multiple-line-values-on.patch create mode 100644 fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch create mode 100644 preserve-already-defined-destructive_tests-and-expen.patch create mode 100644 try-except-undefineflags-as-this-operation-is-not-su.patch create mode 100644 virt-handle-whitespaces-in-vm-names.patch create mode 100644 virt.pool_running-fix-pool-start.patch create mode 100644 virt.volume_infos-fix-for-single-vm.patch diff --git a/_lastrevision b/_lastrevision index 5e9604c..2676b1d 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -6ea034113af0bc6f97110175d633bdf951af0fcd \ No newline at end of file +1d301081a6e8a705499eb861b24c46ab17120691 \ No newline at end of file diff --git a/batch.py-avoid-exception-when-minion-does-not-respon.patch b/batch.py-avoid-exception-when-minion-does-not-respon.patch new file mode 100644 index 0000000..03d203f --- /dev/null +++ b/batch.py-avoid-exception-when-minion-does-not-respon.patch @@ -0,0 +1,42 @@ +From 50377852ca989ffa141fcf32d5ca57d120b455b8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Guilherme=20Vanz?= +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 +--- + 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 + + diff --git a/do-not-break-repo-files-with-multiple-line-values-on.patch b/do-not-break-repo-files-with-multiple-line-values-on.patch new file mode 100644 index 0000000..179dced --- /dev/null +++ b/do-not-break-repo-files-with-multiple-line-values-on.patch @@ -0,0 +1,111 @@ +From b99e55aab52d086315d54cf44af68f40dcf79dc9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +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 + + diff --git a/fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch b/fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch new file mode 100644 index 0000000..ea5fbe8 --- /dev/null +++ b/fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch @@ -0,0 +1,57 @@ +From 8c4066c668147b1180c56f39722d2ade78ffd41c Mon Sep 17 00:00:00 2001 +From: Mihai Dinca +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 ' + 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 + + diff --git a/preserve-already-defined-destructive_tests-and-expen.patch b/preserve-already-defined-destructive_tests-and-expen.patch new file mode 100644 index 0000000..5c7131e --- /dev/null +++ b/preserve-already-defined-destructive_tests-and-expen.patch @@ -0,0 +1,34 @@ +From 5a1e0b7b8eab900e03fa800cc7a0a2b59bf2ff55 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +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 + + diff --git a/salt.changes b/salt.changes index 2d4ff07..2c3302a 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,46 @@ +------------------------------------------------------------------- +Fri Jun 14 12:15:43 UTC 2019 - Pablo Suárez Hernández + +- 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 + +- 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 - psuarezhernandez@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 - psuarezhernandez@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 + ------------------------------------------------------------------- Fri May 24 16:03:09 UTC 2019 - psuarezhernandez@suse.com diff --git a/salt.spec b/salt.spec index 2f8b144..9cb5ed7 100644 --- a/salt.spec +++ b/salt.spec @@ -180,6 +180,22 @@ Patch55: switch-firewalld-state-to-use-change_interface.patch 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 @@ cp %{S:5} ./.travis.yml %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,10 +1450,10 @@ rm -f %{_localstatedir}/cache/salt/minion/thin/version %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 diff --git a/try-except-undefineflags-as-this-operation-is-not-su.patch b/try-except-undefineflags-as-this-operation-is-not-su.patch new file mode 100644 index 0000000..bc5f693 --- /dev/null +++ b/try-except-undefineflags-as-this-operation-is-not-su.patch @@ -0,0 +1,31 @@ +From e0bded83fa691c3b972fa4c22b14c5ac0a7a3f13 Mon Sep 17 00:00:00 2001 +From: Jeroen Schutrup +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 + + diff --git a/virt-handle-whitespaces-in-vm-names.patch b/virt-handle-whitespaces-in-vm-names.patch new file mode 100644 index 0000000..c20ef5f --- /dev/null +++ b/virt-handle-whitespaces-in-vm-names.patch @@ -0,0 +1,243 @@ +From fbad82a38b4460260726cb3b9456cad7986eb4cd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= +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 = ''' + +- myvm ++ my vm + 1048576 + 1048576 + 1 +@@ -1157,7 +1157,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): + + + +- ++ + + + +@@ -1165,7 +1165,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): + + + +- ++ + + + +@@ -1198,7 +1198,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): + + + '''.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(' -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 + + diff --git a/virt.pool_running-fix-pool-start.patch b/virt.pool_running-fix-pool-start.patch new file mode 100644 index 0000000..054605b --- /dev/null +++ b/virt.pool_running-fix-pool-start.patch @@ -0,0 +1,582 @@ +From 946dd98e911e62c7bc3bcdd8adc8a170645c981c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= +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 + + diff --git a/virt.volume_infos-fix-for-single-vm.patch b/virt.volume_infos-fix-for-single-vm.patch new file mode 100644 index 0000000..07a89d4 --- /dev/null +++ b/virt.volume_infos-fix-for-single-vm.patch @@ -0,0 +1,92 @@ +From b0b5a78a463f7587be4f81074b182d1f4b4461be Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= +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 + + From 5e206d233165ab5788de475b73c43474e981078ec71bd227e252987cc6d3d50e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Fri, 14 Jun 2019 14:23:39 +0000 Subject: [PATCH 2/2] osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:275 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=145 --- salt.changes | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/salt.changes b/salt.changes index 2c3302a..8c0c417 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,11 @@ +------------------------------------------------------------------- +Fri Jun 14 14:09:29 UTC 2019 - Pablo Suárez Hernández + +- 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