diff --git a/tests/lldp/test_lldp.py b/tests/lldp/test_lldp.py index 7690cdb15b9..ec82e35f7f8 100644 --- a/tests/lldp/test_lldp.py +++ b/tests/lldp/test_lldp.py @@ -1,8 +1,11 @@ +import contextlib import logging +import re import pytest from tests.common.platform.interface_utils import get_dpu_npu_ports_from_hwsku from tests.common.utilities import wait_until from tests.common.config_reload import config_reload +from tests.common.helpers.assertions import pytest_assert logger = logging.getLogger(__name__) @@ -263,99 +266,448 @@ def test_lldp_neighbor_post_swss_reboot(duthosts, enum_rand_one_per_hwsku_fronte enum_frontend_asic_index, tbinfo, request) -@pytest.mark.disable_loganalyzer -def test_lldp_after_config_reload(duthosts, enum_rand_one_per_hwsku_frontend_hostname, localhost, - collect_techsupport_all_duts, enum_frontend_asic_index, tbinfo, request): - """Verify LLDP neighbors are fully restored after config reload. +def get_expected_chassis_mac(duthost, asic, tbinfo): + """ + Get the expected chassis MAC address based on topology and ASIC configuration. + + For T2 multi-ASIC: each ASIC's lldp container uses its own MAC + For T2 single-ASIC: chassis-id uses router MAC (DEVICE_METADATA['localhost']['mac']) + For non-T2: chassis-id uses management interface MAC - Addresses test gap issue #22376 — validates that lldpd correctly detects - all interfaces after config reload, including chassis ID type and absence - of 'cannot find port' errors in syslog. + Args: + duthost: DUT host object + asic: ASIC instance + tbinfo: Testbed info - Related PR: https://github.com/sonic-net/sonic-buildimage/pull/25436 + Returns: + str: Expected chassis MAC address (lowercase) """ - duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname] - asic_index = enum_frontend_asic_index + if tbinfo["topo"]["type"] == "t2": + if duthost.is_multi_asic: + asic_cfg = asic.config_facts(host=duthost.hostname, source="running")['ansible_facts'] + return asic_cfg['DEVICE_METADATA']['localhost']['mac'].lower() + else: + config_facts = duthost.config_facts(host=duthost.hostname, source="running")['ansible_facts'] + return config_facts['DEVICE_METADATA']['localhost']['mac'].lower() + else: + mgmt_alias = duthost.get_extended_minigraph_facts(tbinfo)["minigraph_mgmt_interface"]["alias"] + return duthost.get_dut_iface_mac(mgmt_alias).lower() + + +def verify_lldp_table(duthost, intf_status_output, test_name=""): + """ + Verify LLDP table interfaces match interface status (admin up, no PortChannels). + + Args: + duthost: DUT host object + intf_status_output: List of interface status dictionaries + test_name: Optional test context name for logging + + Returns: + set: LLDP table interfaces (including eth0 if present) + """ + context = " {}".format(test_name) if test_name else "" + logger.info("Verifying LLDP table{}".format(context)) + + # Get LLDP table output using show_and_parse for robust parsing + lldp_table_parsed = duthost.show_and_parse("show lldp table") + lldp_table_interfaces = set() + for entry in lldp_table_parsed: + interface = entry.get('localport', '') + # Filter out separator/footer lines that show_and_parse may include + if interface and not interface.startswith('-') and not interface.startswith('Total'): + lldp_table_interfaces.add(interface) + + logger.info("LLDP table interfaces{}: {}".format(context, sorted(lldp_table_interfaces))) + logger.info("LLDP table interfaces in total: {}".format(len(lldp_table_interfaces))) + + # On virtual/KVM testbeds, eth0 has no LLDP neighbor so it won't appear in the LLDP table + is_virtual = duthost.facts.get('asic_type', '') == 'vs' + if is_virtual: + if 'eth0' not in lldp_table_interfaces: + logger.info("eth0 not in LLDP table (expected on virtual/KVM testbed){}" + .format(context)) + else: + pytest_assert('eth0' in lldp_table_interfaces, + "eth0 is missing from LLDP table{}".format(context)) + + # For LLDP table comparison: exclude eth0 from lldp_table, exclude PortChannels and admin down from intf_status + lldp_table_interfaces_no_eth0 = lldp_table_interfaces - {'eth0'} + + # Filter intf_status_output: exclude PortChannel interfaces and admin down interfaces + intf_status_filtered_for_lldp = { + intf['interface'] for intf in intf_status_output + if not intf['interface'].startswith('PortChannel') and intf['admin'].lower() == 'up' + } + + missing_in_lldp_table = intf_status_filtered_for_lldp - lldp_table_interfaces_no_eth0 + extra_in_lldp_table = lldp_table_interfaces_no_eth0 - intf_status_filtered_for_lldp + + if missing_in_lldp_table: + logger.warning("Interfaces (admin up, no PortChannels) missing in LLDP table{}: {}".format( + context, sorted(missing_in_lldp_table))) + if extra_in_lldp_table: + logger.warning("Interfaces in LLDP table but not in filtered interface status{}: {}".format( + context, sorted(extra_in_lldp_table))) + + if not missing_in_lldp_table and not extra_in_lldp_table: + logger.info("LLDP table and interface status (admin up, no PortChannels) match perfectly{}".format(context)) + + # Only assert that LLDP table has no unexpected interfaces (extra). + # Missing interfaces in LLDP table are expected on dualtor/some topologies + # where admin-up ports may not have LLDP neighbors. + if missing_in_lldp_table: + logger.info("Interfaces admin-up but missing LLDP neighbors (expected on some topologies){}: {}".format( + context, sorted(missing_in_lldp_table))) + pytest_assert(not extra_in_lldp_table, + "Extra interfaces in LLDP table (not in admin-up non-PortChannel set){}: {}".format( + context, sorted(extra_in_lldp_table))) + + return lldp_table_interfaces + + +def verify_lldpcli_interfaces(duthost, asic, intf_status_output, test_name=""): + """ + Verify lldpcli interfaces match interface status (all interfaces, no PortChannels). + + Args: + duthost: DUT host object + asic: ASIC instance + intf_status_output: List of interface status dictionaries + test_name: Optional test context name for logging + + Returns: + set: lldpcli interfaces (including eth0 if present) + """ + context = " {}".format(test_name) if test_name else "" + logger.info("Verifying lldpcli show interfaces{}".format(context)) + + # Get lldpcli interfaces + lldpcli_output = duthost.shell( + "docker exec lldp{} lldpcli show interfaces".format( + asic.asic_index if duthost.is_multi_asic else "" + ) + )['stdout'] + + lldpcli_interfaces = set() + for line in lldpcli_output.split('\n'): + if line.startswith('Interface:'): + interface = line.split('Interface:')[1].strip() + lldpcli_interfaces.add(interface) + + logger.info("lldpcli interfaces{}: {}".format(context, sorted(lldpcli_interfaces))) + logger.info("lldpcli interfaces in total: {}".format(len(lldpcli_interfaces))) + + # On virtual/KVM testbeds, eth0 may not appear in lldpcli + is_virtual = duthost.facts.get('asic_type', '') == 'vs' + if is_virtual: + if 'eth0' not in lldpcli_interfaces: + logger.info("eth0 not in lldpcli interfaces (expected on virtual/KVM testbed){}" + .format(context)) + else: + pytest_assert('eth0' in lldpcli_interfaces, + "eth0 is missing from lldpcli interfaces{}".format(context)) + + # For lldpcli comparison: exclude eth0 from lldpcli, exclude only PortChannels from intf_status + lldpcli_interfaces_no_eth0 = lldpcli_interfaces - {'eth0'} + + # Filter intf_status_output: exclude only PortChannel interfaces (keep admin down) + # On multi-ASIC, lldpcli only shows interfaces for the current ASIC, + # so also filter intf_status to only include ports belonging to this ASIC. + if duthost.is_multi_asic: + asic_cfg = asic.config_facts(host=duthost.hostname, source="running")['ansible_facts'] + asic_ports = set(asic_cfg.get('PORT', {}).keys()) + logger.info("Multi-ASIC: filtering interface status to ASIC {} ports ({} ports)".format( + asic.asic_index, len(asic_ports))) + intf_status_filtered_for_lldpcli = { + intf['interface'] for intf in intf_status_output + if not intf['interface'].startswith('PortChannel') and intf['interface'] in asic_ports + } + else: + intf_status_filtered_for_lldpcli = { + intf['interface'] for intf in intf_status_output + if not intf['interface'].startswith('PortChannel') + } + + missing_in_lldpcli = intf_status_filtered_for_lldpcli - lldpcli_interfaces_no_eth0 + extra_in_lldpcli = lldpcli_interfaces_no_eth0 - intf_status_filtered_for_lldpcli + + if missing_in_lldpcli: + logger.warning("Interfaces (no PortChannels) missing in lldpcli{}: {}".format( + context, sorted(missing_in_lldpcli))) + if extra_in_lldpcli: + logger.warning("Interfaces in lldpcli but not in interface status{}: {}".format( + context, sorted(extra_in_lldpcli))) + + if not missing_in_lldpcli and not extra_in_lldpcli: + logger.info("lldpcli and interface status (no PortChannels) match perfectly{}".format(context)) + + pytest_assert(intf_status_filtered_for_lldpcli == lldpcli_interfaces_no_eth0, + "Interface mismatch between 'show interface status' (no PortChannels) and lldpcli{}. " + "Missing in lldpcli: {}, Extra in lldpcli: {}".format( + context, sorted(missing_in_lldpcli), sorted(extra_in_lldpcli))) + + return lldpcli_interfaces + + +def verify_lldpctl_facts(duthost, enum_frontend_asic_index, intf_status_output, lldpcli_interfaces, test_name=""): + """ + Verify lldpctl_facts interfaces match interface status (admin up, no PortChannels). + + Args: + duthost: DUT host object + enum_frontend_asic_index: Frontend ASIC index + intf_status_output: List of interface status dictionaries + lldpcli_interfaces: Set of lldpcli interfaces for consistency check + test_name: Optional test context name for logging + Returns: + dict: lldpctl_facts ansible_facts + """ + context = " {}".format(test_name) if test_name else "" + logger.info("Verifying lldpctl_facts{}".format(context)) + + # Get lldpctl_facts internal_port_list = get_dpu_npu_ports_from_hwsku(duthost) - skip_pattern_list = ["eth0", "Ethernet-BP", "Ethernet-IB"] + internal_port_list - - # Step 1: Record LLDP state before config reload - pre_lldpctl_facts = duthost.lldpctl_facts( - asic_instance_id=asic_index, - skip_interface_pattern_list=skip_pattern_list)['ansible_facts'] - assert list(pre_lldpctl_facts['lldpctl'].items()), \ - "No LLDP neighbors detected before config reload" - pre_neighbors = set(pre_lldpctl_facts['lldpctl'].keys()) - pre_count = len(pre_neighbors) - logger.info("LLDP neighbors before config reload (%d): %s", pre_count, sorted(pre_neighbors)) - - # Record interface status before reload - pre_intf_status = duthost.show_interface(command="status")['ansible_facts']['int_status'] - pre_up_intfs = {intf for intf, status in pre_intf_status.items() - if status.get('oper_state', '').lower() == 'up' and not intf.startswith('Loopback')} - logger.info("Interfaces up before config reload: %d", len(pre_up_intfs)) - - # Step 2: Perform config reload - logger.info("Performing config reload") - config_reload(duthost, safe_reload=True, check_intf_up_ports=True) - - # Step 3: Wait for LLDP neighbors to be fully restored - assert wait_until(300, 20, 60, - lambda: pre_count <= get_num_lldpctl_facts(duthost, asic_index)), \ - "LLDP neighbors not fully restored after config reload. " \ - "Expected at least {} entries, got {}".format( - pre_count, get_num_lldpctl_facts(duthost, asic_index)) - - # Step 4: Verify LLDP table matches pre-reload state - post_lldpctl_facts = duthost.lldpctl_facts( - asic_instance_id=asic_index, - skip_interface_pattern_list=skip_pattern_list)['ansible_facts'] - post_neighbors = set(post_lldpctl_facts['lldpctl'].keys()) - - missing = pre_neighbors - post_neighbors - assert not missing, \ - "LLDP neighbors missing after config reload: {}".format(sorted(missing)) - - # Verify neighbor names match - for intf in pre_neighbors: - pre_name = pre_lldpctl_facts['lldpctl'][intf]['chassis']['name'] - post_name = post_lldpctl_facts['lldpctl'][intf]['chassis']['name'] - assert pre_name == post_name, \ - "LLDP neighbor name changed on {} after config reload: '{}' -> '{}'".format( - intf, pre_name, post_name) - - # Step 5: Verify Chassis ID type is MAC (not hostname) + lldpctl_facts = duthost.lldpctl_facts( + asic_instance_id=enum_frontend_asic_index, + skip_interface_pattern_list=["Ethernet-BP", "Ethernet-IB"] + internal_port_list + )['ansible_facts'] + + # Verify eth0 is in lldpctl_facts (only on physical testbeds) + is_virtual = duthost.facts.get('asic_type', '') == 'vs' + if is_virtual: + if 'eth0' not in lldpctl_facts.get('lldpctl', {}): + logger.info("eth0 not in lldpctl_facts (expected on virtual/KVM testbed){}" + .format(context)) + else: + pytest_assert('eth0' in lldpctl_facts.get('lldpctl', {}), + "eth0 is missing from lldpctl_facts{}".format(context)) + + # Get interfaces from lldpctl_facts (excluding eth0) + lldpctl_facts_interfaces = set(lldpctl_facts.get('lldpctl', {}).keys()) - {'eth0'} + logger.info("lldpctl_facts interfaces (excluding eth0){}: {}".format( + context, sorted(lldpctl_facts_interfaces))) + logger.info("lldpctl_facts interfaces in total: {}".format(len(lldpctl_facts_interfaces))) + + # Compare intf_status_output with lldpctl_facts interfaces (exclude PortChannels and admin down from intf_status) + intf_status_filtered_for_lldpctl = { + intf['interface'] for intf in intf_status_output + if not intf['interface'].startswith('PortChannel') and intf['admin'].lower() == 'up' + } + + missing_in_lldpctl_facts = intf_status_filtered_for_lldpctl - lldpctl_facts_interfaces + extra_in_lldpctl_facts = lldpctl_facts_interfaces - intf_status_filtered_for_lldpctl + + if missing_in_lldpctl_facts: + logger.warning("Interfaces in 'show interface status' but missing in lldpctl_facts{}: {}".format( + context, sorted(missing_in_lldpctl_facts))) + if extra_in_lldpctl_facts: + logger.warning("Interfaces in lldpctl_facts but not in 'show interface status'{}: {}".format( + context, sorted(extra_in_lldpctl_facts))) + + if not missing_in_lldpctl_facts and not extra_in_lldpctl_facts: + logger.info("lldpctl_facts and interface status (admin up, no PortChannels) match perfectly{}".format(context)) + + # Only assert that lldpctl_facts has no unexpected interfaces. + # Missing interfaces are expected on dualtor/some topologies where + # admin-up ports may not have LLDP neighbors. + if missing_in_lldpctl_facts: + logger.info("Interfaces admin-up but missing in lldpctl_facts (expected on some topologies){}: {}".format( + context, sorted(missing_in_lldpctl_facts))) + pytest_assert(not extra_in_lldpctl_facts, + "Unexpected interfaces in lldpctl_facts (not admin-up or are PortChannels){}: {}".format( + context, sorted(extra_in_lldpctl_facts))) + + # Verify consistency between lldpctl_facts and lldpcli + for interface in lldpctl_facts.get('lldpctl', {}): + pytest_assert(interface in lldpcli_interfaces, + "Interface {} from lldpctl_facts is missing in lldpcli interfaces{}".format( + interface, context)) + + return lldpctl_facts + + +def verify_chassis_info(duthost, asic, expected_chassis_mac, test_name=""): + """ + Verify chassis ID and capabilities. + + Args: + duthost: DUT host object + asic: ASIC instance + expected_chassis_mac: Expected chassis MAC address + test_name: Optional test context name for logging + """ + context = " {}".format(test_name) if test_name else "" + logger.info("Verifying Chassis ID and Capabilities{}".format(context)) + + # Get chassis information chassis_output = duthost.shell( - "docker exec -i lldp{} lldpcli show chassis".format( - '' if asic_index is None else asic_index))['stdout'] - logger.info("Chassis info after config reload:\n%s", chassis_output) + "docker exec lldp{} lldpcli show chassis".format( + asic.asic_index if duthost.is_multi_asic else "" + ) + )['stdout'] + + logger.info("Chassis output{}:\n{}".format(context, chassis_output)) + + # Verify ChassisID type is mac + chassis_id_match = re.search(r'ChassisID:\s+mac\s+([0-9a-f:]+)', chassis_output, re.IGNORECASE) + pytest_assert(chassis_id_match is not None, + "ChassisID with type 'mac' not found in chassis output{}".format(context)) + + actual_chassis_mac = chassis_id_match.group(1).lower() + pytest_assert(actual_chassis_mac == expected_chassis_mac, + "Chassis MAC mismatch{}. Expected: {}, Got: {}".format( + context, expected_chassis_mac, actual_chassis_mac)) + + # Verify Capabilities are present with correct status + pytest_assert(re.search(r'Capability:\s+Bridge,\s+on', chassis_output, re.IGNORECASE), + "Bridge capability should be 'on' in chassis output{}".format(context)) + pytest_assert(re.search(r'Capability:\s+Router,\s+on', chassis_output, re.IGNORECASE), + "Router capability should be 'on' in chassis output{}".format(context)) + # Wlan and Station capabilities: verify off if present (not all platforms report them) + wlan_match = re.search(r'Capability:\s+Wlan,\s+(\w+)', chassis_output, re.IGNORECASE) + if wlan_match: + pytest_assert(wlan_match.group(1).lower() == 'off', + "Wlan capability should be 'off' in chassis output{}".format(context)) + station_match = re.search(r'Capability:\s+Station,\s+(\w+)', chassis_output, re.IGNORECASE) + if station_match: + pytest_assert(station_match.group(1).lower() == 'off', + "Station capability should be 'off' in chassis output{}".format(context)) + + +def test_lldp_interfaces(duthosts, enum_rand_one_per_hwsku_frontend_hostname, + enum_frontend_asic_index, tbinfo, loganalyzer): + """ + Test LLDP functionality to verify all interfaces and chassis information are correct. + This test is similar to test_lldp_interface_config_reload but without performing config reload. + + Steps: + 1. Record all interfaces from 'show interface status' + 2. Verify LLDP table matches recorded interfaces + 3. Verify lldpcli interfaces match recorded interfaces + 4. Verify lldpctl_facts interfaces match recorded interfaces + 5. Verify chassis ID and capabilities + 6. Check syslog for LLDP errors using loganalyzer + """ + duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname] + asic = duthost.asic_instance(enum_frontend_asic_index) - assert "mac" in chassis_output.lower(), \ - "Chassis ID type should be 'mac' after config reload, got:\n{}".format(chassis_output) + # Configure loganalyzer to only fail on LLDP-specific errors + if loganalyzer: + # Override match_regex for ALL DUTs to only match LLDP errors + # (the fixture teardown analyzes all DUTs, not just the selected one) + for hostname in loganalyzer: + loganalyzer[hostname].match_regex = [ + ".*cannot find port.*", + ".*ERR lldp#lldpmgrd.*" + ] - # Verify chassis MAC matches eth0 MAC (for non-T2 topologies) - if tbinfo["topo"]["type"] != "t2": - mgmt_facts = duthost.get_extended_minigraph_facts(tbinfo) - mgmt_alias = mgmt_facts["minigraph_mgmt_interface"]["alias"] - eth0_mac = duthost.get_dut_iface_mac(mgmt_alias) - assert eth0_mac.lower() in chassis_output.lower(), \ - "Chassis MAC should match {} MAC '{}', got:\n{}".format( - mgmt_alias, eth0_mac, chassis_output) - - # Step 6: Verify lldpcli show interfaces matches expected ports - lldpcli_intfs_output = duthost.shell( - "docker exec -i lldp{} lldpcli show interfaces".format( - '' if asic_index is None else asic_index))['stdout'] - for intf in pre_neighbors: - assert intf in lldpcli_intfs_output, \ - "Interface {} not found in 'lldpcli show interfaces' after config reload".format(intf) - - # Step 7: Check syslog for lldp errors (informational, not a hard failure) - syslog_output = duthost.shell( - "sudo grep -i 'cannot find port\\|ERR lldp#lldpmgrd' /var/log/syslog | tail -20", - module_ignore_errors=True)['stdout'] - if syslog_output: - logger.warning("LLDP errors found in syslog after config reload:\n%s", syslog_output) - else: - logger.info("No LLDP errors found in syslog after config reload") + with loganalyzer[enum_rand_one_per_hwsku_frontend_hostname] if loganalyzer else contextlib.nullcontext(): + logger.info("Step 1: Recording all interfaces") + # Get all interfaces from 'show interface status' using show_and_parse + intf_status_output = duthost.show_and_parse("show interface status") + + # Save all original interfaces + all_interfaces = {intf['interface'] for intf in intf_status_output} + logger.info("All interfaces from 'show interface status': {}".format(sorted(all_interfaces))) + logger.info("All interfaces in total: {}".format(len(all_interfaces))) + + # Get expected chassis MAC address + expected_chassis_mac = get_expected_chassis_mac(duthost, asic, tbinfo) + logger.info("Expected chassis MAC address: {}".format(expected_chassis_mac)) + + # Step 2: Verify LLDP table + verify_lldp_table(duthost, intf_status_output) + + # Step 3: Verify lldpcli interfaces + lldpcli_interfaces = verify_lldpcli_interfaces(duthost, asic, intf_status_output) + + # Step 4: Verify lldpctl_facts + verify_lldpctl_facts(duthost, enum_frontend_asic_index, intf_status_output, lldpcli_interfaces) + + # Step 5: Verify chassis ID and capabilities + verify_chassis_info(duthost, asic, expected_chassis_mac) + + logger.info("Test completed successfully. All LLDP checks passed.") + + +def test_lldp_interfaces_config_reload(duthosts, enum_rand_one_per_hwsku_frontend_hostname, + enum_frontend_asic_index, tbinfo, loganalyzer): + """ + Test LLDP functionality after config reload to verify all interfaces and chassis information are correct. + This test covers the issue: https://github.com/sonic-net/sonic-mgmt/issues/22376 + + Steps: + 1. Record all interfaces before the test + 2. Perform config reload + 3. Verify LLDP table matches recorded interfaces + 4. Verify lldpcli interfaces match recorded interfaces + 5. Verify lldpctl_facts interfaces match recorded interfaces + 6. Verify chassis ID and capabilities + 7. Check syslog for LLDP errors using loganalyzer + """ + duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname] + asic = duthost.asic_instance(enum_frontend_asic_index) + + # Configure loganalyzer to only fail on LLDP-specific errors + if loganalyzer: + # Override match_regex for ALL DUTs to only match LLDP errors + # (the fixture teardown analyzes all DUTs, not just the selected one) + for hostname in loganalyzer: + loganalyzer[hostname].match_regex = [ + ".*cannot find port.*", + ".*ERR lldp#lldpmgrd.*" + ] + + with loganalyzer[enum_rand_one_per_hwsku_frontend_hostname] if loganalyzer else contextlib.nullcontext(): + logger.info("Step 1: Recording all interfaces before config reload") + # Get all interfaces from 'show interface status' using show_and_parse + intf_status_output = duthost.show_and_parse("show interface status") + + # Save all original interfaces + all_pre_reload_interfaces = {intf['interface'] for intf in intf_status_output} + logger.info("All interfaces before config reload: {}".format(sorted(all_pre_reload_interfaces))) + logger.info("All interfaces in total: {}".format(len(all_pre_reload_interfaces))) + + # Get expected chassis MAC address before reload + expected_chassis_mac = get_expected_chassis_mac(duthost, asic, tbinfo) + logger.info("Expected chassis MAC address: {}".format(expected_chassis_mac)) + + # Record pre-reload LLDP neighbor count as baseline + # (not all admin-up ports have neighbors, e.g. dualtor/unused ports) + pre_reload_lldp_neighbors = get_num_lldpctl_facts(duthost, enum_frontend_asic_index) + logger.info("Pre-reload LLDP neighbor count: {}".format(pre_reload_lldp_neighbors)) + pytest_assert(pre_reload_lldp_neighbors > 0, + "No LLDP neighbors found before config reload") + + logger.info("Step 2: Performing config reload") + config_reload(duthost, safe_reload=True, check_intf_up_ports=True) + + logger.info("Step 3: Waiting for system to stabilize after config reload") + # Wait for LLDP to converge + assert wait_until(300, 10, 0, duthost.critical_services_fully_started), \ + "Not all critical services are fully started after config reload" + + # Wait for all LLDP neighbors to be re-discovered using pre-reload count as baseline + pytest_assert( + wait_until(180, 10, 0, lambda: get_num_lldpctl_facts( + duthost, enum_frontend_asic_index) >= pre_reload_lldp_neighbors), + "Expected {} LLDP neighbors but only found {} after config reload".format( + pre_reload_lldp_neighbors, get_num_lldpctl_facts(duthost, enum_frontend_asic_index)) + ) + + # Step 4: Verify LLDP table after config reload + verify_lldp_table(duthost, intf_status_output, "after config reload") + + # Step 5: Verify lldpcli interfaces after config reload + lldpcli_interfaces = verify_lldpcli_interfaces(duthost, asic, intf_status_output, "after config reload") + + # Step 6: Verify lldpctl_facts after config reload + verify_lldpctl_facts(duthost, enum_frontend_asic_index, intf_status_output, + lldpcli_interfaces, "after config reload") + + # Step 7: Verify chassis ID and capabilities after config reload + verify_chassis_info(duthost, asic, expected_chassis_mac, "after config reload") + + logger.info("Test completed successfully. All LLDP checks passed after config reload.")