deploy: register Manager v5 landing parity

This commit is contained in:
Codex
2026-08-22 10:23:07 +03:00
parent 6123a06527
commit 952290a49d
2 changed files with 179 additions and 16 deletions
+125 -16
View File
@@ -260,6 +260,9 @@ DEVICE_PLANE_MANAGER_RELEASE_V3_REL = (
DEVICE_PLANE_MANAGER_RELEASE_V4_REL = ( DEVICE_PLANE_MANAGER_RELEASE_V4_REL = (
"deployment/device-manager-release-v4.json" "deployment/device-manager-release-v4.json"
) )
DEVICE_PLANE_MANAGER_RELEASE_V5_REL = (
"deployment/device-manager-release-v5.json"
)
DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml" DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml"
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL = ( DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL = (
"deployment/device-edge-core-channel-bootstrap-v1.json" "deployment/device-edge-core-channel-bootstrap-v1.json"
@@ -373,6 +376,12 @@ DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_PATCH_ID = (
DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_ARTIFACT_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_ARTIFACT_SHA256 = (
"6e0eb3a0a6f19ceab92d46832b93bffbcea21247dbdc2ea50625a51ff460e4ca" "6e0eb3a0a6f19ceab92d46832b93bffbcea21247dbdc2ea50625a51ff460e4ca"
) )
DEVICE_PLANE_MANAGER_RELEASE_V5_PREDECESSOR_PATCH_ID = (
"device-manager-release-v4-20260822-033"
)
DEVICE_PLANE_MANAGER_RELEASE_V5_PREDECESSOR_ARTIFACT_SHA256 = (
"52ba322042f1e4f595bbfea99f8bb35630b15984e0da648dc55348bc9e5b2066"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID = ( DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID = (
"device-edge-core-channel-bootstrap-20260812-018" "device-edge-core-channel-bootstrap-20260812-018"
) )
@@ -434,6 +443,9 @@ DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256 = (
DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256 = (
"e7dff0f5873ad4586bd55946d3db2bb86092a5e149e886d120adc041e056c256" "e7dff0f5873ad4586bd55946d3db2bb86092a5e149e886d120adc041e056c256"
) )
DEVICE_PLANE_MANAGER_RELEASE_V5_COMPOSE_SHA256 = (
DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256
)
DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256 = (
"369a2acf9c1a1030b9e1c6c366144b1eaf8900aef0ee59bb6bf23250b7b371b9" "369a2acf9c1a1030b9e1c6c366144b1eaf8900aef0ee59bb6bf23250b7b371b9"
) )
@@ -517,6 +529,11 @@ DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES = (
"services/device-manager", "services/device-manager",
DEVICE_PLANE_MANAGER_RELEASE_V4_REL, DEVICE_PLANE_MANAGER_RELEASE_V4_REL,
) )
DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES = (
DEVICE_PLANE_MANAGER_COMPOSE_REL,
"services/device-manager",
DEVICE_PLANE_MANAGER_RELEASE_V5_REL,
)
DEVICE_PLANE_MANAGER_RECONCILIATION_REL = ( DEVICE_PLANE_MANAGER_RECONCILIATION_REL = (
"deployment/device-manager-control-plane-reconciliation-v1.json" "deployment/device-manager-control-plane-reconciliation-v1.json"
) )
@@ -3948,6 +3965,7 @@ def allowed_payload_path(component, rel):
DEVICE_PLANE_MANAGER_RELEASE_V2_REL, DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
DEVICE_PLANE_MANAGER_RELEASE_V3_REL, DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
DEVICE_PLANE_MANAGER_RELEASE_V4_REL, DEVICE_PLANE_MANAGER_RELEASE_V4_REL,
DEVICE_PLANE_MANAGER_RELEASE_V5_REL,
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL,
@@ -9521,6 +9539,7 @@ def is_device_plane_manager_control_plane_slice(component, entries):
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES,
) )
) )
@@ -9549,10 +9568,25 @@ def is_device_plane_manager_release_v4_slice(component, entries):
) )
def is_device_plane_manager_release_v5_slice(component, entries):
return (
component == "device-plane"
and entries is not None
and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES
)
def is_device_plane_manager_persistent_release_slice(component, entries):
return (
is_device_plane_manager_release_v4_slice(component, entries)
or is_device_plane_manager_release_v5_slice(component, entries)
)
def is_device_plane_manager_only_release_slice(component, entries): def is_device_plane_manager_only_release_slice(component, entries):
return ( return (
is_device_plane_manager_release_v3_slice(component, entries) is_device_plane_manager_release_v3_slice(component, entries)
or is_device_plane_manager_release_v4_slice(component, entries) or is_device_plane_manager_persistent_release_slice(component, entries)
) )
@@ -9769,6 +9803,20 @@ def expected_device_plane_manager_release_v4_boundaries():
} }
def expected_device_plane_manager_release_v5_boundaries():
return {
**expected_device_plane_manager_release_v4_boundaries(),
"predecessor": {
"kind": "release",
"patchId": DEVICE_PLANE_MANAGER_RELEASE_V5_PREDECESSOR_PATCH_ID,
"artifactSha256": (
DEVICE_PLANE_MANAGER_RELEASE_V5_PREDECESSOR_ARTIFACT_SHA256
),
},
"overviewLayout": "mission-core-landing-stage-v1",
}
def expected_device_plane_manager_release_boundaries(): def expected_device_plane_manager_release_boundaries():
# Compatibility name for the current release builder/tests. Immutable v1 # Compatibility name for the current release builder/tests. Immutable v1
# predecessors always use expected_device_plane_manager_release_v1_boundaries. # predecessors always use expected_device_plane_manager_release_v1_boundaries.
@@ -10283,7 +10331,10 @@ def validate_device_plane_manager_release_payload_contract(
"device-edge-channel/peers", "device-edge-channel/peers",
"name: nodedc-device-plane-egress", "name: nodedc-device-plane-egress",
)) ))
if schema_version == "nodedc.device-plane.device-manager-release.v4": if schema_version in (
"nodedc.device-plane.device-manager-release.v4",
"nodedc.device-plane.device-manager-release.v5",
):
required_compose.extend(( required_compose.extend((
"NODEDC_DEVICE_MANAGER_PRESENTATION_PATH: " "NODEDC_DEVICE_MANAGER_PRESENTATION_PATH: "
f"{DEVICE_PLANE_MANAGER_PRESENTATION_PATH}", f"{DEVICE_PLANE_MANAGER_PRESENTATION_PATH}",
@@ -10371,6 +10422,22 @@ def validate_device_plane_manager_release_v4_payload(
) )
def validate_device_plane_manager_release_v5_payload(
payload_dir,
*,
expected_release_id=None,
):
return validate_device_plane_manager_release_payload_contract(
payload_dir,
descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V5_REL,
schema_version="nodedc.device-plane.device-manager-release.v5",
boundaries=expected_device_plane_manager_release_v5_boundaries(),
compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V5_COMPOSE_SHA256,
edge_channel=False,
expected_release_id=expected_release_id,
)
def validate_device_plane_manager_release_payload( def validate_device_plane_manager_release_payload(
payload_dir, payload_dir,
*, *,
@@ -10380,12 +10447,18 @@ def validate_device_plane_manager_release_payload(
v2 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V2_REL v2 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V2_REL
v3 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V3_REL v3 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V3_REL
v4 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V4_REL v4 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V4_REL
v5 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V5_REL
present = [ present = [
path for path in (v1, v2, v3, v4) path for path in (v1, v2, v3, v4, v5)
if path.exists() or path.is_symlink() if path.exists() or path.is_symlink()
] ]
if len(present) != 1: if len(present) != 1:
die("Device Manager release descriptor cardinality mismatch") die("Device Manager release descriptor cardinality mismatch")
if present[0] == v5:
return validate_device_plane_manager_release_v5_payload(
payload_dir,
expected_release_id=expected_release_id,
)
if present[0] == v4: if present[0] == v4:
return validate_device_plane_manager_release_v4_payload( return validate_device_plane_manager_release_v4_payload(
payload_dir, payload_dir,
@@ -10669,6 +10742,7 @@ def installed_device_plane_manager_compose_sha256():
v2 = root / DEVICE_PLANE_MANAGER_RELEASE_V2_REL v2 = root / DEVICE_PLANE_MANAGER_RELEASE_V2_REL
v3 = root / DEVICE_PLANE_MANAGER_RELEASE_V3_REL v3 = root / DEVICE_PLANE_MANAGER_RELEASE_V3_REL
v4 = root / DEVICE_PLANE_MANAGER_RELEASE_V4_REL v4 = root / DEVICE_PLANE_MANAGER_RELEASE_V4_REL
v5 = root / DEVICE_PLANE_MANAGER_RELEASE_V5_REL
# A successful v2 overlay intentionally leaves the immutable v1 release # A successful v2 overlay intentionally leaves the immutable v1 release
# descriptor as predecessor evidence. Prefer the highest installed # descriptor as predecessor evidence. Prefer the highest installed
@@ -10676,6 +10750,19 @@ def installed_device_plane_manager_compose_sha256():
# the generation-specific Compose digest. During a failed v2 apply, # the generation-specific Compose digest. During a failed v2 apply,
# rollback removes the candidate-only v2 descriptor before rebuilding the # rollback removes the candidate-only v2 descriptor before rebuilding the
# restored v1 runtime, so the same lookup follows the restored source. # restored v1 runtime, so the same lookup follows the restored source.
if v5.exists() or v5.is_symlink():
descriptor = read_strict_json(
v5,
"installed Device Manager release v5 descriptor",
max_bytes=16 * 1024,
)
validate_device_plane_manager_release_descriptor(
descriptor,
schema_version="nodedc.device-plane.device-manager-release.v5",
boundaries=expected_device_plane_manager_release_v5_boundaries(),
)
return DEVICE_PLANE_MANAGER_RELEASE_V5_COMPOSE_SHA256
if v4.exists() or v4.is_symlink(): if v4.exists() or v4.is_symlink():
descriptor = read_strict_json( descriptor = read_strict_json(
v4, v4,
@@ -12206,6 +12293,7 @@ def validate_device_plane_manager_v3_active_baseline(descriptor):
if descriptor.get("schemaVersion") not in ( if descriptor.get("schemaVersion") not in (
"nodedc.device-plane.device-manager-release.v3", "nodedc.device-plane.device-manager-release.v3",
"nodedc.device-plane.device-manager-release.v4", "nodedc.device-plane.device-manager-release.v4",
"nodedc.device-plane.device-manager-release.v5",
): ):
return None return None
@@ -12353,6 +12441,7 @@ def device_plane_manager_preserved_runtime_health_services(descriptor):
if descriptor.get("schemaVersion") in ( if descriptor.get("schemaVersion") in (
"nodedc.device-plane.device-manager-release.v3", "nodedc.device-plane.device-manager-release.v3",
"nodedc.device-plane.device-manager-release.v4", "nodedc.device-plane.device-manager-release.v4",
"nodedc.device-plane.device-manager-release.v5",
): ):
return ( return (
"device-control-core", "device-control-core",
@@ -12485,6 +12574,7 @@ def validate_device_plane_manager_activation_predecessor(
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES,
) )
): ):
die("Device Manager release predecessor type mismatch") die("Device Manager release predecessor type mismatch")
@@ -12501,6 +12591,8 @@ def validate_device_plane_manager_activation_predecessor(
DEVICE_PLANE_MANAGER_RELEASE_V3_REL, DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES:
DEVICE_PLANE_MANAGER_RELEASE_V4_REL, DEVICE_PLANE_MANAGER_RELEASE_V4_REL,
DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES:
DEVICE_PLANE_MANAGER_RELEASE_V5_REL,
}.get( }.get(
tuple(predecessor_entries), tuple(predecessor_entries),
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
@@ -14879,11 +14971,13 @@ def validate_device_manager_control_plane_runtime(
) )
require_edge_channel = edge_descriptor.is_file() and not edge_descriptor.is_symlink() require_edge_channel = edge_descriptor.is_file() and not edge_descriptor.is_symlink()
if require_persistent_data is None: if require_persistent_data is None:
data_descriptor = ( data_descriptors = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V4_REL DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V5_REL,
DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V4_REL,
) )
require_persistent_data = ( require_persistent_data = any(
data_descriptor.is_file() and not data_descriptor.is_symlink() descriptor.is_file() and not descriptor.is_symlink()
for descriptor in data_descriptors
) )
expected_core_environment = { expected_core_environment = {
"DEVICE_MANAGEMENT_API_ENABLED": "true", "DEVICE_MANAGEMENT_API_ENABLED": "true",
@@ -21250,6 +21344,7 @@ def plan_artifact(artifact):
) in ( ) in (
"nodedc.device-plane.device-manager-release.v3", "nodedc.device-plane.device-manager-release.v3",
"nodedc.device-plane.device-manager-release.v4", "nodedc.device-plane.device-manager-release.v4",
"nodedc.device-plane.device-manager-release.v5",
) )
else "build+recreate:device-control-core,device-manager" else "build+recreate:device-control-core,device-manager"
) )
@@ -21264,6 +21359,7 @@ def plan_artifact(artifact):
) in ( ) in (
"nodedc.device-plane.device-manager-release.v3", "nodedc.device-plane.device-manager-release.v3",
"nodedc.device-plane.device-manager-release.v4", "nodedc.device-plane.device-manager-release.v4",
"nodedc.device-plane.device-manager-release.v5",
) )
else "preserved:device-gateway,device-postgres," else "preserved:device-gateway,device-postgres,"
"device-backhaul-target" "device-backhaul-target"
@@ -21272,18 +21368,19 @@ def plan_artifact(artifact):
manager_schema = device_plane_manager_activation_preflight[ manager_schema = device_plane_manager_activation_preflight[
"descriptor" "descriptor"
].get("schemaVersion") ].get("schemaVersion")
manager_persistent = manager_schema in (
"nodedc.device-plane.device-manager-release.v4",
"nodedc.device-plane.device-manager-release.v5",
)
print( print(
"device_manager_health_gate=" "device_manager_health_gate="
+ ( + (
"bounded-grace+contract+persistent-data" "bounded-grace+contract+persistent-data"
if manager_schema if manager_persistent
== "nodedc.device-plane.device-manager-release.v4"
else "bounded-grace+contract" else "bounded-grace+contract"
) )
) )
if manager_schema == ( if manager_persistent:
"nodedc.device-plane.device-manager-release.v4"
):
print( print(
"device_manager_persistent_data=runner-managed-preserved:" "device_manager_persistent_data=runner-managed-preserved:"
f"{DEVICE_PLANE_MANAGER_DATA_DIR}" f"{DEVICE_PLANE_MANAGER_DATA_DIR}"
@@ -21293,6 +21390,13 @@ def plan_artifact(artifact):
f"{DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR}" f"{DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR}"
) )
print("device_manager_default_accent=#f5f5f5") print("device_manager_default_accent=#f5f5f5")
if manager_schema == (
"nodedc.device-plane.device-manager-release.v5"
):
print(
"device_manager_overview_layout="
"mission-core-landing-stage-v1"
)
if device_plane_manager_activation_preflight["descriptor"].get( if device_plane_manager_activation_preflight["descriptor"].get(
"commandTransport" "commandTransport"
) == "typed-service-ping-v1": ) == "typed-service-ping-v1":
@@ -21308,8 +21412,7 @@ def plan_artifact(artifact):
+ ( + (
"source+reconciled-baseline-runtime+" "source+reconciled-baseline-runtime+"
"persistent-manager-data-preserved" "persistent-manager-data-preserved"
if manager_schema if manager_persistent
== "nodedc.device-plane.device-manager-release.v4"
else "source+reconciled-baseline-runtime" else "source+reconciled-baseline-runtime"
) )
) )
@@ -22998,7 +23101,10 @@ def prepare_component_runtime(component, entries=None):
MAP_GATEWAY_SECRET_RE, MAP_GATEWAY_SECRET_RE,
"Device Core Hub handoff", "Device Core Hub handoff",
) )
if is_device_plane_manager_release_v4_slice(component, entries): if is_device_plane_manager_persistent_release_slice(
component,
entries,
):
ensure_device_plane_manager_persistent_data() ensure_device_plane_manager_persistent_data()
if ( if (
is_device_plane_control_core_release_slice(component, entries) is_device_plane_control_core_release_slice(component, entries)
@@ -24107,7 +24213,10 @@ def run_healthchecks(component, entries=None, services=None):
) )
for check in component_healthchecks(component, entries, services): for check in component_healthchecks(component, entries, services):
healthcheck_url(check) healthcheck_url(check)
if is_device_plane_manager_release_v4_slice(component, entries): if is_device_plane_manager_persistent_release_slice(
component,
entries,
):
validate_device_manager_control_plane_runtime( validate_device_manager_control_plane_runtime(
require_edge_channel=True, require_edge_channel=True,
core_network_mode="private-egress", core_network_mode="private-egress",
@@ -29,6 +29,32 @@ RUNNER = load_runner()
class DevicePlaneRegistryTest(unittest.TestCase): class DevicePlaneRegistryTest(unittest.TestCase):
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): def test_manager_v4_pins_032_and_persistent_white_boundary(self):
self.assertEqual( self.assertEqual(
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_PATCH_ID, RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_PATCH_ID,
@@ -348,6 +374,34 @@ class DevicePlaneRegistryTest(unittest.TestCase):
require_persistent_data=True, 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_v4_prepare_owns_new_parent_and_managed_data_directory(self): def test_manager_v4_prepare_owns_new_parent_and_managed_data_directory(self):
with tempfile.TemporaryDirectory( with tempfile.TemporaryDirectory(
prefix="nodedc-manager-v4-data-", prefix="nodedc-manager-v4-data-",