]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/lib/topojson.py
Merge pull request #12798 from donaldsharp/rib_match_multicast
[mirror_frr.git] / tests / topotests / lib / topojson.py
CommitLineData
acddc0ed 1# SPDX-License-Identifier: ISC
2ad3d000
AP
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#
2ad3d000 7
49581587 8import json
8b547a6d 9import ipaddress
49581587
CH
10import os
11from collections import OrderedDict
4256a209 12from copy import deepcopy
49581587 13from re import search as re_search
4256a209 14
49581587 15import pytest
2ad3d000 16
7fa2079a 17from lib.bgp import create_router_bgp
a53c08bc
CH
18from 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)
04d01cc3 30from lib.ospf import create_router_ospf
e13f9c4f
KK
31from lib.pim import (
32 create_igmp_config,
33 create_pim_config,
34 create_mld_config,
35)
49581587 36from lib.topolog import logger
701a0192 37
bca79837 38
fe50239b 39def build_topo_from_json(tgen, topo=None):
2ad3d000
AP
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.
2ad3d000 44 * `tgen`: Topogen object
fe50239b 45 * `topo`: json file data, or use tgen.json_topo if None
2ad3d000 46 """
fe50239b
CH
47 if topo is None:
48 topo = tgen.json_topo
2ad3d000 49
02547745 50 router_list = sorted(
49581587 51 topo["routers"].keys(), key=lambda x: int(re_search(r"\d+", x).group(0))
787e7624 52 )
bca79837 53
02547745 54 switch_list = []
4256a209 55 if "switches" in topo:
02547745 56 switch_list = sorted(
49581587 57 topo["switches"].keys(), key=lambda x: int(re_search(r"\d+", x).group(0))
4256a209 58 )
59
02547745
CH
60 listRouters = sorted(router_list[:])
61 listSwitches = sorted(switch_list[:])
4256a209 62 listAllRouters = deepcopy(listRouters)
63 dictSwitches = {}
64
02547745 65 for routerN in router_list:
787e7624 66 logger.info("Topo: Add router {}".format(routerN))
bca79837 67 tgen.add_router(routerN)
2ad3d000 68
02547745 69 for switchN in switch_list:
4256a209 70 logger.info("Topo: Add switch {}".format(switchN))
71 dictSwitches[switchN] = tgen.add_switch(switchN)
4256a209 72
787e7624 73 if "ipv4base" in topo:
8b547a6d 74 ipv4Next = ipaddress.IPv4Address(topo["link_ip_start"]["ipv4"])
787e7624 75 ipv4Step = 2 ** (32 - topo["link_ip_start"]["v4mask"])
76 if topo["link_ip_start"]["v4mask"] < 32:
2ad3d000 77 ipv4Next += 1
787e7624 78 if "ipv6base" in topo:
8b547a6d 79 ipv6Next = ipaddress.IPv6Address(topo["link_ip_start"]["ipv6"])
787e7624 80 ipv6Step = 2 ** (128 - topo["link_ip_start"]["v6mask"])
81 if topo["link_ip_start"]["v6mask"] < 127:
2ad3d000
AP
82 ipv6Next += 1
83 for router in listRouters:
787e7624 84 topo["routers"][router]["nextIfname"] = 0
2ad3d000 85
0705f312 86 router_count = 0
2ad3d000
AP
87 while listRouters != []:
88 curRouter = listRouters.pop(0)
89 # Physical Interfaces
787e7624 90 if "links" in topo["routers"][curRouter]:
787e7624 91 for destRouterLink, data in sorted(
c8e5983d 92 topo["routers"][curRouter]["links"].items()
787e7624 93 ):
94 currRouter_lo_json = topo["routers"][curRouter]["links"][destRouterLink]
2ad3d000 95 # Loopback interfaces
787e7624 96 if "type" in data and data["type"] == "loopback":
0705f312 97 router_count += 1
787e7624 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"],
0705f312 104 router_count,
787e7624 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"],
0705f312 114 router_count,
787e7624 115 number_to_column(curRouter),
116 topo["lo_prefix"]["v6mask"],
117 )
2ad3d000
AP
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:
787e7624 134 currRouter_link_json = topo["routers"][curRouter]["links"][
135 destRouterLink
136 ]
137 destRouter_link_json = topo["routers"][destRouter]["links"][
138 curRouterLink
139 ]
2ad3d000
AP
140
141 # Assigning name to interfaces
787e7624 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 )
2ad3d000 148
0705f312 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
787e7624 157 topo["routers"][curRouter]["nextIfname"] += 1
158 topo["routers"][destRouter]["nextIfname"] += 1
2ad3d000
AP
159
160 # Linking routers to each other as defined in JSON file
787e7624 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 )
2ad3d000
AP
170
171 # IPv4
787e7624 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 )
2ad3d000
AP
180 ipv4Next += ipv4Step
181 # IPv6
787e7624 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 )
8b547a6d 190 ipv6Next = ipaddress.IPv6Address(int(ipv6Next) + ipv6Step)
2ad3d000 191
787e7624 192 logger.debug(
193 "Generated link data for router: %s\n%s",
194 curRouter,
49581587 195 json.dumps(
787e7624 196 topo["routers"][curRouter]["links"], indent=4, sort_keys=True
197 ),
198 )
7fa2079a 199
4256a209 200 switch_count = 0
201 add_switch_to_topo = []
202 while listSwitches != []:
203 curSwitch = listSwitches.pop(0)
204 # Physical Interfaces
701a0192 205 if "links" in topo["switches"][curSwitch]:
4256a209 206 for destRouterLink, data in sorted(
701a0192 207 topo["switches"][curSwitch]["links"].items()
208 ):
4256a209 209
210 # Loopback interfaces
211 if "dst_node" in data:
701a0192 212 destRouter = data["dst_node"]
4256a209 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
701a0192 224 topo["routers"][destRouter]["links"][curSwitch] = deepcopy(
225 topo["switches"][curSwitch]["links"][destRouterLink]
226 )
4256a209 227
228 # Assigning name to interfaces
701a0192 229 topo["routers"][destRouter]["links"][curSwitch][
230 "interface"
231 ] = "{}-{}-eth{}".format(
232 destRouter, curSwitch, topo["routers"][destRouter]["nextIfname"]
233 )
4256a209 234
701a0192 235 topo["switches"][curSwitch]["links"][destRouter][
236 "interface"
237 ] = "{}-{}-eth{}".format(
238 curSwitch, destRouter, topo["routers"][destRouter]["nextIfname"]
239 )
4256a209 240
701a0192 241 topo["routers"][destRouter]["nextIfname"] += 1
4256a209 242
243 # Add links
701a0192 244 dictSwitches[curSwitch].add_link(
245 tgen.gears[destRouter],
246 topo["switches"][curSwitch]["links"][destRouter]["interface"],
247 topo["routers"][destRouter]["links"][curSwitch]["interface"],
248 )
4256a209 249
250 # IPv4
701a0192 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 )
4256a209 261 ipv4Next += 1
262 # IPv6
701a0192 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 )
c8e5983d 273 ipv6Next = ipaddress.IPv6Address(int(ipv6Next) + ipv6Step)
4256a209 274
275 logger.debug(
276 "Generated link data for router: %s\n%s",
277 curRouter,
49581587 278 json.dumps(
4256a209 279 topo["routers"][curRouter]["links"], indent=4, sort_keys=True
280 ),
281 )
282
7659b8d4 283
fe50239b 284def linux_intf_config_from_json(tgen, topo=None):
845b234a 285 """Configure interfaces from linux based on topo."""
fe50239b
CH
286 if topo is None:
287 topo = tgen.json_topo
288
845b234a
CH
289 routers = topo["routers"]
290 for rname in routers:
49581587 291 router = tgen.net[rname]
845b234a
CH
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:
49581587 300 router.cmd_raises("ip addr add {} dev {}".format(link["ipv4"], lname))
845b234a 301 if "ipv6" in link:
a53c08bc
CH
302 router.cmd_raises(
303 "ip -6 addr add {} dev {}".format(link["ipv6"], lname)
304 )
845b234a
CH
305
306
fe50239b 307def build_config_from_json(tgen, topo=None, save_bkup=True):
7659b8d4
AP
308 """
309 Reads initial configuraiton from JSON for each router, builds
310 configuration and loads its to router.
311
312 * `tgen`: Topogen object
fe50239b 313 * `topo`: json file data, or use tgen.json_topo if None
7659b8d4
AP
314 """
315
787e7624 316 func_dict = OrderedDict(
317 [
f8d572e8 318 ("vrfs", create_vrf_cfg),
787e7624 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),
e58fdb55 324 ("pim", create_pim_config),
325 ("igmp", create_igmp_config),
e13f9c4f 326 ("mld", create_mld_config),
787e7624 327 ("bgp", create_router_bgp),
701a0192 328 ("ospf", create_router_ospf),
787e7624 329 ]
330 )
7659b8d4 331
fe50239b
CH
332 if topo is None:
333 topo = tgen.json_topo
334
7659b8d4
AP
335 data = topo["routers"]
336 for func_type in func_dict.keys():
787e7624 337 logger.info("Checking for {} configuration in input data".format(func_type))
7659b8d4
AP
338
339 func_dict.get(func_type)(tgen, data, build=True)
340
4f99894d
CH
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)
49581587 346
e13f9c4f
KK
347 logger.info(
348 "Built config now clearing ospf neighbors as that router-id might not be what is used"
349 )
bceb50e4
DS
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
49581587
CH
361
362def 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 """
a53c08bc
CH
370 from lib.topogen import Topogen # Topogen imports this module too
371
49581587
CH
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
391def 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