diff --git a/ansible/config_sonic_basedon_testbed.yml b/ansible/config_sonic_basedon_testbed.yml index 9d13a28d37..22b5a7660c 100644 --- a/ansible/config_sonic_basedon_testbed.yml +++ b/ansible/config_sonic_basedon_testbed.yml @@ -448,6 +448,11 @@ - name: Load variables from topology file for topo_{{ topo }}.yml include_vars: "vars/topo_{{ topo }}.yml" + - name: set BGP confd ASN facts + set_fact: + bgp_confd_asn: "{{ hostvars[inventory_hostname]['configuration_properties']['common']['dut_confed_asn'] | default(None) }}" + bgp_confd_peers: "{{ hostvars[inventory_hostname]['configuration_properties']['common']['dut_confed_peers'] | default(None) }}" + - name: saved original minigraph file in SONiC DUT(ignore errors when file does not exist) shell: mv /etc/sonic/minigraph.xml /etc/sonic/minigraph.xml.orig become: true @@ -611,6 +616,9 @@ topo_name: "{{ topo }}" port_index_map: "{{ port_index_map | default({}) }}" hwsku: "{{ hwsku }}" + num_asics: "{{ num_asics }}" + bgp_confd_asn: "{{ bgp_confd_asn }}" + bgp_confd_peers: "{{ bgp_confd_peers }}" become: true when: enable_macsec is not defined @@ -631,6 +639,30 @@ topo_name: "{{ topo }}" macsec_profile: "{{ macsec_profile }}" num_asics: "{{ num_asics }}" + bgp_confd_asn: "{{ bgp_confd_asn }}" + bgp_confd_peers: "{{ bgp_confd_peers }}" + become: true + when: "('t2' in topo) and (enable_macsec is defined)" + + - name: Copy macsec profile json to dut + copy: src=../tests/common/macsec/profile.json + dest=/tmp/profile.json + become: true + when: "('t2' in topo) and (enable_macsec is defined)" + + - name: Copy golden_config_db_t2 template to DUT + copy: src=templates/golden_config_db_t2.j2 + dest=/tmp/golden_config_db_t2.j2 + become: true + when: "('t2' in topo) and (enable_macsec is defined)" + + - name: Generate golden_config_db.json for t2 + generate_golden_config_db: + topo_name: "{{ topo }}" + macsec_profile: "{{ macsec_profile }}" + num_asics: "{{ num_asics }}" + bgp_confd_asn: "{{ bgp_confd_asn }}" + bgp_confd_peers: "{{ bgp_confd_peers }}" become: true when: "('t2' in topo) and (enable_macsec is defined)" diff --git a/ansible/library/bgp_facts.py b/ansible/library/bgp_facts.py index 41a22b96ed..88316e7e7c 100644 --- a/ansible/library/bgp_facts.py +++ b/ansible/library/bgp_facts.py @@ -120,6 +120,7 @@ def parse_summary(self): def parse_neighbors(self): regex_ipv4 = re.compile( r'^BGP neighbor is \*?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})') + regex_confed_link = re.compile(r'^BGP neighbor is .*(confed-(?:internal|external) link)') regex_ipv6 = re.compile(r'^BGP neighbor is \*?([0-9a-fA-F:]+)') regex_remote_as = re.compile(r'.*remote AS (\d+)') regex_local_as = re.compile(r'.*local AS (\d+)') @@ -165,6 +166,11 @@ def parse_neighbors(self): neighbor_ip = None for line in lines: + if regex_confed_link.match(line): + neighbor['confed_peer'] = True + if neighbor['confed_peer']: + confed_peer_type = regex_confed_link.match(line).group(1) + neighbor['confed_peer_type'] = 'external' if confed_peer_type == 'confed-external link' else 'internal' # noqa: E501 if regex_ipv4.match(line): neighbor_ip = regex_ipv4.match(line).group(1) neighbor['ip_version'] = 4 diff --git a/ansible/library/generate_golden_config_db.py b/ansible/library/generate_golden_config_db.py index 8f6f9eb591..094e3c9b4a 100644 --- a/ansible/library/generate_golden_config_db.py +++ b/ansible/library/generate_golden_config_db.py @@ -54,13 +54,19 @@ def __init__(self): port_index_map=dict(require=False, type='dict', default=None), macsec_profile=dict(require=False, type='str', default=None), num_asics=dict(require=False, type='int', default=1), - hwsku=dict(require=False, type='str', default=None)), + hwsku=dict(require=False, type='str', default=None), + bgp_confd_asn=dict(require=False, type='str', default=None), + bgp_confd_peers=dict(require=False, type='str', default=None)), supports_check_mode=True) self.topo_name = self.module.params['topo_name'] self.port_index_map = self.module.params['port_index_map'] self.hwsku = self.module.params['hwsku'] self.macsec_profile = self.module.params['macsec_profile'] self.num_asics = self.module.params['num_asics'] + self.platform, _ = device_info.get_platform_and_hwsku() + + self.bgp_confd_asn = self.module.params['bgp_confd_asn'] + self.bgp_confd_peers = self.module.params['bgp_confd_peers'] def generate_mgfx_golden_config_db(self): rc, out, err = self.module.run_command("sonic-cfggen -H -m -j /etc/sonic/init_cfg.json --print-data") @@ -253,43 +259,86 @@ def generate_smartswitch_golden_config_db(self): gold_config_db.update(smartswitch_config_obj) return json.dumps(gold_config_db, indent=4) + def generate_ut2_golden_config_db(self): + rendered_json = {} + if self.macsec_profile: + with open(MACSEC_PROFILE_PATH) as f: + macsec_profiles = json.load(f) + + profile = macsec_profiles.get(self.macsec_profile) + if profile: + profile['macsec_profile'] = self.macsec_profile + + # Update the profile context with the asic count + profile['asic_cnt'] = self.num_asics + + def safe_open_template(template_path): + with open(template_path) as template_file: + return Template(template_file.read()) + + # Render the template using the profile + rendered_json = safe_open_template(GOLDEN_CONFIG_TEMPLATE_PATH).render(profile) + + if self.num_asics > 1: + if "localhost" not in rendered_json: + rendered_json["localhost"] = {} + for asic in range(0, self.num_asics): + namespace = "asic{}".format(asic) + if namespace not in rendered_json: + rendered_json[namespace] = {} + rendered_json[namespace] = {"BGP_DEVICE_GLOBAL": {"CONFED": {"asn": "65100", "peers": "65300"}}} + else: + if "BGP_DEVICE_GLOBAL" not in rendered_json: + rendered_json["BGP_DEVICE_GLOBAL"] = {} + rendered_json["BGP_DEVICE_GLOBAL"]["CONFED"] = {"asn": "65100", "peers": "65300"} + return json.dumps(rendered_json, indent=4) + + def generate_t2_golden_config_db(self): + rendered_json = {} + if self.macsec_profile: + with open(MACSEC_PROFILE_PATH) as f: + macsec_profiles = json.load(f) + + profile = macsec_profiles.get(self.macsec_profile) + if profile: + profile['macsec_profile'] = self.macsec_profile + + # Update the profile context with the asic count + profile['asic_cnt'] = self.num_asics + + def safe_open_template(template_path): + with open(template_path) as template_file: + return Template(template_file.read()) + + # Render the template using the profile + rendered_json = safe_open_template(GOLDEN_CONFIG_TEMPLATE_PATH).render(profile) + + return rendered_json + def generate_lt2_ft2_golden_config_db(self): """ - Generate golden_config for FT2 to enable FEC. - **Only PORT table is updated**. + Generate golden_config for FT2 to enable FEC and set BGP confed. """ SUPPORTED_TOPO = ["ft2-64", "lt2-p32o64", "lt2-o128"] if self.topo_name not in SUPPORTED_TOPO: return "{}" SUPPORTED_PORT_SPEED = ["200000", "400000", "800000"] ori_config = json.loads(self.get_config_from_minigraph()) - port_config = ori_config.get("PORT", {}) - for name, config in port_config.items(): + golden_config = ori_config + golden_config["PORT"] = ori_config.get("PORT", {}) + for _, config in golden_config["PORT"].items(): # Enable FEC for ports with supported speed if config["speed"] in SUPPORTED_PORT_SPEED and "fec" not in config: config["fec"] = "rs" - return json.dumps({"PORT": port_config}, indent=4) - - def generate_t2_golden_config_db(self): - with open(MACSEC_PROFILE_PATH) as f: - macsec_profiles = json.load(f) - - profile = macsec_profiles.get(self.macsec_profile) - if profile: - profile['macsec_profile'] = self.macsec_profile - - # Update the profile context with the asic count - profile['asic_cnt'] = self.num_asics + # Add BGP confederation config + if self.bgp_confd_asn and self.bgp_confd_peers: + golden_config["BGP_DEVICE_GLOBAL"] = ori_config.get("BGP_DEVICE_GLOBAL", {}) + # Replace space in confg_peers with ; to align with NDM + golden_config["BGP_DEVICE_GLOBAL"]["CONFED"] = \ + {"asn": str(self.bgp_confd_asn), "peers": str(self.bgp_confd_peers).replace(' ', ';')} - def safe_open_template(template_path): - with open(template_path) as template_file: - return Template(template_file.read()) - - # Render the template using the profile - rendered_json = safe_open_template(GOLDEN_CONFIG_TEMPLATE_PATH).render(profile) - - return rendered_json + return json.dumps(golden_config, indent=4) def generate(self): module_msg = "Success to generate golden_config_db.json" @@ -304,7 +353,12 @@ def generate(self): module_msg = module_msg + " for smartswitch" elif "ft2" in self.topo_name or "lt2" in self.topo_name: config = self.generate_lt2_ft2_golden_config_db() - elif "t2" in self.topo_name and self.macsec_profile: + elif "t2_single_node" in self.topo_name: + config = self.generate_ut2_golden_config_db() + self.module.run_command("sudo rm -f {}".format(MACSEC_PROFILE_PATH)) + self.module.run_command("sudo rm -f {}".format(GOLDEN_CONFIG_TEMPLATE_PATH)) + module_msg = module_msg + " for ut2 device" + elif "t2" in self.topo_name: config = self.generate_t2_golden_config_db() self.module.run_command("sudo rm -f {}".format(MACSEC_PROFILE_PATH)) self.module.run_command("sudo rm -f {}".format(GOLDEN_CONFIG_TEMPLATE_PATH)) diff --git a/ansible/library/topo_facts.py b/ansible/library/topo_facts.py index c34b62e9b1..c6a0a24e06 100644 --- a/ansible/library/topo_facts.py +++ b/ansible/library/topo_facts.py @@ -188,8 +188,12 @@ def parse_topo_defintion(self, topo_definition, po_map, dut_num, neigh_type='VMs # bgp vmconfig[vm]['bgp_asn'] = topo_definition['configuration'][vm]['bgp']['asn'] - dut_asn = topo_definition['configuration_properties']['common']['dut_asn'] - for ipstr in topo_definition['configuration'][vm]['bgp']['peers'][dut_asn]: + peer_in_bgp_confed = topo_definition['configuration'][vm]['bgp'].get('peer_in_bgp_confed', False) + if peer_in_bgp_confed: + peer_asn = topo_definition['configuration_properties']['common']['dut_confed_asn'] + else: + peer_asn = topo_definition['configuration_properties']['common']['dut_asn'] + for ipstr in topo_definition['configuration'][vm]['bgp']['peers'][peer_asn]: ip_mask = None if '/' in ipstr: (ipstr, ip_mask) = ipstr.split('/') diff --git a/ansible/module_utils/port_utils.py b/ansible/module_utils/port_utils.py index 7babddaf3e..a52ec5c033 100644 --- a/ansible/module_utils/port_utils.py +++ b/ansible/module_utils/port_utils.py @@ -296,7 +296,16 @@ def get_port_alias_to_name_map(hwsku, asic_name=None): "Arista-7800R3A-36DM2-D36"]: for i in range(1, 37): sonic_name = "Ethernet%d" % ((i - 1) * 8) - port_alias_to_name_map["Ethernet{}/{}".format(i, 1)] = sonic_name + port_alias_to_name_map["etp{}".format(i)] = sonic_name + elif hwsku in ["Arista-7280R4-32QF-32DF-64O", + "Arista-7280R4K-32QF-32DF-64O"]: + portNum = 0 + for i in range(1, 65): + port_alias_to_name_map["Ethernet{}/{}".format(i, 1)] = "Ethernet%d" % portNum + if i > 16 and i < 49: + portNum += 4 + else: + portNum += 8 elif hwsku == "Arista-7800R3A-36DM2-C72" or\ hwsku == "Arista-7800R3A-36D-C72" or\ hwsku == "Arista-7800R3A-36P-C72" or\ diff --git a/ansible/roles/eos/templates/t0-leaf.j2 b/ansible/roles/eos/templates/t0-leaf.j2 index 5f553453a5..179e5b1cfe 100644 --- a/ansible/roles/eos/templates/t0-leaf.j2 +++ b/ansible/roles/eos/templates/t0-leaf.j2 @@ -102,13 +102,18 @@ interface {{ bp_ifname }} router bgp {{ host['bgp']['asn'] }} router-id {{ host['bgp']['router-id'] if host['bgp']['router-id'] is defined else host['interfaces']['Loopback0']['ipv4'] | ansible.utils.ipaddr('address') }} ! +{% if host['bgp']['confed_asn'] is defined and host['bgp']['confed_peers'] is defined %} + bgp confederation identifier {{ host['bgp']['confed_asn'] }} + bgp confederation peers {{ host['bgp']['confed_peers'] }} + ! +{% endif %} {% for asn, remote_ips in host['bgp']['peers'].items() %} -{% for remote_ip in remote_ips %} + {% for remote_ip in remote_ips %} neighbor {{ remote_ip }} remote-as {{ asn }} neighbor {{ remote_ip }} description {{ asn }} neighbor {{ remote_ip }} next-hop-self {# set LT2/FT2 as reflector to advertise route to DUT #} -{% if props.swrole is defined and props.swrole in ("lowerspine", "fabricspine") %} +{% if props.swrole is defined and props.swrole in ("lowerspine", "fabricspine") and host['bgp']['confed_asn'] is not defined %} neighbor {{ remote_ip }} route-reflector-client neighbor {{ remote_ip }} additional-paths send any {% endif %} diff --git a/ansible/roles/eos/templates/t1-64-lag-tor.j2 b/ansible/roles/eos/templates/t1-64-lag-tor.j2 index fff5482335..758cb9a82e 100644 --- a/ansible/roles/eos/templates/t1-64-lag-tor.j2 +++ b/ansible/roles/eos/templates/t1-64-lag-tor.j2 @@ -106,6 +106,11 @@ router bgp {{ host['bgp']['asn'] }} graceful-restart restart-time {{ bgp_gr_timer }} graceful-restart ! +{% if host['bgp']['confed_asn'] is defined and host['bgp']['confed_peers'] is defined %} + bgp confederation identifier {{ host['bgp']['confed_asn'] }} + bgp confederation peers {{ host['bgp']['confed_peers'] }} +! +{% endif %} {% for asn, remote_ips in host['bgp']['peers'].items() %} {% for remote_ip in remote_ips %} neighbor {{ remote_ip }} remote-as {{ asn }} diff --git a/ansible/roles/eos/templates/t1-lag-spine.j2 b/ansible/roles/eos/templates/t1-lag-spine.j2 index d7459fe33b..8764e34480 100644 --- a/ansible/roles/eos/templates/t1-lag-spine.j2 +++ b/ansible/roles/eos/templates/t1-lag-spine.j2 @@ -102,8 +102,13 @@ interface {{ bp_ifname }} router bgp {{ host['bgp']['asn'] }} router-id {{ host['bgp']['router-id'] if host['bgp']['router-id'] is defined else host['interfaces']['Loopback0']['ipv4'] | ansible.utils.ipaddr('address') }} ! +{% if host['bgp']['confed_asn'] is defined and host['bgp']['confed_peers'] is defined %} + bgp confederation identifier {{ host['bgp']['confed_asn'] }} + bgp confederation peers {{ host['bgp']['confed_peers'] }} +! +{% endif %} {% for asn, remote_ips in host['bgp']['peers'].items() %} -{% for remote_ip in remote_ips %} + {% for remote_ip in remote_ips %} neighbor {{ remote_ip }} remote-as {{ asn }} neighbor {{ remote_ip }} description {{ asn }} neighbor {{ remote_ip }} next-hop-self diff --git a/ansible/roles/eos/templates/t2-leaf.j2 b/ansible/roles/eos/templates/t2-leaf.j2 index cdb746f639..d115e4f53f 100644 --- a/ansible/roles/eos/templates/t2-leaf.j2 +++ b/ansible/roles/eos/templates/t2-leaf.j2 @@ -108,6 +108,12 @@ interface {{ bp_ifname }} router bgp {{ host['bgp']['asn'] }} router-id {{ host['interfaces']['Loopback0']['ipv4'] | ansible.utils.ipaddr('address') }} ! +{% if host['bgp']['confed_asn'] is defined and host['bgp']['confed_peers'] is defined %} + bgp confederation identifier {{ host['bgp']['confed_asn'] }} + bgp confederation peers {{ host['bgp']['confed_peers'] }} + ! + {% endif %} + {% for asn, remote_ips in host['bgp']['peers'].items() %} {% for remote_ip in remote_ips %} neighbor {{ remote_ip }} remote-as {{ asn }} diff --git a/tests/bgp/conftest.py b/tests/bgp/conftest.py index a90980c2dd..f79366a34a 100644 --- a/tests/bgp/conftest.py +++ b/tests/bgp/conftest.py @@ -553,6 +553,9 @@ def bgpmon_setup_teardown(ptfhost, duthosts, enum_rand_one_per_hwsku_frontend_ho peer_addr = connection['neighbor_addr'].split("/")[0] mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts'] asn = mg_facts['minigraph_bgp_asn'] + confed_asn = duthost.get_bgp_confed_asn() + if confed_asn: + asn = confed_asn # TODO: Add a common method to load BGPMON config for test_bgpmon and test_traffic_shift logger.info("Configuring bgp monitor session on DUT") bgpmon_args = { diff --git a/tests/bgp/route_checker.py b/tests/bgp/route_checker.py index d04b6f05d3..9d29a1f0ec 100644 --- a/tests/bgp/route_checker.py +++ b/tests/bgp/route_checker.py @@ -63,7 +63,8 @@ def parse_routes_on_eos(dut_host, neigh_hosts, ip_ver, exp_community=[]): """ mg_facts = dut_host.minigraph_facts( host=dut_host.hostname)['ansible_facts'] - asn = mg_facts['minigraph_bgp_asn'] + confed_asn = dut_host.get_bgp_confed_asn() + all_routes = {} BGP_ENTRY_HEADING = r"BGP routing table entry for " BGP_COMMUNITY_HEADING = r"Community: " @@ -83,6 +84,10 @@ def parse_routes_process(node=None, results=None, my_community=exp_community): # get hostname('ARISTA11T0') by VM name('VM0122') hostname = host_name_map[node['host'].hostname] host = node['host'] + peer_in_bgp_confed = node['conf']['bgp'].get('peer_in_bgp_confed', False) + asn = mg_facts['minigraph_bgp_asn'] + if peer_in_bgp_confed: + asn = confed_asn peer_ips = node['conf']['bgp']['peers'][asn] for ip in peer_ips: if ipaddress.IPNetwork(ip).version == 4: @@ -154,7 +159,7 @@ def parse_routes_process(node=None, results=None, my_community=exp_community): def parse_routes_on_vsonic(dut_host, neigh_hosts, ip_ver): mg_facts = dut_host.minigraph_facts( host=dut_host.hostname)['ansible_facts'] - asn = mg_facts['minigraph_bgp_asn'] + confed_asn = dut_host.get_bgp_confed_asn() all_routes = {} host_name_map = {} @@ -164,6 +169,10 @@ def parse_routes_on_vsonic(dut_host, neigh_hosts, ip_ver): def parse_routes_process_vsonic(node=None, results=None): hostname = host_name_map[node['host'].hostname] host = node['host'] + peer_in_bgp_confed = node['conf']['bgp'].get('peer_in_bgp_confed', False) + asn = mg_facts['minigraph_bgp_asn'] + if peer_in_bgp_confed: + asn = confed_asn peer_ips = node['conf']['bgp']['peers'][asn] for ip in peer_ips: diff --git a/tests/common/helpers/bgp.py b/tests/common/helpers/bgp.py index c9970453b5..d00a7fdab4 100644 --- a/tests/common/helpers/bgp.py +++ b/tests/common/helpers/bgp.py @@ -66,11 +66,20 @@ def run_bgp_facts(duthost, enum_asic_index): namespace = duthost.get_namespace_from_asic_id(enum_asic_index) config_facts = duthost.config_facts(host=duthost.hostname, source="running", namespace=namespace)['ansible_facts'] sonic_db_cmd = "sonic-db-cli {}".format("-n " + namespace if namespace else "") + bgp_confed_asn = config_facts.get('BGP_DEVICE_GLOBAL', {}).get('CONFED', {}).get('asn', None) + bgp_asn = int(config_facts['DEVICE_METADATA']['localhost']['bgp_asn'].encode().decode("utf-8")) for k, v in list(bgp_facts['bgp_neighbors'].items()): # Verify bgp sessions are established assert v['state'] == 'established' # Verify local ASNs in bgp sessions - assert v['local AS'] == int(config_facts['DEVICE_METADATA']['localhost']['bgp_asn'].encode().decode("utf-8")) + confed_peer = v.get('confed_peer', False) + if bgp_confed_asn: + if confed_peer: + assert v['local AS'] == int(bgp_asn) + else: + assert v['local AS'] == int(bgp_confed_asn) + else: + assert v['local AS'] == bgp_asn # Check bgpmon functionality by validate STATE DB contains this neighbor as well state_fact = duthost.shell('{} STATE_DB HGET "NEIGH_STATE_TABLE|{}" "state"' .format(sonic_db_cmd, k), module_ignore_errors=False)['stdout_lines']