diff --git a/lib/ansible/modules/system/reboot.py b/lib/ansible/modules/system/reboot.py new file mode 100644 index 0000000000..0c15f8a357 --- /dev/null +++ b/lib/ansible/modules/system/reboot.py @@ -0,0 +1,81 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'core'} + +DOCUMENTATION = r''' +module: reboot +short_description: Reboot a machine +description: + - Reboot a machine, wait for it to go down, come back up, and respond to commands. +version_added: "2.7" +options: + pre_reboot_delay: + description: + - Seconds for shutdown to wait before requesting reboot. + - On Linux and macOS, this is converted to minutes and rounded down. If less than 60, it will be set to 0. + - On Solaris and FreeBSD, this will be seconds. + default: 0 + type: int + post_reboot_delay: + description: + - Seconds to wait after the reboot was successful and the connection was re-established. + - This is useful if you want wait for something to settle despite your connection already working. + default: 0 + type: int + reboot_timeout: + description: + - Maximum seconds to wait for machine to reboot and respond to a test command. + - This timeout is evaluated separately for both network connection and test command success so the + maximum execution time for the module is twice this amount. + default: 600 + type: int + connect_timeout: + description: + - Maximum seconds to wait for a successful connection to the managed hosts before trying again. + - If unspecified, the default setting for the underlying connection plugin is used. + type: int + test_command: + description: + - Command to run on the rebooted host and expect success from to determine the machine is ready for + further tasks. + default: whoami + type: str + msg: + description: + - Message to display to users before reboot. + default: Reboot initiated by Ansible + type: str +author: + - Matt Davis (@nitzmahone) + - Sam Doran (@samdoran) +''' + +EXAMPLES = r''' +# Unconditionally reboot the machine with all defaults +- reboot: + +# Reboot a slow machine that might have lots of updates to apply +- reboot: + reboot_timeout: 3600 +''' + +RETURN = r''' +rebooted: + description: true if the machine was rebooted + returned: always + type: boolean + sample: true +elapsed: + description: The number of seconds that elapsed waiting for the system to be rebooted. + returned: always + type: int + sample: 23 +''' diff --git a/lib/ansible/plugins/action/reboot.py b/lib/ansible/plugins/action/reboot.py new file mode 100644 index 0000000000..234b47c02f --- /dev/null +++ b/lib/ansible/plugins/action/reboot.py @@ -0,0 +1,287 @@ +# (c) 2016-2018, Matt Davis +# (c) 2018, Sam Doran +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import random +import time + +from datetime import datetime, timedelta + +from ansible.errors import AnsibleError +from ansible.plugins.action import ActionBase +from ansible.module_utils._text import to_native, to_text + + +try: + from __main__ import display +except ImportError: + from ansible.utils.display import Display + display = Display() + + +class TimedOutException(Exception): + pass + + +class ActionModule(ActionBase): + TRANSFERS_FILES = False + + DEFAULT_REBOOT_TIMEOUT = 600 + DEFAULT_CONNECT_TIMEOUT = None + DEFAULT_PRE_REBOOT_DELAY = 0 + DEFAULT_POST_REBOOT_DELAY = 0 + DEFAULT_TEST_COMMAND = 'whoami' + DEFAULT_BOOT_TIME_COMMAND = 'who -b' + DEFAULT_REBOOT_MESSAGE = 'Reboot initiated by Ansible' + DEFAULT_SHUTDOWN_COMMAND = 'shutdown' + DEFAULT_SUDOABLE = True + + DEPRECATED_ARGS = {} + + SHUTDOWN_COMMANDS = { + 'linux': DEFAULT_SHUTDOWN_COMMAND, + 'freebsd': DEFAULT_SHUTDOWN_COMMAND, + 'sunos': '/usr/sbin/shutdown', + 'darwin': '/sbin/shutdown', + } + + SHUTDOWN_COMMAND_ARGS = { + 'linux': '-r {delay_min} "{message}"', + 'freebsd': '-r +{delay_sec}s "{message}"', + 'sunos': '-y -g {delay_sec} -r "{message}"', + 'darwin': '-r +{delay_min_macos} "{message}"' + } + + def __init__(self, *args, **kwargs): + super(ActionModule, self).__init__(*args, **kwargs) + + self._original_connection_timeout = None + self._previous_boot_time = None + + def deprecated_args(self): + for arg, version in self.DEPRECATED_ARGS.items(): + if self._task.args.get(arg) is not None: + display.warning("Since Ansible %s, %s is no longer a valid option for %s" % (version, arg, self._task.action)) + + def construct_command(self): + # Determine the system distribution in order to use the correct shutdown command arguments + uname_result = self._low_level_execute_command('uname') + distribution = uname_result['stdout'].strip().lower() + + shutdown_command = self.SHUTDOWN_COMMANDS.get(distribution, self.SHUTDOWN_COMMAND_ARGS['linux']) + shutdown_command_args = self.SHUTDOWN_COMMAND_ARGS.get(distribution, self.SHUTDOWN_COMMAND_ARGS['linux']) + + pre_reboot_delay = int(self._task.args.get('pre_reboot_delay', self.DEFAULT_PRE_REBOOT_DELAY)) + if pre_reboot_delay < 0: + pre_reboot_delay = 0 + + # Convert seconds to minutes for Linux. If less that 60, set it to 0 except for macOS which will + # sever the connection too quickly if set to 0, so set that to 1. + # We could simplify this by setting them both to 1, but I think of all the time that + # people will lose waiting for that extra 1 minute delay and want to give them their + # lives back. + delay_min = pre_reboot_delay // 60 + delay_min_macos = delay_min | 1 + msg = self._task.args.get('msg', self.DEFAULT_REBOOT_MESSAGE) + + shutdown_command_args = shutdown_command_args.format(delay_sec=pre_reboot_delay, delay_min=delay_min, delay_min_macos=delay_min_macos, message=msg) + + reboot_command = '%s %s' % (shutdown_command, shutdown_command_args) + return reboot_command + + def get_system_boot_time(self): + command_result = self._low_level_execute_command(self.DEFAULT_BOOT_TIME_COMMAND, sudoable=self.DEFAULT_SUDOABLE) + + # For single board computers, e.g., Raspberry Pi, that lack a real time clock and are using fake-hwclock + # launched by systemd, the update of utmp/wtmp is not done correctly. + # Fall back to using uptime -s for those systems. + # https://github.com/systemd/systemd/issues/6057 + if '1970-01-01 00:00' in command_result['stdout']: + command_result = self._low_level_execute_command('uptime -s', sudoable=self.DEFAULT_SUDOABLE) + + if command_result['rc'] != 0: + raise AnsibleError("%s: failed to get host boot time info, rc: %d, stdout: %s, stderr: %s" + % (self._task.action, command_result.rc, to_native(command_result['stdout']), to_native(command_result['stderr']))) + + return command_result['stdout'].strip() + + def check_boot_time(self): + display.vvv("%s: attempting to get system boot time" % self._task.action) + connect_timeout = self._task.args.get('connect_timeout', self._task.args.get('connect_timeout_sec', self.DEFAULT_CONNECT_TIMEOUT)) + + # override connection timeout from defaults to custom value + if connect_timeout: + try: + self._connection.set_option("connection_timeout", connect_timeout) + self._connection.reset() + except AttributeError: + display.warning("Connection plugin does not allow the connection timeout to be overridden") + + # try and get boot time + try: + current_boot_time = self.get_system_boot_time() + except Exception as e: + raise e + + # FreeBSD returns an empty string immediately before reboot so adding a length + # check to prevent prematurely assuming system has rebooted + if len(current_boot_time) == 0 or current_boot_time == self._previous_boot_time: + raise Exception("boot time has not changed") + + def run_test_command(self, **kwargs): + test_command = self._task.args.get('test_command', self.DEFAULT_TEST_COMMAND) + display.vvv("%s: attempting post-reboot test command '%s'" % (self._task.action, test_command)) + command_result = self._low_level_execute_command(test_command, sudoable=self.DEFAULT_SUDOABLE) + + result = {} + if command_result['rc'] != 0: + result['failed'] = True + result['msg'] = 'test command failed: %s %s' % (to_native(command_result['stderr'], to_native(command_result['stdout']))) + else: + result['msg'] = to_native(command_result['stdout']) + + return result + + def do_until_success_or_timeout(self, action, reboot_timeout, action_desc): + max_end_time = datetime.utcnow() + timedelta(seconds=reboot_timeout) + + fail_count = 0 + max_fail_sleep = 12 + + while datetime.utcnow() < max_end_time: + try: + action() + if action_desc: + display.debug('%s: %s success' % (self._task.action, action_desc)) + return + except Exception as e: + # Use exponential backoff with a max timout, plus a little bit of randomness + random_int = random.randint(0, 1000) / 1000 + fail_sleep = 2 ** fail_count + random_int + if fail_sleep > max_fail_sleep: + + fail_sleep = max_fail_sleep + random_int + if action_desc: + display.debug("{0}: {1} fail '{2}', retrying in {3:.4} seconds...".format(self._task.action, action_desc, to_text(e), fail_sleep)) + fail_count += 1 + time.sleep(fail_sleep) + + raise TimedOutException('Timed out waiting for %s' % (action_desc)) + + def perform_reboot(self): + display.debug("%s: rebooting server" % self._task.action) + + remote_command = self.construct_command() + reboot_result = self._low_level_execute_command(remote_command, sudoable=self.DEFAULT_SUDOABLE) + result = {} + result['start'] = datetime.utcnow() + + if reboot_result['rc'] != 0: + result['failed'] = True + result['rebooted'] = False + result['msg'] = "Shutdown command failed. Error was %s, %s" % ( + to_native(reboot_result['stdout'].strip()), to_native(reboot_result['stderr'].strip())) + return result + + result['failed'] = False + + # attempt to store the original connection_timeout option var so it can be reset after + self._original_connection_timeout = None + try: + self._original_connection_timeout = self._connection.get_option('connection_timeout') + except AnsibleError: + display.debug("%s: connect_timeout connection option has not been set" % self._task.action) + + post_reboot_delay = int(self._task.args.get('post_reboot_delay', self._task.args.get('post_reboot_delay_sec', self.DEFAULT_POST_REBOOT_DELAY))) + if post_reboot_delay < 0: + post_reboot_delay = 0 + + if post_reboot_delay != 0: + display.vvv("%s: waiting an additional %d seconds" % (self._task.action, post_reboot_delay)) + time.sleep(post_reboot_delay) + + return result + + def validate_reboot(self): + display.debug('%s: Validating reboot' % self._task.action) + result = {} + + try: + # keep on checking system boot_time with short connection responses + reboot_timeout = int(self._task.args.get('reboot_timeout', self._task.args.get('reboot_timeout_sec', self.DEFAULT_REBOOT_TIMEOUT))) + connect_timeout = self._task.args.get('connect_timeout', self._task.args.get('connect_timeout_sec', self.DEFAULT_CONNECT_TIMEOUT)) + self.do_until_success_or_timeout(self.check_boot_time, reboot_timeout, action_desc="boot_time check") + + if connect_timeout: + # reset the connection to clear the custom connection timeout + try: + self._connection.set_option("connection_timeout", connect_timeout) + self._connection.reset() + except (AnsibleError, AttributeError) as e: + display.debug("Failed to reset connection_timeout back to default: %s" % to_text(e)) + + # finally run test command to ensure everything is working + # FUTURE: add a stability check (system must remain up for N seconds) to deal with self-multi-reboot updates + self.do_until_success_or_timeout(self.run_test_command, reboot_timeout, action_desc="post-reboot test command") + + result['rebooted'] = True + result['changed'] = True + + except TimedOutException as toex: + result['failed'] = True + result['rebooted'] = True + result['msg'] = to_text(toex) + return result + + return result + + def run(self, tmp=None, task_vars=None): + self._supports_check_mode = True + self._supports_async = True + + # If running with local connection, fail so we don't reboot ourself + if self._connection.transport == 'local': + msg = 'Running {0} with local connection would reboot the control node.'.format(self._task.action) + return dict(changed=False, elapsed=0, rebooted=False, failed=True, msg=msg) + + if self._play_context.check_mode: + return dict(changed=True, elapsed=0, rebooted=True) + + if task_vars is None: + task_vars = dict() + + self.deprecated_args() + + result = super(ActionModule, self).run(tmp, task_vars) + + if result.get('skipped', False) or result.get('failed', False): + return result + + # Get current boot time + try: + self._previous_boot_time = self.get_system_boot_time() + except Exception as e: + result['failed'] = True + result['reboot'] = False + result['msg'] = to_text(e) + return result + + # Initiate reboot + reboot_result = self.perform_reboot() + + if reboot_result['failed']: + result = reboot_result + elapsed = datetime.utcnow() - reboot_result['start'] + result['elapsed'] = elapsed.seconds + return result + + # Make sure reboot was successful + result = self.validate_reboot() + + elapsed = datetime.utcnow() - reboot_result['start'] + result['elapsed'] = elapsed.seconds + + return result diff --git a/lib/ansible/plugins/action/win_reboot.py b/lib/ansible/plugins/action/win_reboot.py index e2e9c88931..1bb25fb843 100644 --- a/lib/ansible/plugins/action/win_reboot.py +++ b/lib/ansible/plugins/action/win_reboot.py @@ -1,16 +1,15 @@ -# (c) 2016, Matt Davis +# (c) 2018, Matt Davis # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import time - -from datetime import datetime, timedelta +from datetime import datetime from ansible.errors import AnsibleError from ansible.plugins.action import ActionBase from ansible.module_utils._text import to_native +from ansible.plugins.action.reboot import ActionModule as RebootActionModule try: from __main__ import display @@ -23,201 +22,66 @@ class TimedOutException(Exception): pass -class ActionModule(ActionBase): +class ActionModule(RebootActionModule, ActionBase): TRANSFERS_FILES = False - DEFAULT_REBOOT_TIMEOUT = 600 DEFAULT_CONNECT_TIMEOUT = 5 DEFAULT_PRE_REBOOT_DELAY = 2 - DEFAULT_POST_REBOOT_DELAY = 0 - DEFAULT_TEST_COMMAND = 'whoami' - DEFAULT_REBOOT_MESSAGE = 'Reboot initiated by Ansible.' + DEFAULT_BOOT_TIME_COMMAND = "(Get-WmiObject -ClassName Win32_OperatingSystem).LastBootUpTime" + DEFAULT_SHUTDOWN_COMMAND_ARGS = '/r /t %d /c "%s"' + DEFAULT_SUDOABLE = False - def get_system_uptime(self): - uptime_command = "(Get-WmiObject -ClassName Win32_OperatingSystem).LastBootUpTime" - (rc, stdout, stderr) = self._connection.exec_command(uptime_command) + DEPRECATED_ARGS = { + 'shutdown_timeout': '2.5', + 'shutdown_timeout_sec': '2.5', + } - if rc != 0: - raise Exception("win_reboot: failed to get host uptime info, rc: %d, stdout: %s, stderr: %s" - % (rc, stdout, stderr)) + def construct_command(self): + shutdown_command = self.DEFAULT_SHUTDOWN_COMMAND + pre_reboot_delay = int(self._task.args.get('pre_reboot_delay', self._task.args.get('pre_reboot_delay_sec', self.DEFAULT_PRE_REBOOT_DELAY))) + msg = self._task.args.get('msg', self.DEFAULT_REBOOT_MESSAGE) + shutdown_command_args = self.DEFAULT_SHUTDOWN_COMMAND_ARGS % (pre_reboot_delay, msg) - return stdout + reboot_command = '%s %s' % (shutdown_command, shutdown_command_args) + return reboot_command - def do_until_success_or_timeout(self, what, timeout, what_desc, fail_sleep=1): - max_end_time = datetime.utcnow() + timedelta(seconds=timeout) + def perform_reboot(self): + display.debug("Rebooting server") - exc = "" - while datetime.utcnow() < max_end_time: - try: - what() - if what_desc: - display.debug("win_reboot: %s success" % what_desc) - return - except Exception as e: - exc = e - if what_desc: - display.debug("win_reboot: %s fail '%s' (expected), retrying in %d seconds..." % (what_desc, to_native(e), fail_sleep)) - time.sleep(fail_sleep) + remote_command = self.construct_command() + reboot_result = self._low_level_execute_command(remote_command, sudoable=self.DEFAULT_SUDOABLE) - raise TimedOutException("timed out waiting for %s: %s" % (what_desc, exc)) - - def run(self, tmp=None, task_vars=None): - - self._supports_check_mode = True - self._supports_async = True - - if self._play_context.check_mode: - return dict(changed=True, elapsed=0, rebooted=True) - - if task_vars is None: - task_vars = dict() - - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - - if result.get('skipped', False) or result.get('failed', False): - return result - - # Handle timeout parameters and its alias - deprecated_args = { - 'shutdown_timeout': '2.5', - 'shutdown_timeout_sec': '2.5', - } - for arg, version in deprecated_args.items(): - if self._task.args.get(arg) is not None: - display.warning("Since Ansible %s, %s is no longer used with win_reboot" % (version, arg)) - - if self._task.args.get('connect_timeout') is not None: - connect_timeout = int(self._task.args.get('connect_timeout', self.DEFAULT_CONNECT_TIMEOUT)) - else: - connect_timeout = int(self._task.args.get('connect_timeout_sec', self.DEFAULT_CONNECT_TIMEOUT)) - - if self._task.args.get('reboot_timeout') is not None: - reboot_timeout = int(self._task.args.get('reboot_timeout', self.DEFAULT_REBOOT_TIMEOUT)) - else: - reboot_timeout = int(self._task.args.get('reboot_timeout_sec', self.DEFAULT_REBOOT_TIMEOUT)) - - if self._task.args.get('pre_reboot_delay') is not None: - pre_reboot_delay = int(self._task.args.get('pre_reboot_delay', self.DEFAULT_PRE_REBOOT_DELAY)) - else: - pre_reboot_delay = int(self._task.args.get('pre_reboot_delay_sec', self.DEFAULT_PRE_REBOOT_DELAY)) - - if self._task.args.get('post_reboot_delay') is not None: - post_reboot_delay = int(self._task.args.get('post_reboot_delay', self.DEFAULT_POST_REBOOT_DELAY)) - else: - post_reboot_delay = int(self._task.args.get('post_reboot_delay_sec', self.DEFAULT_POST_REBOOT_DELAY)) - - test_command = str(self._task.args.get('test_command', self.DEFAULT_TEST_COMMAND)) - msg = str(self._task.args.get('msg', self.DEFAULT_REBOOT_MESSAGE)) - - # Get current uptime - try: - before_uptime = self.get_system_uptime() - except Exception as e: - result['failed'] = True - result['reboot'] = False - result['msg'] = to_native(e) - return result - - # Initiate reboot - display.vvv("rebooting server") - (rc, stdout, stderr) = self._connection.exec_command('shutdown /r /t %d /c "%s"' % (pre_reboot_delay, msg)) + pre_reboot_delay = int(self._task.args.get('pre_reboot_delay', self._task.args.get('pre_reboot_delay_sec', self.DEFAULT_PRE_REBOOT_DELAY))) # Test for "A system shutdown has already been scheduled. (1190)" and handle it gracefully - if rc == 1190 or (rc != 0 and b"(1190)" in stderr): + if reboot_result['rc'] == 1190 or (reboot_result['rc'] != 0 and b"(1190)" in reboot_result['stderr']): display.warning('A scheduled reboot was pre-empted by Ansible.') # Try to abort (this may fail if it was already aborted) - (rc, stdout1, stderr1) = self._connection.exec_command('shutdown /a') + result1 = self._low_level_execute_command('shutdown /a', sudoable=self.DEFAULT_SUDOABLE) + # (reboot_result['rc'], stdout1, stderr1) = self._connection.exec_command('shutdown /a') # Initiate reboot again - (rc, stdout2, stderr2) = self._connection.exec_command('shutdown /r /t %d' % pre_reboot_delay) - stdout += stdout1 + stdout2 - stderr += stderr1 + stderr2 + result2 = self._connection.exec_command('shutdown /r /t %d' % pre_reboot_delay) + # (reboot_result['rc'], stdout2, stderr2) = self._connection.exec_command('shutdown /r /t %d' % pre_reboot_delay) - if rc != 0: + stdout = reboot_result['stdout'] + result1['stdout'] + result2['stdout'] + stderr = reboot_result['stderr'] + result1['stderr'] + result2['stderr'] + + result = {} + if reboot_result['rc'] != 0: result['failed'] = True result['rebooted'] = False - result['msg'] = "Shutdown command failed, error text was '%s'" % to_native(stderr) + result['msg'] = "Shutdown command failed, error was: %s %s" % (to_native(stdout.strip()), to_native('stderr'.strip())) return result - start = datetime.now() + result['failed'] = False + result['start'] = datetime.utcnow() + # Get the original connection_timeout option var so it can be reset after - connection_timeout_orig = None try: - connection_timeout_orig = self._connection.get_option('connection_timeout') + self._original_connection_timeout = self._connection.get_option('connection_timeout') except AnsibleError: - display.debug("win_reboot: connection_timeout connection option has not been set") - - try: - # keep on checking system uptime with short connection responses - def check_uptime(): - display.vvv("attempting to get system uptime") - - # override connection timeout from defaults to custom value - try: - self._connection.set_option("connection_timeout", - connect_timeout) - self._connection.reset() - except AttributeError: - display.warning("Connection plugin does not allow the " - "connection timeout to be overridden") - - # try and get uptime - try: - current_uptime = self.get_system_uptime() - except Exception as e: - raise e - - if current_uptime == before_uptime: - raise Exception("uptime has not changed") - - self.do_until_success_or_timeout(check_uptime, reboot_timeout, what_desc="reboot uptime check success") - - # reset the connection to clear the custom connection timeout - try: - self._connection.set_option("connection_timeout", - connection_timeout_orig) - self._connection.reset() - except (AnsibleError, AttributeError) as e: - display.debug("Failed to reset connection_timeout back to default: %s" % to_native(e)) - - # finally run test command to ensure everything is working - def run_test_command(): - display.vvv("attempting post-reboot test command '%s'" % test_command) - try: - (rc, stdout, stderr) = self._connection.exec_command(test_command) - except Exception as e: - # in case of a failure trying to execute the command - # (another reboot occurred) we need to reset the connection - # to make sure we are not re-using the same shell id - try: - self._connection.reset() - except AttributeError: - pass - raise - else: - if rc != 0: - raise Exception("test command failed, stdout: '%s', stderr: '%s', rc: %d" - % (stdout, stderr, rc)) - - # FUTURE: add a stability check (system must remain up for N seconds) to deal with self-multi-reboot updates - - self.do_until_success_or_timeout(run_test_command, reboot_timeout, what_desc="post-reboot test command success") - - result['rebooted'] = True - result['changed'] = True - - except TimedOutException as toex: - result['failed'] = True - result['rebooted'] = True - result['msg'] = to_native(toex) - - if post_reboot_delay != 0: - display.vvv("win_reboot: waiting an additional %d seconds" % post_reboot_delay) - time.sleep(post_reboot_delay) - - elapsed = datetime.now() - start - result['elapsed'] = elapsed.seconds + display.debug("%s: connect_timeout connection option has not been set" % self._task.action) return result diff --git a/test/integration/targets/reboot/aliases b/test/integration/targets/reboot/aliases new file mode 100644 index 0000000000..e9bebbf31a --- /dev/null +++ b/test/integration/targets/reboot/aliases @@ -0,0 +1,2 @@ +# No current way to split controller and test node +unsupported diff --git a/test/integration/targets/reboot/tasks/main.yml b/test/integration/targets/reboot/tasks/main.yml new file mode 100644 index 0000000000..8429a09a8b --- /dev/null +++ b/test/integration/targets/reboot/tasks/main.yml @@ -0,0 +1,50 @@ +- block: + # This block can be removed once we have a mechanism in ansible-test to separate + # the control node from the managed node. + - block: + - name: Write temp file for sanity checking this is not the controller + copy: + content: 'I am the control node' + dest: /tmp/Anything-Nutlike-Nuzzle-Plow-Overdue + delegate_to: localhost + + - name: See if the temp file exists on the managed node + stat: + path: /tmp/Anything-Nutlike-Nuzzle-Plow-Overdue + register: controller_temp_file + + - name: EXPECT FAILURE | Check if the managed node is the control node + assert: + msg: > + This test must be run manually by modifying the inventory file to point + "{{ inventory_hostname }}" at a remote host rather than "{{ ansible_host }}". + Skipping reboot test. + that: + - not controller_temp_file.stat.exists + + - name: Get current boot time + command: who -b + register: before_boot_time + + - name: Reboot with default settings + reboot: + register: reboot_result + + - name: Get current boot time + command: who -b + register: after_boot_time + + - name: Enusure system was actually rebooted + assert: + that: + - reboot_result is changed + - reboot_result.elapsed > 10 + - before_boot_time.stdout != after_boot_time.stdout + + always: + - name: Cleanup temp file + file: + path: /tmp/Anything-Nutlike-Nuzzle-Plow-Overdue + state: absent + + when: ansible_virtualization_type | default('') != 'docker'