Repair monitor cluster upgrade without blocking K1 package configuration
This commit is contained in:
@@ -53,13 +53,13 @@ def watch(collector):
|
|||||||
"+",
|
"+",
|
||||||
"_SYSTEMD_UNIT=mission-core-node-monitor.service",
|
"_SYSTEMD_UNIT=mission-core-node-monitor.service",
|
||||||
"+",
|
"+",
|
||||||
"_SYSTEMD_UNIT=postgresql@16-ndc-monitor.service",
|
"_SYSTEMD_UNIT=postgresql@16-ndcmonitor.service",
|
||||||
]
|
]
|
||||||
for unit in (
|
for unit in (
|
||||||
"mission-core-node.service",
|
"mission-core-node.service",
|
||||||
"mission-core-k1.service",
|
"mission-core-k1.service",
|
||||||
"mission-core-node-monitor.service",
|
"mission-core-node-monitor.service",
|
||||||
"postgresql@16-ndc-monitor.service",
|
"postgresql@16-ndcmonitor.service",
|
||||||
):
|
):
|
||||||
args.extend(["+", "UNIT=" + unit])
|
args.extend(["+", "UNIT=" + unit])
|
||||||
while not collector.stop.is_set():
|
while not collector.stop.is_set():
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ class LinuxMetrics:
|
|||||||
)
|
)
|
||||||
groups = self.paths("sys/fs/cgroup/system.slice/mission-core*.service")
|
groups = self.paths("sys/fs/cgroup/system.slice/mission-core*.service")
|
||||||
groups += self.paths(
|
groups += self.paths(
|
||||||
"sys/fs/cgroup/system.slice/system-postgresql.slice/postgresql@16-ndc-monitor.service"
|
"sys/fs/cgroup/system.slice/system-postgresql.slice/postgresql@16-ndcmonitor.service"
|
||||||
)
|
)
|
||||||
groups += self.paths(
|
groups += self.paths(
|
||||||
"sys/fs/cgroup/user.slice/user-*.slice/user@*.service/app.slice/app-gnome-org.nodedc.MissionCoreNode-*.scope"
|
"sys/fs/cgroup/user.slice/user-*.slice/user@*.service/app.slice/app-gnome-org.nodedc.MissionCoreNode-*.scope"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import sys
|
|||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
VERSION = "0.8.15"
|
VERSION = "0.8.16"
|
||||||
sys.path.insert(0, str(ROOT.parents[1] / "scripts/packaging"))
|
sys.path.insert(0, str(ROOT.parents[1] / "scripts/packaging"))
|
||||||
from debian import package
|
from debian import package
|
||||||
|
|
||||||
|
|||||||
@@ -30,11 +30,27 @@ fi
|
|||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-remove \
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-remove \
|
||||||
"$mc_release_dir/timescaledb-2-loader-postgresql-16_2.29.2~ubuntu24.04-1615_amd64.deb" \
|
"$mc_release_dir/timescaledb-2-loader-postgresql-16_2.29.2~ubuntu24.04-1615_amd64.deb" \
|
||||||
"$mc_release_dir/timescaledb-2-oss-postgresql-16_2.29.2~ubuntu24.04-1615_amd64.deb" \
|
"$mc_release_dir/timescaledb-2-oss-postgresql-16_2.29.2~ubuntu24.04-1615_amd64.deb" \
|
||||||
"$mc_release_dir/mission-core-node_0.8.15_amd64.deb" \
|
"$mc_release_dir/mission-core-node_0.8.16_amd64.deb" \
|
||||||
"$mc_release_dir/mission-core-xgrids-k1_0.1.14_amd64.deb"
|
"$mc_release_dir/mission-core-xgrids-k1_0.1.14_amd64.deb"
|
||||||
systemctl is-active --quiet mission-core-node.service
|
systemctl is-active --quiet mission-core-node.service
|
||||||
systemctl is-active --quiet mission-core-k1.service
|
systemctl is-active --quiet mission-core-k1.service
|
||||||
systemctl is-active --quiet postgresql@16-ndc-monitor.service
|
runuser -u mission-core-node -- /usr/bin/python3 - <<'K1CHECK'
|
||||||
|
import http.client,json,socket,time
|
||||||
|
deadline=time.monotonic()+20
|
||||||
|
while True:
|
||||||
|
c=http.client.HTTPConnection('k1',timeout=3)
|
||||||
|
c.sock=socket.socket(socket.AF_UNIX);c.sock.settimeout(3)
|
||||||
|
try:
|
||||||
|
c.sock.connect('/run/mission-core-k1/driver.sock')
|
||||||
|
c.request('GET','/status');r=c.getresponse();v=json.load(r)
|
||||||
|
if r.status==200 and v.get('available') is True:break
|
||||||
|
except (OSError, ValueError, http.client.HTTPException):pass
|
||||||
|
finally:c.close()
|
||||||
|
if time.monotonic()>deadline:raise SystemExit('K1 enrollment service did not become ready')
|
||||||
|
time.sleep(1)
|
||||||
|
print('K1 enrollment service: ready')
|
||||||
|
K1CHECK
|
||||||
|
systemctl is-active --quiet postgresql@16-ndcmonitor.service
|
||||||
systemctl is-active --quiet mission-core-node-monitor.service
|
systemctl is-active --quiet mission-core-node-monitor.service
|
||||||
runuser -u mission-core-monitor -- /usr/bin/python3 - <<'PY'
|
runuser -u mission-core-monitor -- /usr/bin/python3 - <<'PY'
|
||||||
import http.client,json,socket,time
|
import http.client,json,socket,time
|
||||||
@@ -53,4 +69,4 @@ while True:
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
print('Local telemetry: ready')
|
print('Local telemetry: ready')
|
||||||
PY
|
PY
|
||||||
printf '%s\n' 'Mission Core Node R18: package and local archive checks passed.'
|
printf '%s\n' 'Mission Core Node R19: package and local archive checks passed.'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Mission Core onboard system telemetry collector
|
Description=Mission Core onboard system telemetry collector
|
||||||
After=postgresql@16-ndc-monitor.service
|
After=postgresql@16-ndcmonitor.service
|
||||||
Wants=postgresql@16-ndc-monitor.service
|
Wants=postgresql@16-ndcmonitor.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
|
|||||||
@@ -23,7 +23,15 @@ case "$1" in
|
|||||||
fi
|
fi
|
||||||
rm -f /run/mission-core-node-k1-upgrade-active
|
rm -f /run/mission-core-node-k1-upgrade-active
|
||||||
fi
|
fi
|
||||||
/usr/lib/mission-core-node/setup-monitor
|
# A diagnostic service must not leave Node half-configured and prevent
|
||||||
|
# dpkg from configuring the separately packaged acquisition plugin.
|
||||||
|
if /usr/lib/mission-core-node/setup-monitor; then
|
||||||
|
rm -f /run/mission-core-monitor-setup-failed
|
||||||
|
else
|
||||||
|
mc_monitor_result=$?
|
||||||
|
printf '%s\n' "$mc_monitor_result" > /run/mission-core-monitor-setup-failed
|
||||||
|
echo "Mission Core Node: monitoring setup failed; device services remain independent." >&2
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ case "$1" in
|
|||||||
systemctl disable mission-core-node.service
|
systemctl disable mission-core-node.service
|
||||||
systemctl stop mission-core-node-monitor.service
|
systemctl stop mission-core-node-monitor.service
|
||||||
systemctl disable mission-core-node-monitor.service
|
systemctl disable mission-core-node-monitor.service
|
||||||
systemctl stop postgresql@16-ndc-monitor.service
|
systemctl stop postgresql@16-ndcmonitor.service
|
||||||
systemctl disable postgresql@16-ndc-monitor.service
|
systemctl disable postgresql@16-ndcmonitor.service
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -9,11 +9,22 @@ install -d -m 0750 -o mission-core-monitor -g mission-core-node /var/lib/mission
|
|||||||
install -d -m 0755 -o postgres -g postgres /run/mission-core-monitor-db
|
install -d -m 0755 -o postgres -g postgres /run/mission-core-monitor-db
|
||||||
install -d -m 0755 /etc/tmpfiles.d
|
install -d -m 0755 /etc/tmpfiles.d
|
||||||
printf '%s\n' 'd /run/mission-core-monitor-db 0755 postgres postgres -' > /etc/tmpfiles.d/mission-core-monitor.conf
|
printf '%s\n' 'd /run/mission-core-monitor-db 0755 postgres postgres -' > /etc/tmpfiles.d/mission-core-monitor.conf
|
||||||
if [ ! -d /etc/postgresql/16/ndc-monitor ]; then
|
# R18 used a dash, which the PostgreSQL systemd template expands as a slash.
|
||||||
pg_createcluster 16 ndc-monitor --port=5433 --socketdir=/run/mission-core-monitor-db --datadir=/var/lib/mission-core-monitor-db --start-conf=auto -- --auth-local=peer --auth-host=reject
|
# Rename only our exact cluster. pg_renamecluster preserves this custom data
|
||||||
|
# directory, updates its configuration paths, and retains all database files.
|
||||||
|
if [ -d /etc/postgresql/16/ndc-monitor ]; then
|
||||||
|
test ! -d /etc/postgresql/16/ndcmonitor
|
||||||
|
test "$(pg_conftool -s 16 ndc-monitor show data_directory)" = /var/lib/mission-core-monitor-db
|
||||||
|
test "$(pg_conftool -s 16 ndc-monitor show port)" = 5433
|
||||||
|
pg_renamecluster 16 ndc-monitor ndcmonitor
|
||||||
fi
|
fi
|
||||||
install -d -m 0755 /etc/postgresql/16/ndc-monitor/conf.d
|
if [ ! -d /etc/postgresql/16/ndcmonitor ]; then
|
||||||
cat > /etc/postgresql/16/ndc-monitor/conf.d/60-mission-core-monitor.conf <<'CONF'
|
pg_createcluster 16 ndcmonitor --port=5433 --socketdir=/run/mission-core-monitor-db --datadir=/var/lib/mission-core-monitor-db --start-conf=auto -- --auth-local=peer --auth-host=reject
|
||||||
|
fi
|
||||||
|
test "$(pg_conftool -s 16 ndcmonitor show data_directory)" = /var/lib/mission-core-monitor-db
|
||||||
|
test "$(pg_conftool -s 16 ndcmonitor show port)" = 5433
|
||||||
|
install -d -m 0755 /etc/postgresql/16/ndcmonitor/conf.d
|
||||||
|
cat > /etc/postgresql/16/ndcmonitor/conf.d/60-mission-core-monitor.conf <<'CONF'
|
||||||
listen_addresses = ''
|
listen_addresses = ''
|
||||||
unix_socket_directories = '/run/mission-core-monitor-db'
|
unix_socket_directories = '/run/mission-core-monitor-db'
|
||||||
shared_preload_libraries = 'timescaledb'
|
shared_preload_libraries = 'timescaledb'
|
||||||
@@ -32,8 +43,8 @@ statement_timeout = '5s'
|
|||||||
log_statement = 'none'
|
log_statement = 'none'
|
||||||
log_min_error_statement = 'panic'
|
log_min_error_statement = 'panic'
|
||||||
CONF
|
CONF
|
||||||
install -d -m 0755 /etc/systemd/system/postgresql@16-ndc-monitor.service.d
|
install -d -m 0755 /etc/systemd/system/postgresql@16-ndcmonitor.service.d
|
||||||
cat > /etc/systemd/system/postgresql@16-ndc-monitor.service.d/60-mission-core-monitor.conf <<'CONF'
|
cat > /etc/systemd/system/postgresql@16-ndcmonitor.service.d/60-mission-core-monitor.conf <<'CONF'
|
||||||
[Service]
|
[Service]
|
||||||
ExecStartPre=+/usr/bin/install -d -m 0755 -o postgres -g postgres /run/mission-core-monitor-db
|
ExecStartPre=+/usr/bin/install -d -m 0755 -o postgres -g postgres /run/mission-core-monitor-db
|
||||||
MemoryHigh=256M
|
MemoryHigh=256M
|
||||||
@@ -43,7 +54,7 @@ TasksMax=32
|
|||||||
Nice=10
|
Nice=10
|
||||||
CONF
|
CONF
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl restart postgresql@16-ndc-monitor.service
|
systemctl restart postgresql@16-ndcmonitor.service
|
||||||
runuser -u postgres -- psql -X -v ON_ERROR_STOP=1 -h /run/mission-core-monitor-db -p 5433 -d postgres <<'SQL'
|
runuser -u postgres -- psql -X -v ON_ERROR_STOP=1 -h /run/mission-core-monitor-db -p 5433 -d postgres <<'SQL'
|
||||||
SELECT 'CREATE ROLE "mission-core-monitor" LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE' WHERE NOT EXISTS(SELECT FROM pg_roles WHERE rolname='mission-core-monitor') \gexec
|
SELECT 'CREATE ROLE "mission-core-monitor" LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE' WHERE NOT EXISTS(SELECT FROM pg_roles WHERE rolname='mission-core-monitor') \gexec
|
||||||
SELECT 'CREATE DATABASE mission_core_monitor OWNER "mission-core-monitor"' WHERE NOT EXISTS(SELECT FROM pg_database WHERE datname='mission_core_monitor') \gexec
|
SELECT 'CREATE DATABASE mission_core_monitor OWNER "mission-core-monitor"' WHERE NOT EXISTS(SELECT FROM pg_database WHERE datname='mission_core_monitor') \gexec
|
||||||
|
|||||||
@@ -135,3 +135,30 @@ Pending owner sudo: installed service/version checks, socket-only cluster,
|
|||||||
archive growth, measured collector overhead, Core durable ACK/backfill, and
|
archive growth, measured collector overhead, Core durable ACK/backfill, and
|
||||||
browser QA with real telemetry. A new physical K1 scan and critical scenarios
|
browser QA with real telemetry. A new physical K1 scan and critical scenarios
|
||||||
remain operator qualification, not an automatically exercised acceptance step.
|
remain operator qualification, not an automatically exercised acceptance step.
|
||||||
|
|
||||||
|
|
||||||
|
## R18 installation regression and R19 correction
|
||||||
|
|
||||||
|
Owner resumed 08.09.2026. R18 install-result is 100, Node 0.8.15 half-configured,
|
||||||
|
K1 0.1.14 unpacked, Node active but K1 stopped. The exact systemd assertion is
|
||||||
|
`/etc/postgresql/16/ndc/monitor/postgresql.conf` missing: the installed Debian
|
||||||
|
PostgreSQL template expands `%I` by replacing instance dashes with path separators.
|
||||||
|
The cluster name `ndc-monitor` caused the path mismatch. This is a packaging
|
||||||
|
error, independent of the powered-off K1. Native R18 archive qualification used
|
||||||
|
a temporary directly started PostgreSQL process and did not cover this systemd
|
||||||
|
instance contract; the old lifecycle stub also accepted every systemctl command.
|
||||||
|
|
||||||
|
R19 / Node 0.8.16 uses cluster `ndcmonitor`. A guarded `pg_renamecluster` migrates
|
||||||
|
only the predecessor with exact custom data directory and port 5433, preserving
|
||||||
|
data and updating configuration paths. It refuses a conflicting destination or
|
||||||
|
foreign data path. All service references/cgroup metrics/journal filters agree.
|
||||||
|
Monitoring bootstrap failure is now diagnostic: Node postinst records it and
|
||||||
|
returns success so dpkg can configure the separate K1 plugin. The release
|
||||||
|
installer still fails its monitoring acceptance if the archive is unavailable;
|
||||||
|
it also requires K1's read-only status endpoint to answer available=true.
|
||||||
|
|
||||||
|
Regression tests execute fresh setup, repeated R18 migration, foreign-cluster
|
||||||
|
refusal, and both successful/failing monitor setup across Node/K1 lifecycle
|
||||||
|
states. They implement the actual Debian `%I` assertion rule, so the previous
|
||||||
|
hyphenated name fails. Real root-installed service and archived samples remain
|
||||||
|
an explicit post-install gate, never inferred from stub or fixture success.
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import pytest
|
|||||||
PACKAGING = Path(__file__).resolve().parents[1] / "apps/node-agent/packaging"
|
PACKAGING = Path(__file__).resolve().parents[1] / "apps/node-agent/packaging"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("monitor_fails", [False, True])
|
||||||
@pytest.mark.parametrize("active,configured", [(True, True), (False, True), (True, False)])
|
@pytest.mark.parametrize("active,configured", [(True, True), (False, True), (True, False)])
|
||||||
def test_node_update_restores_only_previously_running_configured_plugin(
|
def test_node_update_restores_only_previously_running_configured_plugin(
|
||||||
tmp_path, active, configured,
|
tmp_path, active, configured, monitor_fails,
|
||||||
):
|
):
|
||||||
binary = tmp_path / "bin"
|
binary = tmp_path / "bin"
|
||||||
binary.mkdir()
|
binary.mkdir()
|
||||||
@@ -34,7 +35,7 @@ case "$1" in
|
|||||||
esac
|
esac
|
||||||
''')
|
''')
|
||||||
(binary / "getent").write_text("#!/bin/sh\nexit 0\n")
|
(binary / "getent").write_text("#!/bin/sh\nexit 0\n")
|
||||||
(binary / "setup-monitor").write_text("#!/bin/sh\nprintf '%s\\n' monitor-setup >> \"$TEST_EVENTS\"\n")
|
(binary / "setup-monitor").write_text("#!/bin/sh\nprintf '%s\\n' monitor-setup >> \"$TEST_EVENTS\"\nexit " + ("1" if monitor_fails else "0") + "\n")
|
||||||
(binary / "dpkg-query").write_text(
|
(binary / "dpkg-query").write_text(
|
||||||
"#!/bin/sh\necho 'install ok " + ("installed" if configured else "unpacked") + "'\n"
|
"#!/bin/sh\necho 'install ok " + ("installed" if configured else "unpacked") + "'\n"
|
||||||
)
|
)
|
||||||
@@ -56,3 +57,5 @@ esac
|
|||||||
assert ("start mission-core-k1.service" in calls) is (active and configured)
|
assert ("start mission-core-k1.service" in calls) is (active and configured)
|
||||||
assert state.exists() is (active and configured)
|
assert state.exists() is (active and configured)
|
||||||
assert not (run / "mission-core-node-k1-upgrade-active").exists()
|
assert not (run / "mission-core-node-k1-upgrade-active").exists()
|
||||||
|
|
||||||
|
assert (run / "mission-core-monitor-setup-failed").exists() is monitor_fails
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
"""Exercise the monitor bootstrap against PostgreSQL's systemd path contract."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
SETUP = Path(__file__).resolve().parents[1] / "apps/node-agent/packaging/setup-monitor"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("legacy,foreign", [(False, False), (True, False), (True, True)])
|
||||||
|
def test_cluster_path_and_r18_migration_are_repeatable(tmp_path, legacy, foreign):
|
||||||
|
binary = tmp_path / "bin"
|
||||||
|
binary.mkdir()
|
||||||
|
conf = tmp_path / "etc/postgresql/16"
|
||||||
|
conf.mkdir(parents=True)
|
||||||
|
if legacy:
|
||||||
|
(conf / "ndc-monitor").mkdir()
|
||||||
|
(conf / "ndc-monitor/postgresql.conf").write_text("preserved R18 configuration")
|
||||||
|
driver = binary / "driver"
|
||||||
|
driver.write_text("""#!/usr/bin/env python3
|
||||||
|
import os,sys,pathlib
|
||||||
|
root=pathlib.Path(os.environ['TEST_ROOT']);name=pathlib.Path(sys.argv[0]).name;args=sys.argv[1:]
|
||||||
|
with (root/'events').open('a') as log:log.write(name+' '+ ' '.join(args)+'\\n')
|
||||||
|
conf=root/'etc/postgresql/16'
|
||||||
|
if name=='id':print(0)
|
||||||
|
elif name=='install':pathlib.Path(args[-1]).mkdir(parents=True,exist_ok=True)
|
||||||
|
elif name=='pg_conftool':
|
||||||
|
print(os.environ.get('TEST_DATA_ROOT',str(root/'var/lib/mission-core-monitor-db')) if args[-1]=='data_directory' else '5433')
|
||||||
|
elif name=='pg_createcluster':
|
||||||
|
(conf/args[1]).mkdir();(conf/args[1]/'postgresql.conf').write_text('new database')
|
||||||
|
elif name=='pg_renamecluster':
|
||||||
|
(conf/args[1]).rename(conf/args[2])
|
||||||
|
elif name=='systemctl' and args[0]=='restart' and args[1].startswith('postgresql@'):
|
||||||
|
# Exact installed Debian template: AssertPathExists=/etc/postgresql/%I/postgresql.conf.
|
||||||
|
instance=args[1].split('@')[1].removesuffix('.service')
|
||||||
|
assert (root/'etc/postgresql'/instance.replace('-','/')/'postgresql.conf').is_file()
|
||||||
|
""")
|
||||||
|
driver.chmod(0o700)
|
||||||
|
for name in [
|
||||||
|
"id",
|
||||||
|
"getent",
|
||||||
|
"install",
|
||||||
|
"pg_conftool",
|
||||||
|
"pg_createcluster",
|
||||||
|
"pg_renamecluster",
|
||||||
|
"systemctl",
|
||||||
|
"runuser",
|
||||||
|
]:
|
||||||
|
(binary / name).symlink_to(driver)
|
||||||
|
script = tmp_path / "setup"
|
||||||
|
text = SETUP.read_text()
|
||||||
|
for path in ["/etc/", "/var/lib/", "/run/"]:
|
||||||
|
text = text.replace(path, str(tmp_path / path.lstrip("/")) + "/")
|
||||||
|
script.write_text(text)
|
||||||
|
env = dict(
|
||||||
|
os.environ, PATH=str(binary) + os.pathsep + os.environ["PATH"], TEST_ROOT=str(tmp_path)
|
||||||
|
)
|
||||||
|
if foreign:
|
||||||
|
env["TEST_DATA_ROOT"] = "/unrelated/database"
|
||||||
|
result = subprocess.run(["/bin/sh", str(script)], env=env, capture_output=True)
|
||||||
|
assert result.returncode != 0
|
||||||
|
assert (conf / "ndc-monitor/postgresql.conf").is_file()
|
||||||
|
assert "pg_renamecluster" not in (tmp_path / "events").read_text()
|
||||||
|
return
|
||||||
|
for _ in range(2):
|
||||||
|
subprocess.run(["/bin/sh", str(script)], env=env, check=True, capture_output=True)
|
||||||
|
calls = (tmp_path / "events").read_text()
|
||||||
|
assert calls.count("pg_renamecluster 16 ndc-monitor ndcmonitor") == int(legacy)
|
||||||
|
assert calls.count("pg_createcluster 16 ndcmonitor") == int(not legacy)
|
||||||
|
assert not (conf / "ndc-monitor").exists()
|
||||||
|
if legacy:
|
||||||
|
assert (conf / "ndcmonitor/postgresql.conf").read_text() == "preserved R18 configuration"
|
||||||
Reference in New Issue
Block a user