Add state modules to manage Linux kernel packages

This commit is contained in:
Adam Mendlik 2017-04-30 08:26:44 -06:00
parent e2524656c4
commit fd09ae7f1a
2 changed files with 371 additions and 0 deletions

213
salt/states/kernelpkg.py Normal file
View File

@ -0,0 +1,213 @@
# -*- coding: utf-8 -*-
'''
Manage kernel packages and active kernel version
=========================================================================
Example state to install the latest kernel from package repositories:
.. code-block:: yaml
install-latest-kernel:
kernel.latest_installed: []
Example state to boot the system if a new kernel has been installed:
.. code-block:: yaml
boot-latest-kernel:
kernel.latest_active:
- at_time: 1
Example state chaining the install and reboot operations:
.. code-block:: yaml
install-latest-kernel:
kernel.latest_installed: []
boot-latest-kernel:
kernel.latest_active:
- at_time: 1
- onchanges:
- kernel: install-latest-kernel
Chaining can also be acheived using wait/listen requisites:
.. code-block:: yaml
install-latest-kernel:
kernel.latest_installed: []
boot-latest-kernel:
kernel.latest_wait:
- at_time: 1
- listen:
- kernel: install-latest-kernel
'''
from __future__ import absolute_import
import logging
log = logging.getLogger(__name__)
def __virtual__():
'''
Only make these states available if a pkg provider has been detected or
assigned for this minion
'''
return 'kernelpkg.upgrade' in __salt__
def latest_installed(name, **kwargs): # pylint: disable=unused-argument
'''
Ensure that the latest version of the kernel available in the
repositories is installed.
.. note::
This state only installs the kernel, but does not activate it.
The new kernel should become active at the next reboot.
See :mod:`kernelpkg.needs_reboot <salt.modules.kernelpkg.needs_reboot>` for details on
how to detect this condition, :mod:`kernelpkg.latest_active <salt.states.kernelpkg.latest_active>`
to initiale a reboot when needed.
name
Arbitrary name for the state. Does not affect behavior.
'''
installed = __salt__['kernelpkg.list_installed']()
upgrade = __salt__['kernelpkg.latest_available']()
ret = {'name': name}
if upgrade in installed:
ret['result'] = True
ret['comment'] = ('The latest kernel package is already installed: '
'{0}').format(upgrade)
ret['changes'] = {}
else:
if __opts__['test']:
ret['result'] = None
ret['changes'] = {}
ret['comment'] = ('The latest kernel package will be installed: '
'{0}').format(upgrade)
else:
result = __salt__['kernelpkg.upgrade']()
ret['result'] = True
ret['changes'] = result['upgrades']
ret['comment'] = ('The latest kernel package has been installed, '
'but not activated.')
return ret
def latest_active(name, at_time=None, **kwargs): # pylint: disable=unused-argument
'''
Initiate a reboot if the running kernel is not the latest one installed.
.. note::
This state does not install any patches. It only compares the running
kernel version number to other kernel versions also installed in the
system. If the running version is not the latest one installed, this
state will reboot the system.
See :mod:`kernelpkg.upgrade <salt.modules.kernelpkg.upgrade>` and
:mod:`kernelpkg.latest_installed <salt.states.kernelpkg.latest_installed>` for ways to install new kernel packages.
This module does not attempt to understand or manage boot loader configurations
it is possible to have a new kernel installed, but a boot loader configuration
that will never activate it. For this reason, it would not be advisable to
schedule this state to run automatically.
Because this state function may cause the system to reboot, it may be preferable
to move it to the very end of the state run. See :mod:`kernelpkg.latest_wait <salt.states.kernelpkg.latest_wait>`
for a waitable state that can be called with the `listen` requesite.
name
Arbitrary name for the state. Does not affect behavior.
at_time
The wait time in minutes before the system will be rebooted.
'''
current = __salt__['kernelpkg.current']()
latest = __salt__['kernelpkg.latest_installed']()
ret = {'name': name}
if __salt__['kernelpkg.needs_reboot']():
ret['comment'] = ('The system will be booted to activate '
'kernel: {0}').format(latest)
if __opts__['test']:
ret['result'] = None
ret['changes'] = {}
ret['pchanges'] = {'kernel': {
'old': current,
'new': latest
}}
else:
__salt__['system.reboot'](at_time=at_time)
ret['result'] = True
ret['changes'] = {'kernel': {
'old': current,
'new': latest
}}
else:
ret['result'] = True
ret['comment'] = ('The latest installed kernel package '
'is active: {0}').format(current)
ret['changes'] = {}
return ret
def latest_wait(name, at_time=None, **kwargs): # pylint: disable=unused-argument
'''
Initiate a reboot if the running kernel is not the latest one installed. This is the
waitable version of :mod:`kernelpkg.latest_active <salt.states.kernelpkg.latest_active>` and
will not take any action unless triggered by a watch or listen requesite.
.. note::
Because this state function may cause the system to reboot, it may be preferable
to move it to the very end of the state run using `listen` or `listen_in` requisites.
.. code-block:: yaml
system-up-to-date:
pkg.uptodate:
- refresh: true
boot-latest-kernel:
kernelpkg.latest_wait:
- at_time: 1
- listen:
- pkg: system-up-to-date
name
Arbitrary name for the state. Does not affect behavior.
at_time
The wait time in minutes before the system will be rebooted.
'''
return {'name': name,
'changes': {},
'result': True,
'comment': ''}
def mod_watch(name, sfun, **kwargs):
'''
Execute a kernelpkg state based on a watch or listen call
'''
if sfun in ('latest_active', 'latest_wait'):
return latest_active(name, **kwargs)
else:
return {'name': name, 'changes': {},
'comment': 'kernelpkg.{0} does not work with the watch '
'requisite.'.format(sfun),
'result': False}

View File

@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
'''
:synopsis: Unit Tests for 'module.aptkernelpkg'
:platform: Linux
:maturity: develop
versionadded:: oxygen
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import skipIf, TestCase
from tests.support.mock import (
NO_MOCK,
NO_MOCK_REASON,
MagicMock,
patch)
# Import Salt Libs
import salt.states.kernelpkg as kernelpkg
KERNEL_LIST = ['4.4.0-70-generic', '4.4.0-71-generic', '4.5.1-14-generic']
STATE_NAME = 'kernelpkg-test'
@skipIf(NO_MOCK, NO_MOCK_REASON)
class KernelPkgTestCase(TestCase, LoaderModuleMockMixin):
'''
Test cases for salt.states.aptpkg
'''
def setup_loader_modules(self):
return {
kernelpkg: {
'__salt__': {
'system.reboot': MagicMock(return_value=None),
'kernelpkg.upgrade': MagicMock(return_value={
'upgrades': {
'kernel': {
'old': '1.0.0',
'new': '2.0.0',
}
}
}),
'kernelpkg.current': MagicMock(return_value=0),
'kernelpkg.latest_installed': MagicMock(return_value=0)
}
}
}
def test_latest_installed_with_changes(self):
'''
Test - latest_installed when an upgrade is available
'''
installed = MagicMock(return_value=KERNEL_LIST[:-1])
upgrade = MagicMock(return_value=KERNEL_LIST[-1])
with patch.dict(kernelpkg.__salt__, {'kernelpkg.list_installed': installed}):
with patch.dict(kernelpkg.__salt__, {'kernelpkg.latest_available': upgrade}):
with patch.dict(kernelpkg.__opts__, {'test': False}):
kernelpkg.__salt__['kernelpkg.upgrade'].reset_mock()
ret = kernelpkg.latest_installed(name=STATE_NAME)
self.assertEqual(ret['name'], STATE_NAME)
self.assertTrue(ret['result'])
self.assertIsInstance(ret['changes'], dict)
self.assertIsInstance(ret['comment'], str)
kernelpkg.__salt__['kernelpkg.upgrade'].assert_called_once()
with patch.dict(kernelpkg.__opts__, {'test': True}):
kernelpkg.__salt__['kernelpkg.upgrade'].reset_mock()
ret = kernelpkg.latest_installed(name=STATE_NAME)
self.assertEqual(ret['name'], STATE_NAME)
self.assertIsNone(ret['result'])
self.assertDictEqual(ret['changes'], {})
self.assertIsInstance(ret['comment'], str)
kernelpkg.__salt__['kernelpkg.upgrade'].assert_not_called()
def test_latest_installed_at_latest(self):
'''
Test - latest_installed when no upgrade is available
'''
installed = MagicMock(return_value=KERNEL_LIST)
upgrade = MagicMock(return_value=KERNEL_LIST[-1])
with patch.dict(kernelpkg.__salt__, {'kernelpkg.list_installed': installed}):
with patch.dict(kernelpkg.__salt__, {'kernelpkg.latest_available': upgrade}):
with patch.dict(kernelpkg.__opts__, {'test': False}):
ret = kernelpkg.latest_installed(name=STATE_NAME)
self.assertEqual(ret['name'], STATE_NAME)
self.assertTrue(ret['result'])
self.assertDictEqual(ret['changes'], {})
self.assertIsInstance(ret['comment'], str)
kernelpkg.__salt__['kernelpkg.upgrade'].assert_not_called()
with patch.dict(kernelpkg.__opts__, {'test': True}):
ret = kernelpkg.latest_installed(name=STATE_NAME)
self.assertEqual(ret['name'], STATE_NAME)
self.assertTrue(ret['result'])
self.assertDictEqual(ret['changes'], {})
self.assertIsInstance(ret['comment'], str)
kernelpkg.__salt__['kernelpkg.upgrade'].assert_not_called()
def test_latest_active_with_changes(self):
'''
Test - latest_active when a new kernel is available
'''
reboot = MagicMock(return_value=True)
with patch.dict(kernelpkg.__salt__, {'kernelpkg.needs_reboot': reboot}):
with patch.dict(kernelpkg.__opts__, {'test': False}):
kernelpkg.__salt__['system.reboot'].reset_mock()
ret = kernelpkg.latest_active(name=STATE_NAME)
self.assertEqual(ret['name'], STATE_NAME)
self.assertTrue(ret['result'])
self.assertIsInstance(ret['changes'], dict)
self.assertIsInstance(ret['comment'], str)
kernelpkg.__salt__['system.reboot'].assert_called_once()
with patch.dict(kernelpkg.__opts__, {'test': True}):
kernelpkg.__salt__['system.reboot'].reset_mock()
ret = kernelpkg.latest_active(name=STATE_NAME)
self.assertEqual(ret['name'], STATE_NAME)
self.assertIsNone(ret['result'])
self.assertDictEqual(ret['changes'], {})
self.assertIsInstance(ret['comment'], str)
kernelpkg.__salt__['system.reboot'].assert_not_called()
def test_latest_active_at_latest(self):
'''
Test - latest_active when the newest kernel is already active
'''
reboot = MagicMock(return_value=False)
with patch.dict(kernelpkg.__salt__, {'kernelpkg.needs_reboot': reboot}):
with patch.dict(kernelpkg.__opts__, {'test': False}):
kernelpkg.__salt__['system.reboot'].reset_mock()
ret = kernelpkg.latest_active(name=STATE_NAME)
self.assertEqual(ret['name'], STATE_NAME)
self.assertTrue(ret['result'])
self.assertDictEqual(ret['changes'], {})
self.assertIsInstance(ret['comment'], str)
kernelpkg.__salt__['system.reboot'].assert_not_called()
with patch.dict(kernelpkg.__opts__, {'test': True}):
kernelpkg.__salt__['system.reboot'].reset_mock()
ret = kernelpkg.latest_active(name=STATE_NAME)
self.assertEqual(ret['name'], STATE_NAME)
self.assertTrue(ret['result'])
self.assertDictEqual(ret['changes'], {})
self.assertIsInstance(ret['comment'], str)
kernelpkg.__salt__['system.reboot'].assert_not_called()
def test_latest_wait(self):
'''
Test - latest_wait static results
'''
ret = kernelpkg.latest_wait(name=STATE_NAME)
self.assertEqual(ret['name'], STATE_NAME)
self.assertTrue(ret['result'])
self.assertDictEqual(ret['changes'], {})
self.assertIsInstance(ret['comment'], str)