]>
Commit | Line | Data |
---|---|---|
acddc0ed | 1 | # SPDX-License-Identifier: ISC |
2ad3d000 AP |
2 | # |
3 | # Modified work Copyright (c) 2019 by VMware, Inc. ("VMware") | |
4 | # Original work Copyright (c) 2018 by Network Device Education | |
5 | # Foundation, Inc. ("NetDEF") | |
6 | # | |
2ad3d000 | 7 | |
49581587 | 8 | import json |
8b547a6d | 9 | import ipaddress |
49581587 CH |
10 | import os |
11 | from collections import OrderedDict | |
4256a209 | 12 | from copy import deepcopy |
49581587 | 13 | from re import search as re_search |
4256a209 | 14 | |
49581587 | 15 | import pytest |
2ad3d000 | 16 | |
7fa2079a | 17 | from lib.bgp import create_router_bgp |
a53c08bc CH |
18 | from lib.common_config import ( |
19 | create_bgp_community_lists, | |
20 | create_interfaces_cfg, | |
21 | create_prefix_lists, | |
22 | create_route_maps, | |
23 | create_static_routes, | |
24 | create_vrf_cfg, | |
25 | load_config_to_routers, | |
26 | start_topology, | |
27 | topo_daemons, | |
28 | number_to_column, | |
29 | ) | |
04d01cc3 | 30 | from lib.ospf import create_router_ospf |
e13f9c4f KK |
31 | from lib.pim import ( |
32 | create_igmp_config, | |
33 | create_pim_config, | |
34 | create_mld_config, | |
35 | ) | |
49581587 | 36 | from lib.topolog import logger |
701a0192 | 37 | |
bca79837 | 38 | |
fe50239b | 39 | def build_topo_from_json(tgen, topo=None): |
2ad3d000 AP |
40 | """ |
41 | Reads configuration from JSON file. Adds routers, creates interface | |
42 | names dynamically and link routers as defined in JSON to create | |
43 | topology. Assigns IPs dynamically to all interfaces of each router. | |
2ad3d000 | 44 | * `tgen`: Topogen object |
fe50239b | 45 | * `topo`: json file data, or use tgen.json_topo if None |
2ad3d000 | 46 | """ |
fe50239b CH |
47 | if topo is None: |
48 | topo = tgen.json_topo | |
2ad3d000 | 49 | |
02547745 | 50 | router_list = sorted( |
49581587 | 51 | topo["routers"].keys(), key=lambda x: int(re_search(r"\d+", x).group(0)) |
787e7624 | 52 | ) |
bca79837 | 53 | |
02547745 | 54 | switch_list = [] |
4256a209 | 55 | if "switches" in topo: |
02547745 | 56 | switch_list = sorted( |
49581587 | 57 | topo["switches"].keys(), key=lambda x: int(re_search(r"\d+", x).group(0)) |
4256a209 | 58 | ) |
59 | ||
02547745 CH |
60 | listRouters = sorted(router_list[:]) |
61 | listSwitches = sorted(switch_list[:]) | |
4256a209 | 62 | listAllRouters = deepcopy(listRouters) |
63 | dictSwitches = {} | |
64 | ||
02547745 | 65 | for routerN in router_list: |
787e7624 | 66 | logger.info("Topo: Add router {}".format(routerN)) |
bca79837 | 67 | tgen.add_router(routerN) |
2ad3d000 | 68 | |
02547745 | 69 | for switchN in switch_list: |
4256a209 | 70 | logger.info("Topo: Add switch {}".format(switchN)) |
71 | dictSwitches[switchN] = tgen.add_switch(switchN) | |
4256a209 | 72 | |
787e7624 | 73 | if "ipv4base" in topo: |
8b547a6d | 74 | ipv4Next = ipaddress.IPv4Address(topo["link_ip_start"]["ipv4"]) |
787e7624 | 75 | ipv4Step = 2 ** (32 - topo["link_ip_start"]["v4mask"]) |
76 | if topo["link_ip_start"]["v4mask"] < 32: | |
2ad3d000 | 77 | ipv4Next += 1 |
787e7624 | 78 | if "ipv6base" in topo: |
8b547a6d | 79 | ipv6Next = ipaddress.IPv6Address(topo["link_ip_start"]["ipv6"]) |
787e7624 | 80 | ipv6Step = 2 ** (128 - topo["link_ip_start"]["v6mask"]) |
81 | if topo["link_ip_start"]["v6mask"] < 127: | |
2ad3d000 AP |
82 | ipv6Next += 1 |
83 | for router in listRouters: | |
787e7624 | 84 | topo["routers"][router]["nextIfname"] = 0 |
2ad3d000 | 85 | |
0705f312 | 86 | router_count = 0 |
2ad3d000 AP |
87 | while listRouters != []: |
88 | curRouter = listRouters.pop(0) | |
89 | # Physical Interfaces | |
787e7624 | 90 | if "links" in topo["routers"][curRouter]: |
787e7624 | 91 | for destRouterLink, data in sorted( |
c8e5983d | 92 | topo["routers"][curRouter]["links"].items() |
787e7624 | 93 | ): |
94 | currRouter_lo_json = topo["routers"][curRouter]["links"][destRouterLink] | |
2ad3d000 | 95 | # Loopback interfaces |
787e7624 | 96 | if "type" in data and data["type"] == "loopback": |
0705f312 | 97 | router_count += 1 |
787e7624 | 98 | if ( |
99 | "ipv4" in currRouter_lo_json | |
100 | and currRouter_lo_json["ipv4"] == "auto" | |
101 | ): | |
102 | currRouter_lo_json["ipv4"] = "{}{}.{}/{}".format( | |
103 | topo["lo_prefix"]["ipv4"], | |
0705f312 | 104 | router_count, |
787e7624 | 105 | number_to_column(curRouter), |
106 | topo["lo_prefix"]["v4mask"], | |
107 | ) | |
108 | if ( | |
109 | "ipv6" in currRouter_lo_json | |
110 | and currRouter_lo_json["ipv6"] == "auto" | |
111 | ): | |
112 | currRouter_lo_json["ipv6"] = "{}{}:{}/{}".format( | |
113 | topo["lo_prefix"]["ipv6"], | |
0705f312 | 114 | router_count, |
787e7624 | 115 | number_to_column(curRouter), |
116 | topo["lo_prefix"]["v6mask"], | |
117 | ) | |
2ad3d000 AP |
118 | |
119 | if "-" in destRouterLink: | |
120 | # Spliting and storing destRouterLink data in tempList | |
121 | tempList = destRouterLink.split("-") | |
122 | ||
123 | # destRouter | |
124 | destRouter = tempList.pop(0) | |
125 | ||
126 | # Current Router Link | |
127 | tempList.insert(0, curRouter) | |
128 | curRouterLink = "-".join(tempList) | |
129 | else: | |
130 | destRouter = destRouterLink | |
131 | curRouterLink = curRouter | |
132 | ||
133 | if destRouter in listRouters: | |
787e7624 | 134 | currRouter_link_json = topo["routers"][curRouter]["links"][ |
135 | destRouterLink | |
136 | ] | |
137 | destRouter_link_json = topo["routers"][destRouter]["links"][ | |
138 | curRouterLink | |
139 | ] | |
2ad3d000 AP |
140 | |
141 | # Assigning name to interfaces | |
787e7624 | 142 | currRouter_link_json["interface"] = "{}-{}-eth{}".format( |
143 | curRouter, destRouter, topo["routers"][curRouter]["nextIfname"] | |
144 | ) | |
145 | destRouter_link_json["interface"] = "{}-{}-eth{}".format( | |
146 | destRouter, curRouter, topo["routers"][destRouter]["nextIfname"] | |
147 | ) | |
2ad3d000 | 148 | |
0705f312 | 149 | # add link interface |
150 | destRouter_link_json["peer-interface"] = "{}-{}-eth{}".format( | |
151 | curRouter, destRouter, topo["routers"][curRouter]["nextIfname"] | |
152 | ) | |
153 | currRouter_link_json["peer-interface"] = "{}-{}-eth{}".format( | |
154 | destRouter, curRouter, topo["routers"][destRouter]["nextIfname"] | |
155 | ) | |
156 | ||
787e7624 | 157 | topo["routers"][curRouter]["nextIfname"] += 1 |
158 | topo["routers"][destRouter]["nextIfname"] += 1 | |
2ad3d000 AP |
159 | |
160 | # Linking routers to each other as defined in JSON file | |
787e7624 | 161 | tgen.gears[curRouter].add_link( |
162 | tgen.gears[destRouter], | |
163 | topo["routers"][curRouter]["links"][destRouterLink][ | |
164 | "interface" | |
165 | ], | |
166 | topo["routers"][destRouter]["links"][curRouterLink][ | |
167 | "interface" | |
168 | ], | |
169 | ) | |
2ad3d000 AP |
170 | |
171 | # IPv4 | |
787e7624 | 172 | if "ipv4" in currRouter_link_json: |
173 | if currRouter_link_json["ipv4"] == "auto": | |
174 | currRouter_link_json["ipv4"] = "{}/{}".format( | |
175 | ipv4Next, topo["link_ip_start"]["v4mask"] | |
176 | ) | |
177 | destRouter_link_json["ipv4"] = "{}/{}".format( | |
178 | ipv4Next + 1, topo["link_ip_start"]["v4mask"] | |
179 | ) | |
2ad3d000 AP |
180 | ipv4Next += ipv4Step |
181 | # IPv6 | |
787e7624 | 182 | if "ipv6" in currRouter_link_json: |
183 | if currRouter_link_json["ipv6"] == "auto": | |
184 | currRouter_link_json["ipv6"] = "{}/{}".format( | |
185 | ipv6Next, topo["link_ip_start"]["v6mask"] | |
186 | ) | |
187 | destRouter_link_json["ipv6"] = "{}/{}".format( | |
188 | ipv6Next + 1, topo["link_ip_start"]["v6mask"] | |
189 | ) | |
8b547a6d | 190 | ipv6Next = ipaddress.IPv6Address(int(ipv6Next) + ipv6Step) |
2ad3d000 | 191 | |
787e7624 | 192 | logger.debug( |
193 | "Generated link data for router: %s\n%s", | |
194 | curRouter, | |
49581587 | 195 | json.dumps( |
787e7624 | 196 | topo["routers"][curRouter]["links"], indent=4, sort_keys=True |
197 | ), | |
198 | ) | |
7fa2079a | 199 | |
4256a209 | 200 | switch_count = 0 |
201 | add_switch_to_topo = [] | |
202 | while listSwitches != []: | |
203 | curSwitch = listSwitches.pop(0) | |
204 | # Physical Interfaces | |
701a0192 | 205 | if "links" in topo["switches"][curSwitch]: |
4256a209 | 206 | for destRouterLink, data in sorted( |
701a0192 | 207 | topo["switches"][curSwitch]["links"].items() |
208 | ): | |
4256a209 | 209 | |
210 | # Loopback interfaces | |
211 | if "dst_node" in data: | |
701a0192 | 212 | destRouter = data["dst_node"] |
4256a209 | 213 | |
214 | elif "-" in destRouterLink: | |
215 | # Spliting and storing destRouterLink data in tempList | |
216 | tempList = destRouterLink.split("-") | |
217 | # destRouter | |
218 | destRouter = tempList.pop(0) | |
219 | else: | |
220 | destRouter = destRouterLink | |
221 | ||
222 | if destRouter in listAllRouters: | |
223 | ||
701a0192 | 224 | topo["routers"][destRouter]["links"][curSwitch] = deepcopy( |
225 | topo["switches"][curSwitch]["links"][destRouterLink] | |
226 | ) | |
4256a209 | 227 | |
228 | # Assigning name to interfaces | |
701a0192 | 229 | topo["routers"][destRouter]["links"][curSwitch][ |
230 | "interface" | |
231 | ] = "{}-{}-eth{}".format( | |
232 | destRouter, curSwitch, topo["routers"][destRouter]["nextIfname"] | |
233 | ) | |
4256a209 | 234 | |
701a0192 | 235 | topo["switches"][curSwitch]["links"][destRouter][ |
236 | "interface" | |
237 | ] = "{}-{}-eth{}".format( | |
238 | curSwitch, destRouter, topo["routers"][destRouter]["nextIfname"] | |
239 | ) | |
4256a209 | 240 | |
701a0192 | 241 | topo["routers"][destRouter]["nextIfname"] += 1 |
4256a209 | 242 | |
243 | # Add links | |
701a0192 | 244 | dictSwitches[curSwitch].add_link( |
245 | tgen.gears[destRouter], | |
246 | topo["switches"][curSwitch]["links"][destRouter]["interface"], | |
247 | topo["routers"][destRouter]["links"][curSwitch]["interface"], | |
248 | ) | |
4256a209 | 249 | |
250 | # IPv4 | |
701a0192 | 251 | if "ipv4" in topo["routers"][destRouter]["links"][curSwitch]: |
252 | if ( | |
253 | topo["routers"][destRouter]["links"][curSwitch]["ipv4"] | |
254 | == "auto" | |
255 | ): | |
256 | topo["routers"][destRouter]["links"][curSwitch][ | |
257 | "ipv4" | |
258 | ] = "{}/{}".format( | |
259 | ipv4Next, topo["link_ip_start"]["v4mask"] | |
260 | ) | |
4256a209 | 261 | ipv4Next += 1 |
262 | # IPv6 | |
701a0192 | 263 | if "ipv6" in topo["routers"][destRouter]["links"][curSwitch]: |
264 | if ( | |
265 | topo["routers"][destRouter]["links"][curSwitch]["ipv6"] | |
266 | == "auto" | |
267 | ): | |
268 | topo["routers"][destRouter]["links"][curSwitch][ | |
269 | "ipv6" | |
270 | ] = "{}/{}".format( | |
271 | ipv6Next, topo["link_ip_start"]["v6mask"] | |
272 | ) | |
c8e5983d | 273 | ipv6Next = ipaddress.IPv6Address(int(ipv6Next) + ipv6Step) |
4256a209 | 274 | |
275 | logger.debug( | |
276 | "Generated link data for router: %s\n%s", | |
277 | curRouter, | |
49581587 | 278 | json.dumps( |
4256a209 | 279 | topo["routers"][curRouter]["links"], indent=4, sort_keys=True |
280 | ), | |
281 | ) | |
282 | ||
7659b8d4 | 283 | |
fe50239b | 284 | def linux_intf_config_from_json(tgen, topo=None): |
845b234a | 285 | """Configure interfaces from linux based on topo.""" |
fe50239b CH |
286 | if topo is None: |
287 | topo = tgen.json_topo | |
288 | ||
845b234a CH |
289 | routers = topo["routers"] |
290 | for rname in routers: | |
49581587 | 291 | router = tgen.net[rname] |
845b234a CH |
292 | links = routers[rname]["links"] |
293 | for rrname in links: | |
294 | link = links[rrname] | |
295 | if rrname == "lo": | |
296 | lname = "lo" | |
297 | else: | |
298 | lname = link["interface"] | |
299 | if "ipv4" in link: | |
49581587 | 300 | router.cmd_raises("ip addr add {} dev {}".format(link["ipv4"], lname)) |
845b234a | 301 | if "ipv6" in link: |
a53c08bc CH |
302 | router.cmd_raises( |
303 | "ip -6 addr add {} dev {}".format(link["ipv6"], lname) | |
304 | ) | |
845b234a CH |
305 | |
306 | ||
fe50239b | 307 | def build_config_from_json(tgen, topo=None, save_bkup=True): |
7659b8d4 AP |
308 | """ |
309 | Reads initial configuraiton from JSON for each router, builds | |
310 | configuration and loads its to router. | |
311 | ||
312 | * `tgen`: Topogen object | |
fe50239b | 313 | * `topo`: json file data, or use tgen.json_topo if None |
7659b8d4 AP |
314 | """ |
315 | ||
787e7624 | 316 | func_dict = OrderedDict( |
317 | [ | |
f8d572e8 | 318 | ("vrfs", create_vrf_cfg), |
787e7624 | 319 | ("links", create_interfaces_cfg), |
320 | ("static_routes", create_static_routes), | |
321 | ("prefix_lists", create_prefix_lists), | |
322 | ("bgp_community_list", create_bgp_community_lists), | |
323 | ("route_maps", create_route_maps), | |
e58fdb55 | 324 | ("pim", create_pim_config), |
325 | ("igmp", create_igmp_config), | |
e13f9c4f | 326 | ("mld", create_mld_config), |
787e7624 | 327 | ("bgp", create_router_bgp), |
701a0192 | 328 | ("ospf", create_router_ospf), |
787e7624 | 329 | ] |
330 | ) | |
7659b8d4 | 331 | |
fe50239b CH |
332 | if topo is None: |
333 | topo = tgen.json_topo | |
334 | ||
7659b8d4 AP |
335 | data = topo["routers"] |
336 | for func_type in func_dict.keys(): | |
787e7624 | 337 | logger.info("Checking for {} configuration in input data".format(func_type)) |
7659b8d4 AP |
338 | |
339 | func_dict.get(func_type)(tgen, data, build=True) | |
340 | ||
4f99894d CH |
341 | routers = sorted(topo["routers"].keys()) |
342 | result = load_config_to_routers(tgen, routers, save_bkup) | |
343 | if not result: | |
344 | logger.info("build_config_from_json: failed to configure topology") | |
345 | pytest.exit(1) | |
49581587 | 346 | |
e13f9c4f KK |
347 | logger.info( |
348 | "Built config now clearing ospf neighbors as that router-id might not be what is used" | |
349 | ) | |
bceb50e4 DS |
350 | for ospf in ["ospf", "ospf6"]: |
351 | for router in data: | |
352 | if ospf not in data[router]: | |
353 | continue | |
354 | ||
355 | r = tgen.gears[router] | |
356 | if ospf == "ospf": | |
357 | r.vtysh_cmd("clear ip ospf process") | |
358 | else: | |
359 | r.vtysh_cmd("clear ipv6 ospf6 process") | |
360 | ||
49581587 CH |
361 | |
362 | def create_tgen_from_json(testfile, json_file=None): | |
363 | """Create a topogen object given a testfile. | |
364 | ||
365 | - `testfile` : The path to the testfile. | |
366 | - `json_file` : The path to the json config file. If None the pathname is derived | |
367 | from the `testfile` first by trying to replace `.py` by `.json` and if that isn't | |
368 | present then by removing `test_` prefix as well. | |
369 | """ | |
a53c08bc CH |
370 | from lib.topogen import Topogen # Topogen imports this module too |
371 | ||
49581587 CH |
372 | thisdir = os.path.dirname(os.path.realpath(testfile)) |
373 | basename = os.path.basename(testfile) | |
374 | logger.debug("starting standard JSON based module setup for %s", basename) | |
375 | ||
376 | assert basename.startswith("test_") | |
377 | assert basename.endswith(".py") | |
378 | json_file = os.path.join(thisdir, basename[:-3] + ".json") | |
379 | if not os.path.exists(json_file): | |
380 | json_file = os.path.join(thisdir, basename[5:-3] + ".json") | |
381 | assert os.path.exists(json_file) | |
382 | with open(json_file, "r") as topof: | |
383 | topo = json.load(topof) | |
384 | ||
385 | # Create topology | |
386 | tgen = Topogen(lambda tgen: build_topo_from_json(tgen, topo), basename[:-3]) | |
387 | tgen.json_topo = topo | |
388 | return tgen | |
389 | ||
390 | ||
391 | def setup_module_from_json(testfile, json_file=None): | |
392 | """Do the standard module setup for JSON based test. | |
393 | ||
394 | * `testfile` : The path to the testfile. The name is used to derive the json config | |
395 | file name as well (removing `test_` prefix and replacing `.py` suffix with `.json` | |
396 | """ | |
397 | # Create topology object | |
398 | tgen = create_tgen_from_json(testfile, json_file) | |
399 | ||
400 | # Start routers (and their daemons) | |
401 | start_topology(tgen, topo_daemons(tgen)) | |
402 | ||
403 | # Configure routers | |
404 | build_config_from_json(tgen) | |
405 | assert not tgen.routers_have_failure() | |
406 | ||
407 | return tgen |