Skip to content

Commit

Permalink
Added primary IP markers
Browse files Browse the repository at this point in the history
  • Loading branch information
johannwagner committed Aug 21, 2024
1 parent a112de2 commit c7128a1
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 36 deletions.
2 changes: 2 additions & 0 deletions cosmo/graphqlclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def get_data(self, device_config):
}
ip_addresses {
address
role
}
untagged_vlan {
id
Expand Down Expand Up @@ -131,6 +132,7 @@ def get_data(self, device_config):
interfaces (type: "virtual") {
ip_addresses {
address
role
}
parent {
name
Expand Down
119 changes: 83 additions & 36 deletions cosmo/serializer.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
import ipaddress
import re
import json
from collections import defaultdict
from ipaddress import IPv4Interface, IPv4Network, IPv6Network, IPv6Interface
from typing import Any, Dict, List

from cosmo.logger import Logger

l = Logger("serializer.py")


class Tags:
def __init__(self, tags):
if tags is None:
tags = []
self.tags = tags

# check if tag exists
def has(self, item, key = None):
def has(self, item, key=None):
if key:
return item.lower() in self.get_from_key(key)
return item.lower() in self.get_from_key(key)
else:
return item.lower() in [t["slug"].lower() for t in self.tags]
return item.lower() in [t["slug"].lower() for t in self.tags]

# return all sub-tags using a key
def get_from_key(self, key):
delimiter = ":"
keylen = len(key) + len(delimiter)
return [t['name'][keylen:] for t in self.tags if t['name'].lower().startswith(key.lower()+delimiter)]
return [t['name'][keylen:] for t in self.tags if t['name'].lower().startswith(key.lower() + delimiter)]

# check if there are any tags with a specified key
def has_key(self, key):
return len(self.get_from_key(key)) > 0


class RouterSerializer:

def __init__(self, device, l2vpn_list, vrfs):
Expand All @@ -49,7 +54,8 @@ def __init__(self, device, l2vpn_list, vrfs):
return

self.device = device
self.l2vpn_vlan_terminations, self.l2vpn_interface_terminations = RouterSerializer.process_l2vpn_terminations(l2vpn_list)
self.l2vpn_vlan_terminations, self.l2vpn_interface_terminations = RouterSerializer.process_l2vpn_terminations(
l2vpn_list)
self.vrfs = vrfs

self.l2vpns = {}
Expand Down Expand Up @@ -110,57 +116,82 @@ def _get_vrf_rib(self, routes, vrf):
else:
table = "inet6.0"
if vrf:
table = vrf+"."+table
table = vrf + "." + table

if table not in rib:
rib[table] = {"static": {}}
rib[table]["static"][route["prefix"]["prefix"]] = r
return rib


def _get_unit(self, iface):
unit_stub = {}
name = iface['name'].split(".")[1]

ipv4s = []
ipv6s = []
ipv4Family = defaultdict(
lambda: {"primaryIP": None, "secondaryIPs": []})
ipv6Family = defaultdict(
lambda: {"primaryIP": None, "secondaryIPs": []})

policer = {}
tags = Tags(iface.get("tags"))
is_edge = tags.has_key("edge")
is_mgmt = iface['name'].startswith(self.mgmt_interface) or (self.bmc_interface and iface['name'].startswith(self.bmc_interface))
is_mgmt = iface['name'].startswith(self.mgmt_interface) or (
self.bmc_interface and iface['name'].startswith(self.bmc_interface))
sample = is_edge and not tags.has("customer", "edge") and not tags.has("disable_sampling")

for ip in iface["ip_addresses"]:
ipa = ipaddress.ip_network(ip["address"], strict=False)
ipa = ipaddress.ip_interface(ip["address"])
is_secondary = ip.get("role", "primary") == "secondary"

# abort if a private IP is used on a unit without a VRF
# we use !is_global instead of is_private since the latter ignores 100.64/10
if not iface["vrf"] and not ipa.is_global and not is_mgmt:
l.error(f"Private IP {ipa} used on interface {iface['name']} in default VRF. Did you forget to configure a VRF?")
l.error(
f"Private IP {ipa} used on interface {iface['name']} in default VRF. Did you forget to configure a VRF?")
exit(f"Error while serializing device {self.device['name']}, aborting.")

if ipa.version == 4:
ipv4s.append(ip)
else:
ipv6s.append(ip)
if is_secondary:
ipv4Family[ipa.network]["secondaryIPs"].append(ipa)
elif ipv4Family[ipa.network]["primaryIP"] is None:
ipv4Family[ipa.network]["primaryIP"] = ipa
else:
l.error(f"{ipa} conflicts with another primary ip address {ipv4Family[ipa.network]['primaryIP']} in network {ipa.network} on interface {iface['name']}. Try setting role to secondary.")
exit(f"Error while serializing device {self.device['name']}, aborting.")
elif ipa.version == 6:
if is_secondary:
ipv6Family[ipa.network]["secondaryIPs"].append(ipa)
elif ipv6Family[ipa.network]["primaryIP"] is None:
ipv6Family[ipa.network]["primaryIP"] = ipa
else:
l.error(f"{ipa} conflicts with another primary ip address {ipv6Family[ipa.network]['primaryIP']} in network {ipa.network} on interface {iface['name']}. Try setting role to secondary.")
exit(f"Error while serializing device {self.device['name']}, aborting.")

if tags.has_key("policer"):
policer["input"] = "POLICER_"+tags.get_from_key("policer")[0]
policer["output"] = "POLICER_"+tags.get_from_key("policer")[0]
policer["input"] = "POLICER_" + tags.get_from_key("policer")[0]
policer["output"] = "POLICER_" + tags.get_from_key("policer")[0]
if tags.has_key("policer_in"):
policer["input"] = "POLICER_"+tags.get_from_key("policer_in")[0]
policer["input"] = "POLICER_" + tags.get_from_key("policer_in")[0]
if tags.has_key("policer_out"):
policer["output"] = "POLICER_"+tags.get_from_key("policer_out")[0]
policer["output"] = "POLICER_" + tags.get_from_key("policer_out")[0]

if policer:
unit_stub["policer"] = policer
if iface["mac_address"]:
unit_stub["mac_address"] = iface["mac_address"]


families = {}
if len(ipv4s) > 0:
families["inet"] = {"address": { address: {} for address in map(lambda addr: addr["address"], ipv4s) }}
if len(ipv4Family) > 0:

families["inet"] = {
'address': {}
}
for network, v in ipv4Family.items():
primaryMarker = {"primary": True} if len(ipv4Family[network]['secondaryIPs']) > 0 else {}
families["inet"]["address"][ipv4Family[network]["primaryIP"].with_prefixlen] = primaryMarker
secondaryIPs = {address: {} for address in map(lambda addr: addr["address"], ipv4Family[network]['secondaryIPs'])}
families["inet"]["address"].update(secondaryIPs)

if is_edge:
families["inet"]["filters"] = ["input-list [ EDGE_FILTER ]"]
if sample:
Expand All @@ -169,8 +200,17 @@ def _get_unit(self, iface):
families["inet"]["policer"] = ["arp POLICER_IXP_ARP"]
if tags.has_key("urpf") and not tags.has("disable", "urpf"):
families["inet"]["rpf_check"] = {"mode": tags.get_from_key("urpf")[0]}
if len(ipv6s) > 0:
families["inet6"] = {"address": { address: {} for address in map(lambda addr: addr["address"], ipv6s) }}

if len(ipv6Family) > 0:
families["inet6"] = {
'address': {}
}
for network, v in ipv4Family.items():
primaryMarker = {"primary": True} if len(ipv4Family[network]['secondaryIPs']) > 0 else {}
families["inet6"]["address"][ipv4Family[network]["primaryIP"].with_prefixlen] = primaryMarker
secondaryIPs = {address: {} for address in map(lambda addr: addr["address"], ipv4Family[network]['secondaryIPs'])}
families["inet6"]["address"].update(secondaryIPs)

if is_edge:
families["inet6"]["filters"] = ["input-list [ EDGE_FILTER_V6 ]"]
if sample:
Expand Down Expand Up @@ -212,8 +252,7 @@ def _get_unit(self, iface):
interface_vlan_id = iface["untagged_vlan"]["id"]

if outer_tag := iface.get('custom_fields', {}).get("outer_tag", None):
unit_stub["vlan"] = int(outer_tag)

unit_stub["vlan"] = int(outer_tag)

l2vpn_vlan_attached = interface_vlan_id and self.l2vpn_vlan_terminations.get(interface_vlan_id)
l2vpn_interface_attached = self.l2vpn_interface_terminations.get(iface["id"])
Expand Down Expand Up @@ -310,7 +349,8 @@ def serialize(self):
if tags.has_key("autoneg"):
if not interface_stub.get('gigether'):
interface_stub['gigether'] = {}
interface_stub['gigether']['autonegotiation'] = True if tags.get_from_key("autoneg")[0] == "on" else False
interface_stub['gigether']['autonegotiation'] = True if tags.get_from_key("autoneg")[
0] == "on" else False

for fec in tags.get_from_key("fec"):
if not interface_stub.get("gigether"):
Expand Down Expand Up @@ -408,7 +448,8 @@ def serialize(self):
"0.0.0.0/0": {
"next_hop": next(
ipaddress.ip_network(
next(iter(interfaces[self.mgmt_interface]["units"][0]["families"]["inet"]["address"].keys())),
next(iter(interfaces[self.mgmt_interface]["units"][0]["families"]["inet"][
"address"].keys())),
strict=False,
).hosts()
).compressed
Expand All @@ -419,7 +460,9 @@ def serialize(self):
}

if interfaces.get(self.lo_interface, {}).get("units", {}).get(0):
router_id = next(iter(interfaces[self.lo_interface]["units"][0]["families"]["inet"]["address"].keys())).split("/")[0]
router_id = \
next(iter(interfaces[self.lo_interface]["units"][0]["families"]["inet"]["address"].keys())).split("/")[
0]

for _, l2vpn in self.l2vpns.items():
if l2vpn['type'] == "VXLAN_EVPN":
Expand Down Expand Up @@ -515,7 +558,8 @@ def serialize(self):
# We pick a `virtual` interface which is not in a VRF and which parent is a `loopback`
# Then we pick the first IPv4 address.
for a in remote_interfaces:
if a["vrf"] == None and a["parent"] and a["parent"]["type"] == "LOOPBACK" and a["parent"]["name"].startswith("lo"):
if a["vrf"] == None and a["parent"] and a["parent"]["type"] == "LOOPBACK" and a["parent"][
"name"].startswith("lo"):
for ip in a['ip_addresses']:
ipa = ipaddress.ip_interface(ip["address"])
if ipa.version == 4:
Expand All @@ -535,9 +579,9 @@ def serialize(self):

for _, l3vpn in self.l3vpns.items():
if l3vpn["rd"]:
rd = router_id+":"+l3vpn["rd"]
rd = router_id + ":" + l3vpn["rd"]
elif len(l3vpn["export_targets"]) > 0:
rd = router_id+":"+l3vpn["id"]
rd = router_id + ":" + l3vpn["id"]
else:
rd = None

Expand Down Expand Up @@ -583,14 +627,15 @@ def serialize(self):
elif interface['lag']:
for i in self.device["interfaces"]:
if interface['lag']['id'] == i['id']:
interface_stub["description"] = "LAG Member of "+i['name']
interface_stub["description"] = "LAG Member of " + i['name']
break

interface_stub["mtu"] = interface["mtu"] if interface["mtu"] else 10000

if interface["type"] == "LAG":
interface_stub["bond_mode"] = "802.3ad"
interface_stub["bond_slaves"] = sorted([i["name"] for i in self.device["interfaces"] if i["lag"] and i["lag"]["id"] == interface["id"]])
interface_stub["bond_slaves"] = sorted(
[i["name"] for i in self.device["interfaces"] if i["lag"] and i["lag"]["id"] == interface["id"]])

if interface["untagged_vlan"]:
interface_stub["untagged_vlan"] = interface["untagged_vlan"]["vid"]
Expand All @@ -599,7 +644,8 @@ def serialize(self):
if interface["tagged_vlans"]:
interface_stub["tagged_vlans"] = [v["vid"] for v in interface["tagged_vlans"]]
# untagged vlans belong to the vid list as well
if interface["untagged_vlan"] and interface["untagged_vlan"]["vid"] not in interface_stub["tagged_vlans"]:
if interface["untagged_vlan"] and interface["untagged_vlan"]["vid"] not in interface_stub[
"tagged_vlans"]:
interface_stub["tagged_vlans"].append(interface["untagged_vlan"]["vid"])
interface_stub["tagged_vlans"].sort()
vlans.update(interface_stub["tagged_vlans"])
Expand Down Expand Up @@ -641,7 +687,8 @@ def serialize(self):
interfaces["bridge"] = {
"mtu": 10000,
"tagged_vlans": sorted(list(vlans)),
"bridge_ports": sorted([i["name"] for i in self.device["interfaces"] if i["enabled"] and not i["lag"] and (i["untagged_vlan"] or len(i["tagged_vlans"]) > 0)]),
"bridge_ports": sorted([i["name"] for i in self.device["interfaces"] if i["enabled"] and not i["lag"] and (
i["untagged_vlan"] or len(i["tagged_vlans"]) > 0)]),
}

device_stub["cumulus__device_interfaces"] = interfaces
Expand Down

0 comments on commit c7128a1

Please sign in to comment.