]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/lib/topojson.py
tests: remove legacy Topo class (fixes many pylint errors)
[mirror_frr.git] / tests / topotests / lib / topojson.py
CommitLineData
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 21import json
8b547a6d 22import ipaddress
49581587
CH
23import os
24from collections import OrderedDict
4256a209 25from copy import deepcopy
49581587 26from re import search as re_search
4256a209 27
49581587 28import pytest
2ad3d000 29
7fa2079a 30from lib.bgp import create_router_bgp
49581587
CH
31from 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 38from lib.ospf import create_router_ospf, create_router_ospf6
49581587
CH
39from lib.pim import create_igmp_config, create_pim_config
40from lib.topolog import logger
701a0192 41
bca79837
AP
42ROUTER_LIST = []
43
44
2ad3d000
AP
45def 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
288def 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
306def 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
344def 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
372def 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