1 # SPDX-License-Identifier: ISC
3 # Modified work Copyright (c) 2019 by VMware, Inc. ("VMware")
4 # Original work Copyright (c) 2018 by Network Device Education
5 # Foundation, Inc. ("NetDEF")
11 from collections
import OrderedDict
12 from copy
import deepcopy
13 from re
import search
as re_search
17 from lib
.bgp
import create_router_bgp
18 from lib
.common_config
import (
19 create_bgp_community_lists
,
20 create_interfaces_cfg
,
25 load_config_to_routers
,
30 from lib
.ospf
import create_router_ospf
36 from lib
.topolog
import logger
39 def build_topo_from_json(tgen
, topo
=None):
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.
44 * `tgen`: Topogen object
45 * `topo`: json file data, or use tgen.json_topo if None
51 topo
["routers"].keys(), key
=lambda x
: int(re_search(r
"\d+", x
).group(0))
55 if "switches" in topo
:
57 topo
["switches"].keys(), key
=lambda x
: int(re_search(r
"\d+", x
).group(0))
60 listRouters
= sorted(router_list
[:])
61 listSwitches
= sorted(switch_list
[:])
62 listAllRouters
= deepcopy(listRouters
)
65 for routerN
in router_list
:
66 logger
.info("Topo: Add router {}".format(routerN
))
67 tgen
.add_router(routerN
)
69 for switchN
in switch_list
:
70 logger
.info("Topo: Add switch {}".format(switchN
))
71 dictSwitches
[switchN
] = tgen
.add_switch(switchN
)
73 if "ipv4base" in topo
:
74 ipv4Next
= ipaddress
.IPv4Address(topo
["link_ip_start"]["ipv4"])
75 ipv4Step
= 2 ** (32 - topo
["link_ip_start"]["v4mask"])
76 if topo
["link_ip_start"]["v4mask"] < 32:
78 if "ipv6base" in topo
:
79 ipv6Next
= ipaddress
.IPv6Address(topo
["link_ip_start"]["ipv6"])
80 ipv6Step
= 2 ** (128 - topo
["link_ip_start"]["v6mask"])
81 if topo
["link_ip_start"]["v6mask"] < 127:
83 for router
in listRouters
:
84 topo
["routers"][router
]["nextIfname"] = 0
87 while listRouters
!= []:
88 curRouter
= listRouters
.pop(0)
90 if "links" in topo
["routers"][curRouter
]:
91 for destRouterLink
, data
in sorted(
92 topo
["routers"][curRouter
]["links"].items()
94 currRouter_lo_json
= topo
["routers"][curRouter
]["links"][destRouterLink
]
96 if "type" in data
and data
["type"] == "loopback":
99 "ipv4" in currRouter_lo_json
100 and currRouter_lo_json
["ipv4"] == "auto"
102 currRouter_lo_json
["ipv4"] = "{}{}.{}/{}".format(
103 topo
["lo_prefix"]["ipv4"],
105 number_to_column(curRouter
),
106 topo
["lo_prefix"]["v4mask"],
109 "ipv6" in currRouter_lo_json
110 and currRouter_lo_json
["ipv6"] == "auto"
112 currRouter_lo_json
["ipv6"] = "{}{}:{}/{}".format(
113 topo
["lo_prefix"]["ipv6"],
115 number_to_column(curRouter
),
116 topo
["lo_prefix"]["v6mask"],
119 if "-" in destRouterLink
:
120 # Spliting and storing destRouterLink data in tempList
121 tempList
= destRouterLink
.split("-")
124 destRouter
= tempList
.pop(0)
126 # Current Router Link
127 tempList
.insert(0, curRouter
)
128 curRouterLink
= "-".join(tempList
)
130 destRouter
= destRouterLink
131 curRouterLink
= curRouter
133 if destRouter
in listRouters
:
134 currRouter_link_json
= topo
["routers"][curRouter
]["links"][
137 destRouter_link_json
= topo
["routers"][destRouter
]["links"][
141 # Assigning name to interfaces
142 currRouter_link_json
["interface"] = "{}-{}-eth{}".format(
143 curRouter
, destRouter
, topo
["routers"][curRouter
]["nextIfname"]
145 destRouter_link_json
["interface"] = "{}-{}-eth{}".format(
146 destRouter
, curRouter
, topo
["routers"][destRouter
]["nextIfname"]
150 destRouter_link_json
["peer-interface"] = "{}-{}-eth{}".format(
151 curRouter
, destRouter
, topo
["routers"][curRouter
]["nextIfname"]
153 currRouter_link_json
["peer-interface"] = "{}-{}-eth{}".format(
154 destRouter
, curRouter
, topo
["routers"][destRouter
]["nextIfname"]
157 topo
["routers"][curRouter
]["nextIfname"] += 1
158 topo
["routers"][destRouter
]["nextIfname"] += 1
160 # Linking routers to each other as defined in JSON file
161 tgen
.gears
[curRouter
].add_link(
162 tgen
.gears
[destRouter
],
163 topo
["routers"][curRouter
]["links"][destRouterLink
][
166 topo
["routers"][destRouter
]["links"][curRouterLink
][
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"]
177 destRouter_link_json
["ipv4"] = "{}/{}".format(
178 ipv4Next
+ 1, topo
["link_ip_start"]["v4mask"]
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"]
187 destRouter_link_json
["ipv6"] = "{}/{}".format(
188 ipv6Next
+ 1, topo
["link_ip_start"]["v6mask"]
190 ipv6Next
= ipaddress
.IPv6Address(int(ipv6Next
) + ipv6Step
)
193 "Generated link data for router: %s\n%s",
196 topo
["routers"][curRouter
]["links"], indent
=4, sort_keys
=True
201 add_switch_to_topo
= []
202 while listSwitches
!= []:
203 curSwitch
= listSwitches
.pop(0)
204 # Physical Interfaces
205 if "links" in topo
["switches"][curSwitch
]:
206 for destRouterLink
, data
in sorted(
207 topo
["switches"][curSwitch
]["links"].items()
210 # Loopback interfaces
211 if "dst_node" in data
:
212 destRouter
= data
["dst_node"]
214 elif "-" in destRouterLink
:
215 # Spliting and storing destRouterLink data in tempList
216 tempList
= destRouterLink
.split("-")
218 destRouter
= tempList
.pop(0)
220 destRouter
= destRouterLink
222 if destRouter
in listAllRouters
:
224 topo
["routers"][destRouter
]["links"][curSwitch
] = deepcopy(
225 topo
["switches"][curSwitch
]["links"][destRouterLink
]
228 # Assigning name to interfaces
229 topo
["routers"][destRouter
]["links"][curSwitch
][
231 ] = "{}-{}-eth{}".format(
232 destRouter
, curSwitch
, topo
["routers"][destRouter
]["nextIfname"]
235 topo
["switches"][curSwitch
]["links"][destRouter
][
237 ] = "{}-{}-eth{}".format(
238 curSwitch
, destRouter
, topo
["routers"][destRouter
]["nextIfname"]
241 topo
["routers"][destRouter
]["nextIfname"] += 1
244 dictSwitches
[curSwitch
].add_link(
245 tgen
.gears
[destRouter
],
246 topo
["switches"][curSwitch
]["links"][destRouter
]["interface"],
247 topo
["routers"][destRouter
]["links"][curSwitch
]["interface"],
251 if "ipv4" in topo
["routers"][destRouter
]["links"][curSwitch
]:
253 topo
["routers"][destRouter
]["links"][curSwitch
]["ipv4"]
256 topo
["routers"][destRouter
]["links"][curSwitch
][
259 ipv4Next
, topo
["link_ip_start"]["v4mask"]
263 if "ipv6" in topo
["routers"][destRouter
]["links"][curSwitch
]:
265 topo
["routers"][destRouter
]["links"][curSwitch
]["ipv6"]
268 topo
["routers"][destRouter
]["links"][curSwitch
][
271 ipv6Next
, topo
["link_ip_start"]["v6mask"]
273 ipv6Next
= ipaddress
.IPv6Address(int(ipv6Next
) + ipv6Step
)
276 "Generated link data for router: %s\n%s",
279 topo
["routers"][curRouter
]["links"], indent
=4, sort_keys
=True
284 def linux_intf_config_from_json(tgen
, topo
=None):
285 """Configure interfaces from linux based on topo."""
287 topo
= tgen
.json_topo
289 routers
= topo
["routers"]
290 for rname
in routers
:
291 router
= tgen
.net
[rname
]
292 links
= routers
[rname
]["links"]
298 lname
= link
["interface"]
300 router
.cmd_raises("ip addr add {} dev {}".format(link
["ipv4"], lname
))
303 "ip -6 addr add {} dev {}".format(link
["ipv6"], lname
)
307 def build_config_from_json(tgen
, topo
=None, save_bkup
=True):
309 Reads initial configuraiton from JSON for each router, builds
310 configuration and loads its to router.
312 * `tgen`: Topogen object
313 * `topo`: json file data, or use tgen.json_topo if None
316 func_dict
= OrderedDict(
318 ("vrfs", create_vrf_cfg
),
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
),
324 ("pim", create_pim_config
),
325 ("igmp", create_igmp_config
),
326 ("mld", create_mld_config
),
327 ("bgp", create_router_bgp
),
328 ("ospf", create_router_ospf
),
333 topo
= tgen
.json_topo
335 data
= topo
["routers"]
336 for func_type
in func_dict
.keys():
337 logger
.info("Checking for {} configuration in input data".format(func_type
))
339 func_dict
.get(func_type
)(tgen
, data
, build
=True)
341 routers
= sorted(topo
["routers"].keys())
342 result
= load_config_to_routers(tgen
, routers
, save_bkup
)
344 logger
.info("build_config_from_json: failed to configure topology")
348 "Built config now clearing ospf neighbors as that router-id might not be what is used"
350 for ospf
in ["ospf", "ospf6"]:
352 if ospf
not in data
[router
]:
355 r
= tgen
.gears
[router
]
357 r
.vtysh_cmd("clear ip ospf process")
359 r
.vtysh_cmd("clear ipv6 ospf6 process")
362 def create_tgen_from_json(testfile
, json_file
=None):
363 """Create a topogen object given a testfile.
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.
370 from lib
.topogen
import Topogen
# Topogen imports this module too
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
)
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
)
386 tgen
= Topogen(lambda tgen
: build_topo_from_json(tgen
, topo
), basename
[:-3])
387 tgen
.json_topo
= topo
391 def setup_module_from_json(testfile
, json_file
=None):
392 """Do the standard module setup for JSON based test.
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`
397 # Create topology object
398 tgen
= create_tgen_from_json(testfile
, json_file
)
400 # Start routers (and their daemons)
401 start_topology(tgen
, topo_daemons(tgen
))
404 build_config_from_json(tgen
)
405 assert not tgen
.routers_have_failure()