Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 12 additions & 7 deletions tests/common/helpers/gnmi_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,12 @@ def create_revoked_cert_and_crl(localhost, ptfhost, duthost=None):
localhost.shell(local_command)


def create_gnmi_certs(duthost, localhost, ptfhost):
def create_gnmi_certs(duthost, localhost, ptfhost, dut_ip=None):
'''
Create GNMI client certificates
'''
prepare_root_cert(localhost)
prepare_server_cert(duthost, localhost)
prepare_server_cert(duthost, localhost, dut_ip=dut_ip)
prepare_client_cert(localhost)
create_revoked_cert_and_crl(localhost, ptfhost)
copy_certificate_to_dut(duthost)
Expand Down Expand Up @@ -351,10 +351,10 @@ def create_root_cert(localhost, days):
_write_pem_certificate("gnmiCA.pem", cert)


def prepare_server_cert(duthost, localhost, days="825"):
def prepare_server_cert(duthost, localhost, days="825", dut_ip=None):
create_server_key(localhost)
create_server_csr(localhost)
sign_server_certificate(duthost, localhost, days)
sign_server_certificate(duthost, localhost, days, dut_ip=dut_ip)


def create_server_key(localhost):
Expand All @@ -371,15 +371,20 @@ def create_server_csr(localhost):
localhost.shell(local_command)


def sign_server_certificate(duthost, localhost, days):
"""Sign gnmiserver.csr with the CA, backdated, with SAN (hostname.com + DUT mgmt IP)."""
def sign_server_certificate(duthost, localhost, days, dut_ip=None):
"""Sign gnmiserver.csr with the CA, backdated, with SAN (hostname.com + DUT mgmt IP).

When dut_ip is provided, it is used as the SAN IP address instead of
duthost.mgmt_ip. This lets callers bind the cert to the address the
gnmi server is actually reachable at (e.g. when bound to a non-default VRF).
"""
ca_cert = _load_pem_certificate("gnmiCA.pem")
ca_key = _load_pem_private_key("gnmiCA.key")
csr = _load_pem_csr("gnmiserver.csr")
not_before, not_after = _cert_validity_period(days)
san_entries = [
x509.DNSName("hostname.com"),
x509.IPAddress(ipaddress.ip_address(duthost.mgmt_ip)),
x509.IPAddress(ipaddress.ip_address(dut_ip or duthost.mgmt_ip)),
]
cert = (
x509.CertificateBuilder()
Expand Down
2 changes: 2 additions & 0 deletions tests/common/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
# Wait 300 seconds because sometime 'interfaces-config' service take 45 seconds to response
# interfaces-config service issue track by: https://github.com/sonic-net/sonic-buildimage/issues/19045
FILE_CHANGE_TIMEOUT = 300
DEFAULT_VRF_NAME = "default"
MGMT_VRF_NAME = "mgmt"

NON_USER_CONFIG_TABLES = ["FLEX_COUNTER_TABLE", "ASIC_SENSORS", "LOGGER"]

Expand Down
27 changes: 23 additions & 4 deletions tests/gnmi/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from tests.common.helpers.assertions import pytest_require as pyrequire
from tests.common.helpers.dut_utils import check_container_state
from tests.gnmi.helper import gnmi_container, apply_cert_config, recover_cert_config
from tests.common.helpers.gnmi_utils import gnmi_container
from tests.gnmi.helper import apply_cert_config, recover_cert_config
from tests.gnmi.helper import GNMI_SERVER_START_WAIT_TIME, check_ntp_sync_status
from tests.common.gu_utils import create_checkpoint, rollback
from tests.common.helpers.gnmi_utils import create_revoked_cert_and_crl, create_gnmi_certs, \
Expand All @@ -15,6 +16,24 @@
logger = logging.getLogger(__name__)
SETUP_ENV_CP = "test_setup_checkpoint"

VRF_SCENARIOS = [
{"name": "default_1", "vrf": None, "description": "Default (no VRF)"},
]


@pytest.fixture(scope="module", params=VRF_SCENARIOS, ids=lambda scenario: f"vrf_{scenario['name']}")
def vrf_config(request):
return request.param


@pytest.fixture(scope="module", autouse=True)
def setup_vrf_configuration(vrf_config):
"""
This fixture runs before setup_gnmi_server to ensure VRF config is in place.
It gets overridden in tests that parameterize the gNMI server VRF binding.
"""
return vrf_config


@pytest.fixture(scope="module")
def setup_gnmi_ntp_client_server(duthosts, rand_one_dut_hostname, ptfhost):
Expand Down Expand Up @@ -42,7 +61,7 @@ def setup_gnmi_ntp_client_server(duthosts, rand_one_dut_hostname, ptfhost):


@pytest.fixture(scope="module")
def setup_gnmi_server(duthosts, rand_one_dut_hostname, localhost, ptfhost):
def setup_gnmi_server(duthosts, rand_one_dut_hostname, localhost, ptfhost, vrf_config, setup_vrf_configuration):
'''
Setup GNMI server with client certificates
'''
Expand All @@ -53,10 +72,10 @@ def setup_gnmi_server(duthosts, rand_one_dut_hostname, localhost, ptfhost):
check_container_state(duthost, gnmi_container(duthost), should_be_running=True),
"Test was not supported on devices which do not support GNMI!")

create_gnmi_certs(duthost, localhost, ptfhost)
create_gnmi_certs(duthost, localhost, ptfhost, dut_ip=vrf_config.get("dut_ip"))

create_checkpoint(duthost, SETUP_ENV_CP)
stopped_programs = apply_cert_config(duthost)
stopped_programs = apply_cert_config(duthost, vrf_config.get("vrf"))

yield

Expand Down
46 changes: 31 additions & 15 deletions tests/gnmi/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from tests.common.platform.device_utils import get_dpu_ip, get_dpu_port
from tests.common.helpers.gnmi_utils import GNMIEnvironment, add_gnmi_client_common_name, del_gnmi_client_common_name, \
dump_gnmi_log, dump_system_status
from tests.common.helpers.gnmi_utils import gnmi_container # noqa: F401
from tests.common.helpers.ntp_helper import NtpDaemon, get_ntp_daemon_in_use # noqa: F401
from tests.common.helpers.dut_utils import check_container_state

Expand All @@ -19,7 +18,12 @@
GNMI_SERVER_START_WAIT_TIME = 15


def apply_cert_config(duthost):
def is_mgmt_vrf_enabled(duthost):
res = duthost.shell('sudo sonic-db-cli CONFIG_DB HGET "MGMT_VRF_CONFIG|vrf_global" "mgmtVrfEnabled"')["stdout"]
return res == "true"


def apply_cert_config(duthost, vrf_name=None):
env = GNMIEnvironment(duthost, GNMIEnvironment.GNMI_MODE)
# Get subtype
cfg_facts = duthost.config_facts(host=duthost.hostname, source="running")['ansible_facts']
Expand Down Expand Up @@ -50,6 +54,8 @@ def apply_cert_config(duthost):
dut_command += "--enable_crl=true "
if subtype == 'SmartSwitch':
dut_command += "--zmq_address=tcp://127.0.0.1:8100 "
if vrf_name:
dut_command += "--gnmi_vrf %s " % vrf_name
dut_command += "--ca_crt /etc/sonic/telemetry/gnmiCA.pem -gnmi_native_write=true -v=10 >/root/gnmi.log 2>&1 &\""
duthost.shell(dut_command)

Expand Down Expand Up @@ -193,7 +199,7 @@ def check_system_time_sync(duthost):
return False


def gnmi_set(duthost, ptfhost, delete_list, update_list, replace_list, cert=None):
def gnmi_set(duthost, ptfhost, delete_list, update_list, replace_list, cert=None, ip=None):
"""
Send GNMI set request with GNMI client

Expand All @@ -207,7 +213,7 @@ def gnmi_set(duthost, ptfhost, delete_list, update_list, replace_list, cert=None
Returns:
"""
env = GNMIEnvironment(duthost, GNMIEnvironment.GNMI_MODE)
ip = duthost.mgmt_ip
ip = ip or duthost.mgmt_ip
port = env.gnmi_port
cmd = '/root/env-python3/bin/python /root/gnxi/gnmi_cli_py/py_gnmicli.py '
cmd += '--timeout 30 '
Expand Down Expand Up @@ -276,7 +282,7 @@ def gnmi_set(duthost, ptfhost, delete_list, update_list, replace_list, cert=None
raise Exception(f"py_gnmicli failed rc={rc}\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}")


def gnmi_get(duthost, ptfhost, path_list):
def gnmi_get(duthost, ptfhost, path_list, ip=None):
"""
Send GNMI get request with GNMI client

Expand All @@ -289,7 +295,7 @@ def gnmi_get(duthost, ptfhost, path_list):
msg_list: list for get result
"""
env = GNMIEnvironment(duthost, GNMIEnvironment.GNMI_MODE)
ip = duthost.mgmt_ip
ip = ip or duthost.mgmt_ip
port = env.gnmi_port
cmd = '/root/env-python3/bin/python /root/gnxi/gnmi_cli_py/py_gnmicli.py '
cmd += '--timeout 30 '
Expand Down Expand Up @@ -325,7 +331,7 @@ def gnmi_get(duthost, ptfhost, path_list):

# py_gnmicli does not fully support POLLING mode
# Use gnmi_cli instead
def gnmi_subscribe_polling(duthost, ptfhost, path_list, interval_ms, count):
def gnmi_subscribe_polling(duthost, ptfhost, path_list, interval_ms, count, ip=None, vrf_name=None):
"""
Send GNMI subscribe request with GNMI client

Expand All @@ -335,6 +341,9 @@ def gnmi_subscribe_polling(duthost, ptfhost, path_list, interval_ms, count):
path_list: list for get path
interval_ms: interval, unit is ms
count: update count
ip: server IP to connect to (defaults to duthost.mgmt_ip)
vrf_name: when set, run gnmi_cli on the DUT inside the given VRF
(using `ip vrf exec`) instead of inside the gnmi container.

Returns:
msg: gnmi client output
Expand All @@ -343,12 +352,18 @@ def gnmi_subscribe_polling(duthost, ptfhost, path_list, interval_ms, count):
logger.error("path_list is None")
return "", ""
env = GNMIEnvironment(duthost, GNMIEnvironment.GNMI_MODE)
dut_facts = duthost.dut_basic_facts()['ansible_facts']['dut_basic_facts']
ip = f"[{duthost.mgmt_ip}]" if dut_facts.get('is_mgmt_ipv6_only', False) else duthost.mgmt_ip
if ip is None:
dut_facts = duthost.dut_basic_facts()['ansible_facts']['dut_basic_facts']
ip = f"[{duthost.mgmt_ip}]" if dut_facts.get('is_mgmt_ipv6_only', False) else duthost.mgmt_ip
port = env.gnmi_port
interval = interval_ms / 1000.0
# Run gnmi_cli in gnmi container as workaround
cmd = "docker exec %s gnmi_cli -client_types=gnmi -a %s:%s " % (env.gnmi_container, ip, port)
# For a non-default VRF the gnmi container does not have `ip vrf exec`
# privileges, so run gnmi_cli on the DUT host in the target VRF instead.
if vrf_name and vrf_name != "default":
cmd = "sudo ip vrf exec %s /tmp/gnmi_cli -client_types=gnmi -a %s:%s " % (vrf_name, ip, port)
else:
# Run gnmi_cli in gnmi container as workaround
cmd = "docker exec %s gnmi_cli -client_types=gnmi -a %s:%s " % (env.gnmi_container, ip, port)
cmd += "-client_crt /etc/sonic/telemetry/gnmiclient.crt "
cmd += "-client_key /etc/sonic/telemetry/gnmiclient.key "
cmd += "-ca_crt /etc/sonic/telemetry/gnmiCA.pem "
Expand All @@ -364,7 +379,8 @@ def gnmi_subscribe_polling(duthost, ptfhost, path_list, interval_ms, count):
return output['stdout'], output['stderr']


def gnmi_subscribe_streaming_sample(duthost, ptfhost, path_list, interval_ms, count, origin=None, target=None):
def gnmi_subscribe_streaming_sample(duthost, ptfhost, path_list, interval_ms, count, origin=None, target=None,
ip=None):
"""
Send GNMI subscribe request with GNMI client

Expand All @@ -382,7 +398,7 @@ def gnmi_subscribe_streaming_sample(duthost, ptfhost, path_list, interval_ms, co
logger.error("path_list is None")
return "", ""
env = GNMIEnvironment(duthost, GNMIEnvironment.GNMI_MODE)
ip = duthost.mgmt_ip
ip = ip or duthost.mgmt_ip
port = env.gnmi_port
cmd = '/root/env-python3/bin/python /root/gnxi/gnmi_cli_py/py_gnmicli.py '
cmd += '--timeout 30 '
Expand All @@ -407,7 +423,7 @@ def gnmi_subscribe_streaming_sample(duthost, ptfhost, path_list, interval_ms, co
return msg, output['stderr']


def gnmi_subscribe_streaming_onchange(duthost, ptfhost, path_list, count):
def gnmi_subscribe_streaming_onchange(duthost, ptfhost, path_list, count, ip=None):
"""
Send GNMI subscribe request with GNMI client

Expand All @@ -424,7 +440,7 @@ def gnmi_subscribe_streaming_onchange(duthost, ptfhost, path_list, count):
logger.error("path_list is None")
return "", ""
env = GNMIEnvironment(duthost, GNMIEnvironment.GNMI_MODE)
ip = duthost.mgmt_ip
ip = ip or duthost.mgmt_ip
port = env.gnmi_port
cmd = '/root/env-python3/bin/python /root/gnxi/gnmi_cli_py/py_gnmicli.py '
cmd += '--timeout 120 '
Expand Down
Loading
Loading