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