Skip to content
Snippets Groups Projects
Commit 2e612108 authored by Shayan Hajipour's avatar Shayan Hajipour
Browse files

refactoring

parent 91166434
No related branches found
No related tags found
2 merge requests!359Release TeraFlowSDN 5.0,!302Resolve "Elaborate IETF Slice NBI to support adding/deleting new sdp, connection group, and match criteria"
...@@ -32,90 +32,177 @@ ADDRESS_PREFIX = 24 ...@@ -32,90 +32,177 @@ ADDRESS_PREFIX = 24
RAISE_IF_DIFFERS = False RAISE_IF_DIFFERS = False
def get_endpoint_controller_type( def validate_ietf_slice_data(request_data: Dict) -> None:
endpoint: EndPointId, context_client: ContextClient """
) -> str: Validate the provided IETF slice data against the YANG model.
endpoint_device: Device = context_client.GetDevice(endpoint.device_id) """
if endpoint_device.controller_id == DeviceId(): yang_validator = YangValidator("ietf-network-slice-service")
return "" _ = yang_validator.parse_to_dict(request_data)
controller = context_client.GetDevice(endpoint_device.controller_id) yang_validator.destroy()
if controller is None:
controller_uuid = endpoint_device.controller_id.device_uuid.uuid
raise Exception("Device({:s}) not found".format(str(controller_uuid)))
return controller.device_type
def get_custom_config_rule( def get_custom_config_rule(
service_config: ServiceConfig, resource_key: str service_config: ServiceConfig, resource_key: str
) -> Optional[ConfigRule]: ) -> Optional[ConfigRule]:
"""
Retrieve the custom config rule with the given resource_key from a ServiceConfig.
"""
for cr in service_config.config_rules: for cr in service_config.config_rules:
if ( if (
cr.WhichOneof("config_rule") == "custom" cr.WhichOneof("config_rule") == "custom"
and cr.custom.resource_key == resource_key and cr.custom.resource_key == resource_key
): ):
return cr return cr
return None
def get_ietf_data_from_config(slice_request: Slice, resource_key: str) -> Dict:
"""
Retrieve the IETF data (as a Python dict) from a slice's config rule for the specified resource_key.
Raises an exception if not found.
"""
config_rule = get_custom_config_rule(slice_request.slice_config, resource_key)
if not config_rule:
raise Exception(f"IETF data not found for resource_key: {resource_key}")
return json.loads(config_rule.custom.resource_value)
def update_ietf_data_in_config(
slice_request: Slice, resource_key: str, ietf_data: Dict
) -> None:
"""
Update the slice config rule (identified by resource_key) with the provided IETF data.
"""
fields = {name: (value, RAISE_IF_DIFFERS) for name, value in ietf_data.items()}
update_config_rule_custom(
slice_request.slice_config.config_rules, resource_key, fields
)
def build_constraints_from_connection_group(connection_group: dict) -> List[Constraint]:
"""
Build a list of Constraints from the 'metric-bound' data in a connection group.
"""
constraints = []
metric_bounds = connection_group["connectivity-construct"][0][
"service-slo-sle-policy"
]["slo-policy"]["metric-bound"]
for metric in metric_bounds:
metric_type = metric["metric-type"]
if metric_type == "ietf-nss:one-way-delay-maximum":
bound_value = float(metric["bound"])
constraint = Constraint()
constraint.sla_latency.e2e_latency_ms = bound_value
constraints.append(constraint)
elif metric_type == "ietf-nss:one-way-bandwidth":
bound_value = float(metric["bound"])
constraint = Constraint()
# Convert from Mbps to Gbps if needed
constraint.sla_capacity.capacity_gbps = bound_value / 1.0e3
constraints.append(constraint)
return constraints
def get_endpoint_controller_type(
endpoint: EndPointId, context_client: ContextClient
) -> str:
"""
Retrieve the device type of an endpoint's controller device, if any; otherwise returns an empty string.
"""
endpoint_device: Device = context_client.GetDevice(endpoint.device_id)
if endpoint_device.controller_id == DeviceId():
return ""
controller = context_client.GetDevice(endpoint_device.controller_id)
if controller is None:
controller_uuid = endpoint_device.controller_id.device_uuid.uuid
raise Exception(f"Controller device {controller_uuid} not found")
return controller.device_type
def sort_endpoints( def sort_endpoints(
endpoinst_list: List[EndPointId], endpoints_list: List[EndPointId],
sdps: List, sdps: List,
connection_group: Dict, connection_group: Dict,
context_client: ContextClient, context_client: ContextClient,
) -> List[EndPointId]: ) -> List[EndPointId]:
first_ep = endpoinst_list[0] """
Sort the endpoints_list based on controller type:
- If the first endpoint is an NCE, keep order.
- If the last endpoint is an NCE, reverse order.
- Otherwise, use the 'p2p-sender-sdp' from the connection group to decide.
"""
if not endpoints_list:
return endpoints_list
first_ep = endpoints_list[0]
last_ep = endpoints_list[-1]
first_controller_type = get_endpoint_controller_type(first_ep, context_client) first_controller_type = get_endpoint_controller_type(first_ep, context_client)
last_ep = endpoinst_list[-1]
last_controller_type = get_endpoint_controller_type(last_ep, context_client) last_controller_type = get_endpoint_controller_type(last_ep, context_client)
if first_controller_type == DeviceTypeEnum.NCE.value: if first_controller_type == DeviceTypeEnum.NCE.value:
return endpoinst_list return endpoints_list
elif last_controller_type == DeviceTypeEnum.NCE.value: elif last_controller_type == DeviceTypeEnum.NCE.value:
return endpoinst_list[::-1] return endpoints_list[::-1]
else:
src_sdp_id = connection_group["connectivity-construct"][0]["p2p-sender-sdp"] src_sdp_id = connection_group["connectivity-construct"][0]["p2p-sender-sdp"]
sdp_id_name_mapping = {sdp["id"]: sdp["node-id"] for sdp in sdps} sdp_id_name_mapping = {sdp["id"]: sdp["node-id"] for sdp in sdps}
if ( if endpoints_list[0].device_id.device_uuid.uuid == sdp_id_name_mapping[src_sdp_id]:
endpoinst_list[0].device_id.device_uuid.uuid return endpoints_list
== sdp_id_name_mapping[src_sdp_id] return endpoints_list[::-1]
):
return endpoinst_list
return endpoinst_list[::-1]
def replace_ont_endpoint_with_emu_dc( def replace_ont_endpoint_with_emu_dc(
endpoint_list: List, context_client: ContextClient endpoint_list: List[EndPointId], context_client: ContextClient
) -> List: ) -> List[EndPointId]:
"""
Replace an ONT endpoint in endpoint_list with an 'emu-datacenter' endpoint if found.
One endpoint must be managed (controller_id != empty), the other must be unmanaged.
"""
if len(endpoint_list) != 2:
raise Exception(
"Expecting exactly two endpoints to handle ONT -> emu-dc replacement"
)
link_list = context_client.ListLinks(Empty()) link_list = context_client.ListLinks(Empty())
links = list(link_list.links) links = list(link_list.links)
devices_list = context_client.ListDevices(Empty()) devices_list = context_client.ListDevices(Empty())
devices = devices_list.devices devices = devices_list.devices
uuid_name_map = {d.device_id.device_uuid.uuid: d.name for d in devices} uuid_name_map = {d.device_id.device_uuid.uuid: d.name for d in devices}
uuid_device_map = {d.device_id.device_uuid.uuid: d for d in devices} uuid_device_map = {d.device_id.device_uuid.uuid: d for d in devices}
name_device_map = {d.name: d for d in devices} name_device_map = {d.name: d for d in devices}
endpoint_id_1 = endpoint_list[0]
endpoint_id_1, endpoint_id_2 = endpoint_list
device_uuid_1 = endpoint_id_1.device_id.device_uuid.uuid device_uuid_1 = endpoint_id_1.device_id.device_uuid.uuid
device_1 = name_device_map[device_uuid_1]
endpoint_id_2 = endpoint_list[1]
device_uuid_2 = endpoint_id_2.device_id.device_uuid.uuid device_uuid_2 = endpoint_id_2.device_id.device_uuid.uuid
device_2 = name_device_map[device_uuid_2]
device_1 = name_device_map.get(device_uuid_1)
device_2 = name_device_map.get(device_uuid_2)
if not device_1 or not device_2:
raise Exception("One or both devices not found in name_device_map")
# Check if the first endpoint is managed
if device_1.controller_id != DeviceId(): if device_1.controller_id != DeviceId():
for link in links: for link in links:
link_endpoints = list(link.link_endpoint_ids) link_endpoints = list(link.link_endpoint_ids)
link_ep_1 = link_endpoints[0] link_ep_1, link_ep_2 = link_endpoints
link_ep_2 = link_endpoints[1]
if ( if (
device_uuid_1 == uuid_name_map[link_ep_1.device_id.device_uuid.uuid] device_uuid_1 == uuid_name_map.get(link_ep_1.device_id.device_uuid.uuid)
and uuid_device_map[link_ep_2.device_id.device_uuid.uuid].device_type and uuid_device_map[link_ep_2.device_id.device_uuid.uuid].device_type
== "emu-datacenter" == "emu-datacenter"
): ):
endpoint_list[0] = link_ep_2 endpoint_list[0] = link_ep_2
break break
# Otherwise, check if the second endpoint is managed
elif device_2.controller_id != DeviceId(): elif device_2.controller_id != DeviceId():
for link in links: for link in links:
link_endpoints = list(link.link_endpoint_ids) link_endpoints = list(link.link_endpoint_ids)
link_ep_1 = link_endpoints[0] link_ep_1, link_ep_2 = link_endpoints
link_ep_2 = link_endpoints[1]
if ( if (
device_uuid_2 == uuid_name_map[link_ep_1.device_id.device_uuid.uuid] device_uuid_2 == uuid_name_map.get(link_ep_1.device_id.device_uuid.uuid)
and uuid_device_map[link_ep_2.device_id.device_uuid.uuid].device_type and uuid_device_map[link_ep_2.device_id.device_uuid.uuid].device_type
== "emu-datacenter" == "emu-datacenter"
): ):
...@@ -123,31 +210,34 @@ def replace_ont_endpoint_with_emu_dc( ...@@ -123,31 +210,34 @@ def replace_ont_endpoint_with_emu_dc(
break break
else: else:
raise Exception( raise Exception(
"one of the sdps should be managed by a controller and the other one should not be controlled" "One endpoint should be managed by a controller and the other should not be"
) )
return endpoint_list
def validate_ietf_slice_data(request_data: Dict) -> None: return endpoint_list
yang_validator = YangValidator("ietf-network-slice-service")
_ = yang_validator.parse_to_dict(request_data)
yang_validator.destroy()
class IETFSliceHandler: class IETFSliceHandler:
@staticmethod @staticmethod
def get_all_ietf_slices(context_client: ContextClient) -> Dict: def get_all_ietf_slices(context_client: ContextClient) -> Dict:
"""
Retrieve all IETF slices from the (single) context. Expects exactly one context in the system.
"""
existing_context_ids = context_client.ListContextIds(Empty()) existing_context_ids = context_client.ListContextIds(Empty())
context_ids = list(existing_context_ids.context_ids) context_ids = list(existing_context_ids.context_ids)
if len(context_ids) != 1: if len(context_ids) != 1:
raise Exception("Number of contexts should be 1") raise Exception("Number of contexts should be exactly 1")
slices_list = context_client.ListSlices(context_ids[0]) slices_list = context_client.ListSlices(context_ids[0])
slices = slices_list.slices slices = slices_list.slices
ietf_slices = {"network-slice-services": {"slice-service": []}} ietf_slices = {"network-slice-services": {"slice-service": []}}
for slice in slices: for slc in slices:
candidate_cr = get_custom_config_rule( candidate_cr = get_custom_config_rule(
slice.slice_config, CANDIDATE_RESOURCE_KEY slc.slice_config, CANDIDATE_RESOURCE_KEY
) )
if not candidate_cr:
# Skip slices that don't have the candidate_ietf_slice data
continue
candidate_ietf_data = json.loads(candidate_cr.custom.resource_value) candidate_ietf_data = json.loads(candidate_cr.custom.resource_value)
ietf_slices["network-slice-services"]["slice-service"].append( ietf_slices["network-slice-services"]["slice-service"].append(
candidate_ietf_data["network-slice-services"]["slice-service"][0] candidate_ietf_data["network-slice-services"]["slice-service"][0]
...@@ -158,27 +248,37 @@ class IETFSliceHandler: ...@@ -158,27 +248,37 @@ class IETFSliceHandler:
def create_slice_service( def create_slice_service(
request_data: dict, context_client: ContextClient request_data: dict, context_client: ContextClient
) -> Slice: ) -> Slice:
"""
Create a new slice service from the provided IETF data, applying validations and constructing a Slice object.
"""
# Ensure the top-level key is "network-slice-services"
if "network-slice-services" not in request_data: if "network-slice-services" not in request_data:
request_data = {"network-slice-services": request_data} request_data = {"network-slice-services": request_data}
validate_ietf_slice_data(request_data) validate_ietf_slice_data(request_data)
slice_services = request_data["network-slice-services"]["slice-service"] slice_service = request_data["network-slice-services"]["slice-service"][0]
slice_service = slice_services[0]
slice_id = slice_service["id"] slice_id = slice_service["id"]
sdps = slice_service["sdps"]["sdp"] sdps = slice_service["sdps"]["sdp"]
connection_groups = slice_service["connection-groups"]["connection-group"]
if len(sdps) != 2: if len(sdps) != 2:
raise Exception("Number of SDPs should be 2") raise Exception("Number of SDPs should be exactly 2")
slice_request: Slice = Slice()
connection_groups = slice_service["connection-groups"]["connection-group"]
slice_request = Slice()
slice_request.slice_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME slice_request.slice_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
slice_request.slice_id.slice_uuid.uuid = slice_id slice_request.slice_id.slice_uuid.uuid = slice_id
slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED
list_endpoints = [] list_endpoints = []
endpoint_config_rules = [] endpoint_config_rules = []
connection_group_ids = set() connection_group_ids = set()
# Build endpoints from SDPs
for sdp in sdps: for sdp in sdps:
attachment_circuits = sdp["attachment-circuits"]["attachment-circuit"] attachment_circuits = sdp["attachment-circuits"]["attachment-circuit"]
if len(attachment_circuits) != 1: if len(attachment_circuits) != 1:
raise Exception("All SDPs should have 1 attachment-circuit") raise Exception("Each SDP must have exactly 1 attachment-circuit")
endpoint = EndPointId() endpoint = EndPointId()
endpoint.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME endpoint.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
device_uuid = sdp["node-id"] device_uuid = sdp["node-id"]
...@@ -186,11 +286,15 @@ class IETFSliceHandler: ...@@ -186,11 +286,15 @@ class IETFSliceHandler:
endpoint_uuid = attachment_circuits[0]["ac-tp-id"] endpoint_uuid = attachment_circuits[0]["ac-tp-id"]
endpoint.endpoint_uuid.uuid = endpoint_uuid endpoint.endpoint_uuid.uuid = endpoint_uuid
list_endpoints.append(endpoint) list_endpoints.append(endpoint)
# Keep track of connection-group-id from each SDP
connection_group_ids.add( connection_group_ids.add(
sdp["service-match-criteria"]["match-criterion"][0][ sdp["service-match-criteria"]["match-criterion"][0][
"target-connection-group-id" "target-connection-group-id"
] ]
) )
# Endpoint-specific config rule fields
endpoint_config_rule_fields = { endpoint_config_rule_fields = {
"address_ip": (endpoint_uuid, RAISE_IF_DIFFERS), "address_ip": (endpoint_uuid, RAISE_IF_DIFFERS),
"address_prefix": (ADDRESS_PREFIX, RAISE_IF_DIFFERS), "address_prefix": (ADDRESS_PREFIX, RAISE_IF_DIFFERS),
...@@ -201,41 +305,36 @@ class IETFSliceHandler: ...@@ -201,41 +305,36 @@ class IETFSliceHandler:
endpoint_config_rule_fields, endpoint_config_rule_fields,
) )
) )
if len(connection_group_ids) != 1: if len(connection_group_ids) != 1:
raise Exception("SDPs target-connection-group-id do not match") raise Exception("SDPs do not share a common connection-group-id")
list_constraints = []
for cg in connection_groups: # Build constraints from the matching connection group
if cg["id"] != list(connection_group_ids)[0]: unique_cg_id = connection_group_ids.pop()
continue found_cg = next(
metric_bounds = cg["connectivity-construct"][0]["service-slo-sle-policy"][ (cg for cg in connection_groups if cg["id"] == unique_cg_id), None
"slo-policy" )
]["metric-bound"] if not found_cg:
for metric in metric_bounds: raise Exception("The connection group referenced by the SDPs was not found")
if metric["metric-type"] == "ietf-nss:one-way-delay-maximum":
constraint = Constraint() list_constraints = build_constraints_from_connection_group(found_cg)
constraint.sla_latency.e2e_latency_ms = float(metric["bound"])
list_constraints.append(constraint) # Sort endpoints and optionally replace the ONT endpoint
elif metric["metric-type"] == "ietf-nss:one-way-bandwidth": list_endpoints = sort_endpoints(list_endpoints, sdps, found_cg, context_client)
constraint = Constraint()
constraint.sla_capacity.capacity_gbps = (
float(metric["bound"]) / 1.0e3
)
list_constraints.append(constraint)
break
else:
raise Exception("connection group not found")
list_endpoints = sort_endpoints(list_endpoints, sdps, cg, context_client)
list_endpoints = replace_ont_endpoint_with_emu_dc( list_endpoints = replace_ont_endpoint_with_emu_dc(
list_endpoints, context_client list_endpoints, context_client
) )
slice_request.slice_endpoint_ids.extend(list_endpoints) slice_request.slice_endpoint_ids.extend(list_endpoints)
slice_request.slice_constraints.extend(list_constraints) slice_request.slice_constraints.extend(list_constraints)
# TODO adding owner, needs to be recoded after updating the bindings
owner = slice_id # Set slice owner
slice_request.slice_owner.owner_string = owner slice_request.slice_owner.owner_string = slice_id
slice_request.slice_owner.owner_uuid.uuid = str( slice_request.slice_owner.owner_uuid.uuid = str(
uuid.uuid5(uuid.NAMESPACE_DNS, owner) uuid.uuid5(uuid.NAMESPACE_DNS, slice_id)
) )
# Update slice config with IETF data (both running and candidate)
ietf_slice_fields = { ietf_slice_fields = {
name: (value, RAISE_IF_DIFFERS) for name, value in request_data.items() name: (value, RAISE_IF_DIFFERS) for name, value in request_data.items()
} }
...@@ -250,6 +349,7 @@ class IETFSliceHandler: ...@@ -250,6 +349,7 @@ class IETFSliceHandler:
ietf_slice_fields, ietf_slice_fields,
) )
# Update endpoint config rules
for ep_cr_key, ep_cr_fields in endpoint_config_rules: for ep_cr_key, ep_cr_fields in endpoint_config_rules:
update_config_rule_custom( update_config_rule_custom(
slice_request.slice_config.config_rules, ep_cr_key, ep_cr_fields slice_request.slice_config.config_rules, ep_cr_key, ep_cr_fields
...@@ -261,120 +361,113 @@ class IETFSliceHandler: ...@@ -261,120 +361,113 @@ class IETFSliceHandler:
def create_sdp( def create_sdp(
request_data: dict, slice_uuid: str, context_client: ContextClient request_data: dict, slice_uuid: str, context_client: ContextClient
) -> Slice: ) -> Slice:
"""
Add a new SDP to an existing slice, updating the candidate IETF data.
"""
sdps = request_data["sdp"] sdps = request_data["sdp"]
if len(sdps) != 1: if len(sdps) != 1:
raise Exception("Number of SDPs should be 1") raise Exception("Number of SDPs to create must be exactly 1")
new_sdp = sdps[0] new_sdp = sdps[0]
# slice_request = get_slice_by_uuid(context_client, slice_uuid)
slice_request = get_slice_by_defualt_name( slice_request = get_slice_by_defualt_name(
context_client, slice_uuid, rw_copy=False context_client, slice_uuid, rw_copy=False
) )
for cr in slice_request.slice_config.config_rules: ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY)
if cr.WhichOneof("config_rule") != "custom":
continue slice_service = ietf_data["network-slice-services"]["slice-service"][0]
if cr.custom.resource_key == CANDIDATE_RESOURCE_KEY:
ietf_data = json.loads(cr.custom.resource_value)
break
else:
raise Exception("ietf data not found")
slice_services = ietf_data["network-slice-services"]["slice-service"]
slice_service = slice_services[0]
slice_sdps = slice_service["sdps"]["sdp"] slice_sdps = slice_service["sdps"]["sdp"]
slice_sdps.append(new_sdp) slice_sdps.append(new_sdp)
fields = {name: (value, RAISE_IF_DIFFERS) for name, value in ietf_data.items()}
update_config_rule_custom( # Save updated IETF data
slice_request.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data)
)
return slice_request return slice_request
@staticmethod @staticmethod
def delete_sdp( def delete_sdp(
slice_uuid: str, sdp_id: str, context_client: ContextClient slice_uuid: str, sdp_id: str, context_client: ContextClient
) -> Slice: ) -> Slice:
# slice_request = get_slice_by_uuid(context_client, slice_uuid) """
Delete the specified SDP from an existing slice's candidate IETF data.
"""
slice_request = get_slice_by_defualt_name( slice_request = get_slice_by_defualt_name(
context_client, slice_uuid, rw_copy=False context_client, slice_uuid, rw_copy=False
) )
for cr in slice_request.slice_config.config_rules: ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY)
if cr.WhichOneof("config_rule") != "custom":
continue slice_service = ietf_data["network-slice-services"]["slice-service"][0]
if cr.custom.resource_key == CANDIDATE_RESOURCE_KEY:
ietf_data = json.loads(cr.custom.resource_value)
break
else:
raise Exception("ietf data not found")
slice_services = ietf_data["network-slice-services"]["slice-service"]
slice_service = slice_services[0]
slice_sdps = slice_service["sdps"]["sdp"] slice_sdps = slice_service["sdps"]["sdp"]
sdp_idx = list((slice_sdp["id"] == sdp_id for slice_sdp in slice_sdps)).index(
True # Find and remove the matching SDP
sdp_idx = next(
(i for i, sdp in enumerate(slice_sdps) if sdp["id"] == sdp_id), None
) )
if sdp_idx is None:
raise Exception(f"SDP with id '{sdp_id}' not found in slice '{slice_uuid}'")
slice_sdps.pop(sdp_idx) slice_sdps.pop(sdp_idx)
fields = {name: (value, RAISE_IF_DIFFERS) for name, value in ietf_data.items()}
update_config_rule_custom( update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data)
slice_request.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields
)
return slice_request return slice_request
@staticmethod @staticmethod
def create_connection_group( def create_connection_group(
request_data: dict, slice_id: str, context_client: ContextClient request_data: dict, slice_id: str, context_client: ContextClient
) -> Slice: ) -> Slice:
"""
Add a new connection group to an existing slice's candidate IETF data.
"""
connection_groups = request_data["connection-group"] connection_groups = request_data["connection-group"]
if len(connection_groups) != 1: if len(connection_groups) != 1:
raise Exception("Number of connection groups should be 1") raise Exception("Number of connection groups to create must be exactly 1")
new_connection_group = connection_groups[0] new_connection_group = connection_groups[0]
# slice = get_slice_by_uuid(context_client, slice_id) slice_request = get_slice_by_defualt_name(
slice = get_slice_by_defualt_name(context_client, slice_id, rw_copy=False) context_client, slice_id, rw_copy=False
for cr in slice.slice_config.config_rules: )
if cr.WhichOneof("config_rule") != "custom": ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY)
continue
if cr.custom.resource_key == CANDIDATE_RESOURCE_KEY: slice_service = ietf_data["network-slice-services"]["slice-service"][0]
ietf_data = json.loads(cr.custom.resource_value)
break
else:
raise Exception("ietf data not found")
slice_services = ietf_data["network-slice-services"]["slice-service"]
slice_service = slice_services[0]
slice_connection_groups = slice_service["connection-groups"]["connection-group"] slice_connection_groups = slice_service["connection-groups"]["connection-group"]
slice_connection_groups.append(new_connection_group) slice_connection_groups.append(new_connection_group)
fields = {name: (value, RAISE_IF_DIFFERS) for name, value in ietf_data.items()}
update_config_rule_custom( # Validate the updated data, then save
slice.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields
)
validate_ietf_slice_data(ietf_data) validate_ietf_slice_data(ietf_data)
return slice update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data)
return slice_request
@staticmethod @staticmethod
def update_connection_group( def update_connection_group(
slice_name: str, slice_name: str,
updated_connection_group: dict, updated_connection_group: dict,
context_client: ContextClient, context_client: ContextClient,
): ) -> Slice:
"""
Update an existing connection group in the candidate IETF data.
"""
slice_request = get_slice_by_defualt_name( slice_request = get_slice_by_defualt_name(
context_client, slice_name, rw_copy=False context_client, slice_name, rw_copy=False
) )
slice_config = slice_request.slice_config candidate_ietf_data = get_ietf_data_from_config(
cr = get_custom_config_rule(slice_config, CANDIDATE_RESOURCE_KEY) slice_request, CANDIDATE_RESOURCE_KEY
candidate_ietf_data = json.loads(cr.custom.resource_value) )
slice_services = candidate_ietf_data["network-slice-services"]["slice-service"]
slice_service = slice_services[0] slice_service = candidate_ietf_data["network-slice-services"]["slice-service"][
0
]
slice_connection_groups = slice_service["connection-groups"]["connection-group"] slice_connection_groups = slice_service["connection-groups"]["connection-group"]
connection_group_id = updated_connection_group["id"]
cg_idx = list( cg_id = updated_connection_group["id"]
( cg_idx = next(
slice_cg["id"] == connection_group_id (i for i, cg in enumerate(slice_connection_groups) if cg["id"] == cg_id),
for slice_cg in slice_connection_groups None,
) )
).index(True) if cg_idx is None:
raise Exception(f"Connection group with id '{cg_id}' not found")
slice_connection_groups[cg_idx] = updated_connection_group slice_connection_groups[cg_idx] = updated_connection_group
fields = { update_ietf_data_in_config(
name: (value, RAISE_IF_DIFFERS) slice_request, CANDIDATE_RESOURCE_KEY, candidate_ietf_data
for name, value in candidate_ietf_data.items()
}
update_config_rule_custom(
slice_request.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields
) )
slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED
return slice_request return slice_request
...@@ -382,30 +475,39 @@ class IETFSliceHandler: ...@@ -382,30 +475,39 @@ class IETFSliceHandler:
def delete_connection_group( def delete_connection_group(
slice_uuid: str, connection_group_id: str, context_client: ContextClient slice_uuid: str, connection_group_id: str, context_client: ContextClient
) -> Slice: ) -> Slice:
# slice_request = get_slice_by_uuid(context_client, slice_uuid) """
Remove an existing connection group from the candidate IETF data of a slice.
"""
slice_request = get_slice_by_defualt_name( slice_request = get_slice_by_defualt_name(
context_client, slice_uuid, rw_copy=False context_client, slice_uuid, rw_copy=False
) )
slice_config = slice_request.slice_config candidate_ietf_data = get_ietf_data_from_config(
cr = get_custom_config_rule(slice_config, CANDIDATE_RESOURCE_KEY) slice_request, CANDIDATE_RESOURCE_KEY
candidate_ietf_data = json.loads(cr.custom.resource_value) )
slice_services = candidate_ietf_data["network-slice-services"]["slice-service"]
slice_service = slice_services[0] slice_service = candidate_ietf_data["network-slice-services"]["slice-service"][
0
]
slice_connection_groups = slice_service["connection-groups"]["connection-group"] slice_connection_groups = slice_service["connection-groups"]["connection-group"]
sdp_idx = list(
cg_idx = next(
( (
slice_cr["id"] == connection_group_id i
for slice_cr in slice_connection_groups for i, cg in enumerate(slice_connection_groups)
if cg["id"] == connection_group_id
),
None,
)
if cg_idx is None:
raise Exception(
f"Connection group with id '{connection_group_id}' not found"
) )
).index(True)
removed_connection_group = slice_connection_groups.pop(sdp_idx) slice_connection_groups.pop(cg_idx)
fields = { update_ietf_data_in_config(
name: (value, RAISE_IF_DIFFERS) slice_request, CANDIDATE_RESOURCE_KEY, candidate_ietf_data
for name, value in candidate_ietf_data.items()
}
update_config_rule_custom(
slice_request.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields
) )
slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED
return slice_request return slice_request
...@@ -413,91 +515,55 @@ class IETFSliceHandler: ...@@ -413,91 +515,55 @@ class IETFSliceHandler:
def create_match_criteria( def create_match_criteria(
request_data: dict, slice_name: str, sdp_id: str, context_client: ContextClient request_data: dict, slice_name: str, sdp_id: str, context_client: ContextClient
) -> Slice: ) -> Slice:
"""
Create a new match-criterion for the specified SDP in a slice's candidate IETF data.
"""
match_criteria = request_data["match-criterion"] match_criteria = request_data["match-criterion"]
if len(match_criteria) != 1: if len(match_criteria) != 1:
raise Exception("Number of SDPs should be 1") raise Exception(
"Number of match-criterion entries to create must be exactly 1"
)
new_match_criterion = match_criteria[0] new_match_criterion = match_criteria[0]
target_connection_group_id = new_match_criterion["target-connection-group-id"] target_connection_group_id = new_match_criterion["target-connection-group-id"]
# slice_request = get_slice_by_uuid(context_client, slice_id)
slice_request = get_slice_by_defualt_name( slice_request = get_slice_by_defualt_name(
context_client, slice_name, rw_copy=False context_client, slice_name, rw_copy=False
) )
for cr in slice_request.slice_config.config_rules: ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY)
if cr.WhichOneof("config_rule") != "custom":
continue slice_service = ietf_data["network-slice-services"]["slice-service"][0]
if cr.custom.resource_key == CANDIDATE_RESOURCE_KEY:
ietf_data = json.loads(cr.custom.resource_value)
break
else:
raise Exception("ietf data not found")
slice_services = ietf_data["network-slice-services"]["slice-service"]
slice_service = slice_services[0]
slice_id = slice_service["id"]
sdps = slice_service["sdps"]["sdp"]
connection_groups = slice_service["connection-groups"]["connection-group"] connection_groups = slice_service["connection-groups"]["connection-group"]
slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED sdps = slice_service["sdps"]["sdp"]
list_endpoints = []
for sdp in sdps: # Find the referenced connection group
if ( found_cg = next(
sdp["service-match-criteria"]["match-criterion"][0][ (cg for cg in connection_groups if cg["id"] == target_connection_group_id),
"target-connection-group-id" None,
] )
== target_connection_group_id if not found_cg:
): raise Exception(
attachment_circuits = sdp["attachment-circuits"]["attachment-circuit"] f"Connection group '{target_connection_group_id}' not found"
if len(attachment_circuits) != 1: )
raise Exception("All SDPs should have 1 attachment-circuit")
endpoint = EndPointId() # Build constraints from that connection group
endpoint.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME list_constraints = build_constraints_from_connection_group(found_cg)
endpoint.device_id.device_uuid.uuid = sdp["node-id"]
endpoint.endpoint_uuid.uuid = attachment_circuits[0]["ac-tp-id"] # Add match-criterion to the relevant SDP
list_endpoints.append(endpoint) sdp_to_update = next((s for s in sdps if s["id"] == sdp_id), None)
break if not sdp_to_update:
else: raise Exception(f"SDP '{sdp_id}' not found")
raise Exception("Second SDP not found")
for sdp in sdps: sdp_to_update["service-match-criteria"]["match-criterion"].append(
if sdp["id"] == sdp_id: new_match_criterion
sdp["service-match-criteria"]["match-criterion"].append( )
new_match_criterion
) # Update constraints at the slice level as needed
attachment_circuits = sdp["attachment-circuits"]["attachment-circuit"]
if len(attachment_circuits) != 1:
raise Exception("All SDPs should have 1 attachment-circuit")
endpoint = EndPointId()
endpoint.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
endpoint.device_id.device_uuid.uuid = sdp["node-id"]
endpoint.endpoint_uuid.uuid = attachment_circuits[0]["ac-tp-id"]
list_endpoints.append(endpoint)
break
else:
raise Exception("SDP not found")
list_constraints = []
for cg in connection_groups:
if cg["id"] != target_connection_group_id:
continue
metric_bounds = cg["connectivity-construct"][0]["service-slo-sle-policy"][
"slo-policy"
]["metric-bound"]
for metric in metric_bounds:
if metric["metric-type"] == "ietf-nss:one-way-delay-maximum":
constraint = Constraint()
constraint.sla_latency.e2e_latency_ms = float(metric["bound"])
list_constraints.append(constraint)
elif metric["metric-type"] == "ietf-nss:one-way-bandwidth":
constraint = Constraint()
constraint.sla_capacity.capacity_gbps = (
float(metric["bound"]) / 1.0e3
)
list_constraints.append(constraint)
break
else:
raise Exception("connection group not found")
del slice_request.slice_constraints[:] del slice_request.slice_constraints[:]
slice_request.slice_constraints.extend(list_constraints) slice_request.slice_constraints.extend(list_constraints)
fields = {name: (value, RAISE_IF_DIFFERS) for name, value in ietf_data.items()} slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED
update_config_rule_custom(
slice_request.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data)
)
return slice_request return slice_request
@staticmethod @staticmethod
...@@ -507,60 +573,54 @@ class IETFSliceHandler: ...@@ -507,60 +573,54 @@ class IETFSliceHandler:
match_criterion_id: int, match_criterion_id: int,
context_client: ContextClient, context_client: ContextClient,
) -> Slice: ) -> Slice:
# slice_request = get_slice_by_uuid(context_client, slice_uuid) """
Delete the specified match-criterion from an SDP in the slice's candidate IETF data.
"""
slice_request = get_slice_by_defualt_name( slice_request = get_slice_by_defualt_name(
context_client, slice_uuid, rw_copy=False context_client, slice_uuid, rw_copy=False
) )
for cr in slice_request.slice_config.config_rules: ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY)
if cr.WhichOneof("config_rule") != "custom":
continue slice_service = ietf_data["network-slice-services"]["slice-service"][0]
if cr.custom.resource_key == CANDIDATE_RESOURCE_KEY:
ietf_data = json.loads(cr.custom.resource_value)
break
else:
raise Exception("ietf data not found")
slice_services = ietf_data["network-slice-services"]["slice-service"]
slice_service = slice_services[0]
sdps = slice_service["sdps"]["sdp"] sdps = slice_service["sdps"]["sdp"]
for sdp in sdps:
if sdp["id"] == sdp_id: # Find and modify the specified SDP
match_criteria = sdp["service-match-criteria"]["match-criterion"] sdp_to_update = next((s for s in sdps if s["id"] == sdp_id), None)
match_criterion_idx = [ if not sdp_to_update:
match_criterion["index"] == match_criterion_id raise Exception(f"SDP '{sdp_id}' not found in slice '{slice_uuid}'")
for match_criterion in match_criteria
].index(True) match_criteria = sdp_to_update["service-match-criteria"]["match-criterion"]
del match_criteria[match_criterion_idx] mc_index = next(
break (
else: i
raise Exception("Second SDP not found") for i, m in enumerate(match_criteria)
fields = {name: (value, RAISE_IF_DIFFERS) for name, value in ietf_data.items()} if m["index"] == match_criterion_id
update_config_rule_custom( ),
slice_request.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields None,
) )
if mc_index is None:
raise Exception(
f"No match-criterion with index '{match_criterion_id}' found in SDP '{sdp_id}'"
)
match_criteria.pop(mc_index)
update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data)
return slice_request return slice_request
@staticmethod @staticmethod
def copy_candidate_ietf_slice_data_to_running( def copy_candidate_ietf_slice_data_to_running(
slice_uuid: str, context_client: ContextClient slice_uuid: str, context_client: ContextClient
) -> Slice: ) -> Slice:
# slice_request = get_slice_by_uuid(context_client, slice_uuid) """
Copy candidate IETF slice data to the running IETF slice data for a given slice.
"""
slice_request = get_slice_by_defualt_name( slice_request = get_slice_by_defualt_name(
context_client, slice_uuid, rw_copy=False context_client, slice_uuid, rw_copy=False
) )
for cr in slice_request.slice_config.config_rules: candidate_ietf_data = get_ietf_data_from_config(
if ( slice_request, CANDIDATE_RESOURCE_KEY
cr.WhichOneof("config_rule") == "custom" )
and cr.custom.resource_key == CANDIDATE_RESOURCE_KEY update_ietf_data_in_config(
): slice_request, RUNNING_RESOURCE_KEY, candidate_ietf_data
candidate_resource_value_dict = json.loads(cr.custom.resource_value)
fields = {
name: (value, RAISE_IF_DIFFERS)
for name, value in candidate_resource_value_dict.items()
}
break
else:
raise Exception("candidate ietf slice data not found")
update_config_rule_custom(
slice_request.slice_config.config_rules, RUNNING_RESOURCE_KEY, fields
) )
return slice_request return slice_request
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment