mirror of
https://github.com/valitydev/salt.git
synced 2024-11-09 01:36:48 +00:00
574 lines
24 KiB
Python
574 lines
24 KiB
Python
# -*- coding: utf-8 -*-
|
|
'''
|
|
:codeauthor: :email:`Rahul Handay <rahulha@saltstack.com>`
|
|
'''
|
|
|
|
# Import Python libs
|
|
from __future__ import absolute_import
|
|
import os
|
|
|
|
# Import Salt Testing Libs
|
|
from tests.support.mixins import LoaderModuleMockMixin
|
|
from tests.support.unit import TestCase, skipIf
|
|
from tests.support.mock import (
|
|
MagicMock,
|
|
patch,
|
|
NO_MOCK,
|
|
NO_MOCK_REASON
|
|
)
|
|
|
|
# Import Salt Libs
|
|
import salt.modules.systemd as systemd
|
|
import salt.utils.systemd
|
|
from salt.exceptions import CommandExecutionError
|
|
|
|
_SYSTEMCTL_STATUS = {
|
|
'sshd.service': {
|
|
'stdout': '''\
|
|
* sshd.service - OpenSSH Daemon
|
|
Loaded: loaded (/usr/lib/systemd/system/sshd.service; disabled; vendor preset: disabled)
|
|
Active: inactive (dead)''',
|
|
'stderr': '',
|
|
'retcode': 3,
|
|
'pid': 12345,
|
|
},
|
|
|
|
'foo.service': {
|
|
'stdout': '''\
|
|
* foo.service
|
|
Loaded: not-found (Reason: No such file or directory)
|
|
Active: inactive (dead)''',
|
|
'stderr': '',
|
|
'retcode': 3,
|
|
'pid': 12345,
|
|
},
|
|
}
|
|
|
|
# This reflects systemd >= 231 behavior
|
|
_SYSTEMCTL_STATUS_GTE_231 = {
|
|
'bar.service': {
|
|
'stdout': 'Unit bar.service could not be found.',
|
|
'stderr': '',
|
|
'retcode': 4,
|
|
'pid': 12345,
|
|
},
|
|
}
|
|
|
|
_LIST_UNIT_FILES = '''\
|
|
service1.service enabled
|
|
service2.service disabled
|
|
service3.service static
|
|
timer1.timer enabled
|
|
timer2.timer disabled
|
|
timer3.timer static'''
|
|
|
|
|
|
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
|
class SystemdTestCase(TestCase, LoaderModuleMockMixin):
|
|
'''
|
|
Test case for salt.modules.systemd
|
|
'''
|
|
def setup_loader_modules(self):
|
|
return {systemd: {}}
|
|
|
|
def test_systemctl_reload(self):
|
|
'''
|
|
Test to Reloads systemctl
|
|
'''
|
|
mock = MagicMock(side_effect=[
|
|
{'stdout': 'Who knows why?',
|
|
'stderr': '',
|
|
'retcode': 1,
|
|
'pid': 12345},
|
|
{'stdout': '',
|
|
'stderr': '',
|
|
'retcode': 0,
|
|
'pid': 54321},
|
|
])
|
|
with patch.dict(systemd.__salt__, {'cmd.run_all': mock}):
|
|
self.assertRaisesRegex(
|
|
CommandExecutionError,
|
|
'Problem performing systemctl daemon-reload: Who knows why?',
|
|
systemd.systemctl_reload
|
|
)
|
|
self.assertTrue(systemd.systemctl_reload())
|
|
|
|
def test_get_enabled(self):
|
|
'''
|
|
Test to return a list of all enabled services
|
|
'''
|
|
cmd_mock = MagicMock(return_value=_LIST_UNIT_FILES)
|
|
listdir_mock = MagicMock(return_value=['foo', 'bar', 'baz', 'README'])
|
|
sd_mock = MagicMock(
|
|
return_value=set(
|
|
[x.replace('.service', '') for x in _SYSTEMCTL_STATUS]
|
|
)
|
|
)
|
|
access_mock = MagicMock(
|
|
side_effect=lambda x, y: x != os.path.join(
|
|
systemd.INITSCRIPT_PATH,
|
|
'README'
|
|
)
|
|
)
|
|
sysv_enabled_mock = MagicMock(side_effect=lambda x: x == 'baz')
|
|
|
|
with patch.dict(systemd.__salt__, {'cmd.run': cmd_mock}):
|
|
with patch.object(os, 'listdir', listdir_mock):
|
|
with patch.object(systemd, '_get_systemd_services', sd_mock):
|
|
with patch.object(os, 'access', side_effect=access_mock):
|
|
with patch.object(systemd, '_sysv_enabled',
|
|
sysv_enabled_mock):
|
|
self.assertListEqual(
|
|
systemd.get_enabled(),
|
|
['baz', 'service1', 'timer1.timer']
|
|
)
|
|
|
|
def test_get_disabled(self):
|
|
'''
|
|
Test to return a list of all disabled services
|
|
'''
|
|
cmd_mock = MagicMock(return_value=_LIST_UNIT_FILES)
|
|
# 'foo' should collide with the systemd services (as returned by
|
|
# sd_mock) and thus not be returned by _get_sysv_services(). It doesn't
|
|
# matter that it's not part of the _LIST_UNIT_FILES output, we just
|
|
# want to ensure that 'foo' isn't identified as a disabled initscript
|
|
# even though below we are mocking it to show as not enabled (since
|
|
# only 'baz' will be considered an enabled sysv service).
|
|
listdir_mock = MagicMock(return_value=['foo', 'bar', 'baz', 'README'])
|
|
sd_mock = MagicMock(
|
|
return_value=set(
|
|
[x.replace('.service', '') for x in _SYSTEMCTL_STATUS]
|
|
)
|
|
)
|
|
access_mock = MagicMock(
|
|
side_effect=lambda x, y: x != os.path.join(
|
|
systemd.INITSCRIPT_PATH,
|
|
'README'
|
|
)
|
|
)
|
|
sysv_enabled_mock = MagicMock(side_effect=lambda x: x == 'baz')
|
|
|
|
with patch.dict(systemd.__salt__, {'cmd.run': cmd_mock}):
|
|
with patch.object(os, 'listdir', listdir_mock):
|
|
with patch.object(systemd, '_get_systemd_services', sd_mock):
|
|
with patch.object(os, 'access', side_effect=access_mock):
|
|
with patch.object(systemd, '_sysv_enabled',
|
|
sysv_enabled_mock):
|
|
self.assertListEqual(
|
|
systemd.get_disabled(),
|
|
['bar', 'service2', 'timer2.timer']
|
|
)
|
|
|
|
def test_get_all(self):
|
|
'''
|
|
Test to return a list of all available services
|
|
'''
|
|
listdir_mock = MagicMock(side_effect=[
|
|
['foo.service', 'multi-user.target.wants', 'mytimer.timer'],
|
|
[],
|
|
['foo.service', 'multi-user.target.wants', 'bar.service'],
|
|
['mysql', 'nginx', 'README']
|
|
])
|
|
access_mock = MagicMock(
|
|
side_effect=lambda x, y: x != os.path.join(
|
|
systemd.INITSCRIPT_PATH,
|
|
'README'
|
|
)
|
|
)
|
|
with patch.object(os, 'listdir', listdir_mock):
|
|
with patch.object(os, 'access', side_effect=access_mock):
|
|
self.assertListEqual(
|
|
systemd.get_all(),
|
|
['bar', 'foo', 'mysql', 'mytimer.timer', 'nginx']
|
|
)
|
|
|
|
def test_available(self):
|
|
'''
|
|
Test to check that the given service is available
|
|
'''
|
|
mock = MagicMock(side_effect=lambda x: _SYSTEMCTL_STATUS[x])
|
|
|
|
# systemd < 231
|
|
with patch.dict(systemd.__context__, {'salt.utils.systemd.version': 230}):
|
|
with patch.object(systemd, '_systemctl_status', mock):
|
|
self.assertTrue(systemd.available('sshd.service'))
|
|
self.assertFalse(systemd.available('foo.service'))
|
|
|
|
# systemd >= 231
|
|
with patch.dict(systemd.__context__, {'salt.utils.systemd.version': 231}):
|
|
with patch.dict(_SYSTEMCTL_STATUS, _SYSTEMCTL_STATUS_GTE_231):
|
|
with patch.object(systemd, '_systemctl_status', mock):
|
|
self.assertTrue(systemd.available('sshd.service'))
|
|
self.assertFalse(systemd.available('bar.service'))
|
|
|
|
# systemd < 231 with retcode/output changes backported (e.g. RHEL 7.3)
|
|
with patch.dict(systemd.__context__, {'salt.utils.systemd.version': 219}):
|
|
with patch.dict(_SYSTEMCTL_STATUS, _SYSTEMCTL_STATUS_GTE_231):
|
|
with patch.object(systemd, '_systemctl_status', mock):
|
|
self.assertTrue(systemd.available('sshd.service'))
|
|
self.assertFalse(systemd.available('bar.service'))
|
|
|
|
def test_missing(self):
|
|
'''
|
|
Test to the inverse of service.available.
|
|
'''
|
|
mock = MagicMock(side_effect=lambda x: _SYSTEMCTL_STATUS[x])
|
|
|
|
# systemd < 231
|
|
with patch.dict(systemd.__context__, {'salt.utils.systemd.version': 230}):
|
|
with patch.object(systemd, '_systemctl_status', mock):
|
|
self.assertFalse(systemd.missing('sshd.service'))
|
|
self.assertTrue(systemd.missing('foo.service'))
|
|
|
|
# systemd >= 231
|
|
with patch.dict(systemd.__context__, {'salt.utils.systemd.version': 231}):
|
|
with patch.dict(_SYSTEMCTL_STATUS, _SYSTEMCTL_STATUS_GTE_231):
|
|
with patch.object(systemd, '_systemctl_status', mock):
|
|
self.assertFalse(systemd.missing('sshd.service'))
|
|
self.assertTrue(systemd.missing('bar.service'))
|
|
|
|
# systemd < 231 with retcode/output changes backported (e.g. RHEL 7.3)
|
|
with patch.dict(systemd.__context__, {'salt.utils.systemd.version': 219}):
|
|
with patch.dict(_SYSTEMCTL_STATUS, _SYSTEMCTL_STATUS_GTE_231):
|
|
with patch.object(systemd, '_systemctl_status', mock):
|
|
self.assertFalse(systemd.missing('sshd.service'))
|
|
self.assertTrue(systemd.missing('bar.service'))
|
|
|
|
def test_show(self):
|
|
'''
|
|
Test to show properties of one or more units/jobs or the manager
|
|
'''
|
|
show_output = 'a=b\nc=d\ne={ f=g ; h=i }\nWants=foo.service bar.service\n'
|
|
mock = MagicMock(return_value=show_output)
|
|
with patch.dict(systemd.__salt__, {'cmd.run': mock}):
|
|
self.assertDictEqual(
|
|
systemd.show('sshd'),
|
|
{'a': 'b',
|
|
'c': 'd',
|
|
'e': {'f': 'g', 'h': 'i'},
|
|
'Wants': ['foo.service', 'bar.service']}
|
|
)
|
|
|
|
def test_execs(self):
|
|
'''
|
|
Test to return a list of all files specified as ``ExecStart`` for all
|
|
services
|
|
'''
|
|
mock = MagicMock(return_value=['a', 'b'])
|
|
with patch.object(systemd, 'get_all', mock):
|
|
mock = MagicMock(return_value={'ExecStart': {'path': 'c'}})
|
|
with patch.object(systemd, 'show', mock):
|
|
self.assertDictEqual(systemd.execs(), {'a': 'c', 'b': 'c'})
|
|
|
|
|
|
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
|
class SystemdScopeTestCase(TestCase, LoaderModuleMockMixin):
|
|
'''
|
|
Test case for salt.modules.systemd, for functions which use systemd
|
|
scopes
|
|
'''
|
|
def setup_loader_modules(self):
|
|
return {systemd: {}}
|
|
|
|
unit_name = 'foo'
|
|
mock_none = MagicMock(return_value=None)
|
|
mock_success = MagicMock(return_value=0)
|
|
mock_failure = MagicMock(return_value=1)
|
|
mock_true = MagicMock(return_value=True)
|
|
mock_false = MagicMock(return_value=False)
|
|
mock_empty_list = MagicMock(return_value=[])
|
|
mock_run_all_success = MagicMock(return_value={'retcode': 0,
|
|
'stdout': '',
|
|
'stderr': '',
|
|
'pid': 12345})
|
|
mock_run_all_failure = MagicMock(return_value={'retcode': 1,
|
|
'stdout': '',
|
|
'stderr': '',
|
|
'pid': 12345})
|
|
|
|
def _change_state(self, action, no_block=False):
|
|
'''
|
|
Common code for start/stop/restart/reload/force_reload tests
|
|
'''
|
|
# We want the traceback if the function name can't be found in the
|
|
# systemd execution module.
|
|
func = getattr(systemd, action)
|
|
# Remove trailing _ in "reload_"
|
|
action = action.rstrip('_').replace('_', '-')
|
|
systemctl_command = ['systemctl']
|
|
if no_block:
|
|
systemctl_command.append('--no-block')
|
|
systemctl_command.extend([action, self.unit_name + '.service'])
|
|
scope_prefix = ['systemd-run', '--scope']
|
|
|
|
assert_kwargs = {'python_shell': False}
|
|
if action in ('enable', 'disable'):
|
|
assert_kwargs['ignore_retcode'] = True
|
|
|
|
with patch.object(systemd, '_check_for_unit_changes', self.mock_none):
|
|
with patch.object(systemd, '_unit_file_changed', self.mock_none):
|
|
with patch.object(systemd, '_check_unmask', self.mock_none):
|
|
with patch.object(systemd, '_get_sysv_services', self.mock_empty_list):
|
|
|
|
# Has scopes available
|
|
with patch.object(salt.utils.systemd, 'has_scope', self.mock_true):
|
|
|
|
# Scope enabled, successful
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': self.mock_true,
|
|
'cmd.run_all': self.mock_run_all_success}):
|
|
ret = func(self.unit_name, no_block=no_block)
|
|
self.assertTrue(ret)
|
|
self.mock_run_all_success.assert_called_with(
|
|
scope_prefix + systemctl_command,
|
|
**assert_kwargs)
|
|
|
|
# Scope enabled, failed
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': self.mock_true,
|
|
'cmd.run_all': self.mock_run_all_failure}):
|
|
if action in ('stop', 'disable'):
|
|
ret = func(self.unit_name, no_block=no_block)
|
|
self.assertFalse(ret)
|
|
else:
|
|
self.assertRaises(
|
|
CommandExecutionError,
|
|
func,
|
|
self.unit_name,
|
|
no_block=no_block)
|
|
self.mock_run_all_failure.assert_called_with(
|
|
scope_prefix + systemctl_command,
|
|
**assert_kwargs)
|
|
|
|
# Scope disabled, successful
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': self.mock_false,
|
|
'cmd.run_all': self.mock_run_all_success}):
|
|
ret = func(self.unit_name, no_block=no_block)
|
|
self.assertTrue(ret)
|
|
self.mock_run_all_success.assert_called_with(
|
|
systemctl_command,
|
|
**assert_kwargs)
|
|
|
|
# Scope disabled, failed
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': self.mock_false,
|
|
'cmd.run_all': self.mock_run_all_failure}):
|
|
if action in ('stop', 'disable'):
|
|
ret = func(self.unit_name, no_block=no_block)
|
|
self.assertFalse(ret)
|
|
else:
|
|
self.assertRaises(
|
|
CommandExecutionError,
|
|
func,
|
|
self.unit_name,
|
|
no_block=no_block)
|
|
self.mock_run_all_failure.assert_called_with(
|
|
systemctl_command,
|
|
**assert_kwargs)
|
|
|
|
# Does not have scopes available
|
|
with patch.object(salt.utils.systemd, 'has_scope', self.mock_false):
|
|
|
|
# The results should be the same irrespective of
|
|
# whether or not scope is enabled, since scope is not
|
|
# available, so we repeat the below tests with it both
|
|
# enabled and disabled.
|
|
for scope_mock in (self.mock_true, self.mock_false):
|
|
|
|
# Successful
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': scope_mock,
|
|
'cmd.run_all': self.mock_run_all_success}):
|
|
ret = func(self.unit_name, no_block=no_block)
|
|
self.assertTrue(ret)
|
|
self.mock_run_all_success.assert_called_with(
|
|
systemctl_command,
|
|
**assert_kwargs)
|
|
|
|
# Failed
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': scope_mock,
|
|
'cmd.run_all': self.mock_run_all_failure}):
|
|
if action in ('stop', 'disable'):
|
|
ret = func(self.unit_name,
|
|
no_block=no_block)
|
|
self.assertFalse(ret)
|
|
else:
|
|
self.assertRaises(
|
|
CommandExecutionError,
|
|
func,
|
|
self.unit_name,
|
|
no_block=no_block)
|
|
self.mock_run_all_failure.assert_called_with(
|
|
systemctl_command,
|
|
**assert_kwargs)
|
|
|
|
def _mask_unmask(self, action, runtime):
|
|
'''
|
|
Common code for mask/unmask tests
|
|
'''
|
|
# We want the traceback if the function name can't be found in the
|
|
# systemd execution module, so don't provide a fallback value for the
|
|
# call to getattr() here.
|
|
func = getattr(systemd, action)
|
|
# Remove trailing _ in "unmask_"
|
|
action = action.rstrip('_').replace('_', '-')
|
|
systemctl_command = ['systemctl', action]
|
|
if runtime:
|
|
systemctl_command.append('--runtime')
|
|
systemctl_command.append(self.unit_name + '.service')
|
|
scope_prefix = ['systemd-run', '--scope']
|
|
|
|
args = [self.unit_name, runtime]
|
|
|
|
masked_mock = self.mock_true if action == 'unmask' else self.mock_false
|
|
|
|
with patch.object(systemd, '_check_for_unit_changes', self.mock_none):
|
|
if action == 'unmask':
|
|
mock_not_run = MagicMock(return_value={'retcode': 0,
|
|
'stdout': '',
|
|
'stderr': '',
|
|
'pid': 12345})
|
|
with patch.dict(systemd.__salt__, {'cmd.run_all': mock_not_run}):
|
|
with patch.object(systemd, 'masked', self.mock_false):
|
|
# Test not masked (should take no action and return True)
|
|
self.assertTrue(systemd.unmask_(self.unit_name))
|
|
# Also should not have called cmd.run_all
|
|
self.assertTrue(mock_not_run.call_count == 0)
|
|
|
|
with patch.object(systemd, 'masked', masked_mock):
|
|
|
|
# Has scopes available
|
|
with patch.object(salt.utils.systemd, 'has_scope', self.mock_true):
|
|
|
|
# Scope enabled, successful
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': self.mock_true,
|
|
'cmd.run_all': self.mock_run_all_success}):
|
|
ret = func(*args)
|
|
self.assertTrue(ret)
|
|
self.mock_run_all_success.assert_called_with(
|
|
scope_prefix + systemctl_command,
|
|
python_shell=False,
|
|
redirect_stderr=True)
|
|
|
|
# Scope enabled, failed
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': self.mock_true,
|
|
'cmd.run_all': self.mock_run_all_failure}):
|
|
self.assertRaises(
|
|
CommandExecutionError,
|
|
func, *args)
|
|
self.mock_run_all_failure.assert_called_with(
|
|
scope_prefix + systemctl_command,
|
|
python_shell=False,
|
|
redirect_stderr=True)
|
|
|
|
# Scope disabled, successful
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': self.mock_false,
|
|
'cmd.run_all': self.mock_run_all_success}):
|
|
ret = func(*args)
|
|
self.assertTrue(ret)
|
|
self.mock_run_all_success.assert_called_with(
|
|
systemctl_command,
|
|
python_shell=False,
|
|
redirect_stderr=True)
|
|
|
|
# Scope disabled, failed
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': self.mock_false,
|
|
'cmd.run_all': self.mock_run_all_failure}):
|
|
self.assertRaises(
|
|
CommandExecutionError,
|
|
func, *args)
|
|
self.mock_run_all_failure.assert_called_with(
|
|
systemctl_command,
|
|
python_shell=False,
|
|
redirect_stderr=True)
|
|
|
|
# Does not have scopes available
|
|
with patch.object(salt.utils.systemd, 'has_scope', self.mock_false):
|
|
|
|
# The results should be the same irrespective of
|
|
# whether or not scope is enabled, since scope is not
|
|
# available, so we repeat the below tests with it both
|
|
# enabled and disabled.
|
|
for scope_mock in (self.mock_true, self.mock_false):
|
|
|
|
# Successful
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': scope_mock,
|
|
'cmd.run_all': self.mock_run_all_success}):
|
|
ret = func(*args)
|
|
self.assertTrue(ret)
|
|
self.mock_run_all_success.assert_called_with(
|
|
systemctl_command,
|
|
python_shell=False,
|
|
redirect_stderr=True)
|
|
|
|
# Failed
|
|
with patch.dict(
|
|
systemd.__salt__,
|
|
{'config.get': scope_mock,
|
|
'cmd.run_all': self.mock_run_all_failure}):
|
|
self.assertRaises(
|
|
CommandExecutionError,
|
|
func, *args)
|
|
self.mock_run_all_failure.assert_called_with(
|
|
systemctl_command,
|
|
python_shell=False,
|
|
redirect_stderr=True)
|
|
|
|
def test_start(self):
|
|
self._change_state('start', no_block=False)
|
|
self._change_state('start', no_block=True)
|
|
|
|
def test_stop(self):
|
|
self._change_state('stop', no_block=False)
|
|
self._change_state('stop', no_block=True)
|
|
|
|
def test_restart(self):
|
|
self._change_state('restart', no_block=False)
|
|
self._change_state('restart', no_block=True)
|
|
|
|
def test_reload(self):
|
|
self._change_state('reload_', no_block=False)
|
|
self._change_state('reload_', no_block=True)
|
|
|
|
def test_force_reload(self):
|
|
self._change_state('force_reload', no_block=False)
|
|
self._change_state('force_reload', no_block=True)
|
|
|
|
def test_enable(self):
|
|
self._change_state('enable', no_block=False)
|
|
self._change_state('enable', no_block=True)
|
|
|
|
def test_disable(self):
|
|
self._change_state('disable', no_block=False)
|
|
self._change_state('disable', no_block=True)
|
|
|
|
def test_mask(self):
|
|
self._mask_unmask('mask', False)
|
|
|
|
def test_mask_runtime(self):
|
|
self._mask_unmask('mask', True)
|
|
|
|
def test_unmask(self):
|
|
self._mask_unmask('unmask_', False)
|
|
|
|
def test_unmask_runtime(self):
|
|
self._mask_unmask('unmask_', True)
|