]> git.proxmox.com Git - mirror_frr.git/blame - 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
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
a53c08bc
CH
31from 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)
04d01cc3 43from lib.ospf import create_router_ospf
e13f9c4f
KK
44from lib.pim import (
45 create_igmp_config,
46 create_pim_config,
47 create_mld_config,
48)
49581587 49from lib.topolog import logger
701a0192 50
bca79837 51
fe50239b 52def build_topo_from_json(tgen, topo=None):
2ad3d000
AP
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.
2ad3d000 57 * `tgen`: Topogen object
fe50239b 58 * `topo`: json file data, or use tgen.json_topo if None
2ad3d000 59 """
fe50239b
CH
60 if topo is None:
61 topo = tgen.json_topo
2ad3d000 62
02547745 63 router_list = sorted(
49581587 64 topo["routers"].keys(), key=lambda x: int(re_search(r"\d+", x).group(0))
787e7624 65 )
bca79837 66
02547745 67 switch_list = []
4256a209 68 if "switches" in topo:
02547745 69 switch_list = sorted(
49581587 70 topo["switches"].keys(), key=lambda x: int(re_search(r"\d+", x).group(0))
4256a209 71 )
72
02547745
CH
73 listRouters = sorted(router_list[:])
74 listSwitches = sorted(switch_list[:])
4256a209 75 listAllRouters = deepcopy(listRouters)
76 dictSwitches = {}
77
02547745 78 for routerN in router_list:
787e7624 79 logger.info("Topo: Add router {}".format(routerN))
bca79837 80 tgen.add_router(routerN)
2ad3d000 81
02547745 82 for switchN in switch_list:
4256a209 83 logger.info("Topo: Add switch {}".format(switchN))
84 dictSwitches[switchN] = tgen.add_switch(switchN)
4256a209 85
787e7624 86 if "ipv4base" in topo:
8b547a6d 87 ipv4Next = ipaddress.IPv4Address(topo["link_ip_start"]["ipv4"])
787e7624 88 ipv4Step = 2 ** (32 - topo["link_ip_start"]["v4mask"])
89 if topo["link_ip_start"]["v4mask"] < 32:
2ad3d000 90 ipv4Next += 1
787e7624 91 if "ipv6base" in topo:
8b547a6d 92 ipv6Next = ipaddress.IPv6Address(topo["link_ip_start"]["ipv6"])
787e7624 93 ipv6Step = 2 ** (128 - topo["link_ip_start"]["v6mask"])
94 if topo["link_ip_start"]["v6mask"] < 127:
2ad3d000
AP
95 ipv6Next += 1
96 for router in listRouters:
787e7624 97 topo["routers"][router]["nextIfname"] = 0
2ad3d000 98
0705f312 99 router_count = 0
2ad3d000
AP
100 while listRouters != []:
101 curRouter = listRouters.pop(0)
102 # Physical Interfaces
787e7624 103 if "links" in topo["routers"][curRouter]:
787e7624 104 for destRouterLink, data in sorted(
c8e5983d 105 topo["routers"][curRouter]["links"].items()
787e7624 106 ):
107 currRouter_lo_json = topo["routers"][curRouter]["links"][destRouterLink]
2ad3d000 108 # Loopback interfaces
787e7624 109 if "type" in data and data["type"] == "loopback":
0705f312 110 router_count += 1
787e7624 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"],
0705f312 117 router_count,
787e7624 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"],
0705f312 127 router_count,
787e7624 128 number_to_column(curRouter),
129 topo["lo_prefix"]["v6mask"],
130 )
2ad3d000
AP
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:
787e7624 147 currRouter_link_json = topo["routers"][curRouter]["links"][
148 destRouterLink
149 ]
150 destRouter_link_json = topo["routers"][destRouter]["links"][
151 curRouterLink
152 ]
2ad3d000
AP
153
154 # Assigning name to interfaces
787e7624 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 )
2ad3d000 161
0705f312 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
787e7624 170 topo["routers"][curRouter]["nextIfname"] += 1
171 topo["routers"][destRouter]["nextIfname"] += 1
2ad3d000
AP
172
173 # Linking routers to each other as defined in JSON file
787e7624 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 )
2ad3d000
AP
183
184 # IPv4
787e7624 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 )
2ad3d000
AP
193 ipv4Next += ipv4Step
194 # IPv6
787e7624 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 )
8b547a6d 203 ipv6Next = ipaddress.IPv6Address(int(ipv6Next) + ipv6Step)
2ad3d000 204
787e7624 205 logger.debug(
206 "Generated link data for router: %s\n%s",
207 curRouter,
49581587 208 json.dumps(
787e7624 209 topo["routers"][curRouter]["links"], indent=4, sort_keys=True
210 ),
211 )
7fa2079a 212
4256a209 213 switch_count = 0
214 add_switch_to_topo = []
215 while listSwitches != []:
216 curSwitch = listSwitches.pop(0)
217 # Physical Interfaces
701a0192 218 if "links" in topo["switches"][curSwitch]:
4256a209 219 for destRouterLink, data in sorted(
701a0192 220 topo["switches"][curSwitch]["links"].items()
221 ):
4256a209 222
223 # Loopback interfaces
224 if "dst_node" in data:
701a0192 225 destRouter = data["dst_node"]
4256a209 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
701a0192 237 topo["routers"][destRouter]["links"][curSwitch] = deepcopy(
238 topo["switches"][curSwitch]["links"][destRouterLink]
239 )
4256a209 240
241 # Assigning name to interfaces
701a0192 242 topo["routers"][destRouter]["links"][curSwitch][
243 "interface"
244 ] = "{}-{}-eth{}".format(
245 destRouter, curSwitch, topo["routers"][destRouter]["nextIfname"]
246 )
4256a209 247
701a0192 248 topo["switches"][curSwitch]["links"][destRouter][
249 "interface"
250 ] = "{}-{}-eth{}".format(
251 curSwitch, destRouter, topo["routers"][destRouter]["nextIfname"]
252 )
4256a209 253
701a0192 254 topo["routers"][destRouter]["nextIfname"] += 1
4256a209 255
256 # Add links
701a0192 257 dictSwitches[curSwitch].add_link(
258 tgen.gears[destRouter],
259 topo["switches"][curSwitch]["links"][destRouter]["interface"],
260 topo["routers"][destRouter]["links"][curSwitch]["interface"],
261 )
4256a209 262
263 # IPv4
701a0192 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 )
4256a209 274 ipv4Next += 1
275 # IPv6
701a0192 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 )
c8e5983d 286 ipv6Next = ipaddress.IPv6Address(int(ipv6Next) + ipv6Step)
4256a209 287
288 logger.debug(
289 "Generated link data for router: %s\n%s",
290 curRouter,
49581587 291 json.dumps(
4256a209 292 topo["routers"][curRouter]["links"], indent=4, sort_keys=True
293 ),
294 )
295
7659b8d4 296
fe50239b 297def linux_intf_config_from_json(tgen, topo=None):
845b234a 298 """Configure interfaces from linux based on topo."""
fe50239b
CH
299 if topo is None:
300 topo = tgen.json_topo
301
845b234a
CH
302 routers = topo["routers"]
303 for rname in routers:
49581587 304 router = tgen.net[rname]
845b234a
CH
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:
49581587 313 router.cmd_raises("ip addr add {} dev {}".format(link["ipv4"], lname))
845b234a 314 if "ipv6" in link:
a53c08bc
CH
315 router.cmd_raises(
316 "ip -6 addr add {} dev {}".format(link["ipv6"], lname)
317 )
845b234a
CH
318
319
fe50239b 320def build_config_from_json(tgen, topo=None, save_bkup=True):
7659b8d4
AP
321 """
322 Reads initial configuraiton from JSON for each router, builds
323 configuration and loads its to router.
324
325 * `tgen`: Topogen object
fe50239b 326 * `topo`: json file data, or use tgen.json_topo if None
7659b8d4
AP
327 """
328
787e7624 329 func_dict = OrderedDict(
330 [
f8d572e8 331 ("vrfs", create_vrf_cfg),
787e7624 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),
e58fdb55 337 ("pim", create_pim_config),
338 ("igmp", create_igmp_config),
e13f9c4f 339 ("mld", create_mld_config),
787e7624 340 ("bgp", create_router_bgp),
701a0192 341 ("ospf", create_router_ospf),
787e7624 342 ]
343 )
7659b8d4 344
fe50239b
CH
345 if topo is None:
346 topo = tgen.json_topo
347
7659b8d4
AP
348 data = topo["routers"]
349 for func_type in func_dict.keys():
787e7624 350 logger.info("Checking for {} configuration in input data".format(func_type))
7659b8d4
AP
351
352 func_dict.get(func_type)(tgen, data, build=True)
353
4f99894d
CH
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)
49581587 359
e13f9c4f
KK
360 logger.info(
361 "Built config now clearing ospf neighbors as that router-id might not be what is used"
362 )
bceb50e4
DS
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
49581587
CH
374
375def 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 """
a53c08bc
CH
383 from lib.topogen import Topogen # Topogen imports this module too
384
49581587
CH
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
404def 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