2 changed files with 1327 additions and 42 deletions
File diff suppressed because it is too large Load Diff
@@ -3,6 +3,7 @@ import importlib.machinery
import importlib.util import importlib.util
import inspect import inspect
import json import json
import stat
import tempfile import tempfile
import unittest import unittest
from pathlib import Path from pathlib import Path
@@ -28,6 +29,548 @@ RUNNER = load_runner()
class DevicePlaneRegistryTest(unittest.TestCase): class DevicePlaneRegistryTest(unittest.TestCase):
def test_manager_v7_pins_v6_and_live_edge_host_projection(self):
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V7_PREDECESSOR_PATCH_ID,
"device-manager-release-v6-20260822-035",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V7_PREDECESSOR_ARTIFACT_SHA256,
"193faabe930e2b3f212f8eb45288e39f850ec714528b28881095be654baf9a80",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V7_CONTROL_CORE_PREDECESSOR_PATCH_ID,
"device-control-core-release-v2-20260822-036",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V7_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256,
"8708cc4b59fa0cd5e9c6e6a7b2654ba01ea60271549167aca2631f94000d3da3",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V7_COMPOSE_SHA256,
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256,
)
boundaries = RUNNER.expected_device_plane_manager_release_v7_boundaries()
self.assertEqual(
boundaries["infrastructureHostProjection"],
"edge-registration-live-channel-v1",
)
self.assertEqual(
boundaries["ontologyStatus"],
"generic-host-domain-candidate-not-canonical",
)
self.assertEqual(
RUNNER.component_services(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES,
),
("device-manager",),
)
def test_manager_v6_pins_034_and_canonical_favicon_boundary(self):
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V6_PREDECESSOR_PATCH_ID,
"device-manager-release-v5-20260822-034",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V6_PREDECESSOR_ARTIFACT_SHA256,
"acc1d2ae2cda66861054826928c25d01a2428e688cc8132a9c381831bf29ab5a",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256,
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V5_COMPOSE_SHA256,
)
boundaries = RUNNER.expected_device_plane_manager_release_v6_boundaries()
self.assertEqual(boundaries["faviconSet"], "nodedc-adaptive-v1")
self.assertEqual(
boundaries["overviewLayout"],
"mission-core-landing-stage-v1",
)
self.assertEqual(
RUNNER.component_services(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V6_ENTRIES,
),
("device-manager",),
)
def test_manager_v5_pins_033_and_mission_core_overview_layout(self):
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V5_PREDECESSOR_PATCH_ID,
"device-manager-release-v4-20260822-033",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V5_PREDECESSOR_ARTIFACT_SHA256,
"52ba322042f1e4f595bbfea99f8bb35630b15984e0da648dc55348bc9e5b2066",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V5_COMPOSE_SHA256,
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256,
)
boundaries = RUNNER.expected_device_plane_manager_release_v5_boundaries()
self.assertEqual(
boundaries["overviewLayout"],
"mission-core-landing-stage-v1",
)
self.assertEqual(
RUNNER.component_services(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES,
),
("device-manager",),
)
def test_manager_v4_pins_032_and_persistent_white_boundary(self):
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_PATCH_ID,
"device-manager-release-v3-20260822-032",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_ARTIFACT_SHA256,
"6e0eb3a0a6f19ceab92d46832b93bffbcea21247dbdc2ea50625a51ff460e4ca",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256,
"e7dff0f5873ad4586bd55946d3db2bb86092a5e149e886d120adc041e056c256",
)
boundaries = RUNNER.expected_device_plane_manager_release_v4_boundaries()
self.assertEqual(boundaries["defaultAccentHex"], "#f5f5f5")
self.assertEqual(
boundaries["presentationDataHostPath"],
"/volume1/docker/nodedc-device-plane/data/device-manager",
)
self.assertEqual(
boundaries["presentationDataContainerPath"],
"/var/lib/nodedc-device-manager",
)
def test_manager_v3_targets_applied_control_core_recovery(self):
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_PATCH_ID,
"device-control-core-release-v2-20260821-030",
)
self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256,
"8459521a662541a5a87cb0188991cdcfb51727427db8ec2a232ce4846bfc3454",
)
self.assertEqual(
RUNNER.expected_device_plane_manager_release_v3_boundaries()[
"controlCorePredecessor"
],
{
"patchId": (
"device-control-core-release-v2-20260821-030"
),
"artifactSha256": (
"8459521a662541a5a87cb0188991cdcfb51727427db8ec2a232ce4846bfc3454"
),
},
)
def test_control_core_predecessor_health_is_phase_scoped(self):
with self.assertRaisesRegex(
RUNNER.DeployError,
"preflight phase is invalid",
):
RUNNER.validate_device_plane_control_core_release_predecessor(
Path("/not-used"),
preflight_phase="unknown",
)
plan_source = inspect.getsource(RUNNER.plan_artifact)
self.assertIn(
'validate_device_plane_control_core_release_predecessor(\n'
' payload_dir,\n'
' preflight_phase="plan",',
plan_source,
)
apply_source = inspect.getsource(RUNNER.apply_artifact)
self.assertIn(
'validate_device_plane_control_core_release_predecessor(\n'
' payload_dir,\n'
' preflight_phase="apply",',
apply_source,
)
def test_control_core_preflight_health_excludes_selected_target(self):
descriptor = {
"preservedServices": [
"device-manager",
"device-gateway",
"device-postgres",
"device-backhaul-target",
],
}
with mock.patch.object(
RUNNER,
"healthcheck_compose_service",
) as healthcheck:
services = (
RUNNER.validate_device_plane_control_core_preserved_runtime_health(
descriptor
)
)
self.assertEqual(
services,
(
"device-manager",
"device-gateway",
"device-postgres",
"device-backhaul-target",
),
)
self.assertEqual(
healthcheck.call_args_list,
[
mock.call("device-plane", "device-manager"),
mock.call("device-plane", "device-gateway"),
mock.call("device-plane", "device-postgres"),
mock.call("device-plane", "device-backhaul-target"),
],
)
self.assertNotIn(
mock.call("device-plane", "device-control-core"),
healthcheck.call_args_list,
)
def test_control_core_selected_predecessor_may_be_unhealthy(self):
inventory = {
"schemaVersion": "nodedc.device-plane.runtime-inventory.v1",
"composeProject": "nodedc-device-plane",
"services": [{
"service": "device-control-core",
"containerId": "a" * 64,
"imageId": "sha256:" + "b" * 64,
"status": "running",
"running": True,
"health": "unhealthy",
"restartCount": 4,
}],
}
with mock.patch.object(
RUNNER,
"device_plane_runtime_inventory",
return_value=inventory,
):
selected = (
RUNNER.validate_device_plane_control_core_selected_predecessor_runtime()
)
self.assertEqual(selected["health"], "unhealthy")
def test_control_core_selected_predecessor_may_be_restarting(self):
inventory = {
"schemaVersion": "nodedc.device-plane.runtime-inventory.v1",
"composeProject": "nodedc-device-plane",
"services": [{
"service": "device-control-core",
"containerId": "a" * 64,
"imageId": "sha256:" + "b" * 64,
"status": "restarting",
"running": True,
"health": "starting",
"restartCount": 5,
}],
}
with mock.patch.object(
RUNNER,
"device_plane_runtime_inventory",
return_value=inventory,
):
selected = (
RUNNER.validate_device_plane_control_core_selected_predecessor_runtime()
)
self.assertEqual(selected["status"], "restarting")
self.assertEqual(selected["health"], "starting")
def test_control_core_rollback_accepts_restored_unhealthy_boundary(self):
service_names = (
"device-control-core",
"device-manager",
"device-gateway",
"device-postgres",
"device-backhaul-target",
)
inventory = {
"schemaVersion": "nodedc.device-plane.runtime-inventory.v1",
"composeProject": "nodedc-device-plane",
"services": [
{
"service": service,
"containerId": chr(97 + index) * 64,
"imageId": "sha256:" + str(index + 1) * 64,
"status": "running",
"running": True,
"health": "unhealthy" if index == 0 else "healthy",
"restartCount": index,
}
for index, service in enumerate(service_names)
],
}
restored_id = "f" * 64
restored = {
"Id": restored_id,
"State": {
"Status": "running",
"Running": True,
"Health": {"Status": "unhealthy"},
},
}
with (
mock.patch.object(
RUNNER,
"healthcheck_compose_service_with_grace",
) as preserved_health,
mock.patch.object(
RUNNER,
"compose_service_container_id",
return_value=restored_id,
),
mock.patch.object(
RUNNER,
"inspect_device_plane_container",
return_value=restored,
),
):
accepted = (
RUNNER.accept_device_plane_control_core_rollback_runtime(
inventory
)
)
self.assertEqual(accepted["health"], "unhealthy")
self.assertEqual(accepted["status"], "running")
self.assertEqual(accepted["predecessorHealth"], "unhealthy")
self.assertEqual(
preserved_health.call_args_list,
[
mock.call("device-plane", "device-manager"),
mock.call("device-plane", "device-gateway"),
mock.call("device-plane", "device-postgres"),
mock.call("device-plane", "device-backhaul-target"),
],
)
def test_control_core_post_apply_health_includes_backhaul(self):
with (
mock.patch.object(
RUNNER,
"healthcheck_compose_service_with_grace",
) as healthcheck,
mock.patch.object(
RUNNER,
"component_healthchecks",
return_value=(),
),
mock.patch.object(
RUNNER,
"validate_device_manager_control_plane_runtime",
),
):
RUNNER.run_healthchecks(
"device-plane",
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES,
("device-control-core",),
)
self.assertEqual(
healthcheck.call_args_list,
[
mock.call("device-plane", "device-control-core"),
mock.call("device-plane", "device-manager"),
mock.call("device-plane", "device-gateway"),
mock.call("device-plane", "device-postgres"),
mock.call("device-plane", "device-backhaul-target"),
],
)
def test_manager_v3_post_apply_uses_private_egress_core_boundary(self):
with (
mock.patch.object(
RUNNER,
"healthcheck_compose_service_with_grace",
),
mock.patch.object(
RUNNER,
"component_healthchecks",
return_value=(),
),
mock.patch.object(
RUNNER,
"validate_device_manager_control_plane_runtime",
) as runtime_acceptance,
):
RUNNER.run_healthchecks(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES,
("device-manager",),
)
runtime_acceptance.assert_called_once_with(
require_edge_channel=True,
core_network_mode="private-egress",
)
def test_manager_v4_post_apply_requires_persistent_data(self):
with (
mock.patch.object(
RUNNER,
"healthcheck_compose_service_with_grace",
),
mock.patch.object(
RUNNER,
"component_healthchecks",
return_value=(),
),
mock.patch.object(
RUNNER,
"validate_device_manager_control_plane_runtime",
) as runtime_acceptance,
):
RUNNER.run_healthchecks(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES,
("device-manager",),
)
runtime_acceptance.assert_called_once_with(
require_edge_channel=True,
core_network_mode="private-egress",
require_persistent_data=True,
)
def test_manager_v5_post_apply_preserves_persistent_data_gate(self):
with (
mock.patch.object(
RUNNER,
"healthcheck_compose_service_with_grace",
),
mock.patch.object(
RUNNER,
"component_healthchecks",
return_value=(),
),
mock.patch.object(
RUNNER,
"validate_device_manager_control_plane_runtime",
) as runtime_acceptance,
):
RUNNER.run_healthchecks(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES,
("device-manager",),
)
runtime_acceptance.assert_called_once_with(
require_edge_channel=True,
core_network_mode="private-egress",
require_persistent_data=True,
)
def test_manager_v6_post_apply_preserves_persistent_data_gate(self):
with (
mock.patch.object(
RUNNER,
"healthcheck_compose_service_with_grace",
),
mock.patch.object(
RUNNER,
"component_healthchecks",
return_value=(),
),
mock.patch.object(
RUNNER,
"validate_device_manager_control_plane_runtime",
) as runtime_acceptance,
):
RUNNER.run_healthchecks(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V6_ENTRIES,
("device-manager",),
)
runtime_acceptance.assert_called_once_with(
require_edge_channel=True,
core_network_mode="private-egress",
require_persistent_data=True,
)
def test_manager_v4_prepare_owns_new_parent_and_managed_data_directory(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-manager-v4-data-",
) as directory:
data_dir = Path(directory) / "data" / "device-manager"
with (
mock.patch.object(
RUNNER,
"DEVICE_PLANE_MANAGER_DATA_DIR",
data_dir,
),
mock.patch.object(RUNNER.os, "chown") as chown,
mock.patch.object(
RUNNER,
"validate_device_plane_manager_persistent_data_metadata",
return_value="uid-1000-gid-1000-mode-0750",
) as validate,
):
result = RUNNER.ensure_device_plane_manager_persistent_data()
self.assertEqual(result, "uid-1000-gid-1000-mode-0750")
self.assertEqual(
chown.call_args_list,
[
mock.call(data_dir.parent, 0, 0),
mock.call(data_dir, 1000, 1000),
],
)
validate.assert_called_once_with()
def test_manager_v4_prepare_preserves_existing_safe_parent_metadata(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-manager-v4-parent-",
) as directory:
data_parent = Path(directory) / "data"
data_parent.mkdir(mode=0o700)
data_dir = data_parent / "device-manager"
with (
mock.patch.object(
RUNNER,
"DEVICE_PLANE_MANAGER_DATA_DIR",
data_dir,
),
mock.patch.object(RUNNER.os, "chown") as chown,
mock.patch.object(
RUNNER,
"validate_device_plane_manager_persistent_data_metadata",
return_value="uid-1000-gid-1000-mode-0750",
),
):
RUNNER.ensure_device_plane_manager_persistent_data()
self.assertEqual(stat.S_IMODE(data_parent.stat().st_mode), 0o700)
chown.assert_called_once_with(data_dir, 1000, 1000)
def test_manager_v4_prepare_rejects_symlink_parent_before_child_creation(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-manager-v4-symlink-",
) as directory:
root = Path(directory)
outside = root / "outside"
outside.mkdir()
data_parent = root / "data"
data_parent.symlink_to(outside, target_is_directory=True)
with mock.patch.object(
RUNNER,
"DEVICE_PLANE_MANAGER_DATA_DIR",
data_parent / "device-manager",
):
with self.assertRaisesRegex(
RUNNER.DeployError,
"persistent data parent is unsafe",
):
RUNNER.ensure_device_plane_manager_persistent_data()
self.assertFalse((outside / "device-manager").exists())
def test_manager_predecessor_health_is_phase_scoped(self): def test_manager_predecessor_health_is_phase_scoped(self):
with self.assertRaisesRegex( with self.assertRaisesRegex(
RUNNER.DeployError, RUNNER.DeployError,