Loading src/service/service/service_handlers/l3nm_nce/ConfigRules.py +1 −1 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # 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. Loading src/service/service/service_handlers/l3nm_nce/L3NMNCEServiceHandler.py +162 −110 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # 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. Loading @@ -15,7 +15,7 @@ import json import logging import re from typing import Any, List, Optional, Tuple, Union from typing import Any, List, Optional, Tuple, Union, TypedDict, Dict from uuid import uuid4 from deepdiff import DeepDiff Loading @@ -40,6 +40,7 @@ CANDIDATE_RESOURCE_KEY = "candidate_ietf_slice" LOGGER = logging.getLogger(__name__) METRICS_POOL = MetricsPool("Service", "Handler", labels={"handler": "l3nm_nce"}) SDP_DIFF_RE = re.compile( r"^root\[\'network-slice-services\'\]\[\'slice-service\'\]\[0\]\[\'sdps\'\]\[\'sdp\'\]\[(\d)\]$" ) Loading @@ -51,9 +52,20 @@ MATCH_CRITERION_DIFF_RE = re.compile( ) class Ipv4Info(TypedDict): src_ip: str dst_ip: str src_port: str dst_port: str def get_removed_items( candidate_ietf_slice_dict: dict, running_ietf_slice_dict: dict ) -> dict: """ For the 'iterable_item_removed' scenario, returns dict with removed sdp / connection_group / match_criterion info. Raises an exception if there's inconsistent data or multiple items removed (which is not supported). """ removed_items = { "sdp": {"sdp_idx": None, "value": {}}, "connection_group": {"connection_group_idx": None, "value": {}}, Loading @@ -63,20 +75,24 @@ def get_removed_items( "value": {}, }, } running_slice_services = running_ietf_slice_dict["network-slice-services"][ "slice-service" ][0] running_slice_sdps = [sdp["id"] for sdp in running_slice_services["sdps"]["sdp"]] candidate_slice_services = candidate_ietf_slice_dict["network-slice-services"][ "slice-service" ][0] running_slice_sdps = [sdp["id"] for sdp in running_slice_services["sdps"]["sdp"]] candidiate_slice_sdps = [ sdp["id"] for sdp in candidate_slice_services["sdps"]["sdp"] ] removed_sdps = set(running_slice_sdps) - set(candidiate_slice_sdps) if len(removed_sdps) > 1: raise Exception("Multiple SDPs removed") removed_sdp_id = list(removed_sdps)[0] raise Exception("Multiple SDPs removed - not supported.") removed_sdp_id = removed_sdps.pop() removed_items["sdp"]["sdp_idx"] = running_slice_sdps.index(removed_sdp_id) removed_items["sdp"]["value"] = next( sdp Loading @@ -88,7 +104,7 @@ def get_removed_items( "match-criterion" ] if len(match_criteria) > 1: raise Exception("Multiple match criteria found") raise Exception("Multiple match criteria found - not supported") match_criterion = match_criteria[0] connection_grp_id = match_criterion["target-connection-group-id"] connection_groups = running_slice_services["connection-groups"]["connection-group"] Loading @@ -99,6 +115,7 @@ def get_removed_items( ) removed_items["connection_group"]["connection_group_idx"] = connection_group[0] removed_items["connection_group"]["value"] = connection_group[1] for sdp in running_slice_services["sdps"]["sdp"]: if sdp["id"] == removed_sdp_id: continue Loading @@ -119,41 +136,116 @@ def get_removed_items( or removed_items["connection_group"]["connection_group_idx"] is None ): raise Exception("sdp, connection group or match criterion not found") return removed_items def get_custom_config_rule( service_config: ServiceConfig, resource_key: str ) -> Optional[ConfigRule]: """ Returns the ConfigRule from service_config matching the provided resource_key if found, otherwise returns None. """ for cr in service_config.config_rules: if ( cr.WhichOneof("config_rule") == "custom" and cr.custom.resource_key == resource_key ): return cr return None 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 def get_running_candidate_ietf_slice_data_diff(service_config: ServiceConfig) -> Dict: """ Loads the JSON from the running/candidate resource ConfigRules and returns their DeepDiff comparison. """ running_cr = get_custom_config_rule(service_config, RUNNING_RESOURCE_KEY) candidate_cr = get_custom_config_rule(service_config, CANDIDATE_RESOURCE_KEY) running_value_dict = json.loads(running_cr.custom.resource_value) candidate_value_dict = json.loads(candidate_cr.custom.resource_value) return DeepDiff(running_value_dict, candidate_value_dict) def extract_qos_info( connection_groups: List, connection_grp_id: str, src_sdp_idx: str, dst_sdp_idx: str ) -> Dict: """ Extract QoS information from connection groups based on the connection group ID. """ qos_info = { "upstream": {"max_delay": "0", "bw": "0", "packet_loss": "0"}, "downstream": {"max_delay": "0", "bw": "0", "packet_loss": "0"}, } connection_group = next( (cg for cg in connection_groups if cg["id"] == connection_grp_id), None ) return ( DeepDiff( running_resource_value_dict, candidate_resource_value_dict, ), DeepDiff( running_resource_value_dict, candidate_resource_value_dict, ignore_order=True, ), if not connection_group: return qos_info for cc in connection_group["connectivity-construct"]: if ( cc["p2p-sender-sdp"] == src_sdp_idx and cc["p2p-receiver-sdp"] == dst_sdp_idx ): direction = "upstream" elif ( cc["p2p-sender-sdp"] == dst_sdp_idx and cc["p2p-receiver-sdp"] == src_sdp_idx ): direction = "downstream" else: raise Exception("invalid sender and receiver sdp ids") 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" ): qos_info[direction]["max_delay"] = metric_bound["bound"] elif ( metric_bound["metric-type"] == "ietf-network-slice-service:one-way-bandwidth" and metric_bound["metric-unit"] == "Mbps" ): qos_info[direction]["bw"] = metric_bound["bound"] elif ( metric_bound["metric-type"] == "ietf-network-slice-service:two-way-packet-loss" and metric_bound["metric-unit"] == "percentage" ): qos_info[direction]["packet_loss"] = metric_bound["percentile-value"] return qos_info def extract_match_criterion_ipv4_info(match_criterion: Dict) -> Ipv4Info: """ Extracts IPv4 info from the match criterion dictionary. """ src_ip = dst_ip = src_port = dst_port = "" for type_value in match_criterion["match-type"]: m_type = type_value["type"] val = type_value["value"][0] if m_type == "ietf-network-slice-service:source-ip-prefix": src_ip = val.split("/")[0] elif m_type == "ietf-network-slice-service:destination-ip-prefix": dst_ip = val.split("/")[0] elif m_type == "ietf-network-slice-service:source-tcp-port": src_port = val elif m_type == "ietf-network-slice-service:destination-tcp-port": dst_port = val return Ipv4Info( src_ip=src_ip, dst_ip=dst_ip, src_port=src_port, dst_port=dst_port, ) Loading @@ -174,22 +266,25 @@ class L3NMNCEServiceHandler(_ServiceHandler): chk_type("endpoints", endpoints, list) if len(endpoints) == 0: return [] results = [] try: context_client = ContextClient() service_config = self.__service.service_config settings = self.__settings_handler.get("/settings") 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) list_devices = context_client.ListDevices(Empty()) devices = list_devices.devices device_name_device = {d.name: d for d in devices} device_uuid_device = {d.device_id.device_uuid.uuid: d for d in devices} running_candidate_diff, running_candidate_diff_no_order = ( get_running_candidate_ietf_slice_data_diff(service_config) device_name_map = {d.name: d for d in devices} running_candidate_diff = get_running_candidate_ietf_slice_data_diff( service_config ) candidate_ietf_slice_cr = get_custom_config_rule( service_config, CANDIDATE_RESOURCE_KEY Loading @@ -203,15 +298,17 @@ class L3NMNCEServiceHandler(_ServiceHandler): running_resource_value_dict = json.loads( running_ietf_slice_cr.custom.resource_value ) service_name = running_resource_value_dict["network-slice-services"][ "slice-service" ][0]["id"] if not running_candidate_diff: # Slice Creation operation_type = "create" slice_services = candidate_resource_value_dict[ "network-slice-services" ]["slice-service"] slice_service = slice_services[0] slice_service = candidate_resource_value_dict["network-slice-services"][ "slice-service" ][0] sdps = slice_service["sdps"]["sdp"] connection_groups = slice_service["connection-groups"][ "connection-group" Loading @@ -219,7 +316,7 @@ class L3NMNCEServiceHandler(_ServiceHandler): sdp_ids = [sdp["id"] for sdp in sdps] for sdp in sdps: node_id = sdp["node-id"] device_obj = device_name_device[node_id] device_obj = device_name_map[node_id] device_controller = self.__task_executor.get_device_controller( device_obj ) Loading @@ -237,15 +334,15 @@ class L3NMNCEServiceHandler(_ServiceHandler): else: raise Exception("connection group id 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] operation_type = "create" slice_service = candidate_resource_value_dict["network-slice-services"][ "slice-service" ][0] sdps = slice_service["sdps"]["sdp"] connection_groups = slice_service["connection-groups"][ "connection-group" ] operation_type = "create" added_items = { "sdp": {"sdp_idx": None, "value": {}}, "connection_group": {"connection_group_idx": None, "value": {}}, Loading Loading @@ -287,6 +384,7 @@ class L3NMNCEServiceHandler(_ServiceHandler): connection_grp_id = connection_groups[ added_items["connection_group"]["connection_group_idx"] ]["id"] if ( connection_grp_id != added_items["match_criterion"]["value"][ Loading @@ -299,15 +397,15 @@ class L3NMNCEServiceHandler(_ServiceHandler): match_criteria = new_sdp["service-match-criteria"]["match-criterion"] match_criterion = match_criteria[0] elif "iterable_item_removed" in running_candidate_diff: # new SDP added slice_services = running_resource_value_dict["network-slice-services"][ operation_type = "delete" slice_service = running_resource_value_dict["network-slice-services"][ "slice-service" ] slice_service = slice_services[0] ][0] sdps = slice_service["sdps"]["sdp"] connection_groups = slice_service["connection-groups"][ "connection-group" ] operation_type = "delete" removed_items = get_removed_items( candidate_resource_value_dict, running_resource_value_dict ) Loading @@ -317,6 +415,7 @@ class L3NMNCEServiceHandler(_ServiceHandler): connection_grp_id = connection_groups[ removed_items["connection_group"]["connection_group_idx"] ]["id"] if ( connection_grp_id != removed_items["match_criterion"]["value"][ Loading @@ -330,65 +429,17 @@ class L3NMNCEServiceHandler(_ServiceHandler): "match-criterion" ] match_criterion = match_criteria[0] for type_value in match_criterion["match-type"]: if type_value["type"] == "ietf-network-slice-service:source-ip-prefix": src_ip = type_value["value"][0].split("/")[0] elif ( type_value["type"] == "ietf-network-slice-service:destination-ip-prefix" ): dst_ip = type_value["value"][0].split("/")[0] elif type_value["type"] == "ietf-network-slice-service:source-tcp-port": src_port = type_value["value"][0] elif ( type_value["type"] == "ietf-network-slice-service:destination-tcp-port" ): dst_port = type_value["value"][0] qos_info = { "upstream": {"max_delay": "0", "bw": "0", "packet_loss": "0"}, "downstream": {"max_delay": "0", "bw": "0", "packet_loss": "0"}, } for cg in connection_groups: if cg["id"] != connection_grp_id: continue for cc in cg["connectivity-construct"]: if ( cc["p2p-sender-sdp"] == src_sdp_idx and cc["p2p-receiver-sdp"] == dst_sdp_idx ): direction = "upstream" elif ( cc["p2p-sender-sdp"] == dst_sdp_idx and cc["p2p-receiver-sdp"] == src_sdp_idx ): direction = "downstream" else: raise Exception("invalid sender and receiver sdp ids") 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" ): qos_info[direction]["max_delay"] = metric_bound["bound"] elif ( metric_bound["metric-type"] == "ietf-network-slice-service:one-way-bandwidth" and metric_bound["metric-unit"] == "Mbps" ): qos_info[direction]["bw"] = metric_bound["bound"] elif ( metric_bound["metric-type"] == "ietf-network-slice-service:two-way-packet-loss" and metric_bound["metric-unit"] == "percentage" ): qos_info[direction]["packet_loss"] = metric_bound[ "percentile-value" ] break raise Exception( "transition from candidate to running info not supported" ) ip_info = extract_match_criterion_ipv4_info(match_criterion) qos_info = extract_qos_info( connection_groups, connection_grp_id, src_sdp_idx, dst_sdp_idx ) resource_value_dict = { "uuid": service_name, "operation_type": operation_type, Loading @@ -401,22 +452,23 @@ class L3NMNCEServiceHandler(_ServiceHandler): "upstream_max_bw": 2 * int(qos_info["upstream"]["bw"]) * 1e6, "downstream_assure_bw": int(qos_info["downstream"]["bw"]) * 1e6, "downstream_max_bw": 2 * int(qos_info["downstream"]["bw"]) * 1e6, "src_ip": src_ip, "src_port": src_port, "dst_ip": dst_ip, "dst_port": dst_port, "src_ip": ip_info["src_ip"], "src_port": ip_info["src_port"], "dst_ip": ip_info["dst_ip"], "dst_port": ip_info["dst_port"], } json_config_rules = setup_config_rules(service_name, resource_value_dict) LOGGER.debug(f"Config Rules: {json_config_rules}") 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) LOGGER.debug('Configured device "{:s}"'.format(controller.name)) except Exception as e: # pylint: disable=broad-except LOGGER.exception(f"P4: {e}") raise e results.append(e) return results @metered_subclass_method(METRICS_POOL) Loading src/service/service/service_handlers/l3nm_nce/__init__.py +1 −1 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # 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. Loading Loading
src/service/service/service_handlers/l3nm_nce/ConfigRules.py +1 −1 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # 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. Loading
src/service/service/service_handlers/l3nm_nce/L3NMNCEServiceHandler.py +162 −110 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # 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. Loading @@ -15,7 +15,7 @@ import json import logging import re from typing import Any, List, Optional, Tuple, Union from typing import Any, List, Optional, Tuple, Union, TypedDict, Dict from uuid import uuid4 from deepdiff import DeepDiff Loading @@ -40,6 +40,7 @@ CANDIDATE_RESOURCE_KEY = "candidate_ietf_slice" LOGGER = logging.getLogger(__name__) METRICS_POOL = MetricsPool("Service", "Handler", labels={"handler": "l3nm_nce"}) SDP_DIFF_RE = re.compile( r"^root\[\'network-slice-services\'\]\[\'slice-service\'\]\[0\]\[\'sdps\'\]\[\'sdp\'\]\[(\d)\]$" ) Loading @@ -51,9 +52,20 @@ MATCH_CRITERION_DIFF_RE = re.compile( ) class Ipv4Info(TypedDict): src_ip: str dst_ip: str src_port: str dst_port: str def get_removed_items( candidate_ietf_slice_dict: dict, running_ietf_slice_dict: dict ) -> dict: """ For the 'iterable_item_removed' scenario, returns dict with removed sdp / connection_group / match_criterion info. Raises an exception if there's inconsistent data or multiple items removed (which is not supported). """ removed_items = { "sdp": {"sdp_idx": None, "value": {}}, "connection_group": {"connection_group_idx": None, "value": {}}, Loading @@ -63,20 +75,24 @@ def get_removed_items( "value": {}, }, } running_slice_services = running_ietf_slice_dict["network-slice-services"][ "slice-service" ][0] running_slice_sdps = [sdp["id"] for sdp in running_slice_services["sdps"]["sdp"]] candidate_slice_services = candidate_ietf_slice_dict["network-slice-services"][ "slice-service" ][0] running_slice_sdps = [sdp["id"] for sdp in running_slice_services["sdps"]["sdp"]] candidiate_slice_sdps = [ sdp["id"] for sdp in candidate_slice_services["sdps"]["sdp"] ] removed_sdps = set(running_slice_sdps) - set(candidiate_slice_sdps) if len(removed_sdps) > 1: raise Exception("Multiple SDPs removed") removed_sdp_id = list(removed_sdps)[0] raise Exception("Multiple SDPs removed - not supported.") removed_sdp_id = removed_sdps.pop() removed_items["sdp"]["sdp_idx"] = running_slice_sdps.index(removed_sdp_id) removed_items["sdp"]["value"] = next( sdp Loading @@ -88,7 +104,7 @@ def get_removed_items( "match-criterion" ] if len(match_criteria) > 1: raise Exception("Multiple match criteria found") raise Exception("Multiple match criteria found - not supported") match_criterion = match_criteria[0] connection_grp_id = match_criterion["target-connection-group-id"] connection_groups = running_slice_services["connection-groups"]["connection-group"] Loading @@ -99,6 +115,7 @@ def get_removed_items( ) removed_items["connection_group"]["connection_group_idx"] = connection_group[0] removed_items["connection_group"]["value"] = connection_group[1] for sdp in running_slice_services["sdps"]["sdp"]: if sdp["id"] == removed_sdp_id: continue Loading @@ -119,41 +136,116 @@ def get_removed_items( or removed_items["connection_group"]["connection_group_idx"] is None ): raise Exception("sdp, connection group or match criterion not found") return removed_items def get_custom_config_rule( service_config: ServiceConfig, resource_key: str ) -> Optional[ConfigRule]: """ Returns the ConfigRule from service_config matching the provided resource_key if found, otherwise returns None. """ for cr in service_config.config_rules: if ( cr.WhichOneof("config_rule") == "custom" and cr.custom.resource_key == resource_key ): return cr return None 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 def get_running_candidate_ietf_slice_data_diff(service_config: ServiceConfig) -> Dict: """ Loads the JSON from the running/candidate resource ConfigRules and returns their DeepDiff comparison. """ running_cr = get_custom_config_rule(service_config, RUNNING_RESOURCE_KEY) candidate_cr = get_custom_config_rule(service_config, CANDIDATE_RESOURCE_KEY) running_value_dict = json.loads(running_cr.custom.resource_value) candidate_value_dict = json.loads(candidate_cr.custom.resource_value) return DeepDiff(running_value_dict, candidate_value_dict) def extract_qos_info( connection_groups: List, connection_grp_id: str, src_sdp_idx: str, dst_sdp_idx: str ) -> Dict: """ Extract QoS information from connection groups based on the connection group ID. """ qos_info = { "upstream": {"max_delay": "0", "bw": "0", "packet_loss": "0"}, "downstream": {"max_delay": "0", "bw": "0", "packet_loss": "0"}, } connection_group = next( (cg for cg in connection_groups if cg["id"] == connection_grp_id), None ) return ( DeepDiff( running_resource_value_dict, candidate_resource_value_dict, ), DeepDiff( running_resource_value_dict, candidate_resource_value_dict, ignore_order=True, ), if not connection_group: return qos_info for cc in connection_group["connectivity-construct"]: if ( cc["p2p-sender-sdp"] == src_sdp_idx and cc["p2p-receiver-sdp"] == dst_sdp_idx ): direction = "upstream" elif ( cc["p2p-sender-sdp"] == dst_sdp_idx and cc["p2p-receiver-sdp"] == src_sdp_idx ): direction = "downstream" else: raise Exception("invalid sender and receiver sdp ids") 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" ): qos_info[direction]["max_delay"] = metric_bound["bound"] elif ( metric_bound["metric-type"] == "ietf-network-slice-service:one-way-bandwidth" and metric_bound["metric-unit"] == "Mbps" ): qos_info[direction]["bw"] = metric_bound["bound"] elif ( metric_bound["metric-type"] == "ietf-network-slice-service:two-way-packet-loss" and metric_bound["metric-unit"] == "percentage" ): qos_info[direction]["packet_loss"] = metric_bound["percentile-value"] return qos_info def extract_match_criterion_ipv4_info(match_criterion: Dict) -> Ipv4Info: """ Extracts IPv4 info from the match criterion dictionary. """ src_ip = dst_ip = src_port = dst_port = "" for type_value in match_criterion["match-type"]: m_type = type_value["type"] val = type_value["value"][0] if m_type == "ietf-network-slice-service:source-ip-prefix": src_ip = val.split("/")[0] elif m_type == "ietf-network-slice-service:destination-ip-prefix": dst_ip = val.split("/")[0] elif m_type == "ietf-network-slice-service:source-tcp-port": src_port = val elif m_type == "ietf-network-slice-service:destination-tcp-port": dst_port = val return Ipv4Info( src_ip=src_ip, dst_ip=dst_ip, src_port=src_port, dst_port=dst_port, ) Loading @@ -174,22 +266,25 @@ class L3NMNCEServiceHandler(_ServiceHandler): chk_type("endpoints", endpoints, list) if len(endpoints) == 0: return [] results = [] try: context_client = ContextClient() service_config = self.__service.service_config settings = self.__settings_handler.get("/settings") 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) list_devices = context_client.ListDevices(Empty()) devices = list_devices.devices device_name_device = {d.name: d for d in devices} device_uuid_device = {d.device_id.device_uuid.uuid: d for d in devices} running_candidate_diff, running_candidate_diff_no_order = ( get_running_candidate_ietf_slice_data_diff(service_config) device_name_map = {d.name: d for d in devices} running_candidate_diff = get_running_candidate_ietf_slice_data_diff( service_config ) candidate_ietf_slice_cr = get_custom_config_rule( service_config, CANDIDATE_RESOURCE_KEY Loading @@ -203,15 +298,17 @@ class L3NMNCEServiceHandler(_ServiceHandler): running_resource_value_dict = json.loads( running_ietf_slice_cr.custom.resource_value ) service_name = running_resource_value_dict["network-slice-services"][ "slice-service" ][0]["id"] if not running_candidate_diff: # Slice Creation operation_type = "create" slice_services = candidate_resource_value_dict[ "network-slice-services" ]["slice-service"] slice_service = slice_services[0] slice_service = candidate_resource_value_dict["network-slice-services"][ "slice-service" ][0] sdps = slice_service["sdps"]["sdp"] connection_groups = slice_service["connection-groups"][ "connection-group" Loading @@ -219,7 +316,7 @@ class L3NMNCEServiceHandler(_ServiceHandler): sdp_ids = [sdp["id"] for sdp in sdps] for sdp in sdps: node_id = sdp["node-id"] device_obj = device_name_device[node_id] device_obj = device_name_map[node_id] device_controller = self.__task_executor.get_device_controller( device_obj ) Loading @@ -237,15 +334,15 @@ class L3NMNCEServiceHandler(_ServiceHandler): else: raise Exception("connection group id 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] operation_type = "create" slice_service = candidate_resource_value_dict["network-slice-services"][ "slice-service" ][0] sdps = slice_service["sdps"]["sdp"] connection_groups = slice_service["connection-groups"][ "connection-group" ] operation_type = "create" added_items = { "sdp": {"sdp_idx": None, "value": {}}, "connection_group": {"connection_group_idx": None, "value": {}}, Loading Loading @@ -287,6 +384,7 @@ class L3NMNCEServiceHandler(_ServiceHandler): connection_grp_id = connection_groups[ added_items["connection_group"]["connection_group_idx"] ]["id"] if ( connection_grp_id != added_items["match_criterion"]["value"][ Loading @@ -299,15 +397,15 @@ class L3NMNCEServiceHandler(_ServiceHandler): match_criteria = new_sdp["service-match-criteria"]["match-criterion"] match_criterion = match_criteria[0] elif "iterable_item_removed" in running_candidate_diff: # new SDP added slice_services = running_resource_value_dict["network-slice-services"][ operation_type = "delete" slice_service = running_resource_value_dict["network-slice-services"][ "slice-service" ] slice_service = slice_services[0] ][0] sdps = slice_service["sdps"]["sdp"] connection_groups = slice_service["connection-groups"][ "connection-group" ] operation_type = "delete" removed_items = get_removed_items( candidate_resource_value_dict, running_resource_value_dict ) Loading @@ -317,6 +415,7 @@ class L3NMNCEServiceHandler(_ServiceHandler): connection_grp_id = connection_groups[ removed_items["connection_group"]["connection_group_idx"] ]["id"] if ( connection_grp_id != removed_items["match_criterion"]["value"][ Loading @@ -330,65 +429,17 @@ class L3NMNCEServiceHandler(_ServiceHandler): "match-criterion" ] match_criterion = match_criteria[0] for type_value in match_criterion["match-type"]: if type_value["type"] == "ietf-network-slice-service:source-ip-prefix": src_ip = type_value["value"][0].split("/")[0] elif ( type_value["type"] == "ietf-network-slice-service:destination-ip-prefix" ): dst_ip = type_value["value"][0].split("/")[0] elif type_value["type"] == "ietf-network-slice-service:source-tcp-port": src_port = type_value["value"][0] elif ( type_value["type"] == "ietf-network-slice-service:destination-tcp-port" ): dst_port = type_value["value"][0] qos_info = { "upstream": {"max_delay": "0", "bw": "0", "packet_loss": "0"}, "downstream": {"max_delay": "0", "bw": "0", "packet_loss": "0"}, } for cg in connection_groups: if cg["id"] != connection_grp_id: continue for cc in cg["connectivity-construct"]: if ( cc["p2p-sender-sdp"] == src_sdp_idx and cc["p2p-receiver-sdp"] == dst_sdp_idx ): direction = "upstream" elif ( cc["p2p-sender-sdp"] == dst_sdp_idx and cc["p2p-receiver-sdp"] == src_sdp_idx ): direction = "downstream" else: raise Exception("invalid sender and receiver sdp ids") 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" ): qos_info[direction]["max_delay"] = metric_bound["bound"] elif ( metric_bound["metric-type"] == "ietf-network-slice-service:one-way-bandwidth" and metric_bound["metric-unit"] == "Mbps" ): qos_info[direction]["bw"] = metric_bound["bound"] elif ( metric_bound["metric-type"] == "ietf-network-slice-service:two-way-packet-loss" and metric_bound["metric-unit"] == "percentage" ): qos_info[direction]["packet_loss"] = metric_bound[ "percentile-value" ] break raise Exception( "transition from candidate to running info not supported" ) ip_info = extract_match_criterion_ipv4_info(match_criterion) qos_info = extract_qos_info( connection_groups, connection_grp_id, src_sdp_idx, dst_sdp_idx ) resource_value_dict = { "uuid": service_name, "operation_type": operation_type, Loading @@ -401,22 +452,23 @@ class L3NMNCEServiceHandler(_ServiceHandler): "upstream_max_bw": 2 * int(qos_info["upstream"]["bw"]) * 1e6, "downstream_assure_bw": int(qos_info["downstream"]["bw"]) * 1e6, "downstream_max_bw": 2 * int(qos_info["downstream"]["bw"]) * 1e6, "src_ip": src_ip, "src_port": src_port, "dst_ip": dst_ip, "dst_port": dst_port, "src_ip": ip_info["src_ip"], "src_port": ip_info["src_port"], "dst_ip": ip_info["dst_ip"], "dst_port": ip_info["dst_port"], } json_config_rules = setup_config_rules(service_name, resource_value_dict) LOGGER.debug(f"Config Rules: {json_config_rules}") 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) LOGGER.debug('Configured device "{:s}"'.format(controller.name)) except Exception as e: # pylint: disable=broad-except LOGGER.exception(f"P4: {e}") raise e results.append(e) return results @metered_subclass_method(METRICS_POOL) Loading
src/service/service/service_handlers/l3nm_nce/__init__.py +1 −1 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # 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. Loading