-
Notifications
You must be signed in to change notification settings - Fork 999
Implement the BGP Bounce Back Routing test script #2538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
0eea567
Implement the BGP Bounce Back Routing test script
wangxin a3e5365
Update the script for multi-DUT
wangxin 13c4e45
Update per review comments
wangxin 43b95f1
Make better use of defaultdict
wangxin 16073c9
Fix typo of using command 'announce' for 'withdraw'
wangxin 8ecb748
Explicitly enable bbr for bbr enabled cases
wangxin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "BGP_BBR": { | ||
| "all": { | ||
| "status": "{{ BGP_BBR_STATUS }}" | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,271 @@ | ||
| '''This script is to test the BGP Bounce Back Routing (BBR) feature of SONiC. | ||
| ''' | ||
| import json | ||
| import logging | ||
| import time | ||
|
|
||
| from collections import defaultdict | ||
| from collections import namedtuple | ||
|
|
||
| import pytest | ||
| import requests | ||
| import ipaddr as ipaddress | ||
|
|
||
| from jinja2 import Template | ||
| from natsort import natsorted | ||
| from tests.common.helpers.assertions import pytest_assert | ||
| from tests.common.helpers.parallel import reset_ansible_local_tmp | ||
| from tests.common.helpers.parallel import parallel_run | ||
|
|
||
| pytestmark = [ | ||
| pytest.mark.topology('t1'), | ||
| pytest.mark.device_type('vs') | ||
| ] | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| EXABGP_BASE_PORT = 5000 | ||
| EXABGP_BASE_PORT_V6 = 6000 | ||
|
|
||
|
|
||
| BBR_PREFIX = '172.16.10.0/24' | ||
| BBR_PREFIX_V6 = '2000:0:172:16:10::/96' | ||
|
|
||
| DUMMY_ASN1 = 64101 | ||
| DUMMY_ASN2 = 64102 | ||
|
|
||
|
|
||
| @pytest.fixture(scope='module', autouse=True) | ||
| def prepare_bbr_config_files(duthosts, rand_one_dut_hostname): | ||
| duthost = duthosts[rand_one_dut_hostname] | ||
| bgp_bbr_config = Template(open("./bgp/templates/bgp_bbr_config.json.j2").read()) | ||
|
|
||
| duthost.copy(content=bgp_bbr_config.render(BGP_BBR_STATUS='disabled'), dest='/tmp/disable_bbr.json') | ||
| duthost.copy(content=bgp_bbr_config.render(BGP_BBR_STATUS='enabled'), dest='/tmp/enable_bbr.json') | ||
|
|
||
|
|
||
| def enable_bbr(duthost): | ||
| logger.info('Disable BGP_BBR') | ||
| duthost.shell('sonic-cfggen -j /tmp/enable_bbr.json -w ') | ||
|
|
||
|
|
||
| def disable_bbr(duthost): | ||
| logger.info('Enable BGP_BBR') | ||
| duthost.shell('sonic-cfggen -j /tmp/disable_bbr.json -w') | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def config_bbr_disabled(duthosts, rand_one_dut_hostname): | ||
| duthost = duthosts[rand_one_dut_hostname] | ||
|
|
||
| disable_bbr(duthost) | ||
| time.sleep(3) | ||
| yield | ||
|
|
||
| enable_bbr(duthost) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def config_bbr_enabled(duthosts, rand_one_dut_hostname): | ||
| duthost = duthosts[rand_one_dut_hostname] | ||
| enable_bbr(duthost) | ||
| time.sleep(3) | ||
|
|
||
|
|
||
| @pytest.fixture(scope='module') | ||
| def setup(duthosts, rand_one_dut_hostname, tbinfo, nbrhosts): | ||
| duthost = duthosts[rand_one_dut_hostname] | ||
| if tbinfo['topo']['type'] != 't1': | ||
| pytest.skip('Unsupported topology type: {}, supported: {}'.format(tbinfo['topo']['type'], 't1')) | ||
|
|
||
| mg_facts = duthost.get_extended_minigraph_facts(tbinfo) | ||
|
|
||
| tor_neighbors = natsorted([neighbor for neighbor in nbrhosts.keys() if neighbor.endswith('T0')]) | ||
| t2_neighbors = [neighbor for neighbor in nbrhosts.keys() if neighbor.endswith('T2')] | ||
| tor1 = tor_neighbors[0] | ||
| other_vms = tor_neighbors[1:] + t2_neighbors | ||
|
|
||
| neigh_peer_map = defaultdict(dict) | ||
| for bgp_neigh in mg_facts['minigraph_bgp']: | ||
| name = bgp_neigh['name'] | ||
| peer_addr = bgp_neigh['peer_addr'] | ||
| if ipaddress.IPAddress(peer_addr).version == 4: | ||
| neigh_peer_map[name].update({'peer_addr': peer_addr}) | ||
| else: | ||
| neigh_peer_map[name].update({'peer_addr_v6': peer_addr}) | ||
|
|
||
| # Announce route to one of the T0 VM | ||
| tor1_offset = tbinfo['topo']['properties']['topology']['VMs'][tor1]['vm_offset'] | ||
| tor1_exabgp_port = EXABGP_BASE_PORT + tor1_offset | ||
| tor1_exabgp_port_v6 = EXABGP_BASE_PORT_V6 + tor1_offset | ||
|
|
||
| dut_asn = tbinfo['topo']['properties']['configuration_properties']['common']['dut_asn'] | ||
| tor1_asn = nbrhosts[tor1]['conf']['bgp']['asn'] | ||
| aspath = '{} {}'.format(dut_asn, DUMMY_ASN1) | ||
| aspath_dual_dut_asn = '{} {} {} {}'.format(dut_asn, DUMMY_ASN1, dut_asn, DUMMY_ASN2) | ||
|
|
||
| Route = namedtuple('Route', ['prefix', 'nexthop', 'aspath']) | ||
|
|
||
| bbr_route = Route(BBR_PREFIX, neigh_peer_map[tor1]['peer_addr'], aspath) | ||
| bbr_route_v6 = Route(BBR_PREFIX_V6, neigh_peer_map[tor1]['peer_addr_v6'], aspath) | ||
|
|
||
| bbr_route_dual_dut_asn = Route(BBR_PREFIX, neigh_peer_map[tor1]['peer_addr'], aspath_dual_dut_asn) | ||
| bbr_route_v6_dual_dut_asn = Route(BBR_PREFIX_V6, neigh_peer_map[tor1]['peer_addr_v6'], aspath_dual_dut_asn) | ||
|
|
||
| setup_info = { | ||
| 'tor1': tor1, | ||
| 'other_vms': other_vms, | ||
| 'tor1_offset': tor1_offset, | ||
| 'tor1_exabgp_port': tor1_exabgp_port, | ||
| 'tor1_exabgp_port_v6': tor1_exabgp_port_v6, | ||
| 'dut_asn': dut_asn, | ||
| 'tor1_asn': tor1_asn, | ||
| 'bbr_route': bbr_route, | ||
| 'bbr_route_v6': bbr_route_v6, | ||
| 'bbr_route_dual_dut_asn': bbr_route_dual_dut_asn, | ||
| 'bbr_route_v6_dual_dut_asn': bbr_route_v6_dual_dut_asn | ||
| } | ||
|
|
||
| logger.info('setup_info: {}'.format(json.dumps(setup_info, indent=2))) | ||
|
|
||
| return setup_info | ||
|
|
||
|
|
||
| def update_routes(action, ptfip, port, route): | ||
| if action == 'announce': | ||
| msg = '{} route {} next-hop {} as-path [ {} ]'.format(action, route.prefix, route.nexthop, route.aspath) | ||
| elif action == 'withdraw': | ||
| msg = '{} route {} next-hop {}'.format(action, route.prefix, route.nexthop) | ||
| else: | ||
| logger.error('Unsupported route update operation.') | ||
| return | ||
| url = 'http://%s:%d' % (ptfip, port) | ||
| data = {'commands': msg } | ||
| r = requests.post(url, data=data) | ||
| assert r.status_code == 200 | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def prepare_routes(setup, ptfhost): | ||
| tor1_exabgp_port = setup['tor1_exabgp_port'] | ||
| tor1_exabgp_port_v6 = setup['tor1_exabgp_port_v6'] | ||
|
|
||
| bbr_routes = [] | ||
|
|
||
| def announce_routes(routes): | ||
| logger.info('Announce routes {} to the first T0'.format(str(routes))) | ||
| for route in routes: | ||
| bbr_routes.append(route) | ||
| if ipaddress.IPNetwork(route.prefix).version == 4: | ||
| update_routes('announce', ptfhost.mgmt_ip, tor1_exabgp_port, route) | ||
| else: | ||
| update_routes('announce', ptfhost.mgmt_ip, tor1_exabgp_port_v6, route) | ||
| time.sleep(3) | ||
|
|
||
| yield announce_routes | ||
|
|
||
| logger.info('Withdraw routes {} from the first T0'.format(str(bbr_routes))) | ||
| for route in bbr_routes: | ||
| if ipaddress.IPNetwork(route.prefix).version == 4: | ||
| update_routes('withdraw', ptfhost.mgmt_ip, tor1_exabgp_port, route) | ||
| else: | ||
| update_routes('withdraw', ptfhost.mgmt_ip, tor1_exabgp_port_v6, route) | ||
|
|
||
|
|
||
| def check_bbr_route_propagation(duthost, nbrhosts, setup, route, accepted=True): | ||
|
|
||
| tor1 = setup['tor1'] | ||
| tor1_asn = setup['tor1_asn'] | ||
| other_vms = setup['other_vms'] | ||
|
|
||
| # Check route on tor1 | ||
| logger.info('Check route for prefix {} on {}'.format(route.prefix, tor1)) | ||
| tor1_route = nbrhosts[tor1]['host'].get_route(route.prefix) | ||
| pytest_assert(route.prefix in tor1_route['vrfs']['default']['bgpRouteEntries'].keys(), | ||
| 'No route for {} found on {}'.format(route.prefix, tor1)) | ||
| tor1_route_aspath = tor1_route['vrfs']['default']['bgpRouteEntries'][route.prefix]['bgpRoutePaths'][0]\ | ||
| ['asPathEntry']['asPath'] | ||
| pytest_assert(tor1_route_aspath==route.aspath, | ||
| 'On {} expected aspath: {}, actual aspath: {}'.format(tor1, route.aspath, tor1_route_aspath)) | ||
|
|
||
| # Check route on DUT | ||
| logger.info('Check route on DUT') | ||
| dut_route = duthost.get_route(route.prefix) | ||
| if accepted: | ||
| pytest_assert(dut_route, 'No route for {} found on DUT'.format(route.prefix)) | ||
| dut_route_aspath = dut_route['paths'][0]['aspath']['string'] | ||
| # Route path from DUT: -> TOR1 -> aspath(other T1 -> DUMMY_ASN1) | ||
| dut_route_aspath_expected = '{} {}'.format(tor1_asn, route.aspath) | ||
| pytest_assert(dut_route_aspath==dut_route_aspath_expected, | ||
| 'On DUT expected aspath: {}, actual aspath: {}'.format(dut_route_aspath_expected, dut_route_aspath)) | ||
| else: | ||
| pytest_assert(not dut_route, 'Prefix {} should not be accepted by DUT'.format(route.prefix)) | ||
|
|
||
| # Check route on other VMs | ||
| @reset_ansible_local_tmp | ||
| def check_other_vms(nbrhosts, setup, route, accepted=True, node=None, results=None): | ||
| logger.info('Check route {} on {}'.format(str(route), node)) | ||
|
|
||
| dut_asn = setup['dut_asn'] | ||
| tor1_asn = setup['tor1_asn'] | ||
|
|
||
| vm_route = nbrhosts[node]['host'].get_route(route.prefix) | ||
| vm_route = {'failed': False} | ||
| vm_route['tor_route'] = vm_route | ||
| if accepted: | ||
| if route.prefix not in vm_route['vrfs']['default']['bgpRouteEntries'].keys(): | ||
| vm_route['failed'] = True | ||
| vm_route['message'] = 'No route for {} found on {}'.format(route.prefix, node) | ||
| else: | ||
| tor_route_aspath = vm_route['vrfs']['default']['bgpRouteEntries'][route.prefix]['bgpRoutePaths'][0]\ | ||
| ['asPathEntry']['asPath'] | ||
| # Route path from other VMs: -> DUT(T1) -> TOR1 -> aspath(other T1 -> DUMMY_ASN1) | ||
| tor_route_aspath_expected = '{} {} {}'.format(dut_asn, tor1_asn, route.aspath) | ||
| if tor_route_aspath != tor_route_aspath_expected: | ||
| vm_route['failed'] = True | ||
| vm_route['message'] = 'On {} expected aspath: {}, actual aspath: {}'\ | ||
| .format(node, tor_route_aspath_expected, tor_route_aspath) | ||
| else: | ||
| if route.prefix in vm_route['vrfs']['default']['bgpRouteEntries'].keys(): | ||
| vm_route['failed'] = True | ||
| vm_route['message'] = 'No route {} expected on {}'.format(route.prefix, node) | ||
| vm_route['message'] = 'Checking route {} on {} passed'.format(str(route), node) | ||
| results[node] = vm_route | ||
|
|
||
| results = parallel_run(check_other_vms, (nbrhosts, setup, route), {'accepted': accepted}, other_vms, timeout=120) | ||
|
|
||
| failed_results = {} | ||
| for node, result in results.items(): | ||
| if result['failed']: | ||
| failed_results[node] = result | ||
|
|
||
| pytest_assert(not failed_results, 'Checking route {} failed, failed_results: {}'\ | ||
| .format(str(route), json.dumps(failed_results, indent=2))) | ||
|
|
||
|
|
||
| def test_bbr_enabled_dut_asn_in_aspath(duthosts, rand_one_dut_hostname, nbrhosts, config_bbr_enabled, setup, prepare_routes): | ||
| duthost = duthosts[rand_one_dut_hostname] | ||
| bbr_route = setup['bbr_route'] | ||
| bbr_route_v6 = setup['bbr_route_v6'] | ||
| prepare_routes([bbr_route, bbr_route_v6]) | ||
| for route in [bbr_route, bbr_route_v6]: | ||
| check_bbr_route_propagation(duthost, nbrhosts, setup, route, accepted=True) | ||
|
|
||
|
|
||
| def test_bbr_enabled_dual_dut_asn_in_aspath(duthosts, rand_one_dut_hostname, nbrhosts, config_bbr_enabled, setup, prepare_routes): | ||
| duthost = duthosts[rand_one_dut_hostname] | ||
| bbr_route_dual_dut_asn = setup['bbr_route_dual_dut_asn'] | ||
| bbr_route_v6_dual_dut_asn = setup['bbr_route_v6_dual_dut_asn'] | ||
| prepare_routes([bbr_route_dual_dut_asn, bbr_route_v6_dual_dut_asn]) | ||
| for route in [bbr_route_dual_dut_asn, bbr_route_v6_dual_dut_asn]: | ||
| check_bbr_route_propagation(duthost, nbrhosts, setup, route, accepted=False) | ||
|
|
||
|
|
||
| def test_bbr_disabled_dut_asn_in_aspath(duthosts, rand_one_dut_hostname, nbrhosts, config_bbr_disabled, setup, prepare_routes): | ||
| duthost = duthosts[rand_one_dut_hostname] | ||
| bbr_route = setup['bbr_route'] | ||
| bbr_route_v6 = setup['bbr_route_v6'] | ||
| prepare_routes([bbr_route, bbr_route_v6]) | ||
| for route in (bbr_route, bbr_route_v6): | ||
| check_bbr_route_propagation(duthost, nbrhosts, setup, route, accepted=False) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.