fix(deploy): allow repeatable typed core releases
This commit is contained in:
@@ -691,6 +691,167 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_control_core_release_v2_builder_supports_v2_release_predecessor(self):
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-control-core-v2-successor-",
|
||||
) as directory:
|
||||
artifact_dir = Path(directory) / "successor"
|
||||
environment = os.environ.copy()
|
||||
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
|
||||
predecessor_id = "device-control-core-release-v2-unit-001"
|
||||
predecessor_sha = "b" * 64
|
||||
completed = subprocess.run(
|
||||
[
|
||||
"node",
|
||||
str(
|
||||
SCRIPT_DIR
|
||||
/ "build-device-control-core-release-artifact.mjs"
|
||||
),
|
||||
"device-control-core-release-v2-unit-002",
|
||||
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,
|
||||
)
|
||||
self.assertEqual(
|
||||
tuple(entries),
|
||||
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES,
|
||||
)
|
||||
descriptor = json.loads(
|
||||
(
|
||||
payload / RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL
|
||||
).read_text(encoding="utf-8")
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["predecessor"],
|
||||
{
|
||||
"kind": "release",
|
||||
"patchId": predecessor_id,
|
||||
"artifactSha256": predecessor_sha,
|
||||
},
|
||||
)
|
||||
|
||||
def test_control_core_release_v2_accepts_current_v2_release_predecessor(self):
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-control-core-v2-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_id = "device-control-core-release-v2-unit-001"
|
||||
predecessor_sha = "b" * 64
|
||||
artifact_name = f"nodedc-device-plane-{predecessor_id}.tgz"
|
||||
(applied / artifact_name).write_bytes(b"reviewed-v2-predecessor")
|
||||
predecessor = {
|
||||
"kind": "release",
|
||||
"patchId": predecessor_id,
|
||||
"artifactSha256": predecessor_sha,
|
||||
}
|
||||
predecessor_descriptor = (
|
||||
RUNNER.expected_device_plane_control_core_release_descriptor(
|
||||
predecessor_id,
|
||||
{
|
||||
"kind": "release",
|
||||
"patchId": "device-control-core-release-20260812-024",
|
||||
"artifactSha256": "a" * 64,
|
||||
},
|
||||
schema_version="v2",
|
||||
)
|
||||
)
|
||||
candidate_descriptor = (
|
||||
RUNNER.expected_device_plane_control_core_release_descriptor(
|
||||
"device-control-core-release-v2-unit-002",
|
||||
predecessor,
|
||||
schema_version="v2",
|
||||
)
|
||||
)
|
||||
installed = (
|
||||
device_plane / RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL
|
||||
)
|
||||
installed.write_text(
|
||||
json.dumps(predecessor_descriptor),
|
||||
encoding="utf-8",
|
||||
)
|
||||
with (
|
||||
mock.patch.object(RUNNER, "APPLIED_DIR", applied),
|
||||
mock.patch.object(RUNNER, "TMP_DIR", temporary),
|
||||
mock.patch.object(RUNNER, "DEVICE_PLANE_ROOT", device_plane),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"validate_device_plane_control_core_release_payload",
|
||||
side_effect=(candidate_descriptor, predecessor_descriptor),
|
||||
),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"load_artifact",
|
||||
return_value=(
|
||||
{
|
||||
"id": predecessor_id,
|
||||
"component": "device-plane",
|
||||
"type": "app-overlay",
|
||||
},
|
||||
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES,
|
||||
root / "predecessor-payload",
|
||||
),
|
||||
),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"load_state",
|
||||
return_value=[{
|
||||
"id": predecessor_id,
|
||||
"artifact": artifact_name,
|
||||
"component": "device-plane",
|
||||
"sha256": predecessor_sha,
|
||||
"status": "ok",
|
||||
}],
|
||||
),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"sha256_file",
|
||||
return_value=predecessor_sha,
|
||||
),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"collect_exact_files",
|
||||
return_value={"same": "source"},
|
||||
),
|
||||
mock.patch.object(RUNNER, "component_compose_files"),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"inspect_device_edge_channel_core_identity_state",
|
||||
return_value="valid-reuse-at-apply",
|
||||
),
|
||||
mock.patch.object(RUNNER, "healthcheck_compose_service"),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"validate_device_manager_control_plane_runtime",
|
||||
),
|
||||
):
|
||||
result = (
|
||||
RUNNER.validate_device_plane_control_core_release_predecessor(
|
||||
root / "candidate-payload"
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
result["mode"],
|
||||
"active-device-control-core-forward-release",
|
||||
)
|
||||
|
||||
def test_control_core_release_rejects_direct_legacy_predecessor(self):
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-control-core-release-invalid-",
|
||||
|
||||
Reference in New Issue
Block a user