Commit 92666714 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

PathComp:

- Finished implementation of KDisjointPath algorithm
- Adjusted Algorithm interface
- Moved documentation and example test commands to misc folder
parent 1a85a101
Loading
Loading
Loading
Loading
+53 −33
Original line number Diff line number Diff line
@@ -13,7 +13,8 @@
# limitations under the License.

import copy
from typing import Optional
from typing import Dict, List, Optional, Set, Tuple
from common.proto.context_pb2 import Link
from common.proto.pathcomp_pb2 import Algorithm_KDisjointPath, Algorithm_KShortestPath, PathCompReply
from ._Algorithm import _Algorithm
from .KShortestPathAlgorithm import KShortestPathAlgorithm
@@ -23,6 +24,36 @@ class KDisjointPathAlgorithm(_Algorithm):
        super().__init__('KDP', False, class_name=class_name)
        self.num_disjoint = algorithm.num_disjoint

    def get_link_from_endpoint(self, endpoint : Dict) -> Tuple[Dict, Link]:
        device_uuid = endpoint['device_id']
        endpoint_uuid = endpoint['endpoint_uuid']
        item = self.endpoint_to_link_dict.get((device_uuid, endpoint_uuid))
        if item is None:
            MSG = 'Link for Endpoint({:s}, {:s}) not found'
            self.logger.warning(MSG.format(device_uuid, endpoint_uuid))
            return None
        return item

    def path_to_links(self, path_endpoints : List[Dict]) -> Tuple[List[Dict], Set[str]]:
        path_links = list()
        path_link_ids = set()
        for endpoint in path_endpoints:
            link_tuple = self.get_link_from_endpoint(endpoint)
            if link_tuple is None: continue
            json_link,_ = link_tuple
            json_link_id = json_link['link_Id']
            if len(path_links) == 0 or path_links[-1]['link_Id'] != json_link_id:
                path_links.append(json_link)
                path_link_ids.add(json_link_id)
        return path_links, path_link_ids

    def remove_traversed_links(self, link_list : List[Dict], path_endpoints : List[Dict]):
        _, path_link_ids = self.path_to_links(path_endpoints)
        new_link_list = list(filter(lambda l: l['link_Id'] not in path_link_ids, link_list))
        self.logger.info('cur_link_list = {:s}'.format(str(link_list)))
        self.logger.info('new_link_list = {:s}'.format(str(new_link_list)))
        return new_link_list

    def execute(self, dump_request_filename: Optional[str] = None, dump_reply_filename: Optional[str] = None) -> None:
        algorithm = KShortestPathAlgorithm(Algorithm_KShortestPath(k_inspection=0, k_return=1))
        algorithm.sync_paths = True
@@ -35,49 +66,38 @@ class KDisjointPathAlgorithm(_Algorithm):
        algorithm.service_list = self.service_list
        algorithm.service_dict = self.service_dict

        disjoint_paths = dict()
        Path = List[Dict]
        Path_NoPath = Optional[Path] # None = no path, list = path
        self.json_reply : Dict[Tuple[str, str], List[Path_NoPath]] = dict()

        for num_path in range(self.num_disjoint):
            algorithm.execute('ksp-{:d}-request.json'.format(num_path), 'ksp-{:d}-reply.txt'.format(num_path))
            #dump_request_filename = 'ksp-{:d}-request.json'.format(num_path)
            #dump_reply_filename   = 'ksp-{:d}-reply.txt'.format(num_path)
            #algorithm.execute(dump_request_filename, dump_reply_filename)
            algorithm.execute()
            response_list = algorithm.json_reply.get('response-list', [])
            for response in response_list:
                service_id = response['serviceId']
                service_key = (service_id['contextId'], service_id['service_uuid'])
                disjoint_paths_service = disjoint_paths.setdefault(service_key, list())
                json_reply_service = self.json_reply.setdefault(service_key, list())

                no_path_issue = response.get('noPath', {}).get('issue')
                if no_path_issue is not None:
                    disjoint_paths_service.append(None)
                    json_reply_service.append(None)
                    continue

                path_endpoints = response['path'][0]['devices']
                path_links = list()
                path_link_ids = set()
                for endpoint in path_endpoints:
                    device_uuid = endpoint['device_id']
                    endpoint_uuid = endpoint['endpoint_uuid']
                    item = algorithm.endpoint_to_link_dict.get((device_uuid, endpoint_uuid))
                    if item is None:
                        MSG = 'Link for Endpoint({:s}, {:s}) not found'
                        self.logger.warning(MSG.format(device_uuid, endpoint_uuid))
                        continue
                    json_link,_ = item
                    json_link_id = json_link['link_Id']
                    if len(path_links) == 0 or path_links[-1]['link_Id'] != json_link_id:
                        path_links.append(json_link)
                        path_link_ids.add(json_link_id)
                self.logger.info('path_links = {:s}'.format(str(path_links)))
                disjoint_paths_service.append(path_links)

                new_link_list = list(filter(lambda l: l['link_Id'] not in path_link_ids, algorithm.link_list))
                self.logger.info('algorithm.link_list = {:s}'.format(str(algorithm.link_list)))
                self.logger.info('new_link_list = {:s}'.format(str(new_link_list)))
                algorithm.link_list = new_link_list


            # TODO: find used links and remove them from algorithm.link_list
            # TODO: compose disjoint path found
                json_reply_service.append(path_endpoints)
                algorithm.link_list = self.remove_traversed_links(algorithm.link_list, path_endpoints)

        self.logger.info('self.json_reply = {:s}'.format(str(self.json_reply)))

        self.logger.info('disjoint_paths = {:s}'.format(str(disjoint_paths)))
        self.json_reply = {}
    def get_reply(self) -> PathCompReply:
        reply = PathCompReply()
        for service_key,paths in self.json_reply.items():
            grpc_service = self.add_service_to_reply(reply, service_key[0], service_key[1])
            for path_endpoints in paths:
                if path_endpoints is None: continue
                grpc_connection = self.add_connection_to_reply(reply, grpc_service)
                self.add_path_to_connection(grpc_connection, path_endpoints)
        return reply
+36 −27
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@

import json, logging, requests, uuid
from typing import Dict, List, Optional, Tuple
from common.proto.context_pb2 import Device, DeviceList, EndPointId, Link, LinkList, Service
from common.proto.context_pb2 import Connection, Device, DeviceList, EndPointId, Link, LinkList, Service
from common.proto.pathcomp_pb2 import PathCompReply, PathCompRequest
from common.tools.grpc.Tools import grpc_message_to_json_string
from pathcomp.frontend.Config import BACKEND_URL
@@ -32,13 +32,13 @@ class _Algorithm:
        self.algorithm_id = algorithm_id
        self.sync_paths = sync_paths

        self.device_list : List[Device] = list()
        self.device_list : List[Dict] = list()
        self.device_dict : Dict[str, Tuple[Dict, Device]] = dict()
        self.endpoint_dict : Dict[str, Dict[str, Tuple[Dict, EndPointId]]] = dict()
        self.link_list : List[Link] = list()
        self.link_list : List[Dict] = list()
        self.link_dict : Dict[str, Tuple[Dict, Link]] = dict()
        self.endpoint_to_link_dict : Dict[Tuple[str, str], Tuple[Dict, Link]] = dict()
        self.service_list : List[Service] = list()
        self.service_list : List[Dict] = list()
        self.service_dict : Dict[Tuple[str, str], Tuple[Dict, Service]] = dict()

    def add_devices(self, grpc_devices : DeviceList) -> None:
@@ -105,15 +105,24 @@ class _Algorithm:
        
        self.json_reply = reply.json()

    def get_reply(self) -> PathCompReply:
        response_list = self.json_reply.get('response-list', [])
        reply = PathCompReply()
        for response in response_list:
            service_id = response['serviceId']
            service_key = (service_id['contextId'], service_id['service_uuid'])
    def add_path_to_connection(self, connection : Connection, path_endpoints : List[Dict]) -> None:
        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(self.endpoint_dict[device_uuid][endpoint_uuid][1])

    def add_connection_to_reply(self, reply : PathCompReply, service : Service) -> Connection:
        connection = reply.connections.add()
        connection.connection_id.connection_uuid.uuid = str(uuid.uuid4())
        connection.service_id.CopyFrom(service.service_id)
        return connection

    def add_service_to_reply(self, reply : PathCompReply, context_uuid : str, service_uuid : str) -> Service:
        service_key = (context_uuid, service_uuid)
        tuple_service = self.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
        _, grpc_service = tuple_service

        # TODO: implement support for multi-point services
        service_endpoint_ids = grpc_service.service_endpoint_ids
@@ -122,9 +131,14 @@ class _Algorithm:
        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)
        return grpc_service

    def get_reply(self) -> PathCompReply:
        response_list = self.json_reply.get('response-list', [])
        reply = PathCompReply()
        for response in response_list:
            service_id = response['serviceId']
            grpc_service = self.add_service_to_reply(reply, service_id['contextId'], service_id['service_uuid'])

            no_path_issue = response.get('noPath', {}).get('issue')
            if no_path_issue is not None:
@@ -132,9 +146,10 @@ class _Algorithm:
                # no_path_issue == 1 => no path due to a constraint
                continue

            service_paths = response['path']
            for service_path in response['path']:
                grpc_connection = self.add_connection_to_reply(reply, grpc_service)
                self.add_path_to_connection(grpc_connection, service_path['devices'])

            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"},
@@ -147,10 +162,4 @@ class _Algorithm:
                #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(self.endpoint_dict[device_uuid][endpoint_uuid][1])
        return reply
+73 −0
Original line number Diff line number Diff line
{
    "connections": [
        {
            "connection_id": {"connection_uuid": {"uuid": "d8cb463a-77c4-4149-80fc-32c4842a191d"}},
            "path_hops_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "DC1-GW"}}, "endpoint_uuid": {"uuid": "int"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "DC1"}}},
                {"device_id": {"device_uuid": {"uuid": "DC1-GW"}}, "endpoint_uuid": {"uuid": "eth1"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "DC1"}}},
                {"device_id": {"device_uuid": {"uuid": "CS1-GW1"}}, "endpoint_uuid": {"uuid": "200"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "CS1"}}},
                {"device_id": {"device_uuid": {"uuid": "TN-R2"}}, "endpoint_uuid": {"uuid": "1"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "TN"}}},
                {"device_id": {"device_uuid": {"uuid": "TN-R3"}}, "endpoint_uuid": {"uuid": "100"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "TN"}}},
                {"device_id": {"device_uuid": {"uuid": "CS2-GW1"}}, "endpoint_uuid": {"uuid": "1000"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "CS2"}}},
                {"device_id": {"device_uuid": {"uuid": "DC2-GW"}}, "endpoint_uuid": {"uuid": "int"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "DC2"}}}
            ],
            "service_id": {
                "context_id": {"context_uuid": {"uuid": "admin"}},
                "service_uuid": {"uuid": "svc:DC1-GW/int==DC2-GW/int"}
            },
            "sub_service_ids": []
        },
        {
            "connection_id": {"connection_uuid": {"uuid": "9f7c5075-0736-4d2a-8435-b8e8c37fa6ea"}},
            "path_hops_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "DC1-GW"}}, "endpoint_uuid": {"uuid": "int"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "DC1"}}},
                {"device_id": {"device_uuid": {"uuid": "DC1-GW"}}, "endpoint_uuid": {"uuid": "eth2"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "DC1"}}},
                {"device_id": {"device_uuid": {"uuid": "CS1-GW2"}}, "endpoint_uuid": {"uuid": "200"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "CS1"}}},
                {"device_id": {"device_uuid": {"uuid": "TN-R1"}}, "endpoint_uuid": {"uuid": "3"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "TN"}}},
                {"device_id": {"device_uuid": {"uuid": "TN-R3"}}, "endpoint_uuid": {"uuid": "200"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "TN"}}},
                {"device_id": {"device_uuid": {"uuid": "CS2-GW2"}}, "endpoint_uuid": {"uuid": "1000"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "CS2"}}},
                {"device_id": {"device_uuid": {"uuid": "DC2-GW"}}, "endpoint_uuid": {"uuid": "int"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "DC2"}}}
            ],
            "service_id": {
                "context_id": {"context_uuid": {"uuid": "admin"}},
                "service_uuid": {"uuid": "svc:DC1-GW/int==DC2-GW/int"}
            },
            "sub_service_ids": []
        }
    ],
    "services": [
        {
            "service_id": {
                "context_id": {"context_uuid": {"uuid": "admin"}},
                "service_uuid": {"uuid": "svc:DC1-GW/int==DC2-GW/int"}
            },
            "service_type": "SERVICETYPE_L3NM",
            "service_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "DC1-GW"}}, "endpoint_uuid": {"uuid": "int"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "DC1"}}},
                {"device_id": {"device_uuid": {"uuid": "DC2-GW"}}, "endpoint_uuid": {"uuid": "int"},
                    "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "DC2"}}}
            ],
            "service_status": {"service_status": "SERVICESTATUS_PLANNED"},
            "service_constraints": [
                {"custom": {"constraint_type": "bandwidth[gbps]", "constraint_value": "10.0"}},
                {"custom": {"constraint_type": "latency[ms]", "constraint_value": "12.0"}}
            ],
            "service_config": {"config_rules": []}
        }
    ]
}
 No newline at end of file
+2 −0
Original line number Diff line number Diff line
@@ -29,3 +29,5 @@ docker network rm teraflowbridge
docker images --filter="dangling=true" --quiet | xargs -r docker rmi

docker exec -i pathcomp bash -c "pytest --log-level=INFO --verbose pathcomp/tests/test_unitary.py"

./scripts/run_tests_locally-pathcomp-frontend.sh