diff --git a/src/service/service/service_handlers/l3slice_ietfslice/ConfigRules.py b/src/service/service/service_handlers/l3slice_ietfslice/ConfigRules.py
new file mode 100644
index 0000000000000000000000000000000000000000..23f3e8159ba4f9e272e008b6c2411bd00fea8887
--- /dev/null
+++ b/src/service/service/service_handlers/l3slice_ietfslice/ConfigRules.py
@@ -0,0 +1,229 @@
+# Copyright 2022-2024 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.
+
+from typing import Dict, List, Tuple
+
+from common.proto.context_pb2 import Link
+from common.tools.object_factory.ConfigRule import (
+ json_config_rule_delete,
+ json_config_rule_set,
+)
+from context.client.ContextClient import ContextClient
+from service.service.service_handler_api.AnyTreeTools import TreeNode
+
+
+def setup_config_rules(service_uuid: str, json_settings: Dict) -> List[Dict]:
+ operation_type: str = json_settings["operation_type"]
+ src_node_id: str = json_settings["src_node_id"]
+ src_mgmt_ip_address: str = json_settings["src_mgmt_ip_address"]
+ src_ac_node_id: str = json_settings["src_ac_node_id"]
+ src_ac_ep_id: str = json_settings["src_ac_ep_id"]
+ src_vlan: str = json_settings["src_vlan"]
+ dst_node_id: str = json_settings["dst_node_id"]
+ dst_mgmt_ip_address: str = json_settings["dst_mgmt_ip_address"]
+ dst_ac_node_id: str = json_settings["dst_ac_node_id"]
+ dst_ac_ep_id: str = json_settings["dst_ac_ep_id"]
+ dst_vlan: str = json_settings["dst_vlan"]
+ slice_id: str = json_settings["slice_id"]
+ delay: str = json_settings["delay"]
+ bandwidth: str = json_settings["bandwidth"]
+ packet_loss: str = json_settings["packet_loss"]
+
+ sdps = [
+ {
+ "id": "1",
+ "node-id": src_node_id,
+ "sdp-ip-address": [src_mgmt_ip_address],
+ "service-match-criteria": {
+ "match-criterion": [
+ {
+ "index": 1,
+ "match-type": [
+ {
+ "type": "ietf-network-slice-service:vlan",
+ "value": [src_vlan],
+ },
+ ],
+ "target-connection-group-id": "line1",
+ }
+ ]
+ },
+ "attachment-circuits": {
+ "attachment-circuit": [
+ {
+ "id": "0",
+ "description": "dsc",
+ "ac-node-id": src_ac_node_id,
+ "ac-tp-id": src_ac_ep_id,
+ }
+ ]
+ },
+ },
+ {
+ "id": "2",
+ "node-id": dst_node_id,
+ "sdp-ip-address": [dst_mgmt_ip_address],
+ "service-match-criteria": {
+ "match-criterion": [
+ {
+ "index": 1,
+ "match-type": [
+ {
+ "type": "ietf-network-slice-service:vlan",
+ "value": [dst_vlan],
+ },
+ ],
+ "target-connection-group-id": "line1",
+ }
+ ]
+ },
+ "attachment-circuits": {
+ "attachment-circuit": [
+ {
+ "id": "0",
+ "description": "dsc",
+ "ac-node-id": dst_ac_node_id,
+ "ac-tp-id": dst_ac_ep_id,
+ }
+ ]
+ },
+ },
+ ]
+
+ connection_groups = [
+ {
+ "id": "line1",
+ "connectivity-type": "point-to-point",
+ "connectivity-construct": [
+ {
+ "id": 1,
+ "p2mp-sender-sdp": "1",
+ "p2mp-receiver-sdp": ["2"],
+ "service-slo-sle-policy": {
+ "slo-policy": {
+ "metric-bound": [
+ {
+ "metric-type": "ietf-network-slice-service:one-way-delay-maximum",
+ "metric-unit": "milliseconds",
+ "bound": delay,
+ },
+ {
+ "metric-type": "ietf-network-slice-service:one-way-bandwidth",
+ "metric-unit": "Mbps",
+ "bound": bandwidth,
+ },
+ {
+ "metric-type": "ietf-network-slice-service:two-way-packet-loss",
+ "metric-unit": "percentage",
+ "percentile-value": packet_loss,
+ },
+ ]
+ }
+ },
+ },
+ {
+ "id": 2,
+ "p2mp-sender-sdp": "2",
+ "p2mp-receiver-sdp": ["1"],
+ "service-slo-sle-policy": {
+ "slo-policy": {
+ "metric-bound": [
+ {
+ "metric-type": "ietf-network-slice-service:one-way-delay-maximum",
+ "metric-unit": "milliseconds",
+ "bound": delay,
+ },
+ {
+ "metric-type": "ietf-network-slice-service:one-way-bandwidth",
+ "metric-unit": "Mbps",
+ "bound": bandwidth,
+ },
+ {
+ "metric-type": "ietf-network-slice-service:two-way-packet-loss",
+ "metric-unit": "percentage",
+ "percentile-value": packet_loss,
+ },
+ ]
+ }
+ },
+ },
+ ],
+ }
+ ]
+ slice_service = {
+ "id": slice_id,
+ "description": "dsc",
+ "sdps": {"sdp": sdps},
+ "connection-groups": {"connection-group": connection_groups},
+ }
+ slice_data_model = {"network-slice-services": {"slice-service": [slice_service]}}
+ json_config_rules = [
+ json_config_rule_set(
+ "/service[{:s}]/IETFSlice".format(service_uuid),
+ slice_data_model,
+ ),
+ json_config_rule_set(
+ "/service[{:s}]/IETFSlice/operation".format(service_uuid),
+ {"type": operation_type},
+ ),
+ ]
+ return json_config_rules
+
+
+def teardown_config_rules(service_uuid: str, json_settings: Dict) -> List[Dict]:
+ json_config_rules = [
+ json_config_rule_delete(
+ "/service[{:s}]/IETFSlice".format(service_uuid),
+ {},
+ ),
+ json_config_rule_delete(
+ "/service[{:s}]/IETFSlice/operation".format(service_uuid),
+ {},
+ ),
+ ]
+ return json_config_rules
+
+
+def get_link_ep_device_names(
+ link: Link, context_client: ContextClient
+) -> Tuple[str, str, str, str]:
+ ep_ids = link.link_endpoint_ids
+ ep_device_id_1 = ep_ids[0].device_id
+ ep_uuid_1 = ep_ids[0].endpoint_uuid.uuid
+ device_obj_1 = context_client.GetDevice(ep_device_id_1)
+ for d_ep in device_obj_1.device_endpoints:
+ if d_ep.endpoint_id.endpoint_uuid.uuid == ep_uuid_1:
+ ep_name_1 = d_ep.name
+ break
+ else:
+ raise Exception("endpoint not found")
+ device_obj_name_1 = device_obj_1.name
+ ep_device_id_2 = ep_ids[1].device_id
+ ep_uuid_2 = ep_ids[1].endpoint_uuid.uuid
+ device_obj_2 = context_client.GetDevice(ep_device_id_2)
+ for d_ep in device_obj_2.device_endpoints:
+ if d_ep.endpoint_id.endpoint_uuid.uuid == ep_uuid_2:
+ ep_name_2 = d_ep.name
+ break
+ else:
+ raise Exception("endpoint not found")
+ device_obj_name_2 = device_obj_2.name
+ return (
+ device_obj_name_1,
+ ep_name_1,
+ device_obj_1,
+ device_obj_name_2,
+ ep_name_2,
+ device_obj_2,
+ )
diff --git a/src/service/service/service_handlers/l3slice_ietfslice/L3SliceIETFSliceServiceHandler.py b/src/service/service/service_handlers/l3slice_ietfslice/L3SliceIETFSliceServiceHandler.py
new file mode 100644
index 0000000000000000000000000000000000000000..e584283a465c4bc74f52a3da2cc4e5a6cb01a910
--- /dev/null
+++ b/src/service/service/service_handlers/l3slice_ietfslice/L3SliceIETFSliceServiceHandler.py
@@ -0,0 +1,630 @@
+# Copyright 2022-2024 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 re
+from typing import Any, List, Optional, Tuple, Union
+
+from deepdiff import DeepDiff
+
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.proto.context_pb2 import ConfigRule, DeviceId, Empty, Service, ServiceConfig
+from common.tools.object_factory.Device import json_device_id
+from common.type_checkers.Checkers import chk_type
+from context.client.ContextClient import ContextClient
+from service.service.service_handler_api._ServiceHandler import _ServiceHandler
+from service.service.service_handler_api.SettingsHandler import SettingsHandler
+from service.service.service_handler_api.Tools import (
+ get_device_endpoint_uuids,
+)
+from service.service.task_scheduler.TaskExecutor import TaskExecutor
+
+from .ConfigRules import (
+ get_link_ep_device_names,
+ setup_config_rules,
+ teardown_config_rules,
+)
+
+RUNNING_RESOURCE_KEY = "running_ietf_slice"
+CANDIDATE_RESOURCE_KEY = "candidate_ietf_slice"
+
+SDP_DIFF_RE = re.compile(
+ r"^root\[\'network-slice-services\'\]\[\'slice-service\'\]\[0\]\[\'sdps\'\]\[\'sdp\'\]\[(\d)\]$"
+)
+CONNECTION_GROUP_DIFF_RE = re.compile(
+ r"^root\[\'network-slice-services\'\]\[\'slice-service\'\]\[0\]\[\'connection-groups\'\]\[\'connection-group\'\]\[(\d)\]$"
+)
+MATCH_CRITERION_DIFF_RE = re.compile(
+ r"^root\[\'network-slice-services\'\]\[\'slice-service\'\]\[0\]\[\'sdps\'\]\[\'sdp\'\]\[(\d)\]\[\'service-match-criteria\'\]\[\'match-criterion\'\]\[(\d)\]$"
+)
+
+RE_GET_ENDPOINT_FROM_INTERFACE = re.compile(r"^\/interface\[([^\]]+)\].*")
+
+LOGGER = logging.getLogger(__name__)
+
+METRICS_POOL = MetricsPool(
+ "Service", "Handler", labels={"handler": "l3slice_ietfslice"}
+)
+
+
+RAISE_IF_DIFFERS = True
+
+
+def get_custom_config_rule(
+ service_config: ServiceConfig, resource_key: str
+) -> Optional[ConfigRule]:
+ for cr in service_config.config_rules:
+ if (
+ cr.WhichOneof("config_rule") == "custom"
+ and cr.custom.resource_key == resource_key
+ ):
+ return cr
+
+
+def get_running_candidate_ietf_slice_data_diff(service_config: ServiceConfig) -> dict:
+ running_ietf_slice_cr = get_custom_config_rule(service_config, RUNNING_RESOURCE_KEY)
+ running_resource_value_dict = json.loads(
+ running_ietf_slice_cr.custom.resource_value
+ )
+ candidate_ietf_slice_cr = get_custom_config_rule(
+ service_config, CANDIDATE_RESOURCE_KEY
+ )
+ candidate_resource_value_dict = json.loads(
+ candidate_ietf_slice_cr.custom.resource_value
+ )
+ return (
+ DeepDiff(
+ running_resource_value_dict,
+ candidate_resource_value_dict,
+ ),
+ DeepDiff(
+ running_resource_value_dict,
+ candidate_resource_value_dict,
+ ignore_order=True,
+ ),
+ )
+
+
+class L3NMSliceIETFSliceServiceHandler(_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)
+ if len(endpoints) == 0:
+ return []
+ service_uuid = self.__service.service_id.service_uuid.uuid
+ service_config = self.__service.service_config
+ src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0])
+ src_device_obj = self.__task_executor.get_device(
+ DeviceId(**json_device_id(src_device_uuid))
+ )
+ src_device_name = src_device_obj.name
+ src_controller = self.__task_executor.get_device_controller(src_device_obj)
+ dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[-1])
+ dst_device_obj = self.__task_executor.get_device(
+ DeviceId(**json_device_id(dst_device_uuid))
+ )
+ dst_device_name = dst_device_obj.name
+ dst_controller = self.__task_executor.get_device_controller(dst_device_obj)
+ if (
+ src_controller.device_id.device_uuid.uuid
+ != dst_controller.device_id.device_uuid.uuid
+ ):
+ raise Exception("Different Src-Dst devices not supported by now")
+ controller = src_controller
+ context_client = ContextClient()
+ edge_device_names = [src_device_name, dst_device_name]
+ link_list = context_client.ListLinks(Empty())
+ links = link_list.links
+ max_delay = 1e9
+ packet_loss = 1.0
+ bandwidth = 0.0
+ device_ep_pairs = []
+ sdp_ids = []
+ running_candidate_diff, running_candidate_diff_no_order = (
+ get_running_candidate_ietf_slice_data_diff(service_config)
+ )
+ candidate_ietf_slice_cr = get_custom_config_rule(
+ service_config, CANDIDATE_RESOURCE_KEY
+ )
+ candidate_resource_value_dict = json.loads(
+ candidate_ietf_slice_cr.custom.resource_value
+ )
+ running_ietf_slice_cr = get_custom_config_rule(
+ service_config, RUNNING_RESOURCE_KEY
+ )
+ running_resource_value_dict = json.loads(
+ running_ietf_slice_cr.custom.resource_value
+ )
+ LOGGER.debug(f"P46: {candidate_resource_value_dict}")
+ LOGGER.debug(f"P47: {running_resource_value_dict}")
+ LOGGER.debug(f"P41: {running_candidate_diff}")
+ LOGGER.debug(f"P45: {running_candidate_diff_no_order}")
+ if not running_candidate_diff: # Slice Creation
+ slice_services = candidate_resource_value_dict["network-slice-services"][
+ "slice-service"
+ ]
+ slice_service = slice_services[0]
+ sdps = slice_service["sdps"]["sdp"]
+ operation_type = "create"
+ sdp_ids = [sdp["node-id"] for sdp in sdps]
+ for sdp in sdps:
+ node_id = sdp["node-id"]
+ for link in links:
+ (
+ device_obj_name_1,
+ ep_name_1,
+ device_obj_1,
+ device_obj_name_2,
+ ep_name_2,
+ device_obj_2,
+ ) = get_link_ep_device_names(link, context_client)
+ if (
+ device_obj_name_1 == node_id
+ and device_obj_name_2 in edge_device_names
+ ):
+ device_ep_pairs.append(
+ (node_id, ep_name_1, device_obj_name_2, ep_name_2)
+ )
+ del edge_device_names[
+ edge_device_names.index(device_obj_name_2)
+ ]
+ match_criteria = sdp["service-match-criteria"][
+ "match-criterion"
+ ]
+ vlan_id = set()
+ for match in match_criteria:
+ for type_value in match["match-type"]:
+ if (
+ type_value["type"]
+ == "ietf-network-slice-service:vlan"
+ ):
+ vlan_id.add(type_value["value"][0])
+ if len(vlan_id) != 1:
+ raise Exception("one vlan id found in SDP match criteria")
+ src_vlan = list(vlan_id)[0]
+ del sdp_ids[sdp_ids.index(node_id)]
+ break
+ for link in links:
+ (
+ device_obj_name_1,
+ ep_name_1,
+ device_obj_1,
+ device_obj_name_2,
+ ep_name_2,
+ device_obj_2,
+ ) = get_link_ep_device_names(link, context_client)
+ if (
+ device_obj_name_1 == edge_device_names[0]
+ and device_obj_2.controller_id != device_obj_1.controller_id
+ ):
+ device_ep_pairs.append(
+ (device_obj_name_2, ep_name_2, device_obj_name_1, ep_name_1)
+ )
+ for sdp in sdps:
+ if sdp["node-id"] != sdp_ids[0]:
+ continue
+ match_criteria = sdp["service-match-criteria"][
+ "match-criterion"
+ ]
+ vlan_id = set()
+ for match in match_criteria:
+ for type_value in match["match-type"]:
+ if (
+ type_value["type"]
+ == "ietf-network-slice-service:vlan"
+ ):
+ vlan_id.add(type_value["value"][0])
+ if len(vlan_id) != 1:
+ raise Exception("one vlan id found in SDP match criteria")
+ dst_vlan = list(vlan_id)[0]
+ break
+ else:
+ raise Exception("sdp between the domains not found")
+ elif "iterable_item_added" in running_candidate_diff: # new SDP added
+ slice_services = candidate_resource_value_dict["network-slice-services"][
+ "slice-service"
+ ]
+ slice_service = slice_services[0]
+ sdps = slice_service["sdps"]["sdp"]
+ operation_type = "update"
+ added_items = {
+ "sdp": {"sdp_idx": None, "value": {}},
+ "connection_group": {"connection_group_idx": None, "value": {}},
+ "match_criterion": {
+ "sdp_idx": None,
+ "match_criterion_idx": None,
+ "value": {},
+ },
+ }
+ for added_key, added_value in running_candidate_diff[
+ "iterable_item_added"
+ ].items():
+ sdp_match = SDP_DIFF_RE.match(added_key)
+ connection_group_match = CONNECTION_GROUP_DIFF_RE.match(added_key)
+ match_criterion_match = MATCH_CRITERION_DIFF_RE.match(added_key)
+ if sdp_match:
+ added_items["sdp"] = {
+ "sdp_idx": int(sdp_match.groups()[0]),
+ "value": added_value,
+ }
+ elif connection_group_match:
+ added_items["connection_group"] = {
+ "connection_group_idx": int(connection_group_match.groups()[0]),
+ "value": added_value,
+ }
+ elif match_criterion_match:
+ added_items["match_criterion"] = {
+ "sdp_idx": int(match_criterion_match.groups()[0]),
+ "match_criterion_idx": int(match_criterion_match.groups()[1]),
+ "value": added_value,
+ }
+ new_sdp = sdps[added_items["sdp"]["sdp_idx"]]
+ src_sdp_name = new_sdp["node-id"]
+ dst_sdp_idx = sdps[added_items["match_criterion"]["sdp_idx"]]["id"]
+ dst_sdp_name = sdps[added_items["match_criterion"]["sdp_idx"]]["node-id"]
+ for link in links:
+ (
+ device_obj_name_1,
+ ep_name_1,
+ device_obj_1,
+ device_obj_name_2,
+ ep_name_2,
+ device_obj_2,
+ ) = get_link_ep_device_names(link, context_client)
+ if (
+ device_obj_name_1 == src_sdp_name
+ and device_obj_2.controller_id != device_obj_1.controller_id
+ ):
+ device_ep_pairs.append(
+ (device_obj_name_2, ep_name_2, device_obj_name_1, ep_name_1)
+ )
+ for sdp in sdps:
+ if sdp["node-id"] != src_sdp_name:
+ continue
+ match_criteria = sdp["service-match-criteria"][
+ "match-criterion"
+ ]
+ vlan_id = set()
+ for match in match_criteria:
+ for type_value in match["match-type"]:
+ if (
+ type_value["type"]
+ == "ietf-network-slice-service:vlan"
+ ):
+ vlan_id.add(type_value["value"][0])
+ if len(vlan_id) != 1:
+ raise Exception("one vlan id found in SDP match criteria")
+ src_vlan = list(vlan_id)[0]
+ break
+ else:
+ raise Exception("sdp between the domains not found")
+ for link in links:
+ (
+ device_obj_name_1,
+ ep_name_1,
+ device_obj_1,
+ device_obj_name_2,
+ ep_name_2,
+ device_obj_2,
+ ) = get_link_ep_device_names(link, context_client)
+ if (
+ device_obj_name_1 == dst_sdp_name
+ and device_obj_2.controller_id != device_obj_1.controller_id
+ ):
+ device_ep_pairs.append(
+ (device_obj_name_2, ep_name_2, device_obj_name_1, ep_name_1)
+ )
+ for sdp in sdps:
+ if sdp["node-id"] != dst_sdp_name:
+ continue
+ match_criteria = sdp["service-match-criteria"][
+ "match-criterion"
+ ]
+ vlan_id = set()
+ for match in match_criteria:
+ for type_value in match["match-type"]:
+ if (
+ type_value["type"]
+ == "ietf-network-slice-service:vlan"
+ ):
+ vlan_id.add(type_value["value"][0])
+ if len(vlan_id) != 1:
+ raise Exception("one vlan id found in SDP match criteria")
+ dst_vlan = list(vlan_id)[0]
+ break
+ else:
+ raise Exception("sdp between the domains not found")
+ elif "iterable_item_removed" in running_candidate_diff: # an SDP removed
+ slice_services = running_resource_value_dict["network-slice-services"][
+ "slice-service"
+ ]
+ slice_service = slice_services[0]
+ sdps = slice_service["sdps"]["sdp"]
+ operation_type = "update"
+ added_items = {
+ "sdp": {"sdp_idx": None, "value": {}},
+ "connection_group": {"connection_group_idx": None, "value": {}},
+ "match_criterion": {
+ "sdp_idx": None,
+ "match_criterion_idx": None,
+ "value": {},
+ },
+ }
+ for added_key, added_value in running_candidate_diff[
+ "iterable_item_removed"
+ ].items():
+ sdp_match = SDP_DIFF_RE.match(added_key)
+ connection_group_match = CONNECTION_GROUP_DIFF_RE.match(added_key)
+ match_criterion_match = MATCH_CRITERION_DIFF_RE.match(added_key)
+ LOGGER.debug(
+ f"P40: {sdp_match} *{connection_group_match}* {match_criterion_match}"
+ )
+ if sdp_match:
+ added_items["sdp"] = {
+ "sdp_idx": int(sdp_match.groups()[0]),
+ "value": added_value,
+ }
+ elif connection_group_match:
+ added_items["connection_group"] = {
+ "connection_group_idx": int(connection_group_match.groups()[0]),
+ "value": added_value,
+ }
+ elif match_criterion_match:
+ added_items["match_criterion"] = {
+ "sdp_idx": int(match_criterion_match.groups()[0]),
+ "match_criterion_idx": int(match_criterion_match.groups()[1]),
+ "value": added_value,
+ }
+ new_sdp = sdps[added_items["sdp"]["sdp_idx"]]
+ src_sdp_name = new_sdp["node-id"]
+ dst_sdp_idx = sdps[added_items["match_criterion"]["sdp_idx"]]["id"]
+ dst_sdp_name = sdps[added_items["match_criterion"]["sdp_idx"]]["node-id"]
+ for link in links:
+ (
+ device_obj_name_1,
+ ep_name_1,
+ device_obj_1,
+ device_obj_name_2,
+ ep_name_2,
+ device_obj_2,
+ ) = get_link_ep_device_names(link, context_client)
+ if (
+ device_obj_name_1 == src_sdp_name
+ and device_obj_2.controller_id != device_obj_1.controller_id
+ ):
+ device_ep_pairs.append(
+ (device_obj_name_2, ep_name_2, device_obj_name_1, ep_name_1)
+ )
+ for sdp in sdps:
+ if sdp["node-id"] != src_sdp_name:
+ continue
+ match_criteria = sdp["service-match-criteria"][
+ "match-criterion"
+ ]
+ vlan_id = set()
+ LOGGER.debug(f"P81: {match_criteria}")
+ for match in match_criteria:
+ for type_value in match["match-type"]:
+ if (
+ type_value["type"]
+ == "ietf-network-slice-service:vlan"
+ ):
+ vlan_id.add(type_value["value"][0])
+ LOGGER.debug(f"P82: {vlan_id}")
+ if len(vlan_id) != 1:
+ raise Exception("one vlan id found in SDP match criteria")
+ src_vlan = list(vlan_id)[0]
+ break
+ else:
+ raise Exception("sdp between the domains not found")
+ for link in links:
+ (
+ device_obj_name_1,
+ ep_name_1,
+ device_obj_1,
+ device_obj_name_2,
+ ep_name_2,
+ device_obj_2,
+ ) = get_link_ep_device_names(link, context_client)
+ if (
+ device_obj_name_1 == dst_sdp_name
+ and device_obj_2.controller_id != device_obj_1.controller_id
+ ):
+ device_ep_pairs.append(
+ (device_obj_name_2, ep_name_2, device_obj_name_1, ep_name_1)
+ )
+ for sdp in sdps:
+ if sdp["node-id"] != dst_sdp_name:
+ continue
+ match_criteria = sdp["service-match-criteria"][
+ "match-criterion"
+ ]
+ LOGGER.debug(f"P83: {match_criteria}")
+ vlan_id = set()
+ for match in match_criteria:
+ for type_value in match["match-type"]:
+ if (
+ type_value["type"]
+ == "ietf-network-slice-service:vlan"
+ ):
+ vlan_id.add(type_value["value"][0])
+
+ LOGGER.debug(f"P84: {vlan_id}")
+ if len(vlan_id) != 1:
+ raise Exception("one vlan id found in SDP match criteria")
+ dst_vlan = list(vlan_id)[0]
+ break
+ else:
+ raise Exception("sdp between the domains not found")
+ connection_groups = slice_service["connection-groups"]["connection-group"]
+ for cg in connection_groups:
+ for cc in cg["connectivity-construct"]:
+ for metric_bound in cc["service-slo-sle-policy"]["slo-policy"][
+ "metric-bound"
+ ]:
+ if (
+ metric_bound["metric-type"]
+ == "ietf-network-slice-service:one-way-delay-maximum"
+ and metric_bound["metric-unit"] == "milliseconds"
+ ):
+ metric_value = int(metric_bound["bound"])
+ if metric_value < max_delay:
+ max_delay = metric_value
+ elif (
+ metric_bound["metric-type"]
+ == "ietf-network-slice-service:two-way-packet-loss"
+ and metric_bound["metric-unit"] == "percentage"
+ ):
+ metric_value = float(metric_bound["percentile-value"])
+ if metric_value < packet_loss:
+ packet_loss = metric_value
+ elif (
+ metric_bound["metric-type"]
+ == "ietf-network-slice-service:one-way-bandwidth"
+ and metric_bound["metric-unit"] == "Mbps"
+ ):
+ metric_value = float(metric_bound["bound"])
+ bandwidth += metric_value
+ results = []
+ resource_value_dict = {
+ "uuid": service_uuid,
+ "operation_type": operation_type,
+ "src_node_id": device_ep_pairs[0][2],
+ "src_mgmt_ip_address": device_ep_pairs[0][2],
+ "src_ac_node_id": device_ep_pairs[0][2],
+ "src_ac_ep_id": device_ep_pairs[0][3],
+ "src_vlan": src_vlan,
+ "dst_node_id": device_ep_pairs[1][2],
+ "dst_mgmt_ip_address": device_ep_pairs[1][2],
+ "dst_ac_node_id": device_ep_pairs[1][2],
+ "dst_ac_ep_id": device_ep_pairs[1][3],
+ "dst_vlan": dst_vlan,
+ "slice_id": service_uuid,
+ "delay": max_delay,
+ "bandwidth": bandwidth,
+ "packet_loss": packet_loss,
+ }
+
+ json_config_rules = setup_config_rules(service_uuid, resource_value_dict)
+ del controller.device_config.config_rules[:]
+ for jcr in json_config_rules:
+ controller.device_config.config_rules.append(ConfigRule(**jcr))
+ self.__task_executor.configure_device(controller)
+ # except Exception as e: # pylint: disable=broad-except
+ # results.append(e)
+ 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
+ results = []
+ try:
+ src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0])
+ src_device_obj = self.__task_executor.get_device(
+ DeviceId(**json_device_id(src_device_uuid))
+ )
+ controller = self.__task_executor.get_device_controller(src_device_obj)
+ json_config_rules = teardown_config_rules(service_uuid, {})
+ if len(json_config_rules) > 0:
+ del controller.device_config.config_rules[:]
+ for json_config_rule in json_config_rules:
+ controller.device_config.config_rules.append(
+ ConfigRule(**json_config_rule)
+ )
+ self.__task_executor.configure_device(controller)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ 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/l3slice_ietfslice/__init__.py b/src/service/service/service_handlers/l3slice_ietfslice/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..53d5157f750bfb085125cbd33faff1cec5924e14
--- /dev/null
+++ b/src/service/service/service_handlers/l3slice_ietfslice/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2022-2024 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.
+