deploy: register Manager v4 persistent data
This commit is contained in:
@@ -66,6 +66,14 @@ PROXY_CONTUR_ENV_FILE = Path("/volume1/docker/proxy-contur/.env")
|
|||||||
DC_AMD_PROXY_RUNTIME_DIR = Path("/volume1/docker/dc-amd-proxy/runtime")
|
DC_AMD_PROXY_RUNTIME_DIR = Path("/volume1/docker/dc-amd-proxy/runtime")
|
||||||
DEVICE_PLANE_ROOT = Path("/volume1/docker/nodedc-device-plane")
|
DEVICE_PLANE_ROOT = Path("/volume1/docker/nodedc-device-plane")
|
||||||
DEVICE_PLANE_SECRET_DIR = DEVICE_PLANE_ROOT / "secrets"
|
DEVICE_PLANE_SECRET_DIR = DEVICE_PLANE_ROOT / "secrets"
|
||||||
|
DEVICE_PLANE_MANAGER_DATA_DIR = DEVICE_PLANE_ROOT / "data" / "device-manager"
|
||||||
|
DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR = "/var/lib/nodedc-device-manager"
|
||||||
|
DEVICE_PLANE_MANAGER_PRESENTATION_PATH = (
|
||||||
|
f"{DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR}/device-manager-presentation.json"
|
||||||
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_MEDIA_ROOT = (
|
||||||
|
f"{DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR}/media"
|
||||||
|
)
|
||||||
DEVICE_PLANE_POSTGRES_PASSWORD_FILE = DEVICE_PLANE_SECRET_DIR / "postgres-password"
|
DEVICE_PLANE_POSTGRES_PASSWORD_FILE = DEVICE_PLANE_SECRET_DIR / "postgres-password"
|
||||||
DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE = DEVICE_PLANE_SECRET_DIR / "gateway-core-token"
|
DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE = DEVICE_PLANE_SECRET_DIR / "gateway-core-token"
|
||||||
DEVICE_PLANE_IDENTIFIER_PEPPER_FILE = DEVICE_PLANE_SECRET_DIR / "identifier-pepper"
|
DEVICE_PLANE_IDENTIFIER_PEPPER_FILE = DEVICE_PLANE_SECRET_DIR / "identifier-pepper"
|
||||||
@@ -249,6 +257,9 @@ DEVICE_PLANE_MANAGER_RELEASE_V2_REL = (
|
|||||||
DEVICE_PLANE_MANAGER_RELEASE_V3_REL = (
|
DEVICE_PLANE_MANAGER_RELEASE_V3_REL = (
|
||||||
"deployment/device-manager-release-v3.json"
|
"deployment/device-manager-release-v3.json"
|
||||||
)
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_REL = (
|
||||||
|
"deployment/device-manager-release-v4.json"
|
||||||
|
)
|
||||||
DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml"
|
DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml"
|
||||||
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL = (
|
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL = (
|
||||||
"deployment/device-edge-core-channel-bootstrap-v1.json"
|
"deployment/device-edge-core-channel-bootstrap-v1.json"
|
||||||
@@ -356,6 +367,12 @@ DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_PATCH_ID = (
|
|||||||
DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_ARTIFACT_SHA256 = (
|
DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_ARTIFACT_SHA256 = (
|
||||||
"c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|
"c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|
||||||
)
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_PATCH_ID = (
|
||||||
|
"device-manager-release-v3-20260822-032"
|
||||||
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_ARTIFACT_SHA256 = (
|
||||||
|
"6e0eb3a0a6f19ceab92d46832b93bffbcea21247dbdc2ea50625a51ff460e4ca"
|
||||||
|
)
|
||||||
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID = (
|
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID = (
|
||||||
"device-edge-core-channel-bootstrap-20260812-018"
|
"device-edge-core-channel-bootstrap-20260812-018"
|
||||||
)
|
)
|
||||||
@@ -414,6 +431,9 @@ DEVICE_PLANE_EDGE_CORE_CHANNEL_INVALID_CERTIFICATE_FINGERPRINT = (
|
|||||||
DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256 = (
|
DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256 = (
|
||||||
"4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e"
|
"4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e"
|
||||||
)
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256 = (
|
||||||
|
"e7dff0f5873ad4586bd55946d3db2bb86092a5e149e886d120adc041e056c256"
|
||||||
|
)
|
||||||
DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256 = (
|
DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256 = (
|
||||||
"369a2acf9c1a1030b9e1c6c366144b1eaf8900aef0ee59bb6bf23250b7b371b9"
|
"369a2acf9c1a1030b9e1c6c366144b1eaf8900aef0ee59bb6bf23250b7b371b9"
|
||||||
)
|
)
|
||||||
@@ -492,6 +512,11 @@ DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES = (
|
|||||||
"services/device-manager",
|
"services/device-manager",
|
||||||
DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
|
DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
|
||||||
)
|
)
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES = (
|
||||||
|
DEVICE_PLANE_MANAGER_COMPOSE_REL,
|
||||||
|
"services/device-manager",
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_REL,
|
||||||
|
)
|
||||||
DEVICE_PLANE_MANAGER_RECONCILIATION_REL = (
|
DEVICE_PLANE_MANAGER_RECONCILIATION_REL = (
|
||||||
"deployment/device-manager-control-plane-reconciliation-v1.json"
|
"deployment/device-manager-control-plane-reconciliation-v1.json"
|
||||||
)
|
)
|
||||||
@@ -3922,6 +3947,7 @@ def allowed_payload_path(component, rel):
|
|||||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
||||||
DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
|
DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
|
||||||
DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
|
DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_REL,
|
||||||
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL,
|
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL,
|
||||||
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL,
|
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL,
|
||||||
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL,
|
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL,
|
||||||
@@ -9494,6 +9520,7 @@ def is_device_plane_manager_control_plane_slice(component, entries):
|
|||||||
DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES,
|
DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES,
|
||||||
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
|
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
|
||||||
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES,
|
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES,
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -9514,6 +9541,21 @@ def is_device_plane_manager_release_v3_slice(component, entries):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_device_plane_manager_release_v4_slice(component, entries):
|
||||||
|
return (
|
||||||
|
component == "device-plane"
|
||||||
|
and entries is not None
|
||||||
|
and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_device_plane_manager_only_release_slice(component, entries):
|
||||||
|
return (
|
||||||
|
is_device_plane_manager_release_v3_slice(component, entries)
|
||||||
|
or is_device_plane_manager_release_v4_slice(component, entries)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_device_plane_edge_core_channel_bootstrap_slice(component, entries):
|
def is_device_plane_edge_core_channel_bootstrap_slice(component, entries):
|
||||||
return (
|
return (
|
||||||
component == "device-plane"
|
component == "device-plane"
|
||||||
@@ -9702,6 +9744,31 @@ def expected_device_plane_manager_release_v3_boundaries():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def expected_device_plane_manager_release_v4_boundaries():
|
||||||
|
return {
|
||||||
|
**expected_device_plane_manager_release_v3_boundaries(),
|
||||||
|
"predecessor": {
|
||||||
|
"kind": "release",
|
||||||
|
"patchId": DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_PATCH_ID,
|
||||||
|
"artifactSha256": (
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_ARTIFACT_SHA256
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"healthGate": "bounded-container-grace+core-contract+persistent-data",
|
||||||
|
"presentationPersistence": "runner-managed-host-data-bind",
|
||||||
|
"presentationDataHostPath": str(DEVICE_PLANE_MANAGER_DATA_DIR),
|
||||||
|
"presentationDataContainerPath": DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR,
|
||||||
|
"presentationDataOwnership": "uid-1000-gid-1000-mode-0750",
|
||||||
|
"presentationDataLifecycle": (
|
||||||
|
"preserve-across-manager-recreate-and-source-rollback"
|
||||||
|
),
|
||||||
|
"presentationPath": DEVICE_PLANE_MANAGER_PRESENTATION_PATH,
|
||||||
|
"mediaRoot": DEVICE_PLANE_MANAGER_MEDIA_ROOT,
|
||||||
|
"defaultAccentHex": "#f5f5f5",
|
||||||
|
"rollback": "restore-preapply-snapshot-preserve-manager-data",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def expected_device_plane_manager_release_boundaries():
|
def expected_device_plane_manager_release_boundaries():
|
||||||
# Compatibility name for the current release builder/tests. Immutable v1
|
# Compatibility name for the current release builder/tests. Immutable v1
|
||||||
# predecessors always use expected_device_plane_manager_release_v1_boundaries.
|
# predecessors always use expected_device_plane_manager_release_v1_boundaries.
|
||||||
@@ -10216,6 +10283,17 @@ def validate_device_plane_manager_release_payload_contract(
|
|||||||
"device-edge-channel/peers",
|
"device-edge-channel/peers",
|
||||||
"name: nodedc-device-plane-egress",
|
"name: nodedc-device-plane-egress",
|
||||||
))
|
))
|
||||||
|
if schema_version == "nodedc.device-plane.device-manager-release.v4":
|
||||||
|
required_compose.extend((
|
||||||
|
"NODEDC_DEVICE_MANAGER_PRESENTATION_PATH: "
|
||||||
|
f"{DEVICE_PLANE_MANAGER_PRESENTATION_PATH}",
|
||||||
|
"NODEDC_DEVICE_MANAGER_MEDIA_ROOT: "
|
||||||
|
f"{DEVICE_PLANE_MANAGER_MEDIA_ROOT}",
|
||||||
|
f"source: {DEVICE_PLANE_MANAGER_DATA_DIR}",
|
||||||
|
f"target: {DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR}",
|
||||||
|
"read_only: false",
|
||||||
|
"create_host_path: false",
|
||||||
|
))
|
||||||
for required in required_compose:
|
for required in required_compose:
|
||||||
if required not in compose:
|
if required not in compose:
|
||||||
die(f"Device Manager control-plane boundary missing: {required}")
|
die(f"Device Manager control-plane boundary missing: {required}")
|
||||||
@@ -10277,6 +10355,22 @@ def validate_device_plane_manager_release_v3_payload(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_device_plane_manager_release_v4_payload(
|
||||||
|
payload_dir,
|
||||||
|
*,
|
||||||
|
expected_release_id=None,
|
||||||
|
):
|
||||||
|
return validate_device_plane_manager_release_payload_contract(
|
||||||
|
payload_dir,
|
||||||
|
descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V4_REL,
|
||||||
|
schema_version="nodedc.device-plane.device-manager-release.v4",
|
||||||
|
boundaries=expected_device_plane_manager_release_v4_boundaries(),
|
||||||
|
compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256,
|
||||||
|
edge_channel=False,
|
||||||
|
expected_release_id=expected_release_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_device_plane_manager_release_payload(
|
def validate_device_plane_manager_release_payload(
|
||||||
payload_dir,
|
payload_dir,
|
||||||
*,
|
*,
|
||||||
@@ -10285,12 +10379,18 @@ def validate_device_plane_manager_release_payload(
|
|||||||
v1 = payload_dir / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL
|
v1 = payload_dir / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL
|
||||||
v2 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V2_REL
|
v2 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V2_REL
|
||||||
v3 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V3_REL
|
v3 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V3_REL
|
||||||
|
v4 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V4_REL
|
||||||
present = [
|
present = [
|
||||||
path for path in (v1, v2, v3)
|
path for path in (v1, v2, v3, v4)
|
||||||
if path.exists() or path.is_symlink()
|
if path.exists() or path.is_symlink()
|
||||||
]
|
]
|
||||||
if len(present) != 1:
|
if len(present) != 1:
|
||||||
die("Device Manager release descriptor cardinality mismatch")
|
die("Device Manager release descriptor cardinality mismatch")
|
||||||
|
if present[0] == v4:
|
||||||
|
return validate_device_plane_manager_release_v4_payload(
|
||||||
|
payload_dir,
|
||||||
|
expected_release_id=expected_release_id,
|
||||||
|
)
|
||||||
if present[0] == v3:
|
if present[0] == v3:
|
||||||
return validate_device_plane_manager_release_v3_payload(
|
return validate_device_plane_manager_release_v3_payload(
|
||||||
payload_dir,
|
payload_dir,
|
||||||
@@ -10568,6 +10668,7 @@ def installed_device_plane_manager_compose_sha256():
|
|||||||
v1 = root / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL
|
v1 = root / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL
|
||||||
v2 = root / DEVICE_PLANE_MANAGER_RELEASE_V2_REL
|
v2 = root / DEVICE_PLANE_MANAGER_RELEASE_V2_REL
|
||||||
v3 = root / DEVICE_PLANE_MANAGER_RELEASE_V3_REL
|
v3 = root / DEVICE_PLANE_MANAGER_RELEASE_V3_REL
|
||||||
|
v4 = root / DEVICE_PLANE_MANAGER_RELEASE_V4_REL
|
||||||
|
|
||||||
# A successful v2 overlay intentionally leaves the immutable v1 release
|
# A successful v2 overlay intentionally leaves the immutable v1 release
|
||||||
# descriptor as predecessor evidence. Prefer the highest installed
|
# descriptor as predecessor evidence. Prefer the highest installed
|
||||||
@@ -10575,6 +10676,19 @@ def installed_device_plane_manager_compose_sha256():
|
|||||||
# the generation-specific Compose digest. During a failed v2 apply,
|
# the generation-specific Compose digest. During a failed v2 apply,
|
||||||
# rollback removes the candidate-only v2 descriptor before rebuilding the
|
# rollback removes the candidate-only v2 descriptor before rebuilding the
|
||||||
# restored v1 runtime, so the same lookup follows the restored source.
|
# restored v1 runtime, so the same lookup follows the restored source.
|
||||||
|
if v4.exists() or v4.is_symlink():
|
||||||
|
descriptor = read_strict_json(
|
||||||
|
v4,
|
||||||
|
"installed Device Manager release v4 descriptor",
|
||||||
|
max_bytes=16 * 1024,
|
||||||
|
)
|
||||||
|
validate_device_plane_manager_release_descriptor(
|
||||||
|
descriptor,
|
||||||
|
schema_version="nodedc.device-plane.device-manager-release.v4",
|
||||||
|
boundaries=expected_device_plane_manager_release_v4_boundaries(),
|
||||||
|
)
|
||||||
|
return DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256
|
||||||
|
|
||||||
if v3.exists() or v3.is_symlink():
|
if v3.exists() or v3.is_symlink():
|
||||||
descriptor = read_strict_json(
|
descriptor = read_strict_json(
|
||||||
v3,
|
v3,
|
||||||
@@ -12089,8 +12203,9 @@ def validate_device_plane_manager_reconciliation_evidence(payload_dir):
|
|||||||
|
|
||||||
|
|
||||||
def validate_device_plane_manager_v3_active_baseline(descriptor):
|
def validate_device_plane_manager_v3_active_baseline(descriptor):
|
||||||
if descriptor.get("schemaVersion") != (
|
if descriptor.get("schemaVersion") not in (
|
||||||
"nodedc.device-plane.device-manager-release.v3"
|
"nodedc.device-plane.device-manager-release.v3",
|
||||||
|
"nodedc.device-plane.device-manager-release.v4",
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -12235,8 +12350,9 @@ def validate_device_plane_manager_v3_active_baseline(descriptor):
|
|||||||
|
|
||||||
|
|
||||||
def device_plane_manager_preserved_runtime_health_services(descriptor):
|
def device_plane_manager_preserved_runtime_health_services(descriptor):
|
||||||
if descriptor.get("schemaVersion") == (
|
if descriptor.get("schemaVersion") in (
|
||||||
"nodedc.device-plane.device-manager-release.v3"
|
"nodedc.device-plane.device-manager-release.v3",
|
||||||
|
"nodedc.device-plane.device-manager-release.v4",
|
||||||
):
|
):
|
||||||
return (
|
return (
|
||||||
"device-control-core",
|
"device-control-core",
|
||||||
@@ -12368,6 +12484,7 @@ def validate_device_plane_manager_activation_predecessor(
|
|||||||
DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES,
|
DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES,
|
||||||
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
|
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
|
||||||
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES,
|
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES,
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES,
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
die("Device Manager release predecessor type mismatch")
|
die("Device Manager release predecessor type mismatch")
|
||||||
@@ -12382,6 +12499,8 @@ def validate_device_plane_manager_activation_predecessor(
|
|||||||
DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
|
DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
|
||||||
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES:
|
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES:
|
||||||
DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
|
DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES:
|
||||||
|
DEVICE_PLANE_MANAGER_RELEASE_V4_REL,
|
||||||
}.get(
|
}.get(
|
||||||
tuple(predecessor_entries),
|
tuple(predecessor_entries),
|
||||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
||||||
@@ -14744,6 +14863,7 @@ def validate_device_manager_control_plane_runtime(
|
|||||||
*,
|
*,
|
||||||
require_edge_channel=None,
|
require_edge_channel=None,
|
||||||
core_network_mode=None,
|
core_network_mode=None,
|
||||||
|
require_persistent_data=None,
|
||||||
):
|
):
|
||||||
core_ids = device_plane_service_container_ids("device-control-core")
|
core_ids = device_plane_service_container_ids("device-control-core")
|
||||||
manager_ids = device_plane_service_container_ids("device-manager")
|
manager_ids = device_plane_service_container_ids("device-manager")
|
||||||
@@ -14758,6 +14878,13 @@ def validate_device_manager_control_plane_runtime(
|
|||||||
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL
|
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL
|
||||||
)
|
)
|
||||||
require_edge_channel = edge_descriptor.is_file() and not edge_descriptor.is_symlink()
|
require_edge_channel = edge_descriptor.is_file() and not edge_descriptor.is_symlink()
|
||||||
|
if require_persistent_data is None:
|
||||||
|
data_descriptor = (
|
||||||
|
DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V4_REL
|
||||||
|
)
|
||||||
|
require_persistent_data = (
|
||||||
|
data_descriptor.is_file() and not data_descriptor.is_symlink()
|
||||||
|
)
|
||||||
expected_core_environment = {
|
expected_core_environment = {
|
||||||
"DEVICE_MANAGEMENT_API_ENABLED": "true",
|
"DEVICE_MANAGEMENT_API_ENABLED": "true",
|
||||||
"DEVICE_MANAGEMENT_CORE_TOKEN_FILE": (
|
"DEVICE_MANAGEMENT_CORE_TOKEN_FILE": (
|
||||||
@@ -14816,6 +14943,23 @@ def validate_device_manager_control_plane_runtime(
|
|||||||
"/run/nodedc-secrets/management-core-token"
|
"/run/nodedc-secrets/management-core-token"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
if require_persistent_data:
|
||||||
|
expected_manager_environment.update({
|
||||||
|
"NODEDC_DEVICE_MANAGER_PRESENTATION_PATH": (
|
||||||
|
DEVICE_PLANE_MANAGER_PRESENTATION_PATH
|
||||||
|
),
|
||||||
|
"NODEDC_DEVICE_MANAGER_MEDIA_ROOT": (
|
||||||
|
DEVICE_PLANE_MANAGER_MEDIA_ROOT
|
||||||
|
),
|
||||||
|
})
|
||||||
|
elif any(
|
||||||
|
key in manager_environment
|
||||||
|
for key in (
|
||||||
|
"NODEDC_DEVICE_MANAGER_PRESENTATION_PATH",
|
||||||
|
"NODEDC_DEVICE_MANAGER_MEDIA_ROOT",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
die("Device Manager runtime unexpectedly owns persistent data")
|
||||||
if any(
|
if any(
|
||||||
manager_environment.get(key) != value
|
manager_environment.get(key) != value
|
||||||
for key, value in expected_manager_environment.items()
|
for key, value in expected_manager_environment.items()
|
||||||
@@ -14900,16 +15044,23 @@ def validate_device_manager_control_plane_runtime(
|
|||||||
DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE
|
DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
if require_persistent_data:
|
||||||
|
expected_manager_mounts[DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR] = (
|
||||||
|
DEVICE_PLANE_MANAGER_DATA_DIR
|
||||||
|
)
|
||||||
if set(manager_mounts) != set(expected_manager_mounts):
|
if set(manager_mounts) != set(expected_manager_mounts):
|
||||||
die("Device Manager mount set mismatch")
|
die("Device Manager mount set mismatch")
|
||||||
for destination, source in expected_manager_mounts.items():
|
for destination, source in expected_manager_mounts.items():
|
||||||
mount = manager_mounts[destination]
|
mount = manager_mounts[destination]
|
||||||
|
persistent_mount = destination == DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR
|
||||||
if (
|
if (
|
||||||
mount.get("Type") != "bind"
|
mount.get("Type") != "bind"
|
||||||
or mount.get("Source") != str(source)
|
or mount.get("Source") != str(source)
|
||||||
or mount.get("RW") is not False
|
or mount.get("RW") is not persistent_mount
|
||||||
):
|
):
|
||||||
die("Device Manager secret mount mismatch")
|
die("Device Manager mount boundary mismatch")
|
||||||
|
if require_persistent_data:
|
||||||
|
validate_device_plane_manager_persistent_data_metadata()
|
||||||
ports = (manager.get("NetworkSettings") or {}).get("Ports") or {}
|
ports = (manager.get("NetworkSettings") or {}).get("Ports") or {}
|
||||||
if any(bindings for bindings in ports.values()):
|
if any(bindings for bindings in ports.values()):
|
||||||
die("Device Manager host port publication is forbidden")
|
die("Device Manager host port publication is forbidden")
|
||||||
@@ -16564,7 +16715,7 @@ def component_services(component, entries=None):
|
|||||||
if is_device_plane_control_core_release_slice(component, entries):
|
if is_device_plane_control_core_release_slice(component, entries):
|
||||||
return ("device-control-core",)
|
return ("device-control-core",)
|
||||||
|
|
||||||
if is_device_plane_manager_release_v3_slice(component, entries):
|
if is_device_plane_manager_only_release_slice(component, entries):
|
||||||
return ("device-manager",)
|
return ("device-manager",)
|
||||||
|
|
||||||
if is_device_plane_edge_core_channel_bootstrap_slice(component, entries):
|
if is_device_plane_edge_core_channel_bootstrap_slice(component, entries):
|
||||||
@@ -17164,7 +17315,7 @@ def component_builds(component, entries=None):
|
|||||||
),
|
),
|
||||||
),)
|
),)
|
||||||
|
|
||||||
if is_device_plane_manager_release_v3_slice(component, entries):
|
if is_device_plane_manager_only_release_slice(component, entries):
|
||||||
return ((
|
return ((
|
||||||
DEVICE_PLANE_ROOT / "services/device-manager",
|
DEVICE_PLANE_ROOT / "services/device-manager",
|
||||||
(
|
(
|
||||||
@@ -21096,7 +21247,10 @@ def plan_artifact(artifact):
|
|||||||
"build+recreate:device-manager"
|
"build+recreate:device-manager"
|
||||||
if device_plane_manager_activation_preflight["descriptor"].get(
|
if device_plane_manager_activation_preflight["descriptor"].get(
|
||||||
"schemaVersion"
|
"schemaVersion"
|
||||||
) == "nodedc.device-plane.device-manager-release.v3"
|
) in (
|
||||||
|
"nodedc.device-plane.device-manager-release.v3",
|
||||||
|
"nodedc.device-plane.device-manager-release.v4",
|
||||||
|
)
|
||||||
else "build+recreate:device-control-core,device-manager"
|
else "build+recreate:device-control-core,device-manager"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -21107,12 +21261,38 @@ def plan_artifact(artifact):
|
|||||||
"device-postgres,device-backhaul-target"
|
"device-postgres,device-backhaul-target"
|
||||||
if device_plane_manager_activation_preflight["descriptor"].get(
|
if device_plane_manager_activation_preflight["descriptor"].get(
|
||||||
"schemaVersion"
|
"schemaVersion"
|
||||||
) == "nodedc.device-plane.device-manager-release.v3"
|
) in (
|
||||||
|
"nodedc.device-plane.device-manager-release.v3",
|
||||||
|
"nodedc.device-plane.device-manager-release.v4",
|
||||||
|
)
|
||||||
else "preserved:device-gateway,device-postgres,"
|
else "preserved:device-gateway,device-postgres,"
|
||||||
"device-backhaul-target"
|
"device-backhaul-target"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
print("device_manager_health_gate=bounded-grace+contract")
|
manager_schema = device_plane_manager_activation_preflight[
|
||||||
|
"descriptor"
|
||||||
|
].get("schemaVersion")
|
||||||
|
print(
|
||||||
|
"device_manager_health_gate="
|
||||||
|
+ (
|
||||||
|
"bounded-grace+contract+persistent-data"
|
||||||
|
if manager_schema
|
||||||
|
== "nodedc.device-plane.device-manager-release.v4"
|
||||||
|
else "bounded-grace+contract"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if manager_schema == (
|
||||||
|
"nodedc.device-plane.device-manager-release.v4"
|
||||||
|
):
|
||||||
|
print(
|
||||||
|
"device_manager_persistent_data=runner-managed-preserved:"
|
||||||
|
f"{DEVICE_PLANE_MANAGER_DATA_DIR}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"device_manager_persistent_mount=read-write:"
|
||||||
|
f"{DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR}"
|
||||||
|
)
|
||||||
|
print("device_manager_default_accent=#f5f5f5")
|
||||||
if device_plane_manager_activation_preflight["descriptor"].get(
|
if device_plane_manager_activation_preflight["descriptor"].get(
|
||||||
"commandTransport"
|
"commandTransport"
|
||||||
) == "typed-service-ping-v1":
|
) == "typed-service-ping-v1":
|
||||||
@@ -21125,7 +21305,13 @@ def plan_artifact(artifact):
|
|||||||
print("device_gateway_tcp_9921=preserved:loopback-only")
|
print("device_gateway_tcp_9921=preserved:loopback-only")
|
||||||
print(
|
print(
|
||||||
"device_plane_rollback="
|
"device_plane_rollback="
|
||||||
"source+reconciled-baseline-runtime"
|
+ (
|
||||||
|
"source+reconciled-baseline-runtime+"
|
||||||
|
"persistent-manager-data-preserved"
|
||||||
|
if manager_schema
|
||||||
|
== "nodedc.device-plane.device-manager-release.v4"
|
||||||
|
else "source+reconciled-baseline-runtime"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if device_plane_control_core_release_preflight is not None:
|
if device_plane_control_core_release_preflight is not None:
|
||||||
core_release = device_plane_control_core_release_preflight
|
core_release = device_plane_control_core_release_preflight
|
||||||
@@ -22689,6 +22875,47 @@ def ensure_device_plane_backhaul_target_state():
|
|||||||
die("Device Plane backhaul runtime trust verification failed")
|
die("Device Plane backhaul runtime trust verification failed")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_device_plane_manager_persistent_data_metadata():
|
||||||
|
try:
|
||||||
|
data_stat = DEVICE_PLANE_MANAGER_DATA_DIR.lstat()
|
||||||
|
except FileNotFoundError:
|
||||||
|
die("Device Manager persistent data directory is missing")
|
||||||
|
if (
|
||||||
|
stat.S_ISLNK(data_stat.st_mode)
|
||||||
|
or not stat.S_ISDIR(data_stat.st_mode)
|
||||||
|
or data_stat.st_uid != 1000
|
||||||
|
or data_stat.st_gid != 1000
|
||||||
|
or stat.S_IMODE(data_stat.st_mode) != 0o750
|
||||||
|
):
|
||||||
|
die("Device Manager persistent data directory boundary mismatch")
|
||||||
|
return "uid-1000-gid-1000-mode-0750"
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_device_plane_manager_persistent_data():
|
||||||
|
data_parent = DEVICE_PLANE_MANAGER_DATA_DIR.parent
|
||||||
|
parent_created = False
|
||||||
|
try:
|
||||||
|
parent_stat = data_parent.lstat()
|
||||||
|
except FileNotFoundError:
|
||||||
|
data_parent.mkdir(mode=0o755)
|
||||||
|
parent_created = True
|
||||||
|
parent_stat = data_parent.lstat()
|
||||||
|
if stat.S_ISLNK(parent_stat.st_mode) or not stat.S_ISDIR(
|
||||||
|
parent_stat.st_mode
|
||||||
|
):
|
||||||
|
die("Device Manager persistent data parent is unsafe")
|
||||||
|
if parent_created:
|
||||||
|
os.chown(data_parent, 0, 0)
|
||||||
|
data_parent.chmod(0o755)
|
||||||
|
DEVICE_PLANE_MANAGER_DATA_DIR.mkdir(exist_ok=True)
|
||||||
|
data_stat = DEVICE_PLANE_MANAGER_DATA_DIR.lstat()
|
||||||
|
if stat.S_ISLNK(data_stat.st_mode) or not stat.S_ISDIR(data_stat.st_mode):
|
||||||
|
die("Device Manager persistent data path is unsafe")
|
||||||
|
os.chown(DEVICE_PLANE_MANAGER_DATA_DIR, 1000, 1000)
|
||||||
|
DEVICE_PLANE_MANAGER_DATA_DIR.chmod(0o750)
|
||||||
|
return validate_device_plane_manager_persistent_data_metadata()
|
||||||
|
|
||||||
|
|
||||||
def stop_and_remove_compose_services(component, services):
|
def stop_and_remove_compose_services(component, services):
|
||||||
if not services:
|
if not services:
|
||||||
return
|
return
|
||||||
@@ -22771,6 +22998,8 @@ def prepare_component_runtime(component, entries=None):
|
|||||||
MAP_GATEWAY_SECRET_RE,
|
MAP_GATEWAY_SECRET_RE,
|
||||||
"Device Core Hub handoff",
|
"Device Core Hub handoff",
|
||||||
)
|
)
|
||||||
|
if is_device_plane_manager_release_v4_slice(component, entries):
|
||||||
|
ensure_device_plane_manager_persistent_data()
|
||||||
if (
|
if (
|
||||||
is_device_plane_control_core_release_slice(component, entries)
|
is_device_plane_control_core_release_slice(component, entries)
|
||||||
or is_device_plane_edge_core_channel_bootstrap_slice(
|
or is_device_plane_edge_core_channel_bootstrap_slice(
|
||||||
@@ -23093,7 +23322,7 @@ def component_healthchecks(component, entries=None, services=None):
|
|||||||
if is_device_plane_manager_control_plane_slice(component, entries):
|
if is_device_plane_manager_control_plane_slice(component, entries):
|
||||||
command_transport = (
|
command_transport = (
|
||||||
"typed-service-ping-v1"
|
"typed-service-ping-v1"
|
||||||
if is_device_plane_manager_release_v3_slice(component, entries)
|
if is_device_plane_manager_only_release_slice(component, entries)
|
||||||
else "disabled"
|
else "disabled"
|
||||||
)
|
)
|
||||||
checks = ({
|
checks = ({
|
||||||
@@ -23107,7 +23336,7 @@ def component_healthchecks(component, entries=None, services=None):
|
|||||||
"commandTransport": command_transport,
|
"commandTransport": command_transport,
|
||||||
},
|
},
|
||||||
},)
|
},)
|
||||||
if is_device_plane_manager_release_v3_slice(component, entries):
|
if is_device_plane_manager_only_release_slice(component, entries):
|
||||||
checks += ({
|
checks += ({
|
||||||
"url": "http://127.0.0.1:18080/healthz",
|
"url": "http://127.0.0.1:18080/healthz",
|
||||||
"headers": {"Host": "device.nodedc.ru"},
|
"headers": {"Host": "device.nodedc.ru"},
|
||||||
@@ -23866,7 +24095,7 @@ def run_healthchecks(component, entries=None, services=None):
|
|||||||
if is_device_plane_manager_control_plane_slice(component, entries):
|
if is_device_plane_manager_control_plane_slice(component, entries):
|
||||||
expected_services = (
|
expected_services = (
|
||||||
("device-manager",)
|
("device-manager",)
|
||||||
if is_device_plane_manager_release_v3_slice(component, entries)
|
if is_device_plane_manager_only_release_slice(component, entries)
|
||||||
else ("device-control-core", "device-manager")
|
else ("device-control-core", "device-manager")
|
||||||
)
|
)
|
||||||
if tuple(services or ()) != expected_services:
|
if tuple(services or ()) != expected_services:
|
||||||
@@ -23878,7 +24107,13 @@ def run_healthchecks(component, entries=None, services=None):
|
|||||||
)
|
)
|
||||||
for check in component_healthchecks(component, entries, services):
|
for check in component_healthchecks(component, entries, services):
|
||||||
healthcheck_url(check)
|
healthcheck_url(check)
|
||||||
if is_device_plane_manager_release_v3_slice(component, entries):
|
if is_device_plane_manager_release_v4_slice(component, entries):
|
||||||
|
validate_device_manager_control_plane_runtime(
|
||||||
|
require_edge_channel=True,
|
||||||
|
core_network_mode="private-egress",
|
||||||
|
require_persistent_data=True,
|
||||||
|
)
|
||||||
|
elif is_device_plane_manager_release_v3_slice(component, entries):
|
||||||
validate_device_manager_control_plane_runtime(
|
validate_device_manager_control_plane_runtime(
|
||||||
require_edge_channel=True,
|
require_edge_channel=True,
|
||||||
core_network_mode="private-egress",
|
core_network_mode="private-egress",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import importlib.machinery
|
|||||||
import importlib.util
|
import importlib.util
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
|
import stat
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -28,6 +29,30 @@ RUNNER = load_runner()
|
|||||||
|
|
||||||
|
|
||||||
class DevicePlaneRegistryTest(unittest.TestCase):
|
class DevicePlaneRegistryTest(unittest.TestCase):
|
||||||
|
def test_manager_v4_pins_032_and_persistent_white_boundary(self):
|
||||||
|
self.assertEqual(
|
||||||
|
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_PATCH_ID,
|
||||||
|
"device-manager-release-v3-20260822-032",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_ARTIFACT_SHA256,
|
||||||
|
"6e0eb3a0a6f19ceab92d46832b93bffbcea21247dbdc2ea50625a51ff460e4ca",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256,
|
||||||
|
"e7dff0f5873ad4586bd55946d3db2bb86092a5e149e886d120adc041e056c256",
|
||||||
|
)
|
||||||
|
boundaries = RUNNER.expected_device_plane_manager_release_v4_boundaries()
|
||||||
|
self.assertEqual(boundaries["defaultAccentHex"], "#f5f5f5")
|
||||||
|
self.assertEqual(
|
||||||
|
boundaries["presentationDataHostPath"],
|
||||||
|
"/volume1/docker/nodedc-device-plane/data/device-manager",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
boundaries["presentationDataContainerPath"],
|
||||||
|
"/var/lib/nodedc-device-manager",
|
||||||
|
)
|
||||||
|
|
||||||
def test_manager_v3_targets_applied_control_core_recovery(self):
|
def test_manager_v3_targets_applied_control_core_recovery(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_PATCH_ID,
|
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_PATCH_ID,
|
||||||
@@ -295,6 +320,110 @@ class DevicePlaneRegistryTest(unittest.TestCase):
|
|||||||
core_network_mode="private-egress",
|
core_network_mode="private-egress",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_manager_v4_post_apply_requires_persistent_data(self):
|
||||||
|
with (
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"healthcheck_compose_service_with_grace",
|
||||||
|
),
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"component_healthchecks",
|
||||||
|
return_value=(),
|
||||||
|
),
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"validate_device_manager_control_plane_runtime",
|
||||||
|
) as runtime_acceptance,
|
||||||
|
):
|
||||||
|
RUNNER.run_healthchecks(
|
||||||
|
"device-plane",
|
||||||
|
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES,
|
||||||
|
("device-manager",),
|
||||||
|
)
|
||||||
|
|
||||||
|
runtime_acceptance.assert_called_once_with(
|
||||||
|
require_edge_channel=True,
|
||||||
|
core_network_mode="private-egress",
|
||||||
|
require_persistent_data=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_manager_v4_prepare_owns_new_parent_and_managed_data_directory(self):
|
||||||
|
with tempfile.TemporaryDirectory(
|
||||||
|
prefix="nodedc-manager-v4-data-",
|
||||||
|
) as directory:
|
||||||
|
data_dir = Path(directory) / "data" / "device-manager"
|
||||||
|
with (
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"DEVICE_PLANE_MANAGER_DATA_DIR",
|
||||||
|
data_dir,
|
||||||
|
),
|
||||||
|
mock.patch.object(RUNNER.os, "chown") as chown,
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"validate_device_plane_manager_persistent_data_metadata",
|
||||||
|
return_value="uid-1000-gid-1000-mode-0750",
|
||||||
|
) as validate,
|
||||||
|
):
|
||||||
|
result = RUNNER.ensure_device_plane_manager_persistent_data()
|
||||||
|
|
||||||
|
self.assertEqual(result, "uid-1000-gid-1000-mode-0750")
|
||||||
|
self.assertEqual(
|
||||||
|
chown.call_args_list,
|
||||||
|
[
|
||||||
|
mock.call(data_dir.parent, 0, 0),
|
||||||
|
mock.call(data_dir, 1000, 1000),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
validate.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_manager_v4_prepare_preserves_existing_safe_parent_metadata(self):
|
||||||
|
with tempfile.TemporaryDirectory(
|
||||||
|
prefix="nodedc-manager-v4-parent-",
|
||||||
|
) as directory:
|
||||||
|
data_parent = Path(directory) / "data"
|
||||||
|
data_parent.mkdir(mode=0o700)
|
||||||
|
data_dir = data_parent / "device-manager"
|
||||||
|
with (
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"DEVICE_PLANE_MANAGER_DATA_DIR",
|
||||||
|
data_dir,
|
||||||
|
),
|
||||||
|
mock.patch.object(RUNNER.os, "chown") as chown,
|
||||||
|
mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"validate_device_plane_manager_persistent_data_metadata",
|
||||||
|
return_value="uid-1000-gid-1000-mode-0750",
|
||||||
|
),
|
||||||
|
):
|
||||||
|
RUNNER.ensure_device_plane_manager_persistent_data()
|
||||||
|
|
||||||
|
self.assertEqual(stat.S_IMODE(data_parent.stat().st_mode), 0o700)
|
||||||
|
chown.assert_called_once_with(data_dir, 1000, 1000)
|
||||||
|
|
||||||
|
def test_manager_v4_prepare_rejects_symlink_parent_before_child_creation(self):
|
||||||
|
with tempfile.TemporaryDirectory(
|
||||||
|
prefix="nodedc-manager-v4-symlink-",
|
||||||
|
) as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
outside = root / "outside"
|
||||||
|
outside.mkdir()
|
||||||
|
data_parent = root / "data"
|
||||||
|
data_parent.symlink_to(outside, target_is_directory=True)
|
||||||
|
with mock.patch.object(
|
||||||
|
RUNNER,
|
||||||
|
"DEVICE_PLANE_MANAGER_DATA_DIR",
|
||||||
|
data_parent / "device-manager",
|
||||||
|
):
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
RUNNER.DeployError,
|
||||||
|
"persistent data parent is unsafe",
|
||||||
|
):
|
||||||
|
RUNNER.ensure_device_plane_manager_persistent_data()
|
||||||
|
self.assertFalse((outside / "device-manager").exists())
|
||||||
|
|
||||||
def test_manager_predecessor_health_is_phase_scoped(self):
|
def test_manager_predecessor_health_is_phase_scoped(self):
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
RUNNER.DeployError,
|
RUNNER.DeployError,
|
||||||
|
|||||||
Reference in New Issue
Block a user