2 # Modified work Copyright (c) 2019 by VMware, Inc. ("VMware")
3 # Original work Copyright (c) 2018 by Network Device Education
4 # Foundation, Inc. ("NetDEF")
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
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
24 from collections
import OrderedDict
25 from copy
import deepcopy
26 from re
import search
as re_search
30 from lib
.bgp
import create_router_bgp
31 from lib
.common_config
import (
32 create_bgp_community_lists
,
33 create_interfaces_cfg
,
38 load_config_to_routers
,
43 from lib
.ospf
import create_router_ospf
49 from lib
.topolog
import logger
52 def build_topo_from_json(tgen
, topo
=None):
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.
57 * `tgen`: Topogen object
58 * `topo`: json file data, or use tgen.json_topo if None
64 topo
["routers"].keys(), key
=lambda x
: int(re_search(r
"\d+", x
).group(0))
68 if "switches" in topo
:
70 topo
["switches"].keys(), key
=lambda x
: int(re_search(r
"\d+", x
).group(0))
73 listRouters
= sorted(router_list
[:])
74 listSwitches
= sorted(switch_list
[:])
75 listAllRouters
= deepcopy(listRouters
)
78 for routerN
in router_list
:
79 logger
.info("Topo: Add router {}".format(routerN
))
80 tgen
.add_router(routerN
)
82 for switchN
in switch_list
:
83 logger
.info("Topo: Add switch {}".format(switchN
))
84 dictSwitches
[switchN
] = tgen
.add_switch(switchN
)
86 if "ipv4base" in topo
:
87 ipv4Next
= ipaddress
.IPv4Address(topo
["link_ip_start"]["ipv4"])
88 ipv4Step
= 2 ** (32 - topo
["link_ip_start"]["v4mask"])
89 if topo
["link_ip_start"]["v4mask"] < 32:
91 if "ipv6base" in topo
:
92 ipv6Next
= ipaddress
.IPv6Address(topo
["link_ip_start"]["ipv6"])
93 ipv6Step
= 2 ** (128 - topo
["link_ip_start"]["v6mask"])
94 if topo
["link_ip_start"]["v6mask"] < 127:
96 for router
in listRouters
:
97 topo
["routers"][router
]["nextIfname"] = 0
100 while listRouters
!= []:
101 curRouter
= listRouters
.pop(0)
102 # Physical Interfaces
103 if "links" in topo
["routers"][curRouter
]:
104 for destRouterLink
, data
in sorted(
105 topo
["routers"][curRouter
]["links"].items()
107 currRouter_lo_json
= topo
["routers"][curRouter
]["links"][destRouterLink
]
108 # Loopback interfaces
109 if "type" in data
and data
["type"] == "loopback":
112 "ipv4" in currRouter_lo_json
113 and currRouter_lo_json
["ipv4"] == "auto"
115 currRouter_lo_json
["ipv4"] = "{}{}.{}/{}".format(
116 topo
["lo_prefix"]["ipv4"],
118 number_to_column(curRouter
),
119 topo
["lo_prefix"]["v4mask"],
122 "ipv6" in currRouter_lo_json
123 and currRouter_lo_json
["ipv6"] == "auto"
125 currRouter_lo_json
["ipv6"] = "{}{}:{}/{}".format(
126 topo
["lo_prefix"]["ipv6"],
128 number_to_column(curRouter
),
129 topo
["lo_prefix"]["v6mask"],
132 if "-" in destRouterLink
:
133 # Spliting and storing destRouterLink data in tempList
134 tempList
= destRouterLink
.split("-")
137 destRouter
= tempList
.pop(0)
139 # Current Router Link
140 tempList
.insert(0, curRouter
)
141 curRouterLink
= "-".join(tempList
)
143 destRouter
= destRouterLink
144 curRouterLink
= curRouter
146 if destRouter
in listRouters
:
147 currRouter_link_json
= topo
["routers"][curRouter
]["links"][
150 destRouter_link_json
= topo
["routers"][destRouter
]["links"][
154 # Assigning name to interfaces
155 currRouter_link_json
["interface"] = "{}-{}-eth{}".format(
156 curRouter
, destRouter
, topo
["routers"][curRouter
]["nextIfname"]
158 destRouter_link_json
["interface"] = "{}-{}-eth{}".format(
159 destRouter
, curRouter
, topo
["routers"][destRouter
]["nextIfname"]
163 destRouter_link_json
["peer-interface"] = "{}-{}-eth{}".format(
164 curRouter
, destRouter
, topo
["routers"][curRouter
]["nextIfname"]
166 currRouter_link_json
["peer-interface"] = "{}-{}-eth{}".format(
167 destRouter
, curRouter
, topo
["routers"][destRouter
]["nextIfname"]
170 topo
["routers"][curRouter
]["nextIfname"] += 1
171 topo
["routers"][destRouter
]["nextIfname"] += 1
173 # Linking routers to each other as defined in JSON file
174 tgen
.gears
[curRouter
].add_link(
175 tgen
.gears
[destRouter
],
176 topo
["routers"][curRouter
]["links"][destRouterLink
][
179 topo
["routers"][destRouter
]["links"][curRouterLink
][
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"]
190 destRouter_link_json
["ipv4"] = "{}/{}".format(
191 ipv4Next
+ 1, topo
["link_ip_start"]["v4mask"]
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"]
200 destRouter_link_json
["ipv6"] = "{}/{}".format(
201 ipv6Next
+ 1, topo
["link_ip_start"]["v6mask"]
203 ipv6Next
= ipaddress
.IPv6Address(int(ipv6Next
) + ipv6Step
)
206 "Generated link data for router: %s\n%s",
209 topo
["routers"][curRouter
]["links"], indent
=4, sort_keys
=True
214 add_switch_to_topo
= []
215 while listSwitches
!= []:
216 curSwitch
= listSwitches
.pop(0)
217 # Physical Interfaces
218 if "links" in topo
["switches"][curSwitch
]:
219 for destRouterLink
, data
in sorted(
220 topo
["switches"][curSwitch
]["links"].items()
223 # Loopback interfaces
224 if "dst_node" in data
:
225 destRouter
= data
["dst_node"]
227 elif "-" in destRouterLink
:
228 # Spliting and storing destRouterLink data in tempList
229 tempList
= destRouterLink
.split("-")
231 destRouter
= tempList
.pop(0)
233 destRouter
= destRouterLink
235 if destRouter
in listAllRouters
:
237 topo
["routers"][destRouter
]["links"][curSwitch
] = deepcopy(
238 topo
["switches"][curSwitch
]["links"][destRouterLink
]
241 # Assigning name to interfaces
242 topo
["routers"][destRouter
]["links"][curSwitch
][
244 ] = "{}-{}-eth{}".format(
245 destRouter
, curSwitch
, topo
["routers"][destRouter
]["nextIfname"]
248 topo
["switches"][curSwitch
]["links"][destRouter
][
250 ] = "{}-{}-eth{}".format(
251 curSwitch
, destRouter
, topo
["routers"][destRouter
]["nextIfname"]
254 topo
["routers"][destRouter
]["nextIfname"] += 1
257 dictSwitches
[curSwitch
].add_link(
258 tgen
.gears
[destRouter
],
259 topo
["switches"][curSwitch
]["links"][destRouter
]["interface"],
260 topo
["routers"][destRouter
]["links"][curSwitch
]["interface"],
264 if "ipv4" in topo
["routers"][destRouter
]["links"][curSwitch
]:
266 topo
["routers"][destRouter
]["links"][curSwitch
]["ipv4"]
269 topo
["routers"][destRouter
]["links"][curSwitch
][
272 ipv4Next
, topo
["link_ip_start"]["v4mask"]
276 if "ipv6" in topo
["routers"][destRouter
]["links"][curSwitch
]:
278 topo
["routers"][destRouter
]["links"][curSwitch
]["ipv6"]
281 topo
["routers"][destRouter
]["links"][curSwitch
][
284 ipv6Next
, topo
["link_ip_start"]["v6mask"]
286 ipv6Next
= ipaddress
.IPv6Address(int(ipv6Next
) + ipv6Step
)
289 "Generated link data for router: %s\n%s",
292 topo
["routers"][curRouter
]["links"], indent
=4, sort_keys
=True
297 def linux_intf_config_from_json(tgen
, topo
=None):
298 """Configure interfaces from linux based on topo."""
300 topo
= tgen
.json_topo
302 routers
= topo
["routers"]
303 for rname
in routers
:
304 router
= tgen
.net
[rname
]
305 links
= routers
[rname
]["links"]
311 lname
= link
["interface"]
313 router
.cmd_raises("ip addr add {} dev {}".format(link
["ipv4"], lname
))
316 "ip -6 addr add {} dev {}".format(link
["ipv6"], lname
)
320 def build_config_from_json(tgen
, topo
=None, save_bkup
=True):
322 Reads initial configuraiton from JSON for each router, builds
323 configuration and loads its to router.
325 * `tgen`: Topogen object
326 * `topo`: json file data, or use tgen.json_topo if None
329 func_dict
= OrderedDict(
331 ("vrfs", create_vrf_cfg
),
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
),
337 ("pim", create_pim_config
),
338 ("igmp", create_igmp_config
),
339 ("mld", create_mld_config
),
340 ("bgp", create_router_bgp
),
341 ("ospf", create_router_ospf
),
346 topo
= tgen
.json_topo
348 data
= topo
["routers"]
349 for func_type
in func_dict
.keys():
350 logger
.info("Checking for {} configuration in input data".format(func_type
))
352 func_dict
.get(func_type
)(tgen
, data
, build
=True)
354 routers
= sorted(topo
["routers"].keys())
355 result
= load_config_to_routers(tgen
, routers
, save_bkup
)
357 logger
.info("build_config_from_json: failed to configure topology")
361 "Built config now clearing ospf neighbors as that router-id might not be what is used"
363 for ospf
in ["ospf", "ospf6"]:
365 if ospf
not in data
[router
]:
368 r
= tgen
.gears
[router
]
370 r
.vtysh_cmd("clear ip ospf process")
372 r
.vtysh_cmd("clear ipv6 ospf6 process")
375 def create_tgen_from_json(testfile
, json_file
=None):
376 """Create a topogen object given a testfile.
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.
383 from lib
.topogen
import Topogen
# Topogen imports this module too
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
)
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
)
399 tgen
= Topogen(lambda tgen
: build_topo_from_json(tgen
, topo
), basename
[:-3])
400 tgen
.json_topo
= topo
404 def setup_module_from_json(testfile
, json_file
=None):
405 """Do the standard module setup for JSON based test.
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`
410 # Create topology object
411 tgen
= create_tgen_from_json(testfile
, json_file
)
413 # Start routers (and their daemons)
414 start_topology(tgen
, topo_daemons(tgen
))
417 build_config_from_json(tgen
)
418 assert not tgen
.routers_have_failure()