Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 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
8 changes: 7 additions & 1 deletion tests/gnmi/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,13 @@ def setup_gnmi_server(duthosts, rand_one_dut_hostname, localhost, ptfhost):

# Rollback configuration
rollback(duthost, SETUP_ENV_CP)
recover_cert_config(duthost)
# Get the skip_gnmi_check flag from duthost options
skip_gnmi_check = duthost.host.options.get('skip_gnmi_check', False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skip_gnmi_check

skip_gnmi_check -> skip_gnmi_reboot

# Skip GNMI restart if the reboot flag is set
if not skip_gnmi_check:
recover_cert_config(duthost)
else:
logging.info("Skipping GNMI restart due to skip_gnmi_check flag")


@pytest.fixture(scope="module", autouse=True)
Expand Down
136 changes: 127 additions & 9 deletions tests/gnmi/test_gnoi_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

from .helper import gnoi_request
from tests.common.helpers.assertions import pytest_assert
from tests.common.reboot import wait_for_startup
import re

pytestmark = [
pytest.mark.topology('any')
]

MAX_TIME_TO_REBOOT = 300

"""
This module contains tests for the gNOI System API.
Expand All @@ -35,6 +37,116 @@ def test_gnoi_system_time(duthosts, rand_one_dut_hostname, localhost):
pytest_assert("time" in msg_json, "System.Time API did not return time")


def test_gnoi_system_reboot(duthosts, rand_one_dut_hostname, localhost):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hdwhdw Since, all the tests are doing cold reboot, could you rename the tests accordingly to include the cold name? I plan to add similar tests for halt method testing as well.

"""
Verify the gNOI System Reboot API triggers a reboot and the device comes back online.
"""
duthost = duthosts[rand_one_dut_hostname]

# Set flag to indicate that this test involves reboot
duthost.host.options['skip_gnmi_check'] = True

# Trigger reboot
ret, msg = gnoi_request(duthost, localhost, "Reboot", '{"method": 1,"delay":0,"message":"Cold Reboot"}')
pytest_assert(ret == 0, "System.Reboot API reported failure (rc = {}) with message: {}".format(ret, msg))
logging.info("System.Reboot API returned msg: {}".format(msg))

@pytest.mark.disable_loganalyzer
def test_gnoi_system_reboot_fail_invalid_method(duthosts, rand_one_dut_hostname, localhost):
"""
Verify the gNOI System Reboot API fails with invalid method.
"""
duthost = duthosts[rand_one_dut_hostname]

# Set flag to indicate that this test involves reboot
duthost.host.options['skip_gnmi_check'] = True

# Trigger reboot with invalid method
ret, msg = gnoi_request(duthost, localhost, "Reboot", '{"method": 99}')
pytest_assert(ret != 0, "System.Reboot API did not report failure with invalid method")

@pytest.mark.disable_loganalyzer
def test_gnoi_system_reboot_when_reboot_active(duthosts, rand_one_dut_hostname, localhost):
"""
Verify the gNOI System Reboot API fails if a reboot is already active.
"""
duthost = duthosts[rand_one_dut_hostname]

# Set flag to indicate that this test involves reboot
duthost.host.options['skip_gnmi_check'] = True

# Trigger first reboot
ret, msg = gnoi_request(duthost, localhost, "Reboot", '{"method": 1,"delay":0,"message":"Cold Reboot"}')
pytest_assert(ret == 0, "System.Reboot API reported failure (rc = {}) with message: {}".format(ret, msg))
logging.info("System.Reboot API returned msg: {}".format(msg))

# Trigger second reboot while the first one is still active
ret, msg = gnoi_request(duthost, localhost, "Reboot", '{"method": 1,"delay":0,"message":"Cold Reboot"}')
pytest_assert(ret != 0, "System.Reboot API did not report failure when reboot is already active")


@pytest.mark.disable_loganalyzer
def test_gnoi_system_reboot_status_immediately(duthosts, rand_one_dut_hostname, localhost):
"""
Verify the gNOI System RebootStatus API returns the correct status immediately after reboot.
"""
duthost = duthosts[rand_one_dut_hostname]

# Set flag to indicate that this test involves reboot
duthost.host.options['skip_gnmi_check'] = True

# Trigger reboot
ret, msg = gnoi_request(duthost, localhost, "Reboot", '{"method": 1, "message": "test"}')
pytest_assert(ret == 0, "System.Reboot API reported failure (rc = {}) with message: {}".format(ret, msg))
logging.info("System.Reboot API returned msg: {}".format(msg))

# Get reboot status
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get

Could you query telemetry OTHERS/proc/uptime to ensure the device was really rebooted recently?

ret, msg = gnoi_request(duthost, localhost, "RebootStatus", "")
pytest_assert(ret == 0, "System.RebootStatus API reported failure (rc = {}) with message: {}".format(ret, msg))
logging.info("System.RebootStatus API returned msg: {}".format(msg))
# Message should contain a json substring like this
# {"active":true,"wait":0,"when":0,"reason":"test","count":1,"method":1,"status":1}
# Extract JSON part from the message
msg_json = extract_first_json_substring(msg)
if not msg_json:
pytest.fail("Failed to extract JSON from System.RebootStatus API response")
logging.info("Extracted JSON: {}".format(msg_json))
pytest_assert("active" in msg_json, "System.RebootStatus API did not return active")
pytest_assert(msg_json["active"] is True, "System.RebootStatus API did not return active = true")


def gnoi_system_reboot_status_after_startup(duthosts, rand_one_dut_hostname, localhost):
"""
Verify the gNOI System RebootStatus API returns the correct status after the device has started up.
"""
duthost = duthosts[rand_one_dut_hostname]

# Set flag to indicate that this test involves reboot
duthost.host.options['skip_gnmi_check'] = True

# Trigger reboot
ret, msg = gnoi_request(duthost, localhost, "Reboot", '{"method": 1, "message": "test"}')
pytest_assert(ret == 0, "System.Reboot API reported failure (rc = {}) with message: {}".format(ret, msg))
logging.info("System.Reboot API returned msg: {}".format(msg))

# Wait for device to come back online
wait_for_startup(duthost)

# Get reboot status
ret, msg = gnoi_request(duthost, localhost, "RebootStatus", "")
pytest_assert(ret == 0, "System.RebootStatus API reported failure (rc = {}) with message: {}".format(ret, msg))
logging.info("System.RebootStatus API returned msg: {}".format(msg))
# Message should contain a json substring like this
# {"active":false,"wait":0,"when":0,"reason":"test","count":1,"method":1,"status":1}
# Extract JSON part from the message
msg_json = extract_first_json_substring(msg)
if not msg_json:
pytest.fail("Failed to extract JSON from System.RebootStatus API response")
logging.info("Extracted JSON: {}".format(msg_json))
pytest_assert("active" in msg_json, "System.RebootStatus API did not return active")
pytest_assert(msg_json["active"] is False, "System.RebootStatus API did not return active = false")


def extract_first_json_substring(s):
"""
Extract the first JSON substring from a given string.
Expand All @@ -43,12 +155,18 @@ def extract_first_json_substring(s):
:return: The first JSON substring if found, otherwise None.
"""

json_pattern = re.compile(r'\{.*?\}')
match = json_pattern.search(s)
if match:
try:
return json.loads(match.group())
except json.JSONDecodeError:
logging.error("Failed to parse JSON: {}".format(match.group()))
return None
return None
start_index = s.find('{') # Find the first '{' in the string
if start_index == -1:
logging.error("No JSON found in response: {}".format(s))
return None
json_str = s[start_index:] # Extract substring starting from '{'
try:
parsed_json = json.loads(json_str) # Attempt to parse the JSON
# Handle cases where "status": {} is empty
if "status" in parsed_json and parsed_json["status"] == {}:
logging.warning("Replacing empty 'status' field with a default value.")
parsed_json["status"] = {"unknown": "empty_status"}
return parsed_json
except json.JSONDecodeError as e:
logging.error("Failed to parse JSON: {} | Error: {}".format(json_str, e))
return None
Loading