fix(device-plane): activate manager from v2 reconciliation
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schemaVersion": "nodedc.device-plane.device-manager-control-plane.v3",
|
||||
"action": "activate",
|
||||
"predecessor": {
|
||||
"patchId": "device-manager-control-plane-v2-reconciliation-20260811-004",
|
||||
"artifactSha256": "09600bfd99717314b43936e17ffaaf6b6fca1f2fd008beddddc68480cdf3d284",
|
||||
"mode": "failed-v2-control-plane-baseline-adoption"
|
||||
},
|
||||
"service": "device-manager",
|
||||
"publicIngress": "reverse-proxy-only",
|
||||
"deviceCoreManagementApi": "file-token-authenticated",
|
||||
"launcherTrust": "file-token-scoped-to-device-core-handoff",
|
||||
"healthGate": "bounded-container-grace+core-contract",
|
||||
"commandTransport": "disabled",
|
||||
"gelios": "untouched",
|
||||
"rollback": "restore-v2-reconciled-baseline"
|
||||
}
|
||||
@@ -12,7 +12,7 @@ const devicePlaneRoot = resolve(platformRoot, "device-plane");
|
||||
const designRoot = resolve(process.env.NODEDC_DEVICE_MANAGER_SOURCE_ROOT || resolve(platformRoot, "../NODEDC_DESIGN_GUIDELINE"));
|
||||
const managerRoot = resolve(designRoot, "apps/device-manager");
|
||||
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
|
||||
const [patchId = "device-manager-control-plane-20260811-003", ...extra] = process.argv.slice(2);
|
||||
const [patchId = "device-manager-control-plane-v3-20260811-005", ...extra] = process.argv.slice(2);
|
||||
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) throw new Error("usage: build-device-manager-control-plane-artifact.mjs [patch-id]");
|
||||
|
||||
const entries = [
|
||||
@@ -26,7 +26,7 @@ const entries = [
|
||||
"services/device-gateway/package.json",
|
||||
"services/device-edge-relay/package.json",
|
||||
"services/device-manager",
|
||||
"deployment/device-manager-control-plane-v2.json",
|
||||
"deployment/device-manager-control-plane-v3.json",
|
||||
];
|
||||
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-manager-control-plane-"));
|
||||
const payload = join(stage, "payload");
|
||||
@@ -89,15 +89,16 @@ try {
|
||||
if (compose.includes(forbidden)) throw new Error(`device_manager_compose_boundary_violation:${forbidden}`);
|
||||
}
|
||||
const descriptor = JSON.parse(await readFile(
|
||||
join(payload, "deployment/device-manager-control-plane-v2.json"),
|
||||
join(payload, "deployment/device-manager-control-plane-v3.json"),
|
||||
"utf8",
|
||||
));
|
||||
if (
|
||||
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-control-plane.v2"
|
||||
|| descriptor.predecessor?.patchId !== "device-manager-control-plane-reconciliation-20260811-002"
|
||||
|| descriptor.predecessor?.artifactSha256 !== "dd86dd58e4f649db0981db5089e003caf3961356179f2abb514662351487e1e6"
|
||||
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-control-plane.v3"
|
||||
|| descriptor.predecessor?.patchId !== "device-manager-control-plane-v2-reconciliation-20260811-004"
|
||||
|| descriptor.predecessor?.artifactSha256 !== "09600bfd99717314b43936e17ffaaf6b6fca1f2fd008beddddc68480cdf3d284"
|
||||
|| descriptor.predecessor?.mode !== "failed-v2-control-plane-baseline-adoption"
|
||||
|| descriptor.healthGate !== "bounded-container-grace+core-contract"
|
||||
|| descriptor.rollback !== "restore-reconciled-baseline"
|
||||
|| descriptor.rollback !== "restore-v2-reconciled-baseline"
|
||||
) throw new Error("device_manager_activation_successor_contract_mismatch");
|
||||
await writeFile(join(stage, "manifest.env"), `id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`, "utf8");
|
||||
await writeFile(join(stage, "files.txt"), `${entries.join("\n")}\n`, "utf8");
|
||||
|
||||
@@ -213,9 +213,12 @@ DEVICE_PLANE_FOUNDATION_ENTRIES = (
|
||||
DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL = (
|
||||
"deployment/device-manager-control-plane-v1.json"
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL = (
|
||||
DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_REL = (
|
||||
"deployment/device-manager-control-plane-v2.json"
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL = (
|
||||
"deployment/device-manager-control-plane-v3.json"
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml"
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_SHA256 = (
|
||||
"4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e"
|
||||
@@ -233,6 +236,19 @@ DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES = (
|
||||
"services/device-manager",
|
||||
DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL,
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_ENTRIES = (
|
||||
".dockerignore",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_REL,
|
||||
"packages/device-protocol-contract",
|
||||
"packages/arusnavi-b2-adapter",
|
||||
"services/device-control-core",
|
||||
"services/device-gateway/package.json",
|
||||
"services/device-edge-relay/package.json",
|
||||
"services/device-manager",
|
||||
DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_REL,
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES = (
|
||||
".dockerignore",
|
||||
"package.json",
|
||||
@@ -334,6 +350,17 @@ DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT_SHA256 = (
|
||||
DEVICE_PLANE_MANAGER_V2_RECONCILIATION_PATCH_ID = (
|
||||
"device-manager-control-plane-v2-reconciliation-20260811-004"
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT = (
|
||||
"nodedc-device-plane-device-manager-control-plane-v2-reconciliation-"
|
||||
"20260811-004.tgz"
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT_SHA256 = (
|
||||
"09600bfd99717314b43936e17ffaaf6b6fca1f2fd008beddddc68480cdf3d284"
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_V2_RECONCILIATION_APPLY_BACKUP_ID = (
|
||||
"device-plane-device-manager-control-plane-v2-reconciliation-"
|
||||
"20260811-004-20260811-100533"
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_V2_RECONCILIATION_BACKUP_ID = (
|
||||
"device-plane-device-manager-control-plane-20260811-003-"
|
||||
"20260811-012505"
|
||||
@@ -371,7 +398,7 @@ DEVICE_PLANE_MANAGER_V2_RECONCILIATION_EXISTING = (
|
||||
DEVICE_PLANE_MANAGER_V2_RECONCILIATION_MISSING = (
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_REL,
|
||||
"services/device-manager",
|
||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
||||
DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_REL,
|
||||
)
|
||||
DEVICE_PLANE_FOUNDATION_RECOVERY_REL = (
|
||||
"deployment/device-plane-foundation-recovery-v1.json"
|
||||
@@ -8677,10 +8704,15 @@ def reject_terminal_device_plane_manager_artifact(
|
||||
if (
|
||||
manifest.get("id") == DEVICE_PLANE_MANAGER_V2_FAILED_PATCH_ID
|
||||
or sha256 == DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT_SHA256
|
||||
or is_device_plane_manager_v2_control_plane_slice(
|
||||
manifest.get("component"),
|
||||
entries,
|
||||
)
|
||||
):
|
||||
die(
|
||||
"Device Manager control-plane 003 is terminal failed; "
|
||||
"use the exact v2 reconciliation successor"
|
||||
"use the exact v2 reconciliation successor followed by the "
|
||||
"exact v3 activation successor"
|
||||
)
|
||||
|
||||
|
||||
@@ -8745,6 +8777,14 @@ def is_device_plane_manager_control_plane_slice(component, entries):
|
||||
)
|
||||
|
||||
|
||||
def is_device_plane_manager_v2_control_plane_slice(component, entries):
|
||||
return (
|
||||
component == "device-plane"
|
||||
and entries is not None
|
||||
and tuple(entries) == DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_ENTRIES
|
||||
)
|
||||
|
||||
|
||||
def is_device_plane_manager_failed_control_plane_slice(component, entries):
|
||||
return (
|
||||
component == "device-plane"
|
||||
@@ -8796,15 +8836,15 @@ def expected_platform_device_manager_public_route_descriptor():
|
||||
def expected_device_plane_manager_control_plane_descriptor():
|
||||
return {
|
||||
"schemaVersion": (
|
||||
"nodedc.device-plane.device-manager-control-plane.v2"
|
||||
"nodedc.device-plane.device-manager-control-plane.v3"
|
||||
),
|
||||
"action": "activate",
|
||||
"predecessor": {
|
||||
"patchId": DEVICE_PLANE_MANAGER_RECONCILIATION_PATCH_ID,
|
||||
"patchId": DEVICE_PLANE_MANAGER_V2_RECONCILIATION_PATCH_ID,
|
||||
"artifactSha256": (
|
||||
DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT_SHA256
|
||||
DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT_SHA256
|
||||
),
|
||||
"mode": "failed-control-plane-baseline-adoption",
|
||||
"mode": "failed-v2-control-plane-baseline-adoption",
|
||||
},
|
||||
"service": "device-manager",
|
||||
"publicIngress": "reverse-proxy-only",
|
||||
@@ -8813,7 +8853,7 @@ def expected_device_plane_manager_control_plane_descriptor():
|
||||
"healthGate": "bounded-container-grace+core-contract",
|
||||
"commandTransport": "disabled",
|
||||
"gelios": "untouched",
|
||||
"rollback": "restore-reconciled-baseline",
|
||||
"rollback": "restore-v2-reconciled-baseline",
|
||||
}
|
||||
|
||||
|
||||
@@ -10455,7 +10495,7 @@ def validate_device_plane_manager_activation_predecessor(payload_dir):
|
||||
payload_dir
|
||||
)
|
||||
reconciliation_artifact = (
|
||||
APPLIED_DIR / DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT
|
||||
APPLIED_DIR / DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT
|
||||
)
|
||||
try:
|
||||
artifact_stat = reconciliation_artifact.lstat()
|
||||
@@ -10465,7 +10505,7 @@ def validate_device_plane_manager_activation_predecessor(payload_dir):
|
||||
stat.S_ISLNK(artifact_stat.st_mode)
|
||||
or not stat.S_ISREG(artifact_stat.st_mode)
|
||||
or sha256_file(reconciliation_artifact)
|
||||
!= DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT_SHA256
|
||||
!= DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT_SHA256
|
||||
):
|
||||
die("Device Manager reconciliation applied artifact mismatch")
|
||||
|
||||
@@ -10473,30 +10513,30 @@ def validate_device_plane_manager_activation_predecessor(payload_dir):
|
||||
value
|
||||
for value in load_state(STATE_FILE)
|
||||
if value.get("id")
|
||||
== DEVICE_PLANE_MANAGER_RECONCILIATION_PATCH_ID
|
||||
== DEVICE_PLANE_MANAGER_V2_RECONCILIATION_PATCH_ID
|
||||
]
|
||||
if len(records) != 1:
|
||||
die("Device Manager reconciliation applied journal count mismatch")
|
||||
record = records[0]
|
||||
if (
|
||||
record.get("artifact")
|
||||
!= DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT
|
||||
!= DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT
|
||||
or record.get("backup_id")
|
||||
!= DEVICE_PLANE_MANAGER_RECONCILIATION_APPLY_BACKUP_ID
|
||||
!= DEVICE_PLANE_MANAGER_V2_RECONCILIATION_APPLY_BACKUP_ID
|
||||
or record.get("component") != "device-plane"
|
||||
or record.get("sha256")
|
||||
!= DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT_SHA256
|
||||
!= DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT_SHA256
|
||||
or record.get("status") != "ok"
|
||||
):
|
||||
die("Device Manager reconciliation applied journal mismatch")
|
||||
|
||||
backup_dir = validate_device_plane_manager_reconciliation_backup()
|
||||
runtime = validate_device_plane_manager_reconciled_baseline(
|
||||
backup_dir = validate_device_plane_manager_v2_reconciliation_backup()
|
||||
runtime = validate_device_plane_manager_v2_reconciled_baseline(
|
||||
backup_dir,
|
||||
marker_installed=True,
|
||||
)
|
||||
return {
|
||||
"mode": "reconciled-manager-forward-activation",
|
||||
"mode": "reconciled-v2-manager-forward-activation",
|
||||
"descriptor": descriptor,
|
||||
"reconciliationArtifact": reconciliation_artifact,
|
||||
"reconciliationRecord": record,
|
||||
@@ -10538,7 +10578,7 @@ def validate_device_plane_manager_v2_reconciliation_backup():
|
||||
backup_dir / "missing-files.txt"
|
||||
))
|
||||
validate_backup_partition(
|
||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES,
|
||||
DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_ENTRIES,
|
||||
existing,
|
||||
missing,
|
||||
"Device Manager v2 reconciliation",
|
||||
@@ -10584,7 +10624,10 @@ def validate_device_plane_manager_v2_reconciled_baseline(
|
||||
)
|
||||
if live_source != backup_source:
|
||||
die("Device Manager v2 rollback source does not match backup")
|
||||
for rel in DEVICE_PLANE_MANAGER_V2_RECONCILIATION_MISSING:
|
||||
for rel in (
|
||||
*DEVICE_PLANE_MANAGER_V2_RECONCILIATION_MISSING,
|
||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
||||
):
|
||||
path = root / rel
|
||||
if path.exists() or path.is_symlink():
|
||||
die(
|
||||
@@ -10667,7 +10710,7 @@ def validate_device_plane_manager_v2_reconciliation_evidence(payload_dir):
|
||||
or failed_manifest.get("component") != "device-plane"
|
||||
or failed_manifest.get("type") != "app-overlay"
|
||||
or tuple(failed_entries)
|
||||
!= DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES
|
||||
!= DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_ENTRIES
|
||||
):
|
||||
die("Device Manager v2 failed artifact contract mismatch")
|
||||
|
||||
@@ -18119,11 +18162,11 @@ def plan_artifact(artifact):
|
||||
)
|
||||
print(
|
||||
"device_plane_predecessor_patch="
|
||||
f"{DEVICE_PLANE_MANAGER_RECONCILIATION_PATCH_ID}"
|
||||
f"{DEVICE_PLANE_MANAGER_V2_RECONCILIATION_PATCH_ID}"
|
||||
)
|
||||
print(
|
||||
"device_plane_predecessor_artifact_sha256="
|
||||
f"{DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT_SHA256}"
|
||||
f"{DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT_SHA256}"
|
||||
)
|
||||
print(
|
||||
"device_plane_runtime_mutation="
|
||||
|
||||
@@ -239,21 +239,28 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
)
|
||||
runtime_acceptance.assert_called_once_with()
|
||||
|
||||
def test_v2_activation_requires_exact_applied_reconciliation(self):
|
||||
def test_v3_activation_requires_exact_applied_v2_reconciliation(self):
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-device-manager-v2-predecessor-",
|
||||
prefix="nodedc-device-manager-v3-predecessor-",
|
||||
) as directory:
|
||||
root = Path(directory)
|
||||
artifact = root / RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT
|
||||
artifact = (
|
||||
root
|
||||
/ RUNNER.DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT
|
||||
)
|
||||
artifact_bytes = b"reviewed-reconciliation-artifact"
|
||||
artifact.write_bytes(artifact_bytes)
|
||||
state_file = root / "applied.jsonl"
|
||||
state_file.write_text(
|
||||
json.dumps({
|
||||
"id": RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_PATCH_ID,
|
||||
"artifact": RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT,
|
||||
"id": (
|
||||
RUNNER.DEVICE_PLANE_MANAGER_V2_RECONCILIATION_PATCH_ID
|
||||
),
|
||||
"artifact": (
|
||||
RUNNER.DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT
|
||||
),
|
||||
"backup_id": (
|
||||
RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_APPLY_BACKUP_ID
|
||||
RUNNER.DEVICE_PLANE_MANAGER_V2_RECONCILIATION_APPLY_BACKUP_ID
|
||||
),
|
||||
"component": "device-plane",
|
||||
"sha256": hashlib.sha256(artifact_bytes).hexdigest(),
|
||||
@@ -267,7 +274,7 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
mock.patch.object(RUNNER, "STATE_FILE", state_file),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT_SHA256",
|
||||
"DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ARTIFACT_SHA256",
|
||||
hashlib.sha256(artifact_bytes).hexdigest(),
|
||||
),
|
||||
mock.patch.object(
|
||||
@@ -279,12 +286,12 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
) as payload,
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"validate_device_plane_manager_reconciliation_backup",
|
||||
"validate_device_plane_manager_v2_reconciliation_backup",
|
||||
return_value=backup,
|
||||
),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"validate_device_plane_manager_reconciled_baseline",
|
||||
"validate_device_plane_manager_v2_reconciled_baseline",
|
||||
return_value={"accepted": True},
|
||||
) as baseline,
|
||||
):
|
||||
@@ -296,7 +303,7 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(
|
||||
result["mode"],
|
||||
"reconciled-manager-forward-activation",
|
||||
"reconciled-v2-manager-forward-activation",
|
||||
)
|
||||
payload.assert_called_once_with(root / "payload")
|
||||
baseline.assert_called_once_with(backup, marker_installed=True)
|
||||
|
||||
@@ -221,6 +221,15 @@ class DeviceManagerControlPlaneReconciliationArtifactTest(unittest.TestCase):
|
||||
{"id": "different"},
|
||||
RUNNER.DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT_SHA256,
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
RUNNER.DeployError,
|
||||
"exact v2 reconciliation successor",
|
||||
):
|
||||
RUNNER.reject_terminal_device_plane_manager_artifact(
|
||||
{"id": "different", "component": "device-plane"},
|
||||
"1" * 64,
|
||||
RUNNER.DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_ENTRIES,
|
||||
)
|
||||
|
||||
def test_health_grace_waits_through_unhealthy_and_exited(self):
|
||||
results = [
|
||||
|
||||
Reference in New Issue
Block a user