Commit 06ada740 authored by Mohamad Rahhal's avatar Mohamad Rahhal
Browse files

SpineLeaf:

 -BGP Support issues
parent ef99684d
Loading
Loading
Loading
Loading
+0 −0

File mode changed from 100644 to 100755.

+111 −27
Original line number Diff line number Diff line
@@ -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())
@@ -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
@@ -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:
@@ -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):
@@ -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:
@@ -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),
@@ -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:
+43 −22
Original line number Diff line number Diff line
@@ -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
@@ -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"],
@@ -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"
            })
        )
+6 −0
Original line number Diff line number Diff line
@@ -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:
@@ -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,
+1 −0
Original line number Diff line number Diff line
@@ -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', []),