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