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()
209 # Loopback interfaces
210 if "dst_node" in data
:
211 destRouter
= data
["dst_node"]
213 elif "-" in destRouterLink
:
214 # Spliting and storing destRouterLink data in tempList
215 tempList
= destRouterLink
.split("-")
217 destRouter
= tempList
.pop(0)
219 destRouter
= destRouterLink
221 if destRouter
in listAllRouters
:
222 topo
["routers"][destRouter
]["links"][curSwitch
] = deepcopy(
223 topo
["switches"][curSwitch
]["links"][destRouterLink
]
226 # Assigning name to interfaces
227 topo
["routers"][destRouter
]["links"][curSwitch
][
229 ] = "{}-{}-eth{}".format(
230 destRouter
, curSwitch
, topo
["routers"][destRouter
]["nextIfname"]
233 topo
["switches"][curSwitch
]["links"][destRouter
][
235 ] = "{}-{}-eth{}".format(
236 curSwitch
, destRouter
, topo
["routers"][destRouter
]["nextIfname"]
239 topo
["routers"][destRouter
]["nextIfname"] += 1
242 dictSwitches
[curSwitch
].add_link(
243 tgen
.gears
[destRouter
],
244 topo
["switches"][curSwitch
]["links"][destRouter
]["interface"],
245 topo
["routers"][destRouter
]["links"][curSwitch
]["interface"],
249 if "ipv4" in topo
["routers"][destRouter
]["links"][curSwitch
]:
251 topo
["routers"][destRouter
]["links"][curSwitch
]["ipv4"]
254 topo
["routers"][destRouter
]["links"][curSwitch
][
257 ipv4Next
, topo
["link_ip_start"]["v4mask"]
261 if "ipv6" in topo
["routers"][destRouter
]["links"][curSwitch
]:
263 topo
["routers"][destRouter
]["links"][curSwitch
]["ipv6"]
266 topo
["routers"][destRouter
]["links"][curSwitch
][
269 ipv6Next
, topo
["link_ip_start"]["v6mask"]
271 ipv6Next
= ipaddress
.IPv6Address(int(ipv6Next
) + ipv6Step
)
274 "Generated link data for router: %s\n%s",
277 topo
["routers"][curRouter
]["links"], indent
=4, sort_keys
=True
282 def linux_intf_config_from_json(tgen
, topo
=None):
283 """Configure interfaces from linux based on topo."""
285 topo
= tgen
.json_topo
287 routers
= topo
["routers"]
288 for rname
in routers
:
289 router
= tgen
.net
[rname
]
290 links
= routers
[rname
]["links"]
296 lname
= link
["interface"]
298 router
.cmd_raises("ip addr add {} dev {}".format(link
["ipv4"], lname
))
301 "ip -6 addr add {} dev {}".format(link
["ipv6"], lname
)
305 def build_config_from_json(tgen
, topo
=None, save_bkup
=True):
307 Reads initial configuraiton from JSON for each router, builds
308 configuration and loads its to router.
310 * `tgen`: Topogen object
311 * `topo`: json file data, or use tgen.json_topo if None
314 func_dict
= OrderedDict(
316 ("vrfs", create_vrf_cfg
),
317 ("ospf", create_router_ospf
),
318 ("links", create_interfaces_cfg
),
319 ("static_routes", create_static_routes
),
320 ("prefix_lists", create_prefix_lists
),
321 ("bgp_community_list", create_bgp_community_lists
),
322 ("route_maps", create_route_maps
),
323 ("pim", create_pim_config
),
324 ("igmp", create_igmp_config
),
325 ("mld", create_mld_config
),
326 ("bgp", create_router_bgp
),
331 topo
= tgen
.json_topo
333 data
= topo
["routers"]
334 for func_type
in func_dict
.keys():
335 logger
.info("Checking for {} configuration in input data".format(func_type
))
337 func_dict
.get(func_type
)(tgen
, data
, build
=True)
339 routers
= sorted(topo
["routers"].keys())
340 result
= load_config_to_routers(tgen
, routers
, save_bkup
)
342 logger
.info("build_config_from_json: failed to configure topology")
346 "Built config now clearing ospf neighbors as that router-id might not be what is used"
348 for ospf
in ["ospf", "ospf6"]:
350 if ospf
not in data
[router
]:
353 r
= tgen
.gears
[router
]
355 r
.vtysh_cmd("clear ip ospf process")
357 r
.vtysh_cmd("clear ipv6 ospf6 process")
360 def create_tgen_from_json(testfile
, json_file
=None):
361 """Create a topogen object given a testfile.
363 - `testfile` : The path to the testfile.
364 - `json_file` : The path to the json config file. If None the pathname is derived
365 from the `testfile` first by trying to replace `.py` by `.json` and if that isn't
366 present then by removing `test_` prefix as well.
368 from lib
.topogen
import Topogen
# Topogen imports this module too
370 thisdir
= os
.path
.dirname(os
.path
.realpath(testfile
))
371 basename
= os
.path
.basename(testfile
)
372 logger
.debug("starting standard JSON based module setup for %s", basename
)
374 assert basename
.startswith("test_")
375 assert basename
.endswith(".py")
376 json_file
= os
.path
.join(thisdir
, basename
[:-3] + ".json")
377 if not os
.path
.exists(json_file
):
378 json_file
= os
.path
.join(thisdir
, basename
[5:-3] + ".json")
379 assert os
.path
.exists(json_file
)
380 with
open(json_file
, "r") as topof
:
381 topo
= json
.load(topof
)
384 tgen
= Topogen(lambda tgen
: build_topo_from_json(tgen
, topo
), basename
[:-3])
385 tgen
.json_topo
= topo
389 def setup_module_from_json(testfile
, json_file
=None):
390 """Do the standard module setup for JSON based test.
392 * `testfile` : The path to the testfile. The name is used to derive the json config
393 file name as well (removing `test_` prefix and replacing `.py` suffix with `.json`
395 # Create topology object
396 tgen
= create_tgen_from_json(testfile
, json_file
)
398 # Start routers (and their daemons)
402 build_config_from_json(tgen
)
403 assert not tgen
.routers_have_failure()