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