Skip to content

Refactoring: transform serializer to visitor, suppress state, isolate logic #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 129 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
129 commits
Select commit Hold shift + click to select a range
3918b4a
refactoring WIP draft ideas
loulecrivain Sep 17, 2024
6dddb34
add object-transformation of netbox data
loulecrivain Feb 6, 2025
43482d5
remove breakpoint
loulecrivain Feb 7, 2025
01ac195
add some properties
loulecrivain Feb 7, 2025
73b6816
more sophisticated getters for special interfaces
loulecrivain Feb 7, 2025
4b7e2b6
remove dead code
loulecrivain Feb 7, 2025
fa32396
add the possibility to get item parent in composite tree
loulecrivain Feb 7, 2025
85f8908
add __typename in graphql query
loulecrivain Feb 7, 2025
2670f43
remove the need for deepcopy
loulecrivain Feb 10, 2025
f2b2c2b
passing 1st test
loulecrivain Feb 10, 2025
fe39631
make AbstractNetboxType iterable
loulecrivain Feb 11, 2025
4246668
start passing tests again and building upon new visitor/iterator pattern
loulecrivain Feb 11, 2025
8811a58
pass mgmt interface test
loulecrivain Feb 11, 2025
7bced48
pass LAG test
loulecrivain Feb 12, 2025
022b181
pass speed test
loulecrivain Feb 12, 2025
f50d0dc
pass FEC test
loulecrivain Feb 12, 2025
db22a6c
refactor tag processing
loulecrivain Feb 12, 2025
64a03f3
delete unreachable code
loulecrivain Feb 12, 2025
2f2889b
return device with sorted lists
loulecrivain Feb 12, 2025
e216623
small fix
loulecrivain Feb 14, 2025
a37da61
add manufacturer strategies
loulecrivain Feb 14, 2025
a81a2e8
speed up manufacturer class lookup
loulecrivain Feb 14, 2025
38254d3
fix missing info on lag
loulecrivain Feb 14, 2025
3f30453
fix missing description for LAG members
loulecrivain Feb 14, 2025
cf0d4b7
fix tagged/untagged VLANs corner case
loulecrivain Feb 14, 2025
42b1107
take into account cases where device might not have manufacturer info
loulecrivain Feb 14, 2025
2861b64
fix overwritten descriptions for LAG members
loulecrivain Feb 14, 2025
7582547
move switch visitor to dedicated file
loulecrivain Feb 14, 2025
928342d
use empty router visitor
loulecrivain Feb 14, 2025
0944a07
add global state info into router visitor
loulecrivain Feb 17, 2025
45cb87d
pass 1st router test
loulecrivain Feb 17, 2025
9e9fef9
pass 2nd test
loulecrivain Feb 17, 2025
5496925
allow for more flexibility regarding sub interface / interface treatment
loulecrivain Feb 17, 2025
423f0ec
pass local_l2x test
loulecrivain Feb 19, 2025
5c42ba8
fix failing integration tests
loulecrivain Feb 19, 2025
ded9f81
pass lag test
loulecrivain Feb 20, 2025
69bac05
pass fec test
loulecrivain Feb 20, 2025
fee620d
pass router ips test
loulecrivain Feb 20, 2025
025d21b
pass router vrf rib test
loulecrivain Feb 20, 2025
357bd0f
pass mpls evpn test
loulecrivain Feb 21, 2025
22a81ca
pass vpws test
loulecrivain Feb 21, 2025
6ab386a
pass l3vpn test
loulecrivain Feb 21, 2025
cf712ae
pass bgp cpe test
loulecrivain Feb 24, 2025
4e5cfe6
pass policer test
loulecrivain Feb 24, 2025
4a101e3
fix inconsistent object cache hashing
loulecrivain Feb 24, 2025
f4d68ed
put all dunder methods together
loulecrivain Feb 24, 2025
d36d916
improve bgp cpe processing code clarity
loulecrivain Feb 24, 2025
8be700d
fix undue warnings
loulecrivain Feb 25, 2025
a631f61
yeet objcache to improve performance
loulecrivain Feb 25, 2025
faacb41
add guard for processing l2vpn terminations
loulecrivain Feb 25, 2025
95aa9ae
extract bgp cpe processing
loulecrivain Feb 25, 2025
7acf4db
extracting l2vpn validation
loulecrivain Feb 25, 2025
9952a56
extract l2vpn processing
loulecrivain Feb 25, 2025
4035893
make composition pattern clearer
loulecrivain Feb 25, 2025
c1b0104
add reverse path filtering
loulecrivain Feb 25, 2025
1ad0204
fixup! add reverse path filtering
loulecrivain Feb 25, 2025
684f580
add ipv6 router advertisement
loulecrivain Feb 25, 2025
66969e7
use match/case statement instead
loulecrivain Feb 25, 2025
720417b
add core tag processing
loulecrivain Feb 25, 2025
0516aec
add dhcp tag processing
loulecrivain Feb 25, 2025
d16c6b5
add unnumbered tag processing
loulecrivain Feb 25, 2025
769055a
add ethernet-ccc encap
loulecrivain Feb 25, 2025
c90e1fc
add specific management interface ip address processing
loulecrivain Feb 25, 2025
8444233
add vxlan termination processing
loulecrivain Feb 25, 2025
442751c
fixup! use match/case statement instead
loulecrivain Feb 25, 2025
b404803
fix eq dunder for AbstractNetboxType
loulecrivain Feb 25, 2025
ad90eaf
register sonderlocke tag as known
loulecrivain Feb 25, 2025
51dd7ad
add missing device properties
loulecrivain Feb 26, 2025
9f1ba51
fix enabled/disabled interface logic
loulecrivain Feb 26, 2025
98cc1be
fix horrible bug where child interfaces were wrongly processed
loulecrivain Feb 26, 2025
cd0f2fa
make description and mtu optional
loulecrivain Feb 26, 2025
990240c
make arp policer property completely optional
loulecrivain Feb 26, 2025
a89b934
fix ethernet-ccc encapsulation
loulecrivain Feb 26, 2025
abd74aa
fix missing properties for sub interfaces
loulecrivain Feb 26, 2025
7bf938f
fix test for sub interfaces
loulecrivain Feb 26, 2025
d1a7d26
output type only for root interfaces, not sub interfaces
loulecrivain Feb 26, 2025
51fb9ce
fix missing specific descriptions linked to some tags
loulecrivain Feb 26, 2025
107599c
fix condition for using ethernet-ccc encap
loulecrivain Feb 26, 2025
4f2412e
fix missing check for property
loulecrivain Feb 26, 2025
414a67d
fix test data missing __typename for tag
loulecrivain Feb 26, 2025
84c5167
add outer_tag feature
loulecrivain Feb 26, 2025
1f31afa
serial should be empty string instead of null
loulecrivain Feb 26, 2025
93b5d5e
fix crash, fix "unkown" tag
loulecrivain Feb 26, 2025
a88eb0a
add bmc to management interfaces for rtbrick
loulecrivain Feb 26, 2025
1791d25
fix missing feature process access tag
loulecrivain Feb 26, 2025
af23c2d
fix dhcp tag processing
loulecrivain Feb 26, 2025
c695919
fix interface associated type
loulecrivain Feb 26, 2025
682ef02
fix and simplify lag guard
loulecrivain Feb 26, 2025
73d8e09
fix missing mac address feature
loulecrivain Feb 26, 2025
00436bb
add some documentation on why so many getters
loulecrivain Feb 26, 2025
2036502
fix l2vpn termination test
loulecrivain Feb 26, 2025
7a81bda
remove unneeded code
loulecrivain Feb 26, 2025
c26af01
fix loopback addr restrictions
loulecrivain Feb 26, 2025
a1750cc
forbid configuring IPs directly on interfaces
loulecrivain Feb 26, 2025
3e35b88
rename my_ prefixed variables
loulecrivain Feb 27, 2025
b8719a6
simplify logic
loulecrivain Feb 27, 2025
a994bd4
add documentation
loulecrivain Feb 27, 2025
f1c86d7
simplify getters
loulecrivain Feb 27, 2025
0d3f7ad
add doc
loulecrivain Feb 27, 2025
7952de9
fix error message
loulecrivain Feb 27, 2025
c86b26c
simplify default route for mgmt interface
loulecrivain Feb 27, 2025
aebf531
fix error message
loulecrivain Feb 27, 2025
035c2e5
drop vrf_list
loulecrivain Feb 27, 2025
49e5f6e
add warning for misconfigured outer_tag
loulecrivain Feb 27, 2025
21ec23f
fix l2vpn terminations check
loulecrivain Feb 27, 2025
d65f59c
fix misnomer
loulecrivain Feb 27, 2025
1a3d4f4
delete unneeded else
loulecrivain Feb 27, 2025
9b5238f
l2vpn helper types
loulecrivain Feb 28, 2025
bf7d299
add encapsulation traits
loulecrivain Feb 28, 2025
8e99904
finish l2vpn type helpers
loulecrivain Feb 28, 2025
3621508
accept vlan and interface for mpls evpn l2vpn
loulecrivain Feb 28, 2025
195bb5b
format
loulecrivain Feb 28, 2025
2b7638b
fix test
loulecrivain Feb 28, 2025
ad07d39
re-add forgotten guard
loulecrivain Feb 28, 2025
bed5015
fix minimal required terminations number for any2any type
loulecrivain Feb 28, 2025
1e9ee91
enhance error messages
loulecrivain Feb 28, 2025
b1f292c
fix encap_type not optional
loulecrivain Feb 28, 2025
8865979
remove unneeded / buggy method
loulecrivain Feb 28, 2025
1aefadd
fix guard which was always false
loulecrivain Feb 28, 2025
61f2f74
fix VLANType L2VPN processing
loulecrivain Feb 28, 2025
b671485
add guard: only sub-interfaces must be processed for VRFs
loulecrivain Feb 28, 2025
69dbbf0
fix: mac addresses should only exist on sub interfaces
loulecrivain Feb 28, 2025
3b5a4a2
fix omitted native_vlan field case
loulecrivain Feb 28, 2025
b6dc863
cleanup dead code
loulecrivain Mar 3, 2025
aeaf54c
fix casing
loulecrivain Mar 3, 2025
7e89a73
documentation
loulecrivain Mar 3, 2025
72f6146
naming
loulecrivain Mar 3, 2025
644448b
use typevar instead of repeating
loulecrivain Mar 3, 2025
1496a4a
naming
loulecrivain Mar 3, 2025
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
13 changes: 3 additions & 10 deletions cosmo/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

from cosmo.clients.netbox import NetboxClient
from cosmo.log import info
from cosmo.serializer import RouterSerializer, SwitchSerializer, AbstractRecoverableError, RouterSerializerConfig

from cosmo.serializer import RouterSerializer, SwitchSerializer
from cosmo.common import AbstractRecoverableError



Expand Down Expand Up @@ -57,11 +57,6 @@ def noop(*args, **kwargs):

yaml.emitter.Emitter.process_tag = noop

for vrf in cosmo_data["vrf_list"]:
if len(vrf["export_targets"]) > 1 or len(vrf["import_targets"]) > 1:
warnings.warn(f"Currently we only support one import/export target per VRF. {vrf['name']} has {len(vrf['import_targets'])} import targets and {len(vrf['export_targets'])} export targets")
continue

for device in cosmo_data["device_list"]:

if 'fqdnSuffix' in cosmo_configuration:
Expand All @@ -77,9 +72,7 @@ def noop(*args, **kwargs):
content = None
try:
if device['name'] in cosmo_configuration['devices']['router']:

router_serializer_cfg = RouterSerializerConfig(cosmo_configuration.get("router_serializer_configuration", {}))
serializer = RouterSerializer(router_serializer_cfg, device, cosmo_data['l2vpn_list'], cosmo_data["vrf_list"], cosmo_data["loopbacks"])
serializer = RouterSerializer(device, cosmo_data['l2vpn_list'], cosmo_data["loopbacks"])
content = serializer.serialize()
elif device['name'] in cosmo_configuration['devices']['switch']:
serializer = SwitchSerializer(device)
Expand Down
12 changes: 12 additions & 0 deletions cosmo/abstractroutervisitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from abc import ABC

from cosmo.types import CosmoLoopbackType
from cosmo.visitors import AbstractNoopNetboxTypesVisitor


class AbstractRouterExporterVisitor(AbstractNoopNetboxTypesVisitor, ABC):
_vrf_key = "routing_instances"
_interfaces_key = "interfaces"
_mgmt_vrf_name = "MGMT-ROUTING-INSTANCE"
_l2circuits_key = "l2circuits"
_allowed_core_mtus = [9216, 9600, 9230, 9586, 9116]
127 changes: 97 additions & 30 deletions cosmo/clients/netbox_v4.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,26 @@ def _fetch_data(self, kwargs):
query_template = Template('''
query {
interface_list(filters: { tag: "bgp_cpe" }) {
__typename
id,
parent {
__typename
id,
connected_endpoints {
... on InterfaceType {
__typename
name
device {
primary_ip4 {
__typename
address
}
interfaces {
id
name
__typename
ip_addresses {
__typename
address
}
}
Expand Down Expand Up @@ -91,20 +99,37 @@ def _fetch_data(self, kwargs):
name: {starts_with: "lo"},
type: {exact:"loopback"}
}) {
__typename
name,
child_interfaces {
__typename
name,
vrf {
__typename
id
name
description
rd
export_targets {
__typename
name
}
import_targets {
__typename
name
}
},
ip_addresses {
__typename
address,
family {
__typename
value,
}
}
}
device{
__typename
name,
}
}
Expand All @@ -127,7 +152,9 @@ def _merge_into(self, data: object, query_data):
l_ipv6 = next(filter(lambda l: l['family']['value'] == 6, child_interface['ip_addresses']), None)
loopbacks[device_name] = {
"ipv4": l_ipv4['address'] if l_ipv4 else None,
"ipv6": l_ipv6['address'] if l_ipv6 else None
"ipv6": l_ipv6['address'] if l_ipv6 else None,
"__typename": "CosmoLoopbackType",
"device": device_name,
}

return {
Expand All @@ -141,20 +168,61 @@ def _fetch_data(self, kwargs):
query_template = Template('''
query {
l2vpn_list (filters: {name: {starts_with: "WAN: "}}) {
__typename
id
name
type
identifier
terminations {
__typename
id
assigned_object {
__typename
... on VLANType {
__typename
id
name
interfaces_as_tagged {
id
name
__typename
device {
__typename
id
name
}
}
interfaces_as_untagged {
id
name
__typename
device {
__typename
id
name
}
}
}
... on InterfaceType {
__typename
id
name
custom_fields
untagged_vlan {
__typename
id
name
vid
}
tagged_vlans {
__typename
id
name
vid
}
device {
__typename
id
name
}
}
Expand All @@ -173,34 +241,6 @@ def _merge_into(self, data: object, query_data):
}


class VrfDataQuery(ParallelQuery):
def _fetch_data(self, kwargs):
query_template = Template('''
query {
vrf_list {
id
name
description
rd
export_targets {
name
}
import_targets {
name
}
}
}
''')

return self.client.query(query_template.substitute())['data']

def _merge_into(self, data: dict, query_data):
return {
**data,
**query_data,
}


class StaticRouteQuery(ParallelQuery):

def _fetch_data(self, kwargs):
Expand All @@ -211,6 +251,8 @@ def _merge_into(self, data: dict, query_data):
for d in data['device_list']:
device_static_routes = list(filter(lambda sr: str(sr['device']['id']) == d['id'], query_data))
d['staticroute_set'] = device_static_routes
for e in d['staticroute_set']:
e['__typename'] = "CosmoStaticRouteType"

return data

Expand Down Expand Up @@ -251,24 +293,30 @@ def _fetch_data(self, kwargs):
device_list(filters: {
name: { i_exact: $device },
}) {
__typename
id
name
serial

device_type {
__typename
slug
}
platform {
__typename
manufacturer {
__typename
slug
}
slug
}
primary_ip4 {
__typename
address
}

interfaces {
__typename
id
name
enabled
Expand All @@ -277,31 +325,51 @@ def _fetch_data(self, kwargs):
mtu
description
vrf {
__typename
id
name
description
rd
export_targets {
__typename
name
}
import_targets {
__typename
name
}
}
lag {
__typename
id
name
}
ip_addresses {
__typename
address
}
untagged_vlan {
__typename
id
name
vid
}
tagged_vlans {
__typename
id
name
vid
}
tags {
__typename
name
slug
}
parent {
__typename
id
mtu
name
}
custom_fields
}
Expand Down Expand Up @@ -342,7 +410,6 @@ def get_data(self, device_config):
)

queries.extend([
VrfDataQuery(self.client, device_list=device_list),
L2VPNDataQuery(self.client, device_list=device_list),
StaticRouteQuery(self.client, device_list=device_list),
DeviceMACQuery(self.client, device_list=device_list),
Expand Down
24 changes: 24 additions & 0 deletions cosmo/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import abc

class AbstractRecoverableError(Exception, abc.ABC):
pass


class DeviceSerializationError(AbstractRecoverableError):
pass


class InterfaceSerializationError(AbstractRecoverableError):
pass


# next() can raise StopIteration, so that's why I use this function
def head(l):
return None if not l else l[0]

def deepsort(e):
if isinstance(e, list):
return sorted(deepsort(v) for v in e)
elif isinstance(e, dict):
return {k: deepsort(v) for k, v in e.items()}
return e


def without_keys(d, keys) -> dict:
if type(keys) != list:
keys = [keys]
return {k: v for k,v in d.items() if k not in keys}
25 changes: 25 additions & 0 deletions cosmo/cperoutervisitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from functools import singledispatchmethod

from cosmo.types import IPAddressType, DeviceType
from cosmo.visitors import AbstractNoopNetboxTypesVisitor


class CpeRouterExporterVisitor(AbstractNoopNetboxTypesVisitor):
"""
This visitor creates a list of networks which are exported from the router
via unnumbered bgp. We allow all configured IP networks on a CPE to be
exported. By definition the primary IP is our management IP and this IP
should not be allowed to be exported via BGP from the router.
"""

@singledispatchmethod
def accept(self, o):
return super().accept(o)

@accept.register
def _(self, o: IPAddressType):
primary_ip4 = o.getParent(DeviceType)["primary_ip4"]
if primary_ip4 and primary_ip4.getIPAddress() == o.getIPAddress():
return # skip
ip_interface = o.getIPInterfaceObject()
return type(ip_interface), ip_interface.network.with_prefixlen
Loading