fix(device-plane): activate manager from reconciled baseline
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "nodedc.device-plane.device-manager-control-plane.v2",
|
||||||
|
"action": "activate",
|
||||||
|
"predecessor": {
|
||||||
|
"patchId": "device-manager-control-plane-reconciliation-20260811-002",
|
||||||
|
"artifactSha256": "dd86dd58e4f649db0981db5089e003caf3961356179f2abb514662351487e1e6",
|
||||||
|
"mode": "failed-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-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 designRoot = resolve(process.env.NODEDC_DEVICE_MANAGER_SOURCE_ROOT || resolve(platformRoot, "../NODEDC_DESIGN_GUIDELINE"));
|
||||||
const managerRoot = resolve(designRoot, "apps/device-manager");
|
const managerRoot = resolve(designRoot, "apps/device-manager");
|
||||||
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
|
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
|
||||||
const [patchId = "device-manager-control-plane-20260810-001", ...extra] = process.argv.slice(2);
|
const [patchId = "device-manager-control-plane-20260811-003", ...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]");
|
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 = [
|
const entries = [
|
||||||
@@ -26,7 +26,7 @@ const entries = [
|
|||||||
"services/device-gateway/package.json",
|
"services/device-gateway/package.json",
|
||||||
"services/device-edge-relay/package.json",
|
"services/device-edge-relay/package.json",
|
||||||
"services/device-manager",
|
"services/device-manager",
|
||||||
"deployment/device-manager-control-plane-v1.json",
|
"deployment/device-manager-control-plane-v2.json",
|
||||||
];
|
];
|
||||||
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-manager-control-plane-"));
|
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-manager-control-plane-"));
|
||||||
const payload = join(stage, "payload");
|
const payload = join(stage, "payload");
|
||||||
@@ -67,6 +67,17 @@ try {
|
|||||||
]) {
|
]) {
|
||||||
if (compose.includes(forbidden)) throw new Error(`device_manager_compose_boundary_violation:${forbidden}`);
|
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"),
|
||||||
|
"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.healthGate !== "bounded-container-grace+core-contract"
|
||||||
|
|| descriptor.rollback !== "restore-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, "manifest.env"), `id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`, "utf8");
|
||||||
await writeFile(join(stage, "files.txt"), `${entries.join("\n")}\n`, "utf8");
|
await writeFile(join(stage, "files.txt"), `${entries.join("\n")}\n`, "utf8");
|
||||||
await mkdir(artifactDir, { recursive: true });
|
await mkdir(artifactDir, { recursive: true });
|
||||||
|
|||||||
@@ -210,13 +210,29 @@ DEVICE_PLANE_FOUNDATION_ENTRIES = (
|
|||||||
"services/device-control-core",
|
"services/device-control-core",
|
||||||
"services/device-gateway",
|
"services/device-gateway",
|
||||||
)
|
)
|
||||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL = (
|
DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL = (
|
||||||
"deployment/device-manager-control-plane-v1.json"
|
"deployment/device-manager-control-plane-v1.json"
|
||||||
)
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL = (
|
||||||
|
"deployment/device-manager-control-plane-v2.json"
|
||||||
|
)
|
||||||
DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml"
|
DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml"
|
||||||
DEVICE_PLANE_MANAGER_COMPOSE_SHA256 = (
|
DEVICE_PLANE_MANAGER_COMPOSE_SHA256 = (
|
||||||
"4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e"
|
"4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e"
|
||||||
)
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_FAILED_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_FAILED_CONTROL_PLANE_REL,
|
||||||
|
)
|
||||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES = (
|
DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES = (
|
||||||
".dockerignore",
|
".dockerignore",
|
||||||
"package.json",
|
"package.json",
|
||||||
@@ -246,6 +262,20 @@ DEVICE_PLANE_MANAGER_FAILED_ARTIFACT = (
|
|||||||
DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256 = (
|
DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256 = (
|
||||||
"50e275c1085286bcb3bb2b273aefc8bbba70f446ca2c7bd464dc745710a291a6"
|
"50e275c1085286bcb3bb2b273aefc8bbba70f446ca2c7bd464dc745710a291a6"
|
||||||
)
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_RECONCILIATION_PATCH_ID = (
|
||||||
|
"device-manager-control-plane-reconciliation-20260811-002"
|
||||||
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT = (
|
||||||
|
"nodedc-device-plane-device-manager-control-plane-reconciliation-"
|
||||||
|
"20260811-002.tgz"
|
||||||
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT_SHA256 = (
|
||||||
|
"dd86dd58e4f649db0981db5089e003caf3961356179f2abb514662351487e1e6"
|
||||||
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_RECONCILIATION_APPLY_BACKUP_ID = (
|
||||||
|
"device-plane-device-manager-control-plane-reconciliation-"
|
||||||
|
"20260811-002-20260811-004839"
|
||||||
|
)
|
||||||
DEVICE_PLANE_MANAGER_RECONCILIATION_BACKUP_ID = (
|
DEVICE_PLANE_MANAGER_RECONCILIATION_BACKUP_ID = (
|
||||||
"device-plane-device-manager-control-plane-20260810-001-"
|
"device-plane-device-manager-control-plane-20260810-001-"
|
||||||
"20260811-000321"
|
"20260811-000321"
|
||||||
@@ -283,7 +313,7 @@ DEVICE_PLANE_MANAGER_RECONCILIATION_EXISTING = (
|
|||||||
DEVICE_PLANE_MANAGER_RECONCILIATION_MISSING = (
|
DEVICE_PLANE_MANAGER_RECONCILIATION_MISSING = (
|
||||||
DEVICE_PLANE_MANAGER_COMPOSE_REL,
|
DEVICE_PLANE_MANAGER_COMPOSE_REL,
|
||||||
"services/device-manager",
|
"services/device-manager",
|
||||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL,
|
||||||
)
|
)
|
||||||
DEVICE_PLANE_FOUNDATION_RECOVERY_REL = (
|
DEVICE_PLANE_FOUNDATION_RECOVERY_REL = (
|
||||||
"deployment/device-plane-foundation-recovery-v1.json"
|
"deployment/device-plane-foundation-recovery-v1.json"
|
||||||
@@ -3169,6 +3199,7 @@ def allowed_payload_path(component, rel):
|
|||||||
DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL,
|
DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL,
|
||||||
DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL,
|
DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL,
|
||||||
DEVICE_PLANE_BACKHAUL_TARGET_REL,
|
DEVICE_PLANE_BACKHAUL_TARGET_REL,
|
||||||
|
DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL,
|
||||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
||||||
DEVICE_PLANE_MANAGER_RECONCILIATION_REL,
|
DEVICE_PLANE_MANAGER_RECONCILIATION_REL,
|
||||||
"packages/device-protocol-contract",
|
"packages/device-protocol-contract",
|
||||||
@@ -8216,6 +8247,13 @@ def load_artifact(artifact, work_dir):
|
|||||||
entries,
|
entries,
|
||||||
):
|
):
|
||||||
validate_device_plane_manager_control_plane_payload(payload_dir)
|
validate_device_plane_manager_control_plane_payload(payload_dir)
|
||||||
|
if is_device_plane_manager_failed_control_plane_slice(
|
||||||
|
manifest["component"],
|
||||||
|
entries,
|
||||||
|
):
|
||||||
|
validate_device_plane_manager_failed_control_plane_payload(
|
||||||
|
payload_dir
|
||||||
|
)
|
||||||
if is_device_plane_manager_reconciliation_slice(
|
if is_device_plane_manager_reconciliation_slice(
|
||||||
manifest["component"],
|
manifest["component"],
|
||||||
entries,
|
entries,
|
||||||
@@ -8555,14 +8593,22 @@ def reject_terminal_device_plane_foundation_artifact(manifest, sha256):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def reject_terminal_device_plane_manager_artifact(manifest, sha256):
|
def reject_terminal_device_plane_manager_artifact(
|
||||||
|
manifest,
|
||||||
|
sha256,
|
||||||
|
entries=None,
|
||||||
|
):
|
||||||
if (
|
if (
|
||||||
manifest.get("id") == DEVICE_PLANE_MANAGER_FAILED_PATCH_ID
|
manifest.get("id") == DEVICE_PLANE_MANAGER_FAILED_PATCH_ID
|
||||||
or sha256 == DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256
|
or sha256 == DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256
|
||||||
|
or is_device_plane_manager_failed_control_plane_slice(
|
||||||
|
manifest.get("component"),
|
||||||
|
entries,
|
||||||
|
)
|
||||||
):
|
):
|
||||||
die(
|
die(
|
||||||
"Device Manager control-plane 001 is terminal failed; "
|
"Device Manager control-plane 001 is terminal failed; "
|
||||||
"use the exact registered reconciliation successor"
|
"use reconciliation followed by the exact v2 activation successor"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -8627,6 +8673,15 @@ def is_device_plane_manager_control_plane_slice(component, entries):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_device_plane_manager_failed_control_plane_slice(component, entries):
|
||||||
|
return (
|
||||||
|
component == "device-plane"
|
||||||
|
and entries is not None
|
||||||
|
and tuple(entries)
|
||||||
|
== DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_device_plane_manager_reconciliation_slice(component, entries):
|
def is_device_plane_manager_reconciliation_slice(component, entries):
|
||||||
return (
|
return (
|
||||||
component == "device-plane"
|
component == "device-plane"
|
||||||
@@ -8658,6 +8713,30 @@ def expected_platform_device_manager_public_route_descriptor():
|
|||||||
|
|
||||||
|
|
||||||
def expected_device_plane_manager_control_plane_descriptor():
|
def expected_device_plane_manager_control_plane_descriptor():
|
||||||
|
return {
|
||||||
|
"schemaVersion": (
|
||||||
|
"nodedc.device-plane.device-manager-control-plane.v2"
|
||||||
|
),
|
||||||
|
"action": "activate",
|
||||||
|
"predecessor": {
|
||||||
|
"patchId": DEVICE_PLANE_MANAGER_RECONCILIATION_PATCH_ID,
|
||||||
|
"artifactSha256": (
|
||||||
|
DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT_SHA256
|
||||||
|
),
|
||||||
|
"mode": "failed-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-reconciled-baseline",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def expected_device_plane_manager_failed_control_plane_descriptor():
|
||||||
return {
|
return {
|
||||||
"schemaVersion": (
|
"schemaVersion": (
|
||||||
"nodedc.device-plane.device-manager-control-plane.v1"
|
"nodedc.device-plane.device-manager-control-plane.v1"
|
||||||
@@ -8782,6 +8861,23 @@ def validate_device_plane_manager_control_plane_payload(payload_dir):
|
|||||||
return descriptor
|
return descriptor
|
||||||
|
|
||||||
|
|
||||||
|
def validate_device_plane_manager_failed_control_plane_payload(payload_dir):
|
||||||
|
descriptor = read_strict_json(
|
||||||
|
payload_dir / DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL,
|
||||||
|
"failed Device Manager control-plane descriptor",
|
||||||
|
max_bytes=16 * 1024,
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
descriptor
|
||||||
|
!= expected_device_plane_manager_failed_control_plane_descriptor()
|
||||||
|
):
|
||||||
|
die("failed Device Manager control-plane descriptor mismatch")
|
||||||
|
compose_path = payload_dir / DEVICE_PLANE_MANAGER_COMPOSE_REL
|
||||||
|
if sha256_file(compose_path) != DEVICE_PLANE_MANAGER_COMPOSE_SHA256:
|
||||||
|
die("failed Device Manager control-plane Compose mismatch")
|
||||||
|
return descriptor
|
||||||
|
|
||||||
|
|
||||||
def validate_device_plane_manager_reconciliation_payload(payload_dir):
|
def validate_device_plane_manager_reconciliation_payload(payload_dir):
|
||||||
descriptor = read_strict_json(
|
descriptor = read_strict_json(
|
||||||
payload_dir / DEVICE_PLANE_MANAGER_RECONCILIATION_REL,
|
payload_dir / DEVICE_PLANE_MANAGER_RECONCILIATION_REL,
|
||||||
@@ -10049,7 +10145,7 @@ def validate_device_plane_manager_reconciliation_backup():
|
|||||||
backup_dir / "missing-files.txt"
|
backup_dir / "missing-files.txt"
|
||||||
))
|
))
|
||||||
validate_backup_partition(
|
validate_backup_partition(
|
||||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES,
|
DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES,
|
||||||
existing,
|
existing,
|
||||||
missing,
|
missing,
|
||||||
"Device Manager reconciliation",
|
"Device Manager reconciliation",
|
||||||
@@ -10090,7 +10186,10 @@ def validate_device_plane_manager_reconciled_baseline(
|
|||||||
)
|
)
|
||||||
if live_source != backup_source:
|
if live_source != backup_source:
|
||||||
die("Device Manager rollback source does not match backup")
|
die("Device Manager rollback source does not match backup")
|
||||||
for rel in DEVICE_PLANE_MANAGER_RECONCILIATION_MISSING:
|
for rel in (
|
||||||
|
*DEVICE_PLANE_MANAGER_RECONCILIATION_MISSING,
|
||||||
|
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
||||||
|
):
|
||||||
path = root / rel
|
path = root / rel
|
||||||
if path.exists() or path.is_symlink():
|
if path.exists() or path.is_symlink():
|
||||||
die(
|
die(
|
||||||
@@ -10200,7 +10299,7 @@ def validate_device_plane_manager_reconciliation_evidence(payload_dir):
|
|||||||
or failed_manifest.get("component") != "device-plane"
|
or failed_manifest.get("component") != "device-plane"
|
||||||
or failed_manifest.get("type") != "app-overlay"
|
or failed_manifest.get("type") != "app-overlay"
|
||||||
or tuple(failed_entries)
|
or tuple(failed_entries)
|
||||||
!= DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES
|
!= DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES
|
||||||
):
|
):
|
||||||
die("Device Manager failed artifact contract mismatch")
|
die("Device Manager failed artifact contract mismatch")
|
||||||
|
|
||||||
@@ -10216,6 +10315,60 @@ def validate_device_plane_manager_reconciliation_evidence(payload_dir):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def validate_device_plane_manager_activation_predecessor(payload_dir):
|
||||||
|
descriptor = validate_device_plane_manager_control_plane_payload(
|
||||||
|
payload_dir
|
||||||
|
)
|
||||||
|
reconciliation_artifact = (
|
||||||
|
APPLIED_DIR / DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
artifact_stat = reconciliation_artifact.lstat()
|
||||||
|
except FileNotFoundError:
|
||||||
|
die("Device Manager reconciliation applied artifact is missing")
|
||||||
|
if (
|
||||||
|
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
|
||||||
|
):
|
||||||
|
die("Device Manager reconciliation applied artifact mismatch")
|
||||||
|
|
||||||
|
records = [
|
||||||
|
value
|
||||||
|
for value in load_state(STATE_FILE)
|
||||||
|
if value.get("id")
|
||||||
|
== DEVICE_PLANE_MANAGER_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
|
||||||
|
or record.get("backup_id")
|
||||||
|
!= DEVICE_PLANE_MANAGER_RECONCILIATION_APPLY_BACKUP_ID
|
||||||
|
or record.get("component") != "device-plane"
|
||||||
|
or record.get("sha256")
|
||||||
|
!= DEVICE_PLANE_MANAGER_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,
|
||||||
|
marker_installed=True,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"mode": "reconciled-manager-forward-activation",
|
||||||
|
"descriptor": descriptor,
|
||||||
|
"reconciliationArtifact": reconciliation_artifact,
|
||||||
|
"reconciliationRecord": record,
|
||||||
|
"runtime": runtime,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def device_plane_service_container_ids(service):
|
def device_plane_service_container_ids(service):
|
||||||
if service not in (
|
if service not in (
|
||||||
*DEVICE_PLANE_RUNTIME_SERVICES,
|
*DEVICE_PLANE_RUNTIME_SERVICES,
|
||||||
@@ -15845,6 +15998,7 @@ def plan_artifact(artifact):
|
|||||||
device_plane_network_publication_preflight = None
|
device_plane_network_publication_preflight = None
|
||||||
device_plane_b2_ingress_preflight = None
|
device_plane_b2_ingress_preflight = None
|
||||||
device_plane_b2_recovery_preflight = None
|
device_plane_b2_recovery_preflight = None
|
||||||
|
device_plane_manager_activation_preflight = None
|
||||||
device_plane_manager_reconciliation_preflight = None
|
device_plane_manager_reconciliation_preflight = None
|
||||||
device_plane_backhaul_preflight = None
|
device_plane_backhaul_preflight = None
|
||||||
device_plane_backhaul_vps_enrollment_preflight = None
|
device_plane_backhaul_vps_enrollment_preflight = None
|
||||||
@@ -15873,7 +16027,11 @@ def plan_artifact(artifact):
|
|||||||
manifest, entries, payload_dir = load_artifact(artifact, Path(tmp))
|
manifest, entries, payload_dir = load_artifact(artifact, Path(tmp))
|
||||||
reject_terminal_engine_l2_failed_artifact(manifest, sha)
|
reject_terminal_engine_l2_failed_artifact(manifest, sha)
|
||||||
reject_terminal_device_plane_foundation_artifact(manifest, sha)
|
reject_terminal_device_plane_foundation_artifact(manifest, sha)
|
||||||
reject_terminal_device_plane_manager_artifact(manifest, sha)
|
reject_terminal_device_plane_manager_artifact(
|
||||||
|
manifest,
|
||||||
|
sha,
|
||||||
|
entries,
|
||||||
|
)
|
||||||
reject_terminal_device_plane_backhaul_artifact(manifest, sha)
|
reject_terminal_device_plane_backhaul_artifact(manifest, sha)
|
||||||
transition_descriptor = None
|
transition_descriptor = None
|
||||||
transition_preflight = None
|
transition_preflight = None
|
||||||
@@ -16051,6 +16209,15 @@ def plan_artifact(artifact):
|
|||||||
payload_dir
|
payload_dir
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if is_device_plane_manager_control_plane_slice(
|
||||||
|
manifest["component"],
|
||||||
|
entries,
|
||||||
|
):
|
||||||
|
device_plane_manager_activation_preflight = (
|
||||||
|
validate_device_plane_manager_activation_predecessor(
|
||||||
|
payload_dir
|
||||||
|
)
|
||||||
|
)
|
||||||
if is_device_plane_manager_reconciliation_slice(
|
if is_device_plane_manager_reconciliation_slice(
|
||||||
manifest["component"],
|
manifest["component"],
|
||||||
entries,
|
entries,
|
||||||
@@ -17613,6 +17780,35 @@ def plan_artifact(artifact):
|
|||||||
)
|
)
|
||||||
print("device_gateway_tcp_9921=disabled:unpublished")
|
print("device_gateway_tcp_9921=disabled:unpublished")
|
||||||
print("device_plane_rollback=marker-only-runtime-unchanged")
|
print("device_plane_rollback=marker-only-runtime-unchanged")
|
||||||
|
if device_plane_manager_activation_preflight is not None:
|
||||||
|
print(
|
||||||
|
"device_plane_transition="
|
||||||
|
f"{device_plane_manager_activation_preflight['mode']}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"device_plane_predecessor_patch="
|
||||||
|
f"{DEVICE_PLANE_MANAGER_RECONCILIATION_PATCH_ID}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"device_plane_predecessor_artifact_sha256="
|
||||||
|
f"{DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT_SHA256}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"device_plane_runtime_mutation="
|
||||||
|
"build+recreate:device-control-core,device-manager"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"device_plane_runtime_services="
|
||||||
|
"preserved:device-gateway,device-postgres,"
|
||||||
|
"device-backhaul-target"
|
||||||
|
)
|
||||||
|
print("device_manager_health_gate=bounded-grace+contract")
|
||||||
|
print("device_manager_public_route=unchanged:absent")
|
||||||
|
print("device_gateway_tcp_9921=preserved:loopback-only")
|
||||||
|
print(
|
||||||
|
"device_plane_rollback="
|
||||||
|
"source+reconciled-baseline-runtime"
|
||||||
|
)
|
||||||
if device_plane_manager_reconciliation_preflight is not None:
|
if device_plane_manager_reconciliation_preflight is not None:
|
||||||
print(
|
print(
|
||||||
"device_plane_transition="
|
"device_plane_transition="
|
||||||
@@ -20849,6 +21045,7 @@ def apply_artifact(artifact):
|
|||||||
reject_terminal_device_plane_manager_artifact(
|
reject_terminal_device_plane_manager_artifact(
|
||||||
manifest,
|
manifest,
|
||||||
sha,
|
sha,
|
||||||
|
entries,
|
||||||
)
|
)
|
||||||
reject_terminal_device_plane_backhaul_artifact(
|
reject_terminal_device_plane_backhaul_artifact(
|
||||||
manifest,
|
manifest,
|
||||||
@@ -20901,6 +21098,13 @@ def apply_artifact(artifact):
|
|||||||
validate_device_plane_b2_discovery_rollback_recovery_evidence(
|
validate_device_plane_b2_discovery_rollback_recovery_evidence(
|
||||||
payload_dir
|
payload_dir
|
||||||
)
|
)
|
||||||
|
if is_device_plane_manager_control_plane_slice(
|
||||||
|
component,
|
||||||
|
entries,
|
||||||
|
):
|
||||||
|
validate_device_plane_manager_activation_predecessor(
|
||||||
|
payload_dir
|
||||||
|
)
|
||||||
if is_device_plane_manager_reconciliation_slice(
|
if is_device_plane_manager_reconciliation_slice(
|
||||||
component,
|
component,
|
||||||
entries,
|
entries,
|
||||||
|
|||||||
@@ -235,6 +235,68 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
runtime_acceptance.assert_called_once_with()
|
runtime_acceptance.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_v2_activation_requires_exact_applied_reconciliation(self):
|
||||||
|
with tempfile.TemporaryDirectory(
|
||||||
|
prefix="nodedc-device-manager-v2-predecessor-",
|
||||||
|
) as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
artifact = root / RUNNER.DEVICE_PLANE_MANAGER_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,
|
||||||
|
"backup_id": (
|
||||||
|
RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_APPLY_BACKUP_ID
|
||||||
|
),
|
||||||
|
"component": "device-plane",
|
||||||
|
"sha256": hashlib.sha256(artifact_bytes).hexdigest(),
|
||||||
|
"status": "ok",
|
||||||
|
}) + "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
backup = root / "failed-backup"
|
||||||
|
with (
|
||||||
|
mock.patch.object(RUNNER, "APPLIED_DIR", root),
|
||||||
|
mock.patch.object(RUNNER, "STATE_FILE", state_file),
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT_SHA256",
|
||||||
|
hashlib.sha256(artifact_bytes).hexdigest(),
|
||||||
|
),
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"validate_device_plane_manager_control_plane_payload",
|
||||||
|
return_value=(
|
||||||
|
RUNNER.expected_device_plane_manager_control_plane_descriptor()
|
||||||
|
),
|
||||||
|
) as payload,
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"validate_device_plane_manager_reconciliation_backup",
|
||||||
|
return_value=backup,
|
||||||
|
),
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"validate_device_plane_manager_reconciled_baseline",
|
||||||
|
return_value={"accepted": True},
|
||||||
|
) as baseline,
|
||||||
|
):
|
||||||
|
result = (
|
||||||
|
RUNNER.validate_device_plane_manager_activation_predecessor(
|
||||||
|
root / "payload"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
result["mode"],
|
||||||
|
"reconciled-manager-forward-activation",
|
||||||
|
)
|
||||||
|
payload.assert_called_once_with(root / "payload")
|
||||||
|
baseline.assert_called_once_with(backup, marker_installed=True)
|
||||||
|
|
||||||
def test_initial_install_rollback_removes_manager_and_restores_core_only(self):
|
def test_initial_install_rollback_removes_manager_and_restores_core_only(self):
|
||||||
entries = RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES
|
entries = RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES
|
||||||
missing = {
|
missing = {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class DeviceManagerControlPlaneReconciliationArtifactTest(unittest.TestCase):
|
|||||||
def test_failed_control_plane_is_terminal(self):
|
def test_failed_control_plane_is_terminal(self):
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
RUNNER.DeployError,
|
RUNNER.DeployError,
|
||||||
"exact registered reconciliation successor",
|
"exact v2 activation successor",
|
||||||
):
|
):
|
||||||
RUNNER.reject_terminal_device_plane_manager_artifact(
|
RUNNER.reject_terminal_device_plane_manager_artifact(
|
||||||
{"id": RUNNER.DEVICE_PLANE_MANAGER_FAILED_PATCH_ID},
|
{"id": RUNNER.DEVICE_PLANE_MANAGER_FAILED_PATCH_ID},
|
||||||
@@ -129,12 +129,21 @@ class DeviceManagerControlPlaneReconciliationArtifactTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
RUNNER.DeployError,
|
RUNNER.DeployError,
|
||||||
"exact registered reconciliation successor",
|
"exact v2 activation successor",
|
||||||
):
|
):
|
||||||
RUNNER.reject_terminal_device_plane_manager_artifact(
|
RUNNER.reject_terminal_device_plane_manager_artifact(
|
||||||
{"id": "different"},
|
{"id": "different"},
|
||||||
RUNNER.DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256,
|
RUNNER.DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256,
|
||||||
)
|
)
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
RUNNER.DeployError,
|
||||||
|
"exact v2 activation successor",
|
||||||
|
):
|
||||||
|
RUNNER.reject_terminal_device_plane_manager_artifact(
|
||||||
|
{"id": "different", "component": "device-plane"},
|
||||||
|
"1" * 64,
|
||||||
|
RUNNER.DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES,
|
||||||
|
)
|
||||||
|
|
||||||
def test_health_grace_waits_through_unhealthy_and_exited(self):
|
def test_health_grace_waits_through_unhealthy_and_exited(self):
|
||||||
results = [
|
results = [
|
||||||
@@ -190,8 +199,13 @@ class DeviceManagerControlPlaneReconciliationArtifactTest(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
existing = list(RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_EXISTING)
|
missing_set = {
|
||||||
missing = list(RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_MISSING)
|
RUNNER.DEVICE_PLANE_MANAGER_COMPOSE_REL,
|
||||||
|
"services/device-manager",
|
||||||
|
RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
||||||
|
}
|
||||||
|
existing = [entry for entry in entries if entry not in missing_set]
|
||||||
|
missing = [entry for entry in entries if entry in missing_set]
|
||||||
with (
|
with (
|
||||||
mock.patch.object(
|
mock.patch.object(
|
||||||
RUNNER,
|
RUNNER,
|
||||||
|
|||||||
Reference in New Issue
Block a user