feat: say whether a host is still in its first boot
CI / test (3.10) (push) Successful in 29s
CI / test (3.11) (push) Successful in 30s
CI / test (3.12) (push) Successful in 29s
CI / test (3.10) (pull_request) Successful in 29s
CI / test (3.11) (pull_request) Successful in 46s
CI / test (3.12) (pull_request) Successful in 42s
CI / test (3.10) (push) Successful in 29s
CI / test (3.11) (push) Successful in 30s
CI / test (3.12) (push) Successful in 29s
CI / test (3.10) (pull_request) Successful in 29s
CI / test (3.11) (pull_request) Successful in 46s
CI / test (3.12) (pull_request) Successful in 42s
first_boot_pending() is true while /firstboot exists on FreeBSD. A FreeBSD cloud image upgrades its base system on its first boot, starts sshd only after that and restarts right away, and /etc/rc removes /firstboot just before that restart. netOrk waits for this before it sets up a new VM (NetOrk/netork#795). OpenBSD has no such marker to ask yet, so it returns False there.
This commit is contained in:
@@ -32,5 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
sysDescr; `get_snmp_config` reports a running agent's community and port.
|
sysDescr; `get_snmp_config` reports a running agent's community and port.
|
||||||
- `get_host_status` (napalm-device-types' `HostStatusMixin`): on FreeBSD,
|
- `get_host_status` (napalm-device-types' `HostStatusMixin`): on FreeBSD,
|
||||||
"reboot required" when the installed kernel differs from the running one.
|
"reboot required" when the installed kernel differs from the running one.
|
||||||
|
- `first_boot_pending()`: whether a FreeBSD host is still in its first boot
|
||||||
|
(`/firstboot`), so netOrk can wait before setting up a new VM
|
||||||
|
(NetOrk/netork#795).
|
||||||
- Parsers tested on output recorded from FreeBSD 15.1 (hand-installed and
|
- Parsers tested on output recorded from FreeBSD 15.1 (hand-installed and
|
||||||
cloud image) and OpenBSD 7.9 (NetOrk/netork#793).
|
cloud image) and OpenBSD 7.9 (NetOrk/netork#793).
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ over SSH, built on [napalm-device-types](https://git.netork.io/NAPALM/napalm-dev
|
|||||||
| `get_services`, `manage_service` | ✓ | ✓ | `service -e` + `service … status` / `service … <action>`; `rcctl ls on` + `rcctl check` / `rcctl <action>` |
|
| `get_services`, `manage_service` | ✓ | ✓ | `service -e` + `service … status` / `service … <action>`; `rcctl ls on` + `rcctl check` / `rcctl <action>` |
|
||||||
| `run_device_action("fix_snmp")`, `get_snmp_config` | ✓ | ✓ | net-snmp from packages: `/usr/local/etc/snmp/snmpd.conf` + `service snmpd`; `/etc/snmp/snmpd.conf` + `rcctl … netsnmpd` |
|
| `run_device_action("fix_snmp")`, `get_snmp_config` | ✓ | ✓ | net-snmp from packages: `/usr/local/etc/snmp/snmpd.conf` + `service snmpd`; `/etc/snmp/snmpd.conf` + `rcctl … netsnmpd` |
|
||||||
| `get_host_status` | ✓ | unknown | napalm-device-types' host status: on FreeBSD `freebsd-version -k` vs `-r` (4.1+) |
|
| `get_host_status` | ✓ | unknown | napalm-device-types' host status: on FreeBSD `freebsd-version -k` vs `-r` (4.1+) |
|
||||||
|
| `first_boot_pending` | ✓ | always `False` | whether `/firstboot` still exists |
|
||||||
|
|
||||||
`get_listening_sockets` has the shape of napalm-device-types'
|
`get_listening_sockets` has the shape of napalm-device-types'
|
||||||
`ListeningSocketsMixin` (whose `ss`/cgroup reading is Linux's) and its rule:
|
`ListeningSocketsMixin` (whose `ss`/cgroup reading is Linux's) and its rule:
|
||||||
@@ -68,6 +69,11 @@ database pkg cannot read (only pkg's "not installed" means no packages).
|
|||||||
(`freebsd-version -k`) differs from the running one (`-r`) as needing a reboot.
|
(`freebsd-version -k`) differs from the running one (`-r`) as needing a reboot.
|
||||||
OpenBSD has no such reading yet, so it stays unknown there.
|
OpenBSD has no such reading yet, so it stays unknown there.
|
||||||
|
|
||||||
|
**First boot.** A FreeBSD cloud image upgrades its base system on its first boot,
|
||||||
|
starts sshd only after that and restarts right away. `/etc/rc` removes `/firstboot`
|
||||||
|
just before the restart, so `first_boot_pending()` is true until then; netOrk waits
|
||||||
|
for it before it sets up a new VM. OpenBSD has no such marker to ask yet.
|
||||||
|
|
||||||
**Services** are the enabled ones. FreeBSD reads their status as root, as
|
**Services** are the enabled ones. FreeBSD reads their status as root, as
|
||||||
root-only pidfiles hide a daemon from anyone else, and without root when sudo
|
root-only pidfiles hide a daemon from anyone else, and without root when sudo
|
||||||
refuses; OpenBSD's `rcctl check` needs no root. Actions run as root.
|
refuses; OpenBSD's `rcctl check` needs no root. Actions run as root.
|
||||||
|
|||||||
@@ -188,6 +188,21 @@ class BsdDriver(HostStatusMixin, OSDriver):
|
|||||||
and the readers below treat empty output as "nothing there"."""
|
and the readers below treat empty output as "nothing there"."""
|
||||||
return self.run_command(command, privileged=privileged, timeout=timeout).stdout.strip()
|
return self.run_command(command, privileged=privileged, timeout=timeout).stdout.strip()
|
||||||
|
|
||||||
|
#: A file that exists until the host's first boot is over; "" when there is none to ask.
|
||||||
|
FIRST_BOOT_MARKER = ""
|
||||||
|
|
||||||
|
def first_boot_pending(self) -> bool:
|
||||||
|
"""Whether the host is still in its first boot.
|
||||||
|
|
||||||
|
A FreeBSD cloud image upgrades its base system on the first boot, starts
|
||||||
|
sshd only after that and restarts right away; ``/etc/rc`` removes
|
||||||
|
``/firstboot`` just before the restart. netOrk waits for this before it
|
||||||
|
sets up a new VM (NetOrk/netork#795).
|
||||||
|
"""
|
||||||
|
if not self.FIRST_BOOT_MARKER:
|
||||||
|
return False
|
||||||
|
return self.run_command(f"test -e {self.FIRST_BOOT_MARKER}").exit_code == 0
|
||||||
|
|
||||||
def _run_host_status_command(self, command: str) -> str:
|
def _run_host_status_command(self, command: str) -> str:
|
||||||
"""The transport for ``HostStatusMixin.get_host_status``: read-only, no root.
|
"""The transport for ``HostStatusMixin.get_host_status``: read-only, no root.
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class FreeBSDDriver(BsdDriver):
|
|||||||
ROUTES_COMMAND = "netstat -rnW"
|
ROUTES_COMMAND = "netstat -rnW"
|
||||||
# sockstat names the process of every socket it can see; root sees them all.
|
# sockstat names the process of every socket it can see; root sees them all.
|
||||||
LISTENING_COMMAND = "sockstat -46lq -P tcp,udp"
|
LISTENING_COMMAND = "sockstat -46lq -P tcp,udp"
|
||||||
|
# /etc/rc removes it at the end of the first boot.
|
||||||
|
FIRST_BOOT_MARKER = "/firstboot"
|
||||||
|
|
||||||
def _parse_listening(self, output: str, *, attributed: bool) -> list[dict]:
|
def _parse_listening(self, output: str, *, attributed: bool) -> list[dict]:
|
||||||
return parse.sockstat(output)
|
return parse.sockstat(output)
|
||||||
|
|||||||
@@ -610,3 +610,26 @@ class TestAFailedReadRaises:
|
|||||||
driver = _channel(OpenBSDDriver, answers)
|
driver = _channel(OpenBSDDriver, answers)
|
||||||
with pytest.raises(RuntimeError, match=failing.split()[0]):
|
with pytest.raises(RuntimeError, match=failing.split()[0]):
|
||||||
driver.get_available_updates()
|
driver.get_available_updates()
|
||||||
|
|
||||||
|
|
||||||
|
class TestFirstBoot:
|
||||||
|
"""Whether the host is still in its first boot (NetOrk/netork#795).
|
||||||
|
|
||||||
|
A FreeBSD cloud image upgrades its base system on the first boot, starts
|
||||||
|
sshd only then and restarts right after; /etc/rc removes /firstboot just
|
||||||
|
before that restart. netOrk waits for it before setting up a new VM.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_freebsd_in_its_first_boot(self):
|
||||||
|
driver = _channel(FreeBSDDriver, {"test -e /firstboot": ("", 0)})
|
||||||
|
assert driver.first_boot_pending() is True
|
||||||
|
assert driver.calls == [("test -e /firstboot", False)]
|
||||||
|
|
||||||
|
def test_freebsd_after_its_first_boot(self):
|
||||||
|
driver = _channel(FreeBSDDriver, {"test -e /firstboot": ("", 1)})
|
||||||
|
assert driver.first_boot_pending() is False
|
||||||
|
|
||||||
|
def test_openbsd_has_no_marker_to_ask(self):
|
||||||
|
driver = _channel(OpenBSDDriver, {})
|
||||||
|
assert driver.first_boot_pending() is False
|
||||||
|
assert driver.calls == []
|
||||||
|
|||||||
Reference in New Issue
Block a user