From 26dbcfd262503ebf9c27f3c01cb23dd3be416937 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 21 Aug 2026 16:23:59 +0300 Subject: [PATCH] deploy: keep manager health out of plan --- infra/deploy-runner/nodedc-deploy | 51 +++++++++++--- .../test_device_plane_registry.py | 70 +++++++++++++++++++ 2 files changed, 111 insertions(+), 10 deletions(-) diff --git a/infra/deploy-runner/nodedc-deploy b/infra/deploy-runner/nodedc-deploy index 657e9a5..d0b2081 100755 --- a/infra/deploy-runner/nodedc-deploy +++ b/infra/deploy-runner/nodedc-deploy @@ -12234,7 +12234,33 @@ def validate_device_plane_manager_v3_active_baseline(descriptor): } -def validate_device_plane_manager_activation_predecessor(payload_dir): +def device_plane_manager_preserved_runtime_health_services(descriptor): + if descriptor.get("schemaVersion") == ( + "nodedc.device-plane.device-manager-release.v3" + ): + return ( + "device-control-core", + "device-postgres", + ) + return ("device-postgres",) + + +def validate_device_plane_manager_preserved_runtime_health(descriptor): + services = device_plane_manager_preserved_runtime_health_services( + descriptor + ) + for service in services: + healthcheck_compose_service("device-plane", service) + return services + + +def validate_device_plane_manager_activation_predecessor( + payload_dir, + *, + preflight_phase, +): + if preflight_phase not in ("plan", "apply"): + die("Device Manager predecessor preflight phase is invalid") descriptor = validate_device_plane_manager_release_payload( payload_dir ) @@ -12367,13 +12393,16 @@ def validate_device_plane_manager_activation_predecessor(payload_dir): ) if installed_descriptor != predecessor_descriptor: die("Device Manager release predecessor is not current") - for service in ( - "device-control-core", - "device-manager", - "device-postgres", - ): - healthcheck_compose_service("device-plane", service) - runtime = {"accepted": True} + runtime = { + "accepted": True, + "preservedRuntimeHealth": "deferred-to-apply", + } + if preflight_phase == "apply": + runtime["preservedRuntimeHealth"] = ( + validate_device_plane_manager_preserved_runtime_health( + descriptor + ) + ) v3_baseline = validate_device_plane_manager_v3_active_baseline( descriptor ) @@ -19306,7 +19335,8 @@ def plan_artifact(artifact): ): device_plane_manager_activation_preflight = ( validate_device_plane_manager_activation_predecessor( - payload_dir + payload_dir, + preflight_phase="plan", ) ) if is_device_plane_edge_core_channel_bootstrap_slice( @@ -24614,7 +24644,8 @@ def apply_artifact(artifact): entries, ): validate_device_plane_manager_activation_predecessor( - payload_dir + payload_dir, + preflight_phase="apply", ) if is_device_plane_edge_core_channel_bootstrap_slice( component, diff --git a/infra/deploy-runner/test_device_plane_registry.py b/infra/deploy-runner/test_device_plane_registry.py index 629acd6..83cc8e6 100644 --- a/infra/deploy-runner/test_device_plane_registry.py +++ b/infra/deploy-runner/test_device_plane_registry.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import importlib.machinery import importlib.util +import inspect import json import tempfile import unittest @@ -27,6 +28,75 @@ RUNNER = load_runner() class DevicePlaneRegistryTest(unittest.TestCase): + def test_manager_predecessor_health_is_phase_scoped(self): + with self.assertRaisesRegex( + RUNNER.DeployError, + "preflight phase is invalid", + ): + RUNNER.validate_device_plane_manager_activation_predecessor( + Path("/not-used"), + preflight_phase="unknown", + ) + + plan_source = inspect.getsource(RUNNER.plan_artifact) + self.assertIn('preflight_phase="plan"', plan_source) + apply_source = inspect.getsource(RUNNER.apply_artifact) + self.assertIn('preflight_phase="apply"', apply_source) + + def test_manager_v3_health_gate_checks_only_preserved_services(self): + descriptor = { + "schemaVersion": ( + "nodedc.device-plane.device-manager-release.v3" + ), + } + with mock.patch.object( + RUNNER, + "healthcheck_compose_service", + ) as healthcheck: + services = ( + RUNNER.validate_device_plane_manager_preserved_runtime_health( + descriptor + ) + ) + + self.assertEqual( + services, + ("device-control-core", "device-postgres"), + ) + self.assertEqual( + healthcheck.call_args_list, + [ + mock.call("device-plane", "device-control-core"), + mock.call("device-plane", "device-postgres"), + ], + ) + self.assertNotIn( + mock.call("device-plane", "device-manager"), + healthcheck.call_args_list, + ) + + def test_legacy_manager_health_gate_excludes_selected_services(self): + descriptor = { + "schemaVersion": ( + "nodedc.device-plane.device-manager-release.v2" + ), + } + with mock.patch.object( + RUNNER, + "healthcheck_compose_service", + ) as healthcheck: + services = ( + RUNNER.validate_device_plane_manager_preserved_runtime_health( + descriptor + ) + ) + + self.assertEqual(services, ("device-postgres",)) + healthcheck.assert_called_once_with( + "device-plane", + "device-postgres", + ) + def test_registry_has_exact_roots_project_and_stateless_services(self): component = RUNNER.COMPONENTS["device-plane"] root = Path("/volume1/docker/nodedc-device-plane")