Create xcvrd restart testcases - #26534
Conversation
Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
Hi, there are workflow run(s) waiting for approval, you may be first-time contributor. I will notify maintainers to help approve once PR is approved. Thanks! ---Powered by SONiC BuildBot
|
There was a problem hiding this comment.
Pull request overview
Adds a new transceiver System / Process Restart test module intended to validate xcvrd restart/crash recovery behavior as part of the transceiver system test plan.
Changes:
- Introduces
test_system_xcvrd_restartto restartxcvrdand verify port recovery. - Introduces
test_system_xcvrd_crash_recoveryto SIGKILLxcvrdand verify recovery. - Wires the tests to shared health-check / link-check style helpers (notably via helper modules referenced as dependencies).
Comments suppressed due to low confidence (2)
tests/transceiver/system/process_restart/test_xcvrd_restart.py:144
- Keyword arguments should not contain spaces around '=' (flake8 E251).
health_baseline = health_baseline,
tests/transceiver/system/process_restart/test_xcvrd_restart.py:133
check_links_up()returns a dict (withpassed/details), but this code treats it as a boolean. Since non-empty dicts are always truthy, the warning branch will never run even when links are down.
if not check_links_up(duthost, port_attributes_dict):
logger.warning("Validation on Start FAILED: some ports are down")
Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Signed-off-by: sutter <sutter.lum@pinewave.com>
Signed-off-by: sutter <sutter.lum@pinewave.com>
Signed-off-by: sutter <sutter.lum@pinewave.com>
Signed-off-by: sutter <sutter.lum@pinewave.com>
…ilty fixes Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
test_report_for_xcvrd_restart-Aug5-512pm.html Hi all, uploaded the first draft of the updated version of the code. Also attached two passlogs. The 5:12pm one is a pass, the 3:58 one is a failure on a slightly older version that was misusing a helper, but I've included it as it happened to capture a link flap, proving the negative case. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 13 changed files in this pull request and generated 5 comments.
Suppressed comments (12)
tests/common/platform/interface_utils.py:120
- wait_ports_oper_status currently returns a list of formatted failure strings, but callers (e.g. standard_port_recovery_and_verification) treat the return value as a list of port names. This breaks down_ports/up_ports computation and can also raise KeyError when indexing per_port_failures.
if wait_until(wait_sec, poll_interval_sec, 0, lambda: not _ports_not_at_status()):
return []
return [
"port {} did not reach oper-{} within {}s".format(port, status, wait_sec)
for port in _ports_not_at_status()
tests/transceiver/common/verification.py:380
- standard_port_recovery_and_verification’s docstring lists LLDP and CMIS checks, but those steps are currently commented out in the implementation. Either re-enable them or update the docstring so the function contract matches what actually runs.
"""Run the Standard Port Recovery and Verification Procedure on a
batch of ports (link status, flap/stability, LLDP, CMIS state,
docker/process health), batched across ``ports`` so fixed per-call
costs aren't multiplied by port count and every port's failures are
surfaced in one call.
tests/transceiver/system/conftest.py:63
- _state_db_key_exists reads STATE_DB without a per-port ASIC namespace. On multi-ASIC DUTs this can report false 'missing' entries because each ASIC has its own STATE_DB namespace. Use the existing db_helpers wrappers and accept a namespace argument.
def _state_db_key_exists(duthost, key):
cmd = f'sonic-db-cli STATE_DB hgetall "{key}"'
out = duthost.shell(cmd, module_ignore_errors=True)
if out.get("rc", 1) != 0:
return False
return bool((out.get("stdout") or "").strip())
tests/transceiver/system/conftest.py:123
- The STATE_DB consistency loop should pass the owning ASIC namespace for each port when checking TRANSCEIVER_INFO/DOM entries; otherwise multi-ASIC DUTs can be incorrectly flagged as missing data.
for port in sorted(port_attributes_dict.keys()):
if not _state_db_key_exists(duthost, f"TRANSCEIVER_INFO|{port}"):
missing_info.append(port)
if not _state_db_key_exists(duthost, f"TRANSCEIVER_DOM_SENSOR|{port}"):
missing_dom.append(port)
tests/common/platform/interface_utils.py:105
- The docstring says wait_ports_oper_status returns one failure string per port, but for downstream use it should return port names (and let callers format messages). Please update the docstring to match the function’s actual contract.
This issue also appears on line 116 of the same file.
Returns a list with one string per port still not at oper-``status`` after
``wait_sec``; empty once all reach it. A port absent from the dump is reported
as a failure (rather than raising) so a missing/renamed port aggregates like
any other laggard.
tests/transceiver/common/verification.py:241
- Typo in the check_cmis_state docstring ('breakou t') makes the comment hard to read and looks like an accidental paste/formatting artifact.
This issue also appears on line 376 of the same file.
Why: ``TRANSCEIVER_STATUS`` is published once per physical module (under
the first sub-port of a breakou t group) and carries every host lane of
the module, so a breakout sub-port must be checked only against its own
tests/transceiver/common/verification.py:9
- Module docstring claims all parent/child helpers return the per-port dict shape, but standard_port_recovery_and_verification returns an aggregate dict with top-level 'passed'/'per_port'/'details'. This mismatch will confuse future call sites and reviewers.
Implements the Standard Port and Verification function,
as well as the related child functions. All parent and
child functions will return an output following the format:
dict: ``{port: {'passed': bool, 'details': str}}``
tests/transceiver/system/process_restart/test_swss_restart.py:64
- This test restarts swss, so the monitored process expected to change PID is orchagent (in the swss container), not xcvrd. As written, the per-test health check will likely fail because orchagent PID changes are treated as unexpected.
expected_pid_changes.add("xcvrd")
tests/transceiver/system/process_restart/test_swss_restart.py:73
- check_links_up returns a result dict (with a 'passed' key). Using it as a boolean will always treat failures as success because non-empty dicts are truthy, so the test will log uptime even when ports are down.
if not check_links_up(duthost, port_attributes_dict):
logger.warning("Validation on Start FAILED: some ports are down")
else:
tests/transceiver/system/process_restart/test_syncd_restart.py:64
- This test restarts syncd, so the monitored process expected to change PID is syncd (in the syncd container), not xcvrd. As written, the per-test health check will likely fail because syncd PID changes are treated as unexpected.
expected_pid_changes.add("xcvrd")
tests/transceiver/system/process_restart/test_syncd_restart.py:73
- check_links_up returns a result dict (with a 'passed' key). Using it as a boolean will always treat failures as success because non-empty dicts are truthy, so the test will log uptime even when ports are down.
if not check_links_up(duthost, port_attributes_dict):
logger.warning("Validation on Start FAILED: some ports are down")
else:
tests/transceiver/system/process_restart/test_pmon_restart.py:70
- check_links_up returns a result dict (with a 'passed' key). Using it as a boolean will always treat failures as success because non-empty dicts are truthy, so the test will log uptime even when ports are down.
if not check_links_up(duthost, port_attributes_dict):
logger.warning("Validation on Start FAILED: some ports are down")
else:
| from tests.common.helpers.sonic_db import AppDbCli as sdbHelp | ||
| from tests.common.platform.processes_utils import check_process_up |
Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
…issues Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 13 changed files in this pull request and generated 10 comments.
Suppressed comments (7)
tests/transceiver/system/process_restart/test_swss_restart.py:40
tests.common.platform.processes_utilsdoes not definecheck_process_up, so this import raisesImportErrorduring pytest collection and none of the swss restart test code can run. Replace it with an existing helper or implement restart verification using a pre/post pmon/xcvrd PID comparison.
from tests.common.platform.processes_utils import check_process_up
tests/transceiver/system/process_restart/test_syncd_restart.py:40
tests.common.platform.processes_utilsdoes not definecheck_process_up, so this import raisesImportErrorduring pytest collection and none of the syncd restart test code can run. Replace it with an existing helper or implement restart verification using a pre/post pmon/xcvrd PID comparison.
from tests.common.platform.processes_utils import check_process_up
tests/transceiver/system/process_restart/test_xcvrd_restart.py:144
expected_pid_changespermits a changed PID but does not assert one, whilekill_pmon_daemon_pid_w_sigignores kill errors. A failed SIGKILL can therefore leave the original xcvrd running and this crash-recovery test still passes. Capture the pre-crash PID and require a RUNNING xcvrd with a different PID after recovery.
expected_pid_changes.add("xcvrd")
tests/transceiver/system/conftest.py:123
- These STATE_DB reads always use the default namespace. On multi-ASIC DUTs, transceiver keys for frontend ports reside in their owning ASIC namespaces, so every such port is falsely reported missing. Resolve each port's namespace and issue the check there, as the new DB helpers already do.
for port in sorted(port_attributes_dict.keys()):
if not _state_db_key_exists(duthost, f"TRANSCEIVER_INFO|{port}"):
missing_info.append(port)
if not _state_db_key_exists(duthost, f"TRANSCEIVER_DOM_SENSOR|{port}"):
missing_dom.append(port)
tests/transceiver/system/process_restart/test_xcvrd_restart.py:21
- The module advertises an I2C-error restart test as skipped, but no
test_system_xcvrd_restart_with_i2c_errorsfunction or skip marker exists, so pytest reports no skipped test and that test-plan case is silently absent. Add the test with an explicitpytest.mark.skip(reason=...)until it is implementable, or narrow the stated PR/module scope.
`- test_system_xcvrd_restart_with_i2c_errors
NOTE: this test has been skipped during initial development
due to library issues
tests/transceiver/common/state_management.py:148
- The helper claims to restore and report admin/link, high-power, and CMIS datapath state, but the final verdict rechecks only links. A failed
sfputil lpmode offcommand or a datapath that remains invalid after recycling leavesstill_failingempty and the teardown reports recovery. Revalidate LPMode and CMIS state after the actions and include those failures in the summary.
final_link = check_links_up(duthost, port_attributes_dict)
if not final_link["passed"]:
summary["still_failing"].extend(final_link["down"])
tests/transceiver/system/conftest.py:123
- A DOM entry is not expected for every logical breakout port:
dom/test_dom_availability.py:108-120explicitly requires non-primary breakout subports to have noTRANSCEIVER_DOM_SENSORdata. This loop therefore reports valid breakout configurations as inconsistent. Check the physical module's first subport only (usinglport_to_first_subport_mapping) for DOM presence.
if not _state_db_key_exists(duthost, f"TRANSCEIVER_DOM_SENSOR|{port}"):
missing_dom.append(port)
| # sonic-db-cli database identifiers (the first positional arg to sonic-db-cli). | ||
| STATE_DB = "STATE_DB" |
| # NOTE: Temporarily disabled for PR streamlining | ||
| # All commented code below is UNTESTED | ||
| # |
| All (port, step) failures are accumulated and reported in a single | ||
| ``pytest.fail`` so one run surfaces every issue. | ||
| """ | ||
| expected_pid_changes.add("xcvrd") |
| All (port, step) failures are accumulated and reported in a single | ||
| ``pytest.fail`` so one run surfaces every issue. | ||
| """ | ||
| expected_pid_changes.add("xcvrd") |
| All (port, step) failures are accumulated and reported in a single | ||
| ``pytest.fail`` so one run surfaces every issue. | ||
| """ | ||
| expected_pid_changes.add("xcvrd") |
| # Failures are reported as warnings instead of fixture errors so they | ||
| # don't mask the actual test results that are already on the report. |
| config_facts = duthost.get_running_config_facts() | ||
| return set(config_facts.get("PORT", {}).keys()) No newline at end of file |
| for port in down_ports: | ||
| per_port_failures[port].append( | ||
| f"port {port} did not reach oper-up within {link_up_timeout_sec}s" |
| time.sleep(pmon_wait + 60) | ||
|
|
||
| # Wait for settle time and verify | ||
| result = standard_port_recovery_and_verification( |
| if datapath_fields_seen == 0 and config_fields_seen == 0: | ||
| per_port[port] = { | ||
| "passed": False, | ||
| "details": ( | ||
| f"{port} (parent {parent}) TRANSCEIVER_STATUS|{parent} " | ||
| f"has no DP<N>State or config_state_hostlane<N> fields " | ||
| f"for this port's active host lanes " | ||
| f"{sorted(active_lanes)} - cannot confirm CMIS state " | ||
| "(schema mismatch, partial publish, or lane-range " | ||
| "mismatch)" | ||
| ), | ||
| } | ||
| continue |
Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Signed-off-by: sutter <sutter.lum@pinewave.com>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Description of PR
Summary:
Created xcvrd restart testcase.
Tests located at tests/transceiver/system/process_restart/test_xcvrtd_restart.py
Original testcase described in docs/testplan/transceiver/system_test_plan.md
NOTE: Please note that this PR is dependent on the helper functions in PR #26273
Fixes # (issue)
Type of change
Approach
What is the motivation for this PR?
Fulfilling the test plan described in system_test_plan.md
How did you do it?
Test initially logs link states, uptime, and DUT corefiles. Restarts syncd and waits based on related settle_sec system attribute. Performs standard port validation and recovery process on all participating ports.
How did you verify/test it?
Tested in Pinewave lab on Arista 7060 using loopback connections.
(P5<->P7), (P13<->P15)
Any platform specific information?
Supported testbed topology if it's a new test case?
Documentation