diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 4d5178ba2..d8da3ada0 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -90,7 +90,7 @@ jobs: run: cmd/install -l - name: Run tests for conn module shell: bash {0} - run: bash testing/unit/run_test_module.sh conn captures ethtool output + run: bash testing/unit/run_test_module.sh conn captures ethtool ifconfig output - name: Run tests for dns module shell: bash {0} run: bash testing/unit/run_test_module.sh dns captures reports output diff --git a/framework/python/src/net_orc/ip_control.py b/framework/python/src/net_orc/ip_control.py index 73a6aceeb..c6df5d91e 100644 --- a/framework/python/src/net_orc/ip_control.py +++ b/framework/python/src/net_orc/ip_control.py @@ -99,7 +99,7 @@ def get_iface_mac_address(iface): return addr_info.address return None - def get_iface_port_stats(self, iface): + def get_iface_ethtool_port_stats(self, iface): """Extract information about packets connection""" response = util.run_command(f'ethtool -S {iface}') if len(response[1]) == 0: @@ -107,6 +107,14 @@ def get_iface_port_stats(self, iface): else: return None + def get_iface_ifconfig_port_stats(self, iface): + """Extract information about packets connection""" + response = util.run_command(f'ifconfig -a {iface}') + if len(response[1]) == 0: + return response[0] + else: + return None + def get_ip_address(self, iface): addrs = psutil.net_if_addrs() if iface in addrs: diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index 25e036ef7..956c9e1b2 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -142,7 +142,7 @@ def start_network(self): self._session.set_status(TestrunStatus.VALIDATING) self.validator.start() self.validator.stop() - except Exception as e: + except Exception as e: # pylint: disable=W0703 LOGGER.error(f'Validation failed {e}') self._session.set_status('Waiting for Device') @@ -257,14 +257,21 @@ def _get_conn_stats(self): 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""" + suffix = 'pre_monitor' if pre_monitor else 'post_monitor' dev_int = self._session.get_device_interface() - port_stats = self._ip_ctrl.get_iface_port_stats(dev_int) - if port_stats is not None: - 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: + ethtool_port_stats = self._ip_ctrl.get_iface_ethtool_port_stats(dev_int) + ifconfig_port_stats = self._ip_ctrl.get_iface_ifconfig_port_stats(dev_int) + if ethtool_port_stats is not None: + ethtool_out_file = os.path.join(NET_DIR, + f'ethtool_port_stats_{suffix}.txt') + with open(ethtool_out_file, 'w', encoding='utf-8') as f: + f.write(ethtool_port_stats) + if ifconfig_port_stats is not None: + ifconfig_out_file = os.path.join(NET_DIR, + f'ifconfig_port_stats_{suffix}.txt') + with open(ifconfig_out_file, 'w', encoding='utf-8') as f: + f.write(ifconfig_port_stats) + if ethtool_port_stats is None and ifconfig_port_stats is None: LOGGER.error('Failed to generate port stats') def monitor_in_progress(self): @@ -307,10 +314,8 @@ def _start_device_monitor(self, device): time.sleep(1) # Check Testrun hasn't been cancelled - if self._session.get_status() in ( - TestrunStatus.STOPPING, - TestrunStatus.CANCELLED - ): + if self._session.get_status() in (TestrunStatus.STOPPING, + TestrunStatus.CANCELLED): sniffer.stop() return @@ -438,8 +443,8 @@ def load_network_modules(self): for module_dir in os.listdir(net_modules_dir): - if (self._get_network_module(module_dir) is None and - module_dir != 'template'): + if (self._get_network_module(module_dir) is None + and module_dir != 'template'): loaded_module = self._load_network_module(module_dir) loaded_modules += loaded_module.dir_name + ' ' @@ -700,8 +705,7 @@ def network_adapters_checker(self, mqtt_client: mqtt.MQTT, topic: str): def is_device_connected(self): """Check if device connected""" return self._ip_ctrl.check_interface_status( - self._session.get_device_interface() - ) + self._session.get_device_interface()) def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): """Checks internet connection and sends a status to frontend""" @@ -711,11 +715,9 @@ def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): # Only check if Testrun is running if self.get_session().get_status() not in [ - TestrunStatus.WAITING_FOR_DEVICE, - TestrunStatus.MONITORING, - TestrunStatus.IN_PROGRESS, - TestrunStatus.STARTING - ]: + TestrunStatus.WAITING_FOR_DEVICE, TestrunStatus.MONITORING, + TestrunStatus.IN_PROGRESS, TestrunStatus.STARTING + ]: message['connection'] = None # Only run if single intf mode not used @@ -734,6 +736,7 @@ def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): # Broadcast via MQTT client mqtt_client.send_message(topic, message) + class NetworkConfig: """Define all the properties of the network configuration""" diff --git a/modules/test/conn/python/src/port_stats_util.py b/modules/test/conn/python/src/port_stats_util.py index a1f68cb03..340cabd02 100644 --- a/modules/test/conn/python/src/port_stats_util.py +++ b/modules/test/conn/python/src/port_stats_util.py @@ -14,12 +14,17 @@ """Module that contains various methods for validating the Port statistics """ import os +import re 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') +IFCONFIG_PORT_STATS_PRE_FILE = ( + 'runtime/network/ifconfig_port_stats_pre_monitor.txt') +IFCONFIG_PORT_STATS_POST_FILE = ( + 'runtime/network/ifconfig_port_stats_post_monitor.txt') LOG_NAME = 'port_stats_util' LOGGER = None @@ -32,10 +37,14 @@ 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): + ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_FILE, + ifconfig_port_stats_pre_file=IFCONFIG_PORT_STATS_PRE_FILE, + ifconfig_port_stats_post_file=IFCONFIG_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 + self.ifconfig_port_stats_pre_file = ifconfig_port_stats_pre_file + self.ifconfig_port_stats_post_file = ifconfig_port_stats_post_file global LOGGER LOGGER = logger self.conn_stats = self._read_stats_file(self.ethtool_conn_stats_file) @@ -48,16 +57,13 @@ def is_autonegotiate(self): auto_negotiation = 'on' in auto_negotiation_status return auto_negotiation - def connection_port_link_test(self): + def ethtool_port_link_test(self): 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: + if stats_pre is not None and stats_pre is not None: tx_errors_pre = self._get_stat_option(stats=stats_pre, option='tx_errors:') tx_errors_post = self._get_stat_option(stats=stats_post, @@ -68,11 +74,33 @@ def connection_port_link_test(self): option='rx_errors:') # Check that the above have been resolved correctly - if (tx_errors_pre is None or tx_errors_post is None or - rx_errors_pre is None or rx_errors_post is None): - result = 'Error' - description = 'Port stats not available' - else: + if (tx_errors_pre is not None and tx_errors_post is not None + and rx_errors_pre is not None and rx_errors_post is not None): + 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 ifconfig_port_link_test(self): + stats_pre = self._read_stats_file(self.ifconfig_port_stats_pre_file) + stats_post = self._read_stats_file(self.ifconfig_port_stats_post_file) + result = None + description = '' + details = '' + if stats_pre is not None and stats_pre is not None: + rx_errors_pre, tx_errors_pre = self.extract_rx_tx_error_counts(stats_pre) + rx_errors_post, tx_errors_post = self.extract_rx_tx_error_counts( + stats_post) + + # Check that the above have been resolved correctly + if (tx_errors_pre is not None and tx_errors_post is not None + and rx_errors_pre is not None and rx_errors_post is not None): 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: @@ -84,6 +112,17 @@ def connection_port_link_test(self): description = 'No port errors detected' return result, description, details + def connection_port_link_test(self): + port_results = self.ethtool_port_link_test() + if port_results[0] is None: + port_results = self.ifconfig_port_link_test() + if port_results[0] is None: + result = 'Error' + description = 'Port stats not available' + details = '' + port_results = result, description, details + return port_results + def connection_port_duplex_test(self): auto_negotiation = self.is_autonegotiate() # Calculate final results @@ -132,11 +171,19 @@ def connection_port_speed_test(self): details = f'Speed negotiated: {speed}' return result, description, details + def extract_rx_tx_error_counts(self, ifconfig): + rx_match = re.search(r'^\s*RX errors (\d+)', ifconfig, re.MULTILINE) + tx_match = re.search(r'^\s*TX errors (\d+)', ifconfig, re.MULTILINE) + + if rx_match and tx_match: + return int(rx_match.group(1)), int(tx_match.group(1)) + else: + return None, None + def _get_stat_option(self, stats, option): """Extract the requested parameter from the ethtool result""" 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 diff --git a/testing/unit/conn/conn_module_test.py b/testing/unit/conn/conn_module_test.py index 1513545f0..29013b2b9 100644 --- a/testing/unit/conn/conn_module_test.py +++ b/testing/unit/conn/conn_module_test.py @@ -40,12 +40,21 @@ TEST_FILES_DIR, 'ethtool', 'ethtool_port_stats_post_monitor_noncompliant.txt') +IFCONFIG_PORT_STATS_PRE_FILE = os.path.join( + TEST_FILES_DIR, 'ifconfig', 'ifconfig_port_stats_pre_monitor.txt') +IFCONFIG_PORT_STATS_POST_FILE = os.path.join( + TEST_FILES_DIR, 'ifconfig', 'ifconfig_port_stats_post_monitor.txt') +IFCONFIG_PORT_STATS_POST_NONCOMPLIANT_FILE = os.path.join( + TEST_FILES_DIR, 'ifconfig', + 'ifconfig_port_stats_post_noncompliant_monitor.txt') + # Define the capture files to be used for the test STARTUP_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'startup.pcap') MONITOR_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'monitor.pcap') LOGGER = None + class ConnectionModuleTest(unittest.TestCase): """Contains and runs all the unit tests concerning Connection module behaviors""" @@ -70,6 +79,29 @@ def connection_port_link_compliant_test(self): LOGGER.info(result) self.assertEqual(result[0], True) + def connection_port_link_ifconfig_compliant_test(self): + LOGGER.info('connection_port_link_ifconfig_compliant_test') + p_stats = PortStatsUtil( + logger=LOGGER, + ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE, + ifconfig_port_stats_pre_file=IFCONFIG_PORT_STATS_PRE_FILE, + ifconfig_port_stats_post_file=IFCONFIG_PORT_STATS_POST_FILE) + result = p_stats.connection_port_link_test() + LOGGER.info(result) + self.assertEqual(result[0], True) + + def connection_port_link_ifconfig_noncompliant_test(self): + LOGGER.info('connection_port_link_ifconfig_noncompliant_test') + p_stats = PortStatsUtil( + logger=LOGGER, + ethtool_conn_stats_file=ETHTOOL_RESULTS_COMPLIANT_FILE, + ifconfig_port_stats_pre_file=IFCONFIG_PORT_STATS_PRE_FILE, + ifconfig_port_stats_post_file=IFCONFIG_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 def connection_port_duplex_compliant_test(self): LOGGER.info('connection_port_duplex_compliant_test') @@ -174,11 +206,15 @@ def communication_network_type_test(self): # Compliant port stats tests suite.addTest(ConnectionModuleTest('connection_port_link_compliant_test')) + suite.addTest( + ConnectionModuleTest('connection_port_link_ifconfig_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_link_ifconfig_noncompliant_test')) suite.addTest( ConnectionModuleTest('connection_port_duplex_noncompliant_test')) suite.addTest(ConnectionModuleTest('connection_port_speed_noncompliant_test')) diff --git a/testing/unit/conn/ifconfig/ifconfig_port_stats_post_monitor.txt b/testing/unit/conn/ifconfig/ifconfig_port_stats_post_monitor.txt new file mode 100644 index 000000000..48f9cacf1 --- /dev/null +++ b/testing/unit/conn/ifconfig/ifconfig_port_stats_post_monitor.txt @@ -0,0 +1,8 @@ +enx00e04c6601a4: flags=4163 mtu 1500 + inet 192.168.228.1 netmask 255.255.255.0 broadcast 192.168.228.255 + inet6 fe80::2e0:4cff:fe66:1a4 prefixlen 64 scopeid 0x20 + ether 00:e0:4c:66:01:a4 txqueuelen 1000 (Ethernet) + RX packets 23 bytes 2718 (2.7 KB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 36 bytes 3831 (3.8 KB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 \ No newline at end of file diff --git a/testing/unit/conn/ifconfig/ifconfig_port_stats_post_noncompliant_monitor.txt b/testing/unit/conn/ifconfig/ifconfig_port_stats_post_noncompliant_monitor.txt new file mode 100644 index 000000000..50cf740cc --- /dev/null +++ b/testing/unit/conn/ifconfig/ifconfig_port_stats_post_noncompliant_monitor.txt @@ -0,0 +1,8 @@ +enx00e04c6601a4: flags=4163 mtu 1500 + inet 192.168.228.1 netmask 255.255.255.0 broadcast 192.168.228.255 + inet6 fe80::2e0:4cff:fe66:1a4 prefixlen 64 scopeid 0x20 + ether 00:e0:4c:66:01:a4 txqueuelen 1000 (Ethernet) + RX packets 23 bytes 2718 (2.7 KB) + RX errors 5 dropped 0 overruns 0 frame 7 + TX packets 36 bytes 3831 (3.8 KB) + TX errors 10 dropped 0 overruns 0 carrier 0 collisions 0 \ No newline at end of file diff --git a/testing/unit/conn/ifconfig/ifconfig_port_stats_pre_monitor.txt b/testing/unit/conn/ifconfig/ifconfig_port_stats_pre_monitor.txt new file mode 100644 index 000000000..d9740c507 --- /dev/null +++ b/testing/unit/conn/ifconfig/ifconfig_port_stats_pre_monitor.txt @@ -0,0 +1,8 @@ +enx00e04c6601a4: flags=4163 mtu 1500 + inet 192.168.228.1 netmask 255.255.255.0 broadcast 192.168.228.255 + inet6 fe80::2e0:4cff:fe66:1a4 prefixlen 64 scopeid 0x20 + ether 00:e0:4c:66:01:a4 txqueuelen 1000 (Ethernet) + RX packets 1 bytes 46 (46.0 B) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 2 bytes 180 (180.0 B) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 \ No newline at end of file