ADD N1, N2 N3 interfacing to topology generation
[oam.git] / code / network-topology-instance-generator / model / python / tapi_topology.py
1 # Copyright 2022 highstreet technologies GmbH
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 #!/usr/bin/python
16 """
17 Module containing the main class for this project for a TAPI Topology.
18 """
19 import uuid
20 from typing import Dict, List, Union
21 from lxml import etree
22
23 from model.python.top import Top
24 from model.python.tapi_node import TapiNode
25 from model.python.tapi_node_smo import TapiNodeSmo
26 from model.python.tapi_node_o_cloud import TapiNodeOCloud
27 from model.python.tapi_node_near_rt_ric import TapiNodeNearRtRic
28 from model.python.tapi_node_amf import TapiNodeAmf
29 from model.python.tapi_node_upf import TapiNodeUpf
30 from model.python.tapi_node_o_cu_cp import TapiNodeOCuCp
31 from model.python.tapi_node_o_cu_up import TapiNodeOCuUp
32 from model.python.tapi_node_o_du import TapiNodeODu
33 from model.python.tapi_node_fronthaul_gateway import TapiNodeFronthaulGateway
34 from model.python.tapi_node_o_ru import TapiNodeORu
35 from model.python.tapi_node_user_equipment import TapiNodeUserEquipment
36 from model.python.tapi_link import TapiLink
37
38
39 class TapiTopology(Top):
40     """
41     Class representing a TAPI Topology
42     """
43
44     __data: Dict[str, Union[str, List[Union[Dict, TapiNode, TapiLink]]]] = None
45     __configuration: dict = None
46
47     # constructor
48     def __init__(self, configuration: dict):
49         super().__init__(configuration)
50         self.__configuration = configuration
51         self.__data = {
52             "uuid": str(uuid.uuid4()),
53             "name": [{
54                 "value-name": "network-name",
55                 "value": configuration['network']['name']}],
56             "layer-protocol-name": ["ETH"],
57             "node": [],
58             "link": []}
59
60         topology_structure: dict = configuration['network']['pattern']
61         network_function_type: str = next(iter(topology_structure))
62         count: int = configuration['network']['pattern'][network_function_type]
63
64         if network_function_type == "smo":
65             self.__create_smos(None, topology_structure, count)
66         elif network_function_type == "near-rt-ric":
67             self.__create_near_rt_rics(None, topology_structure, count)
68         elif network_function_type == "o-cu":
69             self.__create_o_cus(None, topology_structure, count)
70         elif network_function_type == "o-du":
71             self.__create_o_dus(None, topology_structure, count)
72         elif network_function_type == "o-ru":
73             self.__create_o_rus(None, topology_structure, count)
74         elif network_function_type == "ue":
75             self.__create_ues(None, topology_structure, count)
76         else:
77             print("Unknown network function type", network_function_type)
78
79     # getter
80     def configuration(self) -> dict:
81         """
82         Getter for a json object representing the TAPI Topology configuration.
83         :return TAPI Topology configuration as json object.
84         """
85         return self.__configuration
86
87     def data(self) -> dict:
88         """
89         Getter for a json object representing the TAPI Topology.
90         :return TAPI Topology as json object.
91         """
92         return self.__data
93
94     def identifier(self) -> str:
95         """
96         Getter returning the TAPI Topology identifier.
97         :return Object identifier as UUID.
98         """
99         return self.__data["uuid"]
100
101     def name(self) -> str:
102         """
103         Getter for TAPI Topology name. The TAPI topology is a representation of
104         the network. Therefore, the TAPI Topology name has the same value as the
105         Network name.
106         :return TAPI Topology name as string.
107         """
108         return self.__configuration['network']['name']
109
110     def json(self) -> dict:
111         """
112         Getter for a json object representing the TAPI Topology.
113         :return TAPI Topology Context as json object.
114         """
115         result = self.data().copy()
116
117         # nodes handling
118         result["node"] = []
119         for node in self.__data["node"]:
120             result["node"].append(node.json())
121
122         # link handling
123         result["link"] = []
124         for link in self.__data["link"]:
125             result["link"].append(link.json())
126
127         return result
128
129     def svg(self, svg_x: int, svg_y: int) -> etree.Element:
130         """
131         Getter for a xml Element object representing the TAPI Topology Context.
132         :return TAPI Topology Context as svg object.
133         """
134         group = etree.Element("g")
135         title = etree.Element("title")
136         title.text = "\n TAPI Topology \n id: " + \
137             self.identifier()  # + "\n name: " + self.name()
138         group.append(title)
139
140         # nodes handling
141         index_per_type: Dict = {}
142         svg_nodes = []
143         for node in self.__data["node"]:
144             if type(node) in index_per_type:
145                 index_per_type[type(node)] = index_per_type[type(node)] + 1
146             else:
147                 index_per_type[type(node)] = 0
148             index = index_per_type[type(node)]
149             node_x = svg_x + \
150                 index*self.__svg_dynamic_x_offset_by_node_type(type(node)) + \
151                 self.__svg_static_x_offset_by_node_type(type(node))
152             node_y = svg_y + self.__svg_y_offset_by_node_type(type(node))
153             svg_nodes.append(node.svg(node_x, node_y))
154             # group.append(node.svg(node_x, node_y))
155
156         # handling and drawing links
157         for link in self.__data["link"]:
158             group.append(link.svg(0, 0))
159
160         # drawing nodes
161         for svg_node in svg_nodes:
162             group.append(svg_node)
163
164         return group
165
166     def __svg_static_x_offset_by_node_type(self, node_type) -> int:
167         """
168         Mapping function from node types to y position in svg
169         return: int value
170         """
171         pattern = self.configuration()['network']['pattern']
172         padding = 0  # self.FONTSIZE
173         width_unit = (2 * 2 * self.FONTSIZE + padding)
174
175         ru = (pattern['user-equipment']-1) * width_unit - 2*self.FONTSIZE
176         fhgw = (pattern['o-ru'] *
177                 pattern['user-equipment'] - 1) * width_unit - 2*self.FONTSIZE
178         odu = (pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']
179                - 1) * width_unit - 2*self.FONTSIZE
180         ocu = (pattern['o-du'] * pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']
181                - 1) * width_unit - 2*self.FONTSIZE
182         ric = (pattern['near-rt-ric'] * pattern['o-du'] * pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']
183                - 1) * width_unit - 2*self.FONTSIZE
184         smo = (pattern['smo'] * pattern['near-rt-ric'] * pattern['o-du'] * pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']
185                -0.5) * width_unit * 2 - 2*self.FONTSIZE
186
187         x_mapping: Dict[type, int] = {
188             TapiNodeSmo: smo,
189             TapiNodeOCloud: ric,
190             TapiNodeNearRtRic: ric,
191             TapiNodeAmf: ric + 16*self.FONTSIZE,
192             TapiNodeUpf: ric + 24*self.FONTSIZE,
193             TapiNodeOCuCp: ocu - 12.5*self.FONTSIZE,
194             TapiNodeOCuUp: ocu + 12.5*self.FONTSIZE,
195             TapiNodeODu: odu,
196             TapiNodeFronthaulGateway: fhgw,
197             TapiNodeORu: ru,
198             TapiNodeUserEquipment: 0
199         }
200         if node_type in x_mapping:
201             return x_mapping[node_type]
202         return 0
203
204     def __svg_dynamic_x_offset_by_node_type(self, node_type) -> int:
205         """
206         Mapping function from node types to y position in svg
207         return: int value
208         """
209         padding = 0
210         pattern = self.configuration()['network']['pattern']
211         width_unit = (2 * 2 * self.FONTSIZE + padding)
212         x_mapping: Dict[type, int] = {
213             TapiNodeSmo: pattern['near-rt-ric'] * pattern['o-cu'] * pattern['o-du'] * pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']*2,
214             TapiNodeOCloud: pattern['o-cu'] * pattern['o-du'] * pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']*2,
215             TapiNodeNearRtRic: pattern['o-cu'] * pattern['o-du'] * pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']*2,
216             TapiNodeAmf: pattern['o-cu'] * pattern['o-du'] * pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']*2,
217             TapiNodeUpf: pattern['o-cu'] * pattern['o-du'] * pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']*2,
218             TapiNodeOCuCp: pattern['o-du'] * pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']*2,
219             TapiNodeOCuUp: pattern['o-du'] * pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']*2,
220             TapiNodeODu: pattern['fronthaul-gateway'] * pattern['o-ru'] * pattern['user-equipment']*2,
221             TapiNodeFronthaulGateway: pattern['o-ru'] * pattern['user-equipment']*2,
222             TapiNodeORu: pattern['user-equipment']*2,
223             TapiNodeUserEquipment: 2
224         }
225         if node_type in x_mapping:
226             return x_mapping[node_type] * width_unit
227         return 0
228
229     def __svg_y_offset_by_node_type(self, node_type) -> int:
230         """
231         Mapping function from node types to y position in svg
232         return: int value
233         """
234         offset = 11*self.FONTSIZE
235         y_mapping: Dict[type, int] = {
236             TapiNodeSmo: 0 * offset,
237             TapiNodeOCloud: 1 * offset,
238             TapiNodeNearRtRic: 2 * offset,
239             TapiNodeAmf: 2 * offset,
240             TapiNodeUpf: 2 * offset,
241             TapiNodeOCuCp: 3.5 * offset - 4 * self.FONTSIZE,
242             TapiNodeOCuUp: 3.5 * offset + 4 * self.FONTSIZE,
243             TapiNodeODu: 5 * offset,
244             TapiNodeFronthaulGateway: 6 * offset,
245             TapiNodeORu: 7 * offset,
246             TapiNodeUserEquipment: 8 * offset
247         }
248         if node_type in y_mapping:
249             return y_mapping[node_type]
250         return 0
251
252     # methods
253     def add_node(self, node: TapiNode):
254         """
255         Method adding a TAPI node to TAPI Topology.
256         :return TAPI Topology object.
257         """
258         self.__data["node"].append(node)
259         return self
260
261     def add_link(self, link: TapiLink):
262         """
263         Method adding a TAPI node to TAPI Topology.
264         :return TAPI Topology object.
265         """
266         self.__data["link"].append(link)
267         return self
268
269     def find_node(self, type_name:str, client_local_id:str) -> TapiNode:
270         """
271         Method finding the parent no in the hierarchy based on the node type name
272         and the client local identifier.
273         :param type_name: the TAPI NODE Type name of interest.
274         :param client_local_id: which starts with the parent node local id.
275         :re
276         turn TAPI Node object.
277         """
278         for node in self.__data["node"]:
279             if type(node).__name__ == type_name:
280                 if client_local_id.startswith(node.local_id()):
281                     return node
282         return # ERROR 404
283
284     def __create_smos(self, parent: TapiNode, topology_structure: dict, count: int):
285         """
286         Method adding a TAPI node to TAPI Topology.
287         :param parent: A TAPI node which acts a a parent node in the topology.
288         :param topology_structure: Information about the next topology levels.
289         :param count: Number of instance to be created
290         :return TAPI Topology object.
291         """
292         current_type = "smo"
293         next_type = "near-rt-ric"
294         for local_id in range(count):
295             prefix = ""
296
297             if parent is not None:
298                 prefix = parent.data()["name"][1]["value"]
299             config = {"node": {"localId": prefix + str(local_id),
300                                "type": current_type,
301                                "function": "o-ran-sc-topology-common:"+current_type}}
302             node = TapiNodeSmo(parent, config)
303             self.add_node(node)
304
305             # add O-Clouds
306             if "o-cloud" in topology_structure:
307                 structure = topology_structure.copy()
308                 self.__create_o_clouds(
309                     node, structure, structure["o-cloud"])
310
311             if next_type in topology_structure:
312                 structure = topology_structure.copy()
313                 if current_type in structure:
314                     del structure["o-cloud"]
315                 if current_type in structure:
316                     del structure[current_type]
317                 self.__create_near_rt_rics(
318                     node, structure, structure[next_type])
319
320         return self
321
322     def __create_o_clouds(self, parent: TapiNode, topology_structure: dict, count: int):
323         """
324         Method adding a TAPI node to TAPI Topology.
325         :param parent: A TAPI node which acts a a parent node in the topology.
326         :param topology_structure: Information about the next topology levels.
327         :param count: Number of instance to be created
328         :return TAPI Topology object.
329         """
330         current_type = "o-cloud"
331         for local_id in range(count):
332             # add node
333             prefix = ""
334             if parent is not None:
335                 prefix = parent.json()["name"][1]["value"]
336             function = "o-ran-sc-topology-common:"+current_type
337             node_configuration = {"node": {"localId": prefix + str(local_id),
338                                            "type": current_type,
339                                            "function": function}}
340             node = TapiNodeOCloud(parent, node_configuration)
341             self.add_node(node)
342
343             # add links
344             # O2
345             link_configuration = {
346                 "topology_reference": self.data()["uuid"],
347                 "name_prefix": "o2-rest",
348                 "provider": node,
349                 "consumer": parent
350             }
351             self.add_link(TapiLink(link_configuration))
352         return self
353
354     def __create_near_rt_rics(self, parent: TapiNode, topology_structure: dict, count: int):
355         """
356         Method adding a TAPI node to TAPI Topology.
357         :param parent: A TAPI node which acts a a parent node in the topology.
358         :param topology_structure: Information about the next topology levels.
359         :param count: Number of instance to be created
360         :return TAPI Topology object.
361         """
362         current_type = "near-rt-ric"
363         next_type = "o-cu"
364         for local_id in range(count):
365             # add node
366             prefix = ""
367             if parent is not None:
368                 prefix = parent.json()["name"][1]["value"]
369
370             # hardcoded rule!
371             # create 1 5G-Core AMF and 1 5G-Core UPF instance per Near-RT-RIC
372
373             # 5G-Core AMF
374             current_type = "access-and-mobility-management-function"
375             function = "o-ran-sc-topology-common:"+current_type
376             node_configuration = {"node": {"localId": prefix + str(local_id),
377                                            "type": current_type,
378                                            "function": function}}
379             node = TapiNodeAmf(parent, node_configuration)
380             self.add_node(node)
381
382             # 5G-Core UPF
383             current_type = "user-plane-function"
384             function = "o-ran-sc-topology-common:"+current_type
385             node_configuration = {"node": {"localId": prefix + str(local_id),
386                                            "type": current_type,
387                                            "function": function}}
388             node = TapiNodeUpf(parent, node_configuration)
389             self.add_node(node)
390
391             # Near-RT-RIC
392             current_type = "near-rt-ric"
393             function = "o-ran-sc-topology-common:"+current_type
394             node_configuration = {"node": {"localId": prefix + str(local_id),
395                                            "type": current_type,
396                                            "function": function}}
397             node = TapiNodeNearRtRic(parent, node_configuration)
398             self.add_node(node)
399
400             # add links
401             # A1
402             link_configuration = {
403                 "topology_reference": self.data()["uuid"],
404                 "name_prefix": "a1-rest",
405                 "provider": node,
406                 "consumer": parent
407             }
408             self.add_link(TapiLink(link_configuration))
409
410             # O1 NETCONF
411             link_configuration = {
412                 "topology_reference": self.data()["uuid"],
413                 "name_prefix": "o1-netconf",
414                 "provider": node,
415                 "consumer": parent
416             }
417             self.add_link(TapiLink(link_configuration))
418
419             # O1 FILE
420             link_configuration = {
421                 "topology_reference": self.data()["uuid"],
422                 "name_prefix": "o1-file",
423                 "provider": node,
424                 "consumer": parent
425             }
426             self.add_link(TapiLink(link_configuration))
427
428             # O1 VES
429             link_configuration = {
430                 "topology_reference": self.data()["uuid"],
431                 "name_prefix": "o1-ves",
432                 "provider": parent,
433                 "consumer": node
434             }
435             self.add_link(TapiLink(link_configuration))
436
437             # continue
438             if next_type in topology_structure:
439                 structure = topology_structure.copy()
440                 if current_type in structure:
441                     del structure[current_type]
442                 self.__create_o_cus(node, structure, structure[next_type])
443
444         return self
445
446     def __function_identity(self, function_type: str, plane: str) -> str:
447         """
448         Method to calculate the Function IDENTITY
449         """
450         return "".join([
451             "o-ran-sc-topology-common:",
452             function_type,
453             "-",
454             plane
455         ])
456
457     def __create_o_cus(self, parent: TapiNode, topology_structure: dict, count: int):
458         """
459         Method adding a TAPI node to TAPI Topology.
460         :param parent: A TAPI node which acts a a parent node in the topology.
461         :param topology_structure: Information about the next topology levels.
462         :param count: Number of instance to be created
463         :return TAPI Topology object.
464         """
465         current_type = "o-cu"
466         next_type = "o-du"
467         for local_id in range(count):
468             prefix = ""
469             if parent is not None:
470                 prefix = parent.data()["name"][1]["value"]
471
472             node: Dict[str, Union[TapiNodeOCuCp, TapiNodeOCuUp]] = {}
473             for plane in ["cp", "up"]:
474                 config = {"node": {"localId": prefix + str(local_id),
475                                    "type": "-".join([current_type, plane]),
476                                    "function": self.__function_identity(current_type, plane)}}
477                 classes: Dict[str, Union[TapiNodeOCuCp, TapiNodeOCuUp]] = {
478                     "cp": TapiNodeOCuCp,
479                     "up": TapiNodeOCuUp}
480                 node[plane] = classes[plane](parent, config)
481                 self.add_node(node[plane])
482
483                 # add links
484                 # E2
485                 link_configuration = {
486                     "topology_reference": self.data()["uuid"],
487                     "name_prefix": "e2-rest",
488                     "provider": node[plane],
489                     "consumer": parent
490                 }
491                 self.add_link(TapiLink(link_configuration))
492
493                 # O1 NETCONF
494                 link_configuration = {
495                     "topology_reference": self.data()["uuid"],
496                     "name_prefix": "o1-netconf",
497                     "provider": node[plane],
498                     "consumer": parent.parent()
499                 }
500                 self.add_link(TapiLink(link_configuration))
501
502                 # O1 FILE
503                 link_configuration = {
504                     "topology_reference": self.data()["uuid"],
505                     "name_prefix": "o1-file",
506                     "provider": node[plane],
507                     "consumer": parent.parent()
508                 }
509                 self.add_link(TapiLink(link_configuration))
510
511                 # O1 VES
512                 link_configuration = {
513                     "topology_reference": self.data()["uuid"],
514                     "name_prefix": "o1-ves",
515                     "provider": parent.parent(),
516                     "consumer": node[plane]
517                 }
518                 self.add_link(TapiLink(link_configuration))
519
520             # continue
521             # E1 Interface between O-CU-UP and O-CU-CP
522             link_configuration = {
523                 "topology_reference": self.data()["uuid"],
524                 "name_prefix": "e1-unknown",
525                 "provider": node["up"],
526                 "consumer": node["cp"]
527             }
528             self.add_link(TapiLink(link_configuration))
529
530             # N2 Interface between O-CU-CP and AMF
531             link_configuration = {
532                 "topology_reference": self.data()["uuid"],
533                 "name_prefix": "n2-nas",
534                 "provider": self.find_node("TapiNodeAmf", node["cp"].local_id()),
535                 "consumer": node["cp"]
536             }
537             self.add_link(TapiLink(link_configuration))
538
539             # N3 Interface between O-CU-UP and UPF
540             link_configuration = {
541                 "topology_reference": self.data()["uuid"],
542                 "name_prefix": "n3-nas",
543                 "provider": self.find_node("TapiNodeUpf", node["up"].local_id()),
544                 "consumer": node["up"]
545             }
546             self.add_link(TapiLink(link_configuration))
547
548             if next_type in topology_structure:
549                 structure = topology_structure.copy()
550                 if current_type in structure:
551                     del structure[current_type]
552                 self.__create_o_dus(
553                     node, structure, structure[next_type])
554         return self
555
556     def __create_o_dus(self, parents: Dict[str, TapiNode], topology_structure: dict, count: int):
557         """
558         Method adding a TAPI node to TAPI Topology.
559         :param parent: A TAPI node which acts a a parent node in the topology.
560         :param topology_structure: Information about the next topology levels.
561         :param count: Number of instance to be created
562         :return TAPI Topology object.
563         """
564         current_type = "o-du"
565         next_type = "fronthaul-gateway"
566         for local_id in range(count):
567             prefix = "000"
568             if parents["cp"] is not None:
569                 prefix = parents["cp"].data()["name"][1]["value"]
570             config = {"node": {"localId": prefix + str(local_id),
571                                "type": current_type,
572                                "function": "o-ran-sc-topology-common:"+current_type}}
573             node = TapiNodeODu(parents["cp"], config)
574             self.add_node(node)
575
576             for plane, parent in parents.items():
577
578                 # add links
579                 # E2
580                 link_configuration = {
581                     "topology_reference": self.data()["uuid"],
582                     "name_prefix": "e2-rest",
583                     "provider": node,
584                     "consumer": parent.parent()
585                 }
586                 self.add_link(TapiLink(link_configuration))
587
588                 # O1 NETCONF
589                 link_configuration = {
590                     "topology_reference": self.data()["uuid"],
591                     "name_prefix": "o1-netconf",
592                     "provider": node,
593                     "consumer": parent.parent().parent()
594                 }
595                 self.add_link(TapiLink(link_configuration))
596
597                 # O1 FILE
598                 link_configuration = {
599                     "topology_reference": self.data()["uuid"],
600                     "name_prefix": "o1-file",
601                     "provider": node,
602                     "consumer": parent.parent().parent()
603                 }
604                 self.add_link(TapiLink(link_configuration))
605
606                 # O1 VES
607                 link_configuration = {
608                     "topology_reference": self.data()["uuid"],
609                     "name_prefix": "o1-ves",
610                     "provider": parent.parent().parent(),
611                     "consumer": node
612                 }
613                 self.add_link(TapiLink(link_configuration))
614
615                 # F1 User Plane or Control Plane
616                 interfaces: Dict[str, str] = {"cp": "f1-c", "up": "f1-u"}
617                 link_configuration = {
618                     "topology_reference": self.data()["uuid"],
619                     "name_prefix": interfaces[plane]+"-unknown",
620                     "provider": node,
621                     "consumer": parent
622                 }
623                 self.add_link(TapiLink(link_configuration))
624
625             # continue
626             if next_type in topology_structure:
627                 structure = topology_structure.copy()
628                 if current_type in structure:
629                     del structure[current_type]
630                 self.__create_fronthaul_gateways(
631                     node, structure, structure[next_type])
632         return self
633
634     def __create_fronthaul_gateways(self, parent: TapiNode, topology_structure: dict, count: int):
635         """
636         Method adding a TAPI node to TAPI Topology.
637         :param parent: A TAPI node which acts a a parent node in the topology.
638         :param topology_structure: Information about the next topology levels.
639         :param count: Number of instance to be created
640         :return TAPI Topology object.
641         """
642         current_type = "fronthaul-gateway"
643         next_type = "o-ru"
644         for local_id in range(count):
645             prefix = ""
646             if parent is not None:
647                 prefix = parent.data()["name"][1]["value"]
648             node_configuration = {
649                 "node": {
650                     "localId": prefix + str(local_id),
651                     "type": current_type,
652                     "function": "o-ran-sc-topology-common:"+current_type,
653                     "southbound-nep-count": topology_structure[next_type]
654                 }
655             }
656             node = TapiNodeFronthaulGateway(parent, node_configuration)
657             self.add_node(node)
658
659             # add links
660
661             # Eth NBI
662             link_configuration = {
663                 "topology_reference": self.data()["uuid"],
664                 "name_prefix": "oam-netconf",
665                 "provider": node,
666                 "consumer": parent.parent().parent().parent()
667             }
668             self.add_link(TapiLink(link_configuration))
669
670             # Eth SBI
671             link_configuration = {
672                 "topology_reference": self.data()["uuid"],
673                 "name_prefix": "eth-ofh",
674                 "provider": node,
675                 "consumer": parent
676             }
677             self.add_link(TapiLink(link_configuration))
678
679             # continue
680             if next_type in topology_structure:
681                 structure = topology_structure.copy()
682                 if current_type in structure:
683                     del structure[current_type]
684                 self.__create_o_rus(node, structure, structure[next_type])
685         return self
686
687     def __create_o_rus(self, parent: TapiNode, topology_structure: dict, count: int):
688         """
689         Method adding a TAPI node to TAPI Topology.
690         :param parent: A TAPI node which acts a a parent node in the topology.
691         :param topology_structure: Information about the next topology levels.
692         :param count: Number of instance to be created
693         :return TAPI Topology object.
694         """
695         current_type = "o-ru"
696         next_type = "user-equipment"
697         for local_id in range(count):
698             prefix = ""
699             if parent is not None:
700                 prefix = parent.data()["name"][1]["value"]
701             config = {"node": {"localId": prefix + str(local_id),
702                                "type": current_type,
703                                "function": "o-ran-sc-topology-common:"+current_type}}
704             node = TapiNodeORu(parent, config)
705             self.add_node(node)
706
707             # add links
708
709             # O1 NETCONF
710             link_configuration = {
711                 "topology_reference": self.data()["uuid"],
712                 "name_prefix": "ofh-netconf",
713                 "provider": node,
714                 "consumer": parent.parent().parent().parent().parent()
715             }
716             self.add_link(TapiLink(link_configuration))
717
718             # OFH M-Plane to O-DU via fronthaul-gateway
719             link_configuration = {
720                 "topology_reference": self.data()["uuid"],
721                 "name_prefix": "ofh-netconf",
722                 "provider": node,
723                 "consumer": parent
724             }
725             self.add_link(TapiLink(link_configuration))
726
727             # continue
728             if next_type in topology_structure:
729                 structure = topology_structure.copy()
730                 if current_type in structure:
731                     del structure[current_type]
732                 self.__create_ues(node, structure, structure[next_type])
733         return self
734
735     def __create_ues(self, parent: TapiNode, topology_structure: dict, count: int):
736         """
737         Method adding a TAPI node to TAPI Topology.
738         :param parent: A TAPI node which acts a a parent node in the topology.
739         :param topology_structure: Information about the next topology levels.
740         :param count: Number of instance to be created
741         :return TAPI Topology object.
742         """
743         current_type = "user-equipment"
744         for local_id in range(count):
745             prefix = ""
746             if parent is not None:
747                 prefix = parent.data()["name"][1]["value"]
748             config = {"node": {"localId": prefix + str(local_id),
749                                "type": current_type,
750                                "function": "o-ran-sc-topology-common:"+current_type}}
751             node = TapiNodeUserEquipment(parent, config)
752             self.add_node(node)
753
754             # add links
755             # Uu radio
756             link_configuration = {
757                 "topology_reference": self.data()["uuid"],
758                 "name_prefix": "uu-radio",
759                 "provider": parent,
760                 "consumer": node
761             }
762             self.add_link(TapiLink(link_configuration))
763
764             # N! to 5G-Core AMF
765             link_configuration = {
766                 "topology_reference": self.data()["uuid"],
767                 "name_prefix": "n1-nas",
768                 "provider": self.find_node("TapiNodeAmf", node.local_id()),
769                 "consumer": node
770             }
771             self.add_link(TapiLink(link_configuration))
772
773             if "key" in topology_structure:
774                 print("Implement missing topology level.")
775
776         return self