-
Notifications
You must be signed in to change notification settings - Fork 1k
Add performance test for set/delete routes #2344
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
13 commits
Select commit
Hold shift + click to select a range
300c9ae
Add performance test for set/delete routes
86adb3d
fix a typo
97001aa
Improve test code
60fa45e
remove unnecessary imports and add pytest mark
cd2bdf9
move _check_num_routes()
b7bcad4
update pull interval
3ca6363
Address review comments
8e003ab
move option to tests/route/conftest.py
a7e444f
restore line spacing in tests/conftest.py
d4e47d6
Add ipv6 and ECMP
e5adfe4
using eval function to get keys
2cd203d
Move addoption to conftest.py in vxlan to avoid addoption conflict
b5fdf1b
update mac address
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
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,5 @@ | ||
| # Pytest configuration used by the route tests. | ||
| def pytest_addoption(parser): | ||
| # Add options to pytest that are used by route tests | ||
| parser.addoption("--num_routes", action="store", default=10000, type=int, | ||
| help="Number of routes for add/delete") |
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,186 @@ | ||
| import pytest | ||
| import json | ||
| from datetime import datetime | ||
| from tests.common.utilities import wait_until | ||
|
|
||
| pytestmark = [ | ||
| pytest.mark.topology('any'), | ||
| pytest.mark.device_type('vs') | ||
| ] | ||
|
|
||
| ROUTE_TABLE_NAME = 'ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY' | ||
|
|
||
| def prepare_dut(duthost, intf_neighs): | ||
| for intf_neigh in intf_neighs: | ||
| # Set up interface | ||
| duthost.shell('sudo config interface ip add {} {}'.format(intf_neigh['interface'], intf_neigh['ip'])) | ||
|
|
||
| # Set up neighbor | ||
| duthost.shell('sudo ip neigh add {} lladdr {} dev {}'.format(intf_neigh['neighbor'], intf_neigh['mac'], intf_neigh['interface'])) | ||
|
|
||
| def cleanup_dut(duthost, intf_neighs): | ||
| for intf_neigh in intf_neighs: | ||
| # Delete neighbor | ||
| duthost.shell('sudo ip neigh del {} dev {}'.format(intf_neigh['neighbor'], intf_neigh['interface'])) | ||
|
|
||
| # remove interface | ||
| duthost.shell('sudo config interface ip remove {} {}'.format(intf_neigh['interface'], intf_neigh['ip'])) | ||
|
|
||
| def generate_intf_neigh(num_neigh): | ||
| # Generate interfaces and neighbors | ||
| intf_neighs = [] | ||
| str_intf_nexthop = {'ifname':'', 'nexthop':''} | ||
| for idx_neigh in range(num_neigh): | ||
| intf_neigh = { | ||
| 'interface' : 'Ethernet%d' % (idx_neigh * 4 + 4), | ||
| 'ip' : '10.%d.0.1/24' % (idx_neigh + 1), | ||
| 'neighbor' : '10.%d.0.2' % (idx_neigh + 1), | ||
| 'mac' : '54:54:00:ad:48:%0.2x' % idx_neigh | ||
| } | ||
| intf_neighs.append(intf_neigh) | ||
| if idx_neigh == 0: | ||
| str_intf_nexthop['ifname'] += intf_neigh['interface'] | ||
| str_intf_nexthop['nexthop'] += intf_neigh['neighbor'] | ||
| else: | ||
| str_intf_nexthop['ifname'] += ',' + intf_neigh['interface'] | ||
| str_intf_nexthop['nexthop'] += ',' + intf_neigh['neighbor'] | ||
|
|
||
| return intf_neighs, str_intf_nexthop | ||
|
|
||
| def generate_intf_neigh_ipv6(num_neigh): | ||
| # Generate interfaces and neighbors | ||
| intf_neighs = [] | ||
| str_intf_nexthop = {'ifname':'', 'nexthop':''} | ||
| for idx_neigh in range(num_neigh): | ||
| intf_neigh = { | ||
| 'interface' : 'Ethernet%d' % (idx_neigh * 4), | ||
| 'ip' : '%x::1/64' % (0x2000 + idx_neigh), | ||
| 'neighbor' : '%x::2' % (0x2000 + idx_neigh), | ||
| 'mac' : '54:54:00:ad:48:%0.2x' % idx_neigh | ||
| } | ||
| intf_neighs.append(intf_neigh) | ||
| if idx_neigh == 0: | ||
| str_intf_nexthop['ifname'] += intf_neigh['interface'] | ||
| str_intf_nexthop['nexthop'] += intf_neigh['neighbor'] | ||
| else: | ||
| str_intf_nexthop['ifname'] += ',' + intf_neigh['interface'] | ||
| str_intf_nexthop['nexthop'] += ',' + intf_neigh['neighbor'] | ||
|
|
||
| return intf_neighs, str_intf_nexthop | ||
|
|
||
| def generate_route_file(duthost, prefixes, str_intf_nexthop, dir, op): | ||
| route_data = [] | ||
| for prefix in prefixes: | ||
| key = 'ROUTE_TABLE:' + prefix | ||
| route = {} | ||
| route['ifname'] = str_intf_nexthop['ifname'] | ||
| route['nexthop'] = str_intf_nexthop['nexthop'] | ||
| route_command = {} | ||
| route_command[key] = route | ||
| route_command['OP'] = op | ||
| route_data.append(route_command) | ||
|
|
||
| # Copy json file to DUT | ||
| duthost.copy(content=json.dumps(route_data, indent=4), dest=dir) | ||
|
|
||
| def exec_routes(duthost, prefixes, str_intf_nexthop, op): | ||
| # Create a tempfile for routes | ||
| route_file_dir = duthost.shell('mktemp')['stdout'] | ||
|
|
||
| # Generate json file for routes | ||
| generate_route_file(duthost, prefixes, str_intf_nexthop, route_file_dir, op) | ||
|
|
||
| # Check the number of routes in ASIC_DB | ||
| start_num_route = int(duthost.shell('sonic-db-cli ASIC_DB eval "return #redis.call(\'keys\', \'{}*\')" 0'.format(ROUTE_TABLE_NAME))['stdout']) | ||
|
|
||
| # Calculate timeout as a function of the number of routes | ||
| route_timeout = max(len(prefixes) / 500, 1) # Allow at least 1 second even when there is a limited number of routes | ||
|
|
||
| # Calculate expected number of route and record start time | ||
| if op == 'SET': | ||
| expected_num_routes = start_num_route + len(prefixes) | ||
| elif op == 'DEL': | ||
| expected_num_routes = start_num_route - len(prefixes) | ||
| else: | ||
| pytest.fail('Operation {} not supported'.format(op)) | ||
| start_time = datetime.now() | ||
|
|
||
| # Apply routes with swssconfig | ||
| duthost.shell('docker exec -i swss swssconfig /dev/stdin < {}'.format(route_file_dir)) | ||
|
|
||
| # Wait until the routes set/del applys to ASIC_DB | ||
| def _check_num_routes(expected_num_routes): | ||
| # Check the number of routes in ASIC_DB | ||
| num_routes = int(duthost.shell('sonic-db-cli ASIC_DB eval "return #redis.call(\'keys\', \'{}*\')" 0'.format(ROUTE_TABLE_NAME))['stdout']) | ||
| return num_routes == expected_num_routes | ||
| if not wait_until(route_timeout, 0.5, _check_num_routes, expected_num_routes): | ||
| pytest.fail('failed to add routes within time limit') | ||
|
|
||
| # Record time when all routes show up in ASIC_DB | ||
| end_time = datetime.now() | ||
|
|
||
| # Check route entries are correct | ||
| asic_route_keys = duthost.shell('sonic-db-cli ASIC_DB eval "return redis.call(\'keys\', \'{}*\')" 0'.format(ROUTE_TABLE_NAME))['stdout_lines'] | ||
| asic_prefixes = [] | ||
| for key in asic_route_keys: | ||
| json_obj = key[len(ROUTE_TABLE_NAME) + 1 : ] | ||
| asic_prefixes.append(json.loads(json_obj)['dest']) | ||
| if op == 'SET': | ||
| assert all(prefix in asic_prefixes for prefix in prefixes) | ||
| elif op == 'DEL': | ||
| assert all(prefix not in asic_prefixes for prefix in prefixes) | ||
| else: | ||
| pytest.fail('Operation {} not supported'.format(op)) | ||
|
|
||
| # Retuen time used for set/del routes | ||
| return (end_time - start_time).total_seconds() | ||
|
|
||
| def test_perf_add_remove_routes(duthost, request): | ||
| # Number of routes for test | ||
| num_routes = request.config.getoption("--num_routes") | ||
|
|
||
| # Generate interfaces and neighbors | ||
| intf_neighs, str_intf_nexthop = generate_intf_neigh(8) | ||
|
|
||
| # Generate ip prefixes of routes | ||
| prefixes = ['%d.%d.%d.%d/%d' % (101 + int(idx_route / 256 ** 2), int(idx_route / 256) % 256, idx_route % 256, 0, 24) | ||
| for idx_route in range(num_routes)] | ||
|
|
||
| # Set up interface and interface for routes | ||
| prepare_dut(duthost, intf_neighs) | ||
|
|
||
| # Add routes | ||
| time_set = exec_routes(duthost, prefixes, str_intf_nexthop, 'SET') | ||
| print('Time to set %d ipv4 routes is %.2f seconds.' % (num_routes, time_set)) | ||
|
|
||
| # Remove routes | ||
| time_del = exec_routes(duthost, prefixes, str_intf_nexthop, 'DEL') | ||
| print('Time to del %d ipv4 routes is %.2f seconds.' % (num_routes, time_del)) | ||
|
|
||
| # Cleanup DUT | ||
| cleanup_dut(duthost, intf_neighs) | ||
|
|
||
| def test_perf_add_remove_routes_ipv6(duthost, request): | ||
| # Number of routes for test | ||
| num_routes = request.config.getoption("--num_routes") | ||
|
|
||
| # Generate interfaces and neighbors | ||
| intf_neighs, str_intf_nexthop = generate_intf_neigh_ipv6(8) | ||
|
|
||
| # Generate ip prefixes of routes | ||
| prefixes = ['%x:%x:%x::/%d' % (0x3000 + int(idx_route / 65536), idx_route % 65536, 1, 64) | ||
| for idx_route in range(num_routes)] | ||
|
|
||
| # Set up interface and interface for routes | ||
| prepare_dut(duthost, intf_neighs) | ||
|
|
||
| # Add routes | ||
| time_set = exec_routes(duthost, prefixes, str_intf_nexthop, 'SET') | ||
| print('Time to set %d ipv6 routes is %.2f seconds.' % (num_routes, time_set)) | ||
|
|
||
| # Remove routes | ||
| time_del = exec_routes(duthost, prefixes, str_intf_nexthop, 'DEL') | ||
| print('Time to del %d ipv6 routes is %.2f seconds.' % (num_routes, time_del)) | ||
|
|
||
| # Cleanup DUT | ||
| cleanup_dut(duthost, intf_neighs) |
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
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,54 @@ | ||
| def pytest_addoption(parser): | ||
| """ | ||
| Adds pytest options that are used by VxLAN tests | ||
| """ | ||
|
|
||
| parser.addoption( | ||
| "--num_vnet", | ||
| action="store", | ||
| default=8, | ||
| type=int, | ||
| help="number of VNETs for VNET VxLAN test" | ||
| ) | ||
|
|
||
| parser.addoption( | ||
| "--num_routes", | ||
| action="store", | ||
| default=16000, | ||
| type=int, | ||
| help="number of routes for VNET VxLAN test" | ||
| ) | ||
|
|
||
| parser.addoption( | ||
| "--num_endpoints", | ||
| action="store", | ||
| default=4000, | ||
| type=int, | ||
| help="number of endpoints for VNET VxLAN" | ||
| ) | ||
|
|
||
| parser.addoption( | ||
| "--num_intf_per_vnet", | ||
| action="store", | ||
| default=1, | ||
| type=int, | ||
| help="number of VLAN interfaces per VNET" | ||
| ) | ||
|
|
||
| parser.addoption( | ||
| "--ipv6_vxlan_test", | ||
| action="store_true", | ||
| help="Use IPV6 for VxLAN test" | ||
| ) | ||
|
|
||
| parser.addoption( | ||
| "--skip_cleanup", | ||
| action="store_true", | ||
| help="Do not cleanup after VNET VxLAN test" | ||
| ) | ||
|
|
||
| parser.addoption( | ||
| "--skip_apply_config", | ||
| action="store_true", | ||
| help="Apply new configurations on DUT" | ||
| ) |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have vxlan change in this PR? Suggest to separate if not part of this feature. @theasianpianist for visibility
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is to resolve the
--num-routesoption conflicting issue. Please refer to #2344 (comment)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
--num_routesoption is used for route tests and vxlan tests. Currently, vxlan test putpytest_addoptionin init.py. This will be seen by pytest during initialization and cause conflict if any other tests try to add--num_routesoption. I movedpytest_addoptionto conftest.py to resolve conflict.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@prsunny I originally put these in
__init__.pybecause we had a bug that caused CLI options defined in test specificconftest.pyfiles to not be recognized. I believe @wangxin has since fixed the issue, so this should be fine.