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