diff --git a/proto/context.proto b/proto/context.proto index f5dec30796a8426f512947d369b8db5f5889471a..7763b8032a67327dd871c59bdb85bd22390e2a28 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -239,6 +239,7 @@ enum ServiceTypeEnum { SERVICETYPE_L3NM = 1; SERVICETYPE_L2NM = 2; SERVICETYPE_TAPI_CONNECTIVITY_SERVICE = 3; + SERVICETYPE_P4 = 4; } enum ServiceStatusEnum { diff --git a/src/service/service/database/ServiceModel.py b/src/service/service/database/ServiceModel.py index cf756af60a8178a9ae2fda2a5fa5ddeebc73912c..7a4492dcf087cca7a6ecb56708b558ccc7c6d9c3 100644 --- a/src/service/service/database/ServiceModel.py +++ b/src/service/service/database/ServiceModel.py @@ -34,6 +34,7 @@ class ORM_ServiceTypeEnum(Enum): L3NM = ServiceTypeEnum.SERVICETYPE_L3NM L2NM = ServiceTypeEnum.SERVICETYPE_L2NM TAPI_CONNECTIVITY_SERVICE = ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE + P4_SERVICE = ServiceTypeEnum.SERVICETYPE_P4 grpc_to_enum__service_type = functools.partial( grpc_to_enum, ServiceTypeEnum, ORM_ServiceTypeEnum) diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index 98113ba30fb095a29a2142e592b7759d2634eab9..9975ba19c6a0a7e4a3aebc7c5fe72b1a07133b9a 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -24,6 +24,7 @@ SERVICE_TYPE_VALUES = { ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM, ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, + ServiceTypeEnum.SERVICETYPE_P4 } DEVICE_DRIVER_VALUES = { diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 89e717722d152ce978dca10a768119d9e9adaf1e..6f9f3c937a3f3ab4272337abf4dc142a293119e1 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -17,6 +17,7 @@ from ..service_handler_api.FilterFields import FilterFieldEnum from .l2nm_emulated.L2NMEmulatedServiceHandler import L2NMEmulatedServiceHandler from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler +from .p4.p4_service_handler import P4ServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler SERVICE_HANDLERS = [ @@ -44,4 +45,10 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API, } ]), + (P4ServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_P4, + FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, + } + ]), ] diff --git a/src/service/service/service_handlers/p4/__init__.py b/src/service/service/service_handlers/p4/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9953c820575d42fa88351cc8de022d880ba96e6a --- /dev/null +++ b/src/service/service/service_handlers/p4/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/src/service/service/service_handlers/p4/p4_service_handler.py b/src/service/service/service_handlers/p4/p4_service_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..690c6adbcf359f7ebb5df8b372f162fb48f81e12 --- /dev/null +++ b/src/service/service/service_handlers/p4/p4_service_handler.py @@ -0,0 +1,224 @@ +# 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. + +""" +P4 service handler for the TeraFlow SDN controller. +""" + +import json +import logging +from typing import Any, List, Optional, Tuple, Union +from common.orm.Database import Database +from common.orm.HighLevel import get_object +from common.orm.backend.Tools import key_to_str +# from common.proto.context_pb2 import Device +# from common.tools.object_factory.ConfigRule import json_config_rule_delete,\ +# json_config_rule_set +from common.type_checkers.Checkers import chk_type +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from service.service.database.ConfigModel import get_config_rules +from service.service.database.ContextModel import ContextModel +# from service.service.database.DeviceModel import DeviceModel +from service.service.database.ServiceModel import ServiceModel +from service.service.service_handler_api._ServiceHandler import _ServiceHandler + +LOGGER = logging.getLogger(__name__) + + +class P4ServiceHandler(_ServiceHandler): + """ + P4ServiceHandler class inherits the abstract _ServiceHandler class + to provision network connectivity upon P4 devices. + + Attributes + ---------- + db_service: ServiceModel + The service instance from the local in-memory database. + database: Database + The instance of the local in-memory database. + context_client: ContextClient + gRPC client to get Context services + device_client : DeviceClient + gRPC client to get Device services + settings: map + Extra settings required by the service handler. + """ + def __init__(self, # pylint: disable=super-init-not-called + db_service: ServiceModel, + database: Database, + context_client: ContextClient, + device_client : DeviceClient, + **settings) -> None: + self.__db_service = db_service + self.__database = database + self.__context_client = context_client + self.__device_client = device_client + + self.__db_context: ContextModel = get_object( + self.__database, ContextModel, self.__db_service.context_fk) + str_service_key = key_to_str( + [self.__db_context.context_uuid, self.__db_service.service_uuid]) + db_config = get_config_rules( + self.__database, str_service_key, "running") + self.__resolver = None + self.__config = None + + def SetEndpoint(self, endpoints: List[Tuple[str, str, Optional[str]]]) \ + -> List[Union[bool, Exception]]: + """ + Create/Update service endpoints. + + :param endpoints: list of tuples, each containing a device_uuid, + endpoint_uuid and, optionally, the topology_uuid of the endpoint to add. + :return: list of results for endpoint changes requested. + Return values must be in the same order as the requested endpoints. + If an endpoint is properly added, True must be returned; + otherwise, the Exception that is raised during processing + must be returned. + """ + chk_type("endpoints", endpoints, list) + if len(endpoints) == 0: + return [] + return [] + + def DeleteEndpoint(self, endpoints: List[Tuple[str, str, Optional[str]]]) \ + -> List[Union[bool, Exception]]: + """ + Delete service endpoints. + + :param endpoints: list of tuples, each containing a device_uuid, + endpoint_uuid and, optionally, the topology_uuid of the endpoint to + delete. + :return: list of results for endpoint deletions requested. + Return values must be in the same order as the requested endpoints. + If an endpoint is properly deleted, True must be returned; + otherwise, the Exception that is raised during processing + must be returned. + """ + chk_type("endpoints", endpoints, list) + if len(endpoints) == 0: + return [] + return [] + + def SetConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ + Create/Update service constraints. + + :param constraints: List of tuples, each containing a constraint_type + and the new constraint_value to be set. + :return: List of results for constraint changes requested. + Return values must be in the same order as the requested constraints. + If a constraint is properly set, True must be returned; + otherwise, the Exception that is raised during the processing + must be returned. + """ + chk_type("constraints", constraints, list) + if len(constraints) == 0: + return [] + + msg = f"SetConstraint() not yet implemented by the" \ + f" P4 service handler. \nThe following constraints " \ + f"are being ignored: {str(constraints)}" + LOGGER.warning(msg) + return [Exception(msg) for _ in range(len(constraints))] + + def DeleteConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ + Delete service constraints. + + :param constraints: List of tuples, each containing a constraint_type + pointing to the constraint to be deleted, and a constraint_value + containing possible additionally required values to locate the + constraint to be removed. + :return: List of results for constraint deletions requested. + Return values must be in the same order as the requested constraints. + If a constraint is properly deleted, True must be returned; + otherwise, the Exception that is raised during the processing + must be returned. + """ + chk_type("constraints", constraints, list) + if len(constraints) == 0: + return [] + + msg = f"DeleteConstraint() not yet implemented by the" \ + f" P4 service handler. \nThe following constraints " \ + f"are being ignored: {str(constraints)}" + LOGGER.warning(msg) + return [Exception(msg) for _ in range(len(constraints))] + + def SetConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ + Create/Update configuration for a list of service resources. + + :param resources: List of tuples, each containing a resource_key + pointing to the resource to be modified, and a resource_value + containing the new value to be set. + :return: List of results for resource key changes requested. + Return values must be in the same order as the requested resource keys. + If a resource is properly set, True must be returned; + otherwise, the Exception that is raised during the processing + must be returned. + """ + chk_type("resources", resources, list) + if len(resources) == 0: + return [] + + results = [] + for resource in resources: + try: + resource_key, resource_value = resource + resource_value = json.loads(resource_value) + # Do the job + # results.append(True) + except Exception as ex: # pylint: disable=broad-except + LOGGER.exception( + "Failed to execute SetConfig(%s)", str(resource)) + results.append(ex) + + return results + + def DeleteConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ + Delete configuration for a list of service resources. + + :param resources: List of tuples, each containing a resource_key + pointing to the resource to be modified, and a resource_value containing + possible additionally required values to locate the value to be removed. + :return: List of results for resource key deletions requested. + Return values must be in the same order as the requested resource keys. + If a resource is properly deleted, True must be returned; + otherwise, the Exception that is raised during the processing + must be returned. + """ + chk_type("resources", resources, list) + if len(resources) == 0: + return [] + + results = [] + for resource in resources: + try: + resource_key, _ = resource + # Do the job + # results.append(True) + except Exception as ex: # pylint: disable=broad-except + LOGGER.exception( + "Failed to execute DeleteConfig(%s)", str(resource)) + results.append(ex) + + return results diff --git a/src/service/tests/test_unitary.py b/src/service/tests/test_unitary.py index e12ec2bc4dda856d76acd50c90af4b7d7941fb00..bc25dbfbb634a852efab062b9294a022a73d7a35 100644 --- a/src/service/tests/test_unitary.py +++ b/src/service/tests/test_unitary.py @@ -15,12 +15,11 @@ import copy, grpc, logging, pytest from common.proto.context_pb2 import ( Context, ContextId, Device, DeviceId, Link, LinkId, Service, ServiceId, Topology, TopologyId) -from common.tests.PytestGenerateTests import pytest_generate_tests # (required) pylint: disable=unused-import from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from service.client.ServiceClient import ServiceClient -from .PrepareTestScenario import ( # pylint: disable=unused-import +from .PrepareTestScenario import ( # pylint: disable=unused-import # be careful, order of symbols is important here! mock_service, service_service, context_client, device_client, service_client)