Commit 956377ec authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

PathComp component:

- assumed all services issued to backend are in admin context
- implemented logic to retrieve plain non-layered connection when layeted connection cannot be processed
- improved error reporting in EroPathToHops
- implemented logic to synthesize a dummy path for single-device service requests
- improved identification of interdomain pathcomp requests
parent e0859852
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -20,12 +20,11 @@ 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.context_queries.Device import get_devices_in_topology
from common.tools.context_queries.Link import get_links_in_topology
from common.tools.context_queries.InterDomain import is_multi_domain
from common.tools.context_queries.InterDomain import is_inter_domain
from common.tools.grpc.Tools import grpc_message_to_json_string
from common.tools.object_factory.Context import json_context_id
from context.client.ContextClient import ContextClient
from pathcomp.frontend.service.algorithms.Factory import get_algorithm
from pathcomp.frontend.service.algorithms.ShortestPathAlgorithm import ShortestPathAlgorithm

LOGGER = logging.getLogger(__name__)

@@ -46,7 +45,7 @@ class PathCompServiceServicerImpl(PathCompServiceServicer):

        context_client = ContextClient()

        if (len(request.services) == 1) and is_multi_domain(context_client, request.services[0].service_endpoint_ids):
        if (len(request.services) == 1) and is_inter_domain(context_client, request.services[0].service_endpoint_ids):
            devices = get_devices_in_topology(context_client, ADMIN_CONTEXT_ID, INTERDOMAIN_TOPOLOGY_UUID)
            links = get_links_in_topology(context_client, ADMIN_CONTEXT_ID, INTERDOMAIN_TOPOLOGY_UUID)
        else:
+30 −3
Original line number Diff line number Diff line
@@ -12,15 +12,42 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from common.proto.pathcomp_pb2 import Algorithm_ShortestPath
from typing import Dict, Optional
from common.proto.pathcomp_pb2 import Algorithm_ShortestPath, PathCompRequest
from ._Algorithm import _Algorithm

class ShortestPathAlgorithm(_Algorithm):
    def __init__(self, algorithm : Algorithm_ShortestPath, class_name=__name__) -> None:
        super().__init__('SP', False, class_name=class_name)

    def add_service_requests(self, requested_services) -> None:
        super().add_service_requests(requested_services)
    def add_service_requests(self, request : PathCompRequest) -> None:
        super().add_service_requests(request)
        for service_request in self.service_list:
            service_request['algId'    ] = self.algorithm_id
            service_request['syncPaths'] = self.sync_paths

    def _single_device_request(self) -> Optional[Dict]:
        if len(self.service_list) != 1: return None
        service = self.service_list[0]
        endpoint_ids = service['service_endpoints_ids']
        if len(endpoint_ids) != 2: return None
        if endpoint_ids[0]['device_id'] != endpoint_ids[-1]['device_id']: return None
        return {'response-list': [{
            'serviceId': service['serviceId'],
            'service_endpoints_ids': [endpoint_ids[0], endpoint_ids[-1]],
            'path': [{
                # not used by now
                #'path-capacity': {'total-size': {'value': 200, 'unit': 0}},
                #'path-latency': {'fixed-latency-characteristic': '2.000000'},
                #'path-cost': {'cost-name': '', 'cost-value': '1.000000', 'cost-algorithm': '0.000000'},
                'devices': [endpoint_ids[0], endpoint_ids[-1]]
            }]
        }]}

    def execute(self, dump_request_filename : Optional[str] = None, dump_reply_filename : Optional[str] = None) -> None:
        # if request is composed of a single service with single device (not supported by backend),
        # produce synthetic reply directly
        self.json_reply = self._single_device_request()
        if self.json_reply is None:
            # otherwise, follow normal logic through the backend
            return super().execute(dump_request_filename, dump_reply_filename)
+8 −2
Original line number Diff line number Diff line
@@ -23,7 +23,8 @@ from pathcomp.frontend.Config import BACKEND_URL
from pathcomp.frontend.service.algorithms.tools.ConstantsMappings import DEVICE_LAYER_TO_SERVICE_TYPE, DeviceLayerEnum
from .tools.EroPathToHops import eropath_to_hops
from .tools.ComposeRequest import compose_device, compose_link, compose_service
from .tools.ComputeSubServices import convert_explicit_path_hops_to_connections
from .tools.ComputeSubServices import (
    convert_explicit_path_hops_to_connections, convert_explicit_path_hops_to_plain_connection)

class _Algorithm:
    def __init__(self, algorithm_id : str, sync_paths : bool, class_name=__name__) -> None:
@@ -204,7 +205,12 @@ class _Algorithm:

            for service_path_ero in response['path']:
                path_hops = eropath_to_hops(service_path_ero['devices'], self.endpoint_to_link_dict)
                try:
                    connections = convert_explicit_path_hops_to_connections(path_hops, self.device_dict, service_uuid)
                except: # pylint: disable=bare-except
                    # if not able to extrapolate sub-services and sub-connections,
                    # assume single service and single connection
                    connections = convert_explicit_path_hops_to_plain_connection(path_hops, service_uuid)

                for connection in connections:
                    connection_uuid,device_layer,path_hops,_ = connection
+5 −3
Original line number Diff line number Diff line
@@ -34,9 +34,11 @@ def compose_topology_id(topology_id : TopologyId) -> Dict:
    return {'contextId': context_uuid, 'topology_uuid': topology_uuid}

def compose_service_id(service_id : ServiceId) -> Dict:
    context_uuid = service_id.context_id.context_uuid.uuid

    if len(context_uuid) == 0: context_uuid = DEFAULT_CONTEXT_UUID
    # force context_uuid to be always DEFAULT_CONTEXT_UUID for simplicity
    # for interdomain contexts are managed in a particular way
    #context_uuid = service_id.context_id.context_uuid.uuid
    #if len(context_uuid) == 0: context_uuid = DEFAULT_CONTEXT_UUID
    context_uuid = DEFAULT_CONTEXT_UUID

    service_uuid = service_id.service_uuid.uuid
    return {'contextId': context_uuid, 'service_uuid': service_uuid}
+16 −0
Original line number Diff line number Diff line
@@ -94,3 +94,19 @@ def convert_explicit_path_hops_to_connections(
    connections.append(connection_stack.get())
    assert connection_stack.empty()
    return connections

def convert_explicit_path_hops_to_plain_connection(
    path_hops : List[Dict], main_connection_uuid : str
) -> List[Tuple[str, DeviceLayerEnum, List[str], List[str]]]:

    connection : Tuple[str, DeviceLayerEnum, List[str], List[str]] = \
        (main_connection_uuid, DeviceLayerEnum.PACKET_DEVICE, [], [])

    last_device_uuid = None
    for path_hop in path_hops:
        device_uuid = path_hop['device']
        if last_device_uuid == device_uuid: continue
        connection[2].append(path_hop)
        last_device_uuid = device_uuid

    return [connection]
Loading