diff --git a/proto/context.proto b/proto/context.proto index b33750e80b07b0300cf2aa8b526597bfea9a9ee5..7b453a51e3aae48f0bd075a34a5203e8d05932ee 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -20,6 +20,7 @@ import "google/protobuf/any.proto"; import "acl.proto"; import "kpi_sample_types.proto"; +import "tapi_lsp.proto"; service ContextService { rpc ListContextIds (Empty ) returns ( ContextIdList ) {} @@ -78,7 +79,6 @@ service ContextService { rpc RemoveConnection (ConnectionId ) returns ( Empty ) {} rpc GetConnectionEvents(Empty ) returns (stream ConnectionEvent ) {} - // ------------------------------ Experimental ----------------------------- rpc GetOpticalConfig (Empty ) returns (OpticalConfigList) {} rpc SetOpticalConfig (OpticalConfig ) returns (OpticalConfigId ) {} @@ -203,7 +203,6 @@ message Component { // Defined previously in this sectio Uuid component_uuid = 1; string name = 2; string type = 3; - map attributes = 4; // dict[attr.name => json.dumps(attr.value)] string parent = 5; } @@ -331,6 +330,7 @@ enum ServiceTypeEnum { SERVICETYPE_L1NM = 8; SERVICETYPE_INT = 9; SERVICETYPE_ACL = 10; + SERVICETYPE_TAPI_LSP = 11; } enum ServiceStatusEnum { @@ -544,11 +544,17 @@ message ConfigRule_ACL { acl.AclRuleSet rule_set = 2; } +message ConfigRule_TAPI_LSP { + EndPointId endpoint_id = 1; + tapi_lsp.TapiLspRuleSet rule_set = 2; +} + message ConfigRule { ConfigActionEnum action = 1; oneof config_rule { ConfigRule_Custom custom = 2; ConfigRule_ACL acl = 3; + ConfigRule_TAPI_LSP tapi_lsp = 4; } } @@ -579,7 +585,6 @@ message Location { oneof location { string region = 1; GPS_Position gps_position = 2; - string interface=3; string circuit_pack=4; } @@ -711,7 +716,7 @@ message OpticalLinkDetails { string dst_port = 3; string local_peer_port = 4; string remote_peer_port = 5 ; - bool used = 6 ; + bool used = 6 ; map c_slots = 7; map l_slots = 8; map s_slots = 9; diff --git a/proto/tapi_lsp.proto b/proto/tapi_lsp.proto new file mode 100644 index 0000000000000000000000000000000000000000..3d9d717eac78426ccd486c7b1f0a8554b160ebbb --- /dev/null +++ b/proto/tapi_lsp.proto @@ -0,0 +1,24 @@ +// Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// 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. + +syntax = "proto3"; +package tapi_lsp; + +message TapiLspRuleSet { + string src = 1; + string dst = 2; + string uuid = 3; + string bw = 4; + string unit = 5; +} diff --git a/src/common/tools/object_factory/Service.py b/src/common/tools/object_factory/Service.py index 2bfe50ccca7459f4ad6d30bfcd7f007460557c4b..bf71bf83399f9c18430c11c251bdb6ad51804cbb 100644 --- a/src/common/tools/object_factory/Service.py +++ b/src/common/tools/object_factory/Service.py @@ -92,4 +92,14 @@ def json_service_p4_planned( return json_service( service_uuid, ServiceTypeEnum.SERVICETYPE_L1NM, context_id=json_context_id(context_uuid), status=ServiceStatusEnum.SERVICESTATUS_PLANNED, endpoint_ids=endpoint_ids, constraints=constraints, - config_rules=config_rules) \ No newline at end of file + config_rules=config_rules) + +def json_service_tapi_lsp_planned( + service_uuid : str, endpoint_ids : List[Dict] = [], constraints : List[Dict] = [], + config_rules : List[Dict] = [], context_uuid : str = DEFAULT_CONTEXT_NAME + ): + + return json_service( + service_uuid, ServiceTypeEnum.SERVICETYPE_TAPI_LSP, context_id=json_context_id(context_uuid), + status=ServiceStatusEnum.SERVICESTATUS_PLANNED, endpoint_ids=endpoint_ids, constraints=constraints, + config_rules=config_rules) diff --git a/src/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py index e41b0d0d36442703fd649a5afe9d61eae7dbb6d5..ae174d249be6597d95f3d6fdda805d9e039cf9e5 100644 --- a/src/common/type_checkers/Assertions.py +++ b/src/common/type_checkers/Assertions.py @@ -109,6 +109,7 @@ def validate_service_type_enum(message): 'SERVICETYPE_E2E', 'SERVICETYPE_OPTICAL_CONNECTIVITY', 'SERVICETYPE_QKD', + 'SERVICETYPE_TAPI_LSP', ] def validate_service_state_enum(message): @@ -146,6 +147,7 @@ def validate_uuid(message, allow_empty=False): CONFIG_RULE_TYPES = { 'custom', 'acl', + 'tapi_lsp' } def validate_config_rule(message): assert isinstance(message, dict) diff --git a/src/context/service/database/ConfigRule.py b/src/context/service/database/ConfigRule.py index c9db5488c15e57f805cb8b7f87c4f4bde4b2c665..85c46cb3339dc2f67ae212e5dbd25f3e0643dfa8 100644 --- a/src/context/service/database/ConfigRule.py +++ b/src/context/service/database/ConfigRule.py @@ -71,6 +71,9 @@ def compose_config_rules_data( _, _, endpoint_uuid = endpoint_get_uuid(config_rule.acl.endpoint_id, allow_random=False) rule_set_name = config_rule.acl.rule_set.name configrule_name = '{:s}:{:s}:{:s}:{:s}'.format(parent_kind, kind.value, endpoint_uuid, rule_set_name) + elif kind == ConfigRuleKindEnum.TAPI_LSP: + _, _, endpoint_uuid = endpoint_get_uuid(config_rule.tapi_lsp.endpoint_id, allow_random=False) + configrule_name = '{:s}:{:s}:{:s}'.format(parent_kind, kind.value, endpoint_uuid) else: MSG = 'Name for ConfigRule({:s}) cannot be inferred '+\ '(device_uuid={:s}, service_uuid={:s}, slice_uuid={:s})' diff --git a/src/context/service/database/Service.py b/src/context/service/database/Service.py index 9076fc025d86457cab48fe321f55926ba728cb6c..58221dd8876eaa3ef9cd0cabfd8600f4721f3572 100644 --- a/src/context/service/database/Service.py +++ b/src/context/service/database/Service.py @@ -88,6 +88,8 @@ def service_set(db_engine : Engine, messagebroker : MessageBroker, request : Ser service_type = grpc_to_enum__service_type(request.service_type) if service_type is None and request.service_type == ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY: service_type = "OPTICAL_CONNECTIVITY" + if service_type is None and request.service_type == ServiceTypeEnum.SERVICETYPE_TAPI_LSP: + service_type = "TAPI_LSP" service_status = grpc_to_enum__service_status(request.service_status.service_status) diff --git a/src/context/service/database/models/ConfigRuleModel.py b/src/context/service/database/models/ConfigRuleModel.py index 5462df672331d8b2ce28756a818d1e6b290aa2ed..266d66cc61bf72849417335562da384eb19fb4c9 100644 --- a/src/context/service/database/models/ConfigRuleModel.py +++ b/src/context/service/database/models/ConfigRuleModel.py @@ -23,6 +23,7 @@ from ._Base import _Base class ConfigRuleKindEnum(enum.Enum): CUSTOM = 'custom' ACL = 'acl' + TAPI_LSP = 'tapi_lsp' class DeviceConfigRuleModel(_Base): __tablename__ = 'device_configrule' diff --git a/src/context/service/database/models/enums/ServiceType.py b/src/context/service/database/models/enums/ServiceType.py index 93271c3b94e3ffa2911dc0c8023332a8975ff2b5..937766404d1885014f7eeec564c615f95e2fd96e 100644 --- a/src/context/service/database/models/enums/ServiceType.py +++ b/src/context/service/database/models/enums/ServiceType.py @@ -33,6 +33,7 @@ class ORM_ServiceTypeEnum(enum.Enum): QKD = ServiceTypeEnum.SERVICETYPE_QKD INT = ServiceTypeEnum.SERVICETYPE_INT ACL = ServiceTypeEnum.SERVICETYPE_ACL + TAPI_LSP = ServiceTypeEnum.SERVICETYPE_TAPI_LSP grpc_to_enum__service_type = functools.partial( grpc_to_enum, ServiceTypeEnum, ORM_ServiceTypeEnum) diff --git a/src/device/service/Tools.py b/src/device/service/Tools.py index a62a0d702bdb50041f14e2fd462478fd05675692..30fc59211d72a65f9f4c58fa3f908286dc600b9e 100644 --- a/src/device/service/Tools.py +++ b/src/device/service/Tools.py @@ -155,7 +155,7 @@ def populate_endpoints( _sub_device.name = resource_value['name'] _sub_device.device_type = resource_value['type'] _sub_device.device_operational_status = resource_value['status'] - + # Sub-devices might not have a driver assigned. if 'drivers' in resource_value: drivers = resource_value['drivers'] @@ -299,9 +299,9 @@ def populate_initial_config_rules(device_uuid : str, device_config : DeviceConfi def compute_rules_to_add_delete( device : Device, request : Device ) -> Tuple[List[Tuple[str, Any]], List[Tuple[str, Any]]]: - # convert config rules from context into a dictionary + # convert config rules from context into a dictionary context_config_rules = {} - for config_rule in device.device_config.config_rules: + for config_rule in device.device_config.config_rules: config_rule_kind = config_rule.WhichOneof('config_rule') if config_rule_kind == 'custom': # process "custom" rules context_config_rules[config_rule.custom.resource_key] = config_rule.custom.resource_value # get the resource value of the rule resource @@ -310,25 +310,42 @@ def compute_rules_to_add_delete( endpoint_uuid = config_rule.acl.endpoint_id.endpoint_uuid.uuid # get the endpoint name acl_ruleset_name = config_rule.acl.rule_set.name # get the acl name ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/acl_ruleset[{:s}]' - key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_uuid, acl_ruleset_name) + key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_uuid, acl_ruleset_name) context_config_rules[key_or_path] = grpc_message_to_json(config_rule.acl) # get the resource value of the acl - + elif config_rule_kind == 'tapi_lsp': + device_uuid = config_rule.tapi_lsp.endpoint_id.device_id.device_uuid.uuid # get the device name + endpoint_uuid = config_rule.tapi_lsp.endpoint_id.endpoint_uuid.uuid # get the endpoint name request_config_rules = [] + tapi_lsp_ruleset_name = config_rule.tapi_lsp.rule_set.name # get the ip_link name + TAPI_LSP_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/tapi_lsp_ruleset[{:s}]' + key_or_path = TAPI_LSP_KEY_TEMPLATE.format(device_uuid, endpoint_uuid, tapi_lsp_ruleset_name) + context_config_rules[key_or_path] = grpc_message_to_json(config_rule.tapi_lsp) + LOGGER.debug(f'context_config_rules [{key_or_path}] = {context_config_rules[key_or_path]}') + request_config_rules = [] for config_rule in request.device_config.config_rules: config_rule_kind = config_rule.WhichOneof('config_rule') - if config_rule_kind == 'custom': # resource management of "custom" rule + if config_rule_kind == 'custom': # resource management of "custom" rule request_config_rules.append(( config_rule.action, config_rule.custom.resource_key, config_rule.custom.resource_value )) - elif config_rule_kind == 'acl': # resource management of "acl" rule + elif config_rule_kind == 'acl': # resource management of "acl" rule device_uuid = config_rule.acl.endpoint_id.device_id.device_uuid.uuid endpoint_uuid = config_rule.acl.endpoint_id.endpoint_uuid.uuid acl_ruleset_name = config_rule.acl.rule_set.name ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/acl_ruleset[{:s}]' - key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_uuid, acl_ruleset_name) + key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_uuid, acl_ruleset_name) request_config_rules.append(( config_rule.action, key_or_path, grpc_message_to_json(config_rule.acl) )) + elif config_rule_kind == 'tapi_lsp': # resource management of "tapi_lsp" rule + device_uuid = config_rule.tapi_lsp.endpoint_id.device_id.device_uuid.uuid + endpoint_uuid = config_rule.tapi_lsp.endpoint_id.endpoint_uuid.uuid + TAPI_LSP_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/tapi_lsp_ruleset' + key_or_path = TAPI_LSP_KEY_TEMPLATE.format(device_uuid, endpoint_uuid) + request_config_rules.append(( + config_rule.action, key_or_path, grpc_message_to_json(config_rule.tapi_lsp) + )) + LOGGER.debug(f'context_config_rules= {request_config_rules}') resources_to_set : List[Tuple[str, Any]] = [] # key, value resources_to_delete : List[Tuple[str, Any]] = [] # key, value @@ -407,7 +424,7 @@ def subscribe_kpi(request : MonitoringSettings, driver : _Driver, monitoring_loo return errors def unsubscribe_kpi(request : MonitoringSettings, driver : _Driver, monitoring_loops : MonitoringLoops) -> List[str]: - kpi_uuid = request.kpi_id.kpi_id.uuid + kpi_uuid = request.kpi_id.kpi_id.uuid kpi_details = monitoring_loops.get_kpi_by_uuid(kpi_uuid) if kpi_details is None: @@ -502,7 +519,7 @@ def extract_resources(config : dict, device : Device) -> list[list[dict], dict]: else : resources.append(is_key_existed('channel_namespace', config)) resources.append(is_key_existed('add_transceiver', config)) - + conditions['is_opticalband'] = is_opticalband if 'flow' in config: #for tuple_value in config['flow'][device.name]: @@ -510,7 +527,7 @@ def extract_resources(config : dict, device : Device) -> list[list[dict], dict]: dest_vals = [] handled_flow = [] for tuple_value in config['flow']: - source_port = None + source_port = None destination_port = None source_port_uuid, destination_port_uuid = tuple_value if source_port_uuid != '0': diff --git a/src/device/service/drivers/optical_tfs/OpticalTfsDriver.py b/src/device/service/drivers/optical_tfs/OpticalTfsDriver.py index 9cb2371b6eaeed2e754bb2b463462862a16b27ea..3b0e5191f5e5b408f4134c949af4fa6063b8d32c 100644 --- a/src/device/service/drivers/optical_tfs/OpticalTfsDriver.py +++ b/src/device/service/drivers/optical_tfs/OpticalTfsDriver.py @@ -20,6 +20,7 @@ from common.type_checkers.Checkers import chk_string, chk_type from device.service.driver_api._Driver import _Driver, RESOURCE_ENDPOINTS, RESOURCE_SERVICES from device.service.driver_api.ImportTopologyEnum import ImportTopologyEnum, get_import_topology from .TfsApiClient import TfsApiClient +from .templates.tools import create_request #from .TfsOpticalClient import TfsOpticalClient LOGGER = logging.getLogger(__name__) @@ -119,15 +120,25 @@ class OpticalTfsDriver(_Driver): self.tac.check_credentials() for resource in resources: LOGGER.info('resource = {:s}'.format(str(resource))) - resource_key, resource_value = resource - try: - resource_value = json.loads(resource_value) - self.tac.setup_service(resource_value) - results.append((resource_key, True)) - except Exception as e: - MSG = 'Unhandled error processing resource_key({:s})' - LOGGER.exception(MSG.format(str(resource_key))) - results.append((resource_key, e)) + if 'tapi_lsp' in str(resource): + LOGGER.info('Processing tapi_lsp resource') + try: + create_request(resource) + LOGGER.info('Request created successfully') + results.append((resource, True)) + except Exception as e: + MSG = 'Invalid resource_value type: expected dict, got {:s}' + results.append((resource, e)) + else: + resource_key, resource_value = resource + try: + resource_value = json.loads(resource_value) + self.tac.setup_service(resource_value) + results.append((resource_key, True)) + except Exception as e: + MSG = 'Unhandled error processing resource_key({:s})' + LOGGER.exception(MSG.format(str(resource_key))) + results.append((resource_key, e)) return results @metered_subclass_method(METRICS_POOL) diff --git a/src/device/service/drivers/optical_tfs/templates/lsp.json b/src/device/service/drivers/optical_tfs/templates/lsp.json new file mode 100644 index 0000000000000000000000000000000000000000..54845029e01524e0855c7a27c2c647c1e00d4e54 --- /dev/null +++ b/src/device/service/drivers/optical_tfs/templates/lsp.json @@ -0,0 +1,37 @@ +{ + "tapi-connectivity:connectivity-service" : [ + { + "connectivity-direction" : "BIDIRECTIONAL", + "end-point" : [ + { + "direction:" : "BIDIRECTIONAL", + "layer-protocol-name" : "PHOTONIC_MEDIA", + "layer-protocol-qualifier" : "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + "local-id" : "", + "service-interface-point" : { + "service-interface-point-uuid" : "" + } + }, + { + "direction:" : "BIDIRECTIONAL", + "layer-protocol-name" : "PHOTONIC_MEDIA", + "layer-protocol-qualifier" : "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + "local-id" : "", + "service-interface-point" : { + "service-interface-point-uuid" : "" + } + } + ], + "layer-protocol-name" : "PHOTONIC_MEDIA", + "layer-protocol-qualifier" : "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + "requested-capacity" : { + "total-size" : { + "unit" : "", + "value" : "" + } + }, + "route-objective-function" : "10000", + "uuid" : "" + } + ] +} diff --git a/src/device/service/drivers/optical_tfs/templates/tools.py b/src/device/service/drivers/optical_tfs/templates/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..b77419b920c28e26d0d4c780676801a42a91d532 --- /dev/null +++ b/src/device/service/drivers/optical_tfs/templates/tools.py @@ -0,0 +1,49 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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. + +import json +import logging +import os +import requests + +LOGGER = logging.getLogger(__name__) + +def create_request(resource_value): + LOGGER.info("Creating request for resource_value: %s", resource_value) + try: + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + json_path = os.path.join(BASE_DIR, 'lsp.json') + with open(json_path, 'r', encoding='utf-8') as f: + template = json.load(f) + + rule_set = resource_value[1]['rule_set'] + + svc = template["tapi-connectivity:connectivity-service"][0] + svc["end-point"][0]["service-interface-point"]["service-interface-point-uuid"] = rule_set["src"] + svc["end-point"][1]["service-interface-point"]["service-interface-point-uuid"] = rule_set["dst"] + svc["requested-capacity"]["total-size"]["unit"] = rule_set["unit"] + svc["requested-capacity"]["total-size"]["value"] = rule_set["bw"] + svc["uuid"] = rule_set["uuid"] + + url = "http://11.1.1.101:4901/3fb0df67-1c1d-546f-966a-8202f66677c0/restconf/data/tapi-common:context/tapi-connectivity:connectivity-context" + headers = { + "Content-Type": "application/json", + "Accept": "application/json" + } + response = requests.post(url, headers=headers, json=template, timeout=10) + return response + + except (OSError, json.JSONDecodeError, requests.RequestException) as e: + LOGGER.error("Error creating request: %s", str(e)) + diff --git a/src/pathcomp/frontend/service/algorithms/_Algorithm.py b/src/pathcomp/frontend/service/algorithms/_Algorithm.py index a5bfe1352c09f71929719d63a3a869f34eca8edc..f7f3decf84204a7620c94b09ed4edfe9fbef650a 100644 --- a/src/pathcomp/frontend/service/algorithms/_Algorithm.py +++ b/src/pathcomp/frontend/service/algorithms/_Algorithm.py @@ -24,7 +24,7 @@ from pathcomp.frontend.Config import BACKEND_URL from .tools.EroPathToHops import eropath_to_hops from .tools.ComposeConfigRules import ( compose_device_config_rules, compose_l2nm_config_rules, compose_l3nm_config_rules, compose_tapi_config_rules, - generate_neighbor_endpoint_config_rules + generate_neighbor_endpoint_config_rules, compose_tapi_lsp_config_rules ) from .tools.ComposeRequest import compose_device, compose_link, compose_service from .tools.ComputeSubServices import ( @@ -136,7 +136,7 @@ class _Algorithm: if reply.status_code not in {requests.codes.ok}: # pylint: disable=no-member raise Exception('Backend error({:s}) for request({:s})'.format( str(self.raw_reply), json.dumps(request, sort_keys=True))) - + self.json_reply = reply.json() def add_connection_to_reply( @@ -189,6 +189,9 @@ class _Algorithm: elif service_type == ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE and rules_nb == 0: compose_tapi_config_rules(config_rules, service.service_config.config_rules) self.logger.info("Installing default rules for TAPI service") + elif service_type == ServiceTypeEnum.SERVICETYPE_TAPI_LSP: + compose_tapi_lsp_config_rules(config_rules, service.service_config.config_rules) + self.logger.info("Installing default rules for TAPI LSP service") else: MSG = 'Unhandled generic Config Rules for service {:s} {:s}' self.logger.warning(MSG.format(str(service_uuid), str(ServiceTypeEnum.Name(service_type)))) @@ -307,7 +310,7 @@ class _Algorithm: service_key = (context_uuid, service_uuid) grpc_service = grpc_services.get(service_key) if grpc_service is None: raise Exception('Service({:s}) not found'.format(str(service_key))) - + #if connection_uuid in grpc_connections: continue grpc_connection = self.add_connection_to_reply(reply, str(uuid.uuid4()), grpc_service, path_hops) #grpc_connections[connection_uuid] = grpc_connection diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py index 073c3474fe888a0daf0e20187f46809bbd859b0a..3db09ecc4580221a4bbf902ce63acbb23b0a3cbc 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py @@ -54,6 +54,15 @@ TAPI_SETTINGS_FIELD_DEFAULTS = { 'direction' : 'UNIDIRECTIONAL', } +TAPI_LSP_SETINGS_FIELD_DEFAULTS = { + 'lsp_type' : 'TAPI_LSP_TYPE_UNI', + 'lsp_direction' : 'TAPI_LSP_DIRECTION_UNI', + 'lsp_bandwidth' : 50.0, + 'lsp_bandwidth_unit': 'GHz', + 'lsp_layer_protocol_name': 'PHOTONIC_MEDIA', + 'lsp_layer_protocol_qualifier': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC', +} + def find_custom_config_rule(config_rules : List, resource_name : str) -> Optional[Dict]: resource_value : Optional[Dict] = None for config_rule in config_rules: @@ -153,7 +162,7 @@ def compose_device_config_rules( device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys)) if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue - + LOGGER.debug('[compose_device_config_rules] adding acl config rule') subservice_config_rules.append(config_rule) @@ -202,6 +211,11 @@ def compose_device_config_rules( LOGGER.debug('[compose_device_config_rules] end') +def compose_tapi_lsp_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None: + CONFIG_RULES: List[Tuple[str, dict]] = [(SETTINGS_RULE_NAME, TAPI_LSP_SETINGS_FIELD_DEFAULTS)] + for rule_name, defaults in CONFIG_RULES: + compose_config_rules(main_service_config_rules, subservice_config_rules, rule_name, defaults) + def pairwise(iterable : Iterable) -> Tuple[Iterable, Iterable]: # TODO: To be replaced by itertools.pairwise() when we move to Python 3.10 # Python 3.10 introduced method itertools.pairwise() diff --git a/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py b/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py index 2f55db0c668d653e08c9cdbcf3361f83d92cbded..f7fa4f2e12f163711c1a4c4f215ab0468b89439a 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py @@ -42,14 +42,16 @@ OPTICAL_DEVICE_TYPES = { DeviceTypeEnum.OPTICAL_TRANSPONDER, DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER, } -SERVICE_TYPE_L2NM = {ServiceTypeEnum.SERVICETYPE_L2NM} -SERVICE_TYPE_L3NM = {ServiceTypeEnum.SERVICETYPE_L3NM} -SERVICE_TYPE_LXNM = {ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM} -SERVICE_TYPE_TAPI = {ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE} +SERVICE_TYPE_L2NM = {ServiceTypeEnum.SERVICETYPE_L2NM} +SERVICE_TYPE_L3NM = {ServiceTypeEnum.SERVICETYPE_L3NM} +SERVICE_TYPE_LXNM = {ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM} +SERVICE_TYPE_TAPI = {ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE} +SERVICE_TYPE_TAPI_LSP = {ServiceTypeEnum.SERVICETYPE_TAPI_LSP} def get_service_type(device_type : DeviceTypeEnum, prv_service_type : ServiceTypeEnum) -> ServiceTypeEnum: - if device_type in PACKET_DEVICE_TYPES and prv_service_type in SERVICE_TYPE_LXNM: return prv_service_type - if device_type in L2_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_L2NM + if device_type in PACKET_DEVICE_TYPES and prv_service_type in SERVICE_TYPE_LXNM : return prv_service_type + if device_type in PACKET_DEVICE_TYPES and prv_service_type in SERVICE_TYPE_TAPI_LSP: return prv_service_type + if device_type in L2_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_L2NM if device_type in OPTICAL_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE if device_type in NETWORK_DEVICE_TYPES: return prv_service_type diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java b/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java index bf7055e149c7d6a1af66c045c8a2d96bbe84c322..3e12239446587622acb2af5d11fbbb7c346ef5ed 100644 --- a/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java +++ b/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java @@ -1166,6 +1166,8 @@ public class Serializer { return ContextOuterClass.ServiceTypeEnum.SERVICETYPE_L3NM; case TAPI_CONNECTIVITY_SERVICE: return ContextOuterClass.ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE; + case TAPI_LSP: + return ContextOuterClass.ServiceTypeEnum.SERVICETYPE_TAPI_LSP; case UNKNOWN: return ContextOuterClass.ServiceTypeEnum.SERVICETYPE_UNKNOWN; default: @@ -1181,6 +1183,8 @@ public class Serializer { return ServiceTypeEnum.L3NM; case SERVICETYPE_TAPI_CONNECTIVITY_SERVICE: return ServiceTypeEnum.TAPI_CONNECTIVITY_SERVICE; + case SERVICETYPE_TAPI_LSP: + return ServiceTypeEnum.TAPI_LSP; case SERVICETYPE_UNKNOWN: case UNRECOGNIZED: default: diff --git a/src/service/service/ServiceServiceServicerImpl.py b/src/service/service/ServiceServiceServicerImpl.py index bf923eed9880353b5fbde291dda1e66ba3f600e9..e795ba94f541454329d8ea9bc2cbd9577c8acc5d 100644 --- a/src/service/service/ServiceServiceServicerImpl.py +++ b/src/service/service/ServiceServiceServicerImpl.py @@ -114,7 +114,7 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): context_client, request.service_id, rw_copy=False, include_config_rules=True, include_constraints=True, include_endpoint_ids=True) - # Identify service constraints + # Identify service constraints num_disjoint_paths = None is_diverse = False gps_location_aware = False @@ -259,7 +259,7 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): DEFAULT_TOPOLOGY_NAME, context_id_x) topology_details = context_client.GetTopologyDetails( TopologyId(**topology_id_x)) - + refresh_opticalcontroller(topology_id_x) # devices = get_devices_in_topology(context_client, TopologyId(**topology_id_x), ContextId(**context_id_x)) devices = topology_details.devices @@ -402,14 +402,14 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): service.service_config.config_rules[0].custom.resource_value) ob_id = None flow_id = None - + if "ob_id" in c_rules_dict: ob_id = c_rules_dict["ob_id"] if ("flow_id" in c_rules_dict): flow_id = c_rules_dict["flow_id"] #if ("ob_id" in c_rules_dict): # ob_id = c_rules_dict["ob_id"] - + params['bitrate'] = bitrate params['dst' ] = dst params['src' ] = src @@ -560,7 +560,7 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): str_old_connection = grpc_message_to_json_string(old_connection) extra_details = MSG.format(str_pathcomp_request, str_pathcomp_reply, str_old_connection) raise OperationFailedException('no-new-path-found', extra_details=extra_details) - + str_candidate_new_connections = [ grpc_message_to_json_string(candidate_new_connection) for candidate_new_connection in candidate_new_connections diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index a56bcf0f903329b8d272945305fcdbae6a86110d..fb83b531e13dc5823a982956a4daeebd7228cf09 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -31,6 +31,7 @@ SERVICE_TYPE_VALUES = { ServiceTypeEnum.SERVICETYPE_QKD, ServiceTypeEnum.SERVICETYPE_INT, ServiceTypeEnum.SERVICETYPE_ACL, + ServiceTypeEnum.SERVICETYPE_TAPI_LSP, } DEVICE_DRIVER_VALUES = { diff --git a/src/service/service/service_handler_api/ServiceHandlerFactory.py b/src/service/service/service_handler_api/ServiceHandlerFactory.py index a5b3bed2ae80aa928d0d77285761a7b7078dcc09..f0c69e5f6b14ed9a748e3b67302be14fb41ccb8a 100644 --- a/src/service/service/service_handler_api/ServiceHandlerFactory.py +++ b/src/service/service/service_handler_api/ServiceHandlerFactory.py @@ -119,11 +119,21 @@ def get_service_handler_class( str_service_key = grpc_message_to_json_string(service.service_id) - # Assume all devices involved in the service's connection must support at least one driver in common - common_device_drivers = get_common_device_drivers([ - get_device_supported_drivers(device) - for device in connection_devices.values() - ]) + # Checks if the service is of type tapi_lsp + if 'tapi_lsp' in str(service.service_config.config_rules): + tapi_lsp_device_uuid = service.service_config.config_rules[0].tapi_lsp.endpoint_id.device_id.device_uuid.uuid + for device in connection_devices.values(): + if device.name == tapi_lsp_device_uuid: + LOGGER.debug('Device(%s) supported drivers: %s', device.name, device.device_drivers) + common_device_drivers = device.device_drivers + else: + for device in connection_devices.values(): + LOGGER.debug('Device(%s) supported drivers: %s', device.name, device.device_drivers) + + common_device_drivers = get_common_device_drivers([ + get_device_supported_drivers(device) + for device in connection_devices.values() + ]) filter_fields = { FilterFieldEnum.SERVICE_TYPE.value : service.service_type, # must be supported diff --git a/src/service/service/service_handler_api/SettingsHandler.py b/src/service/service/service_handler_api/SettingsHandler.py index b9b8b2950dcfff989861ce09e84cdb08ff628e31..8e17f3acbc852a1a01fcb970e9fdec84e45671d2 100644 --- a/src/service/service/service_handler_api/SettingsHandler.py +++ b/src/service/service/service_handler_api/SettingsHandler.py @@ -47,6 +47,13 @@ class SettingsHandler: ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/index[{:d}]/acl_ruleset[{:s}]' key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_name,endpoint_index, acl_ruleset_name) value = grpc_message_to_json(config_rule.acl) + elif kind == 'tapi_lsp': + device_uuid = config_rule.tapi_lsp.endpoint_id.device_id.device_uuid.uuid + endpoint_uuid = config_rule.tapi_lsp.endpoint_id.endpoint_uuid.uuid + endpoint_name, endpoint_index = extract_endpoint_index(endpoint_uuid) + TAPI_LSP_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/subindex[{:d}]/tapi_lsp' + key_or_path = TAPI_LSP_KEY_TEMPLATE.format(device_uuid, endpoint_name, endpoint_index) + value = config_rule.tapi_lsp else: MSG = 'Unsupported Kind({:s}) in ConfigRule({:s})' LOGGER.warning(MSG.format(str(kind), grpc_message_to_json_string(config_rule))) @@ -83,7 +90,7 @@ class SettingsHandler: if endpoint_settings is not None: return endpoint_settings return None - + def get_endpoint_acls(self, device : Device, endpoint : EndPoint) -> List [Tuple]: endpoint_name = endpoint.name device_keys = device.device_id.device_uuid.uuid, device.name @@ -93,12 +100,12 @@ class SettingsHandler: for endpoint_key in endpoint_keys: endpoint_settings_uri = '/device[{:s}]/endpoint[{:s}]'.format(device_key, endpoint_key) endpoint_settings = self.get(endpoint_settings_uri) - if endpoint_settings is None: continue + if endpoint_settings is None: continue endpoint_name, endpoint_index = extract_endpoint_index(endpoint_name) ACL_RULE_PREFIX = '/device[{:s}]/endpoint[{:s}]/'.format(device_key, endpoint_name) results = dump_subtree(endpoint_settings) - for res_key, res_value in results: + for res_key, res_value in results: if not res_key.startswith(ACL_RULE_PREFIX): continue if not "acl_ruleset" in res_key: continue acl_index = extract_index(res_value) @@ -106,6 +113,29 @@ class SettingsHandler: acl_rules.append((res_key, res_value)) return acl_rules + def get_endpoint_tapi_lsp(self, device : Device, endpoint : EndPoint) -> List [Tuple]: + endpoint_name = endpoint.name + device_keys = device.device_id.device_uuid.uuid, device.name + endpoint_keys = endpoint.endpoint_id.endpoint_uuid.uuid, endpoint.name + tapi_lsps = [] + LOGGER.debug('Getting TAPI LSPs for device(%s) ', device_keys) + for device_key in device_keys: + for endpoint_key in endpoint_keys: + endpoint_settings_uri = '/device[{:s}]/endpoint[{:s}]'.format(device_key, endpoint_key) + endpoint_settings = self.get(endpoint_settings_uri) + if endpoint_settings is None: continue + TAPI_LSP_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/'.format(device_key, endpoint_name) + + results = dump_subtree(endpoint_settings) + for res_key, res_value in results: + if not res_key.startswith(TAPI_LSP_KEY_TEMPLATE): continue + if not "tapi_lsp" in res_key: continue + tapi_lsps.append((res_key, res_value)) + setinterface_index = extract_index(res_value) + if not 'subindex[{:d}]'.format(setinterface_index) in res_key: continue + tapi_lsps.append((res_key, res_value)) + return tapi_lsps + def set(self, key_or_path : Union[str, List[str]], value : Any) -> None: set_subnode_value(self.__resolver, self.__config, key_or_path, value) diff --git a/src/service/service/service_handler_api/Tools.py b/src/service/service/service_handler_api/Tools.py index fa132909a5303554ad599c26f87803d64fb7203a..92dfa59f29021d6655f0f6078eb4aaeacc6fbb98 100644 --- a/src/service/service/service_handler_api/Tools.py +++ b/src/service/service/service_handler_api/Tools.py @@ -61,16 +61,16 @@ def get_device_endpoint_uuids(endpoint : Tuple[str, str, Optional[str]]) -> Tupl return device_uuid, endpoint_uuid def extract_endpoint_index(endpoint_name : str, default_index=0) -> Tuple[str, int]: - RE_PATTERN = '^(eth\-[0-9]+(?:\/[0-9]+)*)(?:\.([0-9]+))?$' + RE_PATTERN = r'^(eth\-[0-9]+(?:\/[0-9]+)*)(?:\.([0-9]+))?$' m = re.match(RE_PATTERN, endpoint_name) if m is None: return endpoint_name, default_index endpoint_name, index = m.groups() if index is not None: index = int(index) return endpoint_name, index -def extract_index(res_value : str) -> int: - acl_value = grpc_message_to_json(res_value,use_integers_for_enums=True) - endpoint = acl_value.split("'endpoint_uuid': {'uuid': '") +def extract_index(res_value : Any) -> int: + res_value = grpc_message_to_json(res_value,use_integers_for_enums=True) + endpoint = res_value['endpoint_id']['endpoint_uuid']['uuid'] endpoint = endpoint[1].split("'}") _ , index = extract_endpoint_index(endpoint[0]) return index diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 1aba88e303ccb99afec1995aa7e9ad5b35414377..a9b8e13cfcb388a0cfca5328051fb0d79cd0f0cc 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -30,6 +30,7 @@ from .p4_fabric_tna_int.p4_fabric_tna_int_service_handler import P4FabricINTServ from .p4_fabric_tna_l2_simple.p4_fabric_tna_l2_simple_service_handler import P4FabricL2SimpleServiceHandler from .p4_fabric_tna_l3.p4_fabric_tna_l3_service_handler import P4FabricL3ServiceHandler from .p4_fabric_tna_acl.p4_fabric_tna_acl_service_handler import P4FabricACLServiceHandler +from .tapi_lsp.Tapi_LSPServiceHandler import Tapi_LSPServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler from .optical_tfs.OpticalTfsServiceHandler import OpticalTfsServiceHandler @@ -178,5 +179,11 @@ SERVICE_HANDLERS = [ FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L3NM, FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_RYU], } - ]) + ]), + (Tapi_LSPServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_TAPI_LSP, + FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_OPTICAL_TFS], + } + ]), ] diff --git a/src/service/service/service_handlers/tapi_lsp/ConfigRules.py b/src/service/service/service_handlers/tapi_lsp/ConfigRules.py new file mode 100644 index 0000000000000000000000000000000000000000..e78379834672456c4fc9c992f4b41c793a3ca05e --- /dev/null +++ b/src/service/service/service_handlers/tapi_lsp/ConfigRules.py @@ -0,0 +1,58 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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. + +import logging +from typing import Any, Dict, List, Optional, Tuple +from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set +from service.service.service_handler_api.AnyTreeTools import TreeNode +LOGGER = logging.getLogger(__name__) + +def get_value(field_name : str, *containers, default=None) -> Optional[Any]: + if len(containers) == 0: raise Exception('No containers specified') + for container in containers: + if field_name not in container: continue + return container[field_name] + return default + +def setup_config_rules( + endpoint_name : str, endpoint_tapi_lsp : List [Tuple] +) -> List[Dict]: + + json_config_rules = [ + ] + + for res_key, res_value in endpoint_tapi_lsp: + json_config_rules.append( + {'action': 1, 'tapi_lsp': res_value} + ) + + return json_config_rules + +def teardown_config_rules( + service_uuid : str, connection_uuid : str, device_uuid : str, endpoint_uuid : str, endpoint_name : str, + service_settings : TreeNode, device_settings : TreeNode, endpoint_settings : TreeNode +) -> List[Dict]: + + if service_settings is None: return [] + if device_settings is None: return [] + if endpoint_settings is None: return [] + + json_settings : Dict = service_settings.value + json_device_settings : Dict = device_settings.value + json_endpoint_settings : Dict = endpoint_settings.value + + settings = (json_settings, json_endpoint_settings, json_device_settings) + + json_config_rules = [] + return json_config_rules diff --git a/src/service/service/service_handlers/tapi_lsp/Tapi_LSPServiceHandler.py b/src/service/service/service_handlers/tapi_lsp/Tapi_LSPServiceHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..a6bf386e3e29ecc82b2c8e1ae12a75f74db903ee --- /dev/null +++ b/src/service/service/service_handlers/tapi_lsp/Tapi_LSPServiceHandler.py @@ -0,0 +1,183 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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. + +import json, logging +from typing import Any, List, Optional, Tuple, Union +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.proto.context_pb2 import ConfigRule, DeviceId, Service +from common.tools.object_factory.Device import json_device_id +from common.type_checkers.Checkers import chk_type +from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.task_scheduler.TaskExecutor import TaskExecutor +from .ConfigRules import setup_config_rules, teardown_config_rules + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'l3nm_openconfig'}) + +class Tapi_LSPServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service : Service, task_executor : TaskExecutor, **settings + ) -> None: + self.__service = service + self.__task_executor = task_executor + self.__settings_handler = SettingsHandler(service.service_config, **settings) + + @metered_subclass_method(METRICS_POOL) + def SetEndpoint( + self, endpoints: List[Tuple[str, str, Optional[str]]], connection_uuid: Optional[str] = None + ) -> List[Union[bool, Exception]]: + chk_type('endpoints', endpoints, list) + endpoints = set(endpoints) # Remove duplicates + LOGGER.debug("[SetEndpoint] Called with endpoints: %s", endpoints) + LOGGER.debug("[SetEndpoint] Connection UUID: %s", connection_uuid) + + if len(endpoints) == 0: + LOGGER.warning("[SetEndpoint] No endpoints to process.") + return [] + + results = [] + for endpoint in endpoints: + + LOGGER.debug("[SetEndpoint] Processing endpoint tuple: %s", endpoint) + try: + device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) + LOGGER.debug("[SetEndpoint] Device UUID: %s | Endpoint UUID: %s", device_uuid, endpoint_uuid) + + device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + + endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) + + endpoint_tapi_lsp = self.__settings_handler.get_endpoint_tapi_lsp(device_obj, endpoint_obj) + for _, endpoint in endpoint_tapi_lsp: + if endpoint.endpoint_id.device_id.device_uuid.uuid != 'TFS-OPTICAL':continue + LOGGER.debug("[SetEndpoint] endpoint_tapi_lsp: %s", endpoint_tapi_lsp) + + endpoint_name = endpoint_obj.name + LOGGER.debug("[SetEndpoint] Endpoint name: %s", endpoint_name) + + json_config_rules = setup_config_rules(endpoint_name, endpoint_tapi_lsp) + LOGGER.debug("[SetEndpoint] Generated json_config_rules: %s", json_config_rules) + + if len(json_config_rules) > 0: + LOGGER.info("[SetEndpoint] Applying %d config rules to device %s", len(json_config_rules), device_uuid) + del device_obj.device_config.config_rules[:] + json_config_rule = json_config_rules[0] + LOGGER.debug("[SetEndpoint] Adding config rule: %s", json_config_rule) + device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule)) + + self.__task_executor.configure_device(device_obj) + LOGGER.info("[SetEndpoint] Configuration sent for device %s", device_uuid) + else: + LOGGER.warning("[SetEndpoint] No config rules generated for endpoint %s", endpoint_uuid) + + results.append(True) + + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('[SetEndpoint] Unable to SetEndpoint(%s)', str(endpoint)) + results.append(e) + + LOGGER.debug("[SetEndpoint] Final results: %s", results) + return results + + + @metered_subclass_method(METRICS_POOL) + def DeleteEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + service_uuid = self.__service.service_id.service_uuid.uuid + settings = self.__settings_handler.get('/settings') + + results = [] + for endpoint in endpoints: + try: + device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) + + device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_settings = self.__settings_handler.get_device_settings(device_obj) + endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) + endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj) + endpoint_name = endpoint_obj.name + + json_config_rules = teardown_config_rules( + service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name, + settings, device_settings, endpoint_settings) + + if len(json_config_rules) > 0: + del device_obj.device_config.config_rules[:] + for json_config_rule in json_config_rules: + device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule)) + self.__task_executor.configure_device(device_obj) + + results.append(True) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint))) + results.append(e) + + return results + + @metered_subclass_method(METRICS_POOL) + def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + results = [] + for resource in resources: + try: + resource_value = json.loads(resource[1]) + self.__settings_handler.set(resource[0], resource_value) + results.append(True) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource))) + results.append(e) + + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + results = [] + for resource in resources: + try: + self.__settings_handler.delete(resource[0]) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource))) + results.append(e) + + return results diff --git a/src/service/service/service_handlers/tapi_lsp/__init__.py b/src/service/service/service_handlers/tapi_lsp/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6242c89c7fa17bc5b6cc44328d8ce58438721d45 --- /dev/null +++ b/src/service/service/service_handlers/tapi_lsp/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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.