From 696e426569fd84771f2665bd33b14ecc613ef44a Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Thu, 16 May 2019 15:59:34 +0800 Subject: [PATCH 1/4] [platform] Implement platform phase 1 cases Signed-off-by: Xin Wang --- tests/conftest.py | 2 +- tests/platform/psu_controller.py | 113 ++++++++++++++++ tests/platform/test_platform_info.py | 177 +++++++++++++++++++++++++ tests/platform/test_sfp.py | 103 ++++++++++++++ tests/platform/test_xcvr_info_in_db.py | 83 ++++++++++++ 5 files changed, 477 insertions(+), 1 deletion(-) create mode 100644 tests/platform/psu_controller.py create mode 100644 tests/platform/test_platform_info.py create mode 100644 tests/platform/test_sfp.py create mode 100644 tests/platform/test_xcvr_info_in_db.py diff --git a/tests/conftest.py b/tests/conftest.py index f8e7a311554..e50ffe29181 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,7 +37,7 @@ def pytest_addoption(parser): parser.addoption("--testbed", action="store", default=None, help="testbed name") parser.addoption("--testbed_file", action="store", default=None, help="testbed file name") -@pytest.fixture +@pytest.fixture(scope="session") def testbed(request): tbname = request.config.getoption("--testbed") tbfile = request.config.getoption("--testbed_file") diff --git a/tests/platform/psu_controller.py b/tests/platform/psu_controller.py new file mode 100644 index 00000000000..420b57ef28e --- /dev/null +++ b/tests/platform/psu_controller.py @@ -0,0 +1,113 @@ +""" +Fixture for controlling PSUs of DUT + +This file defines fixture psu_controller which is for controlling PSUs of DUT. The fixture uses factory design pattern +and returns a function for creating PSU controller instance. The function takes two arguments: +* hostname - hostname of the DUT +* asic_type - asic type of the DUT +Based on these two inputs, different PSU controller implemented by different vendors could be returned. + +The PSU controller implemented by each vendor must be a subclass of the PsuControllerBase class and implements the +methods defined in the base class. +""" +import os +import sys + +import pytest + + +class PsuControllerBase(): + """ + @summary: Base class for PSU controller + + This base class defines the basic interface to be provided by PSU controller. PSU controller implemented by each + vendor must be a subclass of this base class. + """ + def __init__(self): + pass + + def turn_on_psu(self, psu_id): + """ + @summary: Turn on power for specified PSU. + + @param psu_id: PSU ID, it could be integer of string digit. For example: 0 or '1' + @return: Returns True if operation is successful. Otherwise, returns False + """ + raise NotImplementedError + + def turn_off_psu(self, psu_id): + """ + @summary: Turn off power for specified PSU. + + @param psu_id: PSU ID, it could be integer of string digit. For example: 0 or '1' + @return: Returns True if operation is successful. Otherwise, returns False + """ + raise NotImplementedError + + def get_psu_status(self, psu_id=None): + """ + @summary: Get current power status of PSUs + + @param psu_id: Optional PSU ID, it could be integer or string digit. If no psu_id is specified, power status of + all PSUs should be returned + @return: Returns a list of dictionaries. For example: + [{"psu_id": 0, "psu_on": True}, {"psu_id": 1, "psu_on": True}] + If getting PSU status failed, an empty list should be returned. + """ + raise NotImplementedError + + def close(self): + """ + @summary Close the PDU controller to release resources. + """ + raise NotImplementedError + + +@pytest.fixture +def psu_controller(): + """ + @summary: Fixture for controlling power supply to PSUs of DUT + + @returns: Returns a function for creating PSU controller object. The object must implement the PsuControllerBase + interface. + """ + # For holding PSU controller object to be used in fixture tear down section + controllers = [] + + def _make_psu_controller(hostname, asic_type): + """ + @summary: Function for creating PSU controller object. + @param hostname: Hostname of DUT + @param asic_type: ASIC type of DUT, for example: 'mellanox' + """ + controller = None + + # Create PSU controller object based on asic type and hostname of DUT + if asic_type == "mellanox": + current_file_dir = os.path.dirname(os.path.realpath(__file__)) + if current_file_dir not in sys.path: + sys.path.append(current_file_dir) + sub_folder_dir = os.path.join(current_file_dir, "mellanox") + if sub_folder_dir not in sys.path: + sys.path.append(sub_folder_dir) + from mellanox_psu_controller import make_mellanox_psu_controller + + controller = make_mellanox_psu_controller(hostname) + if controller: + # The PSU controller object is returned to test case calling this fixture. Need to save the object + # for later use in tear down section + controllers.append(controller) + + return controller + + yield _make_psu_controller + + # Tear down section, ensure that all PSUs are turned on after test + for controller in controllers: + if controller: + psu_status = controller.get_psu_status() + if psu_status: + for psu in psu_status: + if not psu["psu_on"]: + controller.turn_on_psu(psu["psu_id"]) + controller.close() diff --git a/tests/platform/test_platform_info.py b/tests/platform/test_platform_info.py new file mode 100644 index 00000000000..1c5f99e1576 --- /dev/null +++ b/tests/platform/test_platform_info.py @@ -0,0 +1,177 @@ +""" +Check platform information + +This script covers the test case 'Check platform information' in the SONiC platform test plan: +https://github.com/Azure/SONiC/blob/master/doc/pmon/sonic_platform_test_plan.md +""" +import logging +import re +import time + +import pytest + +from ansible_host import ansible_host +from psu_controller import psu_controller + + +def test_show_platform_summary(localhost, ansible_adhoc, testbed): + """ + @summary: Check output of 'show platform summary' + """ + hostname = testbed['dut'] + ans_host = ansible_host(ansible_adhoc, hostname) + + logging.info("Check output of 'show platform summary'") + platform_summary = ans_host.command("show platform summary") + expected_fields = set(["Platform", "HwSKU", "ASIC"]) + actual_fields = set() + for line in platform_summary["stdout_lines"]: + key_value = line.split(":") + assert len(key_value) == 2, "output format is not 'field_name: field_value'" + assert len(key_value[1]) > 0, "No value for field %s" % key_value[0] + actual_fields.add(line.split(":")[0]) + assert actual_fields == expected_fields, \ + "Unexpected output fields, actual=%s, expected=%s" % (str(actual_fields), str(expected_fields)) + + +def test_show_platform_psustatus(localhost, ansible_adhoc, testbed): + """ + @summary: Check output of 'show platform psustatus' + """ + hostname = testbed['dut'] + ans_host = ansible_host(ansible_adhoc, hostname) + + logging.info("Check PSU status using 'show platform psustatus', hostname: " + hostname) + psu_status = ans_host.command("show platform psustatus") + psu_line_pattern = re.compile(r"PSU\s+\d+\s+(OK|NOT OK)") + for line in psu_status["stdout_lines"][2:]: + assert psu_line_pattern.match(line), "Unexpected PSU status output" + + +def test_turn_on_off_psu_and_check_psustatus(localhost, ansible_adhoc, testbed, psu_controller): + """ + @summary: Turn off/on PSU and check PSU status using 'show platform psustatus' + """ + hostname = testbed['dut'] + ans_host = ansible_host(ansible_adhoc, hostname) + platform_info = parse_platform_summary(ans_host.command("show platform summary")["stdout_lines"]) + + psu_line_pattern = re.compile(r"PSU\s+\d+\s+(OK|NOT OK)") + + logging.info("Check whether the DUT has enough PSUs for this testing") + psu_num_out = ans_host.command("sudo psuutil numpsus") + psu_num = 0 + try: + psu_num = int(psu_num_out["stdout"]) + except: + assert False, "Unable to get the number of PSUs using command 'psuutil numpsus'" + if psu_num < 2: + pytest.skip("At least 2 PSUs required for rest of the testing in this case") + + logging.info("Create PSU controller for testing") + psu_ctrl = psu_controller(hostname, platform_info["asic"]) + if psu_ctrl is None: + pytest.skip("No PSU controller for %s, skip rest of the testing in this case" % hostname) + + logging.info("To avoid DUT losing power, need to turn on PSUs that are not powered") + all_psu_status = psu_ctrl.get_psu_status() + if all_psu_status: + for psu in all_psu_status: + if not psu["psu_on"]: + psu_ctrl.turn_on_psu(psu["psu_id"]) + time.sleep(5) + + logging.info("Initialize test results") + cli_psu_status = ans_host.command("show platform psustatus") + psu_test_results = {} + for line in cli_psu_status["stdout_lines"][2:]: + fields = line.split() + psu_test_results[fields[1]] = False + if " ".join(fields[2:]) == "NOT OK": + pytest.skip("Some PSUs are still not powered, it is not safe to proceed, skip testing") + assert len(psu_test_results.keys()) == psu_num, \ + "In consistent PSU number output by 'show platform psustatus' and 'psuutil numpsus'" + + logging.info("Start testing turn off/on PSUs") + all_psu_status = psu_ctrl.get_psu_status() + for psu in all_psu_status: + psu_under_test = None + + logging.info("Turn off PSU %s" % str(psu["psu_id"])) + psu_ctrl.turn_off_psu(psu["psu_id"]) + time.sleep(5) + + cli_psu_status = ans_host.command("show platform psustatus") + for line in cli_psu_status["stdout_lines"][2:]: + assert psu_line_pattern.match(line), "Unexpected PSU status output" + fields = line.split() + if " ".join(fields[2:]) == "NOT OK": + psu_under_test = fields[1] + assert psu_under_test is not None, "No PSU is turned off" + + logging.info("Turn on PSU %s" % str(psu["psu_id"])) + psu_ctrl.turn_on_psu(psu["psu_id"]) + time.sleep(5) + + cli_psu_status = ans_host.command("show platform psustatus") + for line in cli_psu_status["stdout_lines"][2:]: + assert psu_line_pattern.match(line), "Unexpected PSU status output" + fields = line.split() + if fields[1] == psu_under_test: + assert fields[2] == "OK", "Unexpected PSU status after turned it on" + + psu_test_results[psu_under_test] = True + + for psu in psu_test_results: + assert psu_test_results[psu], "Test psu status of PSU %s failed" % psu + + +def parse_platform_summary(raw_input_lines): + """ + @summary: Helper function for parsing the output of 'show system platform' + @return: Returned parsed information in a dictionary + """ + res = {} + for line in raw_input_lines: + fields = line.split(":") + if len(fields) != 2: + continue + res[fields[0].lower()] = fields[1].strip() + return res + + +def test_show_platform_syseeprom(localhost, ansible_adhoc, testbed): + """ + @summary: Check output of 'show platform syseeprom' + """ + hostname = testbed['dut'] + ans_host = ansible_host(ansible_adhoc, hostname) + + logging.info("Check output of 'show platform syseeprom'") + platform_info = parse_platform_summary(ans_host.command("show platform summary")["stdout_lines"]) + show_output = ans_host.command("show platform syseeprom") + assert show_output["rc"] == 0, "Run command 'show platform syseeprom' failed" + if platform_info["asic"] in ["mellanox"]: + expected_fields = [ + "Product Name", + "Part Number", + "Serial Number", + "Base MAC Address", + "Manufacture Date", + "Device Version", + "MAC Addresses", + "Manufacturer", + "Vendor Extension", + "ONIE Version", + "CRC-32"] + utility_cmd = "sudo python -c \"import imp; \ + m = imp.load_source('eeprom', '/usr/share/sonic/device/%s/plugins/eeprom.py'); \ + t = m.board('board', '', '', ''); e = t.read_eeprom(); t.decode_eeprom(e)\"" % platform_info["platform"] + utility_cmd_output = ans_host.command(utility_cmd) + + for field in expected_fields: + assert show_output["stdout"].find(field) >= 0, "Expected field %s is not found" % field + assert utility_cmd_output["stdout"].find(field) >= 0, "Expected field %s is not found" % field + + assert show_output["stdout"].find(utility_cmd_output["stdout"]) >= 0, \ + "Output of 'show platform syseeprom' is inconsistent with output of eeprom.py utility" diff --git a/tests/platform/test_sfp.py b/tests/platform/test_sfp.py new file mode 100644 index 00000000000..e6f82b75d8f --- /dev/null +++ b/tests/platform/test_sfp.py @@ -0,0 +1,103 @@ +""" +Check SFP status and configure SFP + +This script covers test case 'Check SFP status and configure SFP' in the SONiC platform test plan: +https://github.com/Azure/SONiC/blob/master/doc/pmon/sonic_platform_test_plan.md +""" +import logging +import re +import os +import time + +from ansible_host import ansible_host + + +def parse_presence(output_lines): + """ + @summary: Parse the SFP presence information from command output + @param output_lines: Command output lines + @return: Returns result in a dictionary + """ + res = {} + for line in output_lines: + fields = line.split() + if len(fields) != 2: + continue + res[fields[0]] = fields[1] + return res + + +def parse_eeprom(output_lines): + """ + @summary: Parse the SFP eeprom information from command output + @param output_lines: Command output lines + @return: Returns result in a dictionary + """ + res = {} + for line in output_lines: + if re.match(r"^Ethernet\d+: .*", line): + fields = line.split(":") + res[fields[0]] = fields[1].strip() + return res + + +def test_check_sfp_status_and_configure_sfp(localhost, ansible_adhoc, testbed): + """ + @summary: Check SFP status and configure SFP + + This case is to use the sfputil tool and show command to check SFP status and configure SFP. Currently the + only configuration is to reset SFP. Commands to be tested: + * sfputil show presence + * show interface transceiver presence + * sfputil show eeprom + * show interface transceiver eeprom + * sfputil reset + """ + hostname = testbed['dut'] + ans_host = ansible_host(ansible_adhoc, hostname) + localhost.command("who") + lab_conn_graph_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), \ + "../../ansible/files/lab_connection_graph.xml") + conn_graph_facts = localhost.conn_graph_facts(host=hostname, filename=lab_conn_graph_file).\ + contacted['localhost']['ansible_facts'] + + logging.info("Check output of 'sfputil show presence'") + sfp_presence = ans_host.command("sudo sfputil show presence") + parsed_presence = parse_presence(sfp_presence["stdout_lines"][2:]) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_presence, "Interface is not in output of 'sfputil show presence'" + assert parsed_presence[intf] == "Present", "Interface presence is not 'Present'" + + logging.info("Check output of 'show interface transceiver presence'") + sfp_presence = ans_host.command("show interface transceiver presence") + parsed_presence = parse_presence(sfp_presence["stdout_lines"][2:]) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_presence, "Interface is not in output of 'show interface transceiver presence'" + assert parsed_presence[intf] == "Present", "Interface presence is not 'Present'" + + logging.info("Check output of 'sfputil show eeprom'") + sfp_eeprom = ans_host.command("sudo sfputil show eeprom") + parsed_eeprom = parse_eeprom(sfp_eeprom["stdout_lines"]) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_eeprom, "Interface is not in output of 'sfputil show eeprom'" + assert parsed_eeprom[intf] == "SFP EEPROM detected" + + logging.info("Check output of 'show interface transceiver eeprom'") + sfp_eeprom = ans_host.command("show interface transceiver eeprom") + parsed_eeprom = parse_eeprom(sfp_eeprom["stdout_lines"]) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_eeprom, "Interface is not in output of 'show interface transceiver eeprom'" + assert parsed_eeprom[intf] == "SFP EEPROM detected" + + logging.info("Test 'sfputil reset '") + for intf in conn_graph_facts["device_conn"]: + reset_result = ans_host.command("sudo sfputil reset " + intf) + assert reset_result["rc"] == 0, "'sudo sfputil reset %s failed" % intf + time.sleep(10) # Wait some time for SFP to fully recover after reset + + logging.info("Check sfp presence again after reset") + sfp_presence = ans_host.command("sudo sfputil show presence") + parsed_presence = parse_presence(sfp_presence["stdout_lines"][2:]) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_presence, "Interface is not in output of 'sfputil show presence'" + assert parsed_presence[intf] == "Present", "Interface presence is not 'Present'" diff --git a/tests/platform/test_xcvr_info_in_db.py b/tests/platform/test_xcvr_info_in_db.py new file mode 100644 index 00000000000..142fbd1d101 --- /dev/null +++ b/tests/platform/test_xcvr_info_in_db.py @@ -0,0 +1,83 @@ +""" +Check xcvrd information in DB + +This script is to cover the test case 'Check xcvrd information in DB' in the SONiC platform test plan: +https://github.com/Azure/SONiC/blob/master/doc/pmon/sonic_platform_test_plan.md +""" +import logging +import re +import os + +from ansible_host import ansible_host + + +def parse_transceiver_info(output_lines): + """ + @summary: Parse the list of transceiver from DB table TRANSCEIVER_INFO content + @param output_lines: DB table TRANSCEIVER_INFO content output by 'redis' command + @return: Return parsed transceivers in a list + """ + res = [] + p = re.compile(r"TRANSCEIVER_INFO\|(Ethernet\d+)") + for line in output_lines: + m = p.match(line) + assert m, "Unexpected line %s" % line + res.append(m.group(1)) + return res + + +def parse_transceiver_dom_sensor(output_lines): + """ + @summary: Parse the list of transceiver from DB table TRANSCEIVER_DOM_SENSOR content + @param output_lines: DB table TRANSCEIVER_DOM_SENSOR content output by 'redis' command + @return: Return parsed transceivers in a list + """ + res = [] + p = re.compile(r"TRANSCEIVER_DOM_SENSOR\|(Ethernet\d+)") + for line in output_lines: + m = p.match(line) + assert m, "Unexpected line %s" % line + res.append(m.group(1)) + return res + + +def test_xcvr_info_in_db(localhost, ansible_adhoc, testbed): + """ + @summary: This test case is to verify that xcvrd works as expected by checking transcever information in DB + """ + hostname = testbed['dut'] + ans_host = ansible_host(ansible_adhoc, hostname) + localhost.command("who") + lab_conn_graph_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), \ + "../../ansible/files/lab_connection_graph.xml") + conn_graph_facts = localhost.conn_graph_facts(host=hostname, filename=lab_conn_graph_file).\ + contacted['localhost']['ansible_facts'] + + logging.info("Check whether transceiver information of all ports are in redis") + xcvr_info = ans_host.command("redis-cli -n 6 keys TRANSCEIVER_INFO*") + parsed_xcvr_info = parse_transceiver_info(xcvr_info["stdout_lines"]) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_xcvr_info, "TRANSCEIVER INFO of %s is not found in DB" % intf + + logging.info("Check detailed transceiver information of each connected port") + expected_fields = ["type", "hardwarerev", "serialnum", "manufacturename", "modelname"] + for intf in conn_graph_facts["device_conn"]: + port_xcvr_info = ans_host.command('redis-cli -n 6 hgetall "TRANSCEIVER_INFO|%s"' % intf) + for field in expected_fields: + assert port_xcvr_info["stdout"].find(field) >= 0, \ + "Expected field %s is not found in %s while checking %s" % (field, port_xcvr_info["stdout"], intf) + + logging.info("Check whether TRANSCEIVER_DOM_SENSOR of all ports in redis") + xcvr_dom_senspor = ans_host.command("redis-cli -n 6 keys TRANSCEIVER_DOM_SENSOR*") + parsed_xcvr_dom_senspor = parse_transceiver_dom_sensor(xcvr_dom_senspor["stdout_lines"]) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_xcvr_dom_senspor, "TRANSCEIVER_DOM_SENSOR of %s is not found in DB" % intf + + logging.info("Check detailed TRANSCEIVER_DOM_SENSOR information of each connected ports") + expected_fields = ["temperature", "voltage", "rx1power", "rx2power", "rx3power", "rx4power", "tx1bias", + "tx2bias", "tx3bias", "tx4bias", "tx1power", "tx2power", "tx3power", "tx4power"] + for intf in conn_graph_facts["device_conn"]: + port_xcvr_dom_sensor = ans_host.command('redis-cli -n 6 hgetall "TRANSCEIVER_DOM_SENSOR|%s"' % intf) + for field in expected_fields: + assert port_xcvr_dom_sensor["stdout"].find(field) >= 0, \ + "Expected field %s is not found in %s while checking %s" % (field, port_xcvr_dom_sensor["stdout"], intf) From ec7cffc6ae27e2238d2de7112723c90a0c174097 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Sat, 25 May 2019 18:14:31 +0800 Subject: [PATCH 2/4] [platform] Add mellanox_psu_controller.py Changes: * Add mellanox_psu_controller.py which has Mellanox implementation of PSU controller. * Increase the delay between reset SFP and checking SFP presence for SFP to be fully recovered. * Improve the checking of PSU status. * Correct spelling errors. Signed-off-by: Xin Wang --- .../mellanox/mellanox_psu_controller.py | 269 ++++++++++++++++++ tests/platform/test_platform_info.py | 4 +- tests/platform/test_sfp.py | 2 +- tests/platform/test_xcvr_info_in_db.py | 2 +- 4 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 tests/platform/mellanox/mellanox_psu_controller.py diff --git a/tests/platform/mellanox/mellanox_psu_controller.py b/tests/platform/mellanox/mellanox_psu_controller.py new file mode 100644 index 00000000000..683d6729b6e --- /dev/null +++ b/tests/platform/mellanox/mellanox_psu_controller.py @@ -0,0 +1,269 @@ +""" +Mellanox specific PSU controller + +This script contains illustrative functions and class for creating PSU controller based on Mellanox lab configuration. + +Some actual configurations were or replaced with dummy configurations. +""" +import logging +import subprocess + +import paramiko + +from psu_controller import PsuControllerBase + + +def run_local_cmd(cmd): + """ + @summary: Helper function for run command on localhost -- the sonic-mgmt container + @param cmd: Command to be executed + @return: Returns whatever output to stdout by the command + @raise: Raise an exception if the command return code is not 0. + """ + process = subprocess.Popen(cmd.split(), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate() + ret_code = process.returncode + + if ret_code != 0: + raise Exception("ret_code=%d, error message=%s. cmd=%s" % (ret_code, stderr, ' '.join(cmd))) + + return stdout + + +def connect_mellanox_server(): + """ + @summary: Connect to a server on Mellanox lab network via SSH + @return: Returns a paramiko.client.SSHClient object which can be used for running commands + """ + mellanox_server = None + try: + mellanox_server = paramiko.client.SSHClient() + mellanox_server.set_missing_host_key_policy(paramiko.client.AutoAddPolicy()) + mellanox_server.connect("a_mellanox_server", username="username", password="password") + except Exception as e: + logging.debug("Failed to connect to mellanox server, exception: " + repr(e)) + return mellanox_server + + +def find_psu_controller_conf_file(server): + """ + @summary: Find the exact location of the configuration file which contains mapping between PSU controllers and DUT + switches. + @param server: The paramiko.client.SSHClient object connected to a Mellanox server + @return: Returns the exact path of the configuration file + """ + result = None + try: + locations = ("/path1", "/path2") + config_file_name = "psu_controller_configuration_file.txt" + for location in locations: + _, stdout, stderr = server.exec_command("find %s -name %s" % (location, config_file_name)) + + lines = stdout.readlines() + if len(lines) > 0: + result = lines[0].strip() + break + except paramiko.SSHException as e: + logging.debug("Failed to find psu controller configuration file location, exception: " + repr(e)) + return result + + +def get_psu_controller_host(hostname, server, conf_file_location): + """ + @summary: Check the configuration file to find out IP address of the PDU controlling power to PSUs of DUT. + @param hostname: Hostname of the SONiC DUT + @param server: The paramiko.client.SSHClient object connected to a Mellanox server + @param conf_file_location: Exact path of the configuration file on the Mellanox server + @return: Returns IP address of the PDU controlling power to PSUs of DUT + """ + result = None + try: + _, stdout, stderr = server.exec_command("grep %s %s" % (hostname, conf_file_location)) + for line in stdout.readlines(): + fields = line.strip().split() + if len(fields) == 2: + result = fields[1] + break + except paramiko.SSHException as e: + logging.debug("Failed to get psu controller host, exception: " + repr(e)) + return result + + +def get_psu_controller_type(psu_controller_host): + """ + @summary: Use SNMP to get the type of PSU controller host + @param psu_controller_host: IP address of PSU controller host + @return: Returns type string of the specified PSU controller host + """ + result = None + cmd = "snmpget -v 1 -c public -Ofenqv %s .1.3.6.1.2.1.1.1.0" % psu_controller_host + try: + stdout = run_local_cmd(cmd) + + lines = stdout.splitlines() + if len(lines) > 0: + result = lines[0].strip() + result = result.replace('"', '') + except Exception as e: + logging.debug("Failed to get psu controller type, exception: " + repr(e)) + + return result + + +class SentrySwitchedCDU(PsuControllerBase): + """ + PSU Controller class for 'Sentry Switched CDU' + + This class implements the interface defined in PsuControllerBase class for PDU type 'Sentry Switched CDU' + """ + PORT_NAME_BASE_OID = ".1.3.6.1.4.1.1718.3.2.3.1.3.1" + PORT_STATUS_BASE_OID = ".1.3.6.1.4.1.1718.3.2.3.1.5.1" + PORT_CONTROL_BASE_OID = ".1.3.6.1.4.1.1718.3.2.3.1.11.1" + STATUS_ON = "1" + STATUS_OFF = "0" + CONTROL_ON = "1" + CONTROL_OFF = "2" + + def _get_psu_ports(self): + """ + @summary: Helper method for getting PDU ports connected to PSUs of DUT + """ + try: + cmd = "snmpwalk -v 1 -c public -Ofenq %s %s " % (self.controller, self.PORT_NAME_BASE_OID) + stdout = run_local_cmd(cmd) + for line in stdout.splitlines(): + if self.hostname in line: + fields = line.split() + if len(fields) == 2: + # Remove the preceeding PORT_NAME_BASE_OID, remaining string is the PDU port ID + self.pdu_ports.append(fields[0].replace(self.PORT_NAME_BASE_OID, '')) + except Exception as e: + logging.debug("Failed to get ports controlling PSUs of DUT, exception: " + repr(e)) + + def __init__(self, hostname, controller): + PsuControllerBase.__init__(self) + self.hostname = hostname + self.controller = controller + self.pdu_ports = [] + self._get_psu_ports() + logging.info("Initialized " + self.__class__.__name__) + + def turn_on_psu(self, psu_id): + """ + @summary: Use SNMP to turn on power to PSU of DUT specified by psu_id + + There is a limitation in the Mellanox configuration. Currently we can just find out which PDU ports are + connected to PSUs of which DUT. But we cannot find out the exact mapping between PDU ports and PSUs of DUT. + + To overcome this limitation, the trick is to convert the specified psu_id to integer, then calculate the mode + upon the number of PSUs on DUT. The calculated mode is used as an index to get PDU ports ID stored in + self.pdu_ports. + + @param psu_id: ID of the PSU on SONiC DUT + @return: Return true if successfully execute the command for turning on power. Otherwise return False. + """ + try: + idx = int(psu_id) % len(self.pdu_ports) + port_oid = self.PORT_CONTROL_BASE_OID + self.pdu_ports[idx] + cmd = "snmpset -v1 -C q -c private %s %s i %s" % (self.controller, port_oid, self.CONTROL_ON) + run_local_cmd(cmd) + logging.info("Turned on PSU %s" % str(psu_id)) + return True + except Exception as e: + logging.debug("Failed to turn on PSU %s, exception: %s" % (str(psu_id), repr(e))) + return False + + def turn_off_psu(self, psu_id): + """ + @summary: Use SNMP to turn off power to PSU of DUT specified by psu_id + + There is a limitation in the Mellanox configuration. Currently we can just find out which PDU ports are + connected to PSUs of which DUT. But we cannot find out the exact mapping between PDU ports and PSUs of DUT. + + To overcome this limitation, the trick is to convert the specified psu_id to integer, then calculate the mode + upon the number of PSUs on DUT. The calculated mode is used as an index to get PDU ports ID stored in + self.pdu_ports. + + @param psu_id: ID of the PSU on SONiC DUT + @return: Return true if successfully execute the command for turning off power. Otherwise return False. + """ + try: + idx = int(psu_id) % len(self.pdu_ports) + port_oid = self.PORT_CONTROL_BASE_OID + self.pdu_ports[idx] + cmd = "snmpset -v1 -C q -c private %s %s i %s" % (self.controller, port_oid, self.CONTROL_OFF) + run_local_cmd(cmd) + logging.info("Turned off PSU %s" % str(psu_id)) + return True + except Exception as e: + logging.debug("Failed to turn off PSU %s, exception: %s" % (str(psu_id), repr(e))) + return False + + def get_psu_status(self, psu_id=None): + """ + @summary: Use SNMP to get status of PDU ports supplying power to PSUs of DUT + + There is a limitation in the Mellanox configuration. Currently we can just find out which PDU ports are + connected to PSUs of which DUT. But we cannot find out the exact mapping between PDU ports and PSUs of DUT. + + To overcome this limitation, the trick is to convert the specified psu_id to integer, then calculate the mode + upon the number of PSUs on DUT. The calculated mode is used as an index to get PDU ports ID stored in + self.pdu_ports. + + @param psu_id: Optional. If specified, only return status of PDU port connected to specified PSU of DUT. If + omitted, return status of all PDU ports connected to PSUs of DUT. + @return: Return status of PDU ports connected to PSUs of DUT in a list of dictionary. Example result: + [{"psu_id": 0, "psu_on": True}, {"psu_id": 1, "psu_on": True}] + The psu_id in returned result is integer starts from 0. + """ + results = [] + try: + cmd = "snmpwalk -v 1 -c public -Ofenq %s %s " % (self.controller, self.PORT_STATUS_BASE_OID) + stdout = run_local_cmd(cmd) + for line in stdout.splitlines(): + for idx, port in enumerate(self.pdu_ports): + port_oid = self.PORT_STATUS_BASE_OID + port + if port_oid in line: + fields = line.strip().split() + if len(fields) == 2: + status = {"psu_id": idx, "psu_on": True if fields[1] == self.STATUS_ON else False} + results.append(status) + if psu_id is not None: + idx = int(psu_id) % len(self.pdu_ports) + results = results[idx:idx+1] + logging.info("Got PSU status: %s" % str(results)) + except Exception as e: + logging.debug("Failed to get psu status, exception: " + repr(e)) + return results + + def close(self): + pass + + +def make_mellanox_psu_controller(hostname): + """ + @summary: For creating different type of PSU controller based on Mellanox lab configuration. + @param hostname: Hostname of the SONiC DUT + @return: Returns an instance of PSU controller + """ + mellanox_server = connect_mellanox_server() + if not mellanox_server: + return None + + conf_file_location = find_psu_controller_conf_file(mellanox_server) + logging.info("conf_file_location: %s" % conf_file_location) + if not conf_file_location: + return None + + psu_controller_host = get_psu_controller_host(hostname, mellanox_server, conf_file_location) + logging.info("psu_controller_host: %s" % psu_controller_host) + if not psu_controller_host: + return None + + psu_controller_type = get_psu_controller_type(psu_controller_host) + logging.info("psu_controller_type: %s" % psu_controller_type) + if not psu_controller_type: + return None + + if "Sentry Switched CDU" in psu_controller_type: + logging.info("Initializing PSU controller instance") + return SentrySwitchedCDU(hostname, psu_controller_host) diff --git a/tests/platform/test_platform_info.py b/tests/platform/test_platform_info.py index 1c5f99e1576..c3f4c38deaf 100644 --- a/tests/platform/test_platform_info.py +++ b/tests/platform/test_platform_info.py @@ -56,7 +56,7 @@ def test_turn_on_off_psu_and_check_psustatus(localhost, ansible_adhoc, testbed, ans_host = ansible_host(ansible_adhoc, hostname) platform_info = parse_platform_summary(ans_host.command("show platform summary")["stdout_lines"]) - psu_line_pattern = re.compile(r"PSU\s+\d+\s+(OK|NOT OK)") + psu_line_pattern = re.compile(r"PSU\s+\d+\s+(OK|NOT OK|NOT PRESENT)") logging.info("Check whether the DUT has enough PSUs for this testing") psu_num_out = ans_host.command("sudo psuutil numpsus") @@ -105,7 +105,7 @@ def test_turn_on_off_psu_and_check_psustatus(localhost, ansible_adhoc, testbed, for line in cli_psu_status["stdout_lines"][2:]: assert psu_line_pattern.match(line), "Unexpected PSU status output" fields = line.split() - if " ".join(fields[2:]) == "NOT OK": + if fields[2] != "OK": psu_under_test = fields[1] assert psu_under_test is not None, "No PSU is turned off" diff --git a/tests/platform/test_sfp.py b/tests/platform/test_sfp.py index e6f82b75d8f..0a2bd2b8182 100644 --- a/tests/platform/test_sfp.py +++ b/tests/platform/test_sfp.py @@ -93,7 +93,7 @@ def test_check_sfp_status_and_configure_sfp(localhost, ansible_adhoc, testbed): for intf in conn_graph_facts["device_conn"]: reset_result = ans_host.command("sudo sfputil reset " + intf) assert reset_result["rc"] == 0, "'sudo sfputil reset %s failed" % intf - time.sleep(10) # Wait some time for SFP to fully recover after reset + time.sleep(120) # Wait some time for SFP to fully recover after reset logging.info("Check sfp presence again after reset") sfp_presence = ans_host.command("sudo sfputil show presence") diff --git a/tests/platform/test_xcvr_info_in_db.py b/tests/platform/test_xcvr_info_in_db.py index 142fbd1d101..1d50f5c4333 100644 --- a/tests/platform/test_xcvr_info_in_db.py +++ b/tests/platform/test_xcvr_info_in_db.py @@ -43,7 +43,7 @@ def parse_transceiver_dom_sensor(output_lines): def test_xcvr_info_in_db(localhost, ansible_adhoc, testbed): """ - @summary: This test case is to verify that xcvrd works as expected by checking transcever information in DB + @summary: This test case is to verify that xcvrd works as expected by checking transceiver information in DB """ hostname = testbed['dut'] ans_host = ansible_host(ansible_adhoc, hostname) From c67c6e1df20947e43af8e5c179d2a6621b3805ce Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Mon, 3 Jun 2019 14:35:19 +0800 Subject: [PATCH 3/4] [platform] Improve scripts according to review comments * Replace inline command strings with predefined variables * Add test case for testing SFP low power mode Signed-off-by: Xin Wang --- tests/platform/test_platform_info.py | 36 +++++---- tests/platform/test_sfp.py | 116 ++++++++++++++++++++++----- 2 files changed, 115 insertions(+), 37 deletions(-) diff --git a/tests/platform/test_platform_info.py b/tests/platform/test_platform_info.py index c3f4c38deaf..26262b31aa5 100644 --- a/tests/platform/test_platform_info.py +++ b/tests/platform/test_platform_info.py @@ -14,6 +14,11 @@ from psu_controller import psu_controller +CMD_PLATFORM_SUMMARY = "show platform summary" +CMD_PLATFORM_PSUSTATUS = "show platform psustatus" +CMD_PLATFORM_SYSEEPROM = "show platform syseeprom" + + def test_show_platform_summary(localhost, ansible_adhoc, testbed): """ @summary: Check output of 'show platform summary' @@ -21,8 +26,8 @@ def test_show_platform_summary(localhost, ansible_adhoc, testbed): hostname = testbed['dut'] ans_host = ansible_host(ansible_adhoc, hostname) - logging.info("Check output of 'show platform summary'") - platform_summary = ans_host.command("show platform summary") + logging.info("Check output of '%s'" % CMD_PLATFORM_SUMMARY) + platform_summary = ans_host.command(CMD_PLATFORM_SUMMARY) expected_fields = set(["Platform", "HwSKU", "ASIC"]) actual_fields = set() for line in platform_summary["stdout_lines"]: @@ -41,8 +46,8 @@ def test_show_platform_psustatus(localhost, ansible_adhoc, testbed): hostname = testbed['dut'] ans_host = ansible_host(ansible_adhoc, hostname) - logging.info("Check PSU status using 'show platform psustatus', hostname: " + hostname) - psu_status = ans_host.command("show platform psustatus") + logging.info("Check PSU status using '%s', hostname: %s" % (CMD_PLATFORM_PSUSTATUS, hostname)) + psu_status = ans_host.command(CMD_PLATFORM_PSUSTATUS) psu_line_pattern = re.compile(r"PSU\s+\d+\s+(OK|NOT OK)") for line in psu_status["stdout_lines"][2:]: assert psu_line_pattern.match(line), "Unexpected PSU status output" @@ -54,17 +59,18 @@ def test_turn_on_off_psu_and_check_psustatus(localhost, ansible_adhoc, testbed, """ hostname = testbed['dut'] ans_host = ansible_host(ansible_adhoc, hostname) - platform_info = parse_platform_summary(ans_host.command("show platform summary")["stdout_lines"]) + platform_info = parse_platform_summary(ans_host.command(CMD_PLATFORM_SUMMARY)["stdout_lines"]) psu_line_pattern = re.compile(r"PSU\s+\d+\s+(OK|NOT OK|NOT PRESENT)") + cmd_num_psu = "sudo psuutil numpsus" logging.info("Check whether the DUT has enough PSUs for this testing") - psu_num_out = ans_host.command("sudo psuutil numpsus") + psu_num_out = ans_host.command(cmd_num_psu) psu_num = 0 try: psu_num = int(psu_num_out["stdout"]) except: - assert False, "Unable to get the number of PSUs using command 'psuutil numpsus'" + assert False, "Unable to get the number of PSUs using command '%s'" % cmd_num_psu if psu_num < 2: pytest.skip("At least 2 PSUs required for rest of the testing in this case") @@ -82,7 +88,7 @@ def test_turn_on_off_psu_and_check_psustatus(localhost, ansible_adhoc, testbed, time.sleep(5) logging.info("Initialize test results") - cli_psu_status = ans_host.command("show platform psustatus") + cli_psu_status = ans_host.command(CMD_PLATFORM_PSUSTATUS) psu_test_results = {} for line in cli_psu_status["stdout_lines"][2:]: fields = line.split() @@ -90,7 +96,7 @@ def test_turn_on_off_psu_and_check_psustatus(localhost, ansible_adhoc, testbed, if " ".join(fields[2:]) == "NOT OK": pytest.skip("Some PSUs are still not powered, it is not safe to proceed, skip testing") assert len(psu_test_results.keys()) == psu_num, \ - "In consistent PSU number output by 'show platform psustatus' and 'psuutil numpsus'" + "In consistent PSU number output by '%s' and '%s'" % (CMD_PLATFORM_PSUSTATUS, cmd_num_psu) logging.info("Start testing turn off/on PSUs") all_psu_status = psu_ctrl.get_psu_status() @@ -101,7 +107,7 @@ def test_turn_on_off_psu_and_check_psustatus(localhost, ansible_adhoc, testbed, psu_ctrl.turn_off_psu(psu["psu_id"]) time.sleep(5) - cli_psu_status = ans_host.command("show platform psustatus") + cli_psu_status = ans_host.command(CMD_PLATFORM_PSUSTATUS) for line in cli_psu_status["stdout_lines"][2:]: assert psu_line_pattern.match(line), "Unexpected PSU status output" fields = line.split() @@ -113,7 +119,7 @@ def test_turn_on_off_psu_and_check_psustatus(localhost, ansible_adhoc, testbed, psu_ctrl.turn_on_psu(psu["psu_id"]) time.sleep(5) - cli_psu_status = ans_host.command("show platform psustatus") + cli_psu_status = ans_host.command(CMD_PLATFORM_PSUSTATUS) for line in cli_psu_status["stdout_lines"][2:]: assert psu_line_pattern.match(line), "Unexpected PSU status output" fields = line.split() @@ -148,9 +154,9 @@ def test_show_platform_syseeprom(localhost, ansible_adhoc, testbed): ans_host = ansible_host(ansible_adhoc, hostname) logging.info("Check output of 'show platform syseeprom'") - platform_info = parse_platform_summary(ans_host.command("show platform summary")["stdout_lines"]) - show_output = ans_host.command("show platform syseeprom") - assert show_output["rc"] == 0, "Run command 'show platform syseeprom' failed" + platform_info = parse_platform_summary(ans_host.command(CMD_PLATFORM_SUMMARY)["stdout_lines"]) + show_output = ans_host.command(CMD_PLATFORM_SYSEEPROM) + assert show_output["rc"] == 0, "Run command '%s' failed" % CMD_PLATFORM_SYSEEPROM if platform_info["asic"] in ["mellanox"]: expected_fields = [ "Product Name", @@ -174,4 +180,4 @@ def test_show_platform_syseeprom(localhost, ansible_adhoc, testbed): assert utility_cmd_output["stdout"].find(field) >= 0, "Expected field %s is not found" % field assert show_output["stdout"].find(utility_cmd_output["stdout"]) >= 0, \ - "Output of 'show platform syseeprom' is inconsistent with output of eeprom.py utility" + "Output of '%s' is inconsistent with output of eeprom.py utility" % CMD_PLATFORM_SYSEEPROM diff --git a/tests/platform/test_sfp.py b/tests/platform/test_sfp.py index 0a2bd2b8182..078cf24170d 100644 --- a/tests/platform/test_sfp.py +++ b/tests/platform/test_sfp.py @@ -8,13 +8,14 @@ import re import os import time +import copy from ansible_host import ansible_host -def parse_presence(output_lines): +def parse_output(output_lines): """ - @summary: Parse the SFP presence information from command output + @summary: For parsing command output. The output lines should have format 'key value'. @param output_lines: Command output lines @return: Returns result in a dictionary """ @@ -61,43 +62,114 @@ def test_check_sfp_status_and_configure_sfp(localhost, ansible_adhoc, testbed): conn_graph_facts = localhost.conn_graph_facts(host=hostname, filename=lab_conn_graph_file).\ contacted['localhost']['ansible_facts'] - logging.info("Check output of 'sfputil show presence'") - sfp_presence = ans_host.command("sudo sfputil show presence") - parsed_presence = parse_presence(sfp_presence["stdout_lines"][2:]) + cmd_sfp_presence = "sudo sfputil show presence" + cmd_sfp_eeprom = "sudo sfputil show eeprom" + cmd_sfp_reset = "sudo sfputil reset" + cmd_xcvr_presence = "show interface transceiver presence" + cmd_xcvr_eeprom = "show interface transceiver eeprom" + + logging.info("Check output of '%s'" % cmd_sfp_presence) + sfp_presence = ans_host.command(cmd_sfp_presence) + parsed_presence = parse_output(sfp_presence["stdout_lines"][2:]) for intf in conn_graph_facts["device_conn"]: - assert intf in parsed_presence, "Interface is not in output of 'sfputil show presence'" + assert intf in parsed_presence, "Interface is not in output of '%s'" % cmd_sfp_presence assert parsed_presence[intf] == "Present", "Interface presence is not 'Present'" - logging.info("Check output of 'show interface transceiver presence'") - sfp_presence = ans_host.command("show interface transceiver presence") - parsed_presence = parse_presence(sfp_presence["stdout_lines"][2:]) + logging.info("Check output of '%s'" % cmd_xcvr_presence) + xcvr_presence = ans_host.command(cmd_xcvr_presence) + parsed_presence = parse_output(xcvr_presence["stdout_lines"][2:]) for intf in conn_graph_facts["device_conn"]: - assert intf in parsed_presence, "Interface is not in output of 'show interface transceiver presence'" + assert intf in parsed_presence, "Interface is not in output of '%s'" % cmd_xcvr_presence assert parsed_presence[intf] == "Present", "Interface presence is not 'Present'" - logging.info("Check output of 'sfputil show eeprom'") - sfp_eeprom = ans_host.command("sudo sfputil show eeprom") + logging.info("Check output of '%s'" % cmd_sfp_eeprom) + sfp_eeprom = ans_host.command(cmd_sfp_eeprom) parsed_eeprom = parse_eeprom(sfp_eeprom["stdout_lines"]) for intf in conn_graph_facts["device_conn"]: assert intf in parsed_eeprom, "Interface is not in output of 'sfputil show eeprom'" assert parsed_eeprom[intf] == "SFP EEPROM detected" - logging.info("Check output of 'show interface transceiver eeprom'") - sfp_eeprom = ans_host.command("show interface transceiver eeprom") - parsed_eeprom = parse_eeprom(sfp_eeprom["stdout_lines"]) + logging.info("Check output of '%s'" % cmd_xcvr_eeprom) + xcvr_eeprom = ans_host.command(cmd_xcvr_eeprom) + parsed_eeprom = parse_eeprom(xcvr_eeprom["stdout_lines"]) for intf in conn_graph_facts["device_conn"]: - assert intf in parsed_eeprom, "Interface is not in output of 'show interface transceiver eeprom'" + assert intf in parsed_eeprom, "Interface is not in output of '%s'" % cmd_xcvr_eeprom assert parsed_eeprom[intf] == "SFP EEPROM detected" - logging.info("Test 'sfputil reset '") + logging.info("Test '%s '" % cmd_sfp_reset) for intf in conn_graph_facts["device_conn"]: - reset_result = ans_host.command("sudo sfputil reset " + intf) - assert reset_result["rc"] == 0, "'sudo sfputil reset %s failed" % intf + reset_result = ans_host.command("%s %s" % (cmd_sfp_reset, intf)) + assert reset_result["rc"] == 0, "'%s %s' failed" % (cmd_sfp_reset, intf) time.sleep(120) # Wait some time for SFP to fully recover after reset logging.info("Check sfp presence again after reset") - sfp_presence = ans_host.command("sudo sfputil show presence") - parsed_presence = parse_presence(sfp_presence["stdout_lines"][2:]) + sfp_presence = ans_host.command(cmd_sfp_presence) + parsed_presence = parse_output(sfp_presence["stdout_lines"][2:]) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_presence, "Interface is not in output of '%s'" % cmd_sfp_presence + assert parsed_presence[intf] == "Present", "Interface presence is not 'Present'" + + +def test_check_sfp_low_power_mode(localhost, ansible_adhoc, testbed): + """ + @summary: Check SFP low power mode + + This case is to use the sfputil tool command to check and set SFP low power mode + * sfputil show lpmode + * sfputil lpmode off + * sfputil lpmode on + """ + hostname = testbed['dut'] + ans_host = ansible_host(ansible_adhoc, hostname) + localhost.command("who") + lab_conn_graph_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), \ + "../../ansible/files/lab_connection_graph.xml") + conn_graph_facts = localhost.conn_graph_facts(host=hostname, filename=lab_conn_graph_file).\ + contacted['localhost']['ansible_facts'] + + cmd_sfp_presence = "sudo sfputil show presence" + cmd_sfp_show_lpmode = "sudo sfputil show lpmode" + cmd_sfp_set_lpmode = "sudo sfputil lpmode" + + logging.info("Check output of '%s'" % cmd_sfp_show_lpmode) + lpmode_show = ans_host.command(cmd_sfp_show_lpmode) + parsed_lpmode = parse_output(lpmode_show["stdout_lines"][2:]) + original_lpmode = copy.deepcopy(parsed_lpmode) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_lpmode, "Interface is not in output of '%s'" % cmd_sfp_show_lpmode + assert parsed_lpmode[intf].lower() == "on" or parsed_lpmode[intf].lower() == "off", "Unexpected SFP lpmode" + + logging.info("Try to change SFP lpmode") + for intf in conn_graph_facts["device_conn"]: + new_lpmode = "off" if original_lpmode[intf].lower() == "on" else "on" + lpmode_set_result = ans_host.command("%s %s %s" % (cmd_sfp_set_lpmode, new_lpmode, intf)) + assert lpmode_set_result["rc"] == 0, "'%s %s %s' failed" % (cmd_sfp_set_lpmode, new_lpmode, intf) + time.sleep(10) + + logging.info("Check SFP lower power mode again after changing SFP lpmode") + lpmode_show = ans_host.command(cmd_sfp_show_lpmode) + parsed_lpmode = parse_output(lpmode_show["stdout_lines"][2:]) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_lpmode, "Interface is not in output of '%s'" % cmd_sfp_show_lpmode + assert parsed_lpmode[intf].lower() == "on" or parsed_lpmode[intf].lower() == "off", "Unexpected SFP lpmode" + + logging.info("Try to change SFP lpmode") + for intf in conn_graph_facts["device_conn"]: + new_lpmode = original_lpmode[intf].lower() + lpmode_set_result = ans_host.command("%s %s %s" % (cmd_sfp_set_lpmode, new_lpmode, intf)) + assert lpmode_set_result["rc"] == 0, "'%s %s %s' failed" % (cmd_sfp_set_lpmode, new_lpmode, intf) + time.sleep(10) + + logging.info("Check SFP lower power mode again after changing SFP lpmode") + lpmode_show = ans_host.command(cmd_sfp_show_lpmode) + parsed_lpmode = parse_output(lpmode_show["stdout_lines"][2:]) + for intf in conn_graph_facts["device_conn"]: + assert intf in parsed_lpmode, "Interface is not in output of '%s'" % cmd_sfp_show_lpmode + assert parsed_lpmode[intf].lower() == "on" or parsed_lpmode[intf].lower() == "off", "Unexpected SFP lpmode" + + logging.info("Check sfp presence again after setting lpmode") + sfp_presence = ans_host.command(cmd_sfp_presence) + parsed_presence = parse_output(sfp_presence["stdout_lines"][2:]) for intf in conn_graph_facts["device_conn"]: - assert intf in parsed_presence, "Interface is not in output of 'sfputil show presence'" + assert intf in parsed_presence, "Interface is not in output of '%s'" % cmd_sfp_presence assert parsed_presence[intf] == "Present", "Interface presence is not 'Present'" From 3f941ff2814d7e4d4cb9f855724022d3cfd29da5 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Sat, 22 Jun 2019 22:01:50 +0800 Subject: [PATCH 4/4] [platform] Fix the issue of comparing syseeprom output The order of information output by command "show platform syseeprom" is not guranteed. This commit improve the method of comparing the content output by syseeprom plugin and the show command to avoid the failure caused by inconsistent output order. Signed-off-by: Xin Wang --- tests/platform/test_platform_info.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/platform/test_platform_info.py b/tests/platform/test_platform_info.py index 26262b31aa5..1873fb336cf 100644 --- a/tests/platform/test_platform_info.py +++ b/tests/platform/test_platform_info.py @@ -153,7 +153,7 @@ def test_show_platform_syseeprom(localhost, ansible_adhoc, testbed): hostname = testbed['dut'] ans_host = ansible_host(ansible_adhoc, hostname) - logging.info("Check output of 'show platform syseeprom'") + logging.info("Check output of '%s'" % CMD_PLATFORM_SYSEEPROM) platform_info = parse_platform_summary(ans_host.command(CMD_PLATFORM_SUMMARY)["stdout_lines"]) show_output = ans_host.command(CMD_PLATFORM_SYSEEPROM) assert show_output["rc"] == 0, "Run command '%s' failed" % CMD_PLATFORM_SYSEEPROM @@ -179,5 +179,6 @@ def test_show_platform_syseeprom(localhost, ansible_adhoc, testbed): assert show_output["stdout"].find(field) >= 0, "Expected field %s is not found" % field assert utility_cmd_output["stdout"].find(field) >= 0, "Expected field %s is not found" % field - assert show_output["stdout"].find(utility_cmd_output["stdout"]) >= 0, \ - "Output of '%s' is inconsistent with output of eeprom.py utility" % CMD_PLATFORM_SYSEEPROM + for line in utility_cmd_output["stdout_lines"]: + assert line in show_output["stdout"], \ + "Line %s is not found in output of '%s'" % (line, CMD_PLATFORM_SYSEEPROM)