From: Martin Skorupski Date: Fri, 7 Mar 2025 15:30:51 +0000 (+0100) Subject: Make topo-generation more flexible X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=9e0f72365fdf11abb513c777a84bad7df3a4c5c9;p=oam.git Make topo-generation more flexible - add operational-state parameters to network generation IssueID: OAM-440 Change-Id: Ic604d1663bc273845fdf38949335565d9dd9d592 Signed-off-by: Martin Skorupski --- diff --git a/code/network-generator/network_generation/model/python/o_ran_cloud_du.py b/code/network-generator/network_generation/model/python/o_ran_cloud_du.py index 23edb48..7b22264 100644 --- a/code/network-generator/network_generation/model/python/o_ran_cloud_du.py +++ b/code/network-generator/network_generation/model/python/o_ran_cloud_du.py @@ -93,6 +93,7 @@ class ORanCloudDu(ORanNode): "layout": self.layout, "parent": self, "network": self.network, + "operationalState": self.operationalState, } ) ) diff --git a/code/network-generator/network_generation/model/python/o_ran_cu.py b/code/network-generator/network_generation/model/python/o_ran_cu.py index d214e0c..4823b45 100644 --- a/code/network-generator/network_generation/model/python/o_ran_cu.py +++ b/code/network-generator/network_generation/model/python/o_ran_cu.py @@ -88,6 +88,7 @@ class ORanCu(ORanNode): "layout": self.layout, "parent": self, "network": self.network, + "operationalState": self.operationalState, } ) ) diff --git a/code/network-generator/network_generation/model/python/o_ran_near_rt_ric.py b/code/network-generator/network_generation/model/python/o_ran_near_rt_ric.py index e2f2981..b7dc9ce 100644 --- a/code/network-generator/network_generation/model/python/o_ran_near_rt_ric.py +++ b/code/network-generator/network_generation/model/python/o_ran_near_rt_ric.py @@ -94,6 +94,7 @@ class ORanNearRtRic(ORanNode): "layout": self.layout, "parent": self, "network": self.network, + "operationalState": self.operationalState, } ) ) diff --git a/code/network-generator/network_generation/model/python/o_ran_network.py b/code/network-generator/network_generation/model/python/o_ran_network.py index 5011d1c..fc7fc4a 100644 --- a/code/network-generator/network_generation/model/python/o_ran_network.py +++ b/code/network-generator/network_generation/model/python/o_ran_network.py @@ -1,4 +1,4 @@ -# Copyright 2024 highstreet technologies GmbH +# Copyright 2024 highstreet technologies USA Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,26 +16,19 @@ """ Module for a class representing a O-RAN Network """ + import os import uuid import xml.etree.ElementTree as ET from datetime import datetime, timezone -from typing import Any, cast +from typing import Any, cast, Dict, List, Union import network_generation.model.python.hexagon as Hexagon -from network_generation.model.python.geo_location import ( - GeoLocation, - IGeoLocation, -) +from network_generation.model.python.geo_location import GeoLocation, IGeoLocation from network_generation.model.python.hexagon import Layout -from network_generation.model.python.o_ran_object import ( - IORanObject, - ORanObject, -) +from network_generation.model.python.o_ran_object import IORanObject, ORanObject from network_generation.model.python.o_ran_smo import ORanSmo -from network_generation.model.python.o_ran_spiral_radius_profile import ( - SpiralRadiusProfile, -) +from network_generation.model.python.o_ran_spiral_radius_profile import SpiralRadiusProfile from network_generation.model.python.point import Point @@ -50,9 +43,7 @@ class ORanNetwork(ORanObject): """ __my_default_value: IORanNetwork = cast(IORanNetwork, ORanObject.default()) - __my_network_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, "Operator A")) - # Get the current date and time in UTC __current_time = datetime.now(timezone.utc) # Format the time string as required @@ -73,51 +64,39 @@ class ORanNetwork(ORanObject): super().__init__(cast(dict[str, Any], o_ran_network_data), **kwargs) self.__configuration = configuration - self.name = str(configuration["name"]) + self.name = str(configuration.get("name", "WhiteNetwork")) + self.operationalState = str(configuration.get("operationalState", "disabled")) self._center: IGeoLocation = cast( IGeoLocation, configuration["center"] ) - size: int = int( - int(configuration["pattern"]["nrCellDu"]["maxReach"]) - / ( - 1 - + int( - configuration["pattern"]["nrCellDu"][ - "cellScaleFactorForHandoverArea" - ] - ) - / 100 - ) + # Calculate layout size using the configuration values. + nr_cell_du = configuration["pattern"]["nrCellDu"] + size = int( + int(nr_cell_du["maxReach"]) + / (1 + int(nr_cell_du["cellScaleFactorForHandoverArea"]) / 100) ) layout = Layout( Hexagon.layout_flat, Point(size, size), Point(0, 0) ) # 1 pixel = 1 meter - self._spiral_radius_profile: SpiralRadiusProfile = SpiralRadiusProfile( - { - "oRanSmoSpiralRadiusOfNearRtRics": configuration["pattern"][ - "smo" - ]["nearRtRicSpiralRadius"], - "oRanNearRtRicSpiralRadiusOfOCus": configuration["pattern"][ - "nearRtRic" - ]["oRanCuSpiralRadius"], - "oRanCuSpiralRadiusOfODus": configuration["pattern"]["oRanCu"][ - "oRanDuSpiralRadius" - ], - "oRanDuSpiralRadiusOfTowers": configuration["pattern"][ - "oRanDu" - ]["towerSpiralRadius"], - } - ) - self._o_ran_smo = ORanSmo( - { - "name": "O-RAN-SMO", - "geoLocation": self.center, - "layout": layout, - "parent": self, - "network": self, - } - ) + self._spiral_radius_profile: SpiralRadiusProfile = SpiralRadiusProfile({ + "oRanSmoSpiralRadiusOfNearRtRics": configuration["pattern"][ + "smo"]["nearRtRicSpiralRadius"], + "oRanNearRtRicSpiralRadiusOfOCus": configuration["pattern"][ + "nearRtRic"]["oRanCuSpiralRadius"], + "oRanCuSpiralRadiusOfODus": configuration["pattern"]["oRanCu"][ + "oRanDuSpiralRadius"], + "oRanDuSpiralRadiusOfTowers": configuration["pattern"][ + "oRanDu"]["towerSpiralRadius"], + }) + self._o_ran_smo = ORanSmo({ + "name": "O-RAN-SMO", + "geoLocation": self.center, + "layout": layout, + "parent": self, + "network": self, + "operationalState": self.operationalState + }) def _to_o_ran_network_data(self, data: dict[str, Any]) -> IORanNetwork: result: IORanNetwork = self.__my_default_value @@ -150,15 +129,87 @@ class ORanNetwork(ORanObject): """ return self.__configuration - def to_topology(self) -> dict[str, Any]: - nodes: list[dict[str, Any]] = self._o_ran_smo.to_topology_nodes() - links: list[dict[str, Any]] = self._o_ran_smo.to_topology_links() + def __update_value_by_uuid(self, data: dict[str, Any], target_uuid: str, param_name: str, new_value: str) -> bool: + """ + Recursively searches for an object with the target UUID and updates its parameter value. + + :param data: The JSON dictionary (can be a list or dict). + :param target_uuid: The UUID to find. + :param param_name: The parameter key to modify. + :param new_value: The new value to set. + :return: True if updated, False otherwise. + """ + if isinstance(data, dict): + # If 'uuid' matches, update the parameter + if data.get("o-ran-sc-network:uuid") == target_uuid: + if param_name in data: + data[param_name] = new_value + return True # Stop recursion as we found and updated it + + # Recursively search in nested dictionaries or lists + for _, value in data.items(): + if isinstance(value, (dict, list)) and self.__update_value_by_uuid(value, target_uuid, param_name, new_value): + return True # Stop searching after finding the target + + elif isinstance(data, list): + # Iterate over list items + for item in data: + if self.__update_value_by_uuid(item, target_uuid, param_name, new_value): + return True # Stop searching after finding the target + + return False # UUID not found + + def to_topology(self) -> Dict[str, Any]: + """ + Generate and return the network topology as a dictionary. + """ + profile = self.configuration.get("disabledResourcesProfile", {}) + resource_types = profile.keys() + nodes: List[Dict[str, Any]] = self._o_ran_smo.to_topology_nodes() + + # Initialize identifier lists for each resource type. + identifier_lists: Dict[str, List[str]] = {identifier: [] for identifier in resource_types} + + for node in nodes: + node_type = node.get("o-ran-sc-network:type") + node_uuid = node.get("o-ran-sc-network:uuid") + if node_type in resource_types and node_uuid: + identifier_lists[node_type].append(node_uuid) + + termination_points = node.get("ietf-network-topology:termination-point", []) + for tp in termination_points: + if tp.get("o-ran-sc-network:type") == "o-ran-sc-network:cell": + # Create key for cells if not already present. + if "o-ran-sc-network:cell" not in identifier_lists: + identifier_lists["o-ran-sc-network:cell"] = [] + cell_uuid = tp.get("o-ran-sc-network:uuid") + if cell_uuid: + identifier_lists["o-ran-sc-network:cell"].append(cell_uuid) + + disabled_resources: List[int] = [] + for resource_type in resource_types: + profile_percentage = profile[resource_type] + total_nodes = len(identifier_lists[resource_type]) + max_disabled = round(profile_percentage * total_nodes / 100) + disabled_resources.append(max_disabled) + identifier_lists[resource_type].sort() + identifier_lists[resource_type] = identifier_lists[resource_type][:max_disabled] + for identifier in identifier_lists[resource_type]: + self.__update_value_by_uuid( + nodes, + identifier, + "o-ran-sc-network:operational-state", + "disabled" + ) + + links: List[Dict[str, Any]] = self._o_ran_smo.to_topology_links() return { "ietf-network:networks": { "network": [ { "network-id": self.id, "o-ran-sc-network:name": self.name, + "o-ran-sc-network:operational-state": self.operationalState, "node": nodes, "ietf-network-topology:link": links, } @@ -167,20 +218,26 @@ class ORanNetwork(ORanObject): } def to_directory(self, parent_dir: str) -> None: + """ + Export the network topology to a specified directory. + """ self._o_ran_smo.to_directory(os.path.join(parent_dir, self.id)) def toKml(self) -> ET.Element: - root: ET.Element = ET.Element( - "kml", xmlns="http://www.opengis.net/kml/2.2" - ) + """ + Generate a KML representation of the network. + + Returns: + An xml.etree.ElementTree.Element representing the KML. + """ + root = ET.Element("kml", xmlns="http://www.opengis.net/kml/2.2") document = ET.SubElement(root, "Document") - open: ET.Element = ET.SubElement(document, "open") - open.text = "1" - name: ET.Element = ET.SubElement(document, "name") - name.text = self.name + open_elem = ET.SubElement(document, "open") + open_elem.text = "1" + name_elem = ET.SubElement(document, "name") + name_elem.text = self.name document.append(self._o_ran_smo.toKml()) - return root def toSvg(self) -> ET.Element: @@ -211,14 +268,19 @@ class ORanNetwork(ORanObject): # root.append(self.__context.svg(x, y)) return root - def json(self) -> dict[str, Any]: + def json(self) -> Dict[str, Any]: + """ + Return a JSON representation of the network. + """ return super().json() - def to_teiv_data(self) -> dict[str, Any]: - entities: dict[str, list[dict[str, Any]]] = ( - self._o_ran_smo.add_teiv_data_entities() - ) - relationships: dict[str, list[dict[str, Any]]] = ( - self._o_ran_smo.add_teiv_data_relationships() - ) - return {"entities": [entities], "relationships": [relationships]} + def to_teiv_data(self) -> Dict[str, Any]: + """ + Return the network data formatted for TEIV. + + Returns: + A dictionary containing TEIV entities and relationships. + """ + entities = self._o_ran_smo.add_teiv_data_entities() + relationships = self._o_ran_smo.add_teiv_data_relationships() + return {"entities": [entities], "relationships": [relationships]} \ No newline at end of file diff --git a/code/network-generator/network_generation/model/python/o_ran_node.py b/code/network-generator/network_generation/model/python/o_ran_node.py index f7d83f0..fa9b305 100644 --- a/code/network-generator/network_generation/model/python/o_ran_node.py +++ b/code/network-generator/network_generation/model/python/o_ran_node.py @@ -207,13 +207,15 @@ class ORanNode(ORanObject): result.append(ORanTerminationPoint({ "name": phy_tp, "type": "o-ran-sc-network:phy", - "network": self.network + "operationalState": self.operationalState, + "network": self.network, })) for interface in logical_interfaces: id: str = "-".join([self.name, interface.upper()]) result.append(ORanTerminationPoint({ "name": id, "type": ":".join(["o-ran-sc-network", interface]), + "operationalState": self.operationalState, "supporter": { "network-ref": self.network.id, "node-ref": self.name, @@ -246,6 +248,7 @@ class ORanNode(ORanObject): ) ), "o-ran-sc-network:type": self.type, + "o-ran-sc-network:operational-state": self.operationalState, "ietf-network-topology:termination-point": tps, } ) diff --git a/code/network-generator/network_generation/model/python/o_ran_ru.py b/code/network-generator/network_generation/model/python/o_ran_ru.py index f85059d..fb45921 100644 --- a/code/network-generator/network_generation/model/python/o_ran_ru.py +++ b/code/network-generator/network_generation/model/python/o_ran_ru.py @@ -81,6 +81,7 @@ class ORanRu(ORanNode): "layout": self.layout, "parent": self.parent.parent.parent, "network": self.network, + "operationalState": self.operationalState, } self._oRanDu: ORanDu = ORanDu(o_ran_du_data) @@ -115,6 +116,7 @@ class ORanRu(ORanNode): "layout": self.layout, "parent": self, "network": self.network, + "operationalState": self.operationalState, "cellAngle": cell_angle, "cellScaleFactorForHandoverArea": cell_scale_factor, "maxReach": maxReach, diff --git a/code/network-generator/network_generation/model/python/o_ran_smo.py b/code/network-generator/network_generation/model/python/o_ran_smo.py index 02dc27f..b7946cf 100644 --- a/code/network-generator/network_generation/model/python/o_ran_smo.py +++ b/code/network-generator/network_generation/model/python/o_ran_smo.py @@ -87,6 +87,7 @@ class ORanSmo(ORanNode): "layout": self.layout, "parent": self, "network": self.network, + "operationalState": self.operationalState }, ) ) diff --git a/code/network-generator/network_generation/model/python/o_ran_termination_point.py b/code/network-generator/network_generation/model/python/o_ran_termination_point.py index ae0265f..a036fa6 100644 --- a/code/network-generator/network_generation/model/python/o_ran_termination_point.py +++ b/code/network-generator/network_generation/model/python/o_ran_termination_point.py @@ -57,6 +57,7 @@ class ORanTerminationPoint(ORanObject): super().__init__(cast(dict[str, Any], itp), **kwargs) self._supporter = cast(dict[str, str], itp["supporter"]) self._network: Any = itp["network"] + self._operationalState = cast(dict[str, str], itp["operationalState"]) def _to_itp_data(self, data: dict[str, Any]) -> IORanTerminationPoint: result: IORanTerminationPoint = default_value @@ -88,6 +89,7 @@ class ORanTerminationPoint(ORanObject): "tp-id": self.name, "o-ran-sc-network:uuid": self.id, "o-ran-sc-network:type": self.type, + "o-ran-sc-network:operational-state": self.operationalState } if not (self.type == "o-ran-sc-network:phy"): diff --git a/code/network-generator/network_generation/model/python/tower.py b/code/network-generator/network_generation/model/python/tower.py index 498b75b..0631744 100644 --- a/code/network-generator/network_generation/model/python/tower.py +++ b/code/network-generator/network_generation/model/python/tower.py @@ -97,6 +97,7 @@ class Tower(ORanNode): "layout": self.layout, "parent": self, "network": self.network, + "operationalState": self.operationalState, "cellCount": cell_count, "ruAngle": ru_angle, "ruAzimuth": ru_azimuth,