Skip to content

Commit dee1b38

Browse files
authored
Run test_announce_routes.py only with add-topo (#2741)
What is the motivation for this PR? Although the test_announce_routes.py script is always executed by the run_tests.sh tool while preparing DUT, not every vendor uses run_tests.sh to trigger testing. People usually forget to run test_announce_routes.py after a new topology is deployed. Since announcing routes just need to be executed once after a new topology is deployed, life could be a little bit easier if we announce routes together with add-topo. How did you do it? Changes in this PR: * Added new ansible module announce_routes.py for announcing routes. * Added new playbook announce_routes.yml for starting exabgp processes on PTF and call the announce_routes module. * Updated the add-topo.yml to run the new playbook. * Removed the fib plugin at tests/common/plugins/fib.py * Removed the tests/test_announce_routes.py script * The tests/decap/test_decap.py script was dependent on the fib plugin. Updated this test to generate fib_info file from redis DB information. * Updated the IP_decap_test.py script not to test all the FIB entries to limit test execution time. How did you verify/test it? * Test run remove-topo and add-topo on both VS setup and physical setup. * Test run run_tests.sh. * Test run tests/decap/test_decap.py Signed-off-by: Xin Wang <[email protected]>
1 parent 42ca406 commit dee1b38

File tree

10 files changed

+450
-376
lines changed

10 files changed

+450
-376
lines changed

ansible/library/exabgp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/python
1+
#!/usr/bin/env python
22

33
import re
44

ansible/roles/test/files/ptftests/IP_decap_test.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,20 @@ def run_encap_combination_test(self, outer_pkt_type, inner_pkt_type):
345345
else:
346346
raise Exception('ERROR: Invalid inner packet type passed: ', inner_pkt_type)
347347

348-
for ip_range in ip_ranges:
348+
ip_ranges_length = len(ip_ranges)
349+
if ip_ranges_length > 150:
350+
# This is to limit the test execution time. Because the IP ranges in the head and tail of the list are
351+
# kind of special. We need to always cover them. The IP ranges in the middle are not fundamentally
352+
# different. We can just sample some IP ranges in the middle. Using this method, test coverage is not
353+
# compromized. Test execution time can be reduced from over 5000 seconds to around 300 seconds.
354+
last_ten_index = ip_ranges_length - 10
355+
covered_ip_ranges = ip_ranges[:100] + \
356+
random.sample(ip_ranges[100:last_ten_index], 40) + \
357+
ip_ranges[last_ten_index:]
358+
else:
359+
covered_ip_ranges = ip_ranges[:]
360+
361+
for ip_range in covered_ip_ranges:
349362

350363
# Skip the IP range on VLAN interface, t0 topology
351364
if inner_pkt_type == 'ipv4' and self.vlan_ip and \
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
#!/usr/bin/env python
2+
3+
import math
4+
import os
5+
import yaml
6+
import re
7+
import requests
8+
9+
from ansible.module_utils.basic import *
10+
11+
DOCUMENTATION = '''
12+
module: announce_routes
13+
short_description: announce routes to exabgp processes running in PTF container
14+
description: Announce routes to exabgp processes running in PTF container. This module must be executed on localhost
15+
which is the sonic-mgmt container.
16+
17+
Options:
18+
- option-name: topo_name
19+
description: topology name
20+
required: True
21+
22+
- option-name: ptf_ip
23+
description: PTF container management IP address
24+
required: True
25+
'''
26+
27+
EXAMPLES = '''
28+
- name: Announce routes
29+
announce_routes:
30+
topo_name: "t1-lag"
31+
ptf_ip: "192.168.1.10"
32+
delegate_to: localhost
33+
'''
34+
35+
TOPO_FILE_FOLDER = 'vars/'
36+
TOPO_FILENAME_TEMPLATE = 'topo_{}.yml'
37+
38+
PODSET_NUMBER = 200
39+
TOR_NUMBER = 16
40+
TOR_SUBNET_NUMBER = 2
41+
MAX_TOR_SUBNET_NUMBER = 16
42+
TOR_SUBNET_SIZE = 128
43+
NHIPV4 = '10.10.246.254'
44+
NHIPV6 = 'fc0a::ff'
45+
SPINE_ASN = 65534
46+
LEAF_ASN_START = 64600
47+
TOR_ASN_START = 65500
48+
IPV4_BASE_PORT = 5000
49+
IPV6_BASE_PORT = 6000
50+
51+
52+
def get_topo_type(topo_name):
53+
pattern = re.compile(r'^(t0|t1|ptf|fullmesh|dualtor)')
54+
match = pattern.match(topo_name)
55+
if not match:
56+
raise Exception("Unsupported testbed type - {}".format(topo_name))
57+
topo_type = match.group()
58+
if 'dualtor' in topo_type:
59+
# set dualtor topology type to 't0' to avoid adding it in each test script.
60+
topo_type = 't0'
61+
return topo_type
62+
63+
64+
def read_topo(topo_name):
65+
topo_file_path = os.path.join(TOPO_FILE_FOLDER, TOPO_FILENAME_TEMPLATE.format(topo_name))
66+
try:
67+
with open(topo_file_path) as f:
68+
return yaml.safe_load(f)
69+
except IOError:
70+
return {}
71+
72+
73+
def announce_routes(ptf_ip, port, routes):
74+
messages = []
75+
for prefix, nexthop, aspath in routes:
76+
if aspath:
77+
messages.append("announce route {} next-hop {} as-path [ {} ]".format(prefix, nexthop, aspath))
78+
else:
79+
messages.append("announce route {} next-hop {}".format(prefix, nexthop))
80+
81+
url = "http://%s:%d" % (ptf_ip, port)
82+
data = { "commands": ";".join(messages) }
83+
r = requests.post(url, data=data)
84+
assert r.status_code == 200
85+
86+
87+
def generate_routes(family, podset_number, tor_number, tor_subnet_number,
88+
spine_asn, leaf_asn_start, tor_asn_start,
89+
nexthop, nexthop_v6,
90+
tor_subnet_size, max_tor_subnet_number,
91+
router_type = "leaf", tor_index=None):
92+
routes = []
93+
94+
default_route_as_path = "6666 6667"
95+
96+
if router_type == "leaf":
97+
default_route_as_path = "{} {}".format(spine_asn, default_route_as_path)
98+
99+
if router_type != 'tor':
100+
if family in ["v4", "both"]:
101+
routes.append(("0.0.0.0/0", nexthop, default_route_as_path))
102+
if family in ["v6", "both"]:
103+
routes.append(("::/0", nexthop_v6, default_route_as_path))
104+
105+
# NOTE: Using large enough values (e.g., podset_number = 200,
106+
# us to overflow the 192.168.0.0/16 private address space here.
107+
# This should be fine for internal use, but may pose an issue if used otherwise
108+
for podset in range(0, podset_number):
109+
for tor in range(0, tor_number):
110+
for subnet in range(0, tor_subnet_number):
111+
if router_type == "spine":
112+
# Skip podset 0 for T2
113+
if podset == 0:
114+
continue
115+
elif router_type == "leaf":
116+
# Skip tor 0 podset 0 for T1
117+
if podset == 0 and tor == 0:
118+
continue
119+
elif router_type == "tor":
120+
# Skip non podset 0 for T0
121+
if podset != 0:
122+
continue
123+
elif tor != tor_index:
124+
continue
125+
126+
suffix = ( (podset * tor_number * max_tor_subnet_number * tor_subnet_size) + \
127+
(tor * max_tor_subnet_number * tor_subnet_size) + \
128+
(subnet * tor_subnet_size) )
129+
octet2 = (168 + (suffix / (256 ** 2)))
130+
octet1 = (192 + (octet2 / 256))
131+
octet2 = (octet2 % 256)
132+
octet3 = ((suffix / 256) % 256)
133+
octet4 = (suffix % 256)
134+
prefixlen_v4 = (32 - int(math.log(tor_subnet_size, 2)))
135+
136+
prefix = "{}.{}.{}.{}/{}".format(octet1, octet2, octet3, octet4, prefixlen_v4)
137+
prefix_v6 = "20%02X:%02X%02X:0:%02X::/64" % (octet1, octet2, octet3, octet4)
138+
139+
leaf_asn = leaf_asn_start + podset
140+
tor_asn = tor_asn_start + tor
141+
142+
aspath = None
143+
if router_type == "spine":
144+
aspath = "{} {}".format(leaf_asn, tor_asn)
145+
elif router_type == "leaf":
146+
if podset == 0:
147+
aspath = "{}".format(tor_asn)
148+
else:
149+
aspath = "{} {} {}".format(spine_asn, leaf_asn, tor_asn)
150+
151+
if family in ["v4", "both"]:
152+
routes.append((prefix, nexthop, aspath))
153+
if family in ["v6", "both"]:
154+
routes.append((prefix_v6, nexthop_v6, aspath))
155+
156+
return routes
157+
158+
159+
def fib_t0(topo, ptf_ip):
160+
161+
common_config = topo['configuration_properties'].get('common', {})
162+
podset_number = common_config.get("podset_number", PODSET_NUMBER)
163+
tor_number = common_config.get("tor_number", TOR_NUMBER)
164+
tor_subnet_number = common_config.get("tor_subnet_number", TOR_SUBNET_NUMBER)
165+
max_tor_subnet_number = common_config.get("max_tor_subnet_number", MAX_TOR_SUBNET_NUMBER)
166+
tor_subnet_size = common_config.get("tor_subnet_size", TOR_SUBNET_SIZE)
167+
nhipv4 = common_config.get("nhipv4", NHIPV4)
168+
nhipv6 = common_config.get("nhipv6", NHIPV6)
169+
spine_asn = common_config.get("spine_asn", SPINE_ASN)
170+
leaf_asn_start = common_config.get("leaf_asn_start", LEAF_ASN_START)
171+
tor_asn_start = common_config.get("tor_asn_start", TOR_ASN_START)
172+
173+
vms = topo['topology']['VMs']
174+
for vm in vms.values():
175+
vm_offset = vm['vm_offset']
176+
port = IPV4_BASE_PORT + vm_offset
177+
port6 = IPV6_BASE_PORT + vm_offset
178+
179+
routes_v4 = generate_routes("v4", podset_number, tor_number, tor_subnet_number,
180+
spine_asn, leaf_asn_start, tor_asn_start,
181+
nhipv4, nhipv4, tor_subnet_size, max_tor_subnet_number)
182+
routes_v6 = generate_routes("v6", podset_number, tor_number, tor_subnet_number,
183+
spine_asn, leaf_asn_start, tor_asn_start,
184+
nhipv6, nhipv6, tor_subnet_size, max_tor_subnet_number)
185+
186+
announce_routes(ptf_ip, port, routes_v4)
187+
announce_routes(ptf_ip, port6, routes_v6)
188+
189+
190+
def fib_t1_lag(topo, ptf_ip):
191+
192+
common_config = topo['configuration_properties'].get('common', {})
193+
podset_number = common_config.get("podset_number", PODSET_NUMBER)
194+
tor_number = common_config.get("tor_number", TOR_NUMBER)
195+
tor_subnet_number = common_config.get("tor_subnet_number", TOR_SUBNET_NUMBER)
196+
max_tor_subnet_number = common_config.get("max_tor_subnet_number", MAX_TOR_SUBNET_NUMBER)
197+
tor_subnet_size = common_config.get("tor_subnet_size", TOR_SUBNET_SIZE)
198+
nhipv4 = common_config.get("nhipv4", NHIPV4)
199+
nhipv6 = common_config.get("nhipv6", NHIPV6)
200+
leaf_asn_start = common_config.get("leaf_asn_start", LEAF_ASN_START)
201+
tor_asn_start = common_config.get("tor_asn_start", TOR_ASN_START)
202+
203+
vms = topo['topology']['VMs']
204+
vms_config = topo['configuration']
205+
206+
for k, v in vms_config.items():
207+
vm_offset = vms[k]['vm_offset']
208+
port = IPV4_BASE_PORT + vm_offset
209+
port6 = IPV6_BASE_PORT + vm_offset
210+
211+
router_type = None
212+
if 'spine' in v['properties']:
213+
router_type = 'spine'
214+
elif 'tor' in v['properties']:
215+
router_type = 'tor'
216+
tornum = v.get('tornum', None)
217+
tor_index = tornum - 1 if tornum is not None else None
218+
if router_type:
219+
routes_v4 = generate_routes("v4", podset_number, tor_number, tor_subnet_number,
220+
None, leaf_asn_start, tor_asn_start,
221+
nhipv4, nhipv6, tor_subnet_size, max_tor_subnet_number,
222+
router_type=router_type, tor_index=tor_index)
223+
routes_v6 = generate_routes("v6", podset_number, tor_number, tor_subnet_number,
224+
None, leaf_asn_start, tor_asn_start,
225+
nhipv4, nhipv6, tor_subnet_size, max_tor_subnet_number,
226+
router_type=router_type, tor_index=tor_index)
227+
announce_routes(ptf_ip, port, routes_v4)
228+
announce_routes(ptf_ip, port6, routes_v6)
229+
230+
if 'vips' in v:
231+
routes_vips = []
232+
for prefix in v["vips"]["ipv4"]["prefixes"]:
233+
routes_vips.append((prefix, nhipv4, v["vips"]["ipv4"]["asn"]))
234+
announce_routes(ptf_ip, port, routes_vips)
235+
236+
237+
def main():
238+
239+
module = AnsibleModule(
240+
argument_spec=dict(
241+
topo_name=dict(required=True, type='str'),
242+
ptf_ip=dict(required=True, type='str')
243+
),
244+
supports_check_mode=False)
245+
246+
topo_name = module.params['topo_name']
247+
ptf_ip = module.params['ptf_ip']
248+
249+
topo = read_topo(topo_name)
250+
if not topo:
251+
module.fail_json(msg='Unable to load topology "{}"'.format(topo_name))
252+
253+
topo_type = get_topo_type(topo_name)
254+
255+
if topo_type == "t0":
256+
fib_t0(topo, ptf_ip)
257+
module.exit_json(changed=True)
258+
elif topo_type == "t1":
259+
fib_t1_lag(topo, ptf_ip)
260+
module.exit_json(changed=True)
261+
else:
262+
module.fail_json(msg='Unsupported topology "{}"'.format(topo_name))
263+
264+
265+
if __name__ == '__main__':
266+
main()

ansible/roles/vm_set/tasks/add_topo.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@
123123
include_tasks: start_ptf_tgen.yml
124124
when: topo == 'fullmesh'
125125

126+
- name: Announce routes
127+
include_tasks: announce_routes.yml
128+
126129
- name: Start mux simulator
127130
include_tasks: control_mux_simulator.yml
128131
vars:
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
- name: Include variables for PTF containers
3+
include_vars:
4+
dir: "{{ playbook_dir }}/group_vars/ptf_host/"
5+
6+
- name: Set ptf host
7+
set_fact:
8+
ptf_host: "ptf_{{ vm_set_name }}"
9+
ptf_host_ip: "{{ ptf_ip.split('/')[0] }}"
10+
11+
- name: Add ptf host
12+
add_host:
13+
hostname: "{{ ptf_host }}"
14+
ansible_user: "{{ ptf_host_user }}"
15+
ansible_ssh_host: "{{ ptf_host_ip }}"
16+
ansible_ssh_pass: "{{ ptf_host_pass }}"
17+
ansible_python_interpreter: "/usr/bin/python"
18+
groups:
19+
- ptf_host
20+
21+
- name: Set facts
22+
set_fact:
23+
ptf_local_ipv4: "{{ configuration_properties.common.nhipv4|default('10.10.246.254') }}"
24+
ptf_local_ipv6: "{{ configuration_properties.common.nhipv6|default('fc0a::ff') }}"
25+
26+
- name: Start exabgp processes for IPv4 on PTF
27+
exabgp:
28+
name: "{{ vm_item.key }}"
29+
state: "started"
30+
router_id: "{{ ptf_local_ipv4 }}"
31+
local_ip: "{{ ptf_local_ipv4 }}"
32+
peer_ip: "{{ configuration[vm_item.key].bp_interface.ipv4.split('/')[0] }}"
33+
local_asn: "{{ configuration[vm_item.key].bgp.asn }}"
34+
peer_asn: "{{ configuration[vm_item.key].bgp.asn }}"
35+
port: "{{ 5000 + vm_item.value.vm_offset|int }}"
36+
async: 300
37+
poll: 0
38+
loop: "{{ topology['VMs']|dict2items }}"
39+
loop_control:
40+
loop_var: vm_item
41+
delegate_to: "{{ ptf_host }}"
42+
43+
- name: Start exabgp processes for IPv6 on PTF
44+
exabgp:
45+
name: "{{ vm_item.key }}-v6"
46+
state: "started"
47+
router_id: "{{ ptf_local_ipv4 }}"
48+
local_ip: "{{ ptf_local_ipv6 }}"
49+
peer_ip: "{{ configuration[vm_item.key].bp_interface.ipv6.split('/')[0] }}"
50+
local_asn: "{{ configuration[vm_item.key].bgp.asn }}"
51+
peer_asn: "{{ configuration[vm_item.key].bgp.asn }}"
52+
port: "{{ 6000 + vm_item.value.vm_offset|int }}"
53+
async: 300
54+
poll: 0
55+
loop: "{{ topology['VMs']|dict2items }}"
56+
loop_control:
57+
loop_var: vm_item
58+
delegate_to: "{{ ptf_host }}"
59+
60+
- name: Verify that exabgp processes for IPv4 are started
61+
wait_for:
62+
host: "{{ ptf_host_ip }}"
63+
port: "{{ 5000 + topology.VMs[vm_item.key].vm_offset|int }}"
64+
state: "started"
65+
timeout: 180
66+
loop: "{{ topology['VMs']|dict2items }}"
67+
loop_control:
68+
loop_var: vm_item
69+
delegate_to: localhost
70+
71+
- name: Verify that exabgp processes for IPv6 are started
72+
wait_for:
73+
host: "{{ ptf_host_ip }}"
74+
port: "{{ 6000 + topology.VMs[vm_item.key].vm_offset|int }}"
75+
state: "started"
76+
timeout: 180
77+
loop: "{{ topology['VMs']|dict2items }}"
78+
loop_control:
79+
loop_var: vm_item
80+
delegate_to: localhost
81+
82+
- name: Announce routes
83+
announce_routes:
84+
topo_name: "{{ topo }}"
85+
ptf_ip: "{{ ptf_host_ip }}"
86+
delegate_to: localhost

0 commit comments

Comments
 (0)