Skip to content

Commit 587964e

Browse files
committed
T7101: Add support for hardware watchdog support via systemd
1 parent b50808a commit 587964e

File tree

4 files changed

+236
-0
lines changed

4 files changed

+236
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### Autogenerated by system_watchdog.py ###
2+
[Manager]
3+
RuntimeWatchdogSec={{ timeout }}
4+
ShutdownWatchdogSec={{ shutdown_timeout }}
5+
RebootWatchdogSec={{ reboot_timeout }}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?xml version="1.0"?>
2+
<interfaceDefinition>
3+
<node name="system">
4+
<children>
5+
<node name="watchdog" owner="${vyos_conf_scripts_dir}/system_watchdog.py">
6+
<properties>
7+
<help>Hardware watchdog configuration</help>
8+
<priority>9999</priority>
9+
</properties>
10+
<children>
11+
12+
<leafNode name="module">
13+
<properties>
14+
<help>Kernel module to load for watchdog device (optional)</help>
15+
<valueHelp>
16+
<format>txt</format>
17+
<description>Module name (e.g. 'softdog', 'iTCO_wdt', 'sp5100_tco')</description>
18+
</valueHelp>
19+
<constraint>
20+
<regex>[a-zA-Z0-9_\-]+</regex>
21+
</constraint>
22+
<constraintErrorMessage>Module name must be alphanumeric/underscore/hyphen</constraintErrorMessage>
23+
</properties>
24+
</leafNode>
25+
<leafNode name="timeout">
26+
<properties>
27+
<help>Watchdog timeout for runtime in seconds (1-86400)</help>
28+
<valueHelp>
29+
<format>u32:1-86400</format>
30+
<description>Seconds</description>
31+
</valueHelp>
32+
<validator name="numeric" argument="--range 1-86400"/>
33+
<constraintErrorMessage>Timeout must be between 1 and 86400 seconds</constraintErrorMessage>
34+
</properties>
35+
<defaultValue>10</defaultValue>
36+
</leafNode>
37+
<leafNode name="shutdown-timeout">
38+
<properties>
39+
<help>Watchdog timeout during shutdown in seconds (1-86400)</help>
40+
<valueHelp>
41+
<format>u32:1-86400</format>
42+
<description>Seconds</description>
43+
</valueHelp>
44+
<validator name="numeric" argument="--range 1-86400"/>
45+
<constraintErrorMessage>Shutdown timeout must be between 1 and 86400 seconds</constraintErrorMessage>
46+
</properties>
47+
<defaultValue>120</defaultValue>
48+
</leafNode>
49+
<leafNode name="reboot-timeout">
50+
<properties>
51+
<help>Watchdog timeout during reboot in seconds (1-86400)</help>
52+
<valueHelp>
53+
<format>u32:1-86400</format>
54+
<description>Seconds</description>
55+
</valueHelp>
56+
<validator name="numeric" argument="--range 1-86400"/>
57+
<constraintErrorMessage>Reboot timeout must be between 1 and 86400 seconds</constraintErrorMessage>
58+
</properties>
59+
<defaultValue>120</defaultValue>
60+
</leafNode>
61+
</children>
62+
</node>
63+
</children>
64+
</node>
65+
</interfaceDefinition>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env python3
2+
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
3+
#
4+
# This program is free software; you can redistribute it and/or modify
5+
# it under the terms of the GNU General Public License version 2 or later as
6+
# published by the Free Software Foundation.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
import os
17+
import unittest
18+
19+
from base_vyostest_shim import VyOSUnitTestSHIM
20+
from vyos.utils.process import cmd
21+
22+
base_path = ['system', 'watchdog']
23+
24+
25+
class TestSystemWatchdog(VyOSUnitTestSHIM.TestCase):
26+
def tearDown(self):
27+
self.cli_delete(base_path)
28+
self.cli_commit()
29+
super().tearDown()
30+
31+
def test_enable_watchdog_softdog(self):
32+
"""Configure watchdog (presence enables) with softdog and check state"""
33+
# Presence of 'system watchdog' enables watchdog; set module to softdog
34+
self.cli_set(base_path)
35+
self.cli_set(base_path + ['module', 'softdog'])
36+
self.cli_commit()
37+
# Check if softdog module is loaded
38+
lsmod = cmd('lsmod')
39+
self.assertIn('softdog', lsmod)
40+
# Check /dev/watchdog0 exists
41+
self.assertTrue(
42+
os.path.exists('/dev/watchdog0'), '/dev/watchdog0 does not exist'
43+
)
44+
# Check systemd config file exists
45+
config_path = '/run/systemd/system.conf.d/watchdog.conf'
46+
self.assertTrue(
47+
os.path.exists(config_path), f"Systemd config file not found: {config_path}"
48+
)
49+
50+
51+
if __name__ == '__main__':
52+
unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())

src/conf_mode/system_watchdog.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License version 2 or later as
7+
# published by the Free Software Foundation.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
from sys import exit
18+
from pathlib import Path
19+
20+
from vyos.config import Config
21+
from vyos.template import render
22+
from vyos.utils.process import cmd
23+
from vyos import ConfigError
24+
from vyos import airbag
25+
26+
airbag.enable()
27+
28+
watchdog_config_dir = Path('/run/systemd/system.conf.d')
29+
watchdog_config_file = Path(watchdog_config_dir / 'watchdog.conf')
30+
modules_load_file = Path('/run/modules-load.d/vyos-watchdog.conf')
31+
32+
33+
def get_config(config=None):
34+
if config:
35+
conf = config
36+
else:
37+
conf = Config()
38+
base = ['system', 'watchdog']
39+
40+
if not conf.exists(base):
41+
return None
42+
43+
watchdog = conf.get_config_dict(
44+
base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True
45+
)
46+
47+
return watchdog
48+
49+
50+
def verify(watchdog):
51+
if watchdog is None:
52+
return None
53+
54+
55+
def generate(watchdog):
56+
# If watchdog node removed entirely, clean up everything
57+
if watchdog is None:
58+
watchdog_config_file.unlink(missing_ok=True)
59+
modules_load_file.unlink(missing_ok=True)
60+
return None
61+
62+
# Persist kernel module autoload on boot if specified (even if not enabled)
63+
module = watchdog.get('module')
64+
if module:
65+
try:
66+
modules_load_file.write_text(f"{module}\n")
67+
except Exception as e:
68+
print(f"Warning: Failed writing modules-load configuration: {e}")
69+
else:
70+
# If module option removed, drop persisted autoload file
71+
modules_load_file.unlink(missing_ok=True)
72+
73+
# Try to load kernel module if specified and /dev/watchdog0 is missing
74+
if not Path('/dev/watchdog0').exists():
75+
if module:
76+
# Try to load the module using vyos cmd wrapper for logging/airbag integration
77+
try:
78+
cmd(f'modprobe {module}')
79+
except Exception as e:
80+
print(f"Warning: Could not load watchdog module '{module}': {e}")
81+
# Re-check for device
82+
if not Path('/dev/watchdog0').exists():
83+
print(
84+
"Warning: /dev/watchdog0 not found. Systemd watchdog will not be enabled."
85+
)
86+
watchdog_config_file.unlink(missing_ok=True)
87+
return None
88+
89+
# Ensure the directory exists
90+
watchdog_config_dir.mkdir(parents=True, exist_ok=True)
91+
92+
# Pass through configured time values directly as seconds
93+
render(str(watchdog_config_file), 'system/watchdog.conf.j2', watchdog)
94+
95+
return None
96+
97+
98+
def apply(watchdog):
99+
# Reload systemd daemon to apply watchdog configuration
100+
# The watchdog settings take effect after systemd is reloaded
101+
cmd('systemctl daemon-reload')
102+
103+
return None
104+
105+
106+
if __name__ == '__main__':
107+
try:
108+
c = get_config()
109+
verify(c)
110+
generate(c)
111+
apply(c)
112+
except ConfigError as e:
113+
print(e)
114+
exit(1)

0 commit comments

Comments
 (0)