-
Notifications
You must be signed in to change notification settings - Fork 163
Add systemd soft reboot functionality #4304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -478,6 +478,17 @@ class GuestCapability(enum.Enum): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SYSLOG_ACTION_READ_CLEAR = 'syslog-action-read-clear' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class RebootMode(enum.Enum): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Different reboot detection modes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #: Standard reboot detection using boot time from /proc/stat | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BOOT_TIME = 'boot_time' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #: Systemd soft reboot detection using boot ID from /proc/sys/kernel/random/boot_id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SYSTEMD_SOFT = 'systemd_soft' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @container | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class GuestFacts(SerializableContainer): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2072,6 +2083,56 @@ def reboot( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise NotImplementedError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def reboot_systemd_soft( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| command: Optional[Union[Command, ShellScript]] = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| waiting: Optional[Waiting] = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Perform a systemd soft reboot, and wait for the guest to recover. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Systemd soft reboot preserves the kernel and hardware state while | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| restarting userspace. The boot ID remains the same after a soft reboot. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param command: a command to run on the guest to trigger the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| soft reboot. Defaults to 'systemctl soft-reboot'. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param waiting: amount of time in which the guest must become available | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| again. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :returns: ``True`` if the reboot succeeded, ``False`` otherwise. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| waiting = waiting or default_reboot_waiting() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Default command for systemd soft reboot | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not command: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| command = ShellScript('systemctl soft-reboot') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check if systemd soft-reboot is supported | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check_result = self.execute( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ShellScript('systemctl --help | grep -q "soft-reboot"'), silent=True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if check_result.returncode != 0: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise tmt.steps.provision.RebootModeNotSupportedError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guest=self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hard=False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message="systemctl soft-reboot is not supported on this guest", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as exc: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise tmt.steps.provision.RebootModeNotSupportedError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guest=self, hard=False, message="Cannot check for systemctl soft-reboot support" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) from exc | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.debug(f"Triggering systemd soft reboot with command '{command}'.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Use generalized perform_reboot with systemd soft reboot mode | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return self.perform_reboot( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lambda: self.execute(command, silent=True), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| waiting, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mode=RebootMode.SYSTEMD_SOFT, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetch_reboot_marker=True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def reconnect( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wait: Optional[Waiting] = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -3016,7 +3077,8 @@ def perform_reboot( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| action: Callable[[], Any], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wait: Waiting, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetch_boot_time: bool = True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mode: RebootMode = RebootMode.BOOT_TIME, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetch_reboot_marker: bool = True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Perform the actual reboot and wait for the guest to recover. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -3027,15 +3089,11 @@ def perform_reboot( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :py:meth:`perform_reboot` with the right ``action`` callable. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param action: a callable which will trigger the requested reboot. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param timeout: amount of time in which the guest must become available | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| again. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param tick: how many seconds to wait between two consecutive attempts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| of contacting the guest. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param tick_increase: a multiplier applied to ``tick`` after every | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attempt. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param fetch_boot_time: if set, the current boot time of the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guest would be read first, and used for testing whether the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reboot has been performed. This will require communication | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param wait: waiting configuration for guest recovery. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param mode: reboot detection mode to use. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param fetch_reboot_marker: if set, the current reboot marker | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (boot time or boot ID) would be read first, and used for testing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whether the reboot has been performed. This will require communication | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with the guest, therefore it is recommended to use ``False`` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with hard reboot of unhealthy guests. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :returns: ``True`` if the reboot succeeded, ``False`` otherwise. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -3056,7 +3114,26 @@ def get_boot_time() -> int: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return int(match.group(1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_boot_time = get_boot_time() if fetch_boot_time else 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_boot_id() -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Reads boot ID from /proc/sys/kernel/random/boot_id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = self.execute(ShellScript('cat /proc/sys/kernel/random/boot_id'), silent=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result.stdout.strip() if result.stdout else "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Get the current reboot marker based on mode | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_marker: Union[int, str] = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if fetch_reboot_marker: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if mode == RebootMode.BOOT_TIME: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_marker = get_boot_time() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif mode == RebootMode.SYSTEMD_SOFT: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_marker = get_boot_id() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.debug("Could not get boot ID, falling back to boot time detection") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mode = RebootMode.BOOT_TIME | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_marker = get_boot_time() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.debug(f"Triggering reboot with '{action}'.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -3071,25 +3148,41 @@ def get_boot_time() -> int: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Wait until we get new boot time, connection will drop and will be | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # unreachable for some time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def check_boot_time() -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Wait until reboot marker changes (or stays the same for systemd soft reboot) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def check_reboot_completion() -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new_boot_time = get_boot_time() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if new_boot_time != current_boot_time: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Different boot time and we are reconnected | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check if we can connect | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.execute(Command('whoami'), silent=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Same boot time, reboot didn't happen yet, retrying | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise tmt.utils.wait.WaitingIncompleteError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Get new marker based on mode | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if mode == RebootMode.BOOT_TIME: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new_marker = get_boot_time() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if new_marker != current_marker: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Different boot time and we are reconnected | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Same boot time, reboot didn't happen yet, retrying | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise tmt.utils.wait.WaitingIncompleteError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if mode == RebootMode.SYSTEMD_SOFT: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new_marker = get_boot_id() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # For systemd soft reboot, boot ID should stay the same | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if new_marker == current_marker: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.debug( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Systemd soft reboot completed successfully (boot ID unchanged)." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.debug( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Boot ID changed from {current_marker} to {new_marker}, " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "this might indicate a hard reboot occurred instead." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return # Still accept it as the guest is back up | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3166
to
+3178
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical logic bug in systemd soft reboot detection. The code accepts when The code needs to:
For example, add a flag to track if connection was lost: connection_dropped = False
try:
self.execute(Command('whoami'), silent=True)
if not connection_dropped and mode == RebootMode.SYSTEMD_SOFT:
# Still connected, reboot hasn't happened
raise tmt.utils.wait.WaitingIncompleteError
except tmt.utils.RunError:
connection_dropped = True
raise tmt.utils.wait.WaitingIncompleteError
if mode == RebootMode.SYSTEMD_SOFT and connection_dropped:
# Now check boot ID is same
Suggested change
Spotted by Graphite Agent |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except tmt.utils.RunError as error: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.debug('Failed to connect to the guest.') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise tmt.utils.wait.WaitingIncompleteError from error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wait.wait(check_boot_time, self._logger) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wait.wait(check_reboot_completion, self._logger) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except tmt.utils.wait.WaitingTimedOutError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.debug("Connection to guest failed after reboot.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.