diff --git a/dockers/docker-telemetry-sidecar/Dockerfile.j2 b/dockers/docker-telemetry-sidecar/Dockerfile.j2 new file mode 100644 index 0000000000..64d96d247c --- /dev/null +++ b/dockers/docker-telemetry-sidecar/Dockerfile.j2 @@ -0,0 +1,37 @@ +{% from "dockers/dockerfile-macros.j2" import install_debian_packages, install_python_wheels, copy_files %} +ARG BASE=docker-config-engine-bookworm-{{DOCKER_USERNAME}}:{{DOCKER_USERTAG}} + +FROM $BASE AS base + +ARG docker_container_name +ARG image_version +RUN [ -f /etc/rsyslog.conf ] && sed -ri "s/%syslogtag%/$docker_container_name#%syslogtag%/;" /etc/rsyslog.conf + +# Make apt-get non-interactive +ENV DEBIAN_FRONTEND=noninteractive + +# Pass the image_version to container +ENV IMAGE_VERSION=$image_version + +# K8s will override this +ENV IS_V1_ENABLED=false + +COPY ["systemd_stub.py", "/usr/bin/"] +COPY ["systemd_scripts/", "/usr/share/sonic/systemd_scripts/"] +COPY ["files/container_checker", "/usr/share/sonic/systemd_scripts/container_checker"] +COPY ["files/telemetry.sh", "/usr/share/sonic/systemd_scripts/telemetry_v1.sh"] +COPY ["supervisord.conf", "/etc/supervisor/conf.d/"] + +RUN chmod +x /usr/bin/systemd_stub.py + +FROM $BASE + +RUN --mount=type=bind,from=base,target=/changes-to-image rsync -axAX --no-D --exclude=/sys --exclude=/proc --exclude=/dev --exclude=resolv.conf /changes-to-image/ / + +# Make apt-get non-interactive +ENV DEBIAN_FRONTEND=noninteractive + +# Pass the image_version to container +ENV IMAGE_VERSION=$image_version + +ENTRYPOINT ["/usr/local/bin/supervisord"] diff --git a/dockers/docker-telemetry-sidecar/supervisord.conf b/dockers/docker-telemetry-sidecar/supervisord.conf new file mode 100644 index 0000000000..9f4964826e --- /dev/null +++ b/dockers/docker-telemetry-sidecar/supervisord.conf @@ -0,0 +1,38 @@ +[supervisord] +logfile_maxbytes=1MB +logfile_backups=2 +nodaemon=true + +[eventlistener:dependent-startup] +command=python3 -m supervisord_dependent_startup +autostart=true +autorestart=unexpected +startretries=0 +exitcodes=0,3 +events=PROCESS_STATE +buffer_size=1024 + +[program:rsyslogd] +command=/usr/sbin/rsyslogd -n -iNONE +priority=1 +autostart=false +autorestart=unexpected +stdout_logfile=NONE +stdout_syslog=true +stderr_logfile=NONE +stderr_syslog=true +dependent_startup=true + +[program:systemd_stub] +command=python3 /usr/bin/systemd_stub.py +priority=3 +autostart=true +autorestart=true +startsecs=0 +stdout_logfile=NONE +stdout_syslog=true +stderr_logfile=NONE +stderr_syslog=true +dependent_startup=true +dependent_startup_wait_for=rsyslogd:running +environment=IS_V1_ENABLED=%(ENV_IS_V1_ENABLED)s \ No newline at end of file diff --git a/dockers/docker-telemetry-sidecar/systemd_scripts/telemetry.sh b/dockers/docker-telemetry-sidecar/systemd_scripts/telemetry.sh new file mode 100644 index 0000000000..c196f8c4a1 --- /dev/null +++ b/dockers/docker-telemetry-sidecar/systemd_scripts/telemetry.sh @@ -0,0 +1,100 @@ +#!/bin/bash +set -euo pipefail + +SERVICE="telemetry" +NS="${NS:-sonic}" # k8s namespace +LABEL="raw_container_name=${SERVICE}" # selector used by DaemonSet +KUBECTL_BIN="${KUBECTL_BIN:-kubectl}" +NODE_NAME="${NODE_NAME:-$(hostname)}" +DEV="${2:-}" # accepted for compatibility; unused (single-ASIC) + +log() { /usr/bin/logger -t "${SERVICE}#system" "$*"; } + +require_kubectl() { + if ! command -v "${KUBECTL_BIN}" >/dev/null 2>&1; then + echo "ERROR: kubectl not found (KUBECTL_BIN=${KUBECTL_BIN})." >&2 + exit 127 + fi + # Try a sensible default if KUBECONFIG isn’t set + if [[ -z "${KUBECONFIG:-}" && -r /etc/kubernetes/kubelet.conf ]]; then + export KUBECONFIG=/etc/kubernetes/kubelet.conf + fi +} + +pods_on_node() { + # Prints: " " per line for this node + "${KUBECTL_BIN}" -n "${NS}" get pods \ + -l "${LABEL}" \ + --field-selector "spec.nodeName=${NODE_NAME}" \ + -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.status.phase}{"\n"}{end}' 2>/dev/null || true +} + +kill_pods() { + require_kubectl + local found=0 + while read -r name phase; do + [[ -z "${name}" ]] && continue + found=1 + log "Deleting ${SERVICE} pod ${name} (phase=${phase}) on node ${NODE_NAME}" + # Force/instant delete to emulate “kill”; DaemonSet will recreate + "${KUBECTL_BIN}" -n "${NS}" delete pod "${name}" --grace-period=0 --force >/dev/null 2>&1 || true + done < <(pods_on_node) + if [[ "${found}" -eq 0 ]]; then + log "No ${SERVICE} pods found on node ${NODE_NAME} (namespace=${NS}, label=${LABEL})." + fi +} + +cmd_start() { kill_pods; } # start == kill (DS restarts) +cmd_stop() { kill_pods; } +cmd_restart() { kill_pods; } + +cmd_status() { + require_kubectl + local out; out="$(pods_on_node)" + if [[ -z "${out}" ]]; then + echo "${SERVICE}: NOT RUNNING (no pod on node ${NODE_NAME})" + exit 3 + fi + echo "${out}" | while read -r name phase; do + [[ -z "${name}" ]] && continue + echo "${SERVICE} pod ${name}: ${phase}" + done + # Exit 0 if at least one Running, 1 otherwise + if echo "${out}" | awk '$2=="Running"{found=1} END{exit found?0:1}'; then + exit 0 + else + exit 1 + fi +} + +cmd_wait() { + require_kubectl + log "Waiting on ${SERVICE} pods (ns=${NS}, label=${LABEL}) on node ${NODE_NAME}..." + # Keep the systemd service 'active' as long as at least one pod exists for this node. + while true; do + local out; out="$(pods_on_node)" + if [[ -z "${out}" ]]; then + # no pod presently; keep waiting (DaemonSet may bring it up) + sleep 5 + continue + fi + # If at least one is Running, sleep longer; otherwise poll faster + if echo "${out}" | awk '$2=="Running"{found=1} END{exit found?0:1}'; then + sleep 60 + else + sleep 5 + fi + done +} + +case "${1:-}" in + start) cmd_start ;; + stop) cmd_stop ;; + restart) cmd_restart ;; + wait) cmd_wait ;; + status) cmd_status ;; + *) + echo "Usage: $0 {start|stop|restart|wait|status} [asic-id(optional, ignored)]" >&2 + exit 2 + ;; +esac diff --git a/dockers/docker-telemetry-sidecar/systemd_scripts/tests/test_systemd_stub.py b/dockers/docker-telemetry-sidecar/systemd_scripts/tests/test_systemd_stub.py new file mode 100644 index 0000000000..2d269a16d6 --- /dev/null +++ b/dockers/docker-telemetry-sidecar/systemd_scripts/tests/test_systemd_stub.py @@ -0,0 +1,215 @@ +# tests/test_systemd_stub.py +import sys +import types +import importlib +from pathlib import Path + +import pytest + + +@pytest.fixture(scope="session", autouse=True) +def fake_logger_module(): + pkg = types.ModuleType("sonic_py_common") + logger_mod = types.ModuleType("sonic_py_common.logger") + + class _Logger: + def __init__(self): + self.messages = [] + + def _log(self, level, msg): + self.messages.append((level, msg)) + + def log_debug(self, msg): self._log("DEBUG", msg) + def log_info(self, msg): self._log("INFO", msg) + def log_error(self, msg): self._log("ERROR", msg) + def log_notice(self, msg): self._log("NOTICE", msg) + def log_warning(self, msg): self._log("WARNING", msg) + def log_critical(self, msg): self._log("CRITICAL", msg) + + logger_mod.Logger = _Logger + pkg.logger = logger_mod + sys.modules["sonic_py_common"] = pkg + sys.modules["sonic_py_common.logger"] = logger_mod + yield + + +@pytest.fixture +def ss(tmp_path, monkeypatch): + """ + Import systemd_stub fresh for every test, and provide fakes: + - run_nsenter: simulates a host FS + systemctl/docker calls + - container_fs: dict for "container" files + - host_fs: dict for "host" files + """ + if "systemd_stub" in sys.modules: + del sys.modules["systemd_stub"] + ss = importlib.import_module("systemd_stub") + + # Fake host filesystem and command recorder + host_fs = {} + commands = [] + + # Fake run_nsenter + def fake_run_nsenter(args, *, text=True, input_bytes=None): + commands.append(("nsenter", tuple(args))) + # /bin/cat + if args[:1] == ["/bin/cat"] and len(args) == 2: + path = args[1] + if path in host_fs: + out = host_fs[path] + return 0, (out if not text else out.decode("utf-8", "ignore")), b"" if not text else "" + return 1, b"" if not text else "", b"No such file" if text else b"No such file" + # /bin/sh -lc "cat > /tmp/xxx" + if args[:2] == ["/bin/sh", "-lc"] and len(args) == 3 and args[2].startswith("cat > "): + tmp_path = args[2].split("cat > ", 1)[1].strip() + host_fs[tmp_path] = input_bytes or (b"" if text else b"") + return 0, "" if text else b"", "" if text else b"" + # chmod / mkdir / mv / rm + if args[:1] == ["/bin/chmod"]: + return 0, "" if text else b"", "" if text else b"" + if args[:1] == ["/bin/mkdir"]: + return 0, "" if text else b"", "" if text else b"" + if args[:1] == ["/bin/mv"] and len(args) == 4: + src, dst = args[2], args[3] + host_fs[dst] = host_fs.get(src, b"") + host_fs.pop(src, None) + return 0, "" if text else b"", "" if text else b"" + if args[:1] == ["/bin/rm"]: + target = args[-1] + host_fs.pop(target, None) + return 0, "" if text else b"", "" if text else b"" + # sudo … + if args[:1] == ["sudo"]: + return 0, "" if text else b"", "" if text else b"" + return 1, "" if text else b"", "unsupported" if text else b"unsupported" + + monkeypatch.setattr(ss, "run_nsenter", fake_run_nsenter, raising=True) + + # Fake container FS + container_fs = {} + def fake_read_file_bytes_local(path: str): + return container_fs.get(path, None) + + monkeypatch.setattr(ss, "read_file_bytes_local", fake_read_file_bytes_local, raising=True) + + # Isolate POST_COPY_ACTIONS + monkeypatch.setattr(ss, "POST_COPY_ACTIONS", {}, raising=True) + + return ss, container_fs, host_fs, commands + + +def test_sha256_bytes_basic(): + if "systemd_stub" in sys.modules: + del sys.modules["systemd_stub"] + ss = importlib.import_module("systemd_stub") + assert ss.sha256_bytes(b"") == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + assert ss.sha256_bytes(None) == "" + assert ss.sha256_bytes(b"abc") == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" + + +def test_host_write_atomic_and_read(ss): + ss, container_fs, host_fs, commands = ss + ok = ss.host_write_atomic("/etc/testfile", b"hello", 0o755) + assert ok + data = ss.host_read_bytes("/etc/testfile") + assert data == b"hello" + cmd_names = [c[1][0] for c in commands] + assert "/bin/sh" in cmd_names + assert "/bin/chmod" in cmd_names + assert "/bin/mkdir" in cmd_names + assert "/bin/mv" in cmd_names + + +def test_sync_no_change_fast_path(ss): + ss, container_fs, host_fs, commands = ss + item = ss.SyncItem("/container/telemetry.sh", "/host/telemetry.sh", 0o755) + container_fs[item.src_in_container] = b"same" + host_fs[item.dst_on_host] = b"same" + ss.SYNC_ITEMS[:] = [item] + + ok = ss.ensure_sync() + assert ok is True + assert not any("/bin/sh" == c[1][0] and "-lc" in c[1] for c in commands) + + +def test_sync_updates_and_post_actions(ss): + ss, container_fs, host_fs, commands = ss + item = ss.SyncItem("/container/container_checker", "/bin/container_checker", 0o755) + container_fs[item.src_in_container] = b"NEW" + host_fs[item.dst_on_host] = b"OLD" + ss.SYNC_ITEMS[:] = [item] + + ss.POST_COPY_ACTIONS[item.dst_on_host] = [ + ["sudo", "systemctl", "daemon-reload"], + ["sudo", "systemctl", "restart", "monit"], + ] + + ok = ss.ensure_sync() + assert ok is True + assert host_fs[item.dst_on_host] == b"NEW" + + post_cmds = [args for _, args in commands if args and args[0] == "sudo"] + assert ("sudo", "systemctl", "daemon-reload") in post_cmds + assert ("sudo", "systemctl", "restart", "monit") in post_cmds + + +def test_sync_missing_src_returns_false(ss): + ss, container_fs, host_fs, commands = ss + item = ss.SyncItem("/container/missing.sh", "/usr/local/bin/telemetry.sh", 0o755) + ss.SYNC_ITEMS[:] = [item] + ok = ss.ensure_sync() + assert ok is False + + +def test_main_once_exits_zero_and_disables_post_actions(monkeypatch): + if "systemd_stub" in sys.modules: + del sys.modules["systemd_stub"] + ss = importlib.import_module("systemd_stub") + + ss.POST_COPY_ACTIONS["/bin/container_checker"] = [["sudo", "echo", "hi"]] + monkeypatch.setattr(ss, "ensure_sync", lambda: True, raising=True) + monkeypatch.setattr(sys, "argv", ["systemd_stub.py", "--once", "--no-post-actions"]) + + rc = ss.main() + assert rc == 0 + assert ss.POST_COPY_ACTIONS == {} + + +def test_main_once_exits_nonzero_when_sync_fails(monkeypatch): + if "systemd_stub" in sys.modules: + del sys.modules["systemd_stub"] + ss = importlib.import_module("systemd_stub") + monkeypatch.setattr(ss, "ensure_sync", lambda: False, raising=True) + monkeypatch.setattr(sys, "argv", ["systemd_stub.py", "--once"]) + rc = ss.main() + assert rc == 1 + + +def test_env_controls_telemetry_src_true(monkeypatch): + if "systemd_stub" in sys.modules: + del sys.modules["systemd_stub"] + monkeypatch.setenv("IS_V1_ENABLED", "true") + + ss = importlib.import_module("systemd_stub") + assert ss.IS_V1_ENABLED is True + assert ss._TELEMETRY_SRC.endswith("telemetry_v1.sh") + + +def test_env_controls_telemetry_src_false(monkeypatch): + if "systemd_stub" in sys.modules: + del sys.modules["systemd_stub"] + monkeypatch.setenv("IS_V1_ENABLED", "false") + + ss = importlib.import_module("systemd_stub") + assert ss.IS_V1_ENABLED is False + assert ss._TELEMETRY_SRC.endswith("telemetry.sh") + + +def test_env_controls_telemetry_src_default(monkeypatch): + if "systemd_stub" in sys.modules: + del sys.modules["systemd_stub"] + monkeypatch.delenv("IS_V1_ENABLED", raising=False) + + ss = importlib.import_module("systemd_stub") + assert ss.IS_V1_ENABLED is False + assert ss._TELEMETRY_SRC.endswith("telemetry.sh") diff --git a/dockers/docker-telemetry-sidecar/systemd_stub.py b/dockers/docker-telemetry-sidecar/systemd_stub.py new file mode 100644 index 0000000000..4986add886 --- /dev/null +++ b/dockers/docker-telemetry-sidecar/systemd_stub.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import os +import sys +import time +import argparse +import hashlib +import shlex +import subprocess +from dataclasses import dataclass +from typing import List, Optional, Tuple + +from sonic_py_common import logger as log +logger = log.Logger() + +def get_bool_env_var(name: str, default: bool = False) -> bool: + val = os.getenv(name) + if val is None: + return default + return val.strip().lower() in ("1", "true", "yes", "y", "on") + +IS_V1_ENABLED = get_bool_env_var("IS_V1_ENABLED", default=False) + +# ───────────── Config ───────────── +SYNC_INTERVAL_S = int(os.environ.get("SYNC_INTERVAL_S", "900")) # seconds +NSENTER_BASE = ["nsenter", "--target", "1", "--pid", "--mount", "--uts", "--ipc", "--net"] + +@dataclass(frozen=True) +class SyncItem: + src_in_container: str + dst_on_host: str + mode: int = 0o755 + + +_TELEMETRY_SRC = ( + "/usr/share/sonic/systemd_scripts/telemetry_v1.sh" + if IS_V1_ENABLED + else "/usr/share/sonic/systemd_scripts/telemetry.sh" +) +logger.log_notice(f"IS_V1_ENABLED={IS_V1_ENABLED}; telemetry source set to {_TELEMETRY_SRC}") + +SYNC_ITEMS: List[SyncItem] = [ + SyncItem(_TELEMETRY_SRC, "/usr/local/bin/telemetry.sh"), + SyncItem("/usr/share/sonic/systemd_scripts/container_checker", "/bin/container_checker"), +] + +POST_COPY_ACTIONS = { + "/usr/local/bin/telemetry.sh": [ + ["sudo", "docker", "stop", "telemetry"], + ["sudo", "docker", "rm", "telemetry"], + ["sudo", "systemctl", "daemon-reload"], + ["sudo", "systemctl", "restart", "telemetry"], + ], + "/bin/container_checker": [ + ["sudo", "systemctl", "daemon-reload"], + ["sudo", "systemctl", "restart", "monit"], + ], +} + + +def run(args: List[str], *, text: bool = True, input_bytes: Optional[bytes] = None) -> Tuple[int, str | bytes, str | bytes]: + logger.log_debug("Running: " + " ".join(args)) + p = subprocess.Popen( + args, + text=text, + stdin=subprocess.PIPE if input_bytes is not None else None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + out, err = p.communicate(input=input_bytes if input_bytes is not None else None) + return p.returncode, out, err + + +def run_nsenter(args: List[str], *, text: bool = True, input_bytes: Optional[bytes] = None) -> Tuple[int, str | bytes, str | bytes]: + return run(NSENTER_BASE + args, text=text, input_bytes=input_bytes) + + +def read_file_bytes_local(path: str) -> Optional[bytes]: + try: + with open(path, "rb") as f: + return f.read() + except OSError as e: # covers file-related errors incl. ENOENT, EACCES, EISDIR, etc. + logger.log_error(f"read failed for {path}: {e}") + return None + + +# ───────────── Host file ops via nsenter ───────────── +def host_read_bytes(path_on_host: str) -> Optional[bytes]: + # Use /bin/cat in host namespace + rc, out, _ = run_nsenter(["/bin/cat", path_on_host], text=False) + if rc != 0: + return None + return out + +def host_write_atomic(dst_on_host: str, data: bytes, mode: int) -> bool: + tmp_path = f"/tmp/{os.path.basename(dst_on_host)}.tmp" + + # 1) write bytes to host tmp via stdin + rc, _, err = run_nsenter(["/bin/sh", "-lc", f"cat > {shlex.quote(tmp_path)}"], text=False, input_bytes=data) + if rc != 0: + emsg = err.decode(errors="ignore") if isinstance(err, (bytes, bytearray)) else str(err) + logger.log_error(f"host write tmp failed: {emsg.strip()}") + return False + + # 2) chmod tmp on host + rc, _, err = run_nsenter(["/bin/chmod", f"{mode:o}", tmp_path], text=True) + if rc != 0: + logger.log_error(f"host chmod failed: {str(err).strip()}") + run_nsenter(["/bin/rm", "-f", tmp_path], text=True) + return False + + # 3) ensure parent dir exists on host + parent = os.path.dirname(dst_on_host) or "/" + rc, _, err = run_nsenter(["/bin/mkdir", "-p", parent], text=True) + if rc != 0: + logger.log_error(f"host mkdir failed for {parent}: {str(err).strip()}") + run_nsenter(["/bin/rm", "-f", tmp_path], text=True) + return False + + # 4) atomic replace on host + rc, _, err = run_nsenter(["/bin/mv", "-f", tmp_path, dst_on_host], text=True) + if rc != 0: + logger.log_error(f"host mv failed to {dst_on_host}: {str(err).strip()}") + run_nsenter(["/bin/rm", "-f", tmp_path], text=True) + return False + + return True + +def run_host_actions_for(path_on_host: str) -> None: + actions = POST_COPY_ACTIONS.get(path_on_host, []) + for cmd in actions: + rc, _, err = run_nsenter(cmd, text=True) + if rc == 0: + logger.log_info(f"Post-copy action succeeded: {' '.join(cmd)}") + else: + logger.log_error(f"Post-copy action FAILED (rc={rc}): {' '.join(cmd)}; stderr={str(err).strip()}") + + +# ───────────── file Sync logic ───────────── +def sha256_bytes(b: Optional[bytes]) -> str: + if b is None: + return "" + h = hashlib.sha256() + h.update(b) + return h.hexdigest() + +def sync_items(items: List[SyncItem]) -> bool: + all_ok = True + for item in items: + src_bytes = read_file_bytes_local(item.src_in_container) + if src_bytes is None: + logger.log_error(f"Cannot read {item.src_in_container} in this container") + all_ok = False + continue + + container_file_sha = sha256_bytes(src_bytes) + host_bytes = host_read_bytes(item.dst_on_host) + host_sha = sha256_bytes(host_bytes) + + if host_sha == container_file_sha: + logger.log_info(f"{os.path.basename(item.dst_on_host)} up-to-date (sha256={host_sha})") + continue + + logger.log_info( + f"{os.path.basename(item.dst_on_host)} differs " + f"(container {container_file_sha} vs host {host_sha or 'missing'}), updating…" + ) + if not host_write_atomic(item.dst_on_host, src_bytes, item.mode): + logger.log_error(f"Copy/update failed for {item.dst_on_host}") + all_ok = False + continue + + # verify + new_host_bytes = host_read_bytes(item.dst_on_host) + new_sha = sha256_bytes(new_host_bytes) + if new_sha != container_file_sha: + logger.log_error( + f"Post-copy SHA mismatch for {item.dst_on_host}: host {new_sha or 'read-failed'} vs container {container_file_sha}" + ) + all_ok = False + else: + logger.log_info(f"Sync complete for {item.dst_on_host} (sha256={new_sha})") + run_host_actions_for(item.dst_on_host) + + return all_ok + +def ensure_sync() -> bool: + return sync_items(SYNC_ITEMS) + + +def parse_args() -> argparse.Namespace: + p = argparse.ArgumentParser(description="Sync host scripts from this container to the host via nsenter (syslog logging).") + p.add_argument("--once", action="store_true", help="Run one sync pass and exit") + p.add_argument("--interval", type=int, default=SYNC_INTERVAL_S, help=f"Loop interval seconds (default: {SYNC_INTERVAL_S})") + p.add_argument("--no-post-actions", action="store_true", help="(Optional) Skip host systemctl actions (for debugging)") + return p.parse_args() + +def main() -> int: + args = parse_args() + + if args.no_post_actions: + POST_COPY_ACTIONS.clear() + logger.log_info("Post-copy host actions DISABLED for this run") + + ok = ensure_sync() + if args.once: + return 0 if ok else 1 + + while True: + time.sleep(args.interval) + ok = ensure_sync() + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/files/image_config/monit/container_checker b/files/image_config/monit/container_checker index 28dd26e7db..331dded370 100755 --- a/files/image_config/monit/container_checker +++ b/files/image_config/monit/container_checker @@ -158,7 +158,6 @@ def get_current_running_from_DB(always_running_containers): return running_containers - def get_current_running_from_dockers(): """ @summary: This function will get all running containers from @@ -172,13 +171,16 @@ def get_current_running_from_dockers(): try: lst = ctrs.list(filters={"status": "running"}) for ctr in lst: - running_containers.add(ctr.name) + # Prefer raw_container_name label over actual name + if ctr.labels and "raw_container_name" in ctr.labels: + running_containers.add(ctr.labels["raw_container_name"]) + else: + running_containers.add(ctr.name) except docker.errors.APIError as err: - print("Failed to retrieve the running container list. Error: '{}'".format(err)) + print(f"Failed to retrieve the running container list. Error: '{err}'") pass return running_containers - def get_current_running_containers(always_running_containers): """ @summary: This function will get the list of currently running containers. diff --git a/rules/docker-telemetry-sidecar.dep b/rules/docker-telemetry-sidecar.dep new file mode 100644 index 0000000000..90a59fd1fa --- /dev/null +++ b/rules/docker-telemetry-sidecar.dep @@ -0,0 +1,10 @@ +DPATH := $($(DOCKER_TELEMETRY_SIDECAR)_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/docker-telemetry-sidecar.mk rules/docker-telemetry-sidecar.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(shell git ls-files $(DPATH)) + +$(DOCKER_TELEMETRY_SIDECAR)_CACHE_MODE := GIT_CONTENT_SHA +$(DOCKER_TELEMETRY_SIDECAR)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(DOCKER_TELEMETRY_SIDECAR)_DEP_FILES := $(DEP_FILES) + +$(eval $(call add_dbg_docker,$(DOCKER_TELEMETRY_SIDECAR),$(DOCKER_TELEMETRY_SIDECAR_DBG))) diff --git a/rules/docker-telemetry-sidecar.mk b/rules/docker-telemetry-sidecar.mk new file mode 100644 index 0000000000..62571c3afd --- /dev/null +++ b/rules/docker-telemetry-sidecar.mk @@ -0,0 +1,38 @@ +# docker image for docker-telemetry-sidecar + +DOCKER_TELEMETRY_SIDECAR_STEM = docker-telemetry-sidecar +DOCKER_TELEMETRY_SIDECAR = $(DOCKER_TELEMETRY_SIDECAR_STEM).gz +DOCKER_TELEMETRY_SIDECAR_DBG = $(DOCKER_TELEMETRY_SIDECAR_STEM)-$(DBG_IMAGE_MARK).gz + +$(DOCKER_TELEMETRY_SIDECAR)_LOAD_DOCKERS = $(DOCKER_CONFIG_ENGINE_BOOKWORM) + +$(DOCKER_TELEMETRY_SIDECAR)_PATH = $(DOCKERS_PATH)/$(DOCKER_TELEMETRY_SIDECAR_STEM) + +$(DOCKER_TELEMETRY_SIDECAR)_VERSION = 1.0.0 +$(DOCKER_TELEMETRY_SIDECAR)_PACKAGE_NAME = telemetry-sidecar + +SONIC_DOCKER_IMAGES += $(DOCKER_TELEMETRY_SIDECAR) +SONIC_BOOKWORM_DOCKERS += $(DOCKER_TELEMETRY_SIDECAR) +SONIC_INSTALL_DOCKER_IMAGES += $(DOCKER_TELEMETRY_SIDECAR) + +SONIC_DOCKER_DBG_IMAGES += $(DOCKER_TELEMETRY_SIDECAR_DBG) +SONIC_BOOKWORM_DBG_DOCKERS += $(DOCKER_TELEMETRY_SIDECAR_DBG) +SONIC_INSTALL_DOCKER_DBG_IMAGES += $(DOCKER_TELEMETRY_SIDECAR_DBG) + +$(DOCKER_TELEMETRY_SIDECAR)_CONTAINER_NAME = telemetry-sidecar +$(DOCKER_TELEMETRY_SIDECAR)_RUN_OPT += -t --privileged --pid=host +$(DOCKER_TELEMETRY_SIDECAR)_RUN_OPT += -v /lib/systemd/system:/lib/systemd/system:rw +$(DOCKER_TELEMETRY_SIDECAR)_RUN_OPT += -v /etc/audit:/etc/audit:rw +$(DOCKER_TELEMETRY_SIDECAR)_RUN_OPT += -v /etc/sonic:/etc/sonic:ro +$(DOCKER_TELEMETRY_SIDECAR)_RUN_OPT += -v /etc/localtime:/etc/localtime:ro + +$(DOCKER_TELEMETRY_SIDECAR)_FILES += $(CONTAINER_CHECKER) +$(DOCKER_TELEMETRY_SIDECAR)_FILES += $(TELEMETRY_SYSTEMD) + +.PHONY: docker-telemetry-sidecar-ut +docker-telemetry-sidecar-ut: + @echo "Running unit tests for systemd_stub.py..." + @PYTHONPATH=dockers/docker-telemetry-sidecar \ + python3 -m pytest -q dockers/docker-telemetry-sidecar/systemd_scripts/tests + +target/docker-telemetry-sidecar.gz: docker-telemetry-sidecar-ut diff --git a/rules/scripts.mk b/rules/scripts.mk index 12919d520b..526d26ea59 100644 --- a/rules/scripts.mk +++ b/rules/scripts.mk @@ -23,6 +23,12 @@ $(SUPERVISOR_PROC_EXIT_LISTENER_SCRIPT)_PATH = files/scripts SYSCTL_NET_CONFIG = sysctl-net.conf $(SYSCTL_NET_CONFIG)_PATH = files/image_config/sysctl +CONTAINER_CHECKER = container_checker +$(CONTAINER_CHECKER)_PATH = files/image_config/monit + +TELEMETRY_SYSTEMD = telemetry.sh +$(TELEMETRY_SYSTEMD)_PATH = files/scripts + UPDATE_CHASSISDB_CONFIG_SCRIPT = update_chassisdb_config $(UPDATE_CHASSISDB_CONFIG_SCRIPT)_PATH = files/scripts @@ -43,6 +49,8 @@ SONIC_COPY_FILES += $(CONFIGDB_LOAD_SCRIPT) \ $(CBF_CONFIG_TEMPLATE) \ $(SUPERVISOR_PROC_EXIT_LISTENER_SCRIPT) \ $(SYSCTL_NET_CONFIG) \ + $(CONTAINER_CHECKER) \ + $(TELEMETRY_SYSTEMD) \ $(UPDATE_CHASSISDB_CONFIG_SCRIPT) \ $(SWSS_VARS_TEMPLATE) \ $(RSYSLOG_PLUGIN_CONF_J2) \