From 0a19e553c3866ebf8f1089d1571f7e843c57ac29 Mon Sep 17 00:00:00 2001 From: Sebastian Lohff Date: Wed, 26 Aug 2026 13:10:07 +0200 Subject: [PATCH 1/2] [config-gen] Refactor lb handling into own method Loadbalancer requires some more handling on device discovery. As we need to do something similar for manila we move the code for loadbalancers into its own method to unclutter get_connected_devices() a bit. --- networking_ccloud/tools/netbox_config_gen.py | 107 ++++++++++--------- 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/networking_ccloud/tools/netbox_config_gen.py b/networking_ccloud/tools/netbox_config_gen.py index e01bf9d1..e6fc0af2 100644 --- a/networking_ccloud/tools/netbox_config_gen.py +++ b/networking_ccloud/tools/netbox_config_gen.py @@ -345,56 +345,9 @@ 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[^-]+)-(?:cluster-)?(?P\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) if not far_device.cluster: LOG.debug(" ??? --> Ignoring switch %s interface %s device %s with missing cluster config", @@ -414,6 +367,60 @@ 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[^-]+)-(?:cluster-)?(?P\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 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) From f98a8458f55aca32aa270082354d0df9ef670e7a Mon Sep 17 00:00:00 2001 From: Sebastian Lohff Date: Wed, 26 Aug 2026 13:12:10 +0200 Subject: [PATCH 2/2] [config-gen] Support manila shares With manila the individual node servers are directly connected to the switch in NetBox. Neither the devices nor the parent device have any cluster config whatsoever. To find all devices we need to find the parent device via parent_bay and then look at all the devicebays from there (close, but not similar to how we handle loadbalancers). We also filter on the manila tag being present on the parent device. --- networking_ccloud/tools/netbox_config_gen.py | 53 ++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/networking_ccloud/tools/netbox_config_gen.py b/networking_ccloud/tools/netbox_config_gen.py index e6fc0af2..4a07f14d 100644 --- a/networking_ccloud/tools/netbox_config_gen.py +++ b/networking_ccloud/tools/netbox_config_gen.py @@ -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 { @@ -348,6 +367,7 @@ def get_connected_devices(self, nb_switches: list[Munch]) -> dict[int, Munch]: # 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", @@ -421,6 +441,30 @@ def _handle_lb_device_cluster(self, far_device): ] }) + 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) @@ -525,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 _: