Loading proto/logical_resources.proto +108 −28 Original line number Original line Diff line number Diff line Loading @@ -16,43 +16,123 @@ syntax = "proto3"; package logical_resources; package logical_resources; service LogicalResources { service LogicalResources { rpc AddResources (ConfigParameters) returns (Response) {} rpc GetAvailableResource (Empty) returns (ResourceList) {} rpc ReserveResource (ResourceReservation) returns (Success) {} rpc DeleteResource (ResourceReservation) returns (Success) {} rpc GetAllocatedResources (Empty) returns (ResourceList) {} rpc GetResourceOwner (Value) returns (FabricId) {} rpc GetResourcesByType (ResourceTypeQuery) returns (TypedResourceList) {} // old ones rpc AddDevice (DeviceConfig) returns (Response) {} rpc AddDevice (DeviceConfig) returns (Response) {} rpc DeleteDevice (DeviceId) returns (Response) {} rpc DeleteDevice (DeviceId) returns (Response) {} rpc GetDeviceConfig (DeviceId) returns (DeviceConfig) {} rpc GetDeviceConfig (DeviceId) returns (DeviceConfig) {} rpc GetDatabase (Empty) returns (Database) {} } rpc GetDatabase (Empty) returns (Database) {} } message Empty {} message Empty {} message Value { string value = 1; } message FabricId { string fabric_id = 1; } message Tuple { string type = 1; string value = 2; } message Success { bool success = 1; string message = 2; } message ResourceReservation { Tuple tuple = 1; FabricId fabric_id = 2; } message ResourceTypeQuery { string resource_type = 1; } message ResourceEntry { string type = 1; string value = 2; string fabric_id = 3; bool allocated = 4; } message ResourceList { repeated ResourceEntry resources = 1; } message TypedResourceList { string resource_type = 1; repeated ResourceEntry resources = 2; } message ConfigParameters { string device_uuid = 1 [json_name = "device-uuid"]; string endpoint_uuid = 2 [json_name = "endpoint-uuid"]; string ip_address = 3 [json_name = "ip-address"]; uint32 vlan_tag = 4 [json_name = "vlan-tag"]; string mac_address = 5 [json_name = "mac-address"]; uint32 asn = 6; string router_id = 7 [json_name = "router-id"]; uint32 vni = 8; string local_address = 9 [json_name = "local.address"]; uint32 port = 10; string bridge = 11; string interface = 12; string remote_address = 13 [json_name = "remote.address"]; uint32 remote_as = 14 [json_name = "remote.as"]; string local_role = 15 [json_name = "local.role"]; string routing_table = 16 [json_name = "routing-table"]; bool multihop = 17; string afi = 18 [json_name = "address-families"]; string name = 19; } message DeviceConfig { message DeviceConfig { string device_uuid = 1; string device_uuid = 1 [json_name = "device-uuid"]; string endpoint_uuid = 2; string endpoint_uuid = 2 [json_name = "endpoint-uuid"]; string ip_address = 3; string ip_address = 3 [json_name = "ip-address"]; uint32 vlan_tag = 4; uint32 vlan_tag = 4 [json_name = "vlan-tag"]; string mac_address = 5; string mac_address = 5 [json_name = "mac-address"]; uint32 asn = 6; uint32 asn = 6; string router_id = 7; string router_id = 7 [json_name = "router-id"]; uint32 vni = 8; uint32 vni = 8; string local_address = 9; string local_address = 9 [json_name = "local.address"]; uint32 port = 10; uint32 port = 10; string bridge = 11; string bridge = 11; string interface = 12; string interface = 12; string remote_address = 13; string remote_address = 13 [json_name = "remote.address"]; uint32 remote_as = 14; uint32 remote_as = 14 [json_name = "remote.as"]; string local_role = 15; string local_role = 15 [json_name = "local.role"]; string routing_table = 16; string routing_table = 16 [json_name = "routing-table"]; bool multihop = 17; bool multihop = 17; string afi = 18; string afi = 18 [json_name = "address-families"]; string name = 19; string name = 19; } } message DeviceId { message DeviceId { string device_uuid = 1;} string device_uuid = 1; } message Response { message Response { bool success = 1; bool success = 1; string message = 2;} string message = 2; } message Database { message Database { map<string, DeviceEndpoints> devices = 1;} map<string, DeviceEndpoints> devices = 1; } message DeviceEndpoints { message DeviceEndpoints { map<string, DeviceConfig> endpoints = 1;} map<string, DeviceConfig> endpoints = 1; No newline at end of file } No newline at end of file src/logical_resources/client/Logicalresourceclient.py +80 −7 Original line number Original line Diff line number Diff line Loading @@ -15,7 +15,10 @@ import grpc, logging import grpc, logging from common.Constants import ServiceNameEnum from common.Constants import ServiceNameEnum from common.Settings import get_service_host, get_service_port_grpc from common.Settings import get_service_host, get_service_port_grpc from common.proto.logical_resources_pb2 import Response, DeviceConfig, Database, Empty from common.proto.logical_resources_pb2 import ( ConfigParameters, Empty, ) from common.proto.logical_resources_pb2_grpc import LogicalResourcesStub from common.proto.logical_resources_pb2_grpc import LogicalResourcesStub from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string from common.tools.grpc.Tools import grpc_message_to_json_string Loading Loading @@ -46,17 +49,87 @@ class LogicalResourceClient: self.stub = None self.stub = None @RETRY_DECORATOR @RETRY_DECORATOR def AddDevice(self, request: DeviceConfig)-> Response: def AddResources(self, request): LOGGER.debug('ADD device request: {:s}'.format(grpc_message_to_json_string(request))) LOGGER.debug('AddResources request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.AddDevice(request) response = self.stub.AddResources(request) LOGGER.debug('add device result: {:s}'.format(grpc_message_to_json_string(response))) LOGGER.debug('AddResources result: {:s}'.format(grpc_message_to_json_string(response))) return response return response @RETRY_DECORATOR @RETRY_DECORATOR def GetDatabase(self, request: Empty = None) -> Database: def GetAvailableResource(self, request=None): if request is None: request = Empty() LOGGER.debug('GetAvailableResource request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.GetAvailableResource(request) LOGGER.debug('GetAvailableResource result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def ReserveResource(self, request): LOGGER.debug('ReserveResource request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.ReserveResource(request) LOGGER.debug('ReserveResource result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def DeleteResource(self, request): LOGGER.debug('DeleteResource request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.DeleteResource(request) LOGGER.debug('DeleteResource result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def GetAllocatedResources(self, request=None): if request is None: request = Empty() LOGGER.debug('GetAllocatedResources request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.GetAllocatedResources(request) LOGGER.debug('GetAllocatedResources result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def GetResourceOwner(self, request): LOGGER.debug('GetResourceOwner request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.GetResourceOwner(request) LOGGER.debug('GetResourceOwner result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def GetResourcesByType(self, request): LOGGER.debug('GetResourcesByType request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.GetResourcesByType(request) LOGGER.debug('GetResourcesByType result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def GetDatabase(self, request=None): if request is None: if request is None: request = Empty() request = Empty() LOGGER.debug('Get database request: {:s}'.format(grpc_message_to_json_string(request))) LOGGER.debug('Get database request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.GetDatabase(request) response = self.stub.GetDatabase(request) LOGGER.debug('get database result: {:s}'.format(grpc_message_to_json_string(response))) LOGGER.debug('get database result: {:s}'.format(grpc_message_to_json_string(response))) return response return response @RETRY_DECORATOR def AddDevice(self, request): return self.AddResources(ConfigParameters( device_uuid=request.device_uuid, endpoint_uuid=request.endpoint_uuid, ip_address=request.ip_address, vlan_tag=request.vlan_tag, mac_address=request.mac_address, asn=request.asn, router_id=request.router_id, vni=request.vni, local_address=request.local_address, port=request.port, bridge=request.bridge, interface=request.interface, remote_address=request.remote_address, remote_as=request.remote_as, local_role=request.local_role, routing_table=request.routing_table, multihop=request.multihop, afi=request.afi, name=request.name, )) No newline at end of file src/logical_resources/database.py +83 −3 Original line number Original line Diff line number Diff line Loading @@ -3,8 +3,14 @@ class Database: def __init__(self): def __init__(self): "Creating the database to store things" "Creating the database to store things" self.database = {} self.database = {} self.resources = {} def add_device(self, device_uuid, endpoint_uuid, ip_address, vlan_tag, mac_address, ASN, RouterID, def _track_resource(self, resource_type, resource_value): if not resource_value: return self.resources.setdefault((resource_type, resource_value), '') def add_resources(self, device_uuid, endpoint_uuid, ip_address, vlan_tag, mac_address, ASN, RouterID, vni=None, local_address=None, port=None, bridge=None, interface=None, remote_address=None, vni=None, local_address=None, port=None, bridge=None, interface=None, remote_address=None, remote_as=None, local_role=None, routing_table=None, multihop=None, afi=None, name=None): remote_as=None, local_role=None, routing_table=None, multihop=None, afi=None, name=None): if device_uuid not in self.database: if device_uuid not in self.database: Loading Loading @@ -35,10 +41,84 @@ class Database: if vlan_tag: if vlan_tag: self.database[device_uuid][endpoint_uuid]["vlan_tags"].append(vlan_tag) self.database[device_uuid][endpoint_uuid]["vlan_tags"].append(vlan_tag) self._track_resource('ip', ip_address) self._track_resource('vlan', str(vlan_tag) if vlan_tag else '') self._track_resource('asn', str(ASN) if ASN else '') self._track_resource('loopback', RouterID) # Backward-compatible alias kept while callers migrate to add_resources. def add_device(self, device_uuid, endpoint_uuid, ip_address, vlan_tag, mac_address, ASN, RouterID, vni=None, local_address=None, port=None, bridge=None, interface=None, remote_address=None, remote_as=None, local_role=None, routing_table=None, multihop=None, afi=None, name=None): self.add_resources( device_uuid, endpoint_uuid, ip_address, vlan_tag, mac_address, ASN, RouterID, vni=vni, local_address=local_address, port=port, bridge=bridge, interface=interface, remote_address=remote_address, remote_as=remote_as, local_role=local_role, routing_table=routing_table, multihop=multihop, afi=afi, name=name, ) def get_full_db(self): def get_full_db(self): "Return ALLL" "Return ALLL" return self.database return self.database def get_available_resources(self): return [ (resource_type, resource_value, fabric_id) for (resource_type, resource_value), fabric_id in self.resources.items() if not fabric_id ] def reserve_resource(self, resource_type, resource_value, fabric_id): key = (resource_type, resource_value) if key not in self.resources: return False, 'Resource not found' if self.resources[key]: return False, 'Resource already reserved' self.resources[key] = fabric_id return True, 'Resource reserved' def delete_resource(self, resource_type, resource_value): key = (resource_type, resource_value) if key not in self.resources: return False, 'Resource not found' del self.resources[key] return True, 'Resource deleted' def get_allocated_resources(self): return [ (resource_type, resource_value, fabric_id) for (resource_type, resource_value), fabric_id in self.resources.items() if fabric_id ] def get_resource_owner(self, resource_value): for (_, value), fabric_id in self.resources.items(): if value == resource_value: return fabric_id return '' def get_resources_by_type(self, resource_type): return [ (rtype, value, fabric_id) for (rtype, value), fabric_id in self.resources.items() if rtype == resource_type ] # --- DELETE --- # --- DELETE --- def delete_device(self, device_uuid): def delete_device(self, device_uuid): """to remove the entire device""" """to remove the entire device""" Loading src/logical_resources/service/LogicalResourceServicerImpl.py +93 −4 Original line number Original line Diff line number Diff line import logging import logging from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from common.proto.logical_resources_pb2 import Response, DeviceConfig, Database, DeviceEndpoints from common.proto.logical_resources_pb2 import ( Response, Success, ConfigParameters, DeviceConfig, Database, DeviceEndpoints, ResourceList, ResourceEntry, FabricId, TypedResourceList, ) from common.proto.logical_resources_pb2_grpc import LogicalResourcesServicer from common.proto.logical_resources_pb2_grpc import LogicalResourcesServicer from logical_resources.database import Database as InternalDatabase from logical_resources.database import Database as InternalDatabase import grpc import grpc Loading @@ -15,8 +26,8 @@ class LogicalResourceServicerImpl(LogicalResourcesServicer): LOGGER.debug('Servicer Created') LOGGER.debug('Servicer Created') @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def AddDevice(self, request: DeviceConfig, context : grpc.ServicerContext) -> Response: def AddResources(self, request: ConfigParameters, context : grpc.ServicerContext) -> Response: self.db.add_device( self.db.add_resources( request.device_uuid, request.device_uuid, request.endpoint_uuid, request.endpoint_uuid, request.ip_address, request.ip_address, Loading @@ -37,7 +48,85 @@ class LogicalResourceServicerImpl(LogicalResourcesServicer): request.afi, request.afi, request.name request.name ) ) return Response(success=True, message="Device added to Logical Database") return Response(success=True, message='Resources added to Logical Database') @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetAvailableResource(self, request, context: grpc.ServicerContext) -> ResourceList: reply = ResourceList() for resource_type, resource_value, fabric_id in self.db.get_available_resources(): reply.resources.append(ResourceEntry( type=resource_type, value=resource_value, fabric_id='', allocated=False, )) return reply @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def ReserveResource(self, request, context: grpc.ServicerContext) -> Success: success, message = self.db.reserve_resource( request.tuple.type, request.tuple.value, request.fabric_id.fabric_id, ) return Success(success=success, message=message) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DeleteResource(self, request, context: grpc.ServicerContext) -> Success: success, message = self.db.delete_resource(request.tuple.type, request.tuple.value) return Success(success=success, message=message) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetAllocatedResources(self, request, context: grpc.ServicerContext) -> ResourceList: reply = ResourceList() for resource_type, resource_value, fabric_id in self.db.get_allocated_resources(): reply.resources.append(ResourceEntry( type=resource_type, value=resource_value, fabric_id=fabric_id, allocated=True, )) return reply @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetResourceOwner(self, request, context: grpc.ServicerContext) -> FabricId: return FabricId(fabric_id=self.db.get_resource_owner(request.value)) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetResourcesByType(self, request, context: grpc.ServicerContext) -> TypedResourceList: reply = TypedResourceList(resource_type=request.resource_type) for resource_type, resource_value, fabric_id in self.db.get_resources_by_type(request.resource_type): reply.resources.append(ResourceEntry( type=resource_type, value=resource_value, fabric_id=fabric_id, allocated=bool(fabric_id), )) return reply @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def AddDevice(self, request: DeviceConfig, context : grpc.ServicerContext) -> Response: return self.AddResources(ConfigParameters( device_uuid=request.device_uuid, endpoint_uuid=request.endpoint_uuid, ip_address=request.ip_address, vlan_tag=request.vlan_tag, mac_address=request.mac_address, asn=request.asn, router_id=request.router_id, vni=request.vni, local_address=request.local_address, port=request.port, bridge=request.bridge, interface=request.interface, remote_address=request.remote_address, remote_as=request.remote_as, local_role=request.local_role, routing_table=request.routing_table, multihop=request.multihop, afi=request.afi, name=request.name, ), context) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetDatabase(self, request, context:grpc.ServicerContext) -> Database: def GetDatabase(self, request, context:grpc.ServicerContext) -> Database: Loading src/logical_resources/test/test_unitary.py +66 −32 Original line number Original line Diff line number Diff line Loading @@ -12,9 +12,16 @@ # See the License for the specific language governing permissions and # See the License for the specific language governing permissions and # limitations under the License. # limitations under the License. import pytest from logical_resources.client.Logicalresourceclient import LogicalResourceClient from logical_resources.client.Logicalresourceclient import LogicalResourceClient from common.proto.logical_resources_pb2 import DeviceConfig, Empty from common.proto.logical_resources_pb2 import ( ConfigParameters, Empty, ResourceReservation, Tuple, FabricId, Value, ResourceTypeQuery, ) from .PrepareTestScenario import ( from .PrepareTestScenario import ( Loading @@ -24,7 +31,7 @@ from .PrepareTestScenario import ( def test_add(logical_resource_client : LogicalResourceClient, logical_resource_service): def test_add(logical_resource_client : LogicalResourceClient, logical_resource_service): add_request = DeviceConfig() add_request = ConfigParameters() add_request.device_uuid = "leaf1" add_request.device_uuid = "leaf1" add_request.endpoint_uuid = "sfp1" add_request.endpoint_uuid = "sfp1" add_request.ip_address = "10.0.0.1" add_request.ip_address = "10.0.0.1" Loading @@ -44,9 +51,30 @@ def test_add(logical_resource_client : LogicalResourceClient, logical_resource_s add_request.multihop = True add_request.multihop = True add_request.afi = "ip,l2vpn" add_request.afi = "ip,l2vpn" add_request.name = "test-device" add_request.name = "test-device" add_reply = logical_resource_client.AddDevice(add_request) add_reply = logical_resource_client.AddResources(add_request) assert add_reply.success == True assert add_reply.success == True available = logical_resource_client.GetAvailableResource(Empty()) assert any(r.type == 'ip' and r.value == '10.0.0.1' for r in available.resources) assert any(r.type == 'vlan' and r.value == '100' for r in available.resources) assert any(r.type == 'asn' and r.value == '65001' for r in available.resources) assert any(r.type == 'loopback' and r.value == '1.1.1.1' for r in available.resources) # Validate key underlay/overlay fields are persisted (MikroTik-style mapping). db_reply = logical_resource_client.GetDatabase(Empty()) endpoint = db_reply.devices['leaf1'].endpoints['sfp1'] assert endpoint.local_role == 'ebgp' assert endpoint.routing_table == 'vrf-dataplane' assert endpoint.multihop is True assert endpoint.afi == 'ip,l2vpn' assert endpoint.vni == 10 assert endpoint.port == 4789 assert endpoint.bridge == 'br-dataplane' assert endpoint.interface == 'vxlan10' assert endpoint.local_address == '192.168.1.1' assert endpoint.remote_address == '10.0.0.2' assert endpoint.remote_as == 65002 # to print what is stored in the datbase after the add operation # to print what is stored in the datbase after the add operation import json import json stored_data = logical_resource_service.servicer_impl.db.get_full_db() stored_data = logical_resource_service.servicer_impl.db.get_full_db() Loading @@ -54,9 +82,9 @@ def test_add(logical_resource_client : LogicalResourceClient, logical_resource_s print(json.dumps(stored_data, indent=2)) print(json.dumps(stored_data, indent=2)) print("==================================\n") print("==================================\n") def test_get_database(logical_resource_client : LogicalResourceClient, logical_resource_service): def test_resource_allocation_and_queries(logical_resource_client : LogicalResourceClient, logical_resource_service): add_request = DeviceConfig() add_request = ConfigParameters() add_request.device_uuid = "leaf2" add_request.device_uuid = "leaf2" add_request.endpoint_uuid = "sfp2" add_request.endpoint_uuid = "sfp2" add_request.ip_address = "10.0.0.2" add_request.ip_address = "10.0.0.2" Loading @@ -76,34 +104,40 @@ def test_get_database(logical_resource_client : LogicalResourceClient, logical_r add_request.multihop = False add_request.multihop = False add_request.afi = "ip" add_request.afi = "ip" add_request.name = "test-device2" add_request.name = "test-device2" add_reply = logical_resource_client.AddDevice(add_request) add_reply = logical_resource_client.AddResources(add_request) assert add_reply.success is True assert add_reply.success is True get_request = Empty() reserve_reply = logical_resource_client.ReserveResource(ResourceReservation( get_reply = logical_resource_client.GetDatabase(get_request) tuple=Tuple(type='ip', value='10.0.0.2'), assert get_reply is not None fabric_id=FabricId(fabric_id='fabric-A'), assert "leaf2" in get_reply.devices )) assert "sfp2" in get_reply.devices["leaf2"].endpoints assert reserve_reply.success is True endpoint_cfg = get_reply.devices["leaf2"].endpoints["sfp2"] assert endpoint_cfg.device_uuid == "leaf2" available_after_reserve = logical_resource_client.GetAvailableResource(Empty()) assert endpoint_cfg.endpoint_uuid == "sfp2" assert not any(r.type == 'ip' and r.value == '10.0.0.2' for r in available_after_reserve.resources) assert endpoint_cfg.ip_address == "10.0.0.2" assert endpoint_cfg.vlan_tag == 200 allocated = logical_resource_client.GetAllocatedResources(Empty()) assert endpoint_cfg.mac_address == "AA:BB:CC:DD:EE:01" assert any(r.type == 'ip' and r.value == '10.0.0.2' and r.fabric_id == 'fabric-A' for r in allocated.resources) assert endpoint_cfg.asn == 65002 assert endpoint_cfg.router_id == "2.2.2.2" owner = logical_resource_client.GetResourceOwner(Value(value='10.0.0.2')) assert endpoint_cfg.vni == 20 assert owner.fabric_id == 'fabric-A' assert endpoint_cfg.local_address == "192.168.2.1" assert endpoint_cfg.port == 4790 typed = logical_resource_client.GetResourcesByType(ResourceTypeQuery(resource_type='ip')) assert endpoint_cfg.bridge == "br-dataplane2" assert typed.resource_type == 'ip' assert endpoint_cfg.interface == "vxlan20" assert any(r.value == '10.0.0.2' for r in typed.resources) assert endpoint_cfg.remote_address == "10.0.0.3" assert endpoint_cfg.remote_as == 65003 delete_reply = logical_resource_client.DeleteResource(ResourceReservation( assert endpoint_cfg.local_role == "ibgp" tuple=Tuple(type='ip', value='10.0.0.2'), assert endpoint_cfg.routing_table == "vrf-dataplane2" fabric_id=FabricId(fabric_id='fabric-A'), assert endpoint_cfg.multihop == False )) assert endpoint_cfg.afi == "ip" assert delete_reply.success is True assert endpoint_cfg.name == "test-device2" allocated_after_delete = logical_resource_client.GetAllocatedResources(Empty()) assert not any(r.type == 'ip' and r.value == '10.0.0.2' for r in allocated_after_delete.resources) owner_after_delete = logical_resource_client.GetResourceOwner(Value(value='10.0.0.2')) assert owner_after_delete.fabric_id == '' # to print what is stored in the datbase after the get operation # to print what is stored in the datbase after the get operation import json import json stored_data = logical_resource_service.servicer_impl.db.get_full_db() stored_data = logical_resource_service.servicer_impl.db.get_full_db() Loading Loading
proto/logical_resources.proto +108 −28 Original line number Original line Diff line number Diff line Loading @@ -16,43 +16,123 @@ syntax = "proto3"; package logical_resources; package logical_resources; service LogicalResources { service LogicalResources { rpc AddResources (ConfigParameters) returns (Response) {} rpc GetAvailableResource (Empty) returns (ResourceList) {} rpc ReserveResource (ResourceReservation) returns (Success) {} rpc DeleteResource (ResourceReservation) returns (Success) {} rpc GetAllocatedResources (Empty) returns (ResourceList) {} rpc GetResourceOwner (Value) returns (FabricId) {} rpc GetResourcesByType (ResourceTypeQuery) returns (TypedResourceList) {} // old ones rpc AddDevice (DeviceConfig) returns (Response) {} rpc AddDevice (DeviceConfig) returns (Response) {} rpc DeleteDevice (DeviceId) returns (Response) {} rpc DeleteDevice (DeviceId) returns (Response) {} rpc GetDeviceConfig (DeviceId) returns (DeviceConfig) {} rpc GetDeviceConfig (DeviceId) returns (DeviceConfig) {} rpc GetDatabase (Empty) returns (Database) {} } rpc GetDatabase (Empty) returns (Database) {} } message Empty {} message Empty {} message Value { string value = 1; } message FabricId { string fabric_id = 1; } message Tuple { string type = 1; string value = 2; } message Success { bool success = 1; string message = 2; } message ResourceReservation { Tuple tuple = 1; FabricId fabric_id = 2; } message ResourceTypeQuery { string resource_type = 1; } message ResourceEntry { string type = 1; string value = 2; string fabric_id = 3; bool allocated = 4; } message ResourceList { repeated ResourceEntry resources = 1; } message TypedResourceList { string resource_type = 1; repeated ResourceEntry resources = 2; } message ConfigParameters { string device_uuid = 1 [json_name = "device-uuid"]; string endpoint_uuid = 2 [json_name = "endpoint-uuid"]; string ip_address = 3 [json_name = "ip-address"]; uint32 vlan_tag = 4 [json_name = "vlan-tag"]; string mac_address = 5 [json_name = "mac-address"]; uint32 asn = 6; string router_id = 7 [json_name = "router-id"]; uint32 vni = 8; string local_address = 9 [json_name = "local.address"]; uint32 port = 10; string bridge = 11; string interface = 12; string remote_address = 13 [json_name = "remote.address"]; uint32 remote_as = 14 [json_name = "remote.as"]; string local_role = 15 [json_name = "local.role"]; string routing_table = 16 [json_name = "routing-table"]; bool multihop = 17; string afi = 18 [json_name = "address-families"]; string name = 19; } message DeviceConfig { message DeviceConfig { string device_uuid = 1; string device_uuid = 1 [json_name = "device-uuid"]; string endpoint_uuid = 2; string endpoint_uuid = 2 [json_name = "endpoint-uuid"]; string ip_address = 3; string ip_address = 3 [json_name = "ip-address"]; uint32 vlan_tag = 4; uint32 vlan_tag = 4 [json_name = "vlan-tag"]; string mac_address = 5; string mac_address = 5 [json_name = "mac-address"]; uint32 asn = 6; uint32 asn = 6; string router_id = 7; string router_id = 7 [json_name = "router-id"]; uint32 vni = 8; uint32 vni = 8; string local_address = 9; string local_address = 9 [json_name = "local.address"]; uint32 port = 10; uint32 port = 10; string bridge = 11; string bridge = 11; string interface = 12; string interface = 12; string remote_address = 13; string remote_address = 13 [json_name = "remote.address"]; uint32 remote_as = 14; uint32 remote_as = 14 [json_name = "remote.as"]; string local_role = 15; string local_role = 15 [json_name = "local.role"]; string routing_table = 16; string routing_table = 16 [json_name = "routing-table"]; bool multihop = 17; bool multihop = 17; string afi = 18; string afi = 18 [json_name = "address-families"]; string name = 19; string name = 19; } } message DeviceId { message DeviceId { string device_uuid = 1;} string device_uuid = 1; } message Response { message Response { bool success = 1; bool success = 1; string message = 2;} string message = 2; } message Database { message Database { map<string, DeviceEndpoints> devices = 1;} map<string, DeviceEndpoints> devices = 1; } message DeviceEndpoints { message DeviceEndpoints { map<string, DeviceConfig> endpoints = 1;} map<string, DeviceConfig> endpoints = 1; No newline at end of file } No newline at end of file
src/logical_resources/client/Logicalresourceclient.py +80 −7 Original line number Original line Diff line number Diff line Loading @@ -15,7 +15,10 @@ import grpc, logging import grpc, logging from common.Constants import ServiceNameEnum from common.Constants import ServiceNameEnum from common.Settings import get_service_host, get_service_port_grpc from common.Settings import get_service_host, get_service_port_grpc from common.proto.logical_resources_pb2 import Response, DeviceConfig, Database, Empty from common.proto.logical_resources_pb2 import ( ConfigParameters, Empty, ) from common.proto.logical_resources_pb2_grpc import LogicalResourcesStub from common.proto.logical_resources_pb2_grpc import LogicalResourcesStub from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string from common.tools.grpc.Tools import grpc_message_to_json_string Loading Loading @@ -46,17 +49,87 @@ class LogicalResourceClient: self.stub = None self.stub = None @RETRY_DECORATOR @RETRY_DECORATOR def AddDevice(self, request: DeviceConfig)-> Response: def AddResources(self, request): LOGGER.debug('ADD device request: {:s}'.format(grpc_message_to_json_string(request))) LOGGER.debug('AddResources request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.AddDevice(request) response = self.stub.AddResources(request) LOGGER.debug('add device result: {:s}'.format(grpc_message_to_json_string(response))) LOGGER.debug('AddResources result: {:s}'.format(grpc_message_to_json_string(response))) return response return response @RETRY_DECORATOR @RETRY_DECORATOR def GetDatabase(self, request: Empty = None) -> Database: def GetAvailableResource(self, request=None): if request is None: request = Empty() LOGGER.debug('GetAvailableResource request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.GetAvailableResource(request) LOGGER.debug('GetAvailableResource result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def ReserveResource(self, request): LOGGER.debug('ReserveResource request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.ReserveResource(request) LOGGER.debug('ReserveResource result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def DeleteResource(self, request): LOGGER.debug('DeleteResource request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.DeleteResource(request) LOGGER.debug('DeleteResource result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def GetAllocatedResources(self, request=None): if request is None: request = Empty() LOGGER.debug('GetAllocatedResources request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.GetAllocatedResources(request) LOGGER.debug('GetAllocatedResources result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def GetResourceOwner(self, request): LOGGER.debug('GetResourceOwner request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.GetResourceOwner(request) LOGGER.debug('GetResourceOwner result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def GetResourcesByType(self, request): LOGGER.debug('GetResourcesByType request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.GetResourcesByType(request) LOGGER.debug('GetResourcesByType result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def GetDatabase(self, request=None): if request is None: if request is None: request = Empty() request = Empty() LOGGER.debug('Get database request: {:s}'.format(grpc_message_to_json_string(request))) LOGGER.debug('Get database request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.GetDatabase(request) response = self.stub.GetDatabase(request) LOGGER.debug('get database result: {:s}'.format(grpc_message_to_json_string(response))) LOGGER.debug('get database result: {:s}'.format(grpc_message_to_json_string(response))) return response return response @RETRY_DECORATOR def AddDevice(self, request): return self.AddResources(ConfigParameters( device_uuid=request.device_uuid, endpoint_uuid=request.endpoint_uuid, ip_address=request.ip_address, vlan_tag=request.vlan_tag, mac_address=request.mac_address, asn=request.asn, router_id=request.router_id, vni=request.vni, local_address=request.local_address, port=request.port, bridge=request.bridge, interface=request.interface, remote_address=request.remote_address, remote_as=request.remote_as, local_role=request.local_role, routing_table=request.routing_table, multihop=request.multihop, afi=request.afi, name=request.name, )) No newline at end of file
src/logical_resources/database.py +83 −3 Original line number Original line Diff line number Diff line Loading @@ -3,8 +3,14 @@ class Database: def __init__(self): def __init__(self): "Creating the database to store things" "Creating the database to store things" self.database = {} self.database = {} self.resources = {} def add_device(self, device_uuid, endpoint_uuid, ip_address, vlan_tag, mac_address, ASN, RouterID, def _track_resource(self, resource_type, resource_value): if not resource_value: return self.resources.setdefault((resource_type, resource_value), '') def add_resources(self, device_uuid, endpoint_uuid, ip_address, vlan_tag, mac_address, ASN, RouterID, vni=None, local_address=None, port=None, bridge=None, interface=None, remote_address=None, vni=None, local_address=None, port=None, bridge=None, interface=None, remote_address=None, remote_as=None, local_role=None, routing_table=None, multihop=None, afi=None, name=None): remote_as=None, local_role=None, routing_table=None, multihop=None, afi=None, name=None): if device_uuid not in self.database: if device_uuid not in self.database: Loading Loading @@ -35,10 +41,84 @@ class Database: if vlan_tag: if vlan_tag: self.database[device_uuid][endpoint_uuid]["vlan_tags"].append(vlan_tag) self.database[device_uuid][endpoint_uuid]["vlan_tags"].append(vlan_tag) self._track_resource('ip', ip_address) self._track_resource('vlan', str(vlan_tag) if vlan_tag else '') self._track_resource('asn', str(ASN) if ASN else '') self._track_resource('loopback', RouterID) # Backward-compatible alias kept while callers migrate to add_resources. def add_device(self, device_uuid, endpoint_uuid, ip_address, vlan_tag, mac_address, ASN, RouterID, vni=None, local_address=None, port=None, bridge=None, interface=None, remote_address=None, remote_as=None, local_role=None, routing_table=None, multihop=None, afi=None, name=None): self.add_resources( device_uuid, endpoint_uuid, ip_address, vlan_tag, mac_address, ASN, RouterID, vni=vni, local_address=local_address, port=port, bridge=bridge, interface=interface, remote_address=remote_address, remote_as=remote_as, local_role=local_role, routing_table=routing_table, multihop=multihop, afi=afi, name=name, ) def get_full_db(self): def get_full_db(self): "Return ALLL" "Return ALLL" return self.database return self.database def get_available_resources(self): return [ (resource_type, resource_value, fabric_id) for (resource_type, resource_value), fabric_id in self.resources.items() if not fabric_id ] def reserve_resource(self, resource_type, resource_value, fabric_id): key = (resource_type, resource_value) if key not in self.resources: return False, 'Resource not found' if self.resources[key]: return False, 'Resource already reserved' self.resources[key] = fabric_id return True, 'Resource reserved' def delete_resource(self, resource_type, resource_value): key = (resource_type, resource_value) if key not in self.resources: return False, 'Resource not found' del self.resources[key] return True, 'Resource deleted' def get_allocated_resources(self): return [ (resource_type, resource_value, fabric_id) for (resource_type, resource_value), fabric_id in self.resources.items() if fabric_id ] def get_resource_owner(self, resource_value): for (_, value), fabric_id in self.resources.items(): if value == resource_value: return fabric_id return '' def get_resources_by_type(self, resource_type): return [ (rtype, value, fabric_id) for (rtype, value), fabric_id in self.resources.items() if rtype == resource_type ] # --- DELETE --- # --- DELETE --- def delete_device(self, device_uuid): def delete_device(self, device_uuid): """to remove the entire device""" """to remove the entire device""" Loading
src/logical_resources/service/LogicalResourceServicerImpl.py +93 −4 Original line number Original line Diff line number Diff line import logging import logging from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from common.proto.logical_resources_pb2 import Response, DeviceConfig, Database, DeviceEndpoints from common.proto.logical_resources_pb2 import ( Response, Success, ConfigParameters, DeviceConfig, Database, DeviceEndpoints, ResourceList, ResourceEntry, FabricId, TypedResourceList, ) from common.proto.logical_resources_pb2_grpc import LogicalResourcesServicer from common.proto.logical_resources_pb2_grpc import LogicalResourcesServicer from logical_resources.database import Database as InternalDatabase from logical_resources.database import Database as InternalDatabase import grpc import grpc Loading @@ -15,8 +26,8 @@ class LogicalResourceServicerImpl(LogicalResourcesServicer): LOGGER.debug('Servicer Created') LOGGER.debug('Servicer Created') @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def AddDevice(self, request: DeviceConfig, context : grpc.ServicerContext) -> Response: def AddResources(self, request: ConfigParameters, context : grpc.ServicerContext) -> Response: self.db.add_device( self.db.add_resources( request.device_uuid, request.device_uuid, request.endpoint_uuid, request.endpoint_uuid, request.ip_address, request.ip_address, Loading @@ -37,7 +48,85 @@ class LogicalResourceServicerImpl(LogicalResourcesServicer): request.afi, request.afi, request.name request.name ) ) return Response(success=True, message="Device added to Logical Database") return Response(success=True, message='Resources added to Logical Database') @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetAvailableResource(self, request, context: grpc.ServicerContext) -> ResourceList: reply = ResourceList() for resource_type, resource_value, fabric_id in self.db.get_available_resources(): reply.resources.append(ResourceEntry( type=resource_type, value=resource_value, fabric_id='', allocated=False, )) return reply @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def ReserveResource(self, request, context: grpc.ServicerContext) -> Success: success, message = self.db.reserve_resource( request.tuple.type, request.tuple.value, request.fabric_id.fabric_id, ) return Success(success=success, message=message) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DeleteResource(self, request, context: grpc.ServicerContext) -> Success: success, message = self.db.delete_resource(request.tuple.type, request.tuple.value) return Success(success=success, message=message) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetAllocatedResources(self, request, context: grpc.ServicerContext) -> ResourceList: reply = ResourceList() for resource_type, resource_value, fabric_id in self.db.get_allocated_resources(): reply.resources.append(ResourceEntry( type=resource_type, value=resource_value, fabric_id=fabric_id, allocated=True, )) return reply @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetResourceOwner(self, request, context: grpc.ServicerContext) -> FabricId: return FabricId(fabric_id=self.db.get_resource_owner(request.value)) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetResourcesByType(self, request, context: grpc.ServicerContext) -> TypedResourceList: reply = TypedResourceList(resource_type=request.resource_type) for resource_type, resource_value, fabric_id in self.db.get_resources_by_type(request.resource_type): reply.resources.append(ResourceEntry( type=resource_type, value=resource_value, fabric_id=fabric_id, allocated=bool(fabric_id), )) return reply @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def AddDevice(self, request: DeviceConfig, context : grpc.ServicerContext) -> Response: return self.AddResources(ConfigParameters( device_uuid=request.device_uuid, endpoint_uuid=request.endpoint_uuid, ip_address=request.ip_address, vlan_tag=request.vlan_tag, mac_address=request.mac_address, asn=request.asn, router_id=request.router_id, vni=request.vni, local_address=request.local_address, port=request.port, bridge=request.bridge, interface=request.interface, remote_address=request.remote_address, remote_as=request.remote_as, local_role=request.local_role, routing_table=request.routing_table, multihop=request.multihop, afi=request.afi, name=request.name, ), context) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetDatabase(self, request, context:grpc.ServicerContext) -> Database: def GetDatabase(self, request, context:grpc.ServicerContext) -> Database: Loading
src/logical_resources/test/test_unitary.py +66 −32 Original line number Original line Diff line number Diff line Loading @@ -12,9 +12,16 @@ # See the License for the specific language governing permissions and # See the License for the specific language governing permissions and # limitations under the License. # limitations under the License. import pytest from logical_resources.client.Logicalresourceclient import LogicalResourceClient from logical_resources.client.Logicalresourceclient import LogicalResourceClient from common.proto.logical_resources_pb2 import DeviceConfig, Empty from common.proto.logical_resources_pb2 import ( ConfigParameters, Empty, ResourceReservation, Tuple, FabricId, Value, ResourceTypeQuery, ) from .PrepareTestScenario import ( from .PrepareTestScenario import ( Loading @@ -24,7 +31,7 @@ from .PrepareTestScenario import ( def test_add(logical_resource_client : LogicalResourceClient, logical_resource_service): def test_add(logical_resource_client : LogicalResourceClient, logical_resource_service): add_request = DeviceConfig() add_request = ConfigParameters() add_request.device_uuid = "leaf1" add_request.device_uuid = "leaf1" add_request.endpoint_uuid = "sfp1" add_request.endpoint_uuid = "sfp1" add_request.ip_address = "10.0.0.1" add_request.ip_address = "10.0.0.1" Loading @@ -44,9 +51,30 @@ def test_add(logical_resource_client : LogicalResourceClient, logical_resource_s add_request.multihop = True add_request.multihop = True add_request.afi = "ip,l2vpn" add_request.afi = "ip,l2vpn" add_request.name = "test-device" add_request.name = "test-device" add_reply = logical_resource_client.AddDevice(add_request) add_reply = logical_resource_client.AddResources(add_request) assert add_reply.success == True assert add_reply.success == True available = logical_resource_client.GetAvailableResource(Empty()) assert any(r.type == 'ip' and r.value == '10.0.0.1' for r in available.resources) assert any(r.type == 'vlan' and r.value == '100' for r in available.resources) assert any(r.type == 'asn' and r.value == '65001' for r in available.resources) assert any(r.type == 'loopback' and r.value == '1.1.1.1' for r in available.resources) # Validate key underlay/overlay fields are persisted (MikroTik-style mapping). db_reply = logical_resource_client.GetDatabase(Empty()) endpoint = db_reply.devices['leaf1'].endpoints['sfp1'] assert endpoint.local_role == 'ebgp' assert endpoint.routing_table == 'vrf-dataplane' assert endpoint.multihop is True assert endpoint.afi == 'ip,l2vpn' assert endpoint.vni == 10 assert endpoint.port == 4789 assert endpoint.bridge == 'br-dataplane' assert endpoint.interface == 'vxlan10' assert endpoint.local_address == '192.168.1.1' assert endpoint.remote_address == '10.0.0.2' assert endpoint.remote_as == 65002 # to print what is stored in the datbase after the add operation # to print what is stored in the datbase after the add operation import json import json stored_data = logical_resource_service.servicer_impl.db.get_full_db() stored_data = logical_resource_service.servicer_impl.db.get_full_db() Loading @@ -54,9 +82,9 @@ def test_add(logical_resource_client : LogicalResourceClient, logical_resource_s print(json.dumps(stored_data, indent=2)) print(json.dumps(stored_data, indent=2)) print("==================================\n") print("==================================\n") def test_get_database(logical_resource_client : LogicalResourceClient, logical_resource_service): def test_resource_allocation_and_queries(logical_resource_client : LogicalResourceClient, logical_resource_service): add_request = DeviceConfig() add_request = ConfigParameters() add_request.device_uuid = "leaf2" add_request.device_uuid = "leaf2" add_request.endpoint_uuid = "sfp2" add_request.endpoint_uuid = "sfp2" add_request.ip_address = "10.0.0.2" add_request.ip_address = "10.0.0.2" Loading @@ -76,34 +104,40 @@ def test_get_database(logical_resource_client : LogicalResourceClient, logical_r add_request.multihop = False add_request.multihop = False add_request.afi = "ip" add_request.afi = "ip" add_request.name = "test-device2" add_request.name = "test-device2" add_reply = logical_resource_client.AddDevice(add_request) add_reply = logical_resource_client.AddResources(add_request) assert add_reply.success is True assert add_reply.success is True get_request = Empty() reserve_reply = logical_resource_client.ReserveResource(ResourceReservation( get_reply = logical_resource_client.GetDatabase(get_request) tuple=Tuple(type='ip', value='10.0.0.2'), assert get_reply is not None fabric_id=FabricId(fabric_id='fabric-A'), assert "leaf2" in get_reply.devices )) assert "sfp2" in get_reply.devices["leaf2"].endpoints assert reserve_reply.success is True endpoint_cfg = get_reply.devices["leaf2"].endpoints["sfp2"] assert endpoint_cfg.device_uuid == "leaf2" available_after_reserve = logical_resource_client.GetAvailableResource(Empty()) assert endpoint_cfg.endpoint_uuid == "sfp2" assert not any(r.type == 'ip' and r.value == '10.0.0.2' for r in available_after_reserve.resources) assert endpoint_cfg.ip_address == "10.0.0.2" assert endpoint_cfg.vlan_tag == 200 allocated = logical_resource_client.GetAllocatedResources(Empty()) assert endpoint_cfg.mac_address == "AA:BB:CC:DD:EE:01" assert any(r.type == 'ip' and r.value == '10.0.0.2' and r.fabric_id == 'fabric-A' for r in allocated.resources) assert endpoint_cfg.asn == 65002 assert endpoint_cfg.router_id == "2.2.2.2" owner = logical_resource_client.GetResourceOwner(Value(value='10.0.0.2')) assert endpoint_cfg.vni == 20 assert owner.fabric_id == 'fabric-A' assert endpoint_cfg.local_address == "192.168.2.1" assert endpoint_cfg.port == 4790 typed = logical_resource_client.GetResourcesByType(ResourceTypeQuery(resource_type='ip')) assert endpoint_cfg.bridge == "br-dataplane2" assert typed.resource_type == 'ip' assert endpoint_cfg.interface == "vxlan20" assert any(r.value == '10.0.0.2' for r in typed.resources) assert endpoint_cfg.remote_address == "10.0.0.3" assert endpoint_cfg.remote_as == 65003 delete_reply = logical_resource_client.DeleteResource(ResourceReservation( assert endpoint_cfg.local_role == "ibgp" tuple=Tuple(type='ip', value='10.0.0.2'), assert endpoint_cfg.routing_table == "vrf-dataplane2" fabric_id=FabricId(fabric_id='fabric-A'), assert endpoint_cfg.multihop == False )) assert endpoint_cfg.afi == "ip" assert delete_reply.success is True assert endpoint_cfg.name == "test-device2" allocated_after_delete = logical_resource_client.GetAllocatedResources(Empty()) assert not any(r.type == 'ip' and r.value == '10.0.0.2' for r in allocated_after_delete.resources) owner_after_delete = logical_resource_client.GetResourceOwner(Value(value='10.0.0.2')) assert owner_after_delete.fabric_id == '' # to print what is stored in the datbase after the get operation # to print what is stored in the datbase after the get operation import json import json stored_data = logical_resource_service.servicer_impl.db.get_full_db() stored_data = logical_resource_service.servicer_impl.db.get_full_db() Loading