From 0674f8041f3fee2ec08041cf247ca6883f408ccf Mon Sep 17 00:00:00 2001 From: Sonic Build Admin Date: Tue, 21 Jul 2026 17:08:16 +0000 Subject: [PATCH] Fixed the test_bmcctld_power_on_delay ### Description of PR Fixes `test_bmcctld_power_on_delay` and BMC Log analyzer infrastructure Summary: - Supports PDU lookup for a Switch-Host not listed in `duthosts`. - Passes complete outlet objects to `PduManager`. - Uses `sed` instead of `awk` for marker-bounded `event.log` extraction. - Synchronizes `event.log` around marker writes and log analysis. Fixes: N/A ### Type of change - [x] Bug fix - [ ] Testbed and Framework(new/improvement) - [ ] New Test case - [ ] Skipped for non-supported platforms - [x] Test case improvement ### Back port request - [ ] 202311 - [ ] 202405 - [ ] 202411 - [ ] 202505 - [ ] 202511 - [ ] 202512 - [ ] 202605 Tracking issue/work item for backport/cherry-pick request: N/A Failure type: Other ### Tested branch - [ ] master - [ ] 202311 - [ ] 202405 - [ ] 202411 - [ ] 202505 - [ ] 202511 - [ ] 202512 - [ ] 202605 ### Test result Test passed ### Approach #### What is the motivation for this PR? `test_bmcctld_power_on_delay` could not reliably power-cycle the Switch-Host or extract marker-bounded entries from `/host/bmc/event.log`. The BMC testbed lists the BMC as the DUT, while the PDU connection belongs to its paired Switch-Host. Additionally, the test passed bare outlet IDs to `PduManager`, which expects complete outlet objects. The existing `awk` command used to extract logs after a marker was also sensitive to marker formatting and shell quoting. #### How did you do it? - Updated `get_pdu_controller` to load graph facts on demand for a paired Switch-Host not present in the testbed DUT list. - Updated the test to pass complete outlet dictionaries to `turn_off_outlet()` and `turn_on_outlet()`. - Replaced the `awk` marker extraction with `sed`. - Added `sync` calls around marker writes and before log analysis. #### How did you verify/test it? - Confirmed that the connection graph resolves the Switch-Host PDU. - Confirmed that outlet status and power consumption are returned. - Confirmed that the PDU power-cycle path reaches the BMC restart. - Confirmed `/host/bmc/event.log` contains the expected marker and bmcctld startup messages. - Test passed on the bmc-dual-mgmt testbed #### Any platform specific information? #### Supported testbed topology if it's a new test case? Not a new test case. The updated test uses the `bmc-dual-mgmt` topology. ### Documentation No documentation changes are required. Signed-off-by: Sonic Build Admin --- tests/common/platform/bmc_utils.py | 14 +++++++++++--- tests/common/plugins/pdu_controller/__init__.py | 12 ++++++++++-- tests/platform_tests/daemon/test_bmcctld.py | 9 +++++---- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/tests/common/platform/bmc_utils.py b/tests/common/platform/bmc_utils.py index ce70331d02..3a02c2cb7c 100644 --- a/tests/common/platform/bmc_utils.py +++ b/tests/common/platform/bmc_utils.py @@ -1,10 +1,13 @@ """Shared helpers for BMC platform tests (syslog, event.log, Switch-Host).""" import logging +import shlex + from contextlib import contextmanager import pytest + from tests.common.helpers.assertions import pytest_assert from tests.common.helpers.sonic_db import STATE_DB, redis_hget, redis_hset from tests.common.utilities import wait_until @@ -128,10 +131,12 @@ def init(self, log_target=LOG_TARGET_EVENT_LOG): marker = "{}.{}".format(self.marker_prefix, time.strftime("%Y-%m-%d-%H:%M:%S", time.gmtime())) start_line = "start-LogAnalyzer-{}".format(marker) + self.duthost.shell("sync {}".format(shlex.quote(BMC_EVENT_LOG))) self.duthost.shell( "echo '{}' >> {}".format(start_line, BMC_EVENT_LOG), module_ignore_errors=True, ) + self.duthost.shell("sync {}".format(shlex.quote(BMC_EVENT_LOG))) logger.debug("BmcLogAnalyzer: wrote marker '%s' to %s", start_line, BMC_EVENT_LOG) return marker @@ -150,10 +155,13 @@ def analyze(self, marker, fail=False, log_target=LOG_TARGET_EVENT_LOG): return self._get_syslog_analyzer().analyze(marker, fail=fail) start_line = "start-LogAnalyzer-{}".format(marker) + self.duthost.shell("sync {}".format(shlex.quote(BMC_EVENT_LOG))) result = self.duthost.shell( - "awk '/{}/ {{found=1; next}} found' {} 2>/dev/null".format( - start_line.replace("'", r"'\''"), BMC_EVENT_LOG), - module_ignore_errors=True, + "sed -n {} {}".format( + shlex.quote(r"/{}/,$p".format(start_line)), + shlex.quote(BMC_EVENT_LOG) + ), + module_ignore_errors=True ) content = (result.get('stdout', '') or '').strip() diff --git a/tests/common/plugins/pdu_controller/__init__.py b/tests/common/plugins/pdu_controller/__init__.py index 5d49109de4..d10f624c05 100644 --- a/tests/common/plugins/pdu_controller/__init__.py +++ b/tests/common/plugins/pdu_controller/__init__.py @@ -4,6 +4,7 @@ import pytest from .pdu_manager import pdu_manager_factory +from tests.common.fixtures.conn_graph_facts import get_graph_facts from tests.common.utilities import get_host_visible_vars, get_sup_node_or_random_node @@ -111,12 +112,19 @@ def pdu_controller(duthosts, conn_graph_facts): @pytest.fixture(scope="module") -def get_pdu_controller(conn_graph_facts): +def get_pdu_controller(conn_graph_facts, localhost): controller_map = {} def pdu_controller_helper(duthost): if duthost.hostname not in controller_map: - controller = _get_pdu_controller(duthost, conn_graph_facts) + # conn_graph_facts is scoped to the testbed duthosts. When asked for a host + # that is not part of that set (e.g. the paired Switch-Host resolved from a + # BMC DUT), its PDU wiring is absent, so load the graph facts for that host + # on demand instead of returning an empty (None) controller. + facts = conn_graph_facts + if duthost.hostname not in facts.get('device_pdu_links', {}): + facts = get_graph_facts(duthost, localhost, [duthost.hostname]) + controller = _get_pdu_controller(duthost, facts) controller_map[duthost.hostname] = controller return controller_map[duthost.hostname] diff --git a/tests/platform_tests/daemon/test_bmcctld.py b/tests/platform_tests/daemon/test_bmcctld.py index e2bd8d3b1e..c1d4ac54b1 100644 --- a/tests/platform_tests/daemon/test_bmcctld.py +++ b/tests/platform_tests/daemon/test_bmcctld.py @@ -463,20 +463,21 @@ def test_bmcctld_power_on_delay(self, localhost, get_pdu_controller): pytest.skip(f"No PDU controller wired for switch chassis {switch_host.hostname}, " "skipping power-loss scenario") + # PduManager.turn_{off,on}_outlet expects the full outlet dict from + # get_outlet_status() (with psu_name/feed_name/outlet_id), not a bare ID. outlets = pdu_ctrl.get_outlet_status() - outlet_ids = [o['outlet_id'] for o in outlets if 'outlet_id' in o] - pytest_assert(outlet_ids, "PDU controller returned no outlets for switch chassis") + pytest_assert(outlets, "PDU controller returned no outlets for switch chassis") # Set marker in event.log BEFORE power cycle. event.log is persistent; # syslog is tmpfs and is wiped on power loss so cannot be used here. la = make_bmc_loganalyzer(self.duthost, "bmcctld_power_on_delay_scenario_b") marker_b = la.init() - for outlet in outlet_ids: + for outlet in outlets: pdu_ctrl.turn_off_outlet(outlet) wait_until(120, 5, 10, lambda: self.duthost.shell("true", module_ignore_errors=True).get('rc') != 0) - for outlet in outlet_ids: + for outlet in outlets: pdu_ctrl.turn_on_outlet(outlet) wait_until(600, 15, 30, lambda: self.duthost.critical_services_fully_started())