@@ -10,13 +10,15 @@ import jinja2
1010class VrTopo :
1111 """ vrnetlab topo builder
1212 """
13- def __init__ (self , config ):
14- self .routers = {}
13+ def __init__ (self , config , keep_order ):
14+ self .keep_order = keep_order
1515 self .links = []
1616 self .fullmeshes = {}
1717 self .hubs = {}
1818 if 'routers' in config :
1919 self .routers = config ['routers' ]
20+ else :
21+ self .routers = {}
2022
2123 # sanity checking - use a YANG model and pyang to validate input?
2224 for r , val in self .routers .items ():
@@ -25,21 +27,41 @@ class VrTopo:
2527 if val ['type' ] not in ('dummy' , 'xcon' , 'bgp' , 'xrv' , 'xrv9k' , 'vmx' , 'sros' , 'csr' , 'nxos' , 'nxos9kv' , 'vqfx' , 'vrp' , 'veos' , 'openwrt' ):
2628 raise ValueError ("Unknown type %s for router %s" % (val ['type' ], r ))
2729
28- # expand p2p links
30+ # preserve order of entries if keep_order is set
31+ def maybe_sorted (d ):
32+ if keep_order :
33+ return d
34+ else :
35+ return sorted (d )
36+
2937 links = []
30- if 'p2p' in config :
31- for router in sorted (config ['p2p' ]):
32- neighbors = config ['p2p' ][router ]
33- for neighbor in neighbors :
34- links .append ({ 'left' : { 'router' : router }, 'right' : {
35- 'router' : neighbor }})
38+
39+ # expand p2p links
40+ def p2p ():
41+ nonlocal links
42+ if 'p2p' in config :
43+ for router in maybe_sorted (config ['p2p' ]):
44+ neighbors = config ['p2p' ][router ]
45+ for neighbor in neighbors :
46+ links .append ({ 'left' : { 'router' : router }, 'right' : {
47+ 'router' : neighbor }})
3648
3749 # expand fullmesh into links
38- if 'fullmeshes' in config :
39- for name in sorted (config ['fullmeshes' ]):
40- val = config ['fullmeshes' ][name ]
41- fmlinks = self .expand_fullmesh (val )
42- links .extend (fmlinks )
50+ def fullmesh ():
51+ nonlocal links
52+ if 'fullmeshes' in config :
53+ for name in maybe_sorted (config ['fullmeshes' ]):
54+ val = config ['fullmeshes' ][name ]
55+ fmlinks = self .expand_fullmesh (val )
56+ links .extend (fmlinks )
57+
58+ # expand fullmeshes before p2p if keep_order is set and fullmeshes is defined before p2p
59+ if keep_order and 'fullmeshes' in config and 'p2p' in config and tuple (config ).index ('fullmeshes' ) < tuple (config ).index ('p2p' ):
60+ fullmesh ()
61+ p2p ()
62+ else :
63+ p2p ()
64+ fullmesh ()
4365
4466 self .links = self .assign_interfaces (links )
4567
@@ -73,9 +95,8 @@ class VrTopo:
7395 for num_id in val ['interfaces' ]:
7496 val ['interfaces' ][num_id ] = self .intf_num_to_name (router , num_id )
7597
76-
77-
78- def expand_fullmesh (self , routers ):
98+ @staticmethod
99+ def expand_fullmesh (routers ):
79100 """ Flatten a full-mesh into a list of links
80101
81102 Links are considered bi-directional, so you will only see a link A->B
@@ -179,7 +200,7 @@ class VrTopo:
179200 }
180201
181202 if output_format == 'json' :
182- return json .dumps (output , sort_keys = True , indent = 4 )
203+ return json .dumps (output , sort_keys = ( not self . keep_order ) , indent = 4 )
183204 else :
184205 raise ValueError ("Invalid output format" )
185206
@@ -299,6 +320,7 @@ if __name__ == '__main__':
299320 import argparse
300321 parser = argparse .ArgumentParser ()
301322 parser .add_argument ("--build" , help = "Build topology from config" )
323+ parser .add_argument ("--keep-order" , action = "store_true" , help = "Keep topology order (routers, links, fullmeshes). Applies to --build only" )
302324 parser .add_argument ("--run" , help = "Run topology" )
303325 parser .add_argument ("--dry-run" , action = "store_true" , default = False , help = "Only print what would be performed during --run" )
304326 parser .add_argument ("--prefix" , default = '' , help = "docker container name prefix" )
@@ -321,7 +343,7 @@ if __name__ == '__main__':
321343 config = json .loads (input_file .read (), object_pairs_hook = OrderedDict )
322344 input_file .close ()
323345 try :
324- vt = VrTopo (config )
346+ vt = VrTopo (config , args . keep_order )
325347 except Exception as exc :
326348 print ("ERROR:" , exc )
327349 sys .exit (1 )
0 commit comments