]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/lib/topojson.py
Merge pull request #12798 from donaldsharp/rib_match_multicast
[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
210 # Loopback interfaces
211 if "dst_node" in data:
212 destRouter = data["dst_node"]
213
214 elif "-" in destRouterLink:
215 # Spliting and storing destRouterLink data in tempList
216 tempList = destRouterLink.split("-")
217 # destRouter
218 destRouter = tempList.pop(0)
219 else:
220 destRouter = destRouterLink
221
222 if destRouter in listAllRouters:
223
224 topo["routers"][destRouter]["links"][curSwitch] = deepcopy(
225 topo["switches"][curSwitch]["links"][destRouterLink]
226 )
227
228 # Assigning name to interfaces
229 topo["routers"][destRouter]["links"][curSwitch][
230 "interface"
231 ] = "{}-{}-eth{}".format(
232 destRouter, curSwitch, topo["routers"][destRouter]["nextIfname"]
233 )
234
235 topo["switches"][curSwitch]["links"][destRouter][
236 "interface"
237 ] = "{}-{}-eth{}".format(
238 curSwitch, destRouter, topo["routers"][destRouter]["nextIfname"]
239 )
240
241 topo["routers"][destRouter]["nextIfname"] += 1
242
243 # Add links
244 dictSwitches[curSwitch].add_link(
245 tgen.gears[destRouter],
246 topo["switches"][curSwitch]["links"][destRouter]["interface"],
247 topo["routers"][destRouter]["links"][curSwitch]["interface"],
248 )
249
250 # IPv4
251 if "ipv4" in topo["routers"][destRouter]["links"][curSwitch]:
252 if (
253 topo["routers"][destRouter]["links"][curSwitch]["ipv4"]
254 == "auto"
255 ):
256 topo["routers"][destRouter]["links"][curSwitch][
257 "ipv4"
258 ] = "{}/{}".format(
259 ipv4Next, topo["link_ip_start"]["v4mask"]
260 )
261 ipv4Next += 1
262 # IPv6
263 if "ipv6" in topo["routers"][destRouter]["links"][curSwitch]:
264 if (
265 topo["routers"][destRouter]["links"][curSwitch]["ipv6"]
266 == "auto"
267 ):
268 topo["routers"][destRouter]["links"][curSwitch][
269 "ipv6"
270 ] = "{}/{}".format(
271 ipv6Next, topo["link_ip_start"]["v6mask"]
272 )
273 ipv6Next = ipaddress.IPv6Address(int(ipv6Next) + ipv6Step)
274
275 logger.debug(
276 "Generated link data for router: %s\n%s",
277 curRouter,
278 json.dumps(
279 topo["routers"][curRouter]["links"], indent=4, sort_keys=True
280 ),
281 )
282
283
284 def linux_intf_config_from_json(tgen, topo=None):
285 """Configure interfaces from linux based on topo."""
286 if topo is None:
287 topo = tgen.json_topo
288
289 routers = topo["routers"]
290 for rname in routers:
291 router = tgen.net[rname]
292 links = routers[rname]["links"]
293 for rrname in links:
294 link = links[rrname]
295 if rrname == "lo":
296 lname = "lo"
297 else:
298 lname = link["interface"]
299 if "ipv4" in link:
300 router.cmd_raises("ip addr add {} dev {}".format(link["ipv4"], lname))
301 if "ipv6" in link:
302 router.cmd_raises(
303 "ip -6 addr add {} dev {}".format(link["ipv6"], lname)
304 )
305
306
307 def build_config_from_json(tgen, topo=None, save_bkup=True):
308 """
309 Reads initial configuraiton from JSON for each router, builds
310 configuration and loads its to router.
311
312 * `tgen`: Topogen object
313 * `topo`: json file data, or use tgen.json_topo if None
314 """
315
316 func_dict = OrderedDict(
317 [
318 ("vrfs", create_vrf_cfg),
319 ("links", create_interfaces_cfg),
320 ("static_routes", create_static_routes),
321 ("prefix_lists", create_prefix_lists),
322 ("bgp_community_list", create_bgp_community_lists),
323 ("route_maps", create_route_maps),
324 ("pim", create_pim_config),
325 ("igmp", create_igmp_config),
326 ("mld", create_mld_config),
327 ("bgp", create_router_bgp),
328 ("ospf", create_router_ospf),
329 ]
330 )
331
332 if topo is None:
333 topo = tgen.json_topo
334
335 data = topo["routers"]
336 for func_type in func_dict.keys():
337 logger.info("Checking for {} configuration in input data".format(func_type))
338
339 func_dict.get(func_type)(tgen, data, build=True)
340
341 routers = sorted(topo["routers"].keys())
342 result = load_config_to_routers(tgen, routers, save_bkup)
343 if not result:
344 logger.info("build_config_from_json: failed to configure topology")
345 pytest.exit(1)
346
347 logger.info(
348 "Built config now clearing ospf neighbors as that router-id might not be what is used"
349 )
350 for ospf in ["ospf", "ospf6"]:
351 for router in data:
352 if ospf not in data[router]:
353 continue
354
355 r = tgen.gears[router]
356 if ospf == "ospf":
357 r.vtysh_cmd("clear ip ospf process")
358 else:
359 r.vtysh_cmd("clear ipv6 ospf6 process")
360
361
362 def create_tgen_from_json(testfile, json_file=None):
363 """Create a topogen object given a testfile.
364
365 - `testfile` : The path to the testfile.
366 - `json_file` : The path to the json config file. If None the pathname is derived
367 from the `testfile` first by trying to replace `.py` by `.json` and if that isn't
368 present then by removing `test_` prefix as well.
369 """
370 from lib.topogen import Topogen # Topogen imports this module too
371
372 thisdir = os.path.dirname(os.path.realpath(testfile))
373 basename = os.path.basename(testfile)
374 logger.debug("starting standard JSON based module setup for %s", basename)
375
376 assert basename.startswith("test_")
377 assert basename.endswith(".py")
378 json_file = os.path.join(thisdir, basename[:-3] + ".json")
379 if not os.path.exists(json_file):
380 json_file = os.path.join(thisdir, basename[5:-3] + ".json")
381 assert os.path.exists(json_file)
382 with open(json_file, "r") as topof:
383 topo = json.load(topof)
384
385 # Create topology
386 tgen = Topogen(lambda tgen: build_topo_from_json(tgen, topo), basename[:-3])
387 tgen.json_topo = topo
388 return tgen
389
390
391 def setup_module_from_json(testfile, json_file=None):
392 """Do the standard module setup for JSON based test.
393
394 * `testfile` : The path to the testfile. The name is used to derive the json config
395 file name as well (removing `test_` prefix and replacing `.py` suffix with `.json`
396 """
397 # Create topology object
398 tgen = create_tgen_from_json(testfile, json_file)
399
400 # Start routers (and their daemons)
401 start_topology(tgen, topo_daemons(tgen))
402
403 # Configure routers
404 build_config_from_json(tgen)
405 assert not tgen.routers_have_failure()
406
407 return tgen