diff --git a/providers/base/bin/watchdog_config_test.py b/providers/base/bin/watchdog_config_test.py index 67973c4e85..8795fd0038 100755 --- a/providers/base/bin/watchdog_config_test.py +++ b/providers/base/bin/watchdog_config_test.py @@ -17,13 +17,78 @@ # You should have received a copy of the GNU General Public License # along with Checkbox. If not, see . +""" +Watchdog implementation on both classic and core image no longer rely +on watchdogd service since 20.04, with this change watchdog/systemd-config +tests only systemd configuration on 20.04 and later series while keeping +the original test for prior releases +""" + import subprocess +import argparse +import shlex +import os +import re from checkbox_support.snap_utils.system import on_ubuntucore from checkbox_support.snap_utils.system import get_series +from checkbox_support.scripts.image_checker import get_source + + +def watchdog_argparse() -> argparse.Namespace: + """ + Parse command line arguments and return the parsed arguments. + This function parses the command line arguments and returns the parsed + arguments. The arguments are parsed using the `argparse` module. The + function takes no parameters. -def get_systemd_wdt_usec(): + Returns: + argparse.Namespace: The parsed command line arguments. + """ + parser = argparse.ArgumentParser( + prog="Watchdog Testing Tool", + description="This is a tool to help you perform the watchdog testing", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument( + "-t", + "--check_time", + action="store_true", + help="Check if watchdog service timeout is configured correctly", + ) + group.add_argument( + "-s", + "--check-service", + action="store_true", + help="Check if watchdog service is running", + ) + group.add_argument( + "-d", + "--detect", + action="store_true", + help="Check if there is watchdog under the /sys/class/watchdog/ " + "and no other type of watchdog is detected", + ) + group.add_argument( + "--set-timeout", + nargs="?", + const=35, + type=int, + help="Set the timeout for watchdog service", + ) + group.add_argument( + "--revert-timeout", + nargs="?", + const=35, + type=int, + help="Revert the timeout for watchdog service", + ) + return parser.parse_args() + + +def get_systemd_wdt_usec() -> str: """ Return value of systemd-watchdog RuntimeWatchdogUSec """ @@ -42,7 +107,7 @@ def get_systemd_wdt_usec(): ) -def watchdog_service_check(): +def watchdog_service_check() -> bool: """ Check if the watchdog service is configured correctly """ @@ -53,45 +118,252 @@ def watchdog_service_check(): raise SystemExit("Error: {}".format(err)) -def main(): - runtime_watchdog_usec = get_systemd_wdt_usec() - systemd_wdt_configured = runtime_watchdog_usec != "0" - wdt_service_configured = watchdog_service_check() - ubuntu_version = int(get_series().split(".")[0]) - watchdog_config_ready = True +def check_timeout() -> bool: + ubuntu_version: int = int(get_series().split(".")[0]) + runtime_watchdog_usec: str = get_systemd_wdt_usec() + is_systemd_wdt_configured: bool = runtime_watchdog_usec != "0" - if (ubuntu_version >= 20) or (on_ubuntucore()): - if not systemd_wdt_configured: - print( + if ubuntu_version >= 20 or on_ubuntucore(): + if not is_systemd_wdt_configured: + raise SystemExit( "systemd watchdog should be enabled but reset timeout: " "{}".format(runtime_watchdog_usec) ) - watchdog_config_ready = False - if wdt_service_configured: - print("found unexpected active watchdog.service unit") - watchdog_config_ready = False - if watchdog_config_ready: - print( - "systemd watchdog enabled, reset timeout: {}".format( - runtime_watchdog_usec + print( + "systemd watchdog enabled, reset timeout: {}".format( + runtime_watchdog_usec + ) + ) + else: + if is_systemd_wdt_configured: + raise SystemExit( + "systemd watchdog should not be enabled but " + "reset timeout: {}".format(runtime_watchdog_usec) + ) + print("systemd watchdog disabled") + + +def check_service() -> bool: + ubuntu_version = int(get_series().split(".")[0]) + is_wdt_service_configured = watchdog_service_check() + + if ubuntu_version >= 20 or on_ubuntucore(): + if is_wdt_service_configured: + raise SystemExit("Found unexpected active watchdog.service unit") + print("watchdog.service is not active") + else: + if not is_wdt_service_configured: + raise SystemExit("watchdog.service unit does not report as active") + print("watchdog.service is active") + + +def detect() -> None: + """ + Detects watchdog under /sys/class/watchdog/ and no other type of watchdog. + + This function executes the watchdog detection process based on the image + source. For OEM images, it runs the `udev_resource.py` script with the + argument "-f WATCHDOG". For stock images, it checks if the environment + variables "WATCHDOG_TYPE" and "WATCHDOG_IDENTITY" are set. It then iterates + over the watchdog devices under "/sys/class/watchdog/", verifies their + identities, and raises an exception if an unmatched watchdog is found. + + Raises: + SystemExit: When the image source is not recognized or when the + environment variables "WATCHDOG_TYPE" or "WATCHDOG_IDENTITY" are not + set for stock images. + """ + # Get the image source + source = get_source() + + # Handle OEM image source + if source == "oem": + cmd = "udev_resource.py -f WATCHDOG" + result = subprocess.run( + shlex.split(cmd), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=10, + text=True, + ) + if result.returncode: + raise SystemExit("[ERROR] {}".format(result.stderr.strip())) + print(result.stdout.strip()) + + # Handle stock image source + elif source == "stock": + # Check if environment variables are set + if ( + "WATCHDOG_TYPE" not in os.environ + or "WATCHDOG_IDENTITY" not in os.environ + ): + raise SystemExit( + "WATCHDOG_TYPE or WATCHDOG_IDENTITY not set!\n" + "Please define the WATCHDOG_TYPE and WATCHDOG_IDENTITY " + "in advance!" + ) + input_identities = os.environ["WATCHDOG_IDENTITY"].split(",") + + # Iterate over watchdog devices + watchdogs = os.listdir("/sys/class/watchdog") + for watchdog in watchdogs: + if not watchdog.startswith("watchdog"): + continue + + # Get the identity of the watchdog + path = "/sys/class/watchdog/{}/identity".format(watchdog) + with open(path, "r") as f: + identity = f.readline().strip() + try: + # check that the identity was expected + input_identities.remove(identity) + print("Identity of {}: {}".format(path, identity)) + # Check if the identity matches the expected identity + except KeyError: + raise SystemExit( + "Found an unmatched watchdog!\n" + "Expected: {}\n" + "Found: {}".format( + os.environ["WATCHDOG_IDENTITY"], identity + ) + ) + # Check if there are any remain watchdogs + if input_identities: + raise SystemExit( + "There are still unmatched watchdogs!\n{}".format( + input_identities ) ) - print("watchdog.service is not active") + + # Handle unrecognized image source else: - if systemd_wdt_configured: - print( - "systemd watchdog should not be enabled but reset timeout: " - "{}".format(runtime_watchdog_usec) + raise SystemExit("Unrecognized image source: {}".format(source)) + + +def set_timeout(timeout: int = 35) -> None: + """ + Sets the watchdog timeout in /etc/systemd/system.conf + and reloads configuration. + + Args: + timeout (int): Timeout value in seconds. Default is 35. + + Raises: + SystemExit: If there is an error in reloading the configuration. + """ + # Pattern to match the line containing the current watchdog timeout + pattern = r".?RuntimeWatchdogSec=.*" + + # Read the contents of /etc/systemd/system.conf + with open("/etc/systemd/system.conf", "r") as f: + text = f.read() + + # Check if the timeout is already set + if not re.search(pattern, text): + raise SystemExit("Watchdog timeout is already set") + + # Substitute the current timeout with the new one + text = re.sub( + pattern, + "RuntimeWatchdogSec={}".format(timeout), + text, + flags=re.MULTILINE, + ) + + print("Configuring Watchdog timeout...") + # Write the updated configuration to /etc/systemd/system.conf + with open("/etc/systemd/system.conf", "w") as f: + f.write(text) + + print("Reloading configuration...") + # Reload the configuration + cmd = "systemctl daemon-reexec" + res = subprocess.run( + shlex.split(cmd), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + # Raise an error if there was an error in reloading the configuration + if res.returncode: + raise SystemExit("[ERROR] {}".format(res.stderr.strip())) + + # Print the new timeout value + print("Watchdog timeout is now set to {}".format(timeout)) + + +def revert_timeout(timeout: int = 35) -> None: + """ + Revert the watchdog timeout to 0 in /etc/systemd/system.conf + and reload the configuration. + + Args: + timeout (int): The timeout value to revert to. Default is 35. + + Raises: + SystemExit: If the timeout pattern is not found in the + configuration file or if there is an error in reloading the + configuration. + """ + # Pattern to match the line containing the current watchdog timeout + pattern = "RuntimeWatchdogSec={}".format(timeout) + + # Read the contents of /etc/systemd/system.conf + with open("/etc/systemd/system.conf", "r") as f: + text = f.read() + + # Check if the timeout is already set + if not re.search(pattern, text): + raise SystemExit( + "Could not find Watchdog timeout equal to " + "{} in /etc/systemd/system.conf".format(timeout) ) - watchdog_config_ready = False - if not wdt_service_configured: - print("watchdog.service unit does not report as active") - watchdog_config_ready = False - if watchdog_config_ready: - print("systemd watchdog disabled") - print("watchdog.service active") - - raise SystemExit(not watchdog_config_ready) + + # Substitute the current timeout with 0 + text = re.sub( + pattern, + "#RuntimeWatchdogSec=0", + text, + flags=re.MULTILINE, + ) + + print("Configuring Watchdog timeout...") + # Write the updated configuration to /etc/systemd/system.conf + with open("/etc/systemd/system.conf", "w") as f: + f.write(text) + + print("Reloading configuration...") + # Reload the configuration + cmd = "systemctl daemon-reexec" + res = subprocess.run( + shlex.split(cmd), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + + # Raise an error if there was an error in reloading the configuration + if res.returncode: + raise SystemExit("[ERROR] {}".format(res.stderr.strip())) + + # Print the new timeout value + print("Watchdog timeout is now set to 0 and disabled") + + +def main(): + args = watchdog_argparse() + if args.check_time: + check_timeout() + elif args.check_service: + check_service() + elif args.detect: + detect() + elif args.set_timeout: + set_timeout(args.set_timeout) + elif args.revert_timeout: + revert_timeout(args.revert_timeout) + else: + raise SystemExit("Unexpected arguments") if __name__ == "__main__": diff --git a/providers/base/tests/test_watchdog_config_test.py b/providers/base/tests/test_watchdog_config_test.py new file mode 100644 index 0000000000..f9d6ddacea --- /dev/null +++ b/providers/base/tests/test_watchdog_config_test.py @@ -0,0 +1,465 @@ +import unittest +import argparse +from unittest.mock import patch, Mock, MagicMock +from watchdog_config_test import ( + watchdog_argparse, + get_systemd_wdt_usec, + watchdog_service_check, + main, +) + + +class TestWatchdogConfigTest(unittest.TestCase): + + @patch( + "argparse.ArgumentParser.parse_args", + return_value=argparse.Namespace(check_time=True, check_service=False), + ) + def test_check_time_argument(self, mock_parse_args): + result = watchdog_argparse() + self.assertTrue(result.check_time) + self.assertFalse(result.check_service) + + @patch( + "argparse.ArgumentParser.parse_args", + return_value=argparse.Namespace(check_time=False, check_service=True), + ) + def test_check_service_argument(self, mock_parse_args): + result = watchdog_argparse() + self.assertFalse(result.check_time) + self.assertTrue(result.check_service) + + @patch("watchdog_config_test.subprocess.check_output") + def test_get_systemd_wdt_usec_success(self, mock_check_output): + # Mock subprocess.check_output to return a mock result + mock_check_output.return_value = "RuntimeWatchdogUSec=1000000\n" + + # Call the function under test + result = get_systemd_wdt_usec() + + # Assert that subprocess.check_output was called + # with the correct arguments + mock_check_output.assert_called_once_with( + ["systemctl", "show", "-p", "RuntimeWatchdogUSec"], + universal_newlines=True, + ) + + # Assert that the correct value was returned + self.assertEqual(result, "1000000") + + @patch("watchdog_config_test.subprocess.check_output") + def test_get_systemd_wdt_usec_exception(self, mock_check_output): + # Mock subprocess.check_output to raise an exception + mock_check_output.side_effect = Exception("Something went wrong") + + # Call the function under test + with self.assertRaises(SystemExit): + get_systemd_wdt_usec() + + @patch("watchdog_config_test.subprocess.check_output") + def test_get_systemd_wdt_usec_no_result(self, mock_check_output): + # Mock subprocess.check_output to return an empty result + mock_check_output.return_value = "" + + # Call the function under test + with self.assertRaises(SystemExit): + get_systemd_wdt_usec() + + @patch("watchdog_config_test.subprocess.run") + def test_watchdog_service_check_active(self, mock_subprocess_run): + # Mock subprocess.run to return a process with returncode 0 (active) + mock_process = Mock(returncode=0) + mock_subprocess_run.return_value = mock_process + + # Call the function under test + result = watchdog_service_check() + + # Assert that subprocess.run was called with the correct arguments + mock_subprocess_run.assert_called_once_with( + ["systemctl", "is-active", "watchdog.service", "--quiet"] + ) + + # Assert that the correct value was returned + self.assertTrue(result) + + @patch("watchdog_config_test.subprocess.run") + def test_watchdog_service_check_inactive(self, mock_subprocess_run): + # Mock subprocess.run to return a process with returncode 1 (inactive) + mock_process = Mock(returncode=1) + mock_subprocess_run.return_value = mock_process + + # Call the function under test + result = watchdog_service_check() + + # Assert that subprocess.run was called with the correct arguments + mock_subprocess_run.assert_called_once_with( + ["systemctl", "is-active", "watchdog.service", "--quiet"] + ) + + # Assert that the correct value was returned + self.assertFalse(result) + + @patch("watchdog_config_test.subprocess.run") + def test_watchdog_service_check_exception(self, mock_subprocess_run): + # Mock subprocess.run to raise an exception + mock_subprocess_run.side_effect = Exception("Something went wrong") + + # Call the function under test + with self.assertRaises(SystemExit): + watchdog_service_check() + + @patch("watchdog_config_test.watchdog_argparse") + @patch("watchdog_config_test.get_series") + @patch("watchdog_config_test.get_systemd_wdt_usec") + @patch("watchdog_config_test.on_ubuntucore") + @patch("watchdog_config_test.watchdog_service_check") + def test_main_check_time_and_service( + self, + mock_watchdog_service_check, + mock_on_ubuntucore, + mock_get_systemd_wdt_usec, + mock_get_series, + mock_watchdog_argparse, + ): + # Mock arguments + mock_args = MagicMock() + mock_args.check_time = True + mock_args.check_service = True + mock_watchdog_argparse.return_value = mock_args + + # Mock get_series + mock_get_series.return_value = "20.04" + + # Mock on_ubuntucore + mock_on_ubuntucore.return_value = False + + # Mock get_systemd_wdt_usec + mock_get_systemd_wdt_usec.return_value = "1000000" + + # Mock watchdog_service_check + mock_watchdog_service_check.return_value = False + + # Call the function under test + with patch("builtins.print") as mock_print: + main() + + # Assert that the expected messages are printed + mock_print.assert_any_call( + "systemd watchdog enabled, reset timeout: 1000000" + ) + mock_print.assert_any_call("watchdog.service is not active") + + @patch("watchdog_config_test.watchdog_argparse") + @patch("watchdog_config_test.get_series") + @patch("watchdog_config_test.get_systemd_wdt_usec") + @patch("watchdog_config_test.on_ubuntucore") + @patch("watchdog_config_test.watchdog_service_check") + def test_main_check_time_ubuntucore( + self, + mock_watchdog_service_check, + mock_on_ubuntucore, + mock_get_systemd_wdt_usec, + mock_get_series, + mock_watchdog_argparse, + ): + # Mock arguments + mock_args = MagicMock() + mock_args.check_time = True + mock_args.check_service = False + mock_watchdog_argparse.return_value = mock_args + + # Mock get_systemd_wdt_usec + mock_get_systemd_wdt_usec.return_value = 1000000 + + # Mock get_series + mock_get_series.return_value = "18.04" + + # Mock on_ubuntucore + mock_on_ubuntucore.return_value = True + # Call the function under test + with patch("builtins.print") as mock_print: + main() + + # Assert that the expected messages are printed + mock_print.assert_any_call( + "systemd watchdog enabled, reset timeout: 1000000" + ) + + @patch("watchdog_config_test.watchdog_argparse") + @patch("watchdog_config_test.get_series") + @patch("watchdog_config_test.get_systemd_wdt_usec") + @patch("watchdog_config_test.on_ubuntucore") + @patch("watchdog_config_test.watchdog_service_check") + def test_main_check_time_not_active( + self, + mock_watchdog_service_check, + mock_on_ubuntucore, + mock_get_systemd_wdt_usec, + mock_get_series, + mock_watchdog_argparse, + ): + # Mock arguments + mock_args = MagicMock() + mock_args.check_time = True + mock_args.check_service = False + mock_watchdog_argparse.return_value = mock_args + + # Mock get_series + mock_get_series.return_value = "20.04" + + # Mock on_ubuntucore + mock_on_ubuntucore.return_value = False + + # Mock get_systemd_wdt_usec + mock_get_systemd_wdt_usec.return_value = "0" + + # Call the function under test + with patch("builtins.print") as mock_print: + main() + + # Assert that the expected message is printed + mock_print.assert_any_call( + "systemd watchdog should be enabled but reset timeout: 0" + ) + + @patch("watchdog_config_test.watchdog_argparse") + @patch("watchdog_config_test.get_series") + @patch("watchdog_config_test.get_systemd_wdt_usec") + @patch("watchdog_config_test.on_ubuntucore") + @patch("watchdog_config_test.watchdog_service_check") + def test_main_check_time_and_systemd_wdt_configured( + self, + mock_watchdog_service_check, + mock_on_ubuntucore, + mock_get_systemd_wdt_usec, + mock_get_series, + mock_watchdog_argparse, + ): + # Mock arguments + mock_args = MagicMock() + mock_args.check_time = True + mock_args.check_service = False + mock_watchdog_argparse.return_value = mock_args + + # Mock get_series + mock_get_series.return_value = "20.04" + + # Mock on_ubuntucore + mock_on_ubuntucore.return_value = False + + # Mock get_systemd_wdt_usec + mock_get_systemd_wdt_usec.return_value = "1000000" + + # Call the function under test + with patch("builtins.print") as mock_print: + main() + # Assert that the expected messages are printed + mock_print.assert_any_call( + "systemd watchdog enabled, reset timeout: 1000000" + ) + + @patch("watchdog_config_test.watchdog_argparse") + @patch("watchdog_config_test.get_series") + @patch("watchdog_config_test.get_systemd_wdt_usec") + @patch("watchdog_config_test.on_ubuntucore") + @patch("watchdog_config_test.watchdog_service_check") + def test_main_check_time_and_watchdog_config_ready( + self, + mock_watchdog_service_check, + mock_on_ubuntucore, + mock_get_systemd_wdt_usec, + mock_get_series, + mock_watchdog_argparse, + ): + # Mock arguments + mock_args = MagicMock() + mock_args.check_time = True + mock_args.check_service = False + mock_watchdog_argparse.return_value = mock_args + + # Mock get_series + mock_get_series.return_value = "18.04" + + # Mock on_ubuntucore + mock_on_ubuntucore.return_value = False + + # Mock get_systemd_wdt_usec + mock_get_systemd_wdt_usec.return_value = "0" + + # Call the function under test + with patch("builtins.print") as mock_print: + main() + # Assert that the expected messages are printed + mock_print.assert_any_call("systemd watchdog disabled") + + @patch("watchdog_config_test.watchdog_argparse") + @patch("watchdog_config_test.get_series") + @patch("watchdog_config_test.get_systemd_wdt_usec") + @patch("watchdog_config_test.on_ubuntucore") + @patch("watchdog_config_test.watchdog_service_check") + def test_main_check_time_is_systemd_wdt_configured( + self, + mock_watchdog_service_check, + mock_on_ubuntucore, + mock_get_systemd_wdt_usec, + mock_get_series, + mock_watchdog_argparse, + ): + # Mock arguments + mock_args = MagicMock() + mock_args.check_time = True + mock_args.check_service = False + mock_watchdog_argparse.return_value = mock_args + + # Mock get_series + mock_get_series.return_value = "18.04" + + # Mock on_ubuntucore + mock_on_ubuntucore.return_value = False + + # Mock get_systemd_wdt_usec + mock_get_systemd_wdt_usec.return_value = "1000000" + + # Call the function under test + with patch("builtins.print") as mock_print: + main() + # Assert that the expected messages are printed + mock_print.assert_any_call( + "systemd watchdog should not be enabled but reset timeout: 1000000" + ) + + @patch("watchdog_config_test.watchdog_argparse") + @patch("watchdog_config_test.get_series") + @patch("watchdog_config_test.on_ubuntucore") + @patch("watchdog_config_test.watchdog_service_check") + def test_main_check_service_ubuntucore_not_active( + self, + mock_watchdog_service_check, + mock_on_ubuntucore, + mock_get_series, + mock_watchdog_argparse, + ): + # Mock arguments + mock_args = MagicMock() + mock_args.check_time = False + mock_args.check_service = True + mock_watchdog_argparse.return_value = mock_args + + # Mock get_series + mock_get_series.return_value = "20.04" + + # Mock on_ubuntucore + mock_on_ubuntucore.return_value = False + + # Mock watchdog_service_check + mock_watchdog_service_check.return_value = False + + # Call the function under test + with patch("builtins.print") as mock_print: + main() + + # Assert that the expected message is printed + mock_print.assert_any_call("watchdog.service is not active") + + @patch("watchdog_config_test.watchdog_argparse") + @patch("watchdog_config_test.get_series") + @patch("watchdog_config_test.on_ubuntucore") + @patch("watchdog_config_test.watchdog_service_check") + def test_main_check_service_ubuntucore_is_wdt_service_configured( + self, + mock_watchdog_service_check, + mock_on_ubuntucore, + mock_get_series, + mock_watchdog_argparse, + ): + # Mock arguments + mock_args = MagicMock() + mock_args.check_time = False + mock_args.check_service = True + mock_watchdog_argparse.return_value = mock_args + + # Mock get_series + mock_get_series.return_value = "20.04" + + # Mock on_ubuntucore + mock_on_ubuntucore.return_value = False + + # Mock watchdog_service_check + mock_watchdog_service_check.return_value = True + + # Call the function under test + with patch("builtins.print") as mock_print: + main() + + # Assert that the expected message is printed + mock_print.assert_any_call( + "found unexpected active watchdog.service unit" + ) + + @patch("watchdog_config_test.watchdog_argparse") + @patch("watchdog_config_test.get_series") + @patch("watchdog_config_test.on_ubuntucore") + @patch("watchdog_config_test.watchdog_service_check") + def test_main_check_service_active( + self, + mock_watchdog_service_check, + mock_on_ubuntucore, + mock_get_series, + mock_watchdog_argparse, + ): + # Mock arguments + mock_args = MagicMock() + mock_args.check_time = False + mock_args.check_service = True + mock_watchdog_argparse.return_value = mock_args + + # Mock get_series + mock_get_series.return_value = "18.04" + + # Mock on_ubuntucore + mock_on_ubuntucore.return_value = False + + # Mock watchdog_service_check + mock_watchdog_service_check.return_value = True + + # Call the function under test + with patch("builtins.print") as mock_print: + main() + + # Assert that the expected message is printed + mock_print.assert_any_call("watchdog.service is active") + + @patch("watchdog_config_test.watchdog_argparse") + @patch("watchdog_config_test.get_series") + @patch("watchdog_config_test.on_ubuntucore") + @patch("watchdog_config_test.watchdog_service_check") + def test_main_check_service_is_wdt_service_configured( + self, + mock_watchdog_service_check, + mock_on_ubuntucore, + mock_get_series, + mock_watchdog_argparse, + ): + # Mock arguments + mock_args = MagicMock() + mock_args.check_time = False + mock_args.check_service = True + mock_watchdog_argparse.return_value = mock_args + + # Mock get_series + mock_get_series.return_value = "18.04" + + # Mock on_ubuntucore + mock_on_ubuntucore.return_value = False + + # Mock watchdog_service_check + mock_watchdog_service_check.return_value = False + + # Call the function under test + with patch("builtins.print") as mock_print: + main() + + # Assert that the expected message is printed + mock_print.assert_any_call( + "watchdog.service unit does not report as active" + ) diff --git a/providers/base/units/watchdog/jobs.pxu b/providers/base/units/watchdog/jobs.pxu index dea139f14d..e526d22cd8 100644 --- a/providers/base/units/watchdog/jobs.pxu +++ b/providers/base/units/watchdog/jobs.pxu @@ -1,44 +1,139 @@ -id: watchdog/detect +id: watchdog/check-timeout category_id: com.canonical.plainbox::power-management -_summary: Detect the presence of a Hardware Watchdog +_summary: Check the timeout of Hardware Watchdog +_description: + Check the value of RuntimeWatchdogUSec that shouldn't be 0 or unset + in OEM images because that means that the watchdog is disabled. flags: simple imports: from com.canonical.plainbox import manifest -requires: manifest.has_hardware_watchdog == 'True' -command: udev_resource.py -f WATCHDOG +requires: + manifest.has_hardware_watchdog == 'True' + image_source_and_type.source == 'oem' +command: watchdog_config_test.py --check-time + -id: watchdog/systemd-config -_summary: Check if the hardware watchdog is properly configured -template-engine: jinja2 -command: watchdog_config_test.py +id: watchdog/check-service category_id: com.canonical.plainbox::power-management +_summary: Check the watchdog.service is enabled or not +_description: + Check the state of the watchdog.service. It must be disabled/missing + on both classic and core images since 20.04. Else it must be enabled. flags: simple imports: from com.canonical.plainbox import manifest -requires: manifest.has_hardware_watchdog == 'True' +requires: + manifest.has_hardware_watchdog == 'True' +command: watchdog_config_test.py --check-service -id: watchdog/trigger-system-reset-auto -depends: watchdog/systemd-config -_summary: Test that the watchdog module can trigger a system reset + +id: watchdog/probe-module +category_id: com.canonical.plainbox::power-management +_summary: Probe the suitable module for watchdog +_description: + Probe the module 'WATCHDOG_TYPE'. + This job can only be execute on the Stock Classic image, + because the watchdog module is not probed automatically. +user: root +flags: simple +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_hardware_watchdog == 'True' + image_source_and_type.source == 'stock' + image_source_and_type.type == 'classic' +environ: WATCHDOG_TYPE +command: + if [[ -z "$WATCHDOG_TYPE" ]]; then + >&2 echo "WATCHDOG_TYPE is not available" + exit 1 + fi + echo "Trying to probe '$WATCHDOG_TYPE' module" + if ! modprobe "$WATCHDOG_TYPE"; then + >&2 echo "Unable to probe the '$WATCHDOG_TYPE' module" + exit 1 + fi + if ! lsmod | grep -q -i "$WATCHDOG_TYPE"; then + >&2 echo "Unable to find the '$WATCHDOG_TYPE' module after probing it" + exit 1 + fi + + +id: watchdog/detect +category_id: com.canonical.plainbox::power-management +_summary: Detect presence of a Hardware Watchdog +_description: + Detect the watchdog is under the /sys/class/watchdog/ path and no other type of watchdog +flags: simple +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_hardware_watchdog == 'True' +environ: WATCHDOG_TYPE WATCHDOG_IDENTITY +command: + watchdog_config_test.py --detect + + +id: watchdog/set-timeout +category_id: com.canonical.plainbox::power-management +_summary: Configure the timeout for Hardware Watchdog +_description: + Configure the value of RuntimeWatchdogSec +flags: simple +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_hardware_watchdog == 'True' + image_source_and_type.source == 'stock' +depends: + watchdog/check-service + watchdog/detect +user: root +environ: WATCHDOG_TIMEOUT command: - sync - sleep 5 - echo 1 > /proc/sys/kernel/sysrq - echo 0 > /proc/sys/kernel/panic - echo c > /proc/sysrq-trigger -flags: preserve-locale noreturn autorestart + watchdog_config_test.py --set-timeout ${WATCHDOG_TIMEOUT:-35} + + +id: watchdog/revert-timeout +category_id: com.canonical.plainbox::power-management +_summary: Restore the timeout for Hardware Watchdog +_description: + Restore the value of RuntimeWatchdogSec +flags: simple +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_hardware_watchdog == 'True' + image_source_and_type.source == 'stock' +depends: + watchdog/set-timeout +environ: WATCHDOG_TIMEOUT user: root +command: + watchdog_config_test.py --revert-timeout ${WATCHDOG_TIMEOUT:-35} + + +id: watchdog/trigger-system-reset-auto plugin: shell category_id: com.canonical.plainbox::power-management +_summary: Test that the watchdog module can trigger a system reset +user: root +flags: noreturn autorestart estimated_duration: 60 -_purpose: Ensure that the watchdog module can successfully initiate a system reset. +depends: + watchdog/check-service + watchdog/detect +command: + sync + sleep 5 + echo 1 > /proc/sys/kernel/sysrq + echo 0 > /proc/sys/kernel/panic + echo c > /proc/sysrq-trigger + id: watchdog/post-trigger-system-reset-auto -after: watchdog/trigger-system-reset-auto +plugin: shell category_id: com.canonical.plainbox::power-management _summary: Post watchdog reset service check -_purpose: Check there are no failed services after the watchdog triggered -unit: job -plugin: shell -command: failed_service_check.sh +_description: Check there are no failed services after the watchdog triggered estimated_duration: 1.0 imports: from com.canonical.plainbox import manifest -requires: manifest.has_hardware_watchdog == 'True' +requires: + manifest.has_hardware_watchdog == 'True' +depends: + watchdog/trigger-system-reset-auto +command: failed_service_check.sh diff --git a/providers/base/units/watchdog/test-plan.pxu b/providers/base/units/watchdog/test-plan.pxu index dd2ecb1404..70e2c7c416 100644 --- a/providers/base/units/watchdog/test-plan.pxu +++ b/providers/base/units/watchdog/test-plan.pxu @@ -1,7 +1,7 @@ id: watchdog-full unit: test plan _name: Watchdog tests -_description: Watchdog tests for Ubuntu Core devices +_description: Watchdog tests include: nested_part: watchdog-manual @@ -10,15 +10,21 @@ nested_part: id: watchdog-manual unit: test plan _name: Manual watchdog tests -_description: Manual watchdog tests for Ubuntu Core devices +_description: Manual watchdog tests include: id: watchdog-automated unit: test plan _name: Automated watchdog tests -_description: Automated watchdog tests for Ubuntu Core devices +_description: Automated watchdog tests include: + watchdog/check-timeout + watchdog/check-service + watchdog/probe-module watchdog/detect - watchdog/systemd-config + watchdog/set-timeout watchdog/trigger-system-reset-auto watchdog/post-trigger-system-reset-auto + watchdog/revert-timeout +bootstrap_include: + image_source_and_type