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