11#!/usr/bin/env python3
22
3+ from collections import defaultdict , namedtuple
34import copy
4- from typing import Any , Dict , List , Tuple
5+ from typing import Any , Dict , List , Tuple , Union
56from ipaddress import IPv4Network , IPv6Network
67import click
78import jinja2
1112PTF_BACKPLANE_IPV6 = "fc0a::ff"
1213backplane_additional_offset_ipv6 = 0
1314
15+
16+ class LagPort (set ):
17+ def __init__ (self , * ports ):
18+ super ().__init__ (ports )
19+
20+
21+ class PortList (list ):
22+ def __init__ (self , * lag_ports : Union [LagPort , int ]):
23+ super ().__init__ (lag_ports )
24+
25+ def __contains__ (self , key ):
26+ if super ().__contains__ (key ):
27+ return True
28+ return any ([key in lag_port for lag_port in self if isinstance (lag_port , LagPort )])
29+
30+
31+ Breakout = namedtuple ('Breakout' , ['port' , 'index' ])
32+
1433# Define the roles for the devices in the topology
1534roles_cfg = {
1635 "t0" : {
2746 "uplink" : {"role" : "t2" , "asn" : 65200 , "asn_v6" : 4200200000 },
2847 "peer" : None ,
2948 },
49+ "lt2" : {
50+ "asn" : 4200100000 ,
51+ "asn_v6" : 4200100000 ,
52+ "downlink" : {"role" : "t1" , "asn" : 4200000000 , "asn_v6" : 4200000000 , "asn_increment" : 0 , "num_lags" : 1 },
53+ "uplink" : {"role" : "ut2" , "asn" : 4200200000 , "asn_v6" : 4200200000 , "asn_increment" : 0 },
54+ "peer" : None ,
55+ },
3056}
3157
3258hw_port_cfg = {
84110 'peer_ports' : [],
85111 'skip_ports' : [16 , 17 , 44 , 45 , 48 , 49 ],
86112 "panel_port_step" : 1 },
113+ 'o128lt2' : {"ds_breakout" : 2 , "us_breakout" : 2 , "ds_link_step" : 1 , "us_link_step" : 1 ,
114+ 'uplink_ports' : PortList (LagPort (45 ), 46 , 47 , 48 , LagPort (49 ), 50 , 51 , 52 ),
115+ 'peer_ports' : [],
116+ 'skip_ports' : PortList (63 ),
117+ "panel_port_step" : 1 },
87118}
88119
89120vlan_group_cfgs = [
90- {"name" : "one_vlan_a" , "vlan_count" : 1 , "v4_prefix" : "192.168.0.0/21" , "v6_prefix" : "fc02:1000::0/64" },
91- {"name" : "two_vlan_a" , "vlan_count" : 2 , "v4_prefix" : "192.168.0.0/22" , "v6_prefix" : "fc02:100::0/64" },
92- {"name" : "four_vlan_a" , "vlan_count" : 4 , "v4_prefix" : "192.168.0.0/22" , "v6_prefix" : "fc02:100::0/64" },
121+ {"name" : "one_vlan_a" , "vlan_count" : 1 ,
122+ "v4_prefix" : "192.168.0.0/21" , "v6_prefix" : "fc02:1000::0/64" },
123+ {"name" : "two_vlan_a" , "vlan_count" : 2 ,
124+ "v4_prefix" : "192.168.0.0/22" , "v6_prefix" : "fc02:100::0/64" },
125+ {"name" : "four_vlan_a" , "vlan_count" : 4 ,
126+ "v4_prefix" : "192.168.0.0/22" , "v6_prefix" : "fc02:100::0/64" },
93127]
94128
95129
@@ -124,7 +158,8 @@ def __init__(self,
124158 dut_asn : int ,
125159 dut_asn_v6 : int ,
126160 role_cfg : Dict [str , Any ],
127- ip_offset : int = None ):
161+ ip_offset : int = None ,
162+ num_lags = 0 ):
128163
129164 self .role = role_cfg ["role" ]
130165
@@ -136,7 +171,8 @@ def __init__(self,
136171 self .tornum = tornum
137172
138173 # VLAN configuration
139- self .vlans = [link_id ]
174+ self .vlans = [link_id ] if not isinstance (
175+ link_id , range ) else [* link_id ]
140176
141177 # BGP configuration
142178 self .asn = role_cfg ["asn" ]
@@ -145,22 +181,32 @@ def __init__(self,
145181 self .peer_asn_v6 = dut_asn_v6
146182
147183 # IP addresses
148- self .dut_intf_ipv4 , self .pc_intf_ipv4 = calc_ipv4_pair ("10.0.0.0" , self .ip_offset )
149- self .dut_intf_ipv6 , self .pc_intf_ipv6 = calc_ipv6_pair ("FC00::" , self .ip_offset )
184+ self .dut_intf_ipv4 , self .pc_intf_ipv4 = calc_ipv4_pair (
185+ "10.0.0.0" , self .ip_offset )
186+ self .dut_intf_ipv6 , self .pc_intf_ipv6 = calc_ipv6_pair (
187+ "FC00::" , self .ip_offset )
150188 self .loopback_ipv4 = calc_ipv4 ("100.1.0.0" , self .ip_offset + 1 )
151- self .loopback_ipv6 = calc_ipv6 ("2064:100::" , (self .ip_offset + 1 ) * 2 ** 64 )
189+ self .loopback_ipv6 = calc_ipv6 (
190+ "2064:100::" , (self .ip_offset + 1 ) * 2 ** 64 )
191+
192+ # Set lags
193+ self .num_lags = num_lags
152194
153195 # Backplane IPs
154196 global backplane_additional_offset_ipv4
155- self .bp_ipv4 = calc_ipv4 ("10.10.246.1" , self .ip_offset + 1 + backplane_additional_offset_ipv4 )
197+ self .bp_ipv4 = calc_ipv4 (
198+ "10.10.246.1" , self .ip_offset + 1 + backplane_additional_offset_ipv4 )
156199 if self .bp_ipv4 == PTF_BACKPLANE_IPV4 :
157200 backplane_additional_offset_ipv4 = 1
158- self .bp_ipv4 = calc_ipv4 ("10.10.246.1" , self .ip_offset + 1 + backplane_additional_offset_ipv4 )
201+ self .bp_ipv4 = calc_ipv4 (
202+ "10.10.246.1" , self .ip_offset + 1 + backplane_additional_offset_ipv4 )
159203 global backplane_additional_offset_ipv6
160- self .bp_ipv6 = calc_ipv6 ("fc0a::1" , (self .ip_offset + 1 + backplane_additional_offset_ipv6 ))
204+ self .bp_ipv6 = calc_ipv6 (
205+ "fc0a::1" , (self .ip_offset + 1 + backplane_additional_offset_ipv6 ))
161206 if self .bp_ipv6 == PTF_BACKPLANE_IPV6 :
162207 backplane_additional_offset_ipv6 = 1
163- self .bp_ipv6 = calc_ipv6 ("fc0a::1" , self .ip_offset + 1 + backplane_additional_offset_ipv6 )
208+ self .bp_ipv6 = calc_ipv6 (
209+ "fc0a::1" , self .ip_offset + 1 + backplane_additional_offset_ipv6 )
164210
165211
166212class HostInterface :
@@ -197,12 +243,14 @@ def __init__(self, name: str, vlan_count: int, hostifs: List[HostInterface], v4_
197243
198244 # Split host if into the number of VLANs
199245 hostif_count_per_vlan = len (hostifs ) // vlan_count
200- hostif_groups = [hostifs [i * hostif_count_per_vlan :(i + 1 )* hostif_count_per_vlan ] for i in range (vlan_count )]
246+ hostif_groups = [
247+ hostifs [i * hostif_count_per_vlan :(i + 1 )* hostif_count_per_vlan ] for i in range (vlan_count )]
201248
202249 v4_prefix = IPv4Network (v4_prefix )
203250 v6_prefix = IPv6Network (v6_prefix )
204251 for vlan_index in range (len (hostif_groups )):
205- vlan = Vlan (1000 + vlan_index * 100 , hostif_groups [vlan_index ], v4_prefix , v6_prefix )
252+ vlan = Vlan (1000 + vlan_index * 100 ,
253+ hostif_groups [vlan_index ], v4_prefix , v6_prefix )
206254 self .vlans .append (vlan )
207255
208256 # Move to next subnet based on the prefix length
@@ -218,31 +266,46 @@ def generate_topo(role: str,
218266 port_cfg_type : str = "default" ,
219267 ) -> Tuple [List [VM ], List [HostInterface ]]:
220268
269+ def _find_lag_port (port_id : int ) -> bool :
270+ nonlocal port_cfg
271+ if not isinstance (port_cfg ["uplink_ports" ], PortList ):
272+ return False ,
273+
274+ lag_port = next (
275+ (lp for lp in port_cfg ["uplink_ports" ] if isinstance (lp , LagPort ) and port_id in lp ), None )
276+ return (lag_port is not None , lag_port )
277+
221278 dut_role_cfg = roles_cfg [role ]
222279 port_cfg = hw_port_cfg [port_cfg_type ]
223280
224281 vm_list = []
225282 downlinkif_list = []
226283 uplinkif_list = []
227- per_role_vm_count = {}
284+ per_role_vm_count = defaultdict (lambda : 0 )
285+ lag_port_assigned = set ()
228286 tornum = 1
229287 link_id_start = 0
288+
230289 for panel_port_id in list (range (0 , panel_port_count , port_cfg ['panel_port_step' ])) + peer_ports :
231290 vm_role_cfg = None
232291 link_step = 1
233292 link_type = None
293+
234294 if panel_port_id in uplink_ports :
235295 if dut_role_cfg ["uplink" ] is None :
236- raise ValueError ("Uplink port specified for a role that doesn't have an uplink" )
296+ raise ValueError (
297+ "Uplink port specified for a role that doesn't have an uplink" )
237298
238299 vm_role_cfg = dut_role_cfg ["uplink" ]
239300
240301 link_id_end = link_id_start + port_cfg ['us_breakout' ]
302+ num_breakout = port_cfg ['us_breakout' ]
241303 link_step = port_cfg ['us_link_step' ]
242304 link_type = 'up'
243305 elif panel_port_id in peer_ports :
244306 if dut_role_cfg ["peer" ] is None :
245- raise ValueError ("Peer port specified for a role that doesn't have a peer" )
307+ raise ValueError (
308+ "Peer port specified for a role that doesn't have a peer" )
246309
247310 vm_role_cfg = dut_role_cfg ["peer" ]
248311
@@ -255,23 +318,54 @@ def generate_topo(role: str,
255318 vm_role_cfg = dut_role_cfg ["downlink" ]
256319
257320 link_id_end = link_id_start + port_cfg ['ds_breakout' ]
321+ num_breakout = port_cfg ['ds_breakout' ]
258322 link_step = port_cfg ['ds_link_step' ]
259323 link_type = 'down'
260324
325+ is_lag_port , lag_port = _find_lag_port (panel_port_id )
326+
327+ if panel_port_id in lag_port_assigned :
328+ continue
329+
330+ if is_lag_port :
331+ per_role_vm_count [vm_role_cfg ["role" ]] += 1
332+ end_vlan_range = link_id_start + len (lag_port ) * num_breakout
333+
334+ vm_role_cfg ["asn" ] += vm_role_cfg .get ("asn_increment" , 1 )
335+ vm = VM (range (link_id_start , end_vlan_range ), len (vm_list ), per_role_vm_count [vm_role_cfg ["role" ]], tornum ,
336+ dut_role_cfg ["asn" ], dut_role_cfg ["asn_v6" ], vm_role_cfg , link_id_start ,
337+ num_lags = len (lag_port ) * num_breakout )
338+
339+ vm_list .append (vm )
340+
341+ if link_type == 'up' :
342+ uplinkif_list .append (link_id_start )
343+ elif link_type == 'down' :
344+ tornum += 1
345+ downlinkif_list .append (link_id_start )
346+
347+ lag_port_assigned .update (lag_port )
348+
349+ link_id_start = end_vlan_range
350+ continue
351+
261352 for link_id in range (link_id_start , link_id_end ):
262353 vm = None
263354 hostif = None
264355
265356 # Create the VM or host interface based on the configuration
266357 if vm_role_cfg is not None :
267- if vm_role_cfg ["role" ] not in per_role_vm_count :
268- per_role_vm_count [vm_role_cfg ["role" ]] = 0
269358 per_role_vm_count [vm_role_cfg ["role" ]] += 1
270359
271360 if (link_id - link_id_start ) % link_step == 0 and panel_port_id not in skip_ports :
272- vm_role_cfg ["asn" ] += 1
361+ # Skip breakout if defined
362+ if (panel_port_id , link_id - link_id_start ) in skip_ports :
363+ continue
364+
365+ vm_role_cfg ["asn" ] += vm_role_cfg .get ("asn_increment" , 1 )
273366 vm = VM (link_id , len (vm_list ), per_role_vm_count [vm_role_cfg ["role" ]], tornum ,
274- dut_role_cfg ["asn" ], dut_role_cfg ["asn_v6" ], vm_role_cfg , link_id )
367+ dut_role_cfg ["asn" ], dut_role_cfg ["asn_v6" ], vm_role_cfg , link_id ,
368+ num_lags = vm_role_cfg .get ('num_lags' , 0 ))
275369 vm_list .append (vm )
276370 if link_type == 'up' :
277371 uplinkif_list .append (link_id )
@@ -282,7 +376,6 @@ def generate_topo(role: str,
282376 if (link_id - link_id_start ) % link_step == 0 and panel_port_id not in skip_ports :
283377 hostif = HostInterface (link_id )
284378 downlinkif_list .append (hostif )
285- print (panel_port_id , link_id_start , link_id_end , link_step , vm_role_cfg )
286379 link_id_start = link_id_end
287380
288381 return vm_list , downlinkif_list , uplinkif_list
@@ -339,7 +432,7 @@ def write_topo_file(role: str,
339432
340433
341434@click .command ()
342- @click .option ("--role" , "-r" , required = True , type = click .Choice (['t0' , 't1' ]), help = "Role of the device" )
435+ @click .option ("--role" , "-r" , required = True , type = click .Choice (['t0' , 't1' , 'lt2' ]), help = "Role of the device" )
343436@click .option ("--keyword" , "-k" , required = True , type = str , help = "Keyword for the topology file" )
344437@click .option ("--template" , "-t" , required = True , type = str , help = "Path to the Jinja template file" )
345438@click .option ("--port-count" , "-c" , required = True , type = int , help = "Number of physical ports used on the device" )
@@ -374,19 +467,23 @@ def main(role: str, keyword: str, template: str, port_count: int, uplinks: str,
374467 - ./generate_topo.py -r t0 -k isolated-v6 -t t0-isolated-v6 -c 64 -l 'c512s2-sparse'
375468 - ./generate_topo.py -r t1 -k isolated-v6 -t t1-isolated-v6 -c 64 -l 'c448o16'
376469 - ./generate_topo.py -r t1 -k isolated-v6 -t t1-isolated-v6 -c 64 -l 'c448o16-sparse'
470+ - ./generate_topo.py -r lt2 -k o128 -t lt2_128 -c 64 -l 'o128lt2'
377471
378472 """
379473 uplink_ports = [int (port ) for port in uplinks .split ("," )] if uplinks != "" else \
380474 hw_port_cfg [link_cfg ]['uplink_ports' ]
381- peer_ports = [int (port ) for port in peers .split ("," )] if peers != "" else hw_port_cfg [link_cfg ]['peer_ports' ]
382- skip_ports = [int (port ) for port in skips .split ("," )] if skips != "" else hw_port_cfg [link_cfg ]['skip_ports' ]
475+ peer_ports = [int (port ) for port in peers .split (
476+ "," )] if peers != "" else hw_port_cfg [link_cfg ]['peer_ports' ]
477+ skip_ports = [int (port ) for port in skips .split (
478+ "," )] if skips != "" else hw_port_cfg [link_cfg ]['skip_ports' ]
383479
384480 vm_list , downlinkif_list , uplinkif_list = generate_topo (role , port_count , uplink_ports , peer_ports ,
385481 skip_ports , link_cfg )
386482 vlan_group_list = []
387483 if role == "t0" :
388484 vlan_group_list = generate_vlan_groups (downlinkif_list )
389- file_content = generate_topo_file (role , f"templates/topo_{ template } .j2" , vm_list , downlinkif_list , vlan_group_list )
485+ file_content = generate_topo_file (
486+ role , f"templates/topo_{ template } .j2" , vm_list , downlinkif_list , vlan_group_list )
390487 write_topo_file (role , keyword , len (downlinkif_list ), len (uplinkif_list ),
391488 len (peer_ports ), file_content )
392489
0 commit comments