You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ansible/test/units/plugins/action/test_reboot.py

219 lines
6.6 KiB
Python

# Copyright (c) 2022 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""Tests for the reboot action plugin."""
from __future__ import annotations
import os
import pytest
from ansible.errors import AnsibleConnectionFailure
from ansible.playbook.task import Task
from ansible.plugins.action.reboot import ActionModule as RebootAction
from ansible.plugins.loader import connection_loader
@pytest.fixture
def task_args(request):
"""Return playbook task args."""
return getattr(request, 'param', {})
@pytest.fixture
def module_task(mocker, task_args):
"""Construct a task object."""
task = mocker.MagicMock(Task)
task.action = 'reboot'
task.args = task_args
task.async_val = False
task.check_mode = False
task.diff = False
return task
@pytest.fixture
def play_context(mocker):
"""Construct a play context."""
ctx = mocker.MagicMock()
ctx.check_mode = False
ctx.shell = 'sh'
return ctx
@pytest.fixture
def action_plugin(play_context, module_task):
"""Initialize an action plugin."""
connection = connection_loader.get('local', play_context, os.devnull)
loader = None
templar = None
shared_loader_obj = None
return RebootAction(
module_task,
connection,
play_context,
loader,
templar,
shared_loader_obj,
)
_SENTINEL_REBOOT_COMMAND = '/reboot-command-mock --arg'
_SENTINEL_SHORT_REBOOT_COMMAND = '/reboot-command-mock'
_SENTINEL_TEST_COMMAND = 'cmd-stub'
@pytest.mark.parametrize(
'task_args',
(
{
'reboot_timeout': 5,
'reboot_command': _SENTINEL_REBOOT_COMMAND,
'test_command': _SENTINEL_TEST_COMMAND,
},
{
'reboot_timeout': 5,
'reboot_command': _SENTINEL_SHORT_REBOOT_COMMAND,
'test_command': _SENTINEL_TEST_COMMAND,
},
),
ids=('reboot command with spaces', 'reboot command without spaces'),
indirect=('task_args', ),
)
def test_reboot_command(action_plugin, mocker, monkeypatch, task_args):
"""Check that the reboot command gets called and reboot verified."""
def _patched_low_level_execute_command(cmd, *args, **kwargs):
return {
_SENTINEL_TEST_COMMAND: {
'rc': 0,
'stderr': '<test command stub-stderr>',
'stdout': '<test command stub-stdout>',
},
_SENTINEL_REBOOT_COMMAND: {
'rc': 0,
'stderr': '<reboot command stub-stderr>',
'stdout': '<reboot command stub-stdout>',
},
f'{_SENTINEL_SHORT_REBOOT_COMMAND} ': { # no args is concatenated
'rc': 0,
'stderr': '<short reboot command stub-stderr>',
'stdout': '<short reboot command stub-stdout>',
},
}[cmd]
monkeypatch.setattr(
action_plugin,
'_low_level_execute_command',
_patched_low_level_execute_command,
)
action_plugin._connection = mocker.Mock()
monkeypatch.setattr(action_plugin, 'check_boot_time', lambda *_a, **_kw: 5)
monkeypatch.setattr(action_plugin, 'get_distribution', mocker.MagicMock())
monkeypatch.setattr(action_plugin, 'get_system_boot_time', lambda d: 0)
low_level_cmd_spy = mocker.spy(action_plugin, '_low_level_execute_command')
action_result = action_plugin.run()
assert low_level_cmd_spy.called
expected_reboot_command = (
task_args['reboot_command'] if ' ' in task_args['reboot_command']
else f'{task_args["reboot_command"] !s} '
)
low_level_cmd_spy.assert_any_call(expected_reboot_command, sudoable=True)
low_level_cmd_spy.assert_any_call(task_args['test_command'], sudoable=True)
assert low_level_cmd_spy.call_count == 2
assert low_level_cmd_spy.spy_return == {
'rc': 0,
'stderr': '<test command stub-stderr>',
'stdout': '<test command stub-stdout>',
}
assert low_level_cmd_spy.spy_exception is None
assert 'failed' not in action_result
assert action_result == {'rebooted': True, 'changed': True, 'elapsed': 0}
@pytest.mark.parametrize(
'task_args',
(
{
'reboot_timeout': 5,
'reboot_command': _SENTINEL_REBOOT_COMMAND,
'test_command': _SENTINEL_TEST_COMMAND,
},
),
ids=('reboot command with spaces', ),
indirect=('task_args', ),
)
def test_reboot_command_connection_fail(action_plugin, mocker, monkeypatch, task_args):
"""Check that the reboot command gets called and reboot verified."""
def _patched_low_level_execute_command(cmd, *args, **kwargs):
if cmd == _SENTINEL_REBOOT_COMMAND:
raise AnsibleConnectionFailure('Fake connection drop')
return {
_SENTINEL_TEST_COMMAND: {
'rc': 0,
'stderr': '<test command stub-stderr>',
'stdout': '<test command stub-stdout>',
},
}[cmd]
monkeypatch.setattr(
action_plugin,
'_low_level_execute_command',
_patched_low_level_execute_command,
)
action_plugin._connection = mocker.Mock()
monkeypatch.setattr(action_plugin, 'check_boot_time', lambda *_a, **_kw: 5)
monkeypatch.setattr(action_plugin, 'get_distribution', mocker.MagicMock())
monkeypatch.setattr(action_plugin, 'get_system_boot_time', lambda d: 0)
low_level_cmd_spy = mocker.spy(action_plugin, '_low_level_execute_command')
action_result = action_plugin.run()
assert low_level_cmd_spy.called
low_level_cmd_spy.assert_any_call(
task_args['reboot_command'], sudoable=True,
)
low_level_cmd_spy.assert_any_call(task_args['test_command'], sudoable=True)
assert low_level_cmd_spy.call_count == 2
assert low_level_cmd_spy.spy_return == {
'rc': 0,
'stderr': '<test command stub-stderr>',
'stdout': '<test command stub-stdout>',
}
assert 'failed' not in action_result
assert action_result == {'rebooted': True, 'changed': True, 'elapsed': 0}
def test_reboot_connection_local(action_plugin, module_task):
"""Verify that using local connection doesn't let reboot happen."""
expected_message = ' '.join(
(
'Running', module_task.action,
'with local connection would reboot the control node.',
),
)
expected_action_result = {
'changed': False,
'elapsed': 0,
'failed': True,
'msg': expected_message,
'rebooted': False,
}
action_result = action_plugin.run()
assert action_result == expected_action_result