Skip to content
Open
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
160 changes: 110 additions & 50 deletions networking_ccloud/tools/netbox_config_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,25 @@ class ConfigSchemeException(ConfigException):
name
}
}

parent_bay {
name
device {
id
name
tags {
slug
}
devicebays {
id
installed_device {
id
name
}
}
}
}

devicebays {
installed_device {
cluster {
Expand Down Expand Up @@ -345,56 +364,10 @@ def get_connected_devices(self, nb_switches: list[Munch]) -> dict[int, Munch]:
nb_switch.name, iface.name, far_device.name, far_device.tenant.slug)
continue
LOG.debug(" +++ Found device %s on %s/%s", far_device.name, nb_switch.name, iface.name)
# FIXME: additional filer filtering for parent device in original generator

if far_device.devicebays and not far_device.cluster and \
far_device.role.slug in ('loadbalancer',):
# loadbalancer devices have device bays with multiple clusters (instead of a single cluster) -
# to handle this we create an artificial cluster object containing all subclusters
# (they would need to be merged anyway)
binding_hosts = set()
cluster_devices = set()
cluster_names = set()
cluster_types = set()
for bay in far_device.devicebays:
cluster_def = bay.installed_device.cluster
cluster_types.add(cluster_def.type.slug)

# add binding host
# e.g. qa-de-1-lb414-cluster-01 --> lb414-01
# NOTE(seba): we are just ignoring the region, we trust it's right as it's connected to
# a switch returned by get_switch_list()
m = re.match(r"^[a-z]+-[a-z]+-\d+-(?P<name>[^-]+)-(?:cluster-)?(?P<num>\d+)$", cluster_def.name)
if not m:
LOG.warning("Cluster %s of device %s did not match any known pattern, skipping it",
cluster_def.name, far_device.name)
continue
cluster_names.add(m['name'])

binding_host = f"{m['name']}-{m['num']}"
binding_hosts.add(binding_host)

# add devices
for device in cluster_def.devices:
device = device.parent_bay.device
cluster_devices.add((device.id, device.name))

if cluster_devices and binding_hosts:
if len(cluster_names) > 1 or len(cluster_types) > 1:
LOG.warning("Device %s has inconsistent cluster name/types (%s, %s), selecting one",
far_device.name, cluster_names, cluster_types)

far_device.cluster = Munch.fromDict({
"name": list(cluster_names)[0],
"binding_hosts": sorted(binding_hosts),
"type": {
"slug": list(cluster_types)[0],
},
"devices": [
{"id": device_id, "name": device_name}
for (device_id, device_name) in sorted(cluster_devices, key=itemgetter(1))
]
})

# generate clusters for devices that have different configuration
self._handle_lb_device_cluster(far_device)
self._handle_manila_device_cluster(far_device)

if not far_device.cluster:
LOG.debug(" ??? --> Ignoring switch %s interface %s device %s with missing cluster config",
Expand All @@ -414,6 +387,84 @@ def get_connected_devices(self, nb_switches: list[Munch]) -> dict[int, Munch]:

return devices

def _handle_lb_device_cluster(self, far_device):
# loadbalancer devices have device bays with multiple clusters (instead of a single cluster) -
# to handle this we create an artificial cluster object containing all subclusters
# (they would need to be merged anyway)
if far_device.cluster:
return

if far_device.role.slug != 'loadbalancer' or not far_device.devicebays:
return

binding_hosts = set()
cluster_devices = set()
cluster_names = set()
cluster_types = set()
for bay in far_device.devicebays:
cluster_def = bay.installed_device.cluster
cluster_types.add(cluster_def.type.slug)

# add binding host
# e.g. qa-de-1-lb414-cluster-01 --> lb414-01
# NOTE(seba): we are just ignoring the region, we trust it's right as it's connected to
# a switch returned by get_switch_list()
m = re.match(r"^[a-z]+-[a-z]+-\d+-(?P<name>[^-]+)-(?:cluster-)?(?P<num>\d+)$", cluster_def.name)
if not m:
LOG.warning("Cluster %s of device %s did not match any known pattern, skipping it",
cluster_def.name, far_device.name)
continue
cluster_names.add(m['name'])

binding_host = f"{m['name']}-{m['num']}"
binding_hosts.add(binding_host)

# add devices
for device in cluster_def.devices:
device = device.parent_bay.device
cluster_devices.add((device.id, device.name))

if cluster_devices and binding_hosts:
if len(cluster_names) > 1 or len(cluster_types) > 1:
LOG.warning("Device %s has inconsistent cluster name/types (%s, %s), selecting one",
far_device.name, cluster_names, cluster_types)

far_device.cluster = Munch.fromDict({
"name": list(cluster_names)[0],
"binding_hosts": sorted(binding_hosts),
"type": {
"slug": list(cluster_types)[0],
},
"devices": [
{"id": device_id, "name": device_name}
for (device_id, device_name) in sorted(cluster_devices, key=itemgetter(1))
]
})

def _handle_manila_device_cluster(self, far_device):
# manila devices have a parent, that has device_bays containing the cluster config,
# but no cluster objects attached in netbox. we make sure the parent device is tagged
# with "manila" and then generate an artificial cluster object

if far_device.cluster:
return
if far_device.role.slug != 'filer' or not far_device.parent_bay or not far_device.parent_bay.device:
return

parent_device = far_device.parent_bay.device
if not any(tag.slug == 'manila' for tag in parent_device.tags):
LOG.debug("Device %s is of type filer but parent device %s does not have a manila tag",
far_device.name, parent_device.name)
return

far_device.cluster = Munch.fromDict({
"name": f"manila-share-netapp-{parent_device.name}",
"type": {
"slug": "manila",
},
"devices": [bay.installed_device for bay in parent_device.devicebays],
})

def make_hostgroups(self, nb_switches: list[Munch]) -> list[conf.Hostgroup]:
cluster_hgs = self.make_cluster_hostgroups(nb_switches)
interconnect_hgs = self.make_interconnection_hostgroups(nb_switches)
Expand Down Expand Up @@ -518,6 +569,15 @@ def switch_iface_key(iface: Munch) -> tuple[str, str | None]:
members=[d.name for d in cluster.devices],
)

hostgroups.append(hg)
hostgroups.extend(gen_device_bindings_for_cluster(cluster, direct_binding=True))
case "manila":
hg = conf.Hostgroup(
binding_hosts=[cluster.name],
metagroup=True,
members=[d.name for d in cluster.devices],
)

hostgroups.append(hg)
hostgroups.extend(gen_device_bindings_for_cluster(cluster, direct_binding=True))
case _:
Expand Down
Loading