feat: set up SNMP with net-snmp
CI / test (3.10) (pull_request) Successful in 30s
CI / test (3.11) (pull_request) Successful in 32s
CI / test (3.12) (pull_request) Successful in 36s
CI / test (3.10) (push) Successful in 45s
CI / test (3.11) (push) Successful in 30s
CI / test (3.12) (push) Successful in 29s
CI / test (3.10) (pull_request) Successful in 30s
CI / test (3.11) (pull_request) Successful in 32s
CI / test (3.12) (pull_request) Successful in 36s
CI / test (3.10) (push) Successful in 45s
CI / test (3.11) (push) Successful in 30s
CI / test (3.12) (push) Successful in 29s
run_device_action("fix_snmp") and get_snmp_config for both drivers, as
decided in NetOrk/netork#800: net-snmp from packages rather than the base
daemons, because it answers UCD-SNMP-MIB, which netOrk's health metrics
read.
- fix_snmp installs net-snmp, writes the configuration netOrk uses on
Linux (v2c, community "public", agentAddress udp:161) through stdin as
root, enables and restarts the agent (FreeBSD `service snmpd`, OpenBSD
`rcctl ... netsnmpd`, as `snmpd` is OpenBSD's own daemon) and asks it
for sysDescr. It stops at the first step that fails and says why.
- get_snmp_config reports a running net-snmp's community and port.
- FreeBSD's install_package bootstraps pkg on a classic system that never
had it instead of waiting for an answer that never comes.
Checked live: FreeBSD from a bare system, OpenBSD again over an existing
setup. Memory, swap and load answer on both; FreeBSD's ssCpuIdle about a
minute after start; OpenBSD's CPU figures from net-snmp are wrong (#800).
This commit is contained in:
@@ -36,6 +36,16 @@ _SERVICE_ACTIONS = frozenset({"start", "stop", "restart", "enable", "disable"})
|
||||
_SERVICE_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]*$")
|
||||
_PACKAGE_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.+-]*$")
|
||||
|
||||
#: The agent netOrk sets up, as on Linux: v2c, community "public", every address.
|
||||
_SNMPD_CONF = (
|
||||
"agentAddress udp:161\n"
|
||||
"rocommunity public\n"
|
||||
"sysLocation Managed by netOrk\n"
|
||||
"sysContact netork@localhost\n"
|
||||
)
|
||||
#: What a working agent answers sysDescr.0 with.
|
||||
_SNMP_TYPES = ("STRING:", "INTEGER:", "OID:", "Timeticks:", "Hex-STRING:", "IpAddress:")
|
||||
|
||||
|
||||
class BsdDriver(OSDriver):
|
||||
"""Base for the BSD drivers; a concrete one names the commands that differ."""
|
||||
@@ -344,6 +354,67 @@ class BsdDriver(OSDriver):
|
||||
output = "\n".join(filter(None, (result.stdout.strip(), result.stderr.strip())))
|
||||
return {"success": result.exit_code == 0, "output": output}
|
||||
|
||||
# -- SNMP (net-snmp from packages, NetOrk/netork#800) -------------------------
|
||||
|
||||
#: Where net-snmp reads its configuration, and how its daemon is started.
|
||||
SNMPD_CONF = ""
|
||||
SNMPD_START = ""
|
||||
#: net-snmp's daemon; OpenBSD's base snmpd is /usr/sbin/snmpd.
|
||||
SNMPD_DAEMON = "/usr/local/sbin/snmpd"
|
||||
SNMP_PROBE = "snmpget -v2c -cpublic -t2 -r0 -Ov 127.0.0.1 1.3.6.1.2.1.1.1.0"
|
||||
|
||||
def run_device_action(self, action: str) -> dict[str, Any]:
|
||||
"""Execute a named action on the device; ``fix_snmp`` is the one there is."""
|
||||
if action == "fix_snmp":
|
||||
return self._action_fix_snmp()
|
||||
raise NotImplementedError(f"Unknown action: {action!r}")
|
||||
|
||||
def _action_fix_snmp(self) -> dict[str, Any]:
|
||||
"""Install net-snmp, configure it as on Linux, start it, and ask it.
|
||||
|
||||
net-snmp rather than the base daemons (FreeBSD bsnmpd, OpenBSD snmpd):
|
||||
it answers UCD-SNMP-MIB, which netOrk's health metrics read
|
||||
(NetOrk/netork#800). Stops at the first step that fails.
|
||||
"""
|
||||
lines: list[str] = []
|
||||
conf_dir = self.SNMPD_CONF.rsplit("/", 1)[0]
|
||||
steps = (
|
||||
("install", self.INSTALL_COMMAND.format(name="net-snmp"), None),
|
||||
(
|
||||
"config",
|
||||
f"mkdir -p {conf_dir} && cat > {self.SNMPD_CONF} && chmod 644 {self.SNMPD_CONF}",
|
||||
_SNMPD_CONF.encode(),
|
||||
),
|
||||
("service", self.SNMPD_START, None),
|
||||
)
|
||||
for label, command, stdin in steps:
|
||||
result = self.run_command(command, privileged=True, timeout=600, stdin=stdin)
|
||||
output = "\n".join(filter(None, (result.stdout.strip(), result.stderr.strip())))
|
||||
lines.append(f"[{label}] {output[-300:]}".rstrip())
|
||||
if result.exit_code != 0:
|
||||
return {"success": False, "output": "\n".join(lines)}
|
||||
probe = self.run_command(self.SNMP_PROBE, timeout=30).stdout.strip()
|
||||
lines.append(f"[probe] {probe}")
|
||||
return {"success": any(t in probe for t in _SNMP_TYPES), "output": "\n".join(lines)}
|
||||
|
||||
def get_snmp_config(self) -> Optional[dict[str, Any]]:
|
||||
"""net-snmp's community and port, if its daemon runs; None otherwise."""
|
||||
if not self._out(f"pgrep -f {self.SNMPD_DAEMON}"):
|
||||
return None
|
||||
community, port = "public", 161
|
||||
for line in self._out(f"cat {self.SNMPD_CONF}").splitlines():
|
||||
words = line.split()
|
||||
if len(words) >= 2 and words[0].lower() in (
|
||||
"rocommunity",
|
||||
"rwcommunity",
|
||||
"rocommunity6",
|
||||
):
|
||||
community = words[1]
|
||||
elif words and words[0] == "agentAddress":
|
||||
found = re.search(r":(\d+)", line)
|
||||
port = int(found[1]) if found else port
|
||||
return {"running": True, "community": community, "port": port, "version": "2c"}
|
||||
|
||||
# -- accounts, processes, cron -----------------------------------------------
|
||||
|
||||
def get_users(self) -> list[dict[str, Any]]:
|
||||
|
||||
Reference in New Issue
Block a user