diff --git a/src/pathcomp/frontend/service/PathCompServiceServicerImpl.py b/src/pathcomp/frontend/service/PathCompServiceServicerImpl.py index 2ec045c2755bd6d2079a4ee8ee96d4c3ad4cef36..2987a62aefdbde78513d0546cf65e3feb83a4798 100644 --- a/src/pathcomp/frontend/service/PathCompServiceServicerImpl.py +++ b/src/pathcomp/frontend/service/PathCompServiceServicerImpl.py @@ -13,15 +13,16 @@ # limitations under the License. import grpc, json, logging, requests, uuid -from typing import List -from common.proto.context_pb2 import Connection, Empty, EndPointId +from typing import Dict, Tuple +from common.proto.context_pb2 import Empty, EndPointId, Service from common.proto.pathcomp_pb2 import PathCompReply, PathCompRequest from common.proto.pathcomp_pb2_grpc import PathCompServiceServicer from common.rpc_method_wrapper.Decorator import create_metrics, safe_and_metered_rpc_method -from common.tools.grpc.Tools import grpc_message_to_json, grpc_message_to_json_string +from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient from pathcomp.frontend.Config import BACKEND_HOST, BACKEND_PORT, BACKEND_URL from pathcomp.frontend.service.tools.ComposeRequest import compose_device, compose_link, compose_service +#from pathcomp.frontend.service.tools.Constants import CapacityUnit LOGGER = logging.getLogger(__name__) @@ -55,6 +56,12 @@ class PathCompServiceServicerImpl(PathCompServiceServicer): compose_service(grpc_service, algorithm) for grpc_service in request.services ] + get_service_key = lambda service_id: (service_id['contextId'], service_id['service_uuid']) + service_dict : Dict[Tuple[str, str], Tuple[Dict, Service]] = { + get_service_key(json_service['serviceId']): (json_service, grpc_service) + for json_service,grpc_service in zip(service_list, request.services) + } + #LOGGER.info('service_dict = {:s}'.format(str(service_dict))) # TODO: consider filtering resources @@ -71,6 +78,14 @@ class PathCompServiceServicerImpl(PathCompServiceServicer): compose_device(grpc_device) for grpc_device in grpc_devices.devices ] + endpoint_dict : Dict[str, Dict[str, Tuple[Dict, EndPointId]]] = { + json_device['device_Id']: { + json_endpoint['endpoint_id']['endpoint_uuid']: (json_endpoint['endpoint_id'], grpc_endpoint.endpoint_id) + for json_endpoint,grpc_endpoint in zip(json_device['device_endpoints'], grpc_device.device_endpoints) + } + for json_device,grpc_device in zip(device_list, grpc_devices.devices) + } + #LOGGER.info('endpoint_dict = {:s}'.format(str(endpoint_dict))) grpc_links = context_client.ListLinks(Empty()) link_list = [ @@ -95,31 +110,53 @@ class PathCompServiceServicerImpl(PathCompServiceServicer): LOGGER.info('status_code={:s} reply={:s}'.format( str(reply.status_code), str(reply.content.decode('UTF-8')))) - - + json_reply = reply.json() + response_list = json_reply.get('response-list', []) reply = PathCompReply() - # TODO: compose reply populating reply.services and reply.connections + for response in response_list: + service_key = get_service_key(response['serviceId']) + tuple_service = service_dict.get(service_key) + if tuple_service is None: raise Exception('ServiceKey({:s}) not found'.format(str(service_key))) + json_service, grpc_service = tuple_service - for service in request.services: # TODO: implement support for multi-point services - service_endpoint_ids = service.service_endpoint_ids + service_endpoint_ids = grpc_service.service_endpoint_ids if len(service_endpoint_ids) != 2: raise NotImplementedError('Service must have 2 endpoints') - a_endpoint_id, z_endpoint_id = service_endpoint_ids[0], service_endpoint_ids[-1] - - connection_uuid = str(uuid.uuid4()) - connection_path_hops : List[EndPointId] = [] - connection_path_hops.extend([ - grpc_message_to_json(a_endpoint_id), - grpc_message_to_json(z_endpoint_id), - ]) - connection = Connection(**{ - 'connection_id': {'connection_uuid': {'uuid': connection_uuid}}, - 'service_id': grpc_message_to_json(service.service_id), - 'path_hops_endpoint_ids': connection_path_hops, - 'sub_service_ids': [], - }) - reply.connections.append(connection) #pylint: disable=no-member - reply.services.append(service) #pylint: disable=no-member + + service = reply.services.add() + service.CopyFrom(grpc_service) + + connection = reply.connections.add() + connection.connection_id.connection_uuid.uuid = str(uuid.uuid4()) + connection.service_id.CopyFrom(service.service_id) + + no_path_issue = response.get('noPath', {}).get('issue') + if no_path_issue is not None: + # no path found: leave connection with no endpoints + # no_path_issue == 1 => no path due to a constraint + continue + + service_paths = response['path'] + + for service_path in service_paths: + # ... "path-capacity": {"total-size": {"value": 200, "unit": 0}}, + # ... "path-latency": {"fixed-latency-characteristic": "10.000000"}, + # ... "path-cost": {"cost-name": "", "cost-value": "5.000000", "cost-algorithm": "0.000000"}, + #path_capacity = service_path['path-capacity']['total-size'] + #path_capacity_value = path_capacity['value'] + #path_capacity_unit = CapacityUnit(path_capacity['unit']) + #path_latency = service_path['path-latency']['fixed-latency-characteristic'] + #path_cost = service_path['path-cost'] + #path_cost_name = path_cost['cost-name'] + #path_cost_value = path_cost['cost-value'] + #path_cost_algorithm = path_cost['cost-algorithm'] + + path_endpoints = service_path['devices'] + for endpoint in path_endpoints: + device_uuid = endpoint['device_id'] + endpoint_uuid = endpoint['endpoint_uuid'] + endpoint_id = connection.path_hops_endpoint_ids.add() + endpoint_id.CopyFrom(endpoint_dict[device_uuid][endpoint_uuid][1]) LOGGER.info('[Compute] end ; reply = {:s}'.format(grpc_message_to_json_string(reply))) return reply diff --git a/src/pathcomp/frontend/service/tools/ComposeRequest.py b/src/pathcomp/frontend/service/tools/ComposeRequest.py index 2cd4185f05abaa2221a20b8c2e61b96763ae49a5..68395523b4bb122483479451cc08b26094032b5c 100644 --- a/src/pathcomp/frontend/service/tools/ComposeRequest.py +++ b/src/pathcomp/frontend/service/tools/ComposeRequest.py @@ -12,48 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from enum import IntEnum from typing import Dict from common.proto.context_pb2 import Constraint, Device, EndPointId, Link, Service, ServiceId, TopologyId from common.tools.grpc.Tools import grpc_message_to_json_string - -class CapacityUnit(IntEnum): - TB = 0 - TBPS = 1 - GB = 2 - GBPS = 3 - MB = 4 - MBPS = 5 - KB = 6 - KBPS = 7 - GHZ = 8 - MHZ = 9 - -class LinkPortDirection(IntEnum): - BIDIRECTIONAL = 0 - INPUT = 1 - OUTPUT = 2 - UNKNOWN = 3 - -class TerminationDirection(IntEnum): - BIDIRECTIONAL = 0 - SINK = 1 - SOURCE = 2 - UNKNOWN = 3 - -class TerminationState(IntEnum): - CAN_NEVER_TERMINATE = 0 - NOT_TERMINATED = 1 - TERMINATED_SERVER_TO_CLIENT = 2 - TERMINATED_CLIENT_TO_SERVER = 3 - TERMINATED_BIDIRECTIONAL = 4 - PERMENANTLY_TERMINATED = 5 - TERMINATION_STATE_UNKNOWN = 6 - -class LinkForwardingDirection(IntEnum): - BIDIRECTIONAL = 0 - UNIDIRECTIONAL = 1 - UNKNOWN = 2 +from .Constants import CapacityUnit, LinkForwardingDirection, LinkPortDirection, TerminationDirection, TerminationState def compose_topology_id(topology_id : TopologyId) -> Dict: context_uuid = topology_id.context_id.context_uuid.uuid @@ -127,7 +89,7 @@ def compose_link(grpc_link : Link) -> Dict: for link_endpoint_id in grpc_link.link_endpoint_ids ] - forwarding_direction = LinkForwardingDirection.UNIDIRECTIONAL.value + forwarding_direction = LinkForwardingDirection.BIDIRECTIONAL.value total_potential_capacity = compose_capacity(200, CapacityUnit.MBPS.value) available_capacity = compose_capacity(200, CapacityUnit.MBPS.value) cost_characteristics = compose_cost_characteristics('linkcost', '1', '0') diff --git a/src/pathcomp/frontend/service/tools/Constants.py b/src/pathcomp/frontend/service/tools/Constants.py new file mode 100644 index 0000000000000000000000000000000000000000..cb774669c97144fa65afdaf0f3373c67a67c3212 --- /dev/null +++ b/src/pathcomp/frontend/service/tools/Constants.py @@ -0,0 +1,66 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# 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. + +from enum import IntEnum + +class CapacityUnit(IntEnum): + TB = 0 + TBPS = 1 + GB = 2 + GBPS = 3 + MB = 4 + MBPS = 5 + KB = 6 + KBPS = 7 + GHZ = 8 + MHZ = 9 + +CAPACITY_MULTIPLIER = { + CapacityUnit.TB : 1.e12, + CapacityUnit.TBPS : 1.e12, + CapacityUnit.GB : 1.e9, + CapacityUnit.GBPS : 1.e9, + CapacityUnit.MB : 1.e6, + CapacityUnit.MBPS : 1.e6, + CapacityUnit.KB : 1.e3, + CapacityUnit.KBPS : 1.e3, + CapacityUnit.GHZ : 1.e9, + CapacityUnit.MHZ : 1.e6, +} + +class LinkPortDirection(IntEnum): + BIDIRECTIONAL = 0 + INPUT = 1 + OUTPUT = 2 + UNKNOWN = 3 + +class TerminationDirection(IntEnum): + BIDIRECTIONAL = 0 + SINK = 1 + SOURCE = 2 + UNKNOWN = 3 + +class TerminationState(IntEnum): + CAN_NEVER_TERMINATE = 0 + NOT_TERMINATED = 1 + TERMINATED_SERVER_TO_CLIENT = 2 + TERMINATED_CLIENT_TO_SERVER = 3 + TERMINATED_BIDIRECTIONAL = 4 + PERMENANTLY_TERMINATED = 5 + TERMINATION_STATE_UNKNOWN = 6 + +class LinkForwardingDirection(IntEnum): + BIDIRECTIONAL = 0 + UNIDIRECTIONAL = 1 + UNKNOWN = 2 diff --git a/src/pathcomp/frontend/tests/Objects.py b/src/pathcomp/frontend/tests/Objects.py index f4785d7ae670efcc5525d6b00c4baf3acf3f22b1..586911180bcb7379382b644cac1eb338a31bd522 100644 --- a/src/pathcomp/frontend/tests/Objects.py +++ b/src/pathcomp/frontend/tests/Objects.py @@ -21,10 +21,10 @@ from common.tools.object_factory.Link import get_link_uuid, json_link, json_link from common.tools.object_factory.Service import get_service_uuid, json_service_l3nm_planned from common.tools.object_factory.Topology import json_topology, json_topology_id -def compose_device(device_uuid, endpoint_uuids): +def compose_device(device_uuid, endpoint_uuids, topology_id=None): device_id = json_device_id(device_uuid) endpoints = [(endpoint_uuid, 'copper', []) for endpoint_uuid in endpoint_uuids] - endpoints = json_endpoints(device_id, endpoints, topology_id=TOPOLOGY_A_ID) + endpoints = json_endpoints(device_id, endpoints, topology_id=topology_id) device = json_device_emulated_packet_router_disabled(device_uuid, endpoints=endpoints) return device_id, endpoints, device @@ -45,32 +45,36 @@ CONTEXT_ID = json_context_id(DEFAULT_CONTEXT_UUID) CONTEXT = json_context(DEFAULT_CONTEXT_UUID) # ----- Domains -------------------------------------------------------------------------------------------------------- -TOPOLOGY_ADMIN_ID = json_topology_id(DEFAULT_TOPOLOGY_UUID, context_id=CONTEXT_ID) -TOPOLOGY_ADMIN = json_topology(DEFAULT_TOPOLOGY_UUID, context_id=CONTEXT_ID) +TOPOLOGY_ADMIN_UUID = DEFAULT_TOPOLOGY_UUID +TOPOLOGY_ADMIN_ID = json_topology_id(TOPOLOGY_ADMIN_UUID, context_id=CONTEXT_ID) +TOPOLOGY_ADMIN = json_topology(TOPOLOGY_ADMIN_UUID, context_id=CONTEXT_ID) -TOPOLOGY_A_ID = json_topology_id('A', context_id=CONTEXT_ID) -TOPOLOGY_A = json_topology('A', context_id=CONTEXT_ID) +TOPOLOGY_A_UUID = 'A' +TOPOLOGY_A_ID = json_topology_id(TOPOLOGY_A_UUID, context_id=CONTEXT_ID) +TOPOLOGY_A = json_topology(TOPOLOGY_A_UUID, context_id=CONTEXT_ID) -TOPOLOGY_B_ID = json_topology_id('B', context_id=CONTEXT_ID) -TOPOLOGY_B = json_topology('B', context_id=CONTEXT_ID) +TOPOLOGY_B_UUID = 'B' +TOPOLOGY_B_ID = json_topology_id(TOPOLOGY_B_UUID, context_id=CONTEXT_ID) +TOPOLOGY_B = json_topology(TOPOLOGY_B_UUID, context_id=CONTEXT_ID) -TOPOLOGY_C_ID = json_topology_id('C', context_id=CONTEXT_ID) -TOPOLOGY_C = json_topology('C', context_id=CONTEXT_ID) +TOPOLOGY_C_UUID = 'C' +TOPOLOGY_C_ID = json_topology_id(TOPOLOGY_C_UUID, context_id=CONTEXT_ID) +TOPOLOGY_C = json_topology(TOPOLOGY_C_UUID, context_id=CONTEXT_ID) # ----- Devices Domain A ----------------------------------------------------------------------------------------------- -DEVICE_A1_ID, DEVICE_A1_ENDPOINTS, DEVICE_A1 = compose_device('A1', ['1', '2', '2000']) -DEVICE_A2_ID, DEVICE_A2_ENDPOINTS, DEVICE_A2 = compose_device('A2', ['1', '2', '1001']) -DEVICE_A3_ID, DEVICE_A3_ENDPOINTS, DEVICE_A3 = compose_device('A3', ['1', '2']) +DEVICE_A1_ID, DEVICE_A1_ENDPOINTS, DEVICE_A1 = compose_device('A1', ['1', '2', '2000'], topology_id=TOPOLOGY_A_ID) +DEVICE_A2_ID, DEVICE_A2_ENDPOINTS, DEVICE_A2 = compose_device('A2', ['1', '2', '1001'], topology_id=TOPOLOGY_A_ID) +DEVICE_A3_ID, DEVICE_A3_ENDPOINTS, DEVICE_A3 = compose_device('A3', ['1', '2' ], topology_id=TOPOLOGY_A_ID) # ----- Devices Domain B ----------------------------------------------------------------------------------------------- -DEVICE_B1_ID, DEVICE_B1_ENDPOINTS, DEVICE_B1 = compose_device('B1', ['1', '2', '2000']) -DEVICE_B2_ID, DEVICE_B2_ENDPOINTS, DEVICE_B2 = compose_device('B2', ['1', '2', '1002']) -DEVICE_B3_ID, DEVICE_B3_ENDPOINTS, DEVICE_B3 = compose_device('B3', ['1', '2']) +DEVICE_B1_ID, DEVICE_B1_ENDPOINTS, DEVICE_B1 = compose_device('B1', ['1', '2', '2000'], topology_id=TOPOLOGY_B_ID) +DEVICE_B2_ID, DEVICE_B2_ENDPOINTS, DEVICE_B2 = compose_device('B2', ['1', '2', '1002'], topology_id=TOPOLOGY_B_ID) +DEVICE_B3_ID, DEVICE_B3_ENDPOINTS, DEVICE_B3 = compose_device('B3', ['1', '2' ], topology_id=TOPOLOGY_B_ID) # ----- Devices Domain C ----------------------------------------------------------------------------------------------- -DEVICE_C1_ID, DEVICE_C1_ENDPOINTS, DEVICE_C1 = compose_device('C1', ['1', '2', '1001']) -DEVICE_C2_ID, DEVICE_C2_ENDPOINTS, DEVICE_C2 = compose_device('C2', ['1', '2']) -DEVICE_C3_ID, DEVICE_C3_ENDPOINTS, DEVICE_C3 = compose_device('C3', ['1', '2', '1002']) +DEVICE_C1_ID, DEVICE_C1_ENDPOINTS, DEVICE_C1 = compose_device('C1', ['1', '2', '1001'], topology_id=TOPOLOGY_C_ID) +DEVICE_C2_ID, DEVICE_C2_ENDPOINTS, DEVICE_C2 = compose_device('C2', ['1', '2' ], topology_id=TOPOLOGY_C_ID) +DEVICE_C3_ID, DEVICE_C3_ENDPOINTS, DEVICE_C3 = compose_device('C3', ['1', '2', '1002'], topology_id=TOPOLOGY_C_ID) # ----- InterDomain Links ---------------------------------------------------------------------------------------------- LINK_A2_C3_ID, LINK_A2_C3 = compose_link(DEVICE_A2_ENDPOINTS[2], DEVICE_C3_ENDPOINTS[2]) @@ -94,12 +98,12 @@ LINK_C2_C3_ID, LINK_C2_C3 = compose_link(DEVICE_C2_ENDPOINTS[1], DEVICE_C3_ENDPO # ----- Service -------------------------------------------------------------------------------------------------------- SERVICE_A1_B1 = compose_service(DEVICE_A1_ENDPOINTS[2], DEVICE_B1_ENDPOINTS[2], constraints=[ json_constraint('bandwidth[gbps]', 10.0), - json_constraint('latency[ms]', 5.0), + json_constraint('latency[ms]', 12.0), ]) # ----- Containers ----------------------------------------------------------------------------------------------------- -CONTEXTS = [CONTEXT] -TOPOLOGIES = [TOPOLOGY_ADMIN, TOPOLOGY_A, TOPOLOGY_B, TOPOLOGY_C] +CONTEXTS = [ CONTEXT] +TOPOLOGIES = [ TOPOLOGY_ADMIN, TOPOLOGY_A, TOPOLOGY_B, TOPOLOGY_C] DEVICES = [ DEVICE_A1, DEVICE_A2, DEVICE_A3, DEVICE_B1, DEVICE_B2, DEVICE_B3, DEVICE_C1, DEVICE_C2, DEVICE_C3, ] @@ -107,4 +111,28 @@ LINKS = [ LINK_A2_C3, LINK_C1_B2, LINK_A1_A2, LINK_A1_A3, LINK_A2_A3, LINK_B1_B2, LINK_B1_B3, LINK_B2_B3, LINK_C1_C2, LINK_C1_C3, LINK_C2_C3, ] -SERVICES = [SERVICE_A1_B1] +SERVICES = [ SERVICE_A1_B1] + +OBJECTS_PER_TOPOLOGY = [ + (TOPOLOGY_ADMIN_ID, + [ DEVICE_A1_ID, DEVICE_A2_ID, DEVICE_A3_ID, + DEVICE_B1_ID, DEVICE_B2_ID, DEVICE_B3_ID, + DEVICE_C1_ID, DEVICE_C2_ID, DEVICE_C3_ID, ], + [ LINK_A2_C3_ID, LINK_C1_B2_ID, + LINK_A1_A2_ID, LINK_A1_A3_ID, LINK_A2_A3_ID, + LINK_B1_B2_ID, LINK_B1_B3_ID, LINK_B2_B3_ID, + LINK_C1_C2_ID, LINK_C1_C3_ID, LINK_C2_C3_ID, ], + ), + (TOPOLOGY_A_ID, + [ DEVICE_A1_ID, DEVICE_A2_ID, DEVICE_A3_ID, ], + [ LINK_A1_A2_ID, LINK_A1_A3_ID, LINK_A2_A3_ID, ], + ), + (TOPOLOGY_B_ID, + [ DEVICE_B1_ID, DEVICE_B2_ID, DEVICE_B3_ID, ], + [ LINK_B1_B2_ID, LINK_B1_B3_ID, LINK_B2_B3_ID, ], + ), + (TOPOLOGY_C_ID, + [ DEVICE_C1_ID, DEVICE_C2_ID, DEVICE_C3_ID, ], + [ LINK_C1_C2_ID, LINK_C1_C3_ID, LINK_C2_C3_ID, ], + ), +] diff --git a/src/pathcomp/frontend/tests/test_unitary.py b/src/pathcomp/frontend/tests/test_unitary.py index 90d31bf0ab1f4fb41ddcfede5c4c308a88223c6d..7f614e69c2c6594c2fbf7f1b85b5ae7a9f71f328 100644 --- a/src/pathcomp/frontend/tests/test_unitary.py +++ b/src/pathcomp/frontend/tests/test_unitary.py @@ -19,7 +19,7 @@ from common.tools.grpc.Tools import grpc_message_to_json from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from pathcomp.frontend.client.PathCompClient import PathCompClient -from .Objects import CONTEXTS, DEVICES, LINKS, SERVICES, TOPOLOGIES +from .Objects import CONTEXTS, DEVICES, LINKS, OBJECTS_PER_TOPOLOGY, SERVICES, TOPOLOGIES from .PrepareTestScenario import ( # pylint: disable=unused-import # be careful, order of symbols is important here! mock_service, pathcomp_service, context_client, device_client, pathcomp_client) @@ -37,6 +37,23 @@ def test_prepare_environment( for device in DEVICES : device_client .AddDevice (Device (**device )) for link in LINKS : context_client.SetLink (Link (**link )) + for topology_id, device_ids, link_ids in OBJECTS_PER_TOPOLOGY: + topology = Topology() + topology.CopyFrom(context_client.GetTopology(TopologyId(**topology_id))) + + device_ids_in_topology = {device_id.device_uuid.uuid for device_id in topology.device_ids} + func_device_id_not_added = lambda device_id: device_id['device_uuid']['uuid'] not in device_ids_in_topology + func_device_id_json_to_grpc = lambda device_id: DeviceId(**device_id) + device_ids_to_add = list(map(func_device_id_json_to_grpc, filter(func_device_id_not_added, device_ids))) + topology.device_ids.extend(device_ids_to_add) + + link_ids_in_topology = {link_id.link_uuid.uuid for link_id in topology.link_ids} + func_link_id_not_added = lambda link_id: link_id['link_uuid']['uuid'] not in link_ids_in_topology + func_link_id_json_to_grpc = lambda link_id: LinkId(**link_id) + link_ids_to_add = list(map(func_link_id_json_to_grpc, filter(func_link_id_not_added, link_ids))) + topology.link_ids.extend(link_ids_to_add) + + context_client.SetTopology(topology) def test_request_service( pathcomp_client : PathCompClient): # pylint: disable=redefined-outer-name