diff options
author | Maxim Devaev <[email protected]> | 2021-09-23 17:03:10 +0300 |
---|---|---|
committer | Maxim Devaev <[email protected]> | 2021-09-23 17:12:15 +0300 |
commit | ab92a2d7084dea554edb9d5d0032327b302ef9f3 (patch) | |
tree | 209bc6045d9dd3ea1e40a9b0d9559f7ddb67d852 | |
parent | 8d53c89a6aa3e4b5e9729aeadf8c0a20b985081d (diff) |
rtc watchdog for v3
-rw-r--r-- | configs/os/services/kvmd-watchdog.service | 14 | ||||
-rw-r--r-- | kvmd/apps/__init__.py | 7 | ||||
-rw-r--r-- | kvmd/apps/watchdog/__init__.py | 119 | ||||
-rw-r--r-- | kvmd/apps/watchdog/__main__.py | 24 | ||||
-rwxr-xr-x | setup.py | 2 |
5 files changed, 166 insertions, 0 deletions
diff --git a/configs/os/services/kvmd-watchdog.service b/configs/os/services/kvmd-watchdog.service new file mode 100644 index 00000000..b6f4c45e --- /dev/null +++ b/configs/os/services/kvmd-watchdog.service @@ -0,0 +1,14 @@ +[Unit] +Description=PiKVM - RTC-based hardware watchdog +After=systemd-modules-load.service + +[Service] +Type=simple +Restart=always +RestartSec=3 + +ExecStart=/usr/bin/kvmd-watchdog run +TimeoutStopSec=3 + +[Install] +WantedBy=multi-user.target diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py index 348f2f0d..b3497682 100644 --- a/kvmd/apps/__init__.py +++ b/kvmd/apps/__init__.py @@ -64,6 +64,7 @@ from ..validators.basic import valid_stripped_string from ..validators.basic import valid_stripped_string_not_empty from ..validators.basic import valid_bool from ..validators.basic import valid_number +from ..validators.basic import valid_int_f0 from ..validators.basic import valid_int_f1 from ..validators.basic import valid_float_f0 from ..validators.basic import valid_float_f01 @@ -691,4 +692,10 @@ def _get_config_scheme() -> Dict: "cmd_remove": Option([], type=valid_options), "cmd_append": Option([], type=valid_options), }, + + "watchdog": { + "device": Option(0, type=valid_int_f0), + "timeout": Option(300, type=valid_int_f1), + "interval": Option(30, type=valid_int_f1), + }, } diff --git a/kvmd/apps/watchdog/__init__.py b/kvmd/apps/watchdog/__init__.py new file mode 100644 index 00000000..0caa39a6 --- /dev/null +++ b/kvmd/apps/watchdog/__init__.py @@ -0,0 +1,119 @@ +# ========================================================================== # +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2021 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +# ========================================================================== # + + +import argparse +import errno +import time + +from typing import List +from typing import Optional + +from ...logging import get_logger + +from ...yamlconf import Section + +from ... import env + +from .. import init + + +# ===== +class RtcIsNotAvailableError(Exception): + pass + + +# ===== +def _join_rtc(device: int, key: str) -> str: + return f"{env.SYSFS_PREFIX}/sys/class/rtc/rtc{device}/{key}" + + +def _read_int(device: int, key: str) -> int: + with open(_join_rtc(device, key)) as value_file: + return int(value_file.read().strip() or "0") + + +def _write_int(device: int, key: str, value: int) -> None: + with open(_join_rtc(device, key), "w") as value_file: + value_file.write(str(value)) + + +def _reset_alarm(device: int, timeout: int) -> None: + now = _read_int(device, "since_epoch") + if now == 0: + raise RtcIsNotAvailableError("Current UNIX time == 0") + try: + for wake in [0, now + timeout]: + _write_int(device, "wakealarm", wake) + except OSError as err: + if err.errno != errno.EIO: + raise + raise RtcIsNotAvailableError("IO error, probably the supercapacitor is not charged") + + +# ===== +def _cmd_run(config: Section) -> None: + logger = get_logger(0) + logger.info("Running watchdog loop on RTC%d ...", config.device) + fail = False + try: + while True: + try: + _reset_alarm(config.device, config.timeout) + except RtcIsNotAvailableError as err: + if not fail: + logger.error("RTC%d is not available now: %s; waiting ...", config.device, err) + fail = True + else: + if fail: + logger.info("RTC%d is available, working ...", config.device) + fail = False + time.sleep(config.interval) + except (SystemExit, KeyboardInterrupt): + if not fail: + _reset_alarm(config.device, config.timeout) + logger.info("The watchdog remains alarmed. Use 'kvmd-watchdog cancel' to disarm it") + logger.info("Bye-bye") + + +def _cmd_cancel(config: Section) -> None: + _write_int(config.device, "wakealarm", 0) + + +# ===== +def main(argv: Optional[List[str]]=None) -> None: + (parent_parser, argv, config) = init(add_help=False, argv=argv) + parser = argparse.ArgumentParser( + prog="kvmd-watchdog", + description="RTC-based hardware watchdog", + parents=[parent_parser], + ) + parser.set_defaults(cmd=(lambda *_: parser.print_help())) + subparsers = parser.add_subparsers() + + cmd_run_parser = subparsers.add_parser("run", help="Run watchdog loop") + cmd_run_parser.set_defaults(cmd=_cmd_run) + + cmd_cancel_parser = subparsers.add_parser("cancel", help="Cancel armed timeout") + cmd_cancel_parser.set_defaults(cmd=_cmd_cancel) + + options = parser.parse_args(argv[1:]) + options.cmd(config.watchdog) diff --git a/kvmd/apps/watchdog/__main__.py b/kvmd/apps/watchdog/__main__.py new file mode 100644 index 00000000..0d101204 --- /dev/null +++ b/kvmd/apps/watchdog/__main__.py @@ -0,0 +1,24 @@ +# ========================================================================== # +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2021 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +# ========================================================================== # + + +from . import main +main() @@ -106,6 +106,7 @@ def main() -> None: "kvmd.apps.vnc", "kvmd.apps.vnc.rfb", "kvmd.apps.janus", + "kvmd.apps.watchdog", "kvmd.helpers", "kvmd.helpers.otgmsd", "kvmd.helpers.otgmsd.unlock", @@ -133,6 +134,7 @@ def main() -> None: "kvmd-ipmi = kvmd.apps.ipmi:main", "kvmd-vnc = kvmd.apps.vnc:main", "kvmd-janus = kvmd.apps.janus:main", + "kvmd-watchdog = kvmd.apps.watchdog:main", "kvmd-helper-otgmsd-unlock = kvmd.helpers.otgmsd.unlock:main", "kvmd-helper-otgmsd-remount = kvmd.helpers.otgmsd.remount:main", ], |