fix(deploy): allow unhealthy selected Core recovery
This commit is contained in:
@@ -28,6 +28,197 @@ RUNNER = load_runner()
|
||||
|
||||
|
||||
class DevicePlaneRegistryTest(unittest.TestCase):
|
||||
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_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["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_predecessor_health_is_phase_scoped(self):
|
||||
with self.assertRaisesRegex(
|
||||
RUNNER.DeployError,
|
||||
|
||||
Reference in New Issue
Block a user