diff --git a/device-plane/deployment/device-edge-core-channel-upgrade-v3.json b/device-plane/deployment/device-edge-core-channel-upgrade-v3.json deleted file mode 100644 index cb30c79..0000000 --- a/device-plane/deployment/device-edge-core-channel-upgrade-v3.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "schemaVersion": "nodedc.device-plane.device-edge-core-channel-upgrade.v3", - "transitionId": "__PATCH_ID__", - "action": "upgrade", - "upgradePredecessor": { - "patchId": "device-edge-core-channel-upgrade-v2-20260812-021", - "artifactSha256": "e40a6fd24edfecac09e42cd82635a77850541bcf047788db3e9c55d2b9e58867" - }, - "service": "device-control-core", - "composeActivation": "replace-core-network-membership-with-private-plus-egress", - "identity": "reuse-existing-runner-managed-host-local-private-key-public-certificate-export", - "identityRecovery": "forbidden-valid-existing-identity-required", - "tlsPurpose": "clientAuth", - "direction": "core-initiated", - "endpointPolicy": "public-ipv4-standard-https-tcp-443-only", - "coreNetworks": [ - "device-plane-private", - "device-plane-egress" - ], - "removedCoreNetwork": "device-plane-control", - "composeCompatibility": "synology-compose-v2.20-no-gw-priority", - "publicIngress": "none-on-synology", - "edgeRegistrations": "preserved", - "commandTransport": "disabled", - "gelios": "untouched", - "preservedServices": [ - "device-manager", - "device-gateway", - "device-postgres", - "device-backhaul-target" - ], - "healthGate": "bounded-container-grace+core-edge-contract", - "rollback": "restore-upgrade-v2-021-source-and-preapply-core-runtime" -} diff --git a/device-plane/docker-compose.device-plane.yml b/device-plane/docker-compose.device-plane.yml index f36bd16..b8b859c 100644 --- a/device-plane/docker-compose.device-plane.yml +++ b/device-plane/docker-compose.device-plane.yml @@ -70,6 +70,7 @@ services: - "127.0.0.1:18120:18120" networks: - device-plane-private + - device-plane-control depends_on: device-postgres: condition: service_healthy diff --git a/infra/deploy-runner/build-device-edge-core-channel-bootstrap-artifact.mjs b/infra/deploy-runner/build-device-edge-core-channel-bootstrap-artifact.mjs index 484c9b2..bbdea3c 100644 --- a/infra/deploy-runner/build-device-edge-core-channel-bootstrap-artifact.mjs +++ b/infra/deploy-runner/build-device-edge-core-channel-bootstrap-artifact.mjs @@ -15,25 +15,19 @@ if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) { throw new Error("usage: build-device-edge-core-channel-bootstrap-artifact.mjs [patch-id]"); } -const upgradeV3 = patchId.startsWith("device-edge-core-channel-upgrade-v3-"); -const upgradeV2 = !upgradeV3 && patchId.startsWith("device-edge-core-channel-upgrade-v2-"); -const upgradeV1 = !upgradeV3 && !upgradeV2 && patchId.startsWith("device-edge-core-channel-upgrade-"); -const upgrade = upgradeV1 || upgradeV2 || upgradeV3; -const descriptorPath = upgradeV3 - ? "deployment/device-edge-core-channel-upgrade-v3.json" - : upgradeV2 - ? "deployment/device-edge-core-channel-upgrade-v2.json" +const upgradeV2 = patchId.startsWith("device-edge-core-channel-upgrade-v2-"); +const upgradeV1 = !upgradeV2 && patchId.startsWith("device-edge-core-channel-upgrade-"); +const upgrade = upgradeV1 || upgradeV2; +const descriptorPath = upgradeV2 + ? "deployment/device-edge-core-channel-upgrade-v2.json" : upgradeV1 ? "deployment/device-edge-core-channel-upgrade-v1.json" : "deployment/device-edge-core-channel-bootstrap-v1.json"; -const composePath = upgradeV3 - ? "docker-compose.device-plane.yml" - : "docker-compose.device-edge-core-channel.yml"; const entries = [ ".dockerignore", "package.json", "package-lock.json", - composePath, + "docker-compose.device-edge-core-channel.yml", "packages/device-protocol-contract", "packages/device-edge-channel-contract", "services/device-control-core", @@ -79,79 +73,39 @@ try { } } - const compose = await readFile(join(payload, composePath), "utf8"); - const commonComposeRequired = [ + const compose = await readFile(join(payload, "docker-compose.device-edge-core-channel.yml"), "utf8"); + for (const required of [ "device-control-core:", - ]; - const composeRequired = upgradeV3 - ? [ - ...commonComposeRequired, - " - device-plane-private", - " - device-plane-control", - ] - : [ - ...commonComposeRequired, - "DEVICE_EDGE_CHANNEL_ENABLED: \"true\"", - "DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: /run/nodedc-secrets/device-edge-channel/core-private-key.pem", - "DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE: /run/nodedc-secrets/device-edge-channel/core-certificate.pem", - "DEVICE_EDGE_CHANNEL_TRUST_ROOT: /run/nodedc-secrets/device-edge-channel/peers", - "source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/core-private-key.pem", - "source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/core-certificate.pem", - "source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/peers", - "name: nodedc-device-plane-egress", - ]; - for (const required of composeRequired) { + "DEVICE_EDGE_CHANNEL_ENABLED: \"true\"", + "DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: /run/nodedc-secrets/device-edge-channel/core-private-key.pem", + "DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE: /run/nodedc-secrets/device-edge-channel/core-certificate.pem", + "DEVICE_EDGE_CHANNEL_TRUST_ROOT: /run/nodedc-secrets/device-edge-channel/peers", + "source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/core-private-key.pem", + "source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/core-certificate.pem", + "source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/peers", + "name: nodedc-device-plane-egress", + ]) { if (!compose.includes(required)) { throw new Error(`device_edge_core_channel_compose_contract_missing:${required}`); } } - if (upgradeV3) { - const coreBlock = compose.split("\n device-control-core:", 2)[1]?.split("\n device-gateway:", 1)[0] || ""; - const gatewayBlock = compose.split("\n device-gateway:", 2)[1]?.split("\nnetworks:", 1)[0] || ""; - if ( - !coreBlock.includes(" - device-plane-private") - || coreBlock.includes(" - device-plane-control") - || !gatewayBlock.includes(" - device-plane-private") - || !gatewayBlock.includes(" - device-plane-control") - ) { - throw new Error("device_edge_core_channel_v3_network_boundary_mismatch"); - } - } - const composeForbidden = upgradeV3 - ? ["gw_priority:", "network_mode:", "privileged:"] - : [ - "device-manager:", - "device-gateway:", - "device-postgres:", - "PRIVATE KEY", - "ports:", - "network_mode:", - "privileged:", - ]; - for (const forbidden of composeForbidden) { + for (const forbidden of [ + "device-manager:", + "device-gateway:", + "device-postgres:", + "PRIVATE KEY", + "ports:", + "network_mode:", + "privileged:", + ]) { if (compose.includes(forbidden)) { throw new Error(`device_edge_core_channel_compose_boundary_violation:${forbidden}`); } } const descriptor = JSON.parse(await readFile(join(payload, descriptorPath), "utf8")); - const descriptorContractMatches = upgradeV3 + const descriptorContractMatches = upgradeV2 ? ( - descriptor.schemaVersion === "nodedc.device-plane.device-edge-core-channel-upgrade.v3" - && descriptor.action === "upgrade" - && descriptor.composeActivation === "replace-core-network-membership-with-private-plus-egress" - && descriptor.identityRecovery === "forbidden-valid-existing-identity-required" - && descriptor.endpointPolicy === "public-ipv4-standard-https-tcp-443-only" - && descriptor.upgradePredecessor?.patchId === "device-edge-core-channel-upgrade-v2-20260812-021" - && descriptor.upgradePredecessor?.artifactSha256 === "e40a6fd24edfecac09e42cd82635a77850541bcf047788db3e9c55d2b9e58867" - && JSON.stringify(descriptor.coreNetworks) === JSON.stringify(["device-plane-private", "device-plane-egress"]) - && descriptor.removedCoreNetwork === "device-plane-control" - && descriptor.composeCompatibility === "synology-compose-v2.20-no-gw-priority" - && descriptor.edgeRegistrations === "preserved" - && descriptor.rollback === "restore-upgrade-v2-021-source-and-preapply-core-runtime" - ) - : upgradeV2 - ? ( descriptor.schemaVersion === "nodedc.device-plane.device-edge-core-channel-upgrade.v2" && descriptor.action === "upgrade" && descriptor.composeActivation === "preserve-dedicated-additive-override" diff --git a/infra/deploy-runner/nodedc-deploy b/infra/deploy-runner/nodedc-deploy index de48d21..8124600 100755 --- a/infra/deploy-runner/nodedc-deploy +++ b/infra/deploy-runner/nodedc-deploy @@ -256,21 +256,12 @@ DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL = ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL = ( "deployment/device-edge-core-channel-upgrade-v2.json" ) -DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_REL = ( - "deployment/device-edge-core-channel-upgrade-v3.json" -) DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL = ( "docker-compose.device-edge-core-channel.yml" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_SHA256 = ( "cac1e06b21202d8d96f5694b1adb1e67c0a3cdd31fcfca56cb7e19839c8516d8" ) -DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_PREDECESSOR_BASE_COMPOSE_SHA256 = ( - "eb1018cc0ffeaa0c019944d8810e2daa01eeca4c06289efff175785fb16457e7" -) -DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_BASE_COMPOSE_SHA256 = ( - "6a4c08313cc97bbfd86c4e133c6b2e55ba18a499a3bb6f267c7059754f83e8cf" -) DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES = ( ".dockerignore", "package.json", @@ -301,16 +292,6 @@ DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES = ( "services/device-control-core", DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL, ) -DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_ENTRIES = ( - ".dockerignore", - "package.json", - "package-lock.json", - "docker-compose.device-plane.yml", - "packages/device-protocol-contract", - "packages/device-edge-channel-contract", - "services/device-control-core", - DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_REL, -) DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID = ( "device-edge-core-channel-bootstrap-20260812-018" ) @@ -323,12 +304,6 @@ DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_PATCH_ID = ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_ARTIFACT_SHA256 = ( "8e9a220275959f378c1c4b00be5c7192e79afe2134eaab808a64e515870a8438" ) -DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_PREDECESSOR_PATCH_ID = ( - "device-edge-core-channel-upgrade-v2-20260812-021" -) -DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_PREDECESSOR_ARTIFACT_SHA256 = ( - "e40a6fd24edfecac09e42cd82635a77850541bcf047788db3e9c55d2b9e58867" -) DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_PATCH_ID = ( "device-manager-release-20260811-010" ) @@ -3860,7 +3835,6 @@ def allowed_payload_path(component, rel): DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL, - DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_REL, DEVICE_PLANE_MANAGER_RECONCILIATION_REL, DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL, "packages/device-protocol-contract", @@ -8917,15 +8891,7 @@ def load_artifact(artifact, work_dir): manifest["component"], entries, ): - if is_device_plane_edge_core_channel_upgrade_v3_slice( - manifest["component"], - entries, - ): - validate_device_plane_edge_core_channel_upgrade_v3_payload( - payload_dir, - expected_transition_id=manifest["id"], - ) - elif is_device_plane_edge_core_channel_upgrade_v2_slice( + if is_device_plane_edge_core_channel_upgrade_v2_slice( manifest["component"], entries, ): @@ -9438,7 +9404,6 @@ def is_device_plane_edge_core_channel_bootstrap_slice(component, entries): DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES, - DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_ENTRIES, ) ) @@ -9450,7 +9415,6 @@ def is_device_plane_edge_core_channel_upgrade_slice(component, entries): and tuple(entries) in ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES, - DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_ENTRIES, ) ) @@ -9463,14 +9427,6 @@ def is_device_plane_edge_core_channel_upgrade_v2_slice(component, entries): ) -def is_device_plane_edge_core_channel_upgrade_v3_slice(component, entries): - return ( - component == "device-plane" - and entries is not None - and tuple(entries) == DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_ENTRIES - ) - - def is_device_plane_manager_v2_control_plane_slice(component, entries): return ( component == "device-plane" @@ -9705,58 +9661,6 @@ def expected_device_plane_edge_core_channel_upgrade_v2_descriptor( } -def expected_device_plane_edge_core_channel_upgrade_v3_descriptor( - transition_id, -): - return { - "schemaVersion": ( - "nodedc.device-plane.device-edge-core-channel-upgrade.v3" - ), - "transitionId": transition_id, - "action": "upgrade", - "upgradePredecessor": { - "patchId": ( - DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_PREDECESSOR_PATCH_ID - ), - "artifactSha256": ( - DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_PREDECESSOR_ARTIFACT_SHA256 - ), - }, - "service": "device-control-core", - "composeActivation": ( - "replace-core-network-membership-with-private-plus-egress" - ), - "identity": ( - "reuse-existing-runner-managed-host-local-private-key-" - "public-certificate-export" - ), - "identityRecovery": "forbidden-valid-existing-identity-required", - "tlsPurpose": "clientAuth", - "direction": "core-initiated", - "endpointPolicy": "public-ipv4-standard-https-tcp-443-only", - "coreNetworks": [ - "device-plane-private", - "device-plane-egress", - ], - "removedCoreNetwork": "device-plane-control", - "composeCompatibility": "synology-compose-v2.20-no-gw-priority", - "publicIngress": "none-on-synology", - "edgeRegistrations": "preserved", - "commandTransport": "disabled", - "gelios": "untouched", - "preservedServices": [ - "device-manager", - "device-gateway", - "device-postgres", - "device-backhaul-target", - ], - "healthGate": "bounded-container-grace+core-edge-contract", - "rollback": ( - "restore-upgrade-v2-021-source-and-preapply-core-runtime" - ), - } - - def expected_device_plane_manager_failed_control_plane_descriptor(): return { "schemaVersion": ( @@ -10144,36 +10048,6 @@ def validate_device_plane_edge_core_channel_upgrade_v2_payload( return descriptor -def validate_device_plane_edge_core_channel_upgrade_v3_payload( - payload_dir, - *, - expected_transition_id=None, -): - descriptor = read_strict_json( - payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_REL, - "Device Edge Core channel upgrade v3 descriptor", - max_bytes=16 * 1024, - ) - transition_id = descriptor.get("transitionId") - if ( - not isinstance(transition_id, str) - or not re.fullmatch(r"[A-Za-z0-9._-]{1,96}", transition_id) - or ( - expected_transition_id is not None - and transition_id != expected_transition_id - ) - or descriptor - != expected_device_plane_edge_core_channel_upgrade_v3_descriptor( - transition_id - ) - ): - die("Device Edge Core channel upgrade v3 descriptor mismatch") - validate_device_plane_edge_core_channel_upgrade_v3_base_compose( - payload_dir - ) - return descriptor - - def validate_device_plane_edge_core_channel_compose(payload_dir): compose = payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL if sha256_file(compose) != DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_SHA256: @@ -10213,39 +10087,6 @@ def validate_device_plane_edge_core_channel_compose(payload_dir): return compose -def validate_device_plane_edge_core_channel_upgrade_v3_base_compose( - payload_dir, -): - compose = payload_dir / "docker-compose.device-plane.yml" - if ( - sha256_file(compose) - != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_BASE_COMPOSE_SHA256 - ): - die("Device Edge Core channel v3 base Compose mismatch") - compose_text = compose.read_text(encoding="utf-8") - try: - core_block = compose_text.split( - "\n device-control-core:", 1 - )[1].split("\n device-gateway:", 1)[0] - gateway_block = compose_text.split( - "\n device-gateway:", 1 - )[1].split("\nnetworks:", 1)[0] - except IndexError: - die("Device Edge Core channel v3 service topology is incomplete") - if ( - " - device-plane-private" not in core_block - or " - device-plane-control" in core_block - or " - device-plane-private" not in gateway_block - or " - device-plane-control" not in gateway_block - or "gw_priority:" in compose_text - or "network_mode:" in compose_text - or "privileged:" in compose_text - ): - die("Device Edge Core channel v3 network boundary mismatch") - return compose - return compose - - def installed_device_plane_manager_compose_sha256(): root = DEVICE_PLANE_ROOT v1 = root / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL @@ -11936,12 +11777,6 @@ def inspect_device_edge_channel_core_identity_state(): def validate_device_plane_edge_core_channel_bootstrap_predecessor( payload_dir, ): - if ( - payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_REL - ).exists(): - return validate_device_plane_edge_core_channel_upgrade_v3_predecessor( - payload_dir - ) if ( payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL ).exists(): @@ -12240,96 +12075,6 @@ def validate_device_plane_edge_core_channel_upgrade_v2_predecessor( } -def validate_device_plane_edge_core_channel_upgrade_v3_predecessor( - payload_dir, -): - descriptor = validate_device_plane_edge_core_channel_upgrade_v3_payload( - payload_dir - ) - installed_upgrade = ( - DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_REL - ) - if installed_upgrade.exists() or installed_upgrade.is_symlink(): - die("Device Edge Core channel upgrade v3 is already installed") - predecessor = descriptor["upgradePredecessor"] - artifact_name = f"nodedc-device-plane-{predecessor['patchId']}.tgz" - artifact = APPLIED_DIR / artifact_name - if ( - not artifact.is_file() - or artifact.is_symlink() - or sha256_file(artifact) != predecessor["artifactSha256"] - ): - die("Device Edge Core channel upgrade v3 predecessor mismatch") - records = [ - row for row in load_state(STATE_FILE) - if row.get("id") == predecessor["patchId"] - and row.get("sha256") == predecessor["artifactSha256"] - ] - if ( - len(records) != 1 - or records[0].get("status") != "ok" - or records[0].get("component") != "device-plane" - or records[0].get("artifact") != artifact_name - ): - die("Device Edge Core channel upgrade v3 predecessor journal mismatch") - with tempfile.TemporaryDirectory( - prefix="device-edge-core-channel-upgrade-v3-predecessor-", - dir=TMP_DIR, - ) as directory: - manifest, entries, predecessor_payload = load_artifact( - artifact, - Path(directory), - ) - if ( - manifest.get("id") != predecessor["patchId"] - or manifest.get("component") != "device-plane" - or manifest.get("type") != "app-overlay" - or tuple(entries) - != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES - ): - die("Device Edge Core channel upgrade v3 predecessor type mismatch") - validate_device_plane_edge_core_channel_upgrade_v2_payload( - predecessor_payload, - expected_transition_id=predecessor["patchId"], - ) - expected_source = collect_exact_files( - predecessor_payload, - DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES, - "Device Edge Core channel upgrade v3 predecessor source", - ) - actual_source = collect_exact_files( - DEVICE_PLANE_ROOT, - DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES, - "installed Device Edge Core channel upgrade v2 source", - ) - if actual_source != expected_source: - die("installed Device Edge Core channel upgrade v2 source drift detected") - installed_base_compose = DEVICE_PLANE_ROOT / "docker-compose.device-plane.yml" - if ( - installed_base_compose.is_symlink() - or not installed_base_compose.is_file() - or sha256_file(installed_base_compose) - != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_PREDECESSOR_BASE_COMPOSE_SHA256 - ): - die("installed Device Edge Core channel v3 base predecessor drift detected") - identity_state = inspect_device_edge_channel_core_identity_state() - if identity_state != "valid-reuse-at-apply": - die("Device Edge Core channel upgrade v3 requires the valid active identity") - for service in ( - "device-control-core", - "device-manager", - "device-gateway", - "device-postgres", - ): - healthcheck_compose_service("device-plane", service) - return { - "mode": "edge-core-channel-private-plus-egress-upgrade-v3", - "descriptor": descriptor, - "identityState": identity_state, - "upgradeArtifact": artifact, - } - - def validate_device_plane_manager_v2_reconciliation_backup(): backup_dir = ( BACKUPS_DIR / DEVICE_PLANE_MANAGER_V2_RECONCILIATION_BACKUP_ID @@ -16138,9 +15883,6 @@ def component_compose_files( edge_upgrade_v2_descriptor = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL ) - edge_upgrade_v3_descriptor = ( - DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_REL - ) edge_override = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL ) @@ -16155,10 +15897,6 @@ def component_compose_files( edge_upgrade_v2_descriptor.exists() or edge_upgrade_v2_descriptor.is_symlink() ) - edge_upgrade_v3_descriptor_exists = ( - edge_upgrade_v3_descriptor.exists() - or edge_upgrade_v3_descriptor.is_symlink() - ) edge_override_exists = edge_override.exists() or edge_override.is_symlink() if ( edge_override_exists @@ -16166,7 +15904,6 @@ def component_compose_files( edge_descriptor_exists or edge_upgrade_descriptor_exists or edge_upgrade_v2_descriptor_exists - or edge_upgrade_v3_descriptor_exists ) or edge_upgrade_descriptor_exists and not edge_descriptor_exists @@ -16174,12 +15911,6 @@ def component_compose_files( and not ( edge_descriptor_exists and edge_upgrade_descriptor_exists ) - or edge_upgrade_v3_descriptor_exists - and not ( - edge_descriptor_exists - and edge_upgrade_descriptor_exists - and edge_upgrade_v2_descriptor_exists - ) ): die("installed Device Edge Core channel source is incomplete") if edge_descriptor_exists: @@ -16257,35 +15988,6 @@ def component_compose_files( ) ): die("installed Device Edge Core channel upgrade v2 descriptor drift detected") - if edge_upgrade_v3_descriptor_exists: - if ( - upgrade_v2_id - != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_PREDECESSOR_PATCH_ID - or edge_upgrade_v3_descriptor.is_symlink() - or not edge_upgrade_v3_descriptor.is_file() - or sha256_file( - DEVICE_PLANE_ROOT / "docker-compose.device-plane.yml" - ) - != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_BASE_COMPOSE_SHA256 - ): - die("installed Device Edge Core channel upgrade v3 predecessor drift detected") - upgrade_v3 = read_strict_json( - edge_upgrade_v3_descriptor, - "installed Device Edge Core channel upgrade v3 descriptor", - max_bytes=16 * 1024, - ) - upgrade_v3_id = upgrade_v3.get("transitionId") - if ( - not isinstance(upgrade_v3_id, str) - or not re.fullmatch( - r"[A-Za-z0-9._-]{1,96}", upgrade_v3_id - ) - or upgrade_v3 - != expected_device_plane_edge_core_channel_upgrade_v3_descriptor( - upgrade_v3_id - ) - ): - die("installed Device Edge Core channel upgrade v3 descriptor drift detected") files = (*files, edge_override) overlay = DEVICE_PLANE_ROOT / DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL if overlay.exists() or overlay.is_symlink(): @@ -20281,19 +19983,6 @@ def plan_artifact(artifact): f"{predecessor['artifactSha256']}" ) print("device_edge_channel_endpoint_policy=public-ipv4-standard-https-tcp-443-only") - if "coreNetworks" in edge_descriptor: - print( - "device_edge_channel_core_networks=" - f"{','.join(edge_descriptor['coreNetworks'])}" - ) - print( - "device_edge_channel_removed_core_network=" - f"{edge_descriptor['removedCoreNetwork']}" - ) - print( - "device_edge_channel_compose_compatibility=" - f"{edge_descriptor['composeCompatibility']}" - ) else: print( "device_plane_predecessor_patch=" diff --git a/infra/deploy-runner/test_device_manager_control_plane_artifacts.py b/infra/deploy-runner/test_device_manager_control_plane_artifacts.py index c6b07b7..2d8247c 100644 --- a/infra/deploy-runner/test_device_manager_control_plane_artifacts.py +++ b/infra/deploy-runner/test_device_manager_control_plane_artifacts.py @@ -364,89 +364,6 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase): ) ) - def test_edge_core_channel_upgrade_v3_is_core_only_and_pins_upgrade_021(self): - patch_id = "device-edge-core-channel-upgrade-v3-unit-001" - manifest, entries, names, result = self.assert_deterministic_artifact( - "build-device-edge-core-channel-bootstrap-artifact.mjs", - patch_id, - RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_ENTRIES, - ) - self.assertEqual(manifest["component"], "device-plane") - self.assertEqual( - RUNNER.component_services("device-plane", entries), - ("device-control-core",), - ) - self.assertEqual(len(RUNNER.component_builds("device-plane", entries)), 1) - self.assertEqual(result["services"], ["device-control-core"]) - self.assertIn( - "payload/deployment/device-edge-core-channel-upgrade-v3.json", - names, - ) - self.assertIn( - "payload/docker-compose.device-plane.yml", - names, - ) - descriptor = ( - RUNNER.expected_device_plane_edge_core_channel_upgrade_v3_descriptor( - patch_id - ) - ) - self.assertEqual( - descriptor["upgradePredecessor"]["patchId"], - "device-edge-core-channel-upgrade-v2-20260812-021", - ) - self.assertEqual( - descriptor["upgradePredecessor"]["artifactSha256"], - "e40a6fd24edfecac09e42cd82635a77850541bcf047788db3e9c55d2b9e58867", - ) - self.assertEqual( - descriptor["coreNetworks"], - ["device-plane-private", "device-plane-egress"], - ) - self.assertEqual( - descriptor["removedCoreNetwork"], - "device-plane-control", - ) - self.assertEqual( - descriptor["composeCompatibility"], - "synology-compose-v2.20-no-gw-priority", - ) - self.assertTrue( - RUNNER.is_device_plane_edge_core_channel_upgrade_v3_slice( - "device-plane", - entries, - ) - ) - - def test_edge_core_channel_upgrade_v3_rejects_installed_marker(self): - with tempfile.TemporaryDirectory( - prefix="nodedc-device-edge-upgrade-v3-installed-", - ) as directory: - root = Path(directory) - marker = root / RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_REL - marker.parent.mkdir(parents=True) - marker.write_text("{}\n", encoding="utf-8") - descriptor = ( - RUNNER.expected_device_plane_edge_core_channel_upgrade_v3_descriptor( - "device-edge-core-channel-upgrade-v3-unit-002" - ) - ) - with ( - mock.patch.object(RUNNER, "DEVICE_PLANE_ROOT", root), - mock.patch.object( - RUNNER, - "validate_device_plane_edge_core_channel_upgrade_v3_payload", - return_value=descriptor, - ), - ): - with self.assertRaisesRegex( - RUNNER.DeployError, - "upgrade v3 is already installed", - ): - RUNNER.validate_device_plane_edge_core_channel_upgrade_v3_predecessor( - root / "payload" - ) - def test_edge_core_channel_upgrade_v2_rejects_installed_marker(self): with tempfile.TemporaryDirectory( prefix="nodedc-device-edge-upgrade-v2-installed-", @@ -591,120 +508,6 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase): ], ) - def test_edge_core_channel_upgrade_v3_accepts_exact_applied_021(self): - with tempfile.TemporaryDirectory( - prefix="nodedc-device-edge-upgrade-v3-predecessor-", - ) as directory: - root = Path(directory) - applied = root / "applied" - temporary = root / "tmp" - device_plane = root / "device-plane" - for path in (applied, temporary, device_plane / "deployment"): - path.mkdir(parents=True) - predecessor_patch = ( - RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_PREDECESSOR_PATCH_ID - ) - predecessor_sha = ( - RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_PREDECESSOR_ARTIFACT_SHA256 - ) - artifact_name = f"nodedc-device-plane-{predecessor_patch}.tgz" - artifact = applied / artifact_name - artifact.write_bytes(b"reviewed-upgrade-021") - installed_base = device_plane / "docker-compose.device-plane.yml" - installed_base.write_text("services: {}\n", encoding="utf-8") - state_file = root / "applied.jsonl" - state_file.write_text( - json.dumps({ - "id": predecessor_patch, - "artifact": artifact_name, - "component": "device-plane", - "sha256": predecessor_sha, - "status": "ok", - }) + "\n", - encoding="utf-8", - ) - descriptor = ( - RUNNER.expected_device_plane_edge_core_channel_upgrade_v3_descriptor( - "device-edge-core-channel-upgrade-v3-unit-003" - ) - ) - expected_source = {"source": "upgrade-021"} - with ( - mock.patch.object(RUNNER, "APPLIED_DIR", applied), - mock.patch.object(RUNNER, "TMP_DIR", temporary), - mock.patch.object(RUNNER, "STATE_FILE", state_file), - mock.patch.object(RUNNER, "DEVICE_PLANE_ROOT", device_plane), - mock.patch.object( - RUNNER, - "validate_device_plane_edge_core_channel_upgrade_v3_payload", - return_value=descriptor, - ), - mock.patch.object( - RUNNER, - "sha256_file", - side_effect=lambda path: ( - RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_PREDECESSOR_BASE_COMPOSE_SHA256 - if Path(path) == installed_base - else predecessor_sha - ), - ), - mock.patch.object( - RUNNER, - "load_artifact", - return_value=( - { - "id": predecessor_patch, - "component": "device-plane", - "type": "app-overlay", - }, - RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES, - root / "predecessor-payload", - ), - ), - mock.patch.object( - RUNNER, - "validate_device_plane_edge_core_channel_upgrade_v2_payload", - ) as validate_predecessor, - mock.patch.object( - RUNNER, - "collect_exact_files", - side_effect=(expected_source, expected_source), - ), - mock.patch.object( - RUNNER, - "inspect_device_edge_channel_core_identity_state", - return_value="valid-reuse-at-apply", - ), - mock.patch.object( - RUNNER, - "healthcheck_compose_service", - ) as health, - ): - result = ( - RUNNER.validate_device_plane_edge_core_channel_upgrade_v3_predecessor( - root / "payload" - ) - ) - - self.assertEqual( - result["mode"], - "edge-core-channel-private-plus-egress-upgrade-v3", - ) - self.assertEqual(result["upgradeArtifact"], artifact) - validate_predecessor.assert_called_once_with( - root / "predecessor-payload", - expected_transition_id=predecessor_patch, - ) - self.assertEqual( - [call.args for call in health.call_args_list], - [ - ("device-plane", "device-control-core"), - ("device-plane", "device-manager"), - ("device-plane", "device-gateway"), - ("device-plane", "device-postgres"), - ], - ) - def test_release_v2_keeps_release_v1_predecessor_contract_immutable(self): predecessor = device_manager_release_v1_descriptor( release_id="device-manager-release-20260811-010", @@ -944,24 +747,6 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase): allow_invalid_unexported_recovery=False ) - with ( - mock.patch.object( - RUNNER, - "ensure_platform_runtime_secret", - ), - mock.patch.object( - RUNNER, - "ensure_device_edge_channel_core_identity", - ) as ensure_edge_identity, - ): - RUNNER.prepare_component_runtime( - "device-plane", - RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_ENTRIES, - ) - ensure_edge_identity.assert_called_once_with( - allow_invalid_unexported_recovery=False - ) - def test_apply_gate_checks_exact_services_core_contract_and_runtime_boundary(self): entries = RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES services = ("device-control-core", "device-manager") @@ -1028,40 +813,6 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase): ) runtime_acceptance.assert_called_once_with(require_edge_channel=True) - def test_upgrade_v3_apply_gate_checks_preserved_runtime_and_edge_contract(self): - entries = RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_ENTRIES - services = ("device-control-core",) - with ( - mock.patch.object( - RUNNER, - "healthcheck_compose_service_with_grace", - ) as service_health, - mock.patch.object(RUNNER, "healthcheck_url") as url_health, - mock.patch.object( - RUNNER, - "validate_device_manager_control_plane_runtime", - ) as runtime_acceptance, - ): - RUNNER.run_healthchecks("device-plane", entries, services) - - self.assertEqual( - [call.args for call in service_health.call_args_list], - [ - ("device-plane", "device-control-core"), - ("device-plane", "device-manager"), - ("device-plane", "device-gateway"), - ("device-plane", "device-postgres"), - ], - ) - url_health.assert_called_once_with( - RUNNER.component_healthchecks( - "device-plane", - entries, - services, - )[0] - ) - runtime_acceptance.assert_called_once_with(require_edge_channel=True) - def test_activation_resolves_predecessor_from_descriptor_and_journal(self): with tempfile.TemporaryDirectory( prefix="nodedc-device-manager-release-predecessor-", @@ -1523,79 +1274,6 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase): runtime_acceptance.assert_called_once_with(require_edge_channel=True) self.assertEqual(result, f"source+runtime-restored:{len(entries)}") - def test_edge_upgrade_v3_rollback_restores_upgrade_021_core_runtime(self): - entries = RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_ENTRIES - missing = { - RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V3_REL, - } - existing = [entry for entry in entries if entry not in missing] - with tempfile.TemporaryDirectory( - prefix="nodedc-device-edge-upgrade-v3-rollback-", - ) as directory: - backup = Path(directory) / "backup" - backup.mkdir() - (backup / "existing-files.txt").write_text( - "\n".join(existing) + "\n", - encoding="utf-8", - ) - (backup / "missing-files.txt").write_text( - "\n".join(sorted(missing)) + "\n", - encoding="utf-8", - ) - (backup / "runtime-before.json").write_text( - json.dumps(healthy_device_plane_inventory(include_manager=True)), - encoding="utf-8", - ) - with ( - mock.patch.object( - RUNNER, - "stop_and_remove_compose_services", - ) as stop, - mock.patch.object( - RUNNER, - "restore_platform_overlay", - return_value=len(entries), - ), - mock.patch.object( - RUNNER, - "run_component_runtime", - ) as restore_runtime, - mock.patch.object( - RUNNER, - "healthcheck_compose_service_with_grace", - ) as restore_health, - mock.patch.object( - RUNNER, - "validate_device_manager_control_plane_runtime", - ) as runtime_acceptance, - ): - result = RUNNER.rollback_device_plane_apply( - Path(directory) / "live", - backup, - entries, - "test-stamp", - True, - ("device-control-core",), - ) - - stop.assert_not_called() - restore_runtime.assert_called_once_with( - "device-plane", - existing, - ("device-control-core",), - ) - self.assertEqual( - [call.args for call in restore_health.call_args_list], - [ - ("device-plane", "device-control-core"), - ("device-plane", "device-manager"), - ("device-plane", "device-gateway"), - ("device-plane", "device-postgres"), - ], - ) - runtime_acceptance.assert_called_once_with(require_edge_channel=True) - self.assertEqual(result, f"source+runtime-restored:{len(entries)}") - if __name__ == "__main__": unittest.main(verbosity=2)