]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/lib/topojson.py
tests: Don't double create ospfv3 config
[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 create_igmp_config, create_pim_config
45 from lib.topolog import logger
46
47
48 def build_topo_from_json(tgen, topo=None):
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.
53 * `tgen`: Topogen object
54 * `topo`: json file data, or use tgen.json_topo if None
55 """
56 if topo is None:
57 topo = tgen.json_topo
58
59 router_list = sorted(
60 topo["routers"].keys(), key=lambda x: int(re_search(r"\d+", x).group(0))
61 )
62
63 switch_list = []
64 if "switches" in topo:
65 switch_list = sorted(
66 topo["switches"].keys(), key=lambda x: int(re_search(r"\d+", x).group(0))
67 )
68
69 listRouters = sorted(router_list[:])
70 listSwitches = sorted(switch_list[:])
71 listAllRouters = deepcopy(listRouters)
72 dictSwitches = {}
73
74 for routerN in router_list:
75 logger.info("Topo: Add router {}".format(routerN))
76 tgen.add_router(routerN)
77
78 for switchN in switch_list:
79 logger.info("Topo: Add switch {}".format(switchN))
80 dictSwitches[switchN] = tgen.add_switch(switchN)
81
82 if "ipv4base" in topo:
83 ipv4Next = ipaddress.IPv4Address(topo["link_ip_start"]["ipv4"])
84 ipv4Step = 2 ** (32 - topo["link_ip_start"]["v4mask"])
85 if topo["link_ip_start"]["v4mask"] < 32:
86 ipv4Next += 1
87 if "ipv6base" in topo:
88 ipv6Next = ipaddress.IPv6Address(topo["link_ip_start"]["ipv6"])
89 ipv6Step = 2 ** (128 - topo["link_ip_start"]["v6mask"])
90 if topo["link_ip_start"]["v6mask"] < 127:
91 ipv6Next += 1
92 for router in listRouters:
93 topo["routers"][router]["nextIfname"] = 0
94
95 router_count = 0
96 while listRouters != []:
97 curRouter = listRouters.pop(0)
98 # Physical Interfaces
99 if "links" in topo["routers"][curRouter]:
100 for destRouterLink, data in sorted(
101 topo["routers"][curRouter]["links"].items()
102 ):
103 currRouter_lo_json = topo["routers"][curRouter]["links"][destRouterLink]
104 # Loopback interfaces
105 if "type" in data and data["type"] == "loopback":
106 router_count += 1
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"],
113 router_count,
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"],
123 router_count,
124 number_to_column(curRouter),
125 topo["lo_prefix"]["v6mask"],
126 )
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:
143 currRouter_link_json = topo["routers"][curRouter]["links"][
144 destRouterLink
145 ]
146 destRouter_link_json = topo["routers"][destRouter]["links"][
147 curRouterLink
148 ]
149
150 # Assigning name to interfaces
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 )
157
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
166 topo["routers"][curRouter]["nextIfname"] += 1
167 topo["routers"][destRouter]["nextIfname"] += 1
168
169 # Linking routers to each other as defined in JSON file
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 )
179
180 # IPv4
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 )
189 ipv4Next += ipv4Step
190 # IPv6
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 )
199 ipv6Next = ipaddress.IPv6Address(int(ipv6Next) + ipv6Step)
200
201 logger.debug(
202 "Generated link data for router: %s\n%s",
203 curRouter,
204 json.dumps(
205 topo["routers"][curRouter]["links"], indent=4, sort_keys=True
206 ),
207 )
208
209 switch_count = 0
210 add_switch_to_topo = []
211 while listSwitches != []:
212 curSwitch = listSwitches.pop(0)
213 # Physical Interfaces
214 if "links" in topo["switches"][curSwitch]:
215 for destRouterLink, data in sorted(
216 topo["switches"][curSwitch]["links"].items()
217 ):
218
219 # Loopback interfaces
220 if "dst_node" in data:
221 destRouter = data["dst_node"]
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
233 topo["routers"][destRouter]["links"][curSwitch] = deepcopy(
234 topo["switches"][curSwitch]["links"][destRouterLink]
235 )
236
237 # Assigning name to interfaces
238 topo["routers"][destRouter]["links"][curSwitch][
239 "interface"
240 ] = "{}-{}-eth{}".format(
241 destRouter, curSwitch, topo["routers"][destRouter]["nextIfname"]
242 )
243
244 topo["switches"][curSwitch]["links"][destRouter][
245 "interface"
246 ] = "{}-{}-eth{}".format(
247 curSwitch, destRouter, topo["routers"][destRouter]["nextIfname"]
248 )
249
250 topo["routers"][destRouter]["nextIfname"] += 1
251
252 # Add links
253 dictSwitches[curSwitch].add_link(
254 tgen.gears[destRouter],
255 topo["switches"][curSwitch]["links"][destRouter]["interface"],
256 topo["routers"][destRouter]["links"][curSwitch]["interface"],
257 )
258
259 # IPv4
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 )
270 ipv4Next += 1
271 # IPv6
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 )
282 ipv6Next = ipaddress.IPv6Address(int(ipv6Next) + ipv6Step)
283
284 logger.debug(
285 "Generated link data for router: %s\n%s",
286 curRouter,
287 json.dumps(
288 topo["routers"][curRouter]["links"], indent=4, sort_keys=True
289 ),
290 )
291
292
293 def linux_intf_config_from_json(tgen, topo=None):
294 """Configure interfaces from linux based on topo."""
295 if topo is None:
296 topo = tgen.json_topo
297
298 routers = topo["routers"]
299 for rname in routers:
300 router = tgen.net[rname]
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:
309 router.cmd_raises("ip addr add {} dev {}".format(link["ipv4"], lname))
310 if "ipv6" in link:
311 router.cmd_raises(
312 "ip -6 addr add {} dev {}".format(link["ipv6"], lname)
313 )
314
315
316 def build_config_from_json(tgen, topo=None, save_bkup=True):
317 """
318 Reads initial configuraiton from JSON for each router, builds
319 configuration and loads its to router.
320
321 * `tgen`: Topogen object
322 * `topo`: json file data, or use tgen.json_topo if None
323 """
324
325 func_dict = OrderedDict(
326 [
327 ("vrfs", create_vrf_cfg),
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),
333 ("pim", create_pim_config),
334 ("igmp", create_igmp_config),
335 ("bgp", create_router_bgp),
336 ("ospf", create_router_ospf),
337 ]
338 )
339
340 if topo is None:
341 topo = tgen.json_topo
342
343 data = topo["routers"]
344 for func_type in func_dict.keys():
345 logger.info("Checking for {} configuration in input data".format(func_type))
346
347 func_dict.get(func_type)(tgen, data, build=True)
348
349 routers = sorted(topo["routers"].keys())
350 result = load_config_to_routers(tgen, routers, save_bkup)
351 if not result:
352 logger.info("build_config_from_json: failed to configure topology")
353 pytest.exit(1)
354
355
356 def create_tgen_from_json(testfile, json_file=None):
357 """Create a topogen object given a testfile.
358
359 - `testfile` : The path to the testfile.
360 - `json_file` : The path to the json config file. If None the pathname is derived
361 from the `testfile` first by trying to replace `.py` by `.json` and if that isn't
362 present then by removing `test_` prefix as well.
363 """
364 from lib.topogen import Topogen # Topogen imports this module too
365
366 thisdir = os.path.dirname(os.path.realpath(testfile))
367 basename = os.path.basename(testfile)
368 logger.debug("starting standard JSON based module setup for %s", basename)
369
370 assert basename.startswith("test_")
371 assert basename.endswith(".py")
372 json_file = os.path.join(thisdir, basename[:-3] + ".json")
373 if not os.path.exists(json_file):
374 json_file = os.path.join(thisdir, basename[5:-3] + ".json")
375 assert os.path.exists(json_file)
376 with open(json_file, "r") as topof:
377 topo = json.load(topof)
378
379 # Create topology
380 tgen = Topogen(lambda tgen: build_topo_from_json(tgen, topo), basename[:-3])
381 tgen.json_topo = topo
382 return tgen
383
384
385 def setup_module_from_json(testfile, json_file=None):
386 """Do the standard module setup for JSON based test.
387
388 * `testfile` : The path to the testfile. The name is used to derive the json config
389 file name as well (removing `test_` prefix and replacing `.py` suffix with `.json`
390 """
391 # Create topology object
392 tgen = create_tgen_from_json(testfile, json_file)
393
394 # Start routers (and their daemons)
395 start_topology(tgen, topo_daemons(tgen))
396
397 # Configure routers
398 build_config_from_json(tgen)
399 assert not tgen.routers_have_failure()
400
401 return tgen