Loading scripts/show_logs_spine_leaf.sh 100644 → 100755 +0 −0 File mode changed from 100644 to 100755. View file src/common/tools/descriptor/Loader.py +111 −27 Original line number Diff line number Diff line Loading @@ -415,7 +415,6 @@ class DescriptorLoader: def _resolve_device_uuid(self, device_identifier: str, device_map: Optional[Dict[str, str]] = None) -> Optional[str]: if not device_identifier: return None if device_map is None: device_map = {} response = self.__ctx_cli.ListDevices(Empty()) Loading @@ -426,7 +425,6 @@ class DescriptorLoader: device_map[device_uuid] = device_uuid if device.name: device_map[device.name] = device_uuid return device_map.get(device_identifier) @staticmethod Loading @@ -445,6 +443,19 @@ class DescriptorLoader: return '' return '' @staticmethod def _slugify_label(label: str) -> str: slug = ''.join(c.lower() if c.isalnum() else '-' for c in str(label or '')).strip('-') while '--' in slug: slug = slug.replace('--', '-') return slug @staticmethod def _strip_prefix(address: str) -> str: if not address: return '' return str(address).split('/')[0] def _build_spine_leaf_hints(self, fabric_name: str, device_uuid: str) -> Dict[str, Any]: logical_fabric = None for fabric in self.__logical_resources: Loading Loading @@ -478,10 +489,19 @@ class DescriptorLoader: interfaces = device_interfaces.get('interfaces', device_interfaces.get('endpoints', {})) hint_interfaces: List[Dict[str, str]] = [] underlay_ip = '' remote_address = '' remote_as = '' session_name = '' inferred_sessions: List[Dict[str, Any]] = [] seen_sessions = set() first_underlay_ip = '' first_remote_address = '' first_remote_as = '' first_session_name = '' def _append_session(session: Dict[str, Any]) -> None: session_key = json.dumps(session, sort_keys=True) if session_key in seen_sessions: return seen_sessions.add(session_key) inferred_sessions.append(session) for interface_name, interface_data in interfaces.items(): if isinstance(interface_data, dict): Loading @@ -498,20 +518,30 @@ class DescriptorLoader: or ip_value.endswith('/32') or 'loopback' in comment.lower() ) if not is_loopback and not underlay_ip: underlay_ip = ip_value if not is_loopback: target_device = str(interface_data.get('link_to_device', '')) # Resolve remote_as and remote_address from link_to_device lookup # Resolve remote_as and remote_address from link_to_device lookup. # This is done per-interface so a single device can contribute # multiple distinct BGP sessions. remote_as = str(interface_data.get('remote_as', '')) remote_address = str(interface_data.get('remote_address', '')) session_name = str(interface_data.get('bgp_session_name', '')) if not session_name: comment_slug = self._slugify_label(comment) if comment_slug.startswith('to-'): session_name = comment_slug[3:] if target_device: # Resolve remote AS: explicit in interface entry takes precedence, # otherwise look up the ASN allocated to the target device. remote_as = str(interface_data.get('remote_as', '')) or asn_by_device.get(target_device, '') if not remote_as: remote_as = asn_by_device.get(target_device, '') # Resolve remote address: # 1) use explicit remote_address in interface data if present # 2) else, use the target device's allocated ip (strip CIDR to host) # 3) else, derive peer from local ip_value remote_address = str(interface_data.get('remote_address', '')) if not remote_address: remote_ip_cidr = ip_by_device.get(target_device, '') if remote_ip_cidr: Loading @@ -523,15 +553,32 @@ class DescriptorLoader: if not remote_address: remote_address = self._derive_peer_ip(ip_value) if not session_name: target_name = name_by_device.get(target_device, '') if target_name: slug = ''.join(c.lower() if c.isalnum() else '-' for c in target_name).strip('-') if slug: session_name = f'to-{slug}' target_slug = self._slugify_label(target_name) if target_slug: session_name = target_slug else: # No link_to_device specified; use explicit interface data or derive remote_address = str(interface_data.get('remote_address', '')) or self._derive_peer_ip(ip_value) remote_as = str(interface_data.get('remote_as', '')) if not remote_address: remote_address = self._derive_peer_ip(ip_value) if ip_value and remote_address and remote_as: inferred_session = { 'local.address': self._strip_prefix(ip_value), 'remote.address': remote_address, 'remote.as': int(remote_as) if str(remote_as).isdigit() else remote_as, } if session_name: inferred_session['name'] = session_name _append_session(inferred_session) if not first_underlay_ip: first_underlay_ip = ip_value first_remote_address = remote_address first_remote_as = remote_as first_session_name = session_name else: hint_interfaces.append({ 'interface': str(interface_name), Loading @@ -542,14 +589,51 @@ class DescriptorLoader: hints: Dict[str, Any] = {} if hint_interfaces: hints['interfaces'] = hint_interfaces if underlay_ip: hints['underlay_ip'] = underlay_ip if remote_address: hints['remote_address'] = remote_address if remote_as: hints['remote_as'] = remote_as if session_name: hints['bgp_session_name'] = session_name if first_underlay_ip: hints['underlay_ip'] = first_underlay_ip if first_remote_address: hints['remote_address'] = first_remote_address if first_remote_as: hints['remote_as'] = first_remote_as if first_session_name: hints['bgp_session_name'] = first_session_name if inferred_sessions: hints['bgp_sessions'] = inferred_sessions # Also collect any explicit /network_instances/bgp_session entries from # the device descriptor so spine can declare peers even when the # session list is only present in the original descriptor payload. bgp_sessions: List[Dict[str, Any]] = list(inferred_sessions) seen_sessions = {json.dumps(session, sort_keys=True) for session in bgp_sessions} try: for dev in list(self.__devices_config or []) + list(self.__devices or []): dev_id = '' device_id = dev.get('device_id', {}) if isinstance(device_id, dict): device_uuid_field = device_id.get('device_uuid', {}) if isinstance(device_uuid_field, dict): dev_id = str(device_uuid_field.get('uuid', '')) if dev_id != device_uuid: continue for cfg in dev.get('device_config', {}).get('config_rules', []): custom = cfg.get('custom', {}) key = custom.get('resource_key') if key == '/network_instances/bgp_session': raw = custom.get('resource_value') try: session = json.loads(raw) if isinstance(raw, str) else raw if isinstance(session, dict): session_key = json.dumps(session, sort_keys=True) if session_key not in seen_sessions: seen_sessions.add(session_key) bgp_sessions.append(session) except Exception: continue except Exception: pass if bgp_sessions: hints['bgp_sessions'] = bgp_sessions return hints def _process_spine_leaf(self) -> None: Loading src/spine_leaf/scripts/fabric_config_builder.py +43 −22 Original line number Diff line number Diff line Loading @@ -117,6 +117,25 @@ class FabricConfigBuilder: )) # ---- BGP sessions ---- if role == "spine": sessions = device.get('bgp_sessions', []) or [] if sessions: for s in sessions: try: local_ip = self._strip_prefix(str(s.get('local.address') or s.get('local_address') or '')) remote_ip = self._strip_prefix(str(s.get('remote.address') or s.get('remote_address') or '')) remote_as = s.get('remote.as') or s.get('remote_as') or s.get('remote_asn') name = s.get('name') or s.get('peer') or None if local_ip and remote_ip and remote_as is not None: rules.append(self._bgp_session( name=name or f'peer-{remote_ip}', local_ip=local_ip, remote_ip=remote_ip, remote_as=remote_as, )) except Exception: continue else: leaves = inventory.get("leaves", []) for leaf in leaves: # prefer lists, then single value, then loopback Loading @@ -125,11 +144,15 @@ class FabricConfigBuilder: local_underlays = device.get('underlay_ips', []) remote_underlays = leaf.get('underlay_ips', []) if local_underlays and remote_underlays: local_ip = local_underlays[0] remote_ip = remote_underlays[0] local_ip = self._strip_prefix(local_underlays[0]) remote_ip = self._strip_prefix(remote_underlays[0]) else: local_ip = device.get('underlay_ip') or (local_underlays[0] if local_underlays else None) or lo remote_ip = leaf.get('underlay_ip') or (remote_underlays[0] if remote_underlays else None) or leaf.get('loopback_ip') local_ip = self._strip_prefix( device.get('underlay_ip') or (local_underlays[0] if local_underlays else None) or lo ) remote_ip = self._strip_prefix( leaf.get('underlay_ip') or (remote_underlays[0] if remote_underlays else None) or leaf.get('loopback_ip') ) rules.append(self._bgp_session( name=leaf["device_id"], Loading Loading @@ -199,8 +222,6 @@ class FabricConfigBuilder: "remote.address": remote_ip, "remote.as": int(remote_as), "local.role": "ebgp", "routing-table": "vrf-dataplane", "multihop": "yes", "afi": "ip,l2vpn" "routing-table": "vrf-dataplane" }) ) src/spine_leaf/service/SpineLeafServicerImpl.py +6 −0 Original line number Diff line number Diff line Loading @@ -104,6 +104,10 @@ class SpineLeafServicerImpl(SpineLeafServicer): if session_name: resources['bgp_session_name'] = session_name bgp_sessions = hints.get('bgp_sessions', []) if isinstance(bgp_sessions, list) and bgp_sessions: resources['bgp_sessions'] = bgp_sessions @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DefineSpineLeafFabric(self, request: FabricSettings, context: grpc.ServicerContext) -> FabricDefinitionResponse: Loading @@ -125,6 +129,8 @@ class SpineLeafServicerImpl(SpineLeafServicer): if config is None: return ConfigSetting() self._apply_hints(resources, self._extract_request_hints(request)) try: ok, msg = self.db.set_device_role( request.fabric_id, Loading src/spine_leaf/state_store.py +1 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,7 @@ class SpineLeafStateStore: 'remote_as': resources.get('remote_as'), 'bgp_remote_address': resources.get('bgp_remote_address'), 'bgp_session_name': resources.get('bgp_session_name'), 'bgp_sessions': resources.get('bgp_sessions', []), 'interface_ips': resources.get('interface_ips', []), 'vlans': resources.get('vlans', []), 'vnis': resources.get('vnis', []), Loading Loading
scripts/show_logs_spine_leaf.sh 100644 → 100755 +0 −0 File mode changed from 100644 to 100755. View file
src/common/tools/descriptor/Loader.py +111 −27 Original line number Diff line number Diff line Loading @@ -415,7 +415,6 @@ class DescriptorLoader: def _resolve_device_uuid(self, device_identifier: str, device_map: Optional[Dict[str, str]] = None) -> Optional[str]: if not device_identifier: return None if device_map is None: device_map = {} response = self.__ctx_cli.ListDevices(Empty()) Loading @@ -426,7 +425,6 @@ class DescriptorLoader: device_map[device_uuid] = device_uuid if device.name: device_map[device.name] = device_uuid return device_map.get(device_identifier) @staticmethod Loading @@ -445,6 +443,19 @@ class DescriptorLoader: return '' return '' @staticmethod def _slugify_label(label: str) -> str: slug = ''.join(c.lower() if c.isalnum() else '-' for c in str(label or '')).strip('-') while '--' in slug: slug = slug.replace('--', '-') return slug @staticmethod def _strip_prefix(address: str) -> str: if not address: return '' return str(address).split('/')[0] def _build_spine_leaf_hints(self, fabric_name: str, device_uuid: str) -> Dict[str, Any]: logical_fabric = None for fabric in self.__logical_resources: Loading Loading @@ -478,10 +489,19 @@ class DescriptorLoader: interfaces = device_interfaces.get('interfaces', device_interfaces.get('endpoints', {})) hint_interfaces: List[Dict[str, str]] = [] underlay_ip = '' remote_address = '' remote_as = '' session_name = '' inferred_sessions: List[Dict[str, Any]] = [] seen_sessions = set() first_underlay_ip = '' first_remote_address = '' first_remote_as = '' first_session_name = '' def _append_session(session: Dict[str, Any]) -> None: session_key = json.dumps(session, sort_keys=True) if session_key in seen_sessions: return seen_sessions.add(session_key) inferred_sessions.append(session) for interface_name, interface_data in interfaces.items(): if isinstance(interface_data, dict): Loading @@ -498,20 +518,30 @@ class DescriptorLoader: or ip_value.endswith('/32') or 'loopback' in comment.lower() ) if not is_loopback and not underlay_ip: underlay_ip = ip_value if not is_loopback: target_device = str(interface_data.get('link_to_device', '')) # Resolve remote_as and remote_address from link_to_device lookup # Resolve remote_as and remote_address from link_to_device lookup. # This is done per-interface so a single device can contribute # multiple distinct BGP sessions. remote_as = str(interface_data.get('remote_as', '')) remote_address = str(interface_data.get('remote_address', '')) session_name = str(interface_data.get('bgp_session_name', '')) if not session_name: comment_slug = self._slugify_label(comment) if comment_slug.startswith('to-'): session_name = comment_slug[3:] if target_device: # Resolve remote AS: explicit in interface entry takes precedence, # otherwise look up the ASN allocated to the target device. remote_as = str(interface_data.get('remote_as', '')) or asn_by_device.get(target_device, '') if not remote_as: remote_as = asn_by_device.get(target_device, '') # Resolve remote address: # 1) use explicit remote_address in interface data if present # 2) else, use the target device's allocated ip (strip CIDR to host) # 3) else, derive peer from local ip_value remote_address = str(interface_data.get('remote_address', '')) if not remote_address: remote_ip_cidr = ip_by_device.get(target_device, '') if remote_ip_cidr: Loading @@ -523,15 +553,32 @@ class DescriptorLoader: if not remote_address: remote_address = self._derive_peer_ip(ip_value) if not session_name: target_name = name_by_device.get(target_device, '') if target_name: slug = ''.join(c.lower() if c.isalnum() else '-' for c in target_name).strip('-') if slug: session_name = f'to-{slug}' target_slug = self._slugify_label(target_name) if target_slug: session_name = target_slug else: # No link_to_device specified; use explicit interface data or derive remote_address = str(interface_data.get('remote_address', '')) or self._derive_peer_ip(ip_value) remote_as = str(interface_data.get('remote_as', '')) if not remote_address: remote_address = self._derive_peer_ip(ip_value) if ip_value and remote_address and remote_as: inferred_session = { 'local.address': self._strip_prefix(ip_value), 'remote.address': remote_address, 'remote.as': int(remote_as) if str(remote_as).isdigit() else remote_as, } if session_name: inferred_session['name'] = session_name _append_session(inferred_session) if not first_underlay_ip: first_underlay_ip = ip_value first_remote_address = remote_address first_remote_as = remote_as first_session_name = session_name else: hint_interfaces.append({ 'interface': str(interface_name), Loading @@ -542,14 +589,51 @@ class DescriptorLoader: hints: Dict[str, Any] = {} if hint_interfaces: hints['interfaces'] = hint_interfaces if underlay_ip: hints['underlay_ip'] = underlay_ip if remote_address: hints['remote_address'] = remote_address if remote_as: hints['remote_as'] = remote_as if session_name: hints['bgp_session_name'] = session_name if first_underlay_ip: hints['underlay_ip'] = first_underlay_ip if first_remote_address: hints['remote_address'] = first_remote_address if first_remote_as: hints['remote_as'] = first_remote_as if first_session_name: hints['bgp_session_name'] = first_session_name if inferred_sessions: hints['bgp_sessions'] = inferred_sessions # Also collect any explicit /network_instances/bgp_session entries from # the device descriptor so spine can declare peers even when the # session list is only present in the original descriptor payload. bgp_sessions: List[Dict[str, Any]] = list(inferred_sessions) seen_sessions = {json.dumps(session, sort_keys=True) for session in bgp_sessions} try: for dev in list(self.__devices_config or []) + list(self.__devices or []): dev_id = '' device_id = dev.get('device_id', {}) if isinstance(device_id, dict): device_uuid_field = device_id.get('device_uuid', {}) if isinstance(device_uuid_field, dict): dev_id = str(device_uuid_field.get('uuid', '')) if dev_id != device_uuid: continue for cfg in dev.get('device_config', {}).get('config_rules', []): custom = cfg.get('custom', {}) key = custom.get('resource_key') if key == '/network_instances/bgp_session': raw = custom.get('resource_value') try: session = json.loads(raw) if isinstance(raw, str) else raw if isinstance(session, dict): session_key = json.dumps(session, sort_keys=True) if session_key not in seen_sessions: seen_sessions.add(session_key) bgp_sessions.append(session) except Exception: continue except Exception: pass if bgp_sessions: hints['bgp_sessions'] = bgp_sessions return hints def _process_spine_leaf(self) -> None: Loading
src/spine_leaf/scripts/fabric_config_builder.py +43 −22 Original line number Diff line number Diff line Loading @@ -117,6 +117,25 @@ class FabricConfigBuilder: )) # ---- BGP sessions ---- if role == "spine": sessions = device.get('bgp_sessions', []) or [] if sessions: for s in sessions: try: local_ip = self._strip_prefix(str(s.get('local.address') or s.get('local_address') or '')) remote_ip = self._strip_prefix(str(s.get('remote.address') or s.get('remote_address') or '')) remote_as = s.get('remote.as') or s.get('remote_as') or s.get('remote_asn') name = s.get('name') or s.get('peer') or None if local_ip and remote_ip and remote_as is not None: rules.append(self._bgp_session( name=name or f'peer-{remote_ip}', local_ip=local_ip, remote_ip=remote_ip, remote_as=remote_as, )) except Exception: continue else: leaves = inventory.get("leaves", []) for leaf in leaves: # prefer lists, then single value, then loopback Loading @@ -125,11 +144,15 @@ class FabricConfigBuilder: local_underlays = device.get('underlay_ips', []) remote_underlays = leaf.get('underlay_ips', []) if local_underlays and remote_underlays: local_ip = local_underlays[0] remote_ip = remote_underlays[0] local_ip = self._strip_prefix(local_underlays[0]) remote_ip = self._strip_prefix(remote_underlays[0]) else: local_ip = device.get('underlay_ip') or (local_underlays[0] if local_underlays else None) or lo remote_ip = leaf.get('underlay_ip') or (remote_underlays[0] if remote_underlays else None) or leaf.get('loopback_ip') local_ip = self._strip_prefix( device.get('underlay_ip') or (local_underlays[0] if local_underlays else None) or lo ) remote_ip = self._strip_prefix( leaf.get('underlay_ip') or (remote_underlays[0] if remote_underlays else None) or leaf.get('loopback_ip') ) rules.append(self._bgp_session( name=leaf["device_id"], Loading Loading @@ -199,8 +222,6 @@ class FabricConfigBuilder: "remote.address": remote_ip, "remote.as": int(remote_as), "local.role": "ebgp", "routing-table": "vrf-dataplane", "multihop": "yes", "afi": "ip,l2vpn" "routing-table": "vrf-dataplane" }) )
src/spine_leaf/service/SpineLeafServicerImpl.py +6 −0 Original line number Diff line number Diff line Loading @@ -104,6 +104,10 @@ class SpineLeafServicerImpl(SpineLeafServicer): if session_name: resources['bgp_session_name'] = session_name bgp_sessions = hints.get('bgp_sessions', []) if isinstance(bgp_sessions, list) and bgp_sessions: resources['bgp_sessions'] = bgp_sessions @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DefineSpineLeafFabric(self, request: FabricSettings, context: grpc.ServicerContext) -> FabricDefinitionResponse: Loading @@ -125,6 +129,8 @@ class SpineLeafServicerImpl(SpineLeafServicer): if config is None: return ConfigSetting() self._apply_hints(resources, self._extract_request_hints(request)) try: ok, msg = self.db.set_device_role( request.fabric_id, Loading
src/spine_leaf/state_store.py +1 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,7 @@ class SpineLeafStateStore: 'remote_as': resources.get('remote_as'), 'bgp_remote_address': resources.get('bgp_remote_address'), 'bgp_session_name': resources.get('bgp_session_name'), 'bgp_sessions': resources.get('bgp_sessions', []), 'interface_ips': resources.get('interface_ips', []), 'vlans': resources.get('vlans', []), 'vnis': resources.get('vnis', []), Loading