diff --git a/networking_ccloud/tools/netbox_config_gen.py b/networking_ccloud/tools/netbox_config_gen.py index e01bf9d1..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 { @@ -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[^-]+)-(?: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) + 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", @@ -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[^-]+)-(?: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 _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) @@ -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 _: