Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 124 additions & 27 deletions ansible/generate_topo.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python3

from collections import defaultdict, namedtuple
import copy
from typing import Any, Dict, List, Tuple
from typing import Any, Dict, List, Tuple, Union
from ipaddress import IPv4Network, IPv6Network
import click
import jinja2
Expand All @@ -11,6 +12,24 @@
PTF_BACKPLANE_IPV6 = "fc0a::ff"
backplane_additional_offset_ipv6 = 0


class LagPort(set):
def __init__(self, *ports):
super().__init__(ports)


class PortList(list):
def __init__(self, *lag_ports: Union[LagPort, int]):
super().__init__(lag_ports)

def __contains__(self, key):
if super().__contains__(key):
return True
return any([key in lag_port for lag_port in self if isinstance(lag_port, LagPort)])


Breakout = namedtuple('Breakout', ['port', 'index'])

# Define the roles for the devices in the topology
roles_cfg = {
"t0": {
Expand All @@ -27,6 +46,13 @@
"uplink": {"role": "t2", "asn": 65200, "asn_v6": 4200200000},
"peer": None,
},
"lt2": {
"asn": 4200100000,
"asn_v6": 4200100000,
"downlink": {"role": "t1", "asn": 4200000000, "asn_v6": 4200000000, "asn_increment": 0, "num_lags": 1},
"uplink": {"role": "ut2", "asn": 4200200000, "asn_v6": 4200200000, "asn_increment": 0},
"peer": None,
},
}

hw_port_cfg = {
Expand Down Expand Up @@ -84,12 +110,20 @@
'peer_ports': [],
'skip_ports': [16, 17, 44, 45, 48, 49],
"panel_port_step": 1},
'o128lt2': {"ds_breakout": 2, "us_breakout": 2, "ds_link_step": 1, "us_link_step": 1,
'uplink_ports': PortList(LagPort(45), 46, 47, 48, LagPort(49), 50, 51, 52),
'peer_ports': [],
'skip_ports': PortList(63),
"panel_port_step": 1},
}

vlan_group_cfgs = [
{"name": "one_vlan_a", "vlan_count": 1, "v4_prefix": "192.168.0.0/21", "v6_prefix": "fc02:1000::0/64"},
{"name": "two_vlan_a", "vlan_count": 2, "v4_prefix": "192.168.0.0/22", "v6_prefix": "fc02:100::0/64"},
{"name": "four_vlan_a", "vlan_count": 4, "v4_prefix": "192.168.0.0/22", "v6_prefix": "fc02:100::0/64"},
{"name": "one_vlan_a", "vlan_count": 1,
"v4_prefix": "192.168.0.0/21", "v6_prefix": "fc02:1000::0/64"},
{"name": "two_vlan_a", "vlan_count": 2,
"v4_prefix": "192.168.0.0/22", "v6_prefix": "fc02:100::0/64"},
{"name": "four_vlan_a", "vlan_count": 4,
"v4_prefix": "192.168.0.0/22", "v6_prefix": "fc02:100::0/64"},
]


Expand Down Expand Up @@ -124,7 +158,8 @@ def __init__(self,
dut_asn: int,
dut_asn_v6: int,
role_cfg: Dict[str, Any],
ip_offset: int = None):
ip_offset: int = None,
num_lags=0):

self.role = role_cfg["role"]

Expand All @@ -136,7 +171,8 @@ def __init__(self,
self.tornum = tornum

# VLAN configuration
self.vlans = [link_id]
self.vlans = [link_id] if not isinstance(
link_id, range) else [*link_id]

# BGP configuration
self.asn = role_cfg["asn"]
Expand All @@ -145,22 +181,32 @@ def __init__(self,
self.peer_asn_v6 = dut_asn_v6

# IP addresses
self.dut_intf_ipv4, self.pc_intf_ipv4 = calc_ipv4_pair("10.0.0.0", self.ip_offset)
self.dut_intf_ipv6, self.pc_intf_ipv6 = calc_ipv6_pair("FC00::", self.ip_offset)
self.dut_intf_ipv4, self.pc_intf_ipv4 = calc_ipv4_pair(
"10.0.0.0", self.ip_offset)
self.dut_intf_ipv6, self.pc_intf_ipv6 = calc_ipv6_pair(
"FC00::", self.ip_offset)
self.loopback_ipv4 = calc_ipv4("100.1.0.0", self.ip_offset+1)
self.loopback_ipv6 = calc_ipv6("2064:100::", (self.ip_offset+1) * 2**64)
self.loopback_ipv6 = calc_ipv6(
"2064:100::", (self.ip_offset+1) * 2**64)

# Set lags
self.num_lags = num_lags

# Backplane IPs
global backplane_additional_offset_ipv4
self.bp_ipv4 = calc_ipv4("10.10.246.1", self.ip_offset+1+backplane_additional_offset_ipv4)
self.bp_ipv4 = calc_ipv4(
"10.10.246.1", self.ip_offset+1+backplane_additional_offset_ipv4)
if self.bp_ipv4 == PTF_BACKPLANE_IPV4:
backplane_additional_offset_ipv4 = 1
self.bp_ipv4 = calc_ipv4("10.10.246.1", self.ip_offset+1+backplane_additional_offset_ipv4)
self.bp_ipv4 = calc_ipv4(
"10.10.246.1", self.ip_offset+1+backplane_additional_offset_ipv4)
global backplane_additional_offset_ipv6
self.bp_ipv6 = calc_ipv6("fc0a::1", (self.ip_offset+1+backplane_additional_offset_ipv6))
self.bp_ipv6 = calc_ipv6(
"fc0a::1", (self.ip_offset+1+backplane_additional_offset_ipv6))
if self.bp_ipv6 == PTF_BACKPLANE_IPV6:
backplane_additional_offset_ipv6 = 1
self.bp_ipv6 = calc_ipv6("fc0a::1", self.ip_offset+1+backplane_additional_offset_ipv6)
self.bp_ipv6 = calc_ipv6(
"fc0a::1", self.ip_offset+1+backplane_additional_offset_ipv6)


class HostInterface:
Expand Down Expand Up @@ -197,12 +243,14 @@ def __init__(self, name: str, vlan_count: int, hostifs: List[HostInterface], v4_

# Split host if into the number of VLANs
hostif_count_per_vlan = len(hostifs) // vlan_count
hostif_groups = [hostifs[i*hostif_count_per_vlan:(i+1)*hostif_count_per_vlan] for i in range(vlan_count)]
hostif_groups = [
hostifs[i*hostif_count_per_vlan:(i+1)*hostif_count_per_vlan] for i in range(vlan_count)]

v4_prefix = IPv4Network(v4_prefix)
v6_prefix = IPv6Network(v6_prefix)
for vlan_index in range(len(hostif_groups)):
vlan = Vlan(1000 + vlan_index * 100, hostif_groups[vlan_index], v4_prefix, v6_prefix)
vlan = Vlan(1000 + vlan_index * 100,
hostif_groups[vlan_index], v4_prefix, v6_prefix)
self.vlans.append(vlan)

# Move to next subnet based on the prefix length
Expand All @@ -218,31 +266,46 @@ def generate_topo(role: str,
port_cfg_type: str = "default",
) -> Tuple[List[VM], List[HostInterface]]:

def _find_lag_port(port_id: int) -> bool:
nonlocal port_cfg
if not isinstance(port_cfg["uplink_ports"], PortList):
return False,

lag_port = next(
(lp for lp in port_cfg["uplink_ports"] if isinstance(lp, LagPort) and port_id in lp), None)
return (lag_port is not None, lag_port)

dut_role_cfg = roles_cfg[role]
port_cfg = hw_port_cfg[port_cfg_type]

vm_list = []
downlinkif_list = []
uplinkif_list = []
per_role_vm_count = {}
per_role_vm_count = defaultdict(lambda: 0)
lag_port_assigned = set()
tornum = 1
link_id_start = 0

for panel_port_id in list(range(0, panel_port_count, port_cfg['panel_port_step'])) + peer_ports:
vm_role_cfg = None
link_step = 1
link_type = None

if panel_port_id in uplink_ports:
if dut_role_cfg["uplink"] is None:
raise ValueError("Uplink port specified for a role that doesn't have an uplink")
raise ValueError(
"Uplink port specified for a role that doesn't have an uplink")

vm_role_cfg = dut_role_cfg["uplink"]

link_id_end = link_id_start + port_cfg['us_breakout']
num_breakout = port_cfg['us_breakout']
link_step = port_cfg['us_link_step']
link_type = 'up'
elif panel_port_id in peer_ports:
if dut_role_cfg["peer"] is None:
raise ValueError("Peer port specified for a role that doesn't have a peer")
raise ValueError(
"Peer port specified for a role that doesn't have a peer")

vm_role_cfg = dut_role_cfg["peer"]

Expand All @@ -255,23 +318,54 @@ def generate_topo(role: str,
vm_role_cfg = dut_role_cfg["downlink"]

link_id_end = link_id_start + port_cfg['ds_breakout']
num_breakout = port_cfg['ds_breakout']
link_step = port_cfg['ds_link_step']
link_type = 'down'

is_lag_port, lag_port = _find_lag_port(panel_port_id)

if panel_port_id in lag_port_assigned:
continue

if is_lag_port:
per_role_vm_count[vm_role_cfg["role"]] += 1
end_vlan_range = link_id_start + len(lag_port) * num_breakout

vm_role_cfg["asn"] += vm_role_cfg.get("asn_increment", 1)
vm = VM(range(link_id_start, end_vlan_range), len(vm_list), per_role_vm_count[vm_role_cfg["role"]], tornum,
dut_role_cfg["asn"], dut_role_cfg["asn_v6"], vm_role_cfg, link_id_start,
num_lags=len(lag_port) * num_breakout)

vm_list.append(vm)

if link_type == 'up':
uplinkif_list.append(link_id_start)
elif link_type == 'down':
tornum += 1
downlinkif_list.append(link_id_start)

lag_port_assigned.update(lag_port)

link_id_start = end_vlan_range
continue

for link_id in range(link_id_start, link_id_end):
vm = None
hostif = None

# Create the VM or host interface based on the configuration
if vm_role_cfg is not None:
if vm_role_cfg["role"] not in per_role_vm_count:
per_role_vm_count[vm_role_cfg["role"]] = 0
per_role_vm_count[vm_role_cfg["role"]] += 1

if (link_id - link_id_start) % link_step == 0 and panel_port_id not in skip_ports:
vm_role_cfg["asn"] += 1
# Skip breakout if defined
if (panel_port_id, link_id - link_id_start) in skip_ports:
continue

vm_role_cfg["asn"] += vm_role_cfg.get("asn_increment", 1)
vm = VM(link_id, len(vm_list), per_role_vm_count[vm_role_cfg["role"]], tornum,
dut_role_cfg["asn"], dut_role_cfg["asn_v6"], vm_role_cfg, link_id)
dut_role_cfg["asn"], dut_role_cfg["asn_v6"], vm_role_cfg, link_id,
num_lags=vm_role_cfg.get('num_lags', 0))
vm_list.append(vm)
if link_type == 'up':
uplinkif_list.append(link_id)
Expand All @@ -282,7 +376,6 @@ def generate_topo(role: str,
if (link_id - link_id_start) % link_step == 0 and panel_port_id not in skip_ports:
hostif = HostInterface(link_id)
downlinkif_list.append(hostif)
print(panel_port_id, link_id_start, link_id_end, link_step, vm_role_cfg)
link_id_start = link_id_end

return vm_list, downlinkif_list, uplinkif_list
Expand Down Expand Up @@ -339,7 +432,7 @@ def write_topo_file(role: str,


@click.command()
@click.option("--role", "-r", required=True, type=click.Choice(['t0', 't1']), help="Role of the device")
@click.option("--role", "-r", required=True, type=click.Choice(['t0', 't1', 'lt2']), help="Role of the device")
@click.option("--keyword", "-k", required=True, type=str, help="Keyword for the topology file")
@click.option("--template", "-t", required=True, type=str, help="Path to the Jinja template file")
@click.option("--port-count", "-c", required=True, type=int, help="Number of physical ports used on the device")
Expand Down Expand Up @@ -374,19 +467,23 @@ def main(role: str, keyword: str, template: str, port_count: int, uplinks: str,
- ./generate_topo.py -r t0 -k isolated-v6 -t t0-isolated-v6 -c 64 -l 'c512s2-sparse'
- ./generate_topo.py -r t1 -k isolated-v6 -t t1-isolated-v6 -c 64 -l 'c448o16'
- ./generate_topo.py -r t1 -k isolated-v6 -t t1-isolated-v6 -c 64 -l 'c448o16-sparse'
- ./generate_topo.py -r lt2 -k o128 -t lt2_128 -c 64 -l 'o128lt2'

"""
uplink_ports = [int(port) for port in uplinks.split(",")] if uplinks != "" else \
hw_port_cfg[link_cfg]['uplink_ports']
peer_ports = [int(port) for port in peers.split(",")] if peers != "" else hw_port_cfg[link_cfg]['peer_ports']
skip_ports = [int(port) for port in skips.split(",")] if skips != "" else hw_port_cfg[link_cfg]['skip_ports']
peer_ports = [int(port) for port in peers.split(
",")] if peers != "" else hw_port_cfg[link_cfg]['peer_ports']
skip_ports = [int(port) for port in skips.split(
",")] if skips != "" else hw_port_cfg[link_cfg]['skip_ports']

vm_list, downlinkif_list, uplinkif_list = generate_topo(role, port_count, uplink_ports, peer_ports,
skip_ports, link_cfg)
vlan_group_list = []
if role == "t0":
vlan_group_list = generate_vlan_groups(downlinkif_list)
file_content = generate_topo_file(role, f"templates/topo_{template}.j2", vm_list, downlinkif_list, vlan_group_list)
file_content = generate_topo_file(
role, f"templates/topo_{template}.j2", vm_list, downlinkif_list, vlan_group_list)
write_topo_file(role, keyword, len(downlinkif_list), len(uplinkif_list),
len(peer_ports), file_content)

Expand Down