From de0b1d4de670ac1c50f6591cc54e1e3077b5cc9e Mon Sep 17 00:00:00 2001 From: Martin Skorupski Date: Sun, 9 Mar 2025 14:19:26 +0100 Subject: [PATCH] Generate GeoJSON for topology - preparation of generation chain IssueID: OAM-444 Change-Id: Ic5db1410f65b048d77580f333fe21a5b89c64c7d Signed-off-by: Martin Skorupski --- code/network-generator/network_generation/cli.py | 10 +- .../network_generation/model/python/nr_cell_du.py | 81 ++- .../model/python/o_ran_cloud_du.py | 62 +- .../network_generation/model/python/o_ran_cu.py | 63 +- .../network_generation/model/python/o_ran_du.py | 136 ++++- .../model/python/o_ran_near_rt_ric.py | 60 +- .../model/python/o_ran_network.py | 152 ++++- .../network_generation/model/python/o_ran_node.py | 678 ++++++++++++++++++++- .../network_generation/model/python/o_ran_ru.py | 153 ++++- .../network_generation/model/python/o_ran_smo.py | 59 +- .../model/python/o_ran_termination_point.py | 90 ++- .../network_generation/model/python/tower.py | 59 ++ .../network_generation/view/network_viewer.py | 111 +++- 13 files changed, 1666 insertions(+), 48 deletions(-) diff --git a/code/network-generator/network_generation/cli.py b/code/network-generator/network_generation/cli.py index 03baba8..a96487a 100644 --- a/code/network-generator/network_generation/cli.py +++ b/code/network-generator/network_generation/cli.py @@ -16,7 +16,6 @@ import os import sys - from network_generation.base import NetworkGenerator from network_generation.parameter_validator import ParameterValidator from network_generation.view.network_viewer import NetworkViewer @@ -28,11 +27,9 @@ Module as entry point to generate an ietf topology json def save_viewer_output( - viewer: NetworkViewer, - filename: str, - task: dict[str, str] | dict[str, int], - method_name: str, -) -> None: + viewer: NetworkViewer, filename: str, + task: dict[str, str] | dict[str, int], + method_name: str) -> None: """ Save the output using the specified method of NetworkViewer. """ @@ -48,6 +45,7 @@ def main() -> None: # pragma: no cover `python -m network_generation`. """ validator = ParameterValidator(sys.argv) + if not validator.is_valid(): print(validator.error_message()) return diff --git a/code/network-generator/network_generation/model/python/nr_cell_du.py b/code/network-generator/network_generation/model/python/nr_cell_du.py index b6bc262..fc7900e 100644 --- a/code/network-generator/network_generation/model/python/nr_cell_du.py +++ b/code/network-generator/network_generation/model/python/nr_cell_du.py @@ -1,4 +1,4 @@ -# Copyright 2023 highstreet technologies USA CORP. +# Copyright 2025 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. @@ -122,12 +122,9 @@ class NrCellDu(ORanNode): # returns a list of geo-locations as cell polygon def get_cell_polygons(self) -> list[GeoLocation]: - points: list[Point] = Hexagon.polygon_corners( - self.layout, self.position - ) + points: list[Point] = Hexagon.polygon_corners(self.layout, self.position) method = ( - self.parent.parent.parent.parent.parent.parent - .geo_location.point_to_geo_location + self.parent.parent.parent.parent.parent.parent.geo_location.point_to_geo_location ) geo_locations: list[GeoLocation] = list(map(method, points)) index: int = 1 + int(self._azimuth / self._cell_angle) @@ -141,9 +138,7 @@ class NrCellDu(ORanNode): (points[p1].x + points[p2].x) / 2, (points[p1].y + points[p2].y) / 2, ) - intersect_gl1: GeoLocation = network_center.point_to_geo_location( - intersect1 - ) + intersect_gl1: GeoLocation = network_center.point_to_geo_location(intersect1) p3: int = (2 * index + 3) % 6 p4: int = (2 * index + 4) % 6 @@ -151,9 +146,7 @@ class NrCellDu(ORanNode): (points[p3].x + points[p4].x) / 2, (points[p3].y + points[p4].y) / 2, ) - intersect_gl2: GeoLocation = network_center.point_to_geo_location( - intersect2 - ) + intersect_gl2: GeoLocation = network_center.point_to_geo_location(intersect2) tower = self.geo_location @@ -170,9 +163,7 @@ class NrCellDu(ORanNode): scaled_cell_polygon: list[GeoLocation] = [] arc: float = self.azimuth * math.pi / 180 - meterToDegree: float = ( - 2 * math.pi * GeoLocation().equatorialRadius / 360 - ) + meterToDegree: float = 2 * math.pi * GeoLocation().equatorialRadius / 360 centerX: float = self.layout.size.x * 0.5 * math.sin(arc) centerY: float = self.layout.size.y * 0.5 * math.cos(arc) cell_center: GeoLocation = GeoLocation( @@ -230,6 +221,66 @@ class NrCellDu(ORanNode): def to_directory(self, parent_dir: str) -> None: pass + def get_geojson_href(self) -> str: + return f"https://{self.network.host}/area/o-ran-network.geo.json/features?properties.name={self.name}" + + def to_geojson_feature(self) -> list[dict[str, Any]]: + cell_polygon: list[GeoLocation] = self.get_cell_polygons() + coordinates: list = [] + for gl in cell_polygon: + cord: list[float] = [gl.longitude, gl.latitude] + coordinates.append(cord) + return [ + { + "type": "Feature", + "properties": { + "type": "PropertiesOdu", + "name": self.name, + "uuid": self.id, + "function": "o-ran-sc-network:cell", + "newRadioCellGlobalIdentity": { + "publicLandMobileNetworkIdentifier": "123-45", + "newRadioCellIdentity": "0x0FFFFFFFFF", + } + }, + "geometry": { + "type": "Polygon", + "coordinates": [coordinates], + }, + } + ] + + def to_tmf686_vertex(self) -> list[dict[str, Any]]: + # a cell is not a node it is a Termination Point + result: list[dict[str, Any]] = [] # super().to_topology_nodes() + return result + + def to_tmf686_edge(self) -> list[dict[str, Any]]: + # as a cell is not a node, it does not have links + result: list[dict[str, Any]] = [] # super().to_topology_links() + return result + + def to_tmf633_service_candidate_references(self) -> list[dict[str, Any]]: + return super().to_tmf633_service_candidate_references() + + def to_tmf633_service_candidates(self) -> list[dict[str, Any]]: + return super().to_tmf633_service_candidates() + + def to_tmf633_service_specifications(self) -> list[dict[str, Any]]: + return super().to_tmf633_service_specifications() + + def to_tmf634_resource_candidate_references(self) -> list[dict[str, Any]]: + return super().to_tmf634_resource_candidate_references() + + def to_tmf634_resource_specification_references(self) -> list[dict[str, Any]]: + return super().to_tmf634_resource_specification_references() + + def to_tmf634_resource_candidates(self) -> list[dict[str, Any]]: + return super().to_tmf634_resource_candidates() + + def to_tmf634_resource_specifications(self) -> list[dict[str, Any]]: + return super().to_tmf634_resource_specifications() + def add_teiv_data_entities( self, entity_type: str = "o-ran-smo-teiv-ran:NRCellDU", 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 7b22264..9a14c9a 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 @@ -1,4 +1,4 @@ -# Copyright 2024 highstreet technologies USA CORP. +# Copyright 2025 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. @@ -146,6 +146,66 @@ class ORanCloudDu(ORanNode): "to_topology_links", ) + def to_tmf686_vertex(self) -> list[dict[str, Any]]: + return self._extend_with_tower_references( + super().to_tmf686_vertex, + "to_tmf686_vertex", + ) + + def to_tmf686_edge(self) -> list[dict[str, Any]]: + return self._extend_with_tower_references( + super().to_tmf686_edge, + "to_tmf686_edge", + ) + + def to_geojson_feature(self) -> list[dict[str, Any]]: + return self._extend_with_tower_references( + super().to_geojson_feature, + "to_geojson_feature", + ) + + def to_tmf633_service_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_tower_references( + super().to_tmf633_service_candidate_references, + "to_tmf633_service_candidate_references", + ) + + def to_tmf633_service_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_tower_references( + super().to_tmf633_service_candidates, + "to_tmf633_service_candidates", + ) + + def to_tmf633_service_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_tower_references( + super().to_tmf633_service_specifications, + "to_tmf633_service_specifications", + ) + + def to_tmf634_resource_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_tower_references( + super().to_tmf634_resource_candidate_references, + "to_tmf634_resource_candidate_references", + ) + + def to_tmf634_resource_specification_references(self) -> list[dict[str, Any]]: + return self._extend_with_tower_references( + super().to_tmf634_resource_specification_references, + "to_tmf634_resource_specification_references", + ) + + def to_tmf634_resource_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_tower_references( + super().to_tmf634_resource_candidates, + "to_tmf634_resource_candidates" + ) + + def to_tmf634_resource_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_tower_references( + super().to_tmf634_resource_specifications, + "to_tmf634_resource_specifications", + ) + def _extend_teiv_data_with_tower_references( self: Any, tower_method_name: str ) -> dict[str, list[dict[str, Any]]]: 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 4823b45..f4016af 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 @@ -1,4 +1,4 @@ -# Copyright 2024 highstreet technologies +# Copyright 2025 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. @@ -173,6 +173,66 @@ class ORanCu(ORanNode): ) return result + def to_tmf686_vertex(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cloud_du_references( + super().to_tmf686_vertex, + "to_tmf686_vertex", + ) + + def to_tmf686_edge(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cloud_du_references( + super().to_tmf686_edge, + "to_tmf686_edge", + ) + + def to_geojson_feature(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cloud_du_references( + super().to_geojson_feature, + "to_geojson_feature", + ) + + def to_tmf633_service_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cloud_du_references( + super().to_tmf633_service_candidate_references, + "to_tmf633_service_candidate_references", + ) + + def to_tmf633_service_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cloud_du_references( + super().to_tmf633_service_candidates, + "to_tmf633_service_candidates", + ) + + def to_tmf633_service_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cloud_du_references( + super().to_tmf633_service_specifications, + "to_tmf633_service_specifications", + ) + + def to_tmf634_resource_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cloud_du_references( + super().to_tmf634_resource_candidate_references, + "to_tmf634_resource_candidate_references", + ) + + def to_tmf634_resource_specification_references(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cloud_du_references( + super().to_tmf634_resource_specification_references, + "to_tmf634_resource_specification_references", + ) + + def to_tmf634_resource_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cloud_du_references( + super().to_tmf634_resource_candidates, + "to_tmf634_resource_candidates" + ) + + def to_tmf634_resource_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cloud_du_references( + super().to_tmf634_resource_specifications, + "to_tmf634_resource_specifications", + ) + def _extend_teiv_data_with_o_ran_cloud_du_references( self: Any, teiv_data: dict[str, list[dict[str, Any]]], @@ -234,3 +294,4 @@ class ORanCu(ORanNode): super().add_teiv_data_relationships(id, aside, bside, rel_type), "add_teiv_data_relationships", ) + diff --git a/code/network-generator/network_generation/model/python/o_ran_du.py b/code/network-generator/network_generation/model/python/o_ran_du.py index d57040f..7c4f99a 100644 --- a/code/network-generator/network_generation/model/python/o_ran_du.py +++ b/code/network-generator/network_generation/model/python/o_ran_du.py @@ -47,7 +47,7 @@ class ORanDu(ORanNode): def __init__( self, data: dict[str, Any] = cast(dict[str, Any], default_value), - **kwargs: dict[str, Any], + **kwargs: dict[str, Any] ) -> None: o_ran_du_data: IORanDu = self._to_o_ran_du_data(data) super().__init__(cast(dict[str, Any], o_ran_du_data), **kwargs) @@ -112,6 +112,140 @@ class ORanDu(ORanNode): if not os.path.exists(path): os.mkdir(path) + def to_geojson_feature(self) -> list[dict[str, Any]]: + return super().to_geojson_feature() + + def to_tmf686_vertex(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_tmf686_vertex() + return result + + def to_tmf686_edge(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_tmf686_edge() + for interface in ["e2", "o1"]: + link_id: str = "".join( + [interface, ":", self.name, "<->", self.parent.name] + ) + source_tp: str = "-".join([self.name, interface.upper()]) + dest_tp: str = "-".join([self.parent.name, interface.upper()]) + result.append( + { + "id": link_id, + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}/edge/{link_id}", + "bidirectional": True, + "description": "Description of an edge object", + "name": self.name, + "edgeCharacteristic": [ + # { + # "id": "string", + # "name": "string", + # "valueType": "string", + # "characteristicRelationship": [ + # { + # "id": "string", + # "href": "string", + # "relationshipType": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # } + # ], + # "value": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # } + ], + "edgeSpecification": { + # "id": "string", + # "href": "string", + # "name": "string", + # "version": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # "@referredType": "string", + }, + "entity": { + "id": "indigo", + "href": "https://indigo.cosmos-lab.org", + "name": "INDIGO", + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "EntityRef", + "@referredType": "Individual", + }, + "graph": { + "id": self.network.id, + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}", + "name": self.network.name, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "GraphRef", + "@referredType": "Graph", + }, + "subGraph": { + # "id": "string", + # "href": "string", + # "name": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # "@referredType": "string", + }, + "vertex": [ + { + "id": source_tp, + "href": ( + f'https://{self.host}/tmf-api/topologyDiscovery' + f'/v4/graph/{self.network.id}/vertex/{source_tp}' + ), + "name": source_tp, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "VertexRef", + "@referredType": "Vertex", + }, + { + "id": dest_tp, + "href": ( + f'https://{self.host}/tmf-api/topologyDiscovery' + f'/v4/graph/{self.network.id}/vertex/{dest_tp}' + ), + "name": dest_tp, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "VertexRef", + "@referredType": "Vertex", + }, + ], + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "Edge", + } + ) + return result + + def to_tmf633_service_candidate_references(self) -> list[dict[str, Any]]: + return super().to_tmf633_service_candidate_references() + + def to_tmf633_service_candidates(self) -> list[dict[str, Any]]: + return super().to_tmf633_service_candidates() + + def to_tmf633_service_specifications(self) -> list[dict[str, Any]]: + return super().to_tmf633_service_specifications() + + def to_tmf634_resource_candidate_references(self) -> list[dict[str, Any]]: + return super().to_tmf634_resource_candidate_references() + + def to_tmf634_resource_specification_references(self) -> list[dict[str, Any]]: + return super().to_tmf634_resource_specification_references() + + def to_tmf634_resource_candidates(self) -> list[dict[str, Any]]: + return super().to_tmf634_resource_candidates() + + def to_tmf634_resource_specifications(self) -> list[dict[str, Any]]: + return super().to_tmf634_resource_specifications() + def add_teiv_data_entities( self, entity_type: str = "o-ran-smo-teiv-ran:ODUFunction", 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 b7dc9ce..fe32ee4 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 @@ -1,4 +1,4 @@ -# Copyright 2024 highstreet technologies +# 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. @@ -174,6 +174,64 @@ class ORanNearRtRic(ORanNode): ) return result + def to_tmf686_vertex(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cu_references( + super().to_tmf686_vertex, "to_tmf686_vertex" + ) + + + def to_geojson_feature(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cu_references( + super().to_geojson_feature, "to_geojson_feature" + ) + + def to_tmf686_edge(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cu_references( + super().to_tmf686_edge, "to_tmf686_edge" + ) + + def to_tmf633_service_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cu_references( + super().to_tmf633_service_candidate_references, + "to_tmf633_service_candidate_references", + ) + + def to_tmf633_service_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cu_references( + super().to_tmf633_service_candidates, + "to_tmf633_service_candidates" + ) + + def to_tmf633_service_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cu_references( + super().to_tmf633_service_specifications, + "to_tmf633_service_specifications" + ) + + def to_tmf634_resource_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cu_references( + super().to_tmf634_resource_candidate_references, + "to_tmf634_resource_candidate_references", + ) + + def to_tmf634_resource_specification_references(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cu_references( + super().to_tmf634_resource_specification_references, + "to_tmf634_resource_specification_references", + ) + + def to_tmf634_resource_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cu_references( + super().to_tmf634_resource_candidates, + "to_tmf634_resource_candidates" + ) + + def to_tmf634_resource_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_cu_references( + super().to_tmf634_resource_specifications, + "to_tmf634_resource_specifications", + ) + def _extend_teiv_data_with_o_ran_cu_references( self: Any, teiv_data: dict[str, list[dict[str, Any]]], 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 fc7fc4a..8a1b6f1 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 @@ -16,19 +16,27 @@ """ Module for a class representing a O-RAN Network """ - -import os import uuid import xml.etree.ElementTree as ET +import os +from typing import Any, Dict, cast from datetime import datetime, timezone -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_ru import ORanRu 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 @@ -66,10 +74,13 @@ class ORanNetwork(ORanObject): self.name = str(configuration.get("name", "WhiteNetwork")) self.operationalState = str(configuration.get("operationalState", "disabled")) - self._center: IGeoLocation = cast( - IGeoLocation, configuration["center"] + self.host: str = str(configuration.get("host", "test.operator.io")) + self.description = ( + f'The root service category of 5G services including RAN, core ' + f'network and IoT services for the year ' + f'{self.__current_time.strftime("%Y")}.' ) - + self._center: IGeoLocation = cast(IGeoLocation, configuration["center"]) # Calculate layout size using the configuration values. nr_cell_du = configuration["pattern"]["nrCellDu"] size = int( @@ -129,6 +140,36 @@ class ORanNetwork(ORanObject): """ return self.__configuration + @property + def version(self) -> dict[str, Any]: + """ + Getter for a version value of O-RAN Network. + :return A string with the version. + """ + return self.configuration["version"] + + @property + def valid_for(self) -> dict[str, Any]: + return self.__my_valid_for + + @property + def current_time(self) -> datetime: + return self.__current_time + + @property + def time_string(self) -> str: + return self.__time_string + + @property + def network( + self, + ) -> Any: # expected is ORanNetwork + return self._network + + @network.setter + def network(self, value: Any) -> None: + self._network = value + 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. @@ -268,6 +309,99 @@ class ORanNetwork(ORanObject): # root.append(self.__context.svg(x, y)) return root + def to_geojson(self) -> dict[str, Any]: + features: list[dict[str, Any]] = self._o_ran_smo.to_geojson_feature() + # links: list[dict[str, Any]] = self._o_ran_smo.to_topology_links() + return { + "type": "FeatureCollection", + "features": features, + } + + def to_tmf686(self) -> dict[str, Any]: + vertex: list[dict[str, Any]] = self._o_ran_smo.to_tmf686_vertex() + edge: list[dict[str, Any]] = self._o_ran_smo.to_tmf686_edge() + + return { + "id": self.id, + "type": "Graph", + "name": self.name, + "description": "A Network Topology in the context of INDIGO.", + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.id}", + "dateCreated": self.__time_string, + "lastUpdated": self.__time_string, + "status": "active", + "graphRelationship": [], + "edge": edge, + "vertex": vertex, + "@base": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "Graph", + } + + def to_tmf632_party_organization(self) -> list[dict[str, Any]]: + return [self._party_organization.get_organization()] + + def to_tmf633_service_candidates(self) -> list[dict[str, Any]]: + return self._o_ran_smo.to_tmf633_service_candidates() + + def to_tmf633_service_specifications(self) -> list[dict[str, Any]]: + return self._o_ran_smo.to_tmf633_service_specifications() + + def to_tmf634_resource_catalog(self) -> list[dict[str, Any]]: + return [ + { + "id": self.id, + "href": f"https://{self.host}/tmf-api/resourceCatalog/v5/resourceCatalog/{self.id}", + "name": self.name, + "description": f'Comprehensive catalog of 5G related resource for the year {self.__current_time.strftime("%Y")}.', + "lastUpdate": self.__time_string, + "lifecycleStatus": "Active", + "version": self.version, + "category": [ + { + "id": self.id, + "href": f"https://{self.host}/tmf-api/resourceCatalog/v5/resourceCategory/{self.id}", + "name": self.name, + "version": self.version, + "@type": "ResourceCategoryRef", + } + ], + "relatedParty": self.related_party, + "validFor": self.valid_for, + "@type": "ResourceCatalog", + } + ] + + def to_tmf634_resource_category(self) -> list[dict[str, Any]]: + return [ + { + "id": self.id, + "href": f"https://{self.host}/tmf-api/resourceCatalog/v5/resourceCategory/{self.id}", + "name": self.name, + "description": f'The root resource category of 5G RAN related resources for the year {self.__current_time.strftime("%Y")}.', + "lastUpdate": self.__time_string, + "lifecycleStatus": "Active", + "version": self.version, + "isRoot": True, + "category": [], + "resourceCandidate": self._o_ran_smo.to_tmf634_resource_candidate_references(), + "resourceSpecification": self._o_ran_smo.to_tmf634_resource_specification_references(), + "validFor": self.valid_for, + "relatedParty": self.related_party, + "@type": "ResourceCategory", + } + ] + + def to_tmf634_resource_candidates(self) -> list[dict[str, Any]]: + return self._o_ran_smo.to_tmf634_resource_candidates() + + def to_tmf634_resource_specifications(self) -> list[dict[str, Any]]: + return self._o_ran_smo.to_tmf634_resource_specifications() + + # Get from network a list of the generated cells + def get_list_cells(self) -> list[ORanRu]: + return self._o_ran_smo.rus + def json(self) -> Dict[str, Any]: """ Return a JSON representation of the network. 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 fa9b305..d114e0c 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 @@ -1,4 +1,4 @@ -# Copyright 2024 highstreet technologies +# Copyright 2025 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. @@ -125,6 +125,10 @@ class ORanNode(ORanObject): result[key] = data[key] # type: ignore return result + @property + def host(self) -> str: + return self.parent.host + @property def address(self) -> AddressType: return self._address @@ -241,12 +245,7 @@ class ORanNode(ORanObject): result.append( { "node-id": self.name, - "o-ran-sc-network:uuid": str( - uuid.uuid5( - uuid.NAMESPACE_DNS, - "-".join([self.network.name, self.name]), - ) - ), + "o-ran-sc-network:uuid": str(uuid.uuid5(uuid.NAMESPACE_DNS, "-".join([self.network.name, self.name]))), "o-ran-sc-network:type": self.type, "o-ran-sc-network:operational-state": self.operationalState, "ietf-network-topology:termination-point": tps, @@ -333,6 +332,671 @@ class ORanNode(ORanObject): def to_directory(self, parent_dir: str) -> None: pass + def get_coordinates(self) -> list[float]: + lng: float = 0.0 + lat: float = 0.0 + + gl = self._geo_location + # TODO: Why a is gl sometimes a dict and not a GeoLoaction??? + if isinstance(gl, GeoLocation): + lng = gl.longitude + lat = gl.latitude + elif isinstance(gl, dict): + lng = gl["longitude"] + lat = gl["latitude"] + + return [lng, lat] + + @abstractmethod + def to_geojson_feature(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = [] + tps: list[dict[str, Any]] = [] + for tp in self.termination_points(): + new_tp = tp.to_topology() + if any(existing_tp['tp-id'] == new_tp['tp-id'] for existing_tp in tps): + pass + else: + tps.append(new_tp) + + result.append( + { + "type": "Feature", + "properties": { + "type": "PropertiesNode", + "node-uuid": str(uuid.uuid5(uuid.NAMESPACE_DNS, "-".join([self.network.name, self.name]))), + "node-id": self.name, + "function": self.type + }, + "geometry": { + "type": "Point", + "coordinates": self.get_coordinates(), + }, + } + ) + return result + + @abstractmethod + def to_tmf686_vertex(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = [] + result.append( + { + "id": self.id, + "name": self.name, + "description": f"Description of a vertex object of type {type(self)}", + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}/vertex/{self.id}", + # optional "edge": [], + "entity": { + "id": "indigo", + "href": "https://indigo.cosmos-lab.org", + "name": "INDIGO", + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "EntityRef", + "@referredType": "Individual", + }, + "graph": { + "id": self.network.id, + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}", + "name": self.network.name, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "GraphRef", + "@referredType": "Graph", + }, + "subGraph": { + # "id": "string", + # "href": "string", + # "name": "string", + # "@baseType": "string", + # "@schemaLocation": F'https://{self.host}/schema/tmf686-schema.json', + # "@type": "string", + # "@referredType": "string", + }, + "vertexCharacteristic": [ + # { + # "id": "string", + # "name": "string", + # "valueType": "string", + # "characteristicRelationship": [ + # { + # "id": "string", + # "href": "string", + # "relationshipType": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # } + # ], + # "value": "string", + # "@baseType": "string", + # "@schemaLocation": F'https://{self.host}/schema/tmf686-schema.json', + # "@type": "string", + # } + ], + "vertexSpecification": { + # "id": "string", + # "href": "string", + # "name": "string", + # "version": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # "@referredType": "string", + }, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "Vertex", + } + ) + for tp in self.termination_points(): + result.append(tp.to_tmf686_vertex()) + return result + + @abstractmethod + def to_tmf686_edge(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = [] + source_tp: str = "-".join([self.name, "phy".upper()]) + dest_tp: str = "-".join([self.parent.name, "phy".upper()]) + if self.parent and "Tower" not in source_tp and "Tower" not in dest_tp: + link_id: str = "".join(["phy", ":", self.name, "<->", self.parent.name]) + link: dict[str, Any] = { + "link-id": link_id, + "source": {"source-node": self.name, "source-tp": source_tp}, + "destination": { + "dest-node": self.parent.name, + "dest-tp": dest_tp, + }, + } + link = { + "id": link_id, + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}/edge/{link_id}", + "bidirectional": True, + "description": "Description of an edge object", + "name": self.name, + "edgeCharacteristic": [ + # { + # "id": "string", + # "name": "string", + # "valueType": "string", + # "characteristicRelationship": [ + # { + # "id": "string", + # "href": "string", + # "relationshipType": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # } + # ], + # "value": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # } + ], + "edgeSpecification": { + # "id": "string", + # "href": "string", + # "name": "string", + # "version": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # "@referredType": "string", + }, + "entity": { + "id": "indigo", + "href": "https://indigo.cosmos-lab.org", + "name": "INDIGO", + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "EntityRef", + "@referredType": "Individual", + }, + "graph": { + "id": self.network.id, + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}", + "name": self.network.name, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "GraphRef", + "@referredType": "Graph", + }, + "subGraph": { + # "id": "string", + # "href": "string", + # "name": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # "@referredType": "string", + }, + "vertex": [ + { + "id": source_tp, + "href": ( + f'https://{self.host}/tmf-api/topologyDiscovery/v4' + f'/graph/{self.network.id}/vertex/{source_tp}' + ), + "name": source_tp, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "VertexRef", + "@referredType": "Vertex", + }, + { + "id": dest_tp, + "href": ( + f'https://{self.host}/tmf-api/topologyDiscovery/v4' + f'/graph/{self.network.id}/vertex/{dest_tp}' + ), + "name": dest_tp, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "VertexRef", + "@referredType": "Vertex", + }, + ], + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "Edge", + } + result.append(link) + return result + + @abstractmethod + def to_tmf633_service_candidate_references(self) -> list[dict[str, Any]]: + return [ + { + "id": self.id, + "href": f"https://{self.host}/tmf-api/serviceCatalogManagement/v4/serviceCandidate/{self.id}", + "name": self.name, + "version": self.network.version, + } + ] + + @abstractmethod + def to_tmf633_service_candidates(self) -> list[dict[str, Any]]: + return [ + { + "id": self.id, + "href": f"https://{self.host}/tmf-api/serviceCatalogManagement/v4/serviceCandidate/{self.id}", + "name": self.name, + "description": ( + "The service candidate of 5G services including RAN, core " + "network and IoT services for the year " + f'{self.__current_time.strftime("%Y")}.'), + "lastUpdate": self.__time_string, + "lifecycleStatus": "Active", + "version": self.network.version, + "category": [ + { + "id": self.network.id, + "href": ( + f"https://{self.host}/tmf-api/serviceCatalogManagement/v4/serviceCategory/{self.network.id}" + ), + "name": self.network.name, + "version": self.network.version, + } + ], + "serviceSpecification": { + "id": self.id, + "href": f"https://{self.host}/tmf-api/serviceCatalogManagement/v4/serviceSpecification/{self.id}", + "name": self.name, + "version": self.network.version, + }, + "validFor": self.network.valid_for, + } + ] + + def get_oran_network_id_property(self) -> str: + if self.__class__.__name__ == "NrCellDu": + return "properties.name" + else: + return "properties.node-id" + + @abstractmethod + def to_tmf633_service_specifications(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = [] + result.append( + { + "id": self.id, + "href": f"https://{self.host}/tmf-api/serviceCatalogManagement/v4/serviceSpecification/{self.id}", + "name": self.name, + "description": "INDIGO Service Specification PublicSafety", + "isBundle": False, + "version": self.network.version, + "lastUpdate": self.__time_string, + "lifecycleStatus": "Active", + "relatedParty": self.network.related_party, + "validFor": self.network.valid_for, + "attachment": [], + "entitySpecRelationship": [], + "featureSpecification": [], + "resourceSpecification": self.to_tmf634_resource_specification_references(), + "serviceLevelSpecification": [], + "serviceSpecRelationship": [], + "specCharacteristic": [ + { + "id": "resilienceLevel", + "name": "Resilience Level", + "description": "The resilience level of this service.", + "valueType": "integer", + "configurable": False, + "characteristicValueSpecification": [ + {"value": 0, "isDefault": True} + ], + }, + { + "id": "securityLevel", + "description": "The security level of this service.", + "valueType": "integer", + "configurable": False, + "characteristicValueSpecification": [ + {"value": 1, "isDefault": True} + ], + }, + { + "id": "sNssai", + "name": "Single Network Slice Selection Assistance Information", + "description": "The Single Network Slice Selection Assistance Information of this service.", + "configurable": True, + "valueType": "Integer32", + "isUnique": True, + }, + { + "id": "maxBandwidthUE", + "name": "Maximum Bandwidth UE", + "description": "Maximum bandwidth in [MBps] per User Equipment (UE)", + "configurable": True, + "valueType": "Mbps", + "minCardinality": 5, + "maxCardinality": 1000, + }, + { + "id": "coverageArea", + "name": "Coverage Area", + "description": "The geographical area covered by this entity.", + "configurable": False, + "extensible": False, + "isUnique": True, + "characteristicValueSpecification": [ + { + "valueType": "href", + "value": ( + f'https://{self.host}/area/o-ran-network.' + "geo.json/features?" + f'{self.get_oran_network_id_property()}=' + f'{self.name}'), + } + ], + }, + { + "id": "maxLatency", + "name": "Maximal Latency", + "description": ( + "Maximum tolerable delay in milliseconds [ms]"), + "configurable": False, + "valueType": "time", + "characteristicValueSpecification": [ + {"valueType": "integer", "value": 10} + ], + }, + { + "name": "availability", + "description": "Guaranteed service uptime", + "configurable": False, + "valueType": "Propability", + "characteristicValueSpecification": [ + {"valueType": "Propability", "value": "99.9%"} + ], + }, + { + "id": "mttr", + "name": "Mean Time to Repair", + "description": "Maximal Mean Time To Repair a failure", + "configurable": False, + "valueType": "time", + "characteristicValueSpecification": [ + {"valueType": "maximum time", "value": "4 hours"} + ], + }, + { + "id": "mtbf", + "name": "Mean Time Between Failures", + "description": "Target for Mean Time Between Failures", + "configurable": False, + "valueType": "time", + "characteristicValueSpecification": [ + {"valueType": "minimum time", "value": "1 year"} + ], + }, + ], + } + ) + return result + + @abstractmethod + def to_tmf634_resource_candidate_references(self) -> list[dict[str, Any]]: + return [ + { + "id": self.id, + "href": f"https://{self.host}/tmf-api/resourceCatalog/v5/resourceCandidate/{self.id}", + "name": self.name, + "version": self.network.version, + "@type": "ResourceCandidateRef", + "@referredType": "ResourceCandidate", + } + ] + + @abstractmethod + def to_tmf634_resource_specification_references(self) -> list[dict[str, Any]]: + return [ + { + "id": self.id, + "href": f"https://{self.host}/tmf-api/resourceCatalog/v5/resourceSpecification/{self.id}", + "name": self.name, + "version": self.network.version, + "@type": "ResourceSpecificationRef", + "@referredType": "ResourceSpecification", + } + ] + + @abstractmethod + def to_tmf634_resource_candidates(self) -> list[dict[str, Any]]: + return [ + { + "id": self.id, + "href": f"https://{self.host}/tmf-api/resourceCatalog/v5/resourceCandidate/{self.id}", + "name": self.name, + "description": f'The resource candidate of 5G RAN related resources for year {self.__current_time.strftime("%Y")}.', + "lastUpdate": self.__time_string, + "lifecycleStatus": "Active", + "version": self.network.version, + "category": [ + { + "id": self.network.id, + "href": f"https://{self.host}/tmf-api/resourceCatalog/v5/resourceCategory/{self.network.id}", + "name": self.network.name, + "version": self.network.version, + } + ], + "resourceSpecification": { + "id": self.id, + "href": f"https://{self.host}/tmf-api/resourceCatalog/v5/resourceSpecification/{self.id}", + "name": self.name, + "version": self.network.version, + "@type": "ResourceSpecificationRef", + "@referredType": "ResourceSpecification", + }, + "validFor": self.network.valid_for, + "@type": "ResourceCandidate", + } + ] + + @abstractmethod + def to_tmf634_resource_specifications(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = [] + result.append( + { + "id": self.id, + "href": f"https://{self.host}/tmf-api/resourceCatalog/v5/resourceSpecification/{self.id}", + "name": self.name, + "description": f"INDIGO Resource Specification for type {type(self)}", + "isBundle": False, + "version": self.network.version, + "lastUpdate": self.__time_string, + "lifecycleStatus": "Active", + "relatedParty": self.network.related_party, + "validFor": self.network.valid_for, + "category": "RAN resource", + "attachment": [], + "featureSpecification": [], + "resourceSpecCharacteristic": [ + { + "name": "nRCellName", + "description": "Cell identifier or name of the cell.", + "configurable": True, + "isUnique": True, + "valueType": "string", + }, + { + "name": "nRCellId", + "description": "Uniquely identifies a cell within a PLMN. It is often constructed from gNodeB ID + Physical Cell ID.", + "valueType": "string", + "configurable": True, + "isUnique": True, + }, + { + "name": "nRCellState", + "description": ( + "Represents the active state of the cell. Takes " + "one of the following values: " + "IDLE ACTIVE INACTIVE UNKNOWN"), + "valueType": "string", + "configurable": False, + "isUnique": True, + }, + { + "name": "TAI", + "description": ( + "Tracking Area Identifier (TAI). This is a " + "globally unique tracking area identifier, " + "made up of the PLMN ID and the TAC."), + "valueType": "string", + "configurable": True, + "isUnique": True, + }, + { + "name": "channelBandwidthUl", + "description": "Uplink channel bandwidth.", + "valueType": "float", + "configurable": True, + "isUnique": True, + }, + { + "name": "channelBandwidthDl", + "description": "Downlink channel bandwidth.", + "valueType": "float", + "configurable": True, + "isUnique": True, + }, + { + "name": "maximumOutputPower", + "description": ( + "Maximum power in Watts for the sum of all downlink" + " channels that are allowed to be used " + "simultaneously in a cell."), + "valueType": "float", + "configurable": True, + "isUnique": True, + }, + { + "name": "userCapacity", + "description": ( + "Maximum number of pieces of user equipment (UEs) " + "that can connect to this nrCellDU simultaneously." + ), + "valueType": "Integer", + "configurable": True, + "isUnique": True, + }, + { + "name": "physicalCellID", + "description": ( + "Physical cell identifier. Takes a value in the " + "range 0 to 503. The physical cell id is used by " + "the cell to encode and decode the data that it " + "transmits. It is used in a similar way to the " + "UMTS scrambling code. To avoid interference, " + "neighboring cells should have different physical " + "cell identifiers. The physical cell id is derived " + "from the primary and secondary synchronization " + "signals (PSS and SSS). The PSS takes a value from " + "0 to 2, the SSS takes a value from 0 to 167, and " + "the physical cell id is determined based on the " + "following formula: PSS + 3*SSS. The result of " + "this calculation equates to a value of between " + "0 and 503."), + "valueType": "Integer", + "configurable": True, + "isUnique": True, + }, + { + "name": "localCellId", + "description": "Local cell id unique within the nrCellDU.", + "valueType": "Integer", + "configurable": True, + "isUnique": True, + }, + { + "name": "arfcnDl", + "description": ( + "Absolute Radio Frequency Channel Number " + "(downlink). An integer value which identifies the " + "downlink carrier frequency of the cell."), + "valueType": "Integer", + "configurable": True, + "isUnique": True, + }, + { + "name": "arfcnUl", + "description": ( + "Absolute Radio Frequency Channel Number (uplink). " + "An integer value which identifies the uplink " + "carrier frequency of the cell."), + "valueType": "Integer", + "configurable": True, + "isUnique": True, + }, + { + "name": "nRPCI", + "description": ( + "Holds the Physical Cell Identity (PCI) of the " + "NR cell."), + "valueType": "String", + "configurable": True, + "isUnique": True, + }, + { + "name": "ssbFreq", + "description": ( + "Indicates cell defining SSB frequency domain " + "position. Frequency of the cell defining SSB " + "transmission. The frequency provided in this " + "attribute identifies the position of resource " + "element. The frequency shall be positioned on the " + "NR global frequency raster, and within " + "bSChannelBwDL. Allowed values: 0..3279165"), + "valueType": "Float", + "configurable": True, + "isUnique": True, + }, + { + "name": "ssbPeriodicity", + "description": ( + "Indicates cell defined SSB periodicity in number " + "of subframes(ms). The SSB periodicity in msec is " + "used for the rate matching purpose. " + "Allowed values: 5, 10, 20, 40, 80, 160"), + "valueType": "Integer", + "configurable": True, + "isUnique": True, + }, + { + "name": "ssbSubCarrierSpacing", + "description": "This SSB is used for synchronization. Its units are in kHz. Allowed values: {15, 30, 120, 240}", + "valueType": "Integer", + "configurable": True, + "isUnique": True, + }, + { + "name": "operationalState", + "description": ( + "Operational state of the nrCellDU. Takes one of " + "the following values: Enabled Disabled Other " + "Unknown"), + "valueType": "String", + "configurable": False, + "isUnique": True, + }, + { + "name": "administrativeState", + "description": ( + "Administrative state of the nrCellDU. Takes one of" + " the following values: Unlocked Locked Shutting " + "Down Other Unknown"), + "valueType": "String", + "configurable": False, + "isUnique": True, + }, + ], + "resourceSpecRelationship": [], # TODO self.parent.to_tmf634_resource_specifications(), + "@type": "ResourceSpecification", + } + ) + return result + @abstractmethod def add_teiv_data_entities( self, entity_type: str, attributes: dict[str, Any] = {} 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 fb45921..944b574 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 @@ -1,4 +1,4 @@ -# Copyright 2024 highstreet technologies +# Copyright 2025 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. @@ -202,6 +202,157 @@ class ORanRu(ORanNode): result.extend(self.flatten_list(getattr(cell, cell_method_name)())) return result + def to_geojson_feature(self) -> list[dict[str, Any]]: + return self._extend_with_cell_references( + super().to_geojson_feature, + "to_geojson_feature", + ) + + def to_tmf686_vertex(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_tmf686_vertex() + result.extend(self.oRanDu.to_tmf686_vertex()) + return result + + def to_tmf686_edge(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_links() + result.extend(self.oRanDu.to_topology_links()) + for interface in ["phy", "ofhm", "ofhc", "ofhu", "ofhs"]: + link_id: str = "".join([interface, ":", self.name, "<->", self.oRanDu.name]) + source_tp: str = "-".join([self.name, interface.upper()]) + dest_tp: str = "-".join([self.oRanDu.name, interface.upper()]) + result.append( + { + "id": link_id, + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}/edge/{link_id}", + "bidirectional": True, + "description": "Description of an edge object", + "name": self.name, + "edgeCharacteristic": [ + # { + # "id": "string", + # "name": "string", + # "valueType": "string", + # "characteristicRelationship": [ + # { + # "id": "string", + # "href": "string", + # "relationshipType": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # } + # ], + # "value": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # } + ], + "edgeSpecification": { + # "id": "string", + # "href": "string", + # "name": "string", + # "version": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # "@referredType": "string", + }, + "entity": { + "id": "indigo", + "href": "https://indigo.cosmos-lab.org", + "name": "INDIGO", + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "EntityRef", + "@referredType": "Individual", + }, + "graph": { + "id": self.network.id, + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}", + "name": self.network.name, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "GraphRef", + "@referredType": "Graph", + }, + "subGraph": { + # "id": "string", + # "href": "string", + # "name": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # "@referredType": "string", + }, + "vertex": [ + { + "id": source_tp, + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}/vertex/{source_tp}", + "name": source_tp, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "VertexRef", + "@referredType": "Vertex", + }, + { + "id": dest_tp, + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}/vertex/{dest_tp}", + "name": dest_tp, + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "VertexRef", + "@referredType": "Vertex", + }, + ], + "@baseType": "object", + "@schemaLocation": f"https://{self.host}/schema/tmf686-schema.json", + "@type": "Edge", + } + ) + return result + + def to_tmf633_service_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_cell_references( + super().to_tmf633_service_candidate_references, + "to_tmf633_service_candidate_references", + ) + + def to_tmf633_service_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_cell_references( + super().to_tmf633_service_candidates, + "to_tmf633_service_candidates", + ) + + def to_tmf633_service_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_cell_references( + super().to_tmf633_service_specifications, + "to_tmf633_service_specifications", + ) + + def to_tmf634_resource_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_cell_references( + super().to_tmf634_resource_candidate_references, + "to_tmf634_resource_candidate_references", + ) + + def to_tmf634_resource_specification_references(self) -> list[dict[str, Any]]: + return self._extend_with_cell_references( + super().to_tmf634_resource_specification_references, + "to_tmf634_resource_specification_references", + ) + + def to_tmf634_resource_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_cell_references( + super().to_tmf634_resource_candidates, "to_tmf634_resource_candidates" + ) + + def to_tmf634_resource_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_cell_references( + super().to_tmf634_resource_specifications, + "to_tmf634_resource_specifications", + ) + def add_teiv_data_entities( self, entity_type: str = "o-ran-smo-teiv-ran:ORUFunction", 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 b7946cf..68a56e8 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 @@ -1,4 +1,4 @@ -# Copyright 2024 highstreet technologies +# Copyright 2025 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. @@ -30,6 +30,7 @@ from network_generation.model.python.o_ran_node import ( ORanNode, default_value, ) +from network_generation.model.python.o_ran_ru import ORanRu from network_generation.model.python.tower import Tower # Define the "IORanSmo" interface @@ -41,7 +42,6 @@ class ORanSmo(ORanNode): """ Class representing an O-RAN Service Management and Operation object. """ - _interfaces = ["a1", "o1", "ofhm", "o2"] def __init__( @@ -112,6 +112,14 @@ class ORanSmo(ORanNode): result.append(tower) return result + @property + def rus(self) -> list[ORanRu]: + result: list[ORanRu] = [] + for ric in self.o_ran_near_rt_rics: + for tower in ric.towers: + result.extend(tower.o_ran_rus) + return result + def toKml(self) -> ET.Element: smo = super().toKml() for ric in self.o_ran_near_rt_rics: @@ -152,6 +160,53 @@ class ORanSmo(ORanNode): super().to_topology_links, "to_topology_links" ) + def to_tmf686_vertex(self) -> list[dict[str, Any]]: + return self._extend_with_ric_references( + super().to_tmf686_vertex, "to_tmf686_vertex") + + def to_tmf686_edge(self) -> list[dict[str, Any]]: + return self._extend_with_ric_references( + super().to_tmf686_edge, "to_tmf686_edge") + + def to_geojson_feature(self) -> list[dict[str, Any]]: + return self._extend_with_ric_references( + super().to_geojson_feature, "to_geojson_feature") + + def to_tmf633_service_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_ric_references( + super().to_tmf633_service_candidate_references, + "to_tmf633_service_candidate_references") + + def to_tmf633_service_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_ric_references( + super().to_tmf633_service_candidates, + "to_tmf633_service_candidates") + + def to_tmf633_service_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_ric_references( + super().to_tmf633_service_specifications, + "to_tmf633_service_specifications") + + def to_tmf634_resource_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_ric_references( + super().to_tmf634_resource_candidate_references, + "to_tmf634_resource_candidate_references") + + def to_tmf634_resource_specification_references(self) -> list[dict[str, Any]]: + return self._extend_with_ric_references( + super().to_tmf634_resource_specification_references, + "to_tmf634_resource_specification_references") + + def to_tmf634_resource_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_ric_references( + super().to_tmf634_resource_candidates, + "to_tmf634_resource_candidates") + + def to_tmf634_resource_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_ric_references( + super().to_tmf634_resource_specifications, + "to_tmf634_resource_specifications") + def _extend_teiv_data_with_ric_references( self: Any, teiv_data: dict[str, list[dict[str, Any]]], 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 a036fa6..9d3d69e 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 @@ -1,4 +1,4 @@ -# Copyright 2024 highstreet technologies +# Copyright 2025 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. @@ -66,6 +66,10 @@ class ORanTerminationPoint(ORanObject): result[key] = data[key] # type: ignore return result + @property + def host(self) -> str: + return self.network.host + @property def supporter(self) -> dict[str, str]: return self._supporter @@ -95,3 +99,87 @@ class ORanTerminationPoint(ORanObject): if not (self.type == "o-ran-sc-network:phy"): result["supporting-termination-point"] = [self.supporter] return result + + def to_tmf686_vertex(self) -> dict[str, Any]: + result: dict[str, Any] = { + "id": self.id, + "name": self.name, + "description": f"Description of a vertex object of type {type(self)}", + "@type": "Vertex", + } + if (self.supporter and self.network): + # TODO self.network!!! network.host + result = { + "id": self.id, + "name": self.name, + "description": ( + f'Description of a vertex object of type {type(self)}'), + "href": ( + f'https://{self.network.host}/tmf-api/topologyDiscovery/v4/' + f'graph/{self.network.id}/vertex/{self.id}'), + # optional "edge": [], + "entity": { + "id": "indigo", + "href": "https://indigo.cosmos-lab.org", + "name": "INDIGO", + "@baseType": "object", + "@schemaLocation": ( + f'https://{self.network.host}/schema/tmf686-schema.json' + ), + "@type": "EntityRef", + "@referredType": "Individual", + }, + "graph": { + "id": self.network.id, + "href": f"https://{self.host}/tmf-api/topologyDiscovery/v4/graph/{self.network.id}", + "name": self.network.name, + "@baseType": "object", + "@schemaLocation": f"https://{self.network.host}/schema/tmf686-schema.json", + "@type": "GraphRef", + "@referredType": "Graph", + }, + "subGraph": { + # "id": "string", + # "href": "string", + # "name": "string", + # "@baseType": "string", + # "@schemaLocation": f'https://{self.network.host}/schema/tmf686-schema.json', + # "@type": "string", + # "@referredType": "string", + }, + "vertexCharacteristic": [ + # { + # "id": "string", + # "name": "string", + # "valueType": "string", + # "characteristicRelationship": [ + # { + # "id": "string", + # "href": "string", + # "relationshipType": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # } + # ], + # "value": "string", + # "@baseType": "string", + # "@schemaLocation": f'https://{self.network.host}/schema/tmf686-schema.json', + # "@type": "string", + # } + ], + "vertexSpecification": { + # "id": "string", + # "href": "string", + # "name": "string", + # "version": "string", + # "@baseType": "string", + # "@schemaLocation": "string", + # "@type": "string", + # "@referredType": "string", + }, + "@baseType": "object", + "@schemaLocation": f"https://{self.network.host}/schema/tmf686-schema.json", + "@type": "Vertex", + } + return result diff --git a/code/network-generator/network_generation/model/python/tower.py b/code/network-generator/network_generation/model/python/tower.py index 0631744..5be4820 100644 --- a/code/network-generator/network_generation/model/python/tower.py +++ b/code/network-generator/network_generation/model/python/tower.py @@ -153,6 +153,65 @@ class Tower(ORanNode): "to_topology_links", ) + def to_tmf686_vertex(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_ru_references( + super().to_tmf686_vertex, + "to_tmf686_vertex", + ) + + def to_tmf686_edge(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_ru_references( + super().to_tmf686_edge, + "to_tmf686_edge", + ) + + def to_geojson_feature(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_ru_references( + super().to_geojson_feature, + "to_geojson_feature", + ) + + def to_tmf633_service_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_ru_references( + super().to_tmf633_service_candidate_references, + "to_tmf633_service_candidate_references", + ) + + def to_tmf633_service_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_ru_references( + super().to_tmf633_service_candidates, + "to_tmf633_service_candidates", + ) + + def to_tmf633_service_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_ru_references( + super().to_tmf633_service_specifications, + "to_tmf633_service_specifications", + ) + + def to_tmf634_resource_candidate_references(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_ru_references( + super().to_tmf634_resource_candidate_references, + "to_tmf634_resource_candidate_references", + ) + + def to_tmf634_resource_specification_references(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_ru_references( + super().to_tmf634_resource_specification_references, + "to_tmf634_resource_specification_references", + ) + + def to_tmf634_resource_candidates(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_ru_references( + super().to_tmf634_resource_candidates, "to_tmf634_resource_candidates" + ) + + def to_tmf634_resource_specifications(self) -> list[dict[str, Any]]: + return self._extend_with_o_ran_ru_references( + super().to_tmf634_resource_specifications, + "to_tmf634_resource_specifications", + ) + def _extend_teiv_data_with_o_ran_ru_references( self: Any, o_ran_ru_method_name: str ) -> dict[str, list[dict[str, Any]]]: diff --git a/code/network-generator/network_generation/view/network_viewer.py b/code/network-generator/network_generation/view/network_viewer.py index fae65a3..c565810 100644 --- a/code/network-generator/network_generation/view/network_viewer.py +++ b/code/network-generator/network_generation/view/network_viewer.py @@ -1,4 +1,4 @@ -# Copyright 2024 highstreet technologies GmbH +# Copyright 2025 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. @@ -21,8 +21,7 @@ import gzip import json import xml.etree.ElementTree as ET import zipfile -from typing import Any - +from typing import Any, Callable from typing_extensions import Buffer from network_generation.model.python.o_ran_network import ORanNetwork @@ -242,3 +241,109 @@ class NetworkViewer: kml_file.write(kml) kml_file.close() print(f'File "{filename}.kml" saved!') + + def rfc7946(self, filename: str, compressed: bool = True) -> None: + """ + Method saving the class content to a file in geojson format. + + :param filename: A valid path to a file on the system. + :param compressed: if True, kml is stored as kmz format. + :type filename: string + """ + output: dict[str, Any] = self.__network.to_geojson() + file_extension: str = ".geo.json" + self.__save_on_disc(f"{filename}{file_extension}", compressed, output) + + def tmf686(self, filename: str, compressed: bool = True) -> None: + """ + Method saving the class content to a file in json format. + :param filename: A valid path to a file on the system. + :param compressed: if True, svg is stored as tmf686 format. + :type filename: string + """ + output: dict[str, Any] = self.__network.to_tmf686() + file_extension: str = ".tmf686.json" + self.__save_on_disc(f"{filename}{file_extension}", compressed, output) + + + def tmf632(self, filename: str, compressed: bool = True) -> None: + """ + Method saving the class content to a file in tmf632 format. + """ + + file_suffixes = { + "party-organization": self.__network.to_tmf632_party_organization, + } + for suffix, method in file_suffixes.items(): + output: list[dict[str, Any]] = method() + file_extension: str = f".tmf632.{suffix}.json" + self.__save_on_disc( + f"{filename}{file_extension}", compressed, output) + + def tmf633(self, filename: str, compressed: bool = True) -> None: + """ + Method saving the class content to a file in tmf633 format. + It requires 4 json files: + 1. service-catalog + 2. service-category + 3. service-definition + 4. service-candidate + + :param filename: A valid path to a file on the system. + :param compressed: if True, kml is stored as kmz format. + :type filename: string + """ + + file_suffixes = { + "service-catalogs": self.service_catalog.to_tmf633_service_catalog, + "service-categories": lambda: self.__network.to_tmf633_service_category() + self.__service2.to_tmf633_service_category() + self.__service3.to_tmf633_service_category(), + "service-candidates": lambda: self.__network.to_tmf633_service_candidates() + self.__service2.to_tmf633_service_candidates() + self.__service3.to_tmf633_service_candidates(), + "service-specifications": lambda: self.__network.to_tmf633_service_specifications() + self.__service2.to_tmf633_service_specifications() + self.__service3.to_tmf633_service_specifications(), + } + for suffix, method in file_suffixes.items(): + output: list[dict[str, Any]] = method() + file_extension: str = f".tmf633.{suffix}.json" + self.__save_on_disc( + f"{filename}{file_extension}", compressed, output) + + def tmf634(self, filename: str, compressed: bool = True) -> None: + """ + Method saving the class content to a file in tmf634 format. + It requires 4 json files: + 1. resource-catalog + 2. resource-category + 3. resource-definition + 4. resource-candidate + + :param filename: A valid path to a file on the system. + :param compressed: if True, kml is stored as kmz format. + :type filename: string + """ + + file_suffixes = { + "resource-catalogs": self.__network.to_tmf634_resource_catalog, + "resource-categories": self.__network.to_tmf634_resource_category, + "resource-candidates": self.__network.to_tmf634_resource_candidates, + "resource-specifications": self.__network.to_tmf634_resource_specifications, + } + + for suffix, method in file_suffixes.items(): + output: list[dict[str, Any]] = method() + file_extension: str = f".tmf634.{suffix}.json" + self.__save_on_disc( + f"{filename}{file_extension}", compressed, output) + + # Extend process_data to handle a list of methods + @staticmethod + def chain(methods: list[Callable[[], list[dict[str, Any]]]]) -> list[dict[str, Any]]: + result = [] + + # Loop through each method, call it, and extend the result list + for method in methods: + data = method() # Call the method + # Process the data, e.g., by adding a new field to each dictionary + for item in data: + item['processed'] = True # Adding a new field 'processed' + result.extend(data) + + return result -- 2.16.6