From 1c902a721bda748b7ac9dbc4a3f4ccc2b76febf3 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Thu, 2 May 2024 15:18:03 -0600 Subject: [PATCH 1/7] Add port speed and duplex tests Add unit tests for port stats testing Add place holder for ip port link test --- .../src/net_orc/network_orchestrator.py | 7 ++ modules/test/conn/conf/module_config.json | 18 +++ .../test/conn/python/src/connection_module.py | 32 ++++- .../test/conn/python/src/port_stats_util.py | 99 +++++++++++++++ testing/unit/conn/conn_module_test.py | 118 ++++++++++++++++++ .../ethtool/ethtool_results_compliant.txt | 30 +++++ .../ethtool_results_no_autononegotiation.txt | 30 +++++ .../ethtool/ethtool_results_noncompliant.txt | 30 +++++ testing/unit/run_tests.sh | 4 + 9 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 modules/test/conn/python/src/port_stats_util.py create mode 100644 testing/unit/conn/conn_module_test.py create mode 100644 testing/unit/conn/ethtool/ethtool_results_compliant.txt create mode 100644 testing/unit/conn/ethtool/ethtool_results_no_autononegotiation.txt create mode 100644 testing/unit/conn/ethtool/ethtool_results_noncompliant.txt diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index aa23f1918..854ab7c86 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -203,6 +203,13 @@ def _device_discovered(self, mac_addr): with open(runtime_device_conf, 'w', encoding='utf-8') as f: json.dump(self._session.get_target_device().to_config_json(), f, indent=2) + # Extract information about the physical connection + dev_int = self._session.get_device_interface() + response = util.run_command(f'ethtool {dev_int}') + eth_out_file = os.path.join(NET_DIR, 'ethtool_results.txt') + with open(eth_out_file, 'w', encoding='utf-8') as f: + f.write(response[0]) + if device.ip_addr is None: LOGGER.info( f'Timed out whilst waiting for {mac_addr} to obtain an IP address') diff --git a/modules/test/conn/conf/module_config.json b/modules/test/conn/conf/module_config.json index 888a06c48..5289e7eb0 100644 --- a/modules/test/conn/conf/module_config.json +++ b/modules/test/conn/conf/module_config.json @@ -13,6 +13,24 @@ "timeout": 1800 }, "tests": [ + { + "name": "connection.port_link", + "test_description": "The network switch port connected to the device has an active link without errors", + "expected_behavior": "When the etherent cable is connected to the port, the device triggers the port to its enabled \"Link UP\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \"show interface\" command on most network switches.", + "required_result": "Required" + }, + { + "name": "connection.port_speed", + "test_description": "The network switch port connected to the device has auto-negotiated a speed that is 10 Mbps or higher", + "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \"show interface\" command on most network switches. The output of this command must also show that the \"configured speed\" is set to \"auto\".", + "required_result": "Required" + }, + { + "name": "connection.port_duplex", + "test_description": "The network switch port connected to the device has auto-negotiated full-duplex", + "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.", + "required_result": "Required" + }, { "name": "connection.switch.arp_inspection", "test_description": "The device implements ARP correctly as per RFC826", diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 34e129103..897544ab3 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -20,15 +20,17 @@ from dhcp1.client import Client as DHCPClient1 from dhcp2.client import Client as DHCPClient2 from dhcp_util import DHCPUtil +from port_stats_util import PortStatsUtil LOG_NAME = 'test_connection' -LOGGER = None OUI_FILE = '/usr/local/etc/oui.txt' STARTUP_CAPTURE_FILE = '/runtime/device/startup.pcap' MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap' DHCP_CAPTURE_FILE = '/runtime/network/dhcp-1.pcap' +ETHTOOL_RESULTS_FILE = 'runtime/network/ethtool_results.txt' SLAAC_PREFIX = 'fd10:77be:4186' TR_CONTAINER_MAC_PREFIX = '9a:02:57:1e:8f:' +LOGGER = None # Should be at least twice as much as the max lease time # set in the DHCP server @@ -38,10 +40,20 @@ class ConnectionModule(TestModule): """Connection Test module""" - def __init__(self, module): - super().__init__(module_name=module, log_name=LOG_NAME) + def __init__(self, + module, + log_dir=None, + conf_file=None, + results_dir=None, + ethtool_results_file=ETHTOOL_RESULTS_FILE): + super().__init__(module_name=module, + log_name=LOG_NAME, + log_dir=log_dir, + conf_file=conf_file, + results_dir=results_dir) global LOGGER LOGGER = self._get_logger() + self._port_stats = PortStatsUtil(logger=LOGGER,ethtool_results_file=ethtool_results_file) self.dhcp1_client = DHCPClient1() self.dhcp2_client = DHCPClient2() self._dhcp_util = DHCPUtil(self.dhcp1_client, self.dhcp2_client, LOGGER) @@ -74,6 +86,20 @@ def __init__(self, module): # response = self.dhcp1_client.set_dhcp_range('10.10.10.20','10.10.10.30') # print("Set Range: " + str(response)) + def _connection_port_link(self): + LOGGER.info('Running connection.port_link') + return self._port_stats.connection_port_link_test() + + + def _connection_port_speed(self): + LOGGER.info('Running connection.port_speed') + return self._port_stats.connection_port_speed_test() + + def _connection_port_duplex(self): + LOGGER.info('Running connection.port_duplex') + return self._port_stats.connection_port_duplex_test() + + def _connection_switch_arp_inspection(self): LOGGER.info('Running connection.switch.arp_inspection') diff --git a/modules/test/conn/python/src/port_stats_util.py b/modules/test/conn/python/src/port_stats_util.py new file mode 100644 index 000000000..53edfdc87 --- /dev/null +++ b/modules/test/conn/python/src/port_stats_util.py @@ -0,0 +1,99 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Module that contains various methods for validating the Port statistics """ + + +class PortStatsUtil(): + """Helper class for various tests concerning Port behavior""" + ETHTOOL_RESULTS_FILE = 'runtime/network/ethtool_results.txt' + + LOG_NAME = 'port_stats_util' + LOGGER = None + + def __init__(self, logger, ethtool_results_file=ETHTOOL_RESULTS_FILE): + self.ethtool_results_file = ethtool_results_file + global LOGGER + LOGGER = logger + self.ethtool_results = self._read_ethtool_results_file() + + def is_autonegotiate(self): + auto_negotiation = False + auto_negotiation_status = self._get_ethtool_option('Auto-negotiation:') + if auto_negotiation_status is not None: + auto_negotiation = 'on' in auto_negotiation_status + return auto_negotiation + + def connection_port_link_test(self): + return None, '', '' + + def connection_port_duplex_test(self): + auto_negotiation = self.is_autonegotiate() + # Calculate final results + result = None + description = '' + details = '' + if not auto_negotiation: + result = False + description = 'Interface not configured for auto-negotiation' + else: + duplex = self._get_ethtool_option('Duplex:') + if 'Full' in duplex: + result = True + description = 'Succesfully auto-negotiated full duplex' + details = f'Duplex negotiated: {duplex}' + else: + result = False + description = 'Failed to auto-negotate full duplex' + details = f'Duplex negotiated: {duplex}' + return result, description, details + + def connection_port_speed_test(self): + auto_negotiation = self.is_autonegotiate() + # Calculate final results + result = None + description = '' + details = '' + if not auto_negotiation: + result = False + description = 'Interface not configured for auto-negotiation' + else: + speed = self._get_ethtool_option('Speed:') + if speed in ('100Mb/s', '1000Mb/s'): + result = True + description = 'Succesfully auto-negotiated speeds above 10 Mbps' + details = f'Speed negotiated: {speed}' + else: + result = False + description = 'Failed to auto-negotate speeds above 10 Mbps' + details = f'Speed negotiated: {speed}' + return result, description, details + + def _get_ethtool_option(self, option): + value = None + """Extract the requested parameter from the ethtool result""" + for line in self.ethtool_results.split('\n'): + #LOGGER.info(f'Checking option: {line}') + if line.startswith(f'{option}'): + value = line.split(':')[1].strip() + break + return value + + def _read_ethtool_results_file(self): + with open(self.ethtool_results_file) as f: + ethtool_results = f.read() + #Cleanup the results for easier processing + lines = ethtool_results.split('\n') + cleaned_lines = [line.strip() for line in lines if line.strip()] + recombined_text = '\n'.join(cleaned_lines) + return recombined_text diff --git a/testing/unit/conn/conn_module_test.py b/testing/unit/conn/conn_module_test.py new file mode 100644 index 000000000..bdd35e058 --- /dev/null +++ b/testing/unit/conn/conn_module_test.py @@ -0,0 +1,118 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Module run all the Connection module related unit tests""" +from port_stats_util import PortStatsUtil +import os +import unittest +from common import logger + +MODULE = 'conn' +# Define the file paths +TEST_FILES_DIR = 'testing/unit/' + MODULE +ETHTOOL_RESULTS_COMPLIANT_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', + 'ethtool_results_compliant.txt') +ETHTOOL_RESULTS_NONCOMPLIANT_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', + 'ethtool_results_noncompliant.txt') +ETHTOOL_RESULTS_NO_AUTO_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', + 'ethtool_results_no_autononegotiation.txt') +LOGGER = None + + +class ConnectionModuleTest(unittest.TestCase): + """Contains and runs all the unit tests concerning Connection module behaviors""" + + @classmethod + def setUpClass(cls): + global LOGGER + LOGGER = logger.get_logger('unit_test_' + MODULE) + + # Test the port link status + def connection_port_link_compliant_test(self): + LOGGER.info('connection_port_link_compliant_test') + p_stats = PortStatsUtil(logger=LOGGER, + ethtool_results_file=ETHTOOL_RESULTS_COMPLIANT_FILE) + result = p_stats.connection_port_link_test() + LOGGER.info(result) + + # Test the port duplex setting + def connection_port_duplex_compliant_test(self): + LOGGER.info('connection_port_duplex_compliant_test') + p_stats = PortStatsUtil(logger=LOGGER, + ethtool_results_file=ETHTOOL_RESULTS_COMPLIANT_FILE) + result = p_stats.connection_port_duplex_test() + LOGGER.info(result) + self.assertEqual(result[0], True) + + # Test the port speed + def connection_port_speed_compliant_test(self): + LOGGER.info('connection_port_speed_compliant_test') + p_stats = PortStatsUtil(logger=LOGGER, + ethtool_results_file=ETHTOOL_RESULTS_COMPLIANT_FILE) + result = p_stats.connection_port_speed_test() + LOGGER.info(result) + self.assertEqual(result[0], True) + + # Test the port link status non-compliant + def connection_port_link_noncompliant_test(self): + LOGGER.info('connection_port_link_noncompliant_test') + p_stats = PortStatsUtil(logger=LOGGER, + ethtool_results_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) + result = p_stats.connection_port_link_test() + LOGGER.info(result) + + # Test the port duplex setting non-compliant + def connection_port_duplex_noncompliant_test(self): + LOGGER.info('connection_port_duplex_noncompliant_test') + p_stats = PortStatsUtil(logger=LOGGER, + ethtool_results_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) + result = p_stats.connection_port_duplex_test() + LOGGER.info(result) + self.assertEqual(result[0], False) + + # Test the port speed non-compliant + def connection_port_speed_noncompliant_test(self): + LOGGER.info('connection_port_speed_noncompliant_test') + p_stats = PortStatsUtil(logger=LOGGER, + ethtool_results_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) + result = p_stats.connection_port_speed_test() + LOGGER.info(result) + self.assertEqual(result[0], False) + + # Test the autonegotiation failure test + def connection_port_speed_autonegotiation_fail_test(self): + LOGGER.info('connection_port_speed_autonegotiation_fail_test') + p_stats = PortStatsUtil(logger=LOGGER, + ethtool_results_file=ETHTOOL_RESULTS_NO_AUTO_FILE) + result = p_stats.connection_port_speed_test() + LOGGER.info(result) + self.assertEqual(result[0], False) + +if __name__ == '__main__': + suite = unittest.TestSuite() + + # Compliant port stats tests + suite.addTest(ConnectionModuleTest('connection_port_link_compliant_test')) + suite.addTest(ConnectionModuleTest('connection_port_duplex_compliant_test')) + suite.addTest(ConnectionModuleTest('connection_port_speed_compliant_test')) + + # Non-compliant port stats tests + suite.addTest(ConnectionModuleTest('connection_port_link_noncompliant_test')) + suite.addTest(ConnectionModuleTest('connection_port_duplex_noncompliant_test')) + suite.addTest(ConnectionModuleTest('connection_port_speed_noncompliant_test')) + + # Autonegotiation off failure test + suite.addTest(ConnectionModuleTest('connection_port_speed_autonegotiation_fail_test')) + + runner = unittest.TextTestRunner() + runner.run(suite) diff --git a/testing/unit/conn/ethtool/ethtool_results_compliant.txt b/testing/unit/conn/ethtool/ethtool_results_compliant.txt new file mode 100644 index 000000000..911e8912f --- /dev/null +++ b/testing/unit/conn/ethtool/ethtool_results_compliant.txt @@ -0,0 +1,30 @@ +Settings for enx123456789: + Supported ports: [ TP MII ] + Supported link modes: 10baseT/Half 10baseT/Full + 100baseT/Half 100baseT/Full + 1000baseT/Half 1000baseT/Full + Supported pause frame use: No + Supports auto-negotiation: Yes + Supported FEC modes: Not reported + Advertised link modes: 10baseT/Half 10baseT/Full + 100baseT/Half 100baseT/Full + 1000baseT/Full + Advertised pause frame use: Symmetric Receive-only + Advertised auto-negotiation: Yes + Advertised FEC modes: Not reported + Link partner advertised link modes: 10baseT/Full + 100baseT/Half 100baseT/Full + Link partner advertised pause frame use: Symmetric + Link partner advertised auto-negotiation: Yes + Link partner advertised FEC modes: Not reported + Speed: 100Mb/s + Duplex: Full + Port: MII + PHYAD: 32 + Transceiver: internal + Auto-negotiation: on + Supports Wake-on: pumbg + Wake-on: g + Current message level: 0x00007fff (32767) + drv probe link timer ifdown ifup rx_err tx_err tx_queued intr tx_done rx_status pktdata hw wol + Link detected: yes \ No newline at end of file diff --git a/testing/unit/conn/ethtool/ethtool_results_no_autononegotiation.txt b/testing/unit/conn/ethtool/ethtool_results_no_autononegotiation.txt new file mode 100644 index 000000000..76dd4cf5b --- /dev/null +++ b/testing/unit/conn/ethtool/ethtool_results_no_autononegotiation.txt @@ -0,0 +1,30 @@ +Settings for enx123456789: + Supported ports: [ TP MII ] + Supported link modes: 10baseT/Half 10baseT/Full + 100baseT/Half 100baseT/Full + 1000baseT/Half 1000baseT/Full + Supported pause frame use: No + Supports auto-negotiation: Yes + Supported FEC modes: Not reported + Advertised link modes: 10baseT/Half 10baseT/Full + 100baseT/Half 100baseT/Full + 1000baseT/Full + Advertised pause frame use: Symmetric Receive-only + Advertised auto-negotiation: Yes + Advertised FEC modes: Not reported + Link partner advertised link modes: 10baseT/Full + 100baseT/Half 100baseT/Full + Link partner advertised pause frame use: Symmetric + Link partner advertised auto-negotiation: Yes + Link partner advertised FEC modes: Not reported + Speed: 100Mb/s + Duplex: Full + Port: MII + PHYAD: 32 + Transceiver: internal + Auto-negotiation: off + Supports Wake-on: pumbg + Wake-on: g + Current message level: 0x00007fff (32767) + drv probe link timer ifdown ifup rx_err tx_err tx_queued intr tx_done rx_status pktdata hw wol + Link detected: yes \ No newline at end of file diff --git a/testing/unit/conn/ethtool/ethtool_results_noncompliant.txt b/testing/unit/conn/ethtool/ethtool_results_noncompliant.txt new file mode 100644 index 000000000..76dd4cf5b --- /dev/null +++ b/testing/unit/conn/ethtool/ethtool_results_noncompliant.txt @@ -0,0 +1,30 @@ +Settings for enx123456789: + Supported ports: [ TP MII ] + Supported link modes: 10baseT/Half 10baseT/Full + 100baseT/Half 100baseT/Full + 1000baseT/Half 1000baseT/Full + Supported pause frame use: No + Supports auto-negotiation: Yes + Supported FEC modes: Not reported + Advertised link modes: 10baseT/Half 10baseT/Full + 100baseT/Half 100baseT/Full + 1000baseT/Full + Advertised pause frame use: Symmetric Receive-only + Advertised auto-negotiation: Yes + Advertised FEC modes: Not reported + Link partner advertised link modes: 10baseT/Full + 100baseT/Half 100baseT/Full + Link partner advertised pause frame use: Symmetric + Link partner advertised auto-negotiation: Yes + Link partner advertised FEC modes: Not reported + Speed: 100Mb/s + Duplex: Full + Port: MII + PHYAD: 32 + Transceiver: internal + Auto-negotiation: off + Supports Wake-on: pumbg + Wake-on: g + Current message level: 0x00007fff (32767) + drv probe link timer ifdown ifup rx_err tx_err tx_queued intr tx_done rx_status pktdata hw wol + Link detected: yes \ No newline at end of file diff --git a/testing/unit/run_tests.sh b/testing/unit/run_tests.sh index e471405b8..5ce00454d 100644 --- a/testing/unit/run_tests.sh +++ b/testing/unit/run_tests.sh @@ -27,6 +27,7 @@ PYTHONPATH="$PWD/framework/python/src:$PWD/framework/python/src/common" # Add the test module sources PYTHONPATH="$PYTHONPATH:$PWD/modules/test/base/python/src" +PYTHONPATH="$PYTHONPATH:$PWD/modules/test/conn/python/src" PYTHONPATH="$PYTHONPATH:$PWD/modules/test/tls/python/src" PYTHONPATH="$PYTHONPATH:$PWD/modules/test/dns/python/src" PYTHONPATH="$PYTHONPATH:$PWD/modules/test/nmap/python/src" @@ -39,6 +40,9 @@ export PYTHONPATH python3 -u $PWD/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py python3 -u $PWD/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py +# Run the Conn Module Unit Tests +python3 -u $PWD/testing/unit/conn/conn_module_test.py + # Run the TLS Module Unit Tests python3 -u $PWD/testing/unit/tls/tls_module_test.py From 7558c3872e0e5c56cf9176ed643fe143806b67e8 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Fri, 3 May 2024 09:56:12 -0600 Subject: [PATCH 2/7] Add ethtool to system dependencies Restructure conn stats methods and tests Resolve port stat information for link test --- cmd/prepare | 2 +- framework/python/src/net_orc/ip_control.py | 16 +++ .../src/net_orc/network_orchestrator.py | 34 ++++- .../test/conn/python/src/port_stats_util.py | 22 ++-- testing/unit/conn/conn_module_test.py | 14 +- testing/unit/run_tests.sh | 120 +++++++++--------- 6 files changed, 123 insertions(+), 85 deletions(-) diff --git a/cmd/prepare b/cmd/prepare index 252d505a0..9e68f734d 100755 --- a/cmd/prepare +++ b/cmd/prepare @@ -20,6 +20,6 @@ echo Installing system dependencies # Install system dependencies -sudo apt-get update && sudo apt-get install openvswitch-common openvswitch-switch python3 libpangocairo-1.0-0 +sudo apt-get update && sudo apt-get install openvswitch-common openvswitch-switch python3 libpangocairo-1.0-0 ethtool echo Finished installing system dependencies diff --git a/framework/python/src/net_orc/ip_control.py b/framework/python/src/net_orc/ip_control.py index 1eeafc1a9..6c93e7433 100644 --- a/framework/python/src/net_orc/ip_control.py +++ b/framework/python/src/net_orc/ip_control.py @@ -81,6 +81,22 @@ def get_links(self): netns_links.append(interface_name.strip()) return netns_links + def get_iface_connection_stats(self, iface): + """Extract information about the physical connection""" + response = util.run_command(f'ethtool {iface}') + if len(response[1]) == 0: + return response[0] + else: + return None + + def get_iface_port_stats(self, iface): + """Extract information about packets connection""" + response = util.run_command(f'ethtool -S {iface}') + if len(response[1]) == 0: + return response[0] + else: + return None + def get_namespaces(self): result = util.run_command('ip netns list') #Strip ID's from the namespace results diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index 854ab7c86..fe31911a8 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -173,6 +173,7 @@ def _device_discovered(self, mac_addr): # Ignore discovered device return + self._get_port_stats(pre_monitor=True) self._monitor_in_progress = True LOGGER.debug( @@ -203,12 +204,7 @@ def _device_discovered(self, mac_addr): with open(runtime_device_conf, 'w', encoding='utf-8') as f: json.dump(self._session.get_target_device().to_config_json(), f, indent=2) - # Extract information about the physical connection - dev_int = self._session.get_device_interface() - response = util.run_command(f'ethtool {dev_int}') - eth_out_file = os.path.join(NET_DIR, 'ethtool_results.txt') - with open(eth_out_file, 'w', encoding='utf-8') as f: - f.write(response[0]) + self._get_conn_stats() if device.ip_addr is None: LOGGER.info( @@ -223,6 +219,31 @@ def _device_discovered(self, mac_addr): self._start_device_monitor(device) + def _get_conn_stats(self): + """ Extract information about the physical connection + and store it to a file for the conn test module to access""" + dev_int = self._session.get_device_interface() + conn_stats = self._ip_ctrl.get_iface_connection_stats(dev_int) + if conn_stats is not None: + eth_out_file = os.path.join(NET_DIR, 'ethtool_conn_stats.txt') + with open(eth_out_file, 'w', encoding='utf-8') as f: + f.write(conn_stats) + else: + LOGGER.error('Failed to generate connection stats') + + def _get_port_stats(self,pre_monitor=True): + """ Extract information about the port statistics + and store it to a file for the conn test module to access""" + dev_int = self._session.get_device_interface() + port_stats = self._ip_ctrl.get_iface_port_stats(dev_int) + if port_stats is not None: + prefix = 'pre_monitor' if pre_monitor else 'post_test' + eth_out_file = os.path.join(NET_DIR, f'{prefix}_ethtool_port_stats.txt') + with open(eth_out_file, 'w', encoding='utf-8') as f: + f.write(port_stats) + else: + LOGGER.error('Failed to generate port stats') + def monitor_in_progress(self): return self._monitor_in_progress @@ -268,6 +289,7 @@ def _start_device_monitor(self, device): wrpcap(os.path.join(device_runtime_dir, 'monitor.pcap'), self._monitor_packets) self._monitor_in_progress = False + self._get_port_stats(pre_monitor=False) self.get_listener().call_callback(NetworkEvent.DEVICE_STABLE, device.mac_addr) diff --git a/modules/test/conn/python/src/port_stats_util.py b/modules/test/conn/python/src/port_stats_util.py index 53edfdc87..9568db241 100644 --- a/modules/test/conn/python/src/port_stats_util.py +++ b/modules/test/conn/python/src/port_stats_util.py @@ -16,20 +16,20 @@ class PortStatsUtil(): """Helper class for various tests concerning Port behavior""" - ETHTOOL_RESULTS_FILE = 'runtime/network/ethtool_results.txt' + ETHTOOL_CONN_STATS_FILE = 'runtime/network/ethtool_conn_stats.txt' LOG_NAME = 'port_stats_util' LOGGER = None - def __init__(self, logger, ethtool_results_file=ETHTOOL_RESULTS_FILE): - self.ethtool_results_file = ethtool_results_file + def __init__(self, logger, ethtool_conn_stats_file=ETHTOOL_CONN_STATS_FILE): + self.ethtool_conn_stats_file = ethtool_conn_stats_file global LOGGER LOGGER = logger - self.ethtool_results = self._read_ethtool_results_file() + self.conn_stats = self._read_ethtool_conn_stats_file() def is_autonegotiate(self): auto_negotiation = False - auto_negotiation_status = self._get_ethtool_option('Auto-negotiation:') + auto_negotiation_status = self._get_conn_stat_option('Auto-negotiation:') if auto_negotiation_status is not None: auto_negotiation = 'on' in auto_negotiation_status return auto_negotiation @@ -47,7 +47,7 @@ def connection_port_duplex_test(self): result = False description = 'Interface not configured for auto-negotiation' else: - duplex = self._get_ethtool_option('Duplex:') + duplex = self._get_conn_stat_option('Duplex:') if 'Full' in duplex: result = True description = 'Succesfully auto-negotiated full duplex' @@ -68,7 +68,7 @@ def connection_port_speed_test(self): result = False description = 'Interface not configured for auto-negotiation' else: - speed = self._get_ethtool_option('Speed:') + speed = self._get_conn_stat_option('Speed:') if speed in ('100Mb/s', '1000Mb/s'): result = True description = 'Succesfully auto-negotiated speeds above 10 Mbps' @@ -79,18 +79,18 @@ def connection_port_speed_test(self): details = f'Speed negotiated: {speed}' return result, description, details - def _get_ethtool_option(self, option): + def _get_conn_stat_option(self, option): value = None """Extract the requested parameter from the ethtool result""" - for line in self.ethtool_results.split('\n'): + for line in self.conn_stats.split('\n'): #LOGGER.info(f'Checking option: {line}') if line.startswith(f'{option}'): value = line.split(':')[1].strip() break return value - def _read_ethtool_results_file(self): - with open(self.ethtool_results_file) as f: + def _read_ethtool_conn_stats_file(self): + with open(self.ethtool_conn_stats_file) as f: ethtool_results = f.read() #Cleanup the results for easier processing lines = ethtool_results.split('\n') diff --git a/testing/unit/conn/conn_module_test.py b/testing/unit/conn/conn_module_test.py index bdd35e058..5f5fc27dd 100644 --- a/testing/unit/conn/conn_module_test.py +++ b/testing/unit/conn/conn_module_test.py @@ -41,7 +41,7 @@ def setUpClass(cls): def connection_port_link_compliant_test(self): LOGGER.info('connection_port_link_compliant_test') p_stats = PortStatsUtil(logger=LOGGER, - ethtool_results_file=ETHTOOL_RESULTS_COMPLIANT_FILE) + ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE) result = p_stats.connection_port_link_test() LOGGER.info(result) @@ -49,7 +49,7 @@ def connection_port_link_compliant_test(self): def connection_port_duplex_compliant_test(self): LOGGER.info('connection_port_duplex_compliant_test') p_stats = PortStatsUtil(logger=LOGGER, - ethtool_results_file=ETHTOOL_RESULTS_COMPLIANT_FILE) + ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE) result = p_stats.connection_port_duplex_test() LOGGER.info(result) self.assertEqual(result[0], True) @@ -58,7 +58,7 @@ def connection_port_duplex_compliant_test(self): def connection_port_speed_compliant_test(self): LOGGER.info('connection_port_speed_compliant_test') p_stats = PortStatsUtil(logger=LOGGER, - ethtool_results_file=ETHTOOL_RESULTS_COMPLIANT_FILE) + ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE) result = p_stats.connection_port_speed_test() LOGGER.info(result) self.assertEqual(result[0], True) @@ -67,7 +67,7 @@ def connection_port_speed_compliant_test(self): def connection_port_link_noncompliant_test(self): LOGGER.info('connection_port_link_noncompliant_test') p_stats = PortStatsUtil(logger=LOGGER, - ethtool_results_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) + ethtool_conn_stats_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) result = p_stats.connection_port_link_test() LOGGER.info(result) @@ -75,7 +75,7 @@ def connection_port_link_noncompliant_test(self): def connection_port_duplex_noncompliant_test(self): LOGGER.info('connection_port_duplex_noncompliant_test') p_stats = PortStatsUtil(logger=LOGGER, - ethtool_results_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) + ethtool_conn_stats_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) result = p_stats.connection_port_duplex_test() LOGGER.info(result) self.assertEqual(result[0], False) @@ -84,7 +84,7 @@ def connection_port_duplex_noncompliant_test(self): def connection_port_speed_noncompliant_test(self): LOGGER.info('connection_port_speed_noncompliant_test') p_stats = PortStatsUtil(logger=LOGGER, - ethtool_results_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) + ethtool_conn_stats_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) result = p_stats.connection_port_speed_test() LOGGER.info(result) self.assertEqual(result[0], False) @@ -93,7 +93,7 @@ def connection_port_speed_noncompliant_test(self): def connection_port_speed_autonegotiation_fail_test(self): LOGGER.info('connection_port_speed_autonegotiation_fail_test') p_stats = PortStatsUtil(logger=LOGGER, - ethtool_results_file=ETHTOOL_RESULTS_NO_AUTO_FILE) + ethtool_conn_stats_file=ETHTOOL_RESULTS_NO_AUTO_FILE) result = p_stats.connection_port_speed_test() LOGGER.info(result) self.assertEqual(result[0], False) diff --git a/testing/unit/run_tests.sh b/testing/unit/run_tests.sh index 5ce00454d..2658636dd 100644 --- a/testing/unit/run_tests.sh +++ b/testing/unit/run_tests.sh @@ -1,61 +1,61 @@ -#!/bin/bash -e - -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This script should be run from within the unit_test directory. If -# it is run outside this directory, paths will not be resolved correctly. - -# Move into the root directory of test-run -pushd ../../ >/dev/null 2>&1 - -echo "Root dir: $PWD" - -# Add the framework sources -PYTHONPATH="$PWD/framework/python/src:$PWD/framework/python/src/common" - -# Add the test module sources -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/base/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/conn/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/tls/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/dns/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/nmap/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/ntp/python/src" - -# Set the python path with all sources -export PYTHONPATH - -# Run the DHCP Unit tests -python3 -u $PWD/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py -python3 -u $PWD/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py - -# Run the Conn Module Unit Tests -python3 -u $PWD/testing/unit/conn/conn_module_test.py - -# Run the TLS Module Unit Tests -python3 -u $PWD/testing/unit/tls/tls_module_test.py - -# Run the DNS Module Unit Tests -python3 -u $PWD/testing/unit/dns/dns_module_test.py - -# Run the NMAP Module Unit Tests -python3 -u $PWD/testing/unit/nmap/nmap_module_test.py - -# Run the NTP Module Unit Tests -python3 -u $PWD/testing/unit/ntp/ntp_module_test.py - -# # Run the Report Unit Tests -python3 -u $PWD/testing/unit/report/report_test.py - +#!/bin/bash -e + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script should be run from within the unit_test directory. If +# it is run outside this directory, paths will not be resolved correctly. + +# Move into the root directory of test-run +pushd ../../ >/dev/null 2>&1 + +echo "Root dir: $PWD" + +# Add the framework sources +PYTHONPATH="$PWD/framework/python/src:$PWD/framework/python/src/common" + +# Add the test module sources +PYTHONPATH="$PYTHONPATH:$PWD/modules/test/base/python/src" +PYTHONPATH="$PYTHONPATH:$PWD/modules/test/conn/python/src" +PYTHONPATH="$PYTHONPATH:$PWD/modules/test/tls/python/src" +PYTHONPATH="$PYTHONPATH:$PWD/modules/test/dns/python/src" +PYTHONPATH="$PYTHONPATH:$PWD/modules/test/nmap/python/src" +PYTHONPATH="$PYTHONPATH:$PWD/modules/test/ntp/python/src" + +# Set the python path with all sources +export PYTHONPATH + +# Run the DHCP Unit tests +python3 -u $PWD/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py +python3 -u $PWD/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py + +# Run the Conn Module Unit Tests +python3 -u $PWD/testing/unit/conn/conn_module_test.py + +# Run the TLS Module Unit Tests +python3 -u $PWD/testing/unit/tls/tls_module_test.py + +# Run the DNS Module Unit Tests +python3 -u $PWD/testing/unit/dns/dns_module_test.py + +# Run the NMAP Module Unit Tests +python3 -u $PWD/testing/unit/nmap/nmap_module_test.py + +# Run the NTP Module Unit Tests +python3 -u $PWD/testing/unit/ntp/ntp_module_test.py + +# # Run the Report Unit Tests +python3 -u $PWD/testing/unit/report/report_test.py + popd >/dev/null 2>&1 \ No newline at end of file From 76f46798755fb00121e8e0daafc27cd938cb436d Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Fri, 3 May 2024 10:41:49 -0600 Subject: [PATCH 3/7] Fix runtime issue --- modules/test/conn/python/src/connection_module.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 897544ab3..dc0452c8a 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -27,7 +27,6 @@ STARTUP_CAPTURE_FILE = '/runtime/device/startup.pcap' MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap' DHCP_CAPTURE_FILE = '/runtime/network/dhcp-1.pcap' -ETHTOOL_RESULTS_FILE = 'runtime/network/ethtool_results.txt' SLAAC_PREFIX = 'fd10:77be:4186' TR_CONTAINER_MAC_PREFIX = '9a:02:57:1e:8f:' LOGGER = None @@ -44,8 +43,7 @@ def __init__(self, module, log_dir=None, conf_file=None, - results_dir=None, - ethtool_results_file=ETHTOOL_RESULTS_FILE): + results_dir=None): super().__init__(module_name=module, log_name=LOG_NAME, log_dir=log_dir, @@ -53,7 +51,7 @@ def __init__(self, results_dir=results_dir) global LOGGER LOGGER = self._get_logger() - self._port_stats = PortStatsUtil(logger=LOGGER,ethtool_results_file=ethtool_results_file) + self._port_stats = PortStatsUtil(logger=LOGGER) self.dhcp1_client = DHCPClient1() self.dhcp2_client = DHCPClient2() self._dhcp_util = DHCPUtil(self.dhcp1_client, self.dhcp2_client, LOGGER) From e9cefab400eb37e334a05a48cec866afceed22e0 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Fri, 3 May 2024 13:53:59 -0600 Subject: [PATCH 4/7] Implement link test Fix unit tests Misc port stats updates --- .../src/net_orc/network_orchestrator.py | 4 +- .../test/conn/python/src/port_stats_util.py | 66 ++++++++++++++----- testing/unit/conn/conn_module_test.py | 17 ++++- .../ethtool_port_stats_post_monitor.txt | 14 ++++ ...l_port_stats_post_monitor_noncompliant.txt | 14 ++++ .../ethtool_port_stats_pre_monitor.txt | 14 ++++ .../ethtool/ethtool_results_noncompliant.txt | 6 +- 7 files changed, 111 insertions(+), 24 deletions(-) create mode 100644 testing/unit/conn/ethtool/ethtool_port_stats_post_monitor.txt create mode 100644 testing/unit/conn/ethtool/ethtool_port_stats_post_monitor_noncompliant.txt create mode 100644 testing/unit/conn/ethtool/ethtool_port_stats_pre_monitor.txt diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index fe31911a8..04740851f 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -237,8 +237,8 @@ def _get_port_stats(self,pre_monitor=True): dev_int = self._session.get_device_interface() port_stats = self._ip_ctrl.get_iface_port_stats(dev_int) if port_stats is not None: - prefix = 'pre_monitor' if pre_monitor else 'post_test' - eth_out_file = os.path.join(NET_DIR, f'{prefix}_ethtool_port_stats.txt') + suffix = 'pre_monitor' if pre_monitor else 'post_monitor' + eth_out_file = os.path.join(NET_DIR, f'ethtool_port_stats_{suffix}.txt') with open(eth_out_file, 'w', encoding='utf-8') as f: f.write(port_stats) else: diff --git a/modules/test/conn/python/src/port_stats_util.py b/modules/test/conn/python/src/port_stats_util.py index 9568db241..cbafdce2d 100644 --- a/modules/test/conn/python/src/port_stats_util.py +++ b/modules/test/conn/python/src/port_stats_util.py @@ -13,29 +13,59 @@ # limitations under the License. """Module that contains various methods for validating the Port statistics """ +import os class PortStatsUtil(): """Helper class for various tests concerning Port behavior""" ETHTOOL_CONN_STATS_FILE = 'runtime/network/ethtool_conn_stats.txt' + ETHTOOL_PORT_STATS_PRE_FILE = 'runtime/network/ethtool_port_stats_pre_monitor.txt' + ETHTOOL_PORT_STATS_POST_FILE = 'runtime/network/ethtool_port_stats_post_monitor.txt' LOG_NAME = 'port_stats_util' LOGGER = None - def __init__(self, logger, ethtool_conn_stats_file=ETHTOOL_CONN_STATS_FILE): + def __init__(self, logger, + ethtool_conn_stats_file=ETHTOOL_CONN_STATS_FILE, + ethtool_port_stats_pre_file=ETHTOOL_PORT_STATS_PRE_FILE, + ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_FILE): self.ethtool_conn_stats_file = ethtool_conn_stats_file + self.ethtool_port_stats_pre_file = ethtool_port_stats_pre_file + self.ethtool_port_stats_post_file = ethtool_port_stats_post_file global LOGGER LOGGER = logger - self.conn_stats = self._read_ethtool_conn_stats_file() + self.conn_stats = self._read_stats_file(self.ethtool_conn_stats_file) def is_autonegotiate(self): auto_negotiation = False - auto_negotiation_status = self._get_conn_stat_option('Auto-negotiation:') + auto_negotiation_status = self._get_stat_option(stats=self.conn_stats,option='Auto-negotiation:') if auto_negotiation_status is not None: auto_negotiation = 'on' in auto_negotiation_status return auto_negotiation def connection_port_link_test(self): - return None, '', '' + stats_pre = self._read_stats_file(self.ethtool_port_stats_pre_file) + stats_post = self._read_stats_file(self.ethtool_port_stats_post_file) + result = None + description = '' + details = '' + if stats_pre is None or stats_pre is None: + result = 'Error' + description = 'Port stats not available' + else: + tx_errors_pre = self._get_stat_option(stats=stats_pre,option='tx_errors:') + tx_errors_post = self._get_stat_option(stats=stats_post,option='tx_errors:') + rx_errors_pre = self._get_stat_option(stats=stats_pre,option='rx_errors:') + rx_errors_post = self._get_stat_option(stats=stats_post,option='rx_errors:') + tx_errors = int(tx_errors_post) - int(tx_errors_pre) + rx_errors = int(rx_errors_post) - int(rx_errors_pre) + if tx_errors > 0 or rx_errors > 0: + result = False + description = 'Port errors detected' + details = f'TX errors: {tx_errors}, RX errors: {rx_errors}' + else: + result = True + description = 'No port errors detected' + return result, description, details def connection_port_duplex_test(self): auto_negotiation = self.is_autonegotiate() @@ -47,7 +77,7 @@ def connection_port_duplex_test(self): result = False description = 'Interface not configured for auto-negotiation' else: - duplex = self._get_conn_stat_option('Duplex:') + duplex = self._get_stat_option(stats=self.conn_stats,option='Duplex:') if 'Full' in duplex: result = True description = 'Succesfully auto-negotiated full duplex' @@ -68,7 +98,7 @@ def connection_port_speed_test(self): result = False description = 'Interface not configured for auto-negotiation' else: - speed = self._get_conn_stat_option('Speed:') + speed = self._get_stat_option(stats=self.conn_stats,option='Speed:') if speed in ('100Mb/s', '1000Mb/s'): result = True description = 'Succesfully auto-negotiated speeds above 10 Mbps' @@ -79,21 +109,23 @@ def connection_port_speed_test(self): details = f'Speed negotiated: {speed}' return result, description, details - def _get_conn_stat_option(self, option): - value = None + def _get_stat_option(self, stats, option): """Extract the requested parameter from the ethtool result""" - for line in self.conn_stats.split('\n'): + value = None + for line in stats.split('\n'): #LOGGER.info(f'Checking option: {line}') if line.startswith(f'{option}'): value = line.split(':')[1].strip() break return value - def _read_ethtool_conn_stats_file(self): - with open(self.ethtool_conn_stats_file) as f: - ethtool_results = f.read() - #Cleanup the results for easier processing - lines = ethtool_results.split('\n') - cleaned_lines = [line.strip() for line in lines if line.strip()] - recombined_text = '\n'.join(cleaned_lines) - return recombined_text + def _read_stats_file(self, file): + if os.path.isfile(file): + with open(file) as f: + content = f.read() + # Cleanup the results for easier processing + lines = content.split('\n') + cleaned_lines = [line.strip() for line in lines if line.strip()] + recombined_text = '\n'.join(cleaned_lines) + return recombined_text + return None diff --git a/testing/unit/conn/conn_module_test.py b/testing/unit/conn/conn_module_test.py index 5f5fc27dd..e41401c3d 100644 --- a/testing/unit/conn/conn_module_test.py +++ b/testing/unit/conn/conn_module_test.py @@ -26,6 +26,13 @@ 'ethtool_results_noncompliant.txt') ETHTOOL_RESULTS_NO_AUTO_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', 'ethtool_results_no_autononegotiation.txt') + +ETHTOOL_PORT_STATS_PRE_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', + 'ethtool_port_stats_pre_monitor.txt') +ETHTOOL_PORT_STATS_POST_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', + 'ethtool_port_stats_post_monitor.txt') +ETHTOOL_PORT_STATS_POST_NONCOMPLIANT_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', + 'ethtool_port_stats_post_monitor_noncompliant.txt') LOGGER = None @@ -41,9 +48,12 @@ def setUpClass(cls): def connection_port_link_compliant_test(self): LOGGER.info('connection_port_link_compliant_test') p_stats = PortStatsUtil(logger=LOGGER, - ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE) + ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE, + ethtool_port_stats_pre_file=ETHTOOL_PORT_STATS_PRE_FILE, + ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_FILE) result = p_stats.connection_port_link_test() LOGGER.info(result) + self.assertEqual(result[0], True) # Test the port duplex setting def connection_port_duplex_compliant_test(self): @@ -67,9 +77,12 @@ def connection_port_speed_compliant_test(self): def connection_port_link_noncompliant_test(self): LOGGER.info('connection_port_link_noncompliant_test') p_stats = PortStatsUtil(logger=LOGGER, - ethtool_conn_stats_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) + ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE, + ethtool_port_stats_pre_file=ETHTOOL_PORT_STATS_PRE_FILE, + ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_NONCOMPLIANT_FILE) result = p_stats.connection_port_link_test() LOGGER.info(result) + self.assertEqual(result[0], False) # Test the port duplex setting non-compliant def connection_port_duplex_noncompliant_test(self): diff --git a/testing/unit/conn/ethtool/ethtool_port_stats_post_monitor.txt b/testing/unit/conn/ethtool/ethtool_port_stats_post_monitor.txt new file mode 100644 index 000000000..a4e93ed47 --- /dev/null +++ b/testing/unit/conn/ethtool/ethtool_port_stats_post_monitor.txt @@ -0,0 +1,14 @@ +NIC statistics: + tx_packets: 124 + rx_packets: 216 + tx_errors: 0 + rx_errors: 0 + rx_missed: 0 + align_errors: 0 + tx_single_collisions: 0 + tx_multi_collisions: 0 + rx_unicast: 23 + rx_broadcast: 83 + rx_multicast: 110 + tx_aborted: 0 + tx_underrun: 0 \ No newline at end of file diff --git a/testing/unit/conn/ethtool/ethtool_port_stats_post_monitor_noncompliant.txt b/testing/unit/conn/ethtool/ethtool_port_stats_post_monitor_noncompliant.txt new file mode 100644 index 000000000..1d4b19e54 --- /dev/null +++ b/testing/unit/conn/ethtool/ethtool_port_stats_post_monitor_noncompliant.txt @@ -0,0 +1,14 @@ +NIC statistics: + tx_packets: 124 + rx_packets: 216 + tx_errors: 5 + rx_errors: 12 + rx_missed: 0 + align_errors: 0 + tx_single_collisions: 0 + tx_multi_collisions: 0 + rx_unicast: 23 + rx_broadcast: 83 + rx_multicast: 110 + tx_aborted: 0 + tx_underrun: 0 \ No newline at end of file diff --git a/testing/unit/conn/ethtool/ethtool_port_stats_pre_monitor.txt b/testing/unit/conn/ethtool/ethtool_port_stats_pre_monitor.txt new file mode 100644 index 000000000..18e027efb --- /dev/null +++ b/testing/unit/conn/ethtool/ethtool_port_stats_pre_monitor.txt @@ -0,0 +1,14 @@ +NIC statistics: + tx_packets: 90 + rx_packets: 195 + tx_errors: 0 + rx_errors: 0 + rx_missed: 0 + align_errors: 0 + tx_single_collisions: 0 + tx_multi_collisions: 0 + rx_unicast: 12 + rx_broadcast: 78 + rx_multicast: 105 + tx_aborted: 0 + tx_underrun: 0 \ No newline at end of file diff --git a/testing/unit/conn/ethtool/ethtool_results_noncompliant.txt b/testing/unit/conn/ethtool/ethtool_results_noncompliant.txt index 76dd4cf5b..2ca90e7d0 100644 --- a/testing/unit/conn/ethtool/ethtool_results_noncompliant.txt +++ b/testing/unit/conn/ethtool/ethtool_results_noncompliant.txt @@ -17,12 +17,12 @@ Settings for enx123456789: Link partner advertised pause frame use: Symmetric Link partner advertised auto-negotiation: Yes Link partner advertised FEC modes: Not reported - Speed: 100Mb/s - Duplex: Full + Speed: 10Mb/s + Duplex: Half Port: MII PHYAD: 32 Transceiver: internal - Auto-negotiation: off + Auto-negotiation: on Supports Wake-on: pumbg Wake-on: g Current message level: 0x00007fff (32767) From a5a946d71001ed8a79e15fafd2b7e040e2e6069d Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Fri, 3 May 2024 14:06:05 -0600 Subject: [PATCH 5/7] fix pylint issues --- framework/python/src/net_orc/ip_control.py | 2 +- .../src/net_orc/network_orchestrator.py | 35 +++++---- .../test/conn/python/src/connection_module.py | 8 +-- .../test/conn/python/src/port_stats_util.py | 29 +++++--- testing/unit/conn/conn_module_test.py | 72 ++++++++++--------- 5 files changed, 77 insertions(+), 69 deletions(-) diff --git a/framework/python/src/net_orc/ip_control.py b/framework/python/src/net_orc/ip_control.py index 6c93e7433..506b23a95 100644 --- a/framework/python/src/net_orc/ip_control.py +++ b/framework/python/src/net_orc/ip_control.py @@ -42,7 +42,7 @@ def add_namespace(self, namespace): return success def check_interface_status(self, interface_name): - output = util.run_command(cmd=f'ip link show {interface_name}',output=True) + output = util.run_command(cmd=f'ip link show {interface_name}', output=True) if 'state DOWN ' in output[0]: return False else: diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index 04740851f..11c5918a9 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -231,7 +231,7 @@ def _get_conn_stats(self): else: LOGGER.error('Failed to generate connection stats') - def _get_port_stats(self,pre_monitor=True): + def _get_port_stats(self, pre_monitor=True): """ Extract information about the port statistics and store it to a file for the conn test module to access""" dev_int = self._session.get_device_interface() @@ -527,23 +527,22 @@ def _start_network_service(self, net_module): try: client = docker.from_env() net_module.container = client.containers.run( - net_module.image_name, - auto_remove=True, - cap_add=['NET_ADMIN'], - name=net_module.container_name, - hostname=net_module.container_name, - # Undetermined version of docker seems to have broken - # DNS configuration (/etc/resolv.conf) Re-add when/if - # this network is utilized and DNS issue is resolved - #network=PRIVATE_DOCKER_NET, - privileged=True, - detach=True, - mounts=net_module.mounts, - environment={ - 'TZ': self.get_session().get_timezone(), - 'HOST_USER': util.get_host_user() - } - ) + net_module.image_name, + auto_remove=True, + cap_add=['NET_ADMIN'], + name=net_module.container_name, + hostname=net_module.container_name, + # Undetermined version of docker seems to have broken + # DNS configuration (/etc/resolv.conf) Re-add when/if + # this network is utilized and DNS issue is resolved + #network=PRIVATE_DOCKER_NET, + privileged=True, + detach=True, + mounts=net_module.mounts, + environment={ + 'TZ': self.get_session().get_timezone(), + 'HOST_USER': util.get_host_user() + }) except docker.errors.ContainerError as error: LOGGER.error('Container run error') LOGGER.error(error) diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index dc0452c8a..7df9aa3d9 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -39,11 +39,7 @@ class ConnectionModule(TestModule): """Connection Test module""" - def __init__(self, - module, - log_dir=None, - conf_file=None, - results_dir=None): + def __init__(self, module, log_dir=None, conf_file=None, results_dir=None): super().__init__(module_name=module, log_name=LOG_NAME, log_dir=log_dir, @@ -88,7 +84,6 @@ def _connection_port_link(self): LOGGER.info('Running connection.port_link') return self._port_stats.connection_port_link_test() - def _connection_port_speed(self): LOGGER.info('Running connection.port_speed') return self._port_stats.connection_port_speed_test() @@ -97,7 +92,6 @@ def _connection_port_duplex(self): LOGGER.info('Running connection.port_duplex') return self._port_stats.connection_port_duplex_test() - def _connection_switch_arp_inspection(self): LOGGER.info('Running connection.switch.arp_inspection') diff --git a/modules/test/conn/python/src/port_stats_util.py b/modules/test/conn/python/src/port_stats_util.py index cbafdce2d..e4208344f 100644 --- a/modules/test/conn/python/src/port_stats_util.py +++ b/modules/test/conn/python/src/port_stats_util.py @@ -15,6 +15,7 @@ import os + class PortStatsUtil(): """Helper class for various tests concerning Port behavior""" ETHTOOL_CONN_STATS_FILE = 'runtime/network/ethtool_conn_stats.txt' @@ -24,10 +25,11 @@ class PortStatsUtil(): LOG_NAME = 'port_stats_util' LOGGER = None - def __init__(self, logger, - ethtool_conn_stats_file=ETHTOOL_CONN_STATS_FILE, - ethtool_port_stats_pre_file=ETHTOOL_PORT_STATS_PRE_FILE, - ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_FILE): + def __init__(self, + logger, + ethtool_conn_stats_file=ETHTOOL_CONN_STATS_FILE, + ethtool_port_stats_pre_file=ETHTOOL_PORT_STATS_PRE_FILE, + ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_FILE): self.ethtool_conn_stats_file = ethtool_conn_stats_file self.ethtool_port_stats_pre_file = ethtool_port_stats_pre_file self.ethtool_port_stats_post_file = ethtool_port_stats_post_file @@ -37,7 +39,8 @@ def __init__(self, logger, def is_autonegotiate(self): auto_negotiation = False - auto_negotiation_status = self._get_stat_option(stats=self.conn_stats,option='Auto-negotiation:') + auto_negotiation_status = self._get_stat_option(stats=self.conn_stats, + option='Auto-negotiation:') if auto_negotiation_status is not None: auto_negotiation = 'on' in auto_negotiation_status return auto_negotiation @@ -52,10 +55,14 @@ def connection_port_link_test(self): result = 'Error' description = 'Port stats not available' else: - tx_errors_pre = self._get_stat_option(stats=stats_pre,option='tx_errors:') - tx_errors_post = self._get_stat_option(stats=stats_post,option='tx_errors:') - rx_errors_pre = self._get_stat_option(stats=stats_pre,option='rx_errors:') - rx_errors_post = self._get_stat_option(stats=stats_post,option='rx_errors:') + tx_errors_pre = self._get_stat_option(stats=stats_pre, + option='tx_errors:') + tx_errors_post = self._get_stat_option(stats=stats_post, + option='tx_errors:') + rx_errors_pre = self._get_stat_option(stats=stats_pre, + option='rx_errors:') + rx_errors_post = self._get_stat_option(stats=stats_post, + option='rx_errors:') tx_errors = int(tx_errors_post) - int(tx_errors_pre) rx_errors = int(rx_errors_post) - int(rx_errors_pre) if tx_errors > 0 or rx_errors > 0: @@ -77,7 +84,7 @@ def connection_port_duplex_test(self): result = False description = 'Interface not configured for auto-negotiation' else: - duplex = self._get_stat_option(stats=self.conn_stats,option='Duplex:') + duplex = self._get_stat_option(stats=self.conn_stats, option='Duplex:') if 'Full' in duplex: result = True description = 'Succesfully auto-negotiated full duplex' @@ -98,7 +105,7 @@ def connection_port_speed_test(self): result = False description = 'Interface not configured for auto-negotiation' else: - speed = self._get_stat_option(stats=self.conn_stats,option='Speed:') + speed = self._get_stat_option(stats=self.conn_stats, option='Speed:') if speed in ('100Mb/s', '1000Mb/s'): result = True description = 'Succesfully auto-negotiated speeds above 10 Mbps' diff --git a/testing/unit/conn/conn_module_test.py b/testing/unit/conn/conn_module_test.py index e41401c3d..339485936 100644 --- a/testing/unit/conn/conn_module_test.py +++ b/testing/unit/conn/conn_module_test.py @@ -21,18 +21,19 @@ # Define the file paths TEST_FILES_DIR = 'testing/unit/' + MODULE ETHTOOL_RESULTS_COMPLIANT_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', - 'ethtool_results_compliant.txt') -ETHTOOL_RESULTS_NONCOMPLIANT_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', - 'ethtool_results_noncompliant.txt') -ETHTOOL_RESULTS_NO_AUTO_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', - 'ethtool_results_no_autononegotiation.txt') - -ETHTOOL_PORT_STATS_PRE_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', - 'ethtool_port_stats_pre_monitor.txt') -ETHTOOL_PORT_STATS_POST_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', - 'ethtool_port_stats_post_monitor.txt') -ETHTOOL_PORT_STATS_POST_NONCOMPLIANT_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', - 'ethtool_port_stats_post_monitor_noncompliant.txt') + 'ethtool_results_compliant.txt') +ETHTOOL_RESULTS_NONCOMPLIANT_FILE = os.path.join( + TEST_FILES_DIR, 'ethtool', 'ethtool_results_noncompliant.txt') +ETHTOOL_RESULTS_NO_AUTO_FILE = os.path.join( + TEST_FILES_DIR, 'ethtool', 'ethtool_results_no_autononegotiation.txt') + +ETHTOOL_PORT_STATS_PRE_FILE = os.path.join( + TEST_FILES_DIR, 'ethtool', 'ethtool_port_stats_pre_monitor.txt') +ETHTOOL_PORT_STATS_POST_FILE = os.path.join( + TEST_FILES_DIR, 'ethtool', 'ethtool_port_stats_post_monitor.txt') +ETHTOOL_PORT_STATS_POST_NONCOMPLIANT_FILE = os.path.join( + TEST_FILES_DIR, 'ethtool', + 'ethtool_port_stats_post_monitor_noncompliant.txt') LOGGER = None @@ -47,10 +48,11 @@ def setUpClass(cls): # Test the port link status def connection_port_link_compliant_test(self): LOGGER.info('connection_port_link_compliant_test') - p_stats = PortStatsUtil(logger=LOGGER, - ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE, - ethtool_port_stats_pre_file=ETHTOOL_PORT_STATS_PRE_FILE, - ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_FILE) + p_stats = PortStatsUtil( + logger=LOGGER, + ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE, + ethtool_port_stats_pre_file=ETHTOOL_PORT_STATS_PRE_FILE, + ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_FILE) result = p_stats.connection_port_link_test() LOGGER.info(result) self.assertEqual(result[0], True) @@ -58,8 +60,8 @@ def connection_port_link_compliant_test(self): # Test the port duplex setting def connection_port_duplex_compliant_test(self): LOGGER.info('connection_port_duplex_compliant_test') - p_stats = PortStatsUtil(logger=LOGGER, - ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE) + p_stats = PortStatsUtil( + logger=LOGGER, ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE) result = p_stats.connection_port_duplex_test() LOGGER.info(result) self.assertEqual(result[0], True) @@ -67,8 +69,8 @@ def connection_port_duplex_compliant_test(self): # Test the port speed def connection_port_speed_compliant_test(self): LOGGER.info('connection_port_speed_compliant_test') - p_stats = PortStatsUtil(logger=LOGGER, - ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE) + p_stats = PortStatsUtil( + logger=LOGGER, ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE) result = p_stats.connection_port_speed_test() LOGGER.info(result) self.assertEqual(result[0], True) @@ -76,10 +78,11 @@ def connection_port_speed_compliant_test(self): # Test the port link status non-compliant def connection_port_link_noncompliant_test(self): LOGGER.info('connection_port_link_noncompliant_test') - p_stats = PortStatsUtil(logger=LOGGER, - ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE, - ethtool_port_stats_pre_file=ETHTOOL_PORT_STATS_PRE_FILE, - ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_NONCOMPLIANT_FILE) + p_stats = PortStatsUtil( + logger=LOGGER, + ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE, + ethtool_port_stats_pre_file=ETHTOOL_PORT_STATS_PRE_FILE, + ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_NONCOMPLIANT_FILE) result = p_stats.connection_port_link_test() LOGGER.info(result) self.assertEqual(result[0], False) @@ -87,8 +90,9 @@ def connection_port_link_noncompliant_test(self): # Test the port duplex setting non-compliant def connection_port_duplex_noncompliant_test(self): LOGGER.info('connection_port_duplex_noncompliant_test') - p_stats = PortStatsUtil(logger=LOGGER, - ethtool_conn_stats_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) + p_stats = PortStatsUtil( + logger=LOGGER, + ethtool_conn_stats_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) result = p_stats.connection_port_duplex_test() LOGGER.info(result) self.assertEqual(result[0], False) @@ -96,8 +100,9 @@ def connection_port_duplex_noncompliant_test(self): # Test the port speed non-compliant def connection_port_speed_noncompliant_test(self): LOGGER.info('connection_port_speed_noncompliant_test') - p_stats = PortStatsUtil(logger=LOGGER, - ethtool_conn_stats_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) + p_stats = PortStatsUtil( + logger=LOGGER, + ethtool_conn_stats_file=ETHTOOL_RESULTS_NONCOMPLIANT_FILE) result = p_stats.connection_port_speed_test() LOGGER.info(result) self.assertEqual(result[0], False) @@ -105,12 +110,13 @@ def connection_port_speed_noncompliant_test(self): # Test the autonegotiation failure test def connection_port_speed_autonegotiation_fail_test(self): LOGGER.info('connection_port_speed_autonegotiation_fail_test') - p_stats = PortStatsUtil(logger=LOGGER, - ethtool_conn_stats_file=ETHTOOL_RESULTS_NO_AUTO_FILE) + p_stats = PortStatsUtil( + logger=LOGGER, ethtool_conn_stats_file=ETHTOOL_RESULTS_NO_AUTO_FILE) result = p_stats.connection_port_speed_test() LOGGER.info(result) self.assertEqual(result[0], False) + if __name__ == '__main__': suite = unittest.TestSuite() @@ -121,11 +127,13 @@ def connection_port_speed_autonegotiation_fail_test(self): # Non-compliant port stats tests suite.addTest(ConnectionModuleTest('connection_port_link_noncompliant_test')) - suite.addTest(ConnectionModuleTest('connection_port_duplex_noncompliant_test')) + suite.addTest( + ConnectionModuleTest('connection_port_duplex_noncompliant_test')) suite.addTest(ConnectionModuleTest('connection_port_speed_noncompliant_test')) # Autonegotiation off failure test - suite.addTest(ConnectionModuleTest('connection_port_speed_autonegotiation_fail_test')) + suite.addTest( + ConnectionModuleTest('connection_port_speed_autonegotiation_fail_test')) runner = unittest.TextTestRunner() runner.run(suite) From 969b9ce31e4ad8c049055bb15f7e9846545d3d62 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Fri, 3 May 2024 15:12:44 -0600 Subject: [PATCH 6/7] update readme --- modules/test/conn/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/test/conn/README.md b/modules/test/conn/README.md index cf79c3efb..c2f6377c6 100644 --- a/modules/test/conn/README.md +++ b/modules/test/conn/README.md @@ -14,6 +14,9 @@ Within the ```python/src``` directory, the below tests are executed. A few dhcp | ID | Description | Expected Behavior | Required Result | |------------------------------|----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------| +| connection.port_link | The network switch port connected to the device has an active link without errors | When the etherent cable is connected to the port, the device triggers the port to its enabled \"Link UP\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \"show interface\" command on most network switches. | Required | +| connection.port_speed | The network switch port connected to the device has auto-negotiated a speed that is 10 Mbps or higher | When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \"show interface\" command on most network switches. The output of this command must also show that the \"configured speed\" is set to \"auto\". | Required | +| connection.port_duplex | The network switch port connected to the device has auto-negotiated full-duplex. | When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection. | Required | | connection.dhcp_address | The device under test has received an IP address from the DHCP server and responds to an ICMP echo (ping) request | The device is not set up with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds successfully to an ICMP echo (ping) request. | Required | | connection.mac_address | Check and note device physical address. | N/A | Required | | connection.mac_oui | The device under test has a MAC address prefix that is registered against a known manufacturer. | The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database. | Required | From 90917c54a481b0b1c098422b1fa3750eba6b3675 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Fri, 3 May 2024 15:38:46 -0600 Subject: [PATCH 7/7] pylint fixes --- modules/test/conn/python/src/port_stats_util.py | 17 ++++++++++------- testing/unit/conn/conn_module_test.py | 3 ++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/test/conn/python/src/port_stats_util.py b/modules/test/conn/python/src/port_stats_util.py index e4208344f..d923501eb 100644 --- a/modules/test/conn/python/src/port_stats_util.py +++ b/modules/test/conn/python/src/port_stats_util.py @@ -15,15 +15,18 @@ import os +ETHTOOL_CONN_STATS_FILE = 'runtime/network/ethtool_conn_stats.txt' +ETHTOOL_PORT_STATS_PRE_FILE = ( + 'runtime/network/ethtool_port_stats_pre_monitor.txt') +ETHTOOL_PORT_STATS_POST_FILE = ( + 'runtime/network/ethtool_port_stats_post_monitor.txt') + +LOG_NAME = 'port_stats_util' +LOGGER = None + class PortStatsUtil(): """Helper class for various tests concerning Port behavior""" - ETHTOOL_CONN_STATS_FILE = 'runtime/network/ethtool_conn_stats.txt' - ETHTOOL_PORT_STATS_PRE_FILE = 'runtime/network/ethtool_port_stats_pre_monitor.txt' - ETHTOOL_PORT_STATS_POST_FILE = 'runtime/network/ethtool_port_stats_post_monitor.txt' - - LOG_NAME = 'port_stats_util' - LOGGER = None def __init__(self, logger, @@ -128,7 +131,7 @@ def _get_stat_option(self, stats, option): def _read_stats_file(self, file): if os.path.isfile(file): - with open(file) as f: + with open(file, encoding='utf-8') as f: content = f.read() # Cleanup the results for easier processing lines = content.split('\n') diff --git a/testing/unit/conn/conn_module_test.py b/testing/unit/conn/conn_module_test.py index 339485936..d31a8051f 100644 --- a/testing/unit/conn/conn_module_test.py +++ b/testing/unit/conn/conn_module_test.py @@ -38,7 +38,8 @@ class ConnectionModuleTest(unittest.TestCase): - """Contains and runs all the unit tests concerning Connection module behaviors""" + """Contains and runs all the unit tests concerning Connection + module behaviors""" @classmethod def setUpClass(cls):