osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:318

OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=159
This commit is contained in:
Jochen Breuer 2020-01-15 14:58:58 +00:00 committed by Git OBS Bridge
parent 4ead0d0098
commit ddaff062ff
9 changed files with 3278 additions and 1 deletions

View File

@ -1 +1 @@
119d230d13c22207b56ca0276f65a25692e8f4bf
d3f65020201314619013243463c3fe8098529e3e

View File

@ -0,0 +1,123 @@
From a5072a8e834127c9633c1af78631dcef6db0e6cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= <cbosdonnat@suse.com>
Date: Mon, 30 Dec 2019 17:28:50 +0100
Subject: [PATCH] Add virt.network_get_xml function
Users may want to see the full XML definition of a network.
Add virt.pool_get_xml function
Users may want to see the full XML definition of a virtual storage pool.
---
salt/modules/virt.py | 48 +++++++++++++++++++++++++++++++++++++++++
tests/unit/modules/test_virt.py | 20 +++++++++++++++++
2 files changed, 68 insertions(+)
diff --git a/salt/modules/virt.py b/salt/modules/virt.py
index 44c7e78ef0..339760ead4 100644
--- a/salt/modules/virt.py
+++ b/salt/modules/virt.py
@@ -4633,6 +4633,30 @@ def network_info(name=None, **kwargs):
return result
+def network_get_xml(name, **kwargs):
+ '''
+ Return the XML definition of a virtual network
+
+ :param name: libvirt network name
+ :param connection: libvirt connection URI, overriding defaults
+ :param username: username to connect with, overriding defaults
+ :param password: password to connect with, overriding defaults
+
+ .. versionadded:: Neon
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.network_get_xml default
+ '''
+ conn = __get_conn(**kwargs)
+ try:
+ return conn.networkLookupByName(name).XMLDesc()
+ finally:
+ conn.close()
+
+
def network_start(name, **kwargs):
'''
Start a defined virtual network.
@@ -5377,6 +5401,30 @@ def pool_info(name=None, **kwargs):
return result
+def pool_get_xml(name, **kwargs):
+ '''
+ Return the XML definition of a virtual storage pool
+
+ :param name: libvirt storage pool name
+ :param connection: libvirt connection URI, overriding defaults
+ :param username: username to connect with, overriding defaults
+ :param password: password to connect with, overriding defaults
+
+ .. versionadded:: Neon
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.pool_get_xml default
+ '''
+ conn = __get_conn(**kwargs)
+ try:
+ return conn.storagePoolLookupByName(name).XMLDesc()
+ finally:
+ conn.close()
+
+
def pool_start(name, **kwargs):
'''
Start a defined libvirt storage pool.
diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py
index 698e1922fc..719f97a724 100644
--- a/tests/unit/modules/test_virt.py
+++ b/tests/unit/modules/test_virt.py
@@ -2404,6 +2404,16 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
net = virt.network_info('foo')
self.assertEqual({}, net)
+ def test_network_get_xml(self):
+ '''
+ Test virt.network_get_xml
+ '''
+ network_mock = MagicMock()
+ network_mock.XMLDesc.return_value = '<net>Raw XML</net>'
+ self.mock_conn.networkLookupByName.return_value = network_mock
+
+ self.assertEqual('<net>Raw XML</net>', virt.network_get_xml('default'))
+
def test_pool(self):
'''
Test virt._gen_pool_xml()
@@ -2806,6 +2816,16 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
}
}, pool)
+ def test_pool_get_xml(self):
+ '''
+ Test virt.pool_get_xml
+ '''
+ pool_mock = MagicMock()
+ pool_mock.XMLDesc.return_value = '<pool>Raw XML</pool>'
+ self.mock_conn.storagePoolLookupByName.return_value = pool_mock
+
+ self.assertEqual('<pool>Raw XML</pool>', virt.pool_get_xml('default'))
+
def test_pool_list_volumes(self):
'''
Test virt.pool_list_volumes
--
2.16.4

View File

@ -0,0 +1,217 @@
From 46aeb732ed61051d436ac748bd95a50c6379682a Mon Sep 17 00:00:00 2001
From: Cedric Bosdonnat <cbosdonnat@suse.com>
Date: Mon, 16 Dec 2019 11:27:49 +0100
Subject: [PATCH] Fix virt states to not fail on VMs already stopped.
(#195)
The virt.stopped and virt.powered_off states need to do nothing and
not fail if one of the targeted VMs is already in shutdown state.
---
salt/states/virt.py | 45 ++++++++++++++++++++--------------
tests/unit/states/test_virt.py | 36 +++++++++++++++++++++++++++
2 files changed, 63 insertions(+), 18 deletions(-)
diff --git a/salt/states/virt.py b/salt/states/virt.py
index 32a9e31ae5..68e9ac6fb6 100644
--- a/salt/states/virt.py
+++ b/salt/states/virt.py
@@ -145,35 +145,45 @@ def keys(name, basepath='/etc/pki', **kwargs):
return ret
-def _virt_call(domain, function, section, comment,
+def _virt_call(domain, function, section, comment, state=None,
connection=None, username=None, password=None, **kwargs):
'''
Helper to call the virt functions. Wildcards supported.
- :param domain:
- :param function:
- :param section:
- :param comment:
- :return:
+ :param domain: the domain to apply the function on. Can contain wildcards.
+ :param function: virt function to call
+ :param section: key for the changed domains in the return changes dictionary
+ :param comment: comment to return
+ :param state: the expected final state of the VM. If None the VM state won't be checked.
+ :return: the salt state return
'''
ret = {'name': domain, 'changes': {}, 'result': True, 'comment': ''}
targeted_domains = fnmatch.filter(__salt__['virt.list_domains'](), domain)
changed_domains = list()
ignored_domains = list()
+ noaction_domains = list()
for targeted_domain in targeted_domains:
try:
- response = __salt__['virt.{0}'.format(function)](targeted_domain,
- connection=connection,
- username=username,
- password=password,
- **kwargs)
- if isinstance(response, dict):
- response = response['name']
- changed_domains.append({'domain': targeted_domain, function: response})
+ action_needed = True
+ # If a state has been provided, use it to see if we have something to do
+ if state is not None:
+ domain_state = __salt__['virt.vm_state'](targeted_domain)
+ action_needed = domain_state.get(targeted_domain) != state
+ if action_needed:
+ response = __salt__['virt.{0}'.format(function)](targeted_domain,
+ connection=connection,
+ username=username,
+ password=password,
+ **kwargs)
+ if isinstance(response, dict):
+ response = response['name']
+ changed_domains.append({'domain': targeted_domain, function: response})
+ else:
+ noaction_domains.append(targeted_domain)
except libvirt.libvirtError as err:
ignored_domains.append({'domain': targeted_domain, 'issue': six.text_type(err)})
if not changed_domains:
- ret['result'] = False
+ ret['result'] = not ignored_domains and bool(targeted_domains)
ret['comment'] = 'No changes had happened'
if ignored_domains:
ret['changes'] = {'ignored': ignored_domains}
@@ -206,7 +216,7 @@ def stopped(name, connection=None, username=None, password=None):
virt.stopped
'''
- return _virt_call(name, 'shutdown', 'stopped', "Machine has been shut down",
+ return _virt_call(name, 'shutdown', 'stopped', 'Machine has been shut down', state='shutdown',
connection=connection, username=username, password=password)
@@ -231,8 +241,7 @@ def powered_off(name, connection=None, username=None, password=None):
domain_name:
virt.stopped
'''
-
- return _virt_call(name, 'stop', 'unpowered', 'Machine has been powered off',
+ return _virt_call(name, 'stop', 'unpowered', 'Machine has been powered off', state='shutdown',
connection=connection, username=username, password=password)
diff --git a/tests/unit/states/test_virt.py b/tests/unit/states/test_virt.py
index 2904fa224d..2af5caca1b 100644
--- a/tests/unit/states/test_virt.py
+++ b/tests/unit/states/test_virt.py
@@ -378,8 +378,11 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
'result': True}
shutdown_mock = MagicMock(return_value=True)
+
+ # Normal case
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']),
+ 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}),
'virt.shutdown': shutdown_mock
}):
ret.update({'changes': {
@@ -389,8 +392,10 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
self.assertDictEqual(virt.stopped('myvm'), ret)
shutdown_mock.assert_called_with('myvm', connection=None, username=None, password=None)
+ # Normal case with user-provided connection parameters
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']),
+ 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}),
'virt.shutdown': shutdown_mock,
}):
self.assertDictEqual(virt.stopped('myvm',
@@ -399,8 +404,10 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
password='secret'), ret)
shutdown_mock.assert_called_with('myvm', connection='myconnection', username='user', password='secret')
+ # Case where an error occurred during the shutdown
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']),
+ 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}),
'virt.shutdown': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error'))
}):
ret.update({'changes': {'ignored': [{'domain': 'myvm', 'issue': 'Some error'}]},
@@ -408,10 +415,21 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
'comment': 'No changes had happened'})
self.assertDictEqual(virt.stopped('myvm'), ret)
+ # Case there the domain doesn't exist
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)
+ # Case where the domain is already stopped
+ with patch.dict(virt.__salt__, { # pylint: disable=no-member
+ 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']),
+ 'virt.vm_state': MagicMock(return_value={'myvm': 'shutdown'})
+ }):
+ ret.update({'changes': {},
+ 'result': True,
+ 'comment': 'No changes had happened'})
+ self.assertDictEqual(virt.stopped('myvm'), ret)
+
def test_powered_off(self):
'''
powered_off state test cases.
@@ -421,8 +439,11 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
'result': True}
stop_mock = MagicMock(return_value=True)
+
+ # Normal case
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']),
+ 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}),
'virt.stop': stop_mock
}):
ret.update({'changes': {
@@ -432,8 +453,10 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
self.assertDictEqual(virt.powered_off('myvm'), ret)
stop_mock.assert_called_with('myvm', connection=None, username=None, password=None)
+ # Normal case with user-provided connection parameters
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']),
+ 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}),
'virt.stop': stop_mock,
}):
self.assertDictEqual(virt.powered_off('myvm',
@@ -442,8 +465,10 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
password='secret'), ret)
stop_mock.assert_called_with('myvm', connection='myconnection', username='user', password='secret')
+ # Case where an error occurred during the poweroff
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']),
+ 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}),
'virt.stop': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error'))
}):
ret.update({'changes': {'ignored': [{'domain': 'myvm', 'issue': 'Some error'}]},
@@ -451,10 +476,21 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
'comment': 'No changes had happened'})
self.assertDictEqual(virt.powered_off('myvm'), ret)
+ # Case there the domain doesn't exist
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)
+ # Case where the domain is already stopped
+ with patch.dict(virt.__salt__, { # pylint: disable=no-member
+ 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']),
+ 'virt.vm_state': MagicMock(return_value={'myvm': 'shutdown'})
+ }):
+ ret.update({'changes': {},
+ 'result': True,
+ 'comment': 'No changes had happened'})
+ self.assertDictEqual(virt.powered_off('myvm'), ret)
+
def test_snapshot(self):
'''
snapshot state test cases.
--
2.23.0

View File

@ -0,0 +1,162 @@
From a1e0904a640d01d4bab0871db1ab8ea653335443 Mon Sep 17 00:00:00 2001
From: Jochen Breuer <jbreuer@suse.de>
Date: Thu, 9 Jan 2020 10:11:13 +0100
Subject: [PATCH] list_downloaded for apt Module
---
salt/modules/aptpkg.py | 41 +++++++++++++++++++++++++++++++++++++++
salt/states/pkg.py | 4 ++--
tests/unit/modules/test_aptpkg.py | 29 +++++++++++++++++++++++++++
3 files changed, 72 insertions(+), 2 deletions(-)
diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py
index 1a60255a1d..023049b2af 100644
--- a/salt/modules/aptpkg.py
+++ b/salt/modules/aptpkg.py
@@ -18,6 +18,9 @@ import os
import re
import logging
import time
+import fnmatch
+import datetime
+
# Import third party libs
# pylint: disable=no-name-in-module,import-error,redefined-builtin
@@ -422,6 +425,7 @@ def install(name=None,
pkgs=None,
sources=None,
reinstall=False,
+ downloadonly=False,
ignore_epoch=False,
**kwargs):
'''
@@ -768,6 +772,9 @@ def install(name=None,
cmd.extend(downgrade)
cmds.append(cmd)
+ if downloadonly:
+ cmd.append("--download-only")
+
if to_reinstall:
all_pkgs.extend(to_reinstall)
cmd = copy.deepcopy(cmd_prefix)
@@ -2917,3 +2924,37 @@ def _get_http_proxy_url():
)
return http_proxy_url
+
+
+def list_downloaded(root=None, **kwargs):
+ '''
+ .. versionadded:: 3000?
+
+ List prefetched packages downloaded by apt in the local disk.
+
+ root
+ operate on a different root directory.
+
+ CLI example:
+
+ .. code-block:: bash
+
+ salt '*' pkg.list_downloaded
+ '''
+ CACHE_DIR = '/var/cache/apt'
+ if root:
+ CACHE_DIR = os.path.join(root, os.path.relpath(CACHE_DIR, os.path.sep))
+
+ ret = {}
+ for root, dirnames, filenames in salt.utils.path.os_walk(CACHE_DIR):
+ for filename in fnmatch.filter(filenames, '*.deb'):
+ package_path = os.path.join(root, filename)
+ pkg_info = __salt__['lowpkg.bin_pkg_info'](package_path)
+ pkg_timestamp = int(os.path.getctime(package_path))
+ ret.setdefault(pkg_info['name'], {})[pkg_info['version']] = {
+ 'path': package_path,
+ 'size': os.path.getsize(package_path),
+ 'creation_date_time_t': pkg_timestamp,
+ 'creation_date_time': datetime.datetime.utcfromtimestamp(pkg_timestamp).isoformat(),
+ }
+ return ret
diff --git a/salt/states/pkg.py b/salt/states/pkg.py
index 22a97fe98c..be00498135 100644
--- a/salt/states/pkg.py
+++ b/salt/states/pkg.py
@@ -1975,7 +1975,7 @@ def downloaded(name,
(if specified).
Currently supported for the following pkg providers:
- :mod:`yumpkg <salt.modules.yumpkg>` and :mod:`zypper <salt.modules.zypper>`
+ :mod:`yumpkg <salt.modules.yumpkg>`, :mod:`zypper <salt.modules.zypper>` and :mod:`zypper <salt.modules.aptpkg>`
:param str name:
The name of the package to be downloaded. This parameter is ignored if
@@ -2114,7 +2114,7 @@ def downloaded(name,
if not ret['changes'] and not ret['comment']:
ret['result'] = True
- ret['comment'] = 'Packages are already downloaded: ' \
+ ret['comment'] = 'Packages downloaded: ' \
'{0}'.format(', '.join(targets))
return ret
diff --git a/tests/unit/modules/test_aptpkg.py b/tests/unit/modules/test_aptpkg.py
index bc6b610d86..5c7e38eae7 100644
--- a/tests/unit/modules/test_aptpkg.py
+++ b/tests/unit/modules/test_aptpkg.py
@@ -413,14 +413,17 @@ class AptPkgTestCase(TestCase, LoaderModuleMockMixin):
with patch.multiple(aptpkg, **patch_kwargs):
aptpkg.upgrade()
args_matching = [True for args in patch_kwargs['__salt__']['cmd.run_all'].call_args[0] if "--download-only" in args]
+ # Here we shouldn't see the parameter and args_matching should be empty.
self.assertFalse(any(args_matching))
aptpkg.upgrade(downloadonly=True)
args_matching = [True for args in patch_kwargs['__salt__']['cmd.run_all'].call_args[0] if "--download-only" in args]
+ # --download-only should be in the args list and we should have at least on True in the list.
self.assertTrue(any(args_matching))
aptpkg.upgrade(download_only=True)
args_matching = [True for args in patch_kwargs['__salt__']['cmd.run_all'].call_args[0] if "--download-only" in args]
+ # --download-only should be in the args list and we should have at least on True in the list.
self.assertTrue(any(args_matching))
def test_show(self):
@@ -545,6 +548,32 @@ class AptPkgTestCase(TestCase, LoaderModuleMockMixin):
self.assert_called_once(refresh_mock)
refresh_mock.reset_mock()
+ @patch('salt.utils.path.os_walk', MagicMock(return_value=[('test', 'test', 'test')]))
+ @patch('os.path.getsize', MagicMock(return_value=123456))
+ @patch('os.path.getctime', MagicMock(return_value=1234567890.123456))
+ @patch('fnmatch.filter', MagicMock(return_value=['/var/cache/apt/archive/test_package.rpm']))
+ def test_list_downloaded(self):
+ '''
+ Test downloaded packages listing.
+ :return:
+ '''
+ DOWNLOADED_RET = {
+ 'test-package': {
+ '1.0': {
+ 'path': '/var/cache/apt/archive/test_package.rpm',
+ 'size': 123456,
+ 'creation_date_time_t': 1234567890,
+ 'creation_date_time': '2009-02-13T23:31:30',
+ }
+ }
+ }
+
+ with patch.dict(aptpkg.__salt__, {'lowpkg.bin_pkg_info': MagicMock(return_value={'name': 'test-package',
+ 'version': '1.0'})}):
+ list_downloaded = aptpkg.list_downloaded()
+ self.assertEqual(len(list_downloaded), 1)
+ self.assertDictEqual(list_downloaded, DOWNLOADED_RET)
+
@skipIf(pytest is None, 'PyTest is missing')
class AptUtilsTestCase(TestCase, LoaderModuleMockMixin):
--
2.16.4

View File

@ -1,3 +1,43 @@
-------------------------------------------------------------------
Mon Jan 13 16:09:36 UTC 2020 - Jochen Breuer <jbreuer@suse.de>
- Support for Btrfs and XFS in parted and mkfs added
- Added:
* support-for-btrfs-and-xfs-in-parted-and-mkfs.patch
-------------------------------------------------------------------
Thu Jan 9 19:20:34 UTC 2020 - Jochen Breuer <jbreuer@suse.de>
- Adds list_downloaded for apt Module to enable pre-downloading support
- Adds virt.(pool|network)_get_xml functions
- Various libvirt updates
* Add virt.pool_capabilities function
* virt.pool_running improvements
* Add virt.pool_deleted state
* virt.network_define allow adding IP configuration
- Added:
* virt.network_define-allow-adding-ip-configuration.patch
* list_downloaded-for-apt-module.patch
* add-virt.network_get_xml-function.patch
-------------------------------------------------------------------
Tue Jan 7 10:28:04 UTC 2020 - Pablo Suárez Hernández <pablo.suarezhernandez@suse.com>
- virt: adding kernel boot parameters to libvirt xml
- Added:
* virt-adding-kernel-boot-parameters-to-libvirt-xml-55.patch
-------------------------------------------------------------------
Mon Dec 16 10:36:42 UTC 2019 - Pablo Suárez Hernández <pablo.suarezhernandez@suse.com>
- Fix virt states to not fail on VMs already stopped
- Added:
* fix-virt-states-to-not-fail-on-vms-already-stopped.-.patch
-------------------------------------------------------------------
Thu Dec 12 10:21:15 UTC 2019 - Pablo Suárez Hernández <pablo.suarezhernandez@suse.com>

View File

@ -272,6 +272,22 @@ Patch96: align-virt-full-info-fixes-with-upstream-192.patch
Patch97: fix-virt.get_hypervisor-188.patch
# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/193
Patch98: xfs-do-not-fails-if-type-is-not-present.patch
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/54196
# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/195
Patch99: fix-virt-states-to-not-fail-on-vms-already-stopped.-.patch
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/55245
Patch100: virt-adding-kernel-boot-parameters-to-libvirt-xml-55.patch
# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/189
# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/185
# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/184
# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/183
Patch101: virt.network_define-allow-adding-ip-configuration.patch
# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/196
Patch102: add-virt.network_get_xml-function.patch
# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/198
Patch103: list_downloaded-for-apt-module.patch
# PATCH_FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/200
Patch104: support-for-btrfs-and-xfs-in-parted-and-mkfs.patch
BuildRoot: %{_tmppath}/%{name}-%{version}-build
BuildRequires: logrotate
@ -886,6 +902,12 @@ cp %{S:5} ./.travis.yml
%patch96 -p1
%patch97 -p1
%patch98 -p1
%patch99 -p1
%patch100 -p1
%patch101 -p1
%patch102 -p1
%patch103 -p1
%patch104 -p1
%build
%if 0%{?build_py2}

View File

@ -0,0 +1,56 @@
From 92df25d10789e5d0686a882a1cbadbfc0602b2f5 Mon Sep 17 00:00:00 2001
From: Jochen Breuer <jbreuer@suse.de>
Date: Fri, 10 Jan 2020 17:18:14 +0100
Subject: [PATCH] Support for Btrfs and XFS in parted and mkfs
---
salt/modules/parted_partition.py | 4 ++--
tests/unit/modules/test_parted_partition.py | 16 ++++++++++++++++
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/salt/modules/parted_partition.py b/salt/modules/parted_partition.py
index c2e0ebb882..e68124c245 100644
--- a/salt/modules/parted_partition.py
+++ b/salt/modules/parted_partition.py
@@ -390,8 +390,8 @@ def _is_fstype(fs_type):
:param fs_type: file system type
:return: True if fs_type is supported in this module, False otherwise
'''
- return fs_type in set(['ext2', 'ext3', 'ext4', 'fat32', 'fat16', 'linux-swap', 'reiserfs',
- 'hfs', 'hfs+', 'hfsx', 'NTFS', 'ntfs', 'ufs'])
+ return fs_type in set(['btrfs', 'ext2', 'ext3', 'ext4', 'fat32', 'fat16', 'linux-swap', 'reiserfs',
+ 'hfs', 'hfs+', 'hfsx', 'NTFS', 'ntfs', 'ufs', 'xfs'])
def mkfs(device, fs_type):
diff --git a/tests/unit/modules/test_parted_partition.py b/tests/unit/modules/test_parted_partition.py
index 1959e5978e..5d92bd6d14 100644
--- a/tests/unit/modules/test_parted_partition.py
+++ b/tests/unit/modules/test_parted_partition.py
@@ -377,6 +377,22 @@ class PartedTestCase(TestCase, LoaderModuleMockMixin):
}
self.assertEqual(output, expected)
+ def test_btrfs_fstypes(self):
+ '''Tests if we see btrfs as valid fs type'''
+ with patch('salt.modules.parted_partition._validate_device', MagicMock()):
+ try:
+ parted.mkfs('/dev/foo', 'btrfs')
+ except CommandExecutionError:
+ self.fail("Btrfs is not in the supported fstypes")
+
+ def test_xfs_fstypes(self):
+ '''Tests if we see xfs as valid fs type'''
+ with patch('salt.modules.parted_partition._validate_device', MagicMock()):
+ try:
+ parted.mkfs('/dev/foo', 'xfs')
+ except CommandExecutionError:
+ self.fail("XFS is not in the supported fstypes")
+
def test_disk_set(self):
with patch('salt.modules.parted_partition._validate_device', MagicMock()):
self.cmdrun.return_value = ''
--
2.16.4

View File

@ -0,0 +1,583 @@
From 26a227868bcf1f790348e197e000561903f7fc72 Mon Sep 17 00:00:00 2001
From: Larry Dewey <ldewey@suse.com>
Date: Tue, 7 Jan 2020 02:48:11 -0700
Subject: [PATCH] virt: adding kernel boot parameters to libvirt xml
#55245 (#197)
* virt: adding kernel boot parameters to libvirt xml
SUSE's autoyast and Red Hat's kickstart take advantage of kernel paths,
initrd paths, and kernel boot command line parameters. These changes
provide the option of using these, and will allow salt and
autoyast/kickstart to work together.
Signed-off-by: Larry Dewey <ldewey@suse.com>
* virt: Download linux and initrd
Signed-off-by: Larry Dewey <ldewey@suse.com>
---
salt/modules/virt.py | 129 ++++++++++++++++++++++-
salt/states/virt.py | 29 ++++-
salt/templates/virt/libvirt_domain.jinja | 12 ++-
salt/utils/virt.py | 45 +++++++-
tests/unit/modules/test_virt.py | 79 +++++++++++++-
tests/unit/states/test_virt.py | 19 +++-
6 files changed, 302 insertions(+), 11 deletions(-)
diff --git a/salt/modules/virt.py b/salt/modules/virt.py
index dedcf8cb6f..0f62856f5c 100644
--- a/salt/modules/virt.py
+++ b/salt/modules/virt.py
@@ -106,6 +106,8 @@ import salt.utils.templates
import salt.utils.validate.net
import salt.utils.versions
import salt.utils.yaml
+
+from salt.utils.virt import check_remote, download_remote
from salt.exceptions import CommandExecutionError, SaltInvocationError
from salt.ext import six
from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin
@@ -119,6 +121,8 @@ JINJA = jinja2.Environment(
)
)
+CACHE_DIR = '/var/lib/libvirt/saltinst'
+
VIRT_STATE_NAME_MAP = {0: 'running',
1: 'running',
2: 'running',
@@ -532,6 +536,7 @@ def _gen_xml(name,
os_type,
arch,
graphics=None,
+ boot=None,
**kwargs):
'''
Generate the XML string to define a libvirt VM
@@ -568,11 +573,15 @@ def _gen_xml(name,
else:
context['boot_dev'] = ['hd']
+ context['boot'] = boot if boot else {}
+
if os_type == 'xen':
# Compute the Xen PV boot method
if __grains__['os_family'] == 'Suse':
- context['kernel'] = '/usr/lib/grub2/x86_64-xen/grub.xen'
- context['boot_dev'] = []
+ if not boot or not boot.get('kernel', None):
+ context['boot']['kernel'] = \
+ '/usr/lib/grub2/x86_64-xen/grub.xen'
+ context['boot_dev'] = []
if 'serial_type' in kwargs:
context['serial_type'] = kwargs['serial_type']
@@ -1115,6 +1124,34 @@ def _get_merged_nics(hypervisor, profile, interfaces=None, dmac=None):
return nicp
+def _handle_remote_boot_params(orig_boot):
+ """
+ Checks if the boot parameters contain a remote path. If so, it will copy
+ the parameters, download the files specified in the remote path, and return
+ a new dictionary with updated paths containing the canonical path to the
+ kernel and/or initrd
+
+ :param orig_boot: The original boot parameters passed to the init or update
+ functions.
+ """
+ saltinst_dir = None
+ new_boot = orig_boot.copy()
+
+ try:
+ for key in ['kernel', 'initrd']:
+ if check_remote(orig_boot.get(key)):
+ if saltinst_dir is None:
+ os.makedirs(CACHE_DIR)
+ saltinst_dir = CACHE_DIR
+
+ new_boot[key] = download_remote(orig_boot.get(key),
+ saltinst_dir)
+
+ return new_boot
+ except Exception as err:
+ raise err
+
+
def init(name,
cpu,
mem,
@@ -1136,6 +1173,7 @@ def init(name,
graphics=None,
os_type=None,
arch=None,
+ boot=None,
**kwargs):
'''
Initialize a new vm
@@ -1266,6 +1304,22 @@ def init(name,
:param password: password to connect with, overriding defaults
.. versionadded:: 2019.2.0
+ :param boot:
+ Specifies kernel for the virtual machine, as well as boot parameters
+ for the virtual machine. This is an optionl parameter, and all of the
+ keys are optional within the dictionary. If a remote path is provided
+ to kernel or initrd, salt will handle the downloading of the specified
+ remote fild, and will modify the XML accordingly.
+
+ .. code-block:: python
+
+ {
+ 'kernel': '/root/f8-i386-vmlinuz',
+ 'initrd': '/root/f8-i386-initrd',
+ 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/'
+ }
+
+ .. versionadded:: neon
.. _init-nic-def:
@@ -1513,7 +1567,11 @@ def init(name,
if arch is None:
arch = 'x86_64' if 'x86_64' in arches else arches[0]
- vm_xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, os_type, arch, graphics, **kwargs)
+ if boot is not None:
+ boot = _handle_remote_boot_params(boot)
+
+ vm_xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, os_type, arch,
+ graphics, boot, **kwargs)
conn = __get_conn(**kwargs)
try:
conn.defineXML(vm_xml)
@@ -1692,6 +1750,7 @@ def update(name,
interfaces=None,
graphics=None,
live=True,
+ boot=None,
**kwargs):
'''
Update the definition of an existing domain.
@@ -1727,6 +1786,23 @@ def update(name,
:param username: username to connect with, overriding defaults
:param password: password to connect with, overriding defaults
+ :param boot:
+ Specifies kernel for the virtual machine, as well as boot parameters
+ for the virtual machine. This is an optionl parameter, and all of the
+ keys are optional within the dictionary. If a remote path is provided
+ to kernel or initrd, salt will handle the downloading of the specified
+ remote fild, and will modify the XML accordingly.
+
+ .. code-block:: python
+
+ {
+ 'kernel': '/root/f8-i386-vmlinuz',
+ 'initrd': '/root/f8-i386-initrd',
+ 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/'
+ }
+
+ .. versionadded:: neon
+
:return:
Returns a dictionary indicating the status of what has been done. It is structured in
@@ -1767,6 +1843,10 @@ def update(name,
# Compute the XML to get the disks, interfaces and graphics
hypervisor = desc.get('type')
all_disks = _disk_profile(disk_profile, hypervisor, disks, name, **kwargs)
+
+ if boot is not None:
+ boot = _handle_remote_boot_params(boot)
+
new_desc = ElementTree.fromstring(_gen_xml(name,
cpu,
mem,
@@ -1776,6 +1856,7 @@ def update(name,
domain.OSType(),
desc.find('.//os/type').get('arch'),
graphics,
+ boot,
**kwargs))
# Update the cpu
@@ -1785,6 +1866,48 @@ def update(name,
cpu_node.set('current', six.text_type(cpu))
need_update = True
+ # Update the kernel boot parameters
+ boot_tags = ['kernel', 'initrd', 'cmdline']
+ parent_tag = desc.find('os')
+
+ # We need to search for each possible subelement, and update it.
+ for tag in boot_tags:
+ # The Existing Tag...
+ found_tag = desc.find(tag)
+
+ # The new value
+ boot_tag_value = boot.get(tag, None) if boot else None
+
+ # Existing tag is found and values don't match
+ if found_tag and found_tag.text != boot_tag_value:
+
+ # If the existing tag is found, but the new value is None
+ # remove it. If the existing tag is found, and the new value
+ # doesn't match update it. In either case, mark for update.
+ if boot_tag_value is None \
+ and boot is not None \
+ and parent_tag is not None:
+ ElementTree.remove(parent_tag, tag)
+ else:
+ found_tag.text = boot_tag_value
+
+ need_update = True
+
+ # Existing tag is not found, but value is not None
+ elif found_tag is None and boot_tag_value is not None:
+
+ # Need to check for parent tag, and add it if it does not exist.
+ # Add a subelement and set the value to the new value, and then
+ # mark for update.
+ if parent_tag is not None:
+ child_tag = ElementTree.SubElement(parent_tag, tag)
+ else:
+ new_parent_tag = ElementTree.Element('os')
+ child_tag = ElementTree.SubElement(new_parent_tag, tag)
+
+ child_tag.text = boot_tag_value
+ need_update = True
+
# Update the memory, note that libvirt outputs all memory sizes in KiB
for mem_node_name in ['memory', 'currentMemory']:
mem_node = desc.find(mem_node_name)
diff --git a/salt/states/virt.py b/salt/states/virt.py
index 68e9ac6fb6..c700cae849 100644
--- a/salt/states/virt.py
+++ b/salt/states/virt.py
@@ -264,7 +264,8 @@ def running(name,
username=None,
password=None,
os_type=None,
- arch=None):
+ arch=None,
+ boot=None):
'''
Starts an existing guest, or defines and starts a new VM with specified arguments.
@@ -349,6 +350,23 @@ def running(name,
.. versionadded:: Neon
+ :param boot:
+ Specifies kernel for the virtual machine, as well as boot parameters
+ for the virtual machine. This is an optionl parameter, and all of the
+ keys are optional within the dictionary. If a remote path is provided
+ to kernel or initrd, salt will handle the downloading of the specified
+ remote fild, and will modify the XML accordingly.
+
+ .. code-block:: python
+
+ {
+ 'kernel': '/root/f8-i386-vmlinuz',
+ 'initrd': '/root/f8-i386-initrd',
+ 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/'
+ }
+
+ .. versionadded:: neon
+
.. rubric:: Example States
Make sure an already-defined virtual machine called ``domain_name`` is running:
@@ -413,7 +431,8 @@ def running(name,
live=False,
connection=connection,
username=username,
- password=password)
+ password=password,
+ boot=boot)
if status['definition']:
action_msg = 'updated and started'
__salt__['virt.start'](name)
@@ -431,7 +450,8 @@ def running(name,
graphics=graphics,
connection=connection,
username=username,
- password=password)
+ password=password,
+ boot=boot)
ret['changes'][name] = status
if status.get('errors', None):
ret['comment'] = 'Domain {0} updated, but some live update(s) failed'.format(name)
@@ -466,7 +486,8 @@ def running(name,
priv_key=priv_key,
connection=connection,
username=username,
- password=password)
+ password=password,
+ boot=boot)
ret['changes'][name] = 'Domain defined and started'
ret['comment'] = 'Domain {0} defined and started'.format(name)
except libvirt.libvirtError as err:
diff --git a/salt/templates/virt/libvirt_domain.jinja b/salt/templates/virt/libvirt_domain.jinja
index 0b4c3fc2d6..fdaea168f2 100644
--- a/salt/templates/virt/libvirt_domain.jinja
+++ b/salt/templates/virt/libvirt_domain.jinja
@@ -5,7 +5,17 @@
<currentMemory unit='KiB'>{{ mem }}</currentMemory>
<os>
<type arch='{{ arch }}'>{{ os_type }}</type>
- {% if kernel %}<kernel>{{ kernel }}</kernel>{% endif %}
+ {% if boot %}
+ {% if 'kernel' in boot %}
+ <kernel>{{ boot.kernel }}</kernel>
+ {% endif %}
+ {% if 'initrd' in boot %}
+ <initrd>{{ boot.initrd }}</initrd>
+ {% endif %}
+ {% if 'cmdline' in boot %}
+ <cmdline>{{ boot.cmdline }}</cmdline>
+ {% endif %}
+ {% endif %}
{% for dev in boot_dev %}
<boot dev='{{ dev }}' />
{% endfor %}
diff --git a/salt/utils/virt.py b/salt/utils/virt.py
index 9dad849c0e..b36adba81c 100644
--- a/salt/utils/virt.py
+++ b/salt/utils/virt.py
@@ -6,16 +6,59 @@ from __future__ import absolute_import, print_function, unicode_literals
# Import python libs
import os
+import re
import time
import logging
+import hashlib
+
+# pylint: disable=E0611
+from salt.ext.six.moves.urllib.parse import urlparse
+from salt.ext.six.moves.urllib import request
# Import salt libs
import salt.utils.files
-
log = logging.getLogger(__name__)
+def download_remote(url, dir):
+ """
+ Attempts to download a file specified by 'url'
+
+ :param url: The full remote path of the file which should be downloaded.
+ :param dir: The path the file should be downloaded to.
+ """
+
+ try:
+ rand = hashlib.md5(os.urandom(32)).hexdigest()
+ remote_filename = urlparse(url).path.split('/')[-1]
+ full_directory = \
+ os.path.join(dir, "{}-{}".format(rand, remote_filename))
+ with salt.utils.files.fopen(full_directory, 'wb') as file,\
+ request.urlopen(url) as response:
+ file.write(response.rease())
+
+ return full_directory
+
+ except Exception as err:
+ raise err
+
+
+def check_remote(cmdline_path):
+ """
+ Checks to see if the path provided contains ftp, http, or https. Returns
+ the full path if it is found.
+
+ :param cmdline_path: The path to the initrd image or the kernel
+ """
+ regex = re.compile('^(ht|f)tps?\\b')
+
+ if regex.match(urlparse(cmdline_path).scheme):
+ return True
+
+ return False
+
+
class VirtKey(object):
'''
Used to manage key signing requests.
diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py
index 6f594a8ff3..4bdb933a2d 100644
--- a/tests/unit/modules/test_virt.py
+++ b/tests/unit/modules/test_virt.py
@@ -10,6 +10,7 @@ from __future__ import absolute_import, print_function, unicode_literals
import os
import re
import datetime
+import shutil
# Import Salt Testing libs
from tests.support.mixins import LoaderModuleMockMixin
@@ -23,6 +24,7 @@ import salt.modules.config as config
from salt._compat import ElementTree as ET
import salt.config
import salt.syspaths
+import tempfile
from salt.exceptions import CommandExecutionError
# Import third party libs
@@ -30,7 +32,6 @@ from salt.ext import six
# pylint: disable=import-error
from salt.ext.six.moves import range # pylint: disable=redefined-builtin
-
# pylint: disable=invalid-name,protected-access,attribute-defined-outside-init,too-many-public-methods,unused-argument
@@ -610,6 +611,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
'xen',
'xen',
'x86_64',
+ boot=None
)
root = ET.fromstring(xml_data)
self.assertEqual(root.attrib['type'], 'xen')
@@ -1123,6 +1125,67 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
self.assertFalse('<interface' in definition)
self.assertFalse('<disk' in definition)
+ # Ensure the init() function allows creating VM without NIC and
+ # disk but with boot parameters.
+
+ defineMock.reset_mock()
+ mock_run.reset_mock()
+ boot = {
+ 'kernel': '/root/f8-i386-vmlinuz',
+ 'initrd': '/root/f8-i386-initrd',
+ 'cmdline':
+ 'console=ttyS0 ks=http://example.com/f8-i386/os/'
+ }
+ retval = virt.init('test vm boot params',
+ 2,
+ 1234,
+ nic=None,
+ disk=None,
+ seed=False,
+ start=False,
+ boot=boot)
+ definition = defineMock.call_args_list[0][0][0]
+ self.assertEqual('<kernel' in definition, True)
+ self.assertEqual('<initrd' in definition, True)
+ self.assertEqual('<cmdline' in definition, True)
+ self.assertEqual(retval, True)
+
+ # Verify that remote paths are downloaded and the xml has been
+ # modified
+ mock_response = MagicMock()
+ mock_response.read = MagicMock(return_value='filecontent')
+ cache_dir = tempfile.mkdtemp()
+
+ with patch.dict(virt.__dict__, {'CACHE_DIR': cache_dir}):
+ with patch('salt.ext.six.moves.urllib.request.urlopen',
+ MagicMock(return_value=mock_response)):
+ with patch('salt.utils.files.fopen',
+ return_value=mock_response):
+
+ defineMock.reset_mock()
+ mock_run.reset_mock()
+ boot = {
+ 'kernel':
+ 'https://www.example.com/download/vmlinuz',
+ 'initrd': '',
+ 'cmdline':
+ 'console=ttyS0 '
+ 'ks=http://example.com/f8-i386/os/'
+ }
+
+ retval = virt.init('test remote vm boot params',
+ 2,
+ 1234,
+ nic=None,
+ disk=None,
+ seed=False,
+ start=False,
+ boot=boot)
+ definition = defineMock.call_args_list[0][0][0]
+ self.assertEqual(cache_dir in definition, True)
+
+ shutil.rmtree(cache_dir)
+
# Test case creating disks
defineMock.reset_mock()
mock_run.reset_mock()
@@ -1222,6 +1285,20 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
self.assertEqual(setxml.find('vcpu').text, '2')
self.assertEqual(setvcpus_mock.call_args[0][0], 2)
+ boot = {
+ 'kernel': '/root/f8-i386-vmlinuz',
+ 'initrd': '/root/f8-i386-initrd',
+ 'cmdline':
+ 'console=ttyS0 ks=http://example.com/f8-i386/os/'
+ }
+
+ # Update with boot parameter case
+ self.assertEqual({
+ 'definition': True,
+ 'disk': {'attached': [], 'detached': []},
+ 'interface': {'attached': [], 'detached': []}
+ }, virt.update('my vm', boot=boot))
+
# Update memory case
setmem_mock = MagicMock(return_value=0)
domain_mock.setMemoryFlags = setmem_mock
diff --git a/tests/unit/states/test_virt.py b/tests/unit/states/test_virt.py
index 2af5caca1b..109faf5fba 100644
--- a/tests/unit/states/test_virt.py
+++ b/tests/unit/states/test_virt.py
@@ -249,7 +249,7 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
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,
+ os_type=None, arch=None, boot=None,
disk=None, disks=None, nic=None, interfaces=None,
graphics=None, hypervisor=None,
seed=True, install=True, pub_key=None, priv_key=None,
@@ -314,6 +314,7 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
graphics=graphics,
hypervisor='qemu',
seed=False,
+ boot=None,
install=False,
pub_key='/path/to/key.pub',
priv_key='/path/to/key',
@@ -338,6 +339,22 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
'comment': 'Domain myvm updated, restart to fully apply the changes'})
self.assertDictEqual(virt.running('myvm', update=True, cpu=2), ret)
+ # Working update case when running with boot params
+ boot = {
+ 'kernel': '/root/f8-i386-vmlinuz',
+ 'initrd': '/root/f8-i386-initrd',
+ 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/'
+ }
+
+ with patch.dict(virt.__salt__, { # pylint: disable=no-member
+ 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}),
+ 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True})
+ }):
+ ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}},
+ 'result': True,
+ 'comment': 'Domain myvm updated, restart to fully apply the changes'})
+ self.assertDictEqual(virt.running('myvm', update=True, boot=boot), ret)
+
# Working update case when stopped
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}),
--
2.23.0

File diff suppressed because it is too large Load Diff