diff --git a/salt/modules/kernelpkg_linux_yum.py b/salt/modules/kernelpkg_linux_yum.py index b0fabe516b..4ddddc2c18 100644 --- a/salt/modules/kernelpkg_linux_yum.py +++ b/salt/modules/kernelpkg_linux_yum.py @@ -6,11 +6,13 @@ from __future__ import absolute_import import functools import logging -# Import Salt libs -from salt.ext import six - try: + # Import Salt libs + from salt.ext import six from salt.utils.versions import LooseVersion as _LooseVersion + from salt.exceptions import CommandExecutionError + import salt.utils.systemd + import salt.modules.yumpkg HAS_REQUIRED_LIBS = True except ImportError: HAS_REQUIRED_LIBS = False @@ -20,6 +22,9 @@ log = logging.getLogger(__name__) # Define the module's virtual name __virtualname__ = 'kernelpkg' +# Import functions from yumpkg +_yum = salt.utils.namespaced_function(salt.modules.yumpkg._yum, globals()) + def __virtual__(): ''' @@ -186,6 +191,54 @@ def upgrade_available(): return _LooseVersion(latest_available()) > _LooseVersion(latest_installed()) +def remove(release): + ''' + Remove a specific version of the kernel. + + release + The release number of an installed kernel. This must be the entire release + number as returned by :py:func:`~salt.modules.kernelpkg.list_installed`, + not the package name. + ''' + if release not in list_installed(): + raise CommandExecutionError('Kernel release \'{0}\' is not installed'.format(release)) + + if release == active(): + raise CommandExecutionError('Active kernel cannot be removed') + + target = '{0}-{1}'.format(_package_name(), release) + log.info('Removing kernel package {0}'.format(target)) + old = __salt__['pkg.list_pkgs']() + + # Build the command string + cmd = [] + if salt.utils.systemd.has_scope(__context__) \ + and __salt__['config.get']('systemd.scope', True): + cmd.extend(['systemd-run', '--scope']) + cmd.extend([_yum(), '-y', 'remove', target]) + + # Execute the command + out = __salt__['cmd.run_all']( + cmd, + output_loglevel='trace', + python_shell=False + ) + + # Look for the changes in installed packages + __context__.pop('pkg.list_pkgs', None) + new = __salt__['pkg.list_pkgs']() + ret = salt.utils.compare_dicts(old, new) + + # Look for command execution errors + if out['retcode'] != 0: + raise CommandExecutionError( + 'Error occurred removing package(s)', + info={'errors': [out['stderr']], 'changes': ret} + ) + + return {'removed': [target]} + + def _package_name(): ''' Return static string for the package name diff --git a/tests/unit/modules/test_kernelpkg.py b/tests/unit/modules/test_kernelpkg.py index 1dc2331582..2d30926096 100644 --- a/tests/unit/modules/test_kernelpkg.py +++ b/tests/unit/modules/test_kernelpkg.py @@ -10,6 +10,7 @@ from __future__ import absolute_import # Salt testing libs try: from tests.support.mock import MagicMock, patch + from salt.exceptions import CommandExecutionError except ImportError: pass @@ -171,3 +172,25 @@ class KernelPkgTestCase(object): with patch.object(self._kernelpkg, 'latest_available', return_value=self.KERNEL_LIST[0]): with patch.object(self._kernelpkg, 'latest_installed', return_value=self.KERNEL_LIST[-1]): self.assertFalse(self._kernelpkg.upgrade_available()) + + def test_remove_active(self): + ''' + Test - remove kernel package + ''' + mock = MagicMock(return_value={'retcode': 0, 'stderr': []}) + with patch.dict(self._kernelpkg.__salt__, {'cmd.run_all': mock}): + with patch.object(self._kernelpkg, 'active', return_value=self.KERNEL_LIST[-1]): + with patch.object(self._kernelpkg, 'list_installed', return_value=self.KERNEL_LIST): + self.assertRaises(CommandExecutionError, self._kernelpkg.remove, release=self.KERNEL_LIST[-1]) + self._kernelpkg.__salt__['cmd.run_all'].assert_not_called() + + def test_remove_invalid(self): + ''' + Test - remove kernel package + ''' + mock = MagicMock(return_value={'retcode': 0, 'stderr': []}) + with patch.dict(self._kernelpkg.__salt__, {'cmd.run_all': mock}): + with patch.object(self._kernelpkg, 'active', return_value=self.KERNEL_LIST[-1]): + with patch.object(self._kernelpkg, 'list_installed', return_value=self.KERNEL_LIST): + self.assertRaises(CommandExecutionError, self._kernelpkg.remove, release='invalid') + self._kernelpkg.__salt__['cmd.run_all'].assert_not_called() diff --git a/tests/unit/modules/test_kernelpkg_linux_yum.py b/tests/unit/modules/test_kernelpkg_linux_yum.py index 9674c049bf..adc8f79431 100644 --- a/tests/unit/modules/test_kernelpkg_linux_yum.py +++ b/tests/unit/modules/test_kernelpkg_linux_yum.py @@ -19,6 +19,7 @@ try: from tests.unit.modules.test_kernelpkg import KernelPkgTestCase import salt.modules.kernelpkg_linux_yum as kernelpkg import salt.modules.yumpkg as pkg + from salt.exceptions import CommandExecutionError HAS_MODULES = True except ImportError: HAS_MODULES = False @@ -32,18 +33,22 @@ class YumKernelPkgTestCase(KernelPkgTestCase, TestCase, LoaderModuleMockMixin): KERNEL_LIST = ['3.10.0-327.el7', '3.11.0-327.el7', '4.9.1-100.el7'] LATEST = KERNEL_LIST[-1] OS_ARCH = 'x86_64' + OS_NAME = 'RedHat' def setup_loader_modules(self): return { kernelpkg: { '__grains__': { + 'os': self.OS_NAME, 'kernelrelease': '{0}.{1}'.format(self.KERNEL_LIST[0], self.OS_ARCH) }, '__salt__': { 'pkg.normalize_name': pkg.normalize_name, 'pkg.upgrade': MagicMock(return_value={}), + 'pkg.list_pkgs': MagicMock(return_value={}), 'pkg.version': MagicMock(return_value=self.KERNEL_LIST), - 'system.reboot': MagicMock(return_value=None) + 'system.reboot': MagicMock(return_value=None), + 'config.get': MagicMock(return_value=True) } }, pkg: { @@ -68,3 +73,28 @@ class YumKernelPkgTestCase(KernelPkgTestCase, TestCase, LoaderModuleMockMixin): mock = MagicMock(return_value=None) with patch.dict(self._kernelpkg.__salt__, {'pkg.version': mock}): self.assertListEqual(self._kernelpkg.list_installed(), []) + + def test_remove_success(self): + ''' + Test - remove kernel package + ''' + mock = MagicMock(return_value={'retcode': 0, 'stderr': []}) + with patch.dict(self._kernelpkg.__salt__, {'cmd.run_all': mock}): + with patch.object(self._kernelpkg, 'active', return_value=self.KERNEL_LIST[-1]): + with patch.object(self._kernelpkg, 'list_installed', return_value=self.KERNEL_LIST): + result = self._kernelpkg.remove(release=self.KERNEL_LIST[0]) + self._kernelpkg.__salt__['cmd.run_all'].assert_called_once() + self.assertIn('removed', result) + target = '{0}-{1}'.format(self._kernelpkg._package_name(), self.KERNEL_LIST[0]) + self.assertListEqual(result['removed'], [target]) + + def test_remove_error(self): + ''' + Test - remove kernel package + ''' + mock = MagicMock(return_value={'retcode': -1, 'stderr': []}) + with patch.dict(self._kernelpkg.__salt__, {'cmd.run_all': mock}): + with patch.object(self._kernelpkg, 'active', return_value=self.KERNEL_LIST[-1]): + with patch.object(self._kernelpkg, 'list_installed', return_value=self.KERNEL_LIST): + self.assertRaises(CommandExecutionError, self._kernelpkg.remove, release=self.KERNEL_LIST[0]) + self._kernelpkg.__salt__['cmd.run_all'].assert_called_once()