meta-digi-containers: generate DCP with DRM policy block

Align the policy DRM block with the latest changes done in the
cc-container-mng tool.

https://onedigi.atlassian.net/browse/DEL-10075

Signed-off-by: Isaac Hermida <isaac.hermida@digi.com>
This commit is contained in:
Isaac Hermida 2026-04-21 13:02:12 +02:00
parent 408a8bec60
commit 2fa5427622
6 changed files with 115 additions and 15 deletions

View File

@ -98,24 +98,36 @@ This generates a bundle named like:
`dey-image-container-manager` includes `cc-container-mng` that has the capability to `dey-image-container-manager` includes `cc-container-mng` that has the capability to
publish container statistics through the local CCCS Python API. publish container statistics through the local CCCS Python API.
For generated DCPs, per-container DRM sampling is enabled For generated DCPs, DRM behavior is controlled through `registration_defaults.drm`
through `registration_defaults.stats_publish` in the artifact manifest. in the artifact manifest.
Those manifest defaults establish the initial runtime policy on the target. Those manifest defaults establish the initial runtime policy on the target.
After installation, mutable policy such as `autostart`, `monitor`, `restart`, and After installation, mutable policy such as `autostart`, `monitor`, `restart`, and
`stats_publish` can be inspected or updated through the container manager `config` `drm` can be inspected or updated through the container manager `config`
`get` and `set` operations without regenerating the DCP. `get` and `set` operations without regenerating the DCP.
The image recipe generates the DCP automatically from the following variables: The image recipe generates the DCP automatically from the following variables:
- `CONTAINER_STATS_PUBLISH_ENABLED` - `CONTAINER_DRM_ENABLED`
- `CONTAINER_STATS_PUBLISH_SAMPLE_INTERVAL` - `CONTAINER_DRM_STATS_SAMPLE_INTERVAL`
- `CONTAINER_DRM_STATS_LIST_OF_METRICS`
Example: Example:
```conf ```conf
CONTAINER_STATS_PUBLISH_ENABLED = "true" CONTAINER_DRM_ENABLED = "true"
CONTAINER_STATS_PUBLISH_SAMPLE_INTERVAL = "30" CONTAINER_DRM_STATS_SAMPLE_INTERVAL = "30"
CONTAINER_DRM_STATS_LIST_OF_METRICS = "[\"cpu\", \"mem\"]"
``` ```
`CONTAINER_DRM_STATS_LIST_OF_METRICS` follows the same semantics as the target
manager:
- `["all"]` is accepted as a shorthand input for all periodic metrics.
- `[]` publishes no periodic metrics.
- any other list publishes only the selected metrics.
Generated manifests and target-side effective configuration use the explicit
metric list.
## Layer Scope ## Layer Scope
Main recipes: Main recipes:
@ -294,8 +306,9 @@ Relevant variables:
- `CONTAINER_PACKAGE_ID` - `CONTAINER_PACKAGE_ID`
- `CONTAINER_ARTIFACT_VERSION` - `CONTAINER_ARTIFACT_VERSION`
- `CONTAINER_CREATE_ARGS_PODMAN` - `CONTAINER_CREATE_ARGS_PODMAN`
- `CONTAINER_STATS_PUBLISH_ENABLED` - `CONTAINER_DRM_ENABLED`
- `CONTAINER_STATS_PUBLISH_SAMPLE_INTERVAL` - `CONTAINER_DRM_STATS_SAMPLE_INTERVAL`
- `CONTAINER_DRM_STATS_LIST_OF_METRICS`
- `CONTAINER_FIRMWARE_VERSIONS` - `CONTAINER_FIRMWARE_VERSIONS`
- `CONTAINER_DEVICE_TYPES_JSON` - `CONTAINER_DEVICE_TYPES_JSON`
- `CONTAINER_ARTIFACT_DESCRIPTION` - `CONTAINER_ARTIFACT_DESCRIPTION`

View File

@ -5,6 +5,19 @@
"registration_defaults": { "registration_defaults": {
"autostart": true, "autostart": true,
"monitor": true, "monitor": true,
"drm": {
"enabled": true,
"stats_sample_interval_s": 30,
"stats_list_of_metrics": [
"uptime",
"cpu",
"mem",
"net/rx",
"net/tx",
"disk/read",
"disk/write"
]
},
"restart": { "restart": {
"enabled": true, "enabled": true,
"max_retries": 3, "max_retries": 3,

View File

@ -6,6 +6,19 @@
"registration_defaults": { "registration_defaults": {
"autostart": true, "autostart": true,
"monitor": true, "monitor": true,
"drm": {
"enabled": true,
"stats_sample_interval_s": 30,
"stats_list_of_metrics": [
"uptime",
"cpu",
"mem",
"net/rx",
"net/tx",
"disk/read",
"disk/write"
]
},
"restart": { "restart": {
"enabled": true, "enabled": true,
"max_retries": 3, "max_retries": 3,

View File

@ -53,8 +53,9 @@ do_image_container_artifacts() {
CREATE_ARGS="${create_args}" \ CREATE_ARGS="${create_args}" \
AUTOSTART="${CONTAINER_AUTOSTART}" \ AUTOSTART="${CONTAINER_AUTOSTART}" \
MONITOR="${CONTAINER_MONITOR}" \ MONITOR="${CONTAINER_MONITOR}" \
STATS_PUBLISH_ENABLED="${CONTAINER_STATS_PUBLISH_ENABLED}" \ DRM_ENABLED="${CONTAINER_DRM_ENABLED}" \
STATS_PUBLISH_SAMPLE_INTERVAL="${CONTAINER_STATS_PUBLISH_SAMPLE_INTERVAL}" \ DRM_STATS_SAMPLE_INTERVAL="${CONTAINER_DRM_STATS_SAMPLE_INTERVAL}" \
DRM_STATS_LIST_OF_METRICS="${CONTAINER_DRM_STATS_LIST_OF_METRICS}" \
RESTART_ENABLED="${CONTAINER_RESTART_ENABLED}" \ RESTART_ENABLED="${CONTAINER_RESTART_ENABLED}" \
RESTART_MAX_RETRIES="${CONTAINER_RESTART_MAX_RETRIES}" \ RESTART_MAX_RETRIES="${CONTAINER_RESTART_MAX_RETRIES}" \
RESTART_WINDOW="${CONTAINER_RESTART_WINDOW}" \ RESTART_WINDOW="${CONTAINER_RESTART_WINDOW}" \
@ -66,7 +67,7 @@ do_image_container_artifacts() {
LABELS_JSON="${CONTAINER_ARTIFACT_LABELS_JSON}" \ LABELS_JSON="${CONTAINER_ARTIFACT_LABELS_JSON}" \
python3 -c 'import json, os, sys; \ python3 -c 'import json, os, sys; \
parse_bool = lambda name: os.environ[name].strip().lower() == "true"; \ parse_bool = lambda name: os.environ[name].strip().lower() == "true"; \
payload = {"package_id": os.environ["PACKAGE_ID"], "version": os.environ["VERSION"], "runtime": os.environ["RUNTIME"], "registration_defaults": {"autostart": parse_bool("AUTOSTART"), "monitor": parse_bool("MONITOR"), "stats_publish": {"enabled": parse_bool("STATS_PUBLISH_ENABLED"), "sample_interval_s": int(os.environ["STATS_PUBLISH_SAMPLE_INTERVAL"])}, "restart": {"enabled": parse_bool("RESTART_ENABLED"), "max_retries": int(os.environ["RESTART_MAX_RETRIES"]), "window": int(os.environ["RESTART_WINDOW"]), "retry_delay": int(os.environ["RESTART_RETRY_DELAY"]) } }, "device_types": json.loads(os.environ["DEVICE_TYPES_JSON"]), "firmware_versions": os.environ["FIRMWARE_VERSIONS"], "build_id": os.environ["BUILD_ID"], "description": os.environ["DESCRIPTION"], "labels": json.loads(os.environ["LABELS_JSON"])}; \ payload = {"package_id": os.environ["PACKAGE_ID"], "version": os.environ["VERSION"], "runtime": os.environ["RUNTIME"], "registration_defaults": {"autostart": parse_bool("AUTOSTART"), "monitor": parse_bool("MONITOR"), "drm": {"enabled": parse_bool("DRM_ENABLED"), "stats_sample_interval_s": int(os.environ["DRM_STATS_SAMPLE_INTERVAL"]), "stats_list_of_metrics": json.loads(os.environ["DRM_STATS_LIST_OF_METRICS"])}, "restart": {"enabled": parse_bool("RESTART_ENABLED"), "max_retries": int(os.environ["RESTART_MAX_RETRIES"]), "window": int(os.environ["RESTART_WINDOW"]), "retry_delay": int(os.environ["RESTART_RETRY_DELAY"]) } }, "device_types": json.loads(os.environ["DEVICE_TYPES_JSON"]), "firmware_versions": os.environ["FIRMWARE_VERSIONS"], "build_id": os.environ["BUILD_ID"], "description": os.environ["DESCRIPTION"], "labels": json.loads(os.environ["LABELS_JSON"])}; \
create_args = os.environ["CREATE_ARGS"].strip(); \ create_args = os.environ["CREATE_ARGS"].strip(); \
payload.update({"create_args": create_args} if create_args else {}); \ payload.update({"create_args": create_args} if create_args else {}); \
open(sys.argv[1], "w", encoding="utf-8").write(json.dumps(payload, indent=2) + "\n")' \ open(sys.argv[1], "w", encoding="utf-8").write(json.dumps(payload, indent=2) + "\n")' \

View File

@ -69,8 +69,9 @@ CONTAINER_ARTIFACT_LABELS_JSON ?= "{}"
# monitor and (auto/re)start configuration # monitor and (auto/re)start configuration
CONTAINER_AUTOSTART ?= "true" CONTAINER_AUTOSTART ?= "true"
CONTAINER_MONITOR ?= "true" CONTAINER_MONITOR ?= "true"
CONTAINER_STATS_PUBLISH_ENABLED ?= "true" CONTAINER_DRM_ENABLED ?= "true"
CONTAINER_STATS_PUBLISH_SAMPLE_INTERVAL ?= "30" CONTAINER_DRM_STATS_SAMPLE_INTERVAL ?= "30"
CONTAINER_DRM_STATS_LIST_OF_METRICS ?= "[\"uptime\", \"cpu\", \"mem\", \"net/rx\", \"net/tx\", \"disk/read\", \"disk/write\"]"
CONTAINER_RESTART_ENABLED ?= "true" CONTAINER_RESTART_ENABLED ?= "true"
CONTAINER_RESTART_MAX_RETRIES ?= "3" CONTAINER_RESTART_MAX_RETRIES ?= "3"
CONTAINER_RESTART_WINDOW ?= "60" CONTAINER_RESTART_WINDOW ?= "60"

View File

@ -16,6 +16,16 @@ import tarfile
import tempfile import tempfile
BASE36_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz" BASE36_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz"
SUPPORTED_STATS_METRICS = (
"uptime",
"cpu",
"mem",
"net/rx",
"net/tx",
"disk/read",
"disk/write",
)
SUPPORTED_STATS_METRICS_SET = set(SUPPORTED_STATS_METRICS)
def fail(message: str) -> "NoReturn": def fail(message: str) -> "NoReturn":
@ -100,6 +110,34 @@ def validate_labels(value: object) -> dict:
return value return value
def validate_string_list(value: object, *, path: str, allowed: set[str]) -> list[str]:
if value is None:
return []
if not isinstance(value, list):
fail(f"invalid manifest: {path} must be an array")
result: list[str] = []
seen: set[str] = set()
for item in value:
if not isinstance(item, str) or not item.strip():
fail(f"invalid manifest: {path} entries must be non-empty strings")
metric = item.strip()
if metric not in allowed:
fail(f"invalid manifest: {path} contains unsupported metric {metric}")
if metric in seen:
continue
seen.add(metric)
result.append(metric)
if "all" in result and len(result) > 1:
fail(f"invalid manifest: {path} cannot combine all with specific metrics")
return result
def normalize_drm_stats_metrics(metrics: list[str]) -> list[str]:
if "all" in metrics:
return list(SUPPORTED_STATS_METRICS)
return metrics
def validate_manifest(data: dict) -> dict: def validate_manifest(data: dict) -> dict:
package_id = validate_string(data, "package_id", path="manifest") package_id = validate_string(data, "package_id", path="manifest")
version = validate_string(data, "version", path="manifest") version = validate_string(data, "version", path="manifest")
@ -114,6 +152,9 @@ def validate_manifest(data: dict) -> dict:
restart = registration_defaults.get("restart") restart = registration_defaults.get("restart")
if not isinstance(restart, dict): if not isinstance(restart, dict):
fail("invalid manifest: registration_defaults.restart must be an object") fail("invalid manifest: registration_defaults.restart must be an object")
drm = registration_defaults.get("drm", {})
if not isinstance(drm, dict):
fail("invalid manifest: registration_defaults.drm must be an object")
create_args = data.get("create_args") create_args = data.get("create_args")
if runtime == "podman": if runtime == "podman":
@ -135,7 +176,7 @@ def validate_manifest(data: dict) -> dict:
if description is not None and not isinstance(description, str): if description is not None and not isinstance(description, str):
fail("invalid manifest: description must be a string") fail("invalid manifest: description must be a string")
return { validated = {
"package_id": package_id, "package_id": package_id,
"version": version, "version": version,
"runtime": runtime, "runtime": runtime,
@ -143,6 +184,20 @@ def validate_manifest(data: dict) -> dict:
"registration_defaults": { "registration_defaults": {
"autostart": validate_bool(registration_defaults, "autostart", path="registration_defaults"), "autostart": validate_bool(registration_defaults, "autostart", path="registration_defaults"),
"monitor": validate_bool(registration_defaults, "monitor", path="registration_defaults"), "monitor": validate_bool(registration_defaults, "monitor", path="registration_defaults"),
"drm": {
"enabled": validate_bool(drm, "enabled", path="registration_defaults.drm"),
"stats_sample_interval_s": validate_int(
drm,
"stats_sample_interval_s",
path="registration_defaults.drm",
minimum=1,
),
"stats_list_of_metrics": validate_string_list(
drm.get("stats_list_of_metrics"),
path="registration_defaults.drm.stats_list_of_metrics",
allowed=SUPPORTED_STATS_METRICS_SET | {"all"},
),
},
"restart": { "restart": {
"enabled": validate_bool(restart, "enabled", path="registration_defaults.restart"), "enabled": validate_bool(restart, "enabled", path="registration_defaults.restart"),
"max_retries": validate_int(restart, "max_retries", path="registration_defaults.restart", minimum=0), "max_retries": validate_int(restart, "max_retries", path="registration_defaults.restart", minimum=0),
@ -156,6 +211,10 @@ def validate_manifest(data: dict) -> dict:
"description": description or "", "description": description or "",
"labels": validate_labels(data.get("labels")), "labels": validate_labels(data.get("labels")),
} }
validated["registration_defaults"]["drm"]["stats_list_of_metrics"] = normalize_drm_stats_metrics(
validated["registration_defaults"]["drm"]["stats_list_of_metrics"]
)
return validated
def validate_payload(path: Path, runtime: str) -> None: def validate_payload(path: Path, runtime: str) -> None: