Loading src/spine_leaf/scripts/fabric_config_builder.py 0 → 100644 +114 −0 Original line number Diff line number Diff line # Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from typing import List, Tuple class FabricConfigBuilder: def __init__(self, state_store): self.store = state_store def build(self, fabric_id: str, device_id: str) -> List[Tuple[str, str]]: rules = [] inventory = self.store.get_fabric_inventory(fabric_id) device = self.store.get_device_role(fabric_id, device_id) if not device: return rules role = device["role"] asn = device["asn"] lo = device["loopback_ip"] # ---- Loopback IP ---- rules.append(( "/interfaces/ip", json.dumps({ "address": f"{lo}/32", "interface": "lo", "comment": f"{role} loopback" }) )) # ---- BGP Instance ---- rules.append(( "/network_instances/bgp_instance", json.dumps({ "name": "default", "as": asn, "router_id": lo }) )) # ---- VXLAN (leaf only) ---- if role in ("leaf", "gateway"): vni = device["vnis"][0] rules.append(( "/interfaces/vxlan", json.dumps({ "name": f"vxlan{vni}", "vni": int(vni), "local-address": lo, "port": 4789 }) )) # ---- BGP sessions ---- if role == "spine": for leaf in inventory["leaves"]: rules.append(self._bgp_session( name=leaf["device_id"], local_ip=lo, remote_ip=leaf["loopback_ip"], remote_as=leaf["asn"] )) elif role in ("leaf", "gateway"): for spine in inventory["spines"]: rules.append(self._bgp_session( name=spine["device_id"], local_ip=lo, remote_ip=spine["loopback_ip"], remote_as=spine["asn"] )) # ---- EVPN (leaf only) ---- if role in ("leaf", "gateway"): rules.append(( "/network_instances/bgp_evpn", json.dumps({ "name": "evpn-vni10", "instance": "default", "vni": int(vni) }) )) return rules def _bgp_session(self, name, local_ip, remote_ip, remote_as): return ( "/network_instances/bgp_session", json.dumps({ "name": name, "instance": "default", "local.address": local_ip, "remote.address": remote_ip, "remote.as": int(remote_as), "local.role": "ebgp", "routing-table": "vrf-dataplane", "multihop": "yes", "afi": "ip,l2vpn" }) ) src/spine_leaf/scripts/resource_allocator.py +13 −11 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ import logging from typing import Dict, List, Optional, Tuple as TypingTuple from networkx import config from common.proto.logical_resources_pb2 import ( FabricId, ResourceReservation, Loading @@ -24,15 +26,14 @@ from common.proto.logical_resources_pb2 import ( Tuple as LRTuple, ) from common.proto.spineleaf_pb2 import ConfigSetting from logical_resources_client.client.logicalresources import logicalresourcesClient from logical_resources_client.client.logicalresources import LogicalResourceClient LOGGER = logging.getLogger(__name__) class ResourceAllocator: def __init__(self, logical_resource_client=None) -> None: def __init__(self) -> None: super().__init__() self.logical_resource_client = logical_resource_client or logicalresourcesClient() self.logical_resource_client = LogicalResourceClient() def _pick_first_available(self, resource_reply) -> Optional[str]: for entry in resource_reply.resources: Loading @@ -51,15 +52,18 @@ class ResourceAllocator: reply = self.logical_resource_client.ReserveResource(request) return bool(getattr(reply, 'success', False)), str(getattr(reply, 'message', '')) def _release(self, resource_type: str, value: str, fabric_id: str) -> None: if self.logical_resource_client is None: return request = ResourceReservation( tuple=LRTuple(type=resource_type, value=value), fabric_id=FabricId(fabric_id=fabric_id), ) self.logical_resource_client.ReleaseResource(request) def _rollback_reservations(self, reservations: List[TypingTuple[str, str]], fabric_id: str) -> None: for resource_type, value in reservations: try: Loading @@ -74,7 +78,6 @@ class ResourceAllocator: return self._pick_first_available(reply) def allocate_for_role(self, device_uuid: str, fabric_id: str, role: str): # Base underlay resources for every role. resource_types = ['asn', 'loopback', 'ip'] if role in ('leaf', 'gateway'): resource_types.extend(['vlan', 'vni']) Loading @@ -87,11 +90,9 @@ class ResourceAllocator: value = self._get_one_available(resource_type) if not value: raise RuntimeError(f'No available resource for type={resource_type}') ok, message = self._reserve(resource_type, value, fabric_id) if not ok: raise RuntimeError(f'Could not reserve {resource_type}={value}: {message}') selected[resource_type] = value reserved.append((resource_type, value)) Loading @@ -107,12 +108,13 @@ class ResourceAllocator: ) db_resources = { 'asn': config.asn, 'asn': str(config.asn), 'loopback_ip': config.ip_address, 'underlay_ips': [config.remote_address] if config.remote_address else [], 'vlans': [config.vlan_tag] if config.vlan_tag else [], 'vnis': [config.vni] if config.vni else [], 'vlans': [str(config.vlan_tag)] if config.vlan_tag else [], 'vnis': [str(config.vni)] if config.vni else [], } return config, db_resources except Exception: # pylint: disable=broad-except self._rollback_reservations(reserved, fabric_id) Loading src/spine_leaf/service/SpineLeafServicerImpl.py +107 −40 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ from common.proto.spineleaf_pb2 import ( from common.proto.spineleaf_pb2_grpc import SpineLeafServicer from ..state_store import SpineLeafStateStore as InternalDatabase from ..scripts.config_payload import build_deploy_payload from ..scripts.resource_allocator import ResourceAllocator LOGGER = logging.getLogger(__name__) METRICS_POOL = MetricsPool('SpineLeaf', 'RPC') Loading @@ -43,8 +44,30 @@ class SpineLeafServicerImpl(SpineLeafServicer): def __init__(self) -> None: LOGGER.debug('Creating Servicer...') self.db = InternalDatabase() self.resource_allocator = ResourceAllocator() LOGGER.debug('Servicer Created') def _allocate_for_role(self, request: SetDeviceRoleRequest, role: str, context: grpc.ServicerContext): fabric = self.db.get_fabric(request.fabric_id) if not fabric: context.set_code(grpc.StatusCode.NOT_FOUND) context.set_details(f'Fabric {request.fabric_id} not found') return None, None config, resources = self.resource_allocator.allocate_for_role( request.device_uuid, request.fabric_id, role ) if not config or not resources: context.set_code(grpc.StatusCode.FAILED_PRECONDITION) context.set_details( f'Unable to allocate resources for role={role} device={request.device_uuid}' ) return None, None return config, resources @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DefineSpineLeafFabric(self, request: FabricSettings, context: grpc.ServicerContext) -> FabricDefinitionResponse: fabric_id, _, _ = self.db.create_fabric( Loading @@ -60,59 +83,92 @@ class SpineLeafServicerImpl(SpineLeafServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def SetDeviceAsSpine(self, request: SetDeviceRoleRequest, context: grpc.ServicerContext) -> ConfigSetting: role = 'spine' config = ConfigSetting(device_uuid=request.device_uuid, endpoint_uuid=request.device_uuid, local_role=role) ok, msg = self.db.set_device_role(request.fabric_id, request.device_uuid, role, { 'asn': config.asn, 'loopback_ip': config.ip_address, 'underlay_ips': [config.remote_address] if config.remote_address else [], 'vlans': [], 'vnis': [], }) if not ok: context.set_code(grpc.StatusCode.FAILED_PRECONDITION) context.set_details(msg) config, resources = self._allocate_for_role(request, role, context) if config is None: return ConfigSetting() try: ok, msg = self.db.set_device_role( request.fabric_id, request.device_uuid, role, resources ) if not ok: raise RuntimeError(msg) self.db.store_config(request.fabric_id, request.device_uuid, config) return config except Exception as e: # rollback allocated LogicalResources for rtype, value in self.db.get_reserved_resources_for_device( request.fabric_id, request.device_uuid ): self.resource_allocator._release(rtype, value, request.fabric_id) context.set_code(grpc.StatusCode.INTERNAL) context.set_details(str(e)) return ConfigSetting() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def SetDeviceAsLeaf(self, request: SetDeviceRoleRequest, context: grpc.ServicerContext) -> ConfigSetting: role = 'leaf' config = ConfigSetting(device_uuid=request.device_uuid, endpoint_uuid=request.device_uuid, local_role=role) ok, msg = self.db.set_device_role(request.fabric_id, request.device_uuid, role, { 'asn': config.asn, 'loopback_ip': config.ip_address, 'underlay_ips': [config.remote_address] if config.remote_address else [], 'vlans': [config.vlan_tag] if config.vlan_tag else [], 'vnis': [config.vni] if config.vni else [], }) if not ok: context.set_code(grpc.StatusCode.FAILED_PRECONDITION) context.set_details(msg) config, resources = self._allocate_for_role(request, role, context) if config is None: return ConfigSetting() try: ok, msg = self.db.set_device_role( request.fabric_id, request.device_uuid, role, resources ) if not ok: raise RuntimeError(msg) self.db.store_config(request.fabric_id, request.device_uuid, config) return config except Exception as e: for rtype, value in self.db.get_reserved_resources_for_device( request.fabric_id, request.device_uuid ): self.resource_allocator._release(rtype, value, request.fabric_id) context.set_code(grpc.StatusCode.INTERNAL) context.set_details(str(e)) return ConfigSetting() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def SetDeviceAsGateway(self, request: SetDeviceRoleRequest, context: grpc.ServicerContext) -> ConfigSetting: role = 'gateway' config = ConfigSetting(device_uuid=request.device_uuid, endpoint_uuid=request.device_uuid, local_role=role) ok, msg = self.db.set_device_role(request.fabric_id, request.device_uuid, role, { 'asn': config.asn, 'loopback_ip': config.ip_address, 'underlay_ips': [config.remote_address] if config.remote_address else [], 'vlans': [config.vlan_tag] if config.vlan_tag else [], 'vnis': [config.vni] if config.vni else [], }) if not ok: context.set_code(grpc.StatusCode.FAILED_PRECONDITION) context.set_details(msg) config, resources = self._allocate_for_role(request, role, context) if config is None: return ConfigSetting() try: ok, msg = self.db.set_device_role( request.fabric_id, request.device_uuid, role, resources ) if not ok: raise RuntimeError(msg) self.db.store_config(request.fabric_id, request.device_uuid, config) return config except Exception as e: for rtype, value in self.db.get_reserved_resources_for_device( request.fabric_id, request.device_uuid ): self.resource_allocator._release(rtype, value, request.fabric_id) context.set_code(grpc.StatusCode.INTERNAL) context.set_details(str(e)) return ConfigSetting() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DeployDeviceConfig(self, request: DeployDeviceConfigRequest, context: grpc.ServicerContext) -> DeployResponse: Loading @@ -123,7 +179,18 @@ class SpineLeafServicerImpl(SpineLeafServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def UnsetDeviceRole(self, request: UnsetDeviceRoleRequest, context: grpc.ServicerContext) -> OperationStatus: success, message = self.db.unset_device_role(request.fabric_id, request.device_uuid) fabric_id = request.fabric_id device_id = request.device_uuid reserved = self.db.get_reserved_resources_for_device(fabric_id, device_id) for resource_type, value in reserved: try: self.resource_allocator._release(resource_type, value, fabric_id) except Exception: LOGGER.exception( 'Failed to release resource %s=%s for device=%s fabric=%s', resource_type, value, device_id, fabric_id ) success, message = self.db.unset_device_role(fabric_id, device_id) return OperationStatus(success=success, message=message) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) Loading src/spine_leaf/state_store.py +1 −1 Original line number Diff line number Diff line Loading @@ -69,7 +69,7 @@ class SpineLeafStateStore: tracked: List[Tuple[str, str]] = [] if resources.get('asn'): tracked.append(('asn', resources['asn'])) tracked.append(('asn', str(resources['asn']))) if resources.get('loopback_ip'): tracked.append(('loopback', resources['loopback_ip'])) for ip in resources.get('underlay_ips', []): Loading Loading
src/spine_leaf/scripts/fabric_config_builder.py 0 → 100644 +114 −0 Original line number Diff line number Diff line # Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from typing import List, Tuple class FabricConfigBuilder: def __init__(self, state_store): self.store = state_store def build(self, fabric_id: str, device_id: str) -> List[Tuple[str, str]]: rules = [] inventory = self.store.get_fabric_inventory(fabric_id) device = self.store.get_device_role(fabric_id, device_id) if not device: return rules role = device["role"] asn = device["asn"] lo = device["loopback_ip"] # ---- Loopback IP ---- rules.append(( "/interfaces/ip", json.dumps({ "address": f"{lo}/32", "interface": "lo", "comment": f"{role} loopback" }) )) # ---- BGP Instance ---- rules.append(( "/network_instances/bgp_instance", json.dumps({ "name": "default", "as": asn, "router_id": lo }) )) # ---- VXLAN (leaf only) ---- if role in ("leaf", "gateway"): vni = device["vnis"][0] rules.append(( "/interfaces/vxlan", json.dumps({ "name": f"vxlan{vni}", "vni": int(vni), "local-address": lo, "port": 4789 }) )) # ---- BGP sessions ---- if role == "spine": for leaf in inventory["leaves"]: rules.append(self._bgp_session( name=leaf["device_id"], local_ip=lo, remote_ip=leaf["loopback_ip"], remote_as=leaf["asn"] )) elif role in ("leaf", "gateway"): for spine in inventory["spines"]: rules.append(self._bgp_session( name=spine["device_id"], local_ip=lo, remote_ip=spine["loopback_ip"], remote_as=spine["asn"] )) # ---- EVPN (leaf only) ---- if role in ("leaf", "gateway"): rules.append(( "/network_instances/bgp_evpn", json.dumps({ "name": "evpn-vni10", "instance": "default", "vni": int(vni) }) )) return rules def _bgp_session(self, name, local_ip, remote_ip, remote_as): return ( "/network_instances/bgp_session", json.dumps({ "name": name, "instance": "default", "local.address": local_ip, "remote.address": remote_ip, "remote.as": int(remote_as), "local.role": "ebgp", "routing-table": "vrf-dataplane", "multihop": "yes", "afi": "ip,l2vpn" }) )
src/spine_leaf/scripts/resource_allocator.py +13 −11 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ import logging from typing import Dict, List, Optional, Tuple as TypingTuple from networkx import config from common.proto.logical_resources_pb2 import ( FabricId, ResourceReservation, Loading @@ -24,15 +26,14 @@ from common.proto.logical_resources_pb2 import ( Tuple as LRTuple, ) from common.proto.spineleaf_pb2 import ConfigSetting from logical_resources_client.client.logicalresources import logicalresourcesClient from logical_resources_client.client.logicalresources import LogicalResourceClient LOGGER = logging.getLogger(__name__) class ResourceAllocator: def __init__(self, logical_resource_client=None) -> None: def __init__(self) -> None: super().__init__() self.logical_resource_client = logical_resource_client or logicalresourcesClient() self.logical_resource_client = LogicalResourceClient() def _pick_first_available(self, resource_reply) -> Optional[str]: for entry in resource_reply.resources: Loading @@ -51,15 +52,18 @@ class ResourceAllocator: reply = self.logical_resource_client.ReserveResource(request) return bool(getattr(reply, 'success', False)), str(getattr(reply, 'message', '')) def _release(self, resource_type: str, value: str, fabric_id: str) -> None: if self.logical_resource_client is None: return request = ResourceReservation( tuple=LRTuple(type=resource_type, value=value), fabric_id=FabricId(fabric_id=fabric_id), ) self.logical_resource_client.ReleaseResource(request) def _rollback_reservations(self, reservations: List[TypingTuple[str, str]], fabric_id: str) -> None: for resource_type, value in reservations: try: Loading @@ -74,7 +78,6 @@ class ResourceAllocator: return self._pick_first_available(reply) def allocate_for_role(self, device_uuid: str, fabric_id: str, role: str): # Base underlay resources for every role. resource_types = ['asn', 'loopback', 'ip'] if role in ('leaf', 'gateway'): resource_types.extend(['vlan', 'vni']) Loading @@ -87,11 +90,9 @@ class ResourceAllocator: value = self._get_one_available(resource_type) if not value: raise RuntimeError(f'No available resource for type={resource_type}') ok, message = self._reserve(resource_type, value, fabric_id) if not ok: raise RuntimeError(f'Could not reserve {resource_type}={value}: {message}') selected[resource_type] = value reserved.append((resource_type, value)) Loading @@ -107,12 +108,13 @@ class ResourceAllocator: ) db_resources = { 'asn': config.asn, 'asn': str(config.asn), 'loopback_ip': config.ip_address, 'underlay_ips': [config.remote_address] if config.remote_address else [], 'vlans': [config.vlan_tag] if config.vlan_tag else [], 'vnis': [config.vni] if config.vni else [], 'vlans': [str(config.vlan_tag)] if config.vlan_tag else [], 'vnis': [str(config.vni)] if config.vni else [], } return config, db_resources except Exception: # pylint: disable=broad-except self._rollback_reservations(reserved, fabric_id) Loading
src/spine_leaf/service/SpineLeafServicerImpl.py +107 −40 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ from common.proto.spineleaf_pb2 import ( from common.proto.spineleaf_pb2_grpc import SpineLeafServicer from ..state_store import SpineLeafStateStore as InternalDatabase from ..scripts.config_payload import build_deploy_payload from ..scripts.resource_allocator import ResourceAllocator LOGGER = logging.getLogger(__name__) METRICS_POOL = MetricsPool('SpineLeaf', 'RPC') Loading @@ -43,8 +44,30 @@ class SpineLeafServicerImpl(SpineLeafServicer): def __init__(self) -> None: LOGGER.debug('Creating Servicer...') self.db = InternalDatabase() self.resource_allocator = ResourceAllocator() LOGGER.debug('Servicer Created') def _allocate_for_role(self, request: SetDeviceRoleRequest, role: str, context: grpc.ServicerContext): fabric = self.db.get_fabric(request.fabric_id) if not fabric: context.set_code(grpc.StatusCode.NOT_FOUND) context.set_details(f'Fabric {request.fabric_id} not found') return None, None config, resources = self.resource_allocator.allocate_for_role( request.device_uuid, request.fabric_id, role ) if not config or not resources: context.set_code(grpc.StatusCode.FAILED_PRECONDITION) context.set_details( f'Unable to allocate resources for role={role} device={request.device_uuid}' ) return None, None return config, resources @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DefineSpineLeafFabric(self, request: FabricSettings, context: grpc.ServicerContext) -> FabricDefinitionResponse: fabric_id, _, _ = self.db.create_fabric( Loading @@ -60,59 +83,92 @@ class SpineLeafServicerImpl(SpineLeafServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def SetDeviceAsSpine(self, request: SetDeviceRoleRequest, context: grpc.ServicerContext) -> ConfigSetting: role = 'spine' config = ConfigSetting(device_uuid=request.device_uuid, endpoint_uuid=request.device_uuid, local_role=role) ok, msg = self.db.set_device_role(request.fabric_id, request.device_uuid, role, { 'asn': config.asn, 'loopback_ip': config.ip_address, 'underlay_ips': [config.remote_address] if config.remote_address else [], 'vlans': [], 'vnis': [], }) if not ok: context.set_code(grpc.StatusCode.FAILED_PRECONDITION) context.set_details(msg) config, resources = self._allocate_for_role(request, role, context) if config is None: return ConfigSetting() try: ok, msg = self.db.set_device_role( request.fabric_id, request.device_uuid, role, resources ) if not ok: raise RuntimeError(msg) self.db.store_config(request.fabric_id, request.device_uuid, config) return config except Exception as e: # rollback allocated LogicalResources for rtype, value in self.db.get_reserved_resources_for_device( request.fabric_id, request.device_uuid ): self.resource_allocator._release(rtype, value, request.fabric_id) context.set_code(grpc.StatusCode.INTERNAL) context.set_details(str(e)) return ConfigSetting() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def SetDeviceAsLeaf(self, request: SetDeviceRoleRequest, context: grpc.ServicerContext) -> ConfigSetting: role = 'leaf' config = ConfigSetting(device_uuid=request.device_uuid, endpoint_uuid=request.device_uuid, local_role=role) ok, msg = self.db.set_device_role(request.fabric_id, request.device_uuid, role, { 'asn': config.asn, 'loopback_ip': config.ip_address, 'underlay_ips': [config.remote_address] if config.remote_address else [], 'vlans': [config.vlan_tag] if config.vlan_tag else [], 'vnis': [config.vni] if config.vni else [], }) if not ok: context.set_code(grpc.StatusCode.FAILED_PRECONDITION) context.set_details(msg) config, resources = self._allocate_for_role(request, role, context) if config is None: return ConfigSetting() try: ok, msg = self.db.set_device_role( request.fabric_id, request.device_uuid, role, resources ) if not ok: raise RuntimeError(msg) self.db.store_config(request.fabric_id, request.device_uuid, config) return config except Exception as e: for rtype, value in self.db.get_reserved_resources_for_device( request.fabric_id, request.device_uuid ): self.resource_allocator._release(rtype, value, request.fabric_id) context.set_code(grpc.StatusCode.INTERNAL) context.set_details(str(e)) return ConfigSetting() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def SetDeviceAsGateway(self, request: SetDeviceRoleRequest, context: grpc.ServicerContext) -> ConfigSetting: role = 'gateway' config = ConfigSetting(device_uuid=request.device_uuid, endpoint_uuid=request.device_uuid, local_role=role) ok, msg = self.db.set_device_role(request.fabric_id, request.device_uuid, role, { 'asn': config.asn, 'loopback_ip': config.ip_address, 'underlay_ips': [config.remote_address] if config.remote_address else [], 'vlans': [config.vlan_tag] if config.vlan_tag else [], 'vnis': [config.vni] if config.vni else [], }) if not ok: context.set_code(grpc.StatusCode.FAILED_PRECONDITION) context.set_details(msg) config, resources = self._allocate_for_role(request, role, context) if config is None: return ConfigSetting() try: ok, msg = self.db.set_device_role( request.fabric_id, request.device_uuid, role, resources ) if not ok: raise RuntimeError(msg) self.db.store_config(request.fabric_id, request.device_uuid, config) return config except Exception as e: for rtype, value in self.db.get_reserved_resources_for_device( request.fabric_id, request.device_uuid ): self.resource_allocator._release(rtype, value, request.fabric_id) context.set_code(grpc.StatusCode.INTERNAL) context.set_details(str(e)) return ConfigSetting() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DeployDeviceConfig(self, request: DeployDeviceConfigRequest, context: grpc.ServicerContext) -> DeployResponse: Loading @@ -123,7 +179,18 @@ class SpineLeafServicerImpl(SpineLeafServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def UnsetDeviceRole(self, request: UnsetDeviceRoleRequest, context: grpc.ServicerContext) -> OperationStatus: success, message = self.db.unset_device_role(request.fabric_id, request.device_uuid) fabric_id = request.fabric_id device_id = request.device_uuid reserved = self.db.get_reserved_resources_for_device(fabric_id, device_id) for resource_type, value in reserved: try: self.resource_allocator._release(resource_type, value, fabric_id) except Exception: LOGGER.exception( 'Failed to release resource %s=%s for device=%s fabric=%s', resource_type, value, device_id, fabric_id ) success, message = self.db.unset_device_role(fabric_id, device_id) return OperationStatus(success=success, message=message) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) Loading
src/spine_leaf/state_store.py +1 −1 Original line number Diff line number Diff line Loading @@ -69,7 +69,7 @@ class SpineLeafStateStore: tracked: List[Tuple[str, str]] = [] if resources.get('asn'): tracked.append(('asn', resources['asn'])) tracked.append(('asn', str(resources['asn']))) if resources.get('loopback_ip'): tracked.append(('loopback', resources['loopback_ip'])) for ip in resources.get('underlay_ips', []): Loading