Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5885e8f
Triggering PR. Not to commit
yue-fred-gao Feb 5, 2026
e4b67d1
Merge branch 'master' into test_vpp-acl-multi-table
yue-fred-gao Feb 10, 2026
a4058d9
Update .gitmodules
yue-fred-gao Feb 17, 2026
4b928dd
Merge branch 'sonic-net:master' into test_vpp-acl-multi-table
yue-fred-gao Mar 13, 2026
b01ed1b
Experiment with bulk operation
yue-fred-gao Mar 13, 2026
040679e
arp passthrough pr checker
yue-fred-gao Mar 16, 2026
c2ee912
Relocate vpp.mk to platform/vpp
yue-fred-gao Mar 6, 2026
5906039
change platform/vpp to master
yue-fred-gao Mar 16, 2026
60a75ed
Add TELEMETRY_WATCHDOG_CERT_PROBE_ENABLED function into watchdog and …
FengPan-Frank Mar 13, 2026
20355ef
Enhance lldpd.conf.j2 for IPv6 support (#24468)
abdosi Mar 14, 2026
deaa28f
[build] Add ccache support for C/C++ compilation (SONIC_CONFIG_USE_CC…
rustiqly Mar 14, 2026
8417a09
[submodule] Update submodule sonic-platform-daemons to the latest HEA…
mssonicbld Mar 14, 2026
97b0f93
[submodule] Update submodule sonic-mgmt-common to the latest HEAD aut…
mssonicbld Mar 14, 2026
bdb1d9d
[submodule] Update submodule sonic-gnmi to the latest HEAD automatica…
mssonicbld Mar 14, 2026
5e8d30b
[submodule] Update submodule sonic-bmp to the latest HEAD automatical…
mssonicbld Mar 14, 2026
215c33d
[submodule] Update submodule sonic-swss to the latest HEAD automatica…
mssonicbld Mar 14, 2026
d42e612
[submodule] Update submodule sonic-swss to the latest HEAD automatica…
mssonicbld Mar 15, 2026
4c4b9fb
[submodule] Update submodule sonic-gnmi to the latest HEAD automatica…
mssonicbld Mar 15, 2026
b4e0177
Include generated services in Sysmonitor (#25561)
william8545 Mar 15, 2026
6929cb8
[ci/build]: Upgrade SONiC package versions (#26193)
mssonicbld Mar 15, 2026
1ecc895
BROADCOM_LEGACY_SAI_COMPAT: Allow platforms to disable sai_query_stat…
lipxu Mar 16, 2026
8f67dff
Fix parallel build issue (#26142)
pavannaregundi Mar 16, 2026
b30f9e5
[submodule] Update submodule sonic-utilities to the latest HEAD autom…
mssonicbld Mar 16, 2026
ffccd20
Relocate vpp.mk to platform/vpp
yue-fred-gao Mar 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
url = https://github.com/sonic-net/sonic-linux-kernel
[submodule "sonic-sairedis"]
path = src/sonic-sairedis
url = https://github.com/sonic-net/sonic-sairedis
url = https://github.com/yue-fred-gao/sonic-sairedis
branch = arp_passthrough
[submodule "sonic-swss"]
path = src/sonic-swss
url = https://github.com/sonic-net/sonic-swss
Expand Down Expand Up @@ -129,7 +130,8 @@
url = https://github.com/Marvell-switching/sonic-platform-marvell.git
[submodule "platform/vpp"]
path = platform/vpp
url = https://github.com/sonic-net/sonic-platform-vpp.git
url = https://github.com/yue-fred-gao/sonic-platform-vpp.git
branch = relocate_vpp_mk
[submodule "platform/marvell-prestera/mrvl-prestera"]
path = platform/marvell-prestera/mrvl-prestera
url = https://github.com/Marvell-switching/mrvl-prestera.git
Expand Down
4 changes: 2 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ resources:
repositories:
- repository: sonic-mgmt
type: github
name: sonic-net/sonic-mgmt
ref: master
name: yue-fred-gao/sonic-mgmt
ref: enable_vpp_pr_checker
endpoint: sonic-net
- repository: buildimage
type: github
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
SAI_INIT_CONFIG_FILE=/usr/share/sonic/hwsku/th-a7060-cx32s-32x100G-t1.config.bcm
SAI_NUM_ECMP_MEMBERS=64
# BROADCOM_LEGACY_SAI_COMPAT: TH1 (BCM56960) has no streaming telemetry platform driver;
# sai_query_stats_st_capability crashes in brcm_sai_st_pd_ctr_cap_list_get.
SAI_STATS_ST_CAPABILITY_SUPPORTED=0
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
SAI_INIT_CONFIG_FILE=/usr/share/sonic/hwsku/th-a7060-cx32s-8x100G+48x50G.config.bcm
SAI_NUM_ECMP_MEMBERS=64
# BROADCOM_LEGACY_SAI_COMPAT: TH1 (BCM56960) has no streaming telemetry platform driver;
# sai_query_stats_st_capability crashes in brcm_sai_st_pd_ctr_cap_list_get.
SAI_STATS_ST_CAPABILITY_SUPPORTED=0
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
SAI_INIT_CONFIG_FILE=/usr/share/sonic/hwsku/th-a7060-cx32s-8x100G+24x40G.config.bcm
SAI_NUM_ECMP_MEMBERS=64
# BROADCOM_LEGACY_SAI_COMPAT: TH1 (BCM56960) has no streaming telemetry platform driver;
# sai_query_stats_st_capability crashes in brcm_sai_st_pd_ctr_cap_list_get.
SAI_STATS_ST_CAPABILITY_SUPPORTED=0
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
SAI_INIT_CONFIG_FILE=/usr/share/sonic/hwsku/th-a7060-cx32s-8x100G+96x25G.config.bcm
SAI_NUM_ECMP_MEMBERS=64
# BROADCOM_LEGACY_SAI_COMPAT: TH1 (BCM56960) has no streaming telemetry platform driver;
# sai_query_stats_st_capability crashes in brcm_sai_st_pd_ctr_cap_list_get.
SAI_STATS_ST_CAPABILITY_SUPPORTED=0
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
SAI_INIT_CONFIG_FILE=/usr/share/sonic/platform/th-a7060-cx32s-flex-all.config.bcm
# BROADCOM_LEGACY_SAI_COMPAT: TH1 (BCM56960) has no streaming telemetry platform driver;
# sai_query_stats_st_capability crashes in brcm_sai_st_pd_ctr_cap_list_get.
SAI_STATS_ST_CAPABILITY_SUPPORTED=0
8 changes: 8 additions & 0 deletions dockers/docker-lldp/lldpd.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
{% if mgmt_if.update({'port_name' : mgmt_name}) %} {% endif %}
{% if mgmt_if.update({'ipv4' : mgmt_prefix|ip}) %} {% endif %}
{% endif %}
{% if mgmt_prefix|ipv6 and (mgmt_if.ipv4 is not defined) %}
{% if mgmt_if.update({'port_name' : mgmt_name}) %} {% endif %}
{% if mgmt_if.update({'ipv6' : mgmt_prefix|ip}) %} {% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% if mgmt_if %}
Expand All @@ -16,7 +20,11 @@ configure ports eth0 lldp portidsubtype local {{ MGMT_PORT[mgmt_if.port_name].al
configure ports eth0 lldp portidsubtype local {{ mgmt_if.port_name }}
{% endif %}
{% endif %}
{% if mgmt_if.ipv4 %}
configure system ip management pattern {{ mgmt_if.ipv4 }}
{% elif mgmt_if.ipv6 %}
configure system ip management pattern {{ mgmt_if.ipv6 }}
{% endif %}
{% endif %}
configure system hostname {{ DEVICE_METADATA['localhost']['hostname'] }}
{# pause lldpd operations until all interfaces are well configured, resume command will run in lldpmgrd #}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,31 +342,143 @@ def test_reconcile_enables_user_auth_and_cname(ss):
ss, container_fs, host_fs, commands, config_db = ss
# Set module-level flags directly (they're read inside reconcile)
ss.GNMI_VERIFY_ENABLED = True
ss.GNMI_CLIENT_CNAME = "AME Infra CA o6"
ss.GNMI_CLIENT_CERTS = [{"cname": "fake-infra-ca.test.example.com", "role": "gnmi_show_readonly"}]

# Precondition: empty DB
assert config_db == {}

ss.reconcile_config_db_once()

# user_auth must be set to 'cert'
assert config_db.get("GNMI|gnmi", {}).get("user_auth") == "cert"
# CNAME hash must exist with role=gnmi_show_readonly (default GNMI_CLIENT_ROLE)
cname_key = f"GNMI_CLIENT_CERT|{ss.GNMI_CLIENT_CNAME}"
assert config_db.get(cname_key, {}).get("role") == "gnmi_show_readonly"
assert config_db.get("TELEMETRY|gnmi", {}).get("user_auth") == "cert"
# CNAME hash must exist with role=gnmi_show_readonly
assert config_db.get("GNMI_CLIENT_CERT|fake-infra-ca.test.example.com", {}).get("role") == "gnmi_show_readonly"


def test_reconcile_disabled_removes_cname(ss):
ss, container_fs, host_fs, commands, config_db = ss
ss.GNMI_VERIFY_ENABLED = False
ss.GNMI_CLIENT_CNAME = "AME Infra CA o6"
ss.GNMI_CLIENT_CERTS = [{"cname": "fake-infra-ca.test.example.com", "role": "gnmi_show_readonly"}]

# Seed an existing entry to be removed
config_db[f"GNMI_CLIENT_CERT|{ss.GNMI_CLIENT_CNAME}"] = {"role": "gnmi_show_readonly"}
config_db["GNMI_CLIENT_CERT|fake-infra-ca.test.example.com"] = {"role": "gnmi_show_readonly"}

ss.reconcile_config_db_once()

assert f"GNMI_CLIENT_CERT|{ss.GNMI_CLIENT_CNAME}" not in config_db
assert "GNMI_CLIENT_CERT|fake-infra-ca.test.example.com" not in config_db

def test_reconcile_multiple_cnames(ss):
ss, container_fs, host_fs, commands, config_db = ss
ss.GNMI_VERIFY_ENABLED = True
ss.GNMI_CLIENT_CERTS = [
{"cname": "fake-client.test.example.com", "role": "admin"},
{"cname": "fake-server.test.example.com", "role": '["gnmi_show_readonly","admin"]'},
]
assert config_db == {}
ss.reconcile_config_db_once()

assert config_db.get("TELEMETRY|gnmi", {}).get("user_auth") == "cert"
assert config_db.get("GNMI_CLIENT_CERT|fake-client.test.example.com", {}).get("role") == "admin"
assert config_db.get("GNMI_CLIENT_CERT|fake-server.test.example.com", {}).get("role") == '["gnmi_show_readonly","admin"]'

# ─────────────────────────── Tests for _parse_client_certs ───────────────────────────

class TestParseClientCerts:
"""Tests for _parse_client_certs() env-var parsing."""

@pytest.fixture(autouse=True)
def _fresh_module(self, monkeypatch):
if "systemd_stub" in sys.modules:
del sys.modules["systemd_stub"]
self.monkeypatch = monkeypatch

def _import_with_env(self, env_vars):
"""Set env vars, re-import systemd_stub, and return the parsed GNMI_CLIENT_CERTS."""
for k, v in env_vars.items():
if v is None:
self.monkeypatch.delenv(k, raising=False)
else:
self.monkeypatch.setenv(k, v)
# Clear stale env vars not in the dict
for k in ("GNMI_CLIENT_CERTS", "TELEMETRY_CLIENT_CNAME", "GNMI_CLIENT_ROLE"):
if k not in env_vars:
self.monkeypatch.delenv(k, raising=False)
if "systemd_stub" in sys.modules:
del sys.modules["systemd_stub"]
ss = importlib.import_module("systemd_stub")
return ss.GNMI_CLIENT_CERTS

def test_valid_json_array(self):
certs = self._import_with_env({
"GNMI_CLIENT_CERTS": '[{"cname": "client.gbl", "role": "admin"}]'
})
assert certs == [{"cname": "client.gbl", "role": "admin"}]

def test_valid_json_multiple_entries(self):
certs = self._import_with_env({
"GNMI_CLIENT_CERTS": '[{"cname": "a.gbl", "role": "admin"}, {"cname": "b.gbl", "role": "readonly"}]'
})
assert len(certs) == 2
assert certs[0] == {"cname": "a.gbl", "role": "admin"}
assert certs[1] == {"cname": "b.gbl", "role": "readonly"}

def test_non_array_json_falls_back_to_legacy(self):
certs = self._import_with_env({
"GNMI_CLIENT_CERTS": '{"cname": "c.gbl", "role": "admin"}',
"TELEMETRY_CLIENT_CNAME": "legacy.gbl",
"GNMI_CLIENT_ROLE": "readonly",
})
assert certs == [{"cname": "legacy.gbl", "role": "readonly"}]

def test_invalid_json_falls_back_to_legacy(self):
certs = self._import_with_env({
"GNMI_CLIENT_CERTS": "not-json!",
"TELEMETRY_CLIENT_CNAME": "fallback.gbl",
})
assert certs == [{"cname": "fallback.gbl", "role": "gnmi_show_readonly"}]

def test_entry_not_dict_falls_back(self):
certs = self._import_with_env({
"GNMI_CLIENT_CERTS": '["not-a-dict"]',
"TELEMETRY_CLIENT_CNAME": "fb.gbl",
})
assert certs == [{"cname": "fb.gbl", "role": "gnmi_show_readonly"}]

def test_entry_missing_role_falls_back(self):
certs = self._import_with_env({
"GNMI_CLIENT_CERTS": '[{"cname": "x.gbl"}]',
"TELEMETRY_CLIENT_CNAME": "fb.gbl",
})
assert certs == [{"cname": "fb.gbl", "role": "gnmi_show_readonly"}]

def test_entry_empty_cname_falls_back(self):
certs = self._import_with_env({
"GNMI_CLIENT_CERTS": '[{"cname": " ", "role": "admin"}]',
"TELEMETRY_CLIENT_CNAME": "fb.gbl",
})
assert certs == [{"cname": "fb.gbl", "role": "gnmi_show_readonly"}]

def test_legacy_single_entry(self):
certs = self._import_with_env({
"TELEMETRY_CLIENT_CNAME": "legacy.gbl",
"GNMI_CLIENT_ROLE": "admin",
})
assert certs == [{"cname": "legacy.gbl", "role": "admin"}]

def test_legacy_default_role(self):
certs = self._import_with_env({
"TELEMETRY_CLIENT_CNAME": "legacy.gbl",
})
assert certs == [{"cname": "legacy.gbl", "role": "gnmi_show_readonly"}]

def test_no_env_returns_empty(self):
certs = self._import_with_env({})
assert certs == []

def test_whitespace_stripped(self):
certs = self._import_with_env({
"GNMI_CLIENT_CERTS": '[{"cname": " client.gbl ", "role": " admin "}]'
})
assert certs == [{"cname": "client.gbl", "role": "admin"}]


# ─────────────────────────── Tests for _get_branch_name ───────────────────────────
Expand Down
77 changes: 54 additions & 23 deletions dockers/docker-telemetry-sidecar/systemd_stub.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#!/usr/bin/env python3
from __future__ import annotations

import json
import os
import re
import subprocess
import time
import argparse
from typing import List
from typing import Dict, List

from sonic_py_common.sidecar_common import (
get_bool_env_var, logger, SyncItem,
Expand All @@ -21,11 +22,46 @@

# CONFIG_DB reconcile env
GNMI_VERIFY_ENABLED = get_bool_env_var("TELEMETRY_CLIENT_CERT_VERIFY_ENABLED", default=False)
GNMI_CLIENT_CNAME = os.getenv("TELEMETRY_CLIENT_CNAME", "")
GNMI_CLIENT_ROLE = os.getenv("GNMI_CLIENT_ROLE", "gnmi_show_readonly")
def _parse_client_certs() -> List[Dict[str, str]]:
"""
Build the list of GNMI client cert entries from env vars.

Preferred: GNMI_CLIENT_CERTS (JSON array of {"cname": ..., "role": ...})
Fallback: TELEMETRY_CLIENT_CNAME / GNMI_CLIENT_ROLE (single entry, backward-compat)
"""
raw = os.getenv("GNMI_CLIENT_CERTS", "").strip()
if raw:
try:
entries = json.loads(raw)
if not isinstance(entries, list):
raise ValueError("GNMI_CLIENT_CERTS must be a JSON array")
normalized: List[Dict[str, str]] = []
for e in entries:
if not isinstance(e, dict):
raise ValueError(f"Each entry must be an object: {e!r}")
if "cname" not in e or "role" not in e:
raise ValueError(f"Each entry needs 'cname' and 'role': {e}")
cname = str(e.get("cname", "")).strip()
role = str(e.get("role", "")).strip()
if not cname or not role:
raise ValueError(f"'cname' and 'role' must be non-empty strings: {e}")
normalized.append({"cname": cname, "role": role})
return normalized
except (json.JSONDecodeError, ValueError) as exc:
logger.log_error(f"Bad GNMI_CLIENT_CERTS env var: {exc}; falling back to legacy")

# Legacy single-entry env vars
cname = os.getenv("TELEMETRY_CLIENT_CNAME", "").strip()
role = os.getenv("GNMI_CLIENT_ROLE", "gnmi_show_readonly").strip()
if cname:
return [{"cname": cname, "role": role}]
return []


GNMI_CLIENT_CERTS: List[Dict[str, str]] = _parse_client_certs()

logger.log_notice(f"IS_V1_ENABLED={IS_V1_ENABLED}")
logger.log_notice(f"GNMI_CLIENT_ROLE={GNMI_CLIENT_ROLE}")
logger.log_notice(f"GNMI_CLIENT_CERTS={GNMI_CLIENT_CERTS}")

_TELEMETRY_SRC = (
"/usr/share/sonic/systemd_scripts/telemetry_v1.sh"
Expand Down Expand Up @@ -134,52 +170,47 @@ def _get_branch_name() -> str:


def _ensure_user_auth_cert() -> None:
cur = db_hget("GNMI|gnmi", "user_auth")
cur = db_hget("TELEMETRY|gnmi", "user_auth")
if cur != "cert":
if db_hset("GNMI|gnmi", "user_auth", "cert"):
logger.log_notice(f"Set GNMI|gnmi.user_auth=cert (was: {cur or '<unset>'})")
if db_hset("TELEMETRY|gnmi", "user_auth", "cert"):
logger.log_notice(f"Set TELEMETRY|gnmi.user_auth=cert (was: {cur or '<unset>'})")
else:
logger.log_error("Failed to set GNMI|gnmi.user_auth=cert")
logger.log_error("Failed to set TELEMETRY|gnmi.user_auth=cert")


def _ensure_cname_present(cname: str) -> None:
if not cname:
logger.log_warning("TELEMETRY_CLIENT_CNAME not set; skip CNAME creation")
return

def _ensure_cname_present(cname: str, role: str) -> None:
key = f"GNMI_CLIENT_CERT|{cname}"
entry = db_hgetall(key)
if not entry:
if db_hset(key, "role", GNMI_CLIENT_ROLE):
logger.log_notice(f"Created {key} with role={GNMI_CLIENT_ROLE}")
if db_hset(key, "role", role):
logger.log_notice(f"Created {key} with role={role}")
else:
logger.log_error(f"Failed to create {key}")


def _ensure_cname_absent(cname: str) -> None:
if not cname:
return
key = f"GNMI_CLIENT_CERT|{cname}"
if db_hgetall(key):
if db_del(key):
logger.log_notice(f"Removed {key}")
else:
logger.log_error(f"Failed to remove {key}")


def reconcile_config_db_once() -> None:
"""
Idempotent drift-correction for CONFIG_DB:
- When TELEMETRY_CLIENT_CERT_VERIFY_ENABLED=true:
* Ensure GNMI|gnmi.user_auth=cert
* Ensure GNMI_CLIENT_CERT|<CNAME> exists with role=<GNMI_CLIENT_ROLE>
- When false: ensure the CNAME row is absent
* Ensure TELEMETRY|gnmi.user_auth=cert
* Ensure every GNMI_CLIENT_CERT|<CNAME> entry exists with its role
- When false: ensure all CNAME rows are absent
"""
if GNMI_VERIFY_ENABLED:
_ensure_user_auth_cert()
_ensure_cname_present(GNMI_CLIENT_CNAME)
for entry in GNMI_CLIENT_CERTS:
_ensure_cname_present(entry["cname"], entry["role"])
else:
_ensure_cname_absent(GNMI_CLIENT_CNAME)
for entry in GNMI_CLIENT_CERTS:
_ensure_cname_absent(entry["cname"])

# Host destination for service_checker.py
HOST_SERVICE_CHECKER = "/usr/local/lib/python3.11/dist-packages/health_checker/service_checker.py"
Expand Down
Loading
Loading