feat(deploy): register Device Control Core releases
This commit is contained in:
@@ -455,6 +455,166 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
root / "payload"
|
||||
)
|
||||
|
||||
def test_control_core_release_is_repeatable_core_only_and_compose_free(self):
|
||||
patch_id = "device-control-core-release-unit-001"
|
||||
manifest, entries, names, result = self.assert_deterministic_artifact(
|
||||
"build-device-control-core-release-artifact.mjs",
|
||||
patch_id,
|
||||
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_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-control-core-release-v1.json",
|
||||
names,
|
||||
)
|
||||
self.assertFalse(any("docker-compose" in name for name in names))
|
||||
descriptor = RUNNER.expected_device_plane_control_core_release_descriptor(
|
||||
patch_id,
|
||||
{
|
||||
"kind": "edge-core-channel-upgrade-v4",
|
||||
"patchId": (
|
||||
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_FIRST_PREDECESSOR_PATCH_ID
|
||||
),
|
||||
"artifactSha256": (
|
||||
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_FIRST_PREDECESSOR_ARTIFACT_SHA256
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["coreNetworks"],
|
||||
["device-plane-private", "device-plane-egress"],
|
||||
)
|
||||
self.assertEqual(descriptor["edgeRegistrations"], "preserved")
|
||||
|
||||
def test_control_core_release_apply_gate_checks_preserved_runtime(self):
|
||||
entries = RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_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,
|
||||
core_network_mode="private-egress",
|
||||
)
|
||||
|
||||
def test_control_core_release_builder_supports_release_predecessor(self):
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-control-core-successor-",
|
||||
) as directory:
|
||||
result = self.build(
|
||||
"build-device-control-core-release-artifact.mjs",
|
||||
"device-control-core-release-unit-004",
|
||||
Path(directory),
|
||||
)
|
||||
first_artifact = Path(result["artifact"])
|
||||
# Rebuild through the CLI's repeatable-release predecessor form.
|
||||
environment = os.environ.copy()
|
||||
successor_dir = Path(directory) / "successor"
|
||||
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(successor_dir)
|
||||
predecessor_id = "device-control-core-release-unit-004"
|
||||
predecessor_sha = hashlib.sha256(
|
||||
first_artifact.read_bytes()
|
||||
).hexdigest()
|
||||
completed = subprocess.run(
|
||||
[
|
||||
"node",
|
||||
str(
|
||||
SCRIPT_DIR
|
||||
/ "build-device-control-core-release-artifact.mjs"
|
||||
),
|
||||
"device-control-core-release-unit-005",
|
||||
predecessor_id,
|
||||
predecessor_sha,
|
||||
],
|
||||
cwd=PLATFORM_ROOT,
|
||||
env=environment,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
successor = json.loads(completed.stdout)
|
||||
extracted = Path(directory) / "extracted-successor"
|
||||
extracted.mkdir()
|
||||
_manifest, _entries, payload = RUNNER.load_artifact(
|
||||
Path(successor["artifact"]),
|
||||
extracted,
|
||||
)
|
||||
descriptor = json.loads(
|
||||
(
|
||||
payload / RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_REL
|
||||
).read_text(encoding="utf-8")
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["predecessor"],
|
||||
{
|
||||
"kind": "release",
|
||||
"patchId": predecessor_id,
|
||||
"artifactSha256": predecessor_sha,
|
||||
},
|
||||
)
|
||||
|
||||
def test_control_core_release_rejects_direct_legacy_predecessor(self):
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-control-core-release-invalid-",
|
||||
) as directory:
|
||||
payload = Path(directory)
|
||||
descriptor_path = (
|
||||
payload / RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_REL
|
||||
)
|
||||
descriptor_path.parent.mkdir(parents=True)
|
||||
predecessor = {
|
||||
"kind": "release",
|
||||
"patchId": "device-edge-core-channel-upgrade-v4-20260812-023",
|
||||
"artifactSha256": "a" * 64,
|
||||
}
|
||||
descriptor = RUNNER.expected_device_plane_control_core_release_descriptor(
|
||||
"device-control-core-release-unit-002",
|
||||
predecessor,
|
||||
)
|
||||
descriptor_path.write_text(
|
||||
json.dumps(descriptor),
|
||||
encoding="utf-8",
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
RUNNER.DeployError,
|
||||
"release descriptor mismatch",
|
||||
):
|
||||
RUNNER.validate_device_plane_control_core_release_payload(
|
||||
payload
|
||||
)
|
||||
|
||||
def test_edge_core_channel_upgrade_v2_rejects_installed_marker(self):
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-device-edge-upgrade-v2-installed-",
|
||||
@@ -1651,6 +1811,80 @@ 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_control_core_release_rollback_restores_only_core_on_v4_topology(self):
|
||||
entries = RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES
|
||||
missing = {RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_REL}
|
||||
existing = [entry for entry in entries if entry not in missing]
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-control-core-release-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(
|
||||
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_REL + "\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,
|
||||
core_network_mode="private-egress",
|
||||
)
|
||||
self.assertEqual(result, f"source+runtime-restored:{len(entries)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
Reference in New Issue
Block a user