Commit 4b02fa16 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

PathComp - Frontend component:

Unitary test:
- Corrected Objects definition
- Added devices and links to topology

Frontend component:
- Implemented parsing of backend reply and composition of front-end reply
- Other minor arrangements
parent 744d4443
Loading
Loading
Loading
Loading
+61 −24
Original line number Diff line number Diff line
@@ -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
+2 −40
Original line number Diff line number Diff line
@@ -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')
+66 −0
Original line number Diff line number Diff line
# 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
+51 −23
Original line number Diff line number Diff line
@@ -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,7 +98,7 @@ 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 -----------------------------------------------------------------------------------------------------
@@ -108,3 +112,27 @@ LINKS = [ LINK_A2_C3, LINK_C1_B2,
                LINK_B1_B2, LINK_B1_B3, LINK_B2_B3,
                LINK_C1_C2, LINK_C1_C3, LINK_C2_C3, ]
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,    ],
    ),
]
+18 −1
Original line number Diff line number Diff line
@@ -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