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

Merge branch...

Merge branch 'feat/237-elaborate-ietf-slice-nbi-to-support-adding-deleting-new-sdp-connection-group-and-match' into camara-demo-integration
parents 8aeb35fa 2e612108
No related branches found
No related tags found
1 merge request!321Resolve: "(CTTC) CAMARA Demo Integration tests"
This commit is part of merge request !321. Comments created here will be created in the context of that merge request.
......@@ -32,90 +32,177 @@ ADDRESS_PREFIX = 24
RAISE_IF_DIFFERS = False
def get_endpoint_controller_type(
endpoint: EndPointId, context_client: ContextClient
) -> str:
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("Device({:s}) not found".format(str(controller_uuid)))
return controller.device_type
def validate_ietf_slice_data(request_data: Dict) -> None:
"""
Validate the provided IETF slice data against the YANG model.
"""
yang_validator = YangValidator("ietf-network-slice-service")
_ = yang_validator.parse_to_dict(request_data)
yang_validator.destroy()
def get_custom_config_rule(
service_config: ServiceConfig, resource_key: str
) -> Optional[ConfigRule]:
"""
Retrieve the custom config rule with the given resource_key from a ServiceConfig.
"""
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_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(
endpoinst_list: List[EndPointId],
endpoints_list: List[EndPointId],
sdps: List,
connection_group: Dict,
context_client: ContextClient,
) -> 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)
last_ep = endpoinst_list[-1]
last_controller_type = get_endpoint_controller_type(last_ep, context_client)
if first_controller_type == DeviceTypeEnum.NCE.value:
return endpoinst_list
return endpoints_list
elif last_controller_type == DeviceTypeEnum.NCE.value:
return endpoinst_list[::-1]
else:
src_sdp_id = connection_group["connectivity-construct"][0]["p2p-sender-sdp"]
sdp_id_name_mapping = {sdp["id"]: sdp["node-id"] for sdp in sdps}
if (
endpoinst_list[0].device_id.device_uuid.uuid
== sdp_id_name_mapping[src_sdp_id]
):
return endpoinst_list
return endpoinst_list[::-1]
return endpoints_list[::-1]
src_sdp_id = connection_group["connectivity-construct"][0]["p2p-sender-sdp"]
sdp_id_name_mapping = {sdp["id"]: sdp["node-id"] for sdp in sdps}
if endpoints_list[0].device_id.device_uuid.uuid == sdp_id_name_mapping[src_sdp_id]:
return endpoints_list
return endpoints_list[::-1]
def replace_ont_endpoint_with_emu_dc(
endpoint_list: List, context_client: ContextClient
) -> List:
endpoint_list: List[EndPointId], context_client: ContextClient
) -> 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())
links = list(link_list.links)
devices_list = context_client.ListDevices(Empty())
devices = devices_list.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}
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_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_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():
for link in links:
link_endpoints = list(link.link_endpoint_ids)
link_ep_1 = link_endpoints[0]
link_ep_2 = link_endpoints[1]
link_ep_1, link_ep_2 = link_endpoints
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
== "emu-datacenter"
):
endpoint_list[0] = link_ep_2
break
# Otherwise, check if the second endpoint is managed
elif device_2.controller_id != DeviceId():
for link in links:
link_endpoints = list(link.link_endpoint_ids)
link_ep_1 = link_endpoints[0]
link_ep_2 = link_endpoints[1]
link_ep_1, link_ep_2 = link_endpoints
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
== "emu-datacenter"
):
......@@ -123,31 +210,34 @@ def replace_ont_endpoint_with_emu_dc(
break
else:
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:
yang_validator = YangValidator("ietf-network-slice-service")
_ = yang_validator.parse_to_dict(request_data)
yang_validator.destroy()
return endpoint_list
class IETFSliceHandler:
@staticmethod
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())
context_ids = list(existing_context_ids.context_ids)
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 = slices_list.slices
ietf_slices = {"network-slice-services": {"slice-service": []}}
for slice in slices:
for slc in slices:
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)
ietf_slices["network-slice-services"]["slice-service"].append(
candidate_ietf_data["network-slice-services"]["slice-service"][0]
......@@ -158,27 +248,37 @@ class IETFSliceHandler:
def create_slice_service(
request_data: dict, context_client: ContextClient
) -> 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:
request_data = {"network-slice-services": request_data}
validate_ietf_slice_data(request_data)
slice_services = request_data["network-slice-services"]["slice-service"]
slice_service = slice_services[0]
slice_service = request_data["network-slice-services"]["slice-service"][0]
slice_id = slice_service["id"]
sdps = slice_service["sdps"]["sdp"]
connection_groups = slice_service["connection-groups"]["connection-group"]
if len(sdps) != 2:
raise Exception("Number of SDPs should be 2")
slice_request: Slice = Slice()
raise Exception("Number of SDPs should be exactly 2")
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.slice_uuid.uuid = slice_id
slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED
list_endpoints = []
endpoint_config_rules = []
connection_group_ids = set()
# Build endpoints from SDPs
for sdp in sdps:
attachment_circuits = sdp["attachment-circuits"]["attachment-circuit"]
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.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
device_uuid = sdp["node-id"]
......@@ -186,11 +286,15 @@ class IETFSliceHandler:
endpoint_uuid = attachment_circuits[0]["ac-tp-id"]
endpoint.endpoint_uuid.uuid = endpoint_uuid
list_endpoints.append(endpoint)
# Keep track of connection-group-id from each SDP
connection_group_ids.add(
sdp["service-match-criteria"]["match-criterion"][0][
"target-connection-group-id"
]
)
# Endpoint-specific config rule fields
endpoint_config_rule_fields = {
"address_ip": (endpoint_uuid, RAISE_IF_DIFFERS),
"address_prefix": (ADDRESS_PREFIX, RAISE_IF_DIFFERS),
......@@ -201,41 +305,36 @@ class IETFSliceHandler:
endpoint_config_rule_fields,
)
)
if len(connection_group_ids) != 1:
raise Exception("SDPs target-connection-group-id do not match")
list_constraints = []
for cg in connection_groups:
if cg["id"] != list(connection_group_ids)[0]:
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")
list_endpoints = sort_endpoints(list_endpoints, sdps, cg, context_client)
raise Exception("SDPs do not share a common connection-group-id")
# Build constraints from the matching connection group
unique_cg_id = connection_group_ids.pop()
found_cg = next(
(cg for cg in connection_groups if cg["id"] == unique_cg_id), None
)
if not found_cg:
raise Exception("The connection group referenced by the SDPs was not found")
list_constraints = build_constraints_from_connection_group(found_cg)
# Sort endpoints and optionally replace the ONT endpoint
list_endpoints = sort_endpoints(list_endpoints, sdps, found_cg, context_client)
list_endpoints = replace_ont_endpoint_with_emu_dc(
list_endpoints, context_client
)
slice_request.slice_endpoint_ids.extend(list_endpoints)
slice_request.slice_constraints.extend(list_constraints)
# TODO adding owner, needs to be recoded after updating the bindings
owner = slice_id
slice_request.slice_owner.owner_string = owner
# Set slice owner
slice_request.slice_owner.owner_string = slice_id
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 = {
name: (value, RAISE_IF_DIFFERS) for name, value in request_data.items()
}
......@@ -250,6 +349,7 @@ class IETFSliceHandler:
ietf_slice_fields,
)
# Update endpoint config rules
for ep_cr_key, ep_cr_fields in endpoint_config_rules:
update_config_rule_custom(
slice_request.slice_config.config_rules, ep_cr_key, ep_cr_fields
......@@ -261,120 +361,113 @@ class IETFSliceHandler:
def create_sdp(
request_data: dict, slice_uuid: str, context_client: ContextClient
) -> Slice:
"""
Add a new SDP to an existing slice, updating the candidate IETF data.
"""
sdps = request_data["sdp"]
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]
# slice_request = get_slice_by_uuid(context_client, slice_uuid)
slice_request = get_slice_by_defualt_name(
context_client, slice_uuid, rw_copy=False
)
for cr in slice_request.slice_config.config_rules:
if cr.WhichOneof("config_rule") != "custom":
continue
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]
ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY)
slice_service = ietf_data["network-slice-services"]["slice-service"][0]
slice_sdps = slice_service["sdps"]["sdp"]
slice_sdps.append(new_sdp)
fields = {name: (value, RAISE_IF_DIFFERS) for name, value in ietf_data.items()}
update_config_rule_custom(
slice_request.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields
)
# Save updated IETF data
update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data)
return slice_request
@staticmethod
def delete_sdp(
slice_uuid: str, sdp_id: str, context_client: ContextClient
) -> 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(
context_client, slice_uuid, rw_copy=False
)
for cr in slice_request.slice_config.config_rules:
if cr.WhichOneof("config_rule") != "custom":
continue
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]
ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY)
slice_service = ietf_data["network-slice-services"]["slice-service"][0]
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)
fields = {name: (value, RAISE_IF_DIFFERS) for name, value in ietf_data.items()}
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
@staticmethod
def create_connection_group(
request_data: dict, slice_id: str, context_client: ContextClient
) -> Slice:
"""
Add a new connection group to an existing slice's candidate IETF data.
"""
connection_groups = request_data["connection-group"]
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]
# slice = get_slice_by_uuid(context_client, slice_id)
slice = get_slice_by_defualt_name(context_client, slice_id, rw_copy=False)
for cr in slice.slice_config.config_rules:
if cr.WhichOneof("config_rule") != "custom":
continue
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_request = get_slice_by_defualt_name(
context_client, slice_id, rw_copy=False
)
ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY)
slice_service = ietf_data["network-slice-services"]["slice-service"][0]
slice_connection_groups = slice_service["connection-groups"]["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(
slice.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields
)
# Validate the updated data, then save
validate_ietf_slice_data(ietf_data)
return slice
update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data)
return slice_request
@staticmethod
def update_connection_group(
slice_name: str,
updated_connection_group: dict,
context_client: ContextClient,
):
) -> Slice:
"""
Update an existing connection group in the candidate IETF data.
"""
slice_request = get_slice_by_defualt_name(
context_client, slice_name, rw_copy=False
)
slice_config = slice_request.slice_config
cr = get_custom_config_rule(slice_config, 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]
candidate_ietf_data = get_ietf_data_from_config(
slice_request, CANDIDATE_RESOURCE_KEY
)
slice_service = candidate_ietf_data["network-slice-services"]["slice-service"][
0
]
slice_connection_groups = slice_service["connection-groups"]["connection-group"]
connection_group_id = updated_connection_group["id"]
cg_idx = list(
(
slice_cg["id"] == connection_group_id
for slice_cg in slice_connection_groups
)
).index(True)
cg_id = updated_connection_group["id"]
cg_idx = next(
(i for i, cg in enumerate(slice_connection_groups) if cg["id"] == cg_id),
None,
)
if cg_idx is None:
raise Exception(f"Connection group with id '{cg_id}' not found")
slice_connection_groups[cg_idx] = updated_connection_group
fields = {
name: (value, RAISE_IF_DIFFERS)
for name, value in candidate_ietf_data.items()
}
update_config_rule_custom(
slice_request.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields
update_ietf_data_in_config(
slice_request, CANDIDATE_RESOURCE_KEY, candidate_ietf_data
)
slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED
return slice_request
......@@ -382,30 +475,39 @@ class IETFSliceHandler:
def delete_connection_group(
slice_uuid: str, connection_group_id: str, context_client: ContextClient
) -> 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(
context_client, slice_uuid, rw_copy=False
)
slice_config = slice_request.slice_config
cr = get_custom_config_rule(slice_config, 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]
candidate_ietf_data = get_ietf_data_from_config(
slice_request, CANDIDATE_RESOURCE_KEY
)
slice_service = candidate_ietf_data["network-slice-services"]["slice-service"][
0
]
slice_connection_groups = slice_service["connection-groups"]["connection-group"]
sdp_idx = list(
cg_idx = next(
(
slice_cr["id"] == connection_group_id
for slice_cr in slice_connection_groups
i
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)
fields = {
name: (value, RAISE_IF_DIFFERS)
for name, value in candidate_ietf_data.items()
}
update_config_rule_custom(
slice_request.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields
slice_connection_groups.pop(cg_idx)
update_ietf_data_in_config(
slice_request, CANDIDATE_RESOURCE_KEY, candidate_ietf_data
)
slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED
return slice_request
......@@ -413,91 +515,55 @@ class IETFSliceHandler:
def create_match_criteria(
request_data: dict, slice_name: str, sdp_id: str, context_client: ContextClient
) -> Slice:
"""
Create a new match-criterion for the specified SDP in a slice's candidate IETF data.
"""
match_criteria = request_data["match-criterion"]
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]
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(
context_client, slice_name, rw_copy=False
)
for cr in slice_request.slice_config.config_rules:
if cr.WhichOneof("config_rule") != "custom":
continue
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"]
ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY)
slice_service = ietf_data["network-slice-services"]["slice-service"][0]
connection_groups = slice_service["connection-groups"]["connection-group"]
slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED
list_endpoints = []
for sdp in sdps:
if (
sdp["service-match-criteria"]["match-criterion"][0][
"target-connection-group-id"
]
== target_connection_group_id
):
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("Second SDP not found")
for sdp in sdps:
if sdp["id"] == sdp_id:
sdp["service-match-criteria"]["match-criterion"].append(
new_match_criterion
)
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")
sdps = slice_service["sdps"]["sdp"]
# Find the referenced connection group
found_cg = next(
(cg for cg in connection_groups if cg["id"] == target_connection_group_id),
None,
)
if not found_cg:
raise Exception(
f"Connection group '{target_connection_group_id}' not found"
)
# Build constraints from that connection group
list_constraints = build_constraints_from_connection_group(found_cg)
# Add match-criterion to the relevant SDP
sdp_to_update = next((s for s in sdps if s["id"] == sdp_id), None)
if not sdp_to_update:
raise Exception(f"SDP '{sdp_id}' not found")
sdp_to_update["service-match-criteria"]["match-criterion"].append(
new_match_criterion
)
# Update constraints at the slice level as needed
del slice_request.slice_constraints[:]
slice_request.slice_constraints.extend(list_constraints)
fields = {name: (value, RAISE_IF_DIFFERS) for name, value in 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
update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data)
return slice_request
@staticmethod
......@@ -507,60 +573,54 @@ class IETFSliceHandler:
match_criterion_id: int,
context_client: ContextClient,
) -> 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(
context_client, slice_uuid, rw_copy=False
)
for cr in slice_request.slice_config.config_rules:
if cr.WhichOneof("config_rule") != "custom":
continue
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]
ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY)
slice_service = ietf_data["network-slice-services"]["slice-service"][0]
sdps = slice_service["sdps"]["sdp"]
for sdp in sdps:
if sdp["id"] == sdp_id:
match_criteria = sdp["service-match-criteria"]["match-criterion"]
match_criterion_idx = [
match_criterion["index"] == match_criterion_id
for match_criterion in match_criteria
].index(True)
del match_criteria[match_criterion_idx]
break
else:
raise Exception("Second SDP not found")
fields = {name: (value, RAISE_IF_DIFFERS) for name, value in ietf_data.items()}
update_config_rule_custom(
slice_request.slice_config.config_rules, CANDIDATE_RESOURCE_KEY, fields
# Find and modify the specified SDP
sdp_to_update = next((s for s in sdps if s["id"] == sdp_id), None)
if not sdp_to_update:
raise Exception(f"SDP '{sdp_id}' not found in slice '{slice_uuid}'")
match_criteria = sdp_to_update["service-match-criteria"]["match-criterion"]
mc_index = next(
(
i
for i, m in enumerate(match_criteria)
if m["index"] == match_criterion_id
),
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
@staticmethod
def copy_candidate_ietf_slice_data_to_running(
slice_uuid: str, context_client: ContextClient
) -> 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(
context_client, slice_uuid, rw_copy=False
)
for cr in slice_request.slice_config.config_rules:
if (
cr.WhichOneof("config_rule") == "custom"
and cr.custom.resource_key == CANDIDATE_RESOURCE_KEY
):
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
candidate_ietf_data = get_ietf_data_from_config(
slice_request, CANDIDATE_RESOURCE_KEY
)
update_ietf_data_in_config(
slice_request, RUNNING_RESOURCE_KEY, candidate_ietf_data
)
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