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