feat(deploy): add versioned Core channel upgrade v2

This commit is contained in:
Codex
2026-08-12 09:48:51 +03:00
parent 020d4dbad7
commit 9fa74a1243
4 changed files with 633 additions and 17 deletions
+268 -9
View File
@@ -253,6 +253,9 @@ DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL = (
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL = (
"deployment/device-edge-core-channel-upgrade-v1.json"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL = (
"deployment/device-edge-core-channel-upgrade-v2.json"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL = (
"docker-compose.device-edge-core-channel.yml"
)
@@ -279,12 +282,28 @@ DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES = (
"services/device-control-core",
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL,
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES = (
".dockerignore",
"package.json",
"package-lock.json",
DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL,
"packages/device-protocol-contract",
"packages/device-edge-channel-contract",
"services/device-control-core",
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL,
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID = (
"device-edge-core-channel-bootstrap-20260812-018"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_ARTIFACT_SHA256 = (
"5598b7388b491fe524ab46038ce476482a93a6cf07d8ca5e00206c69ded02931"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_PATCH_ID = (
"device-edge-core-channel-upgrade-20260812-019"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_ARTIFACT_SHA256 = (
"8e9a220275959f378c1c4b00be5c7192e79afe2134eaab808a64e515870a8438"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_PATCH_ID = (
"device-manager-release-20260811-010"
)
@@ -3815,6 +3834,7 @@ def allowed_payload_path(component, rel):
DEVICE_PLANE_MANAGER_RELEASE_V2_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_MANAGER_RECONCILIATION_REL,
DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL,
"packages/device-protocol-contract",
@@ -8871,7 +8891,15 @@ def load_artifact(artifact, work_dir):
manifest["component"],
entries,
):
if is_device_plane_edge_core_channel_upgrade_slice(
if is_device_plane_edge_core_channel_upgrade_v2_slice(
manifest["component"],
entries,
):
validate_device_plane_edge_core_channel_upgrade_v2_payload(
payload_dir,
expected_transition_id=manifest["id"],
)
elif is_device_plane_edge_core_channel_upgrade_slice(
manifest["component"],
entries,
):
@@ -9375,6 +9403,7 @@ def is_device_plane_edge_core_channel_bootstrap_slice(component, entries):
and tuple(entries) in (
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES,
)
)
@@ -9383,7 +9412,18 @@ def is_device_plane_edge_core_channel_upgrade_slice(component, entries):
return (
component == "device-plane"
and entries is not None
and tuple(entries) == DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES
and tuple(entries) in (
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES,
)
)
def is_device_plane_edge_core_channel_upgrade_v2_slice(component, entries):
return (
component == "device-plane"
and entries is not None
and tuple(entries) == DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES
)
@@ -9577,6 +9617,50 @@ def expected_device_plane_edge_core_channel_upgrade_descriptor(
}
def expected_device_plane_edge_core_channel_upgrade_v2_descriptor(
transition_id,
):
return {
"schemaVersion": (
"nodedc.device-plane.device-edge-core-channel-upgrade.v2"
),
"transitionId": transition_id,
"action": "upgrade",
"upgradePredecessor": {
"patchId": (
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_PATCH_ID
),
"artifactSha256": (
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_ARTIFACT_SHA256
),
},
"service": "device-control-core",
"composeActivation": "preserve-dedicated-additive-override",
"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",
"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-019-source-and-preapply-core-runtime"
),
}
def expected_device_plane_manager_failed_control_plane_descriptor():
return {
"schemaVersion": (
@@ -9936,6 +10020,34 @@ def validate_device_plane_edge_core_channel_upgrade_payload(
return descriptor
def validate_device_plane_edge_core_channel_upgrade_v2_payload(
payload_dir,
*,
expected_transition_id=None,
):
descriptor = read_strict_json(
payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL,
"Device Edge Core channel upgrade v2 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_v2_descriptor(
transition_id
)
):
die("Device Edge Core channel upgrade v2 descriptor mismatch")
validate_device_plane_edge_core_channel_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:
@@ -11665,6 +11777,12 @@ 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_V2_REL
).exists():
return validate_device_plane_edge_core_channel_upgrade_v2_predecessor(
payload_dir
)
if (
payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL
).exists():
@@ -11864,6 +11982,99 @@ def validate_device_plane_edge_core_channel_upgrade_predecessor(
}
def validate_device_plane_edge_core_channel_upgrade_v2_predecessor(
payload_dir,
):
descriptor = validate_device_plane_edge_core_channel_upgrade_v2_payload(
payload_dir
)
installed_upgrade = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL
)
if installed_upgrade.exists() or installed_upgrade.is_symlink():
die("Device Edge Core channel upgrade v2 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 v2 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 v2 predecessor journal mismatch")
with tempfile.TemporaryDirectory(
prefix="device-edge-core-channel-upgrade-v2-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_ENTRIES
):
die("Device Edge Core channel upgrade v2 predecessor type mismatch")
validate_device_plane_edge_core_channel_upgrade_payload(
predecessor_payload,
expected_transition_id=predecessor["patchId"],
)
expected_source = collect_exact_files(
predecessor_payload,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES,
"Device Edge Core channel upgrade v2 predecessor source",
)
actual_source = collect_exact_files(
DEVICE_PLANE_ROOT,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES,
"installed Device Edge Core channel upgrade v1 source",
)
if actual_source != expected_source:
die("installed Device Edge Core channel upgrade v1 source drift detected")
installed_bootstrap = read_strict_json(
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL,
"installed Device Edge Core channel bootstrap predecessor",
max_bytes=16 * 1024,
)
if installed_bootstrap != (
expected_device_plane_edge_core_channel_bootstrap_descriptor(
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID
)
):
die("installed Device Edge Core channel bootstrap 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 v2 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-forward-upgrade-v2",
"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
@@ -15669,6 +15880,9 @@ def component_compose_files(
edge_upgrade_descriptor = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL
)
edge_upgrade_v2_descriptor = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL
)
edge_override = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL
)
@@ -15679,12 +15893,24 @@ def component_compose_files(
edge_upgrade_descriptor.exists()
or edge_upgrade_descriptor.is_symlink()
)
edge_upgrade_v2_descriptor_exists = (
edge_upgrade_v2_descriptor.exists()
or edge_upgrade_v2_descriptor.is_symlink()
)
edge_override_exists = edge_override.exists() or edge_override.is_symlink()
if (
edge_override_exists
!= (edge_descriptor_exists or edge_upgrade_descriptor_exists)
!= (
edge_descriptor_exists
or edge_upgrade_descriptor_exists
or edge_upgrade_v2_descriptor_exists
)
or edge_upgrade_descriptor_exists
and not edge_descriptor_exists
or edge_upgrade_v2_descriptor_exists
and not (
edge_descriptor_exists and edge_upgrade_descriptor_exists
)
):
die("installed Device Edge Core channel source is incomplete")
if edge_descriptor_exists:
@@ -15713,6 +15939,7 @@ def component_compose_files(
)
):
die("installed Device Edge Core channel descriptor drift detected")
upgrade_id = None
if edge_upgrade_descriptor_exists:
if (
transition_id
@@ -15736,6 +15963,31 @@ def component_compose_files(
)
):
die("installed Device Edge Core channel upgrade descriptor drift detected")
if edge_upgrade_v2_descriptor_exists:
if (
upgrade_id
!= DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_PATCH_ID
or edge_upgrade_v2_descriptor.is_symlink()
or not edge_upgrade_v2_descriptor.is_file()
):
die("installed Device Edge Core channel upgrade v2 predecessor drift detected")
upgrade_v2 = read_strict_json(
edge_upgrade_v2_descriptor,
"installed Device Edge Core channel upgrade v2 descriptor",
max_bytes=16 * 1024,
)
upgrade_v2_id = upgrade_v2.get("transitionId")
if (
not isinstance(upgrade_v2_id, str)
or not re.fullmatch(
r"[A-Za-z0-9._-]{1,96}", upgrade_v2_id
)
or upgrade_v2
!= expected_device_plane_edge_core_channel_upgrade_v2_descriptor(
upgrade_v2_id
)
):
die("installed Device Edge Core channel upgrade v2 descriptor drift detected")
files = (*files, edge_override)
overlay = DEVICE_PLANE_ROOT / DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL
if overlay.exists() or overlay.is_symlink():
@@ -19713,18 +19965,22 @@ def plan_artifact(artifact):
"device_plane_transition="
f"{device_plane_edge_core_channel_preflight['mode']}"
)
upgrade = (
device_plane_edge_core_channel_preflight["mode"]
== "edge-core-channel-standard-https-443-upgrade"
edge_descriptor = (
device_plane_edge_core_channel_preflight["descriptor"]
)
upgrade = edge_descriptor["action"] == "upgrade"
if upgrade:
predecessor = edge_descriptor.get(
"upgradePredecessor",
edge_descriptor.get("bootstrapPredecessor"),
)
print(
"device_plane_predecessor_patch="
f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID}"
f"{predecessor['patchId']}"
)
print(
"device_plane_predecessor_artifact_sha256="
f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_ARTIFACT_SHA256}"
f"{predecessor['artifactSha256']}"
)
print("device_edge_channel_endpoint_policy=public-ipv4-standard-https-tcp-443-only")
else:
@@ -19763,7 +20019,10 @@ def plan_artifact(artifact):
print("device_edge_channel_identity=host-local-private-key:public-certificate-export-only")
if upgrade:
print("device_edge_channel_identity_recovery=forbidden:reuse-valid-only")
print("device_edge_channel_registrations=preserved:explicit-443-reconciliation-required")
if edge_descriptor["edgeRegistrations"] == "preserved":
print("device_edge_channel_registrations=preserved")
else:
print("device_edge_channel_registrations=preserved:explicit-443-reconciliation-required")
else:
print("device_edge_channel_invalid_failed_016=recover-exact-unexported-only")
print("device_edge_channel_registrations=preserved")