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