feat(telemetry): Rework
[csit.git] / resources / libraries / python / topology.py
1 # Copyright (c) 2022 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Defines nodes and topology structure."""
15
16 import re
17
18 from collections import Counter
19
20 from yaml import safe_load
21
22 from robot.api import logger
23 from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError
24
25 from resources.libraries.python.Constants import Constants
26
27 __all__ = [
28     u"DICT__nodes", u"Topology", u"NodeType", u"SocketType", u"NodeSubTypeTG"
29 ]
30
31
32 def load_topo_from_yaml():
33     """Load topology from file defined in "${TOPOLOGY_PATH}" variable.
34
35     :returns: Nodes from loaded topology.
36     """
37     try:
38         topo_path = BuiltIn().get_variable_value(u"${TOPOLOGY_PATH}")
39     except RobotNotRunningError:
40         return ''
41
42     with open(topo_path) as work_file:
43         return safe_load(work_file.read())[u"nodes"]
44
45
46 class NodeType:
47     """Defines node types used in topology dictionaries."""
48     # Device Under Test (this node has VPP running on it)
49     DUT = u"DUT"
50     # Traffic Generator (this node has traffic generator on it)
51     # pylint: disable=invalid-name
52     TG = u"TG"
53     # Virtual Machine (this node running on DUT node)
54     # pylint: disable=invalid-name
55     VM = u"VM"
56
57
58 class NodeSubTypeTG:
59     """Defines node sub-type TG - traffic generator."""
60     # T-Rex traffic generator
61     TREX = u"TREX"
62     # Moongen
63     MOONGEN = u"MOONGEN"
64     # IxNetwork
65     IXNET = u"IXNET"
66
67
68 class SocketType:
69     """Defines socket types used in topology dictionaries."""
70     # VPP Socket PAPI
71     PAPI = u"PAPI"
72     # VPP PAPI Stats (legacy option until stats are migrated to Socket PAPI)
73     STATS = u"STATS"
74     # VPP Socket CLI
75     CLI = u"CLI"
76
77
78 DICT__nodes = load_topo_from_yaml()
79
80
81 class Topology:
82     """Topology data manipulation and extraction methods.
83
84     Defines methods used for manipulation and extraction of data from
85     the active topology.
86
87     "Active topology" contains initially data from the topology file and can be
88     extended with additional data from the DUTs like internal interface indexes
89     or names. Additional data which can be filled to the active topology are
90
91         - additional internal representation (index, name, ...)
92         - operational data (dynamic ports)
93
94     To access the port data it is recommended to use a port key because the key
95     does not rely on the data retrieved from nodes, this allows to call most of
96     the methods without having filled active topology with internal nodes data.
97     """
98     @staticmethod
99     def add_node_item(node, value, path):
100         """Add item to topology node.
101
102         :param node: Topology node.
103         :param value: Value to insert.
104         :param path: Path where to insert item.
105         :type node: dict
106         :type value: str
107         :type path: list
108         """
109         if len(path) == 1:
110             node[path[0]] = value
111             return
112         if path[0] not in node:
113             node[path[0]] = dict()
114         elif isinstance(node[path[0]], str):
115             node[path[0]] = dict() if node[path[0]] == u"" \
116                 else {node[path[0]]: u""}
117         Topology.add_node_item(node[path[0]], value, path[1:])
118
119     @staticmethod
120     def add_new_port(node, ptype):
121         """Add new port to the node to active topology.
122
123         :param node: Node to add new port on.
124         :param ptype: Port type, used as key prefix.
125         :type node: dict
126         :type ptype: str
127         :returns: Port key or None
128         :rtype: string or None
129         """
130         max_ports = 1000000
131         iface = None
132         for i in range(1, max_ports):
133             if node[u"interfaces"].get(str(ptype) + str(i)) is None:
134                 iface = str(ptype) + str(i)
135                 node[u"interfaces"][iface] = dict()
136                 break
137         return iface
138
139     @staticmethod
140     def remove_port(node, iface_key):
141         """Remove required port from active topology.
142
143         :param node: Node to remove port on.
144         :param: iface_key: Topology key of the interface.
145         :type node: dict
146         :type iface_key: str
147         :returns: Nothing
148         """
149         try:
150             node[u"interfaces"].pop(iface_key)
151         except KeyError:
152             pass
153
154     @staticmethod
155     def remove_all_ports(node, ptype):
156         """Remove all ports with ptype as prefix.
157
158         :param node: Node to remove ports on.
159         :param: ptype: Port type, used as key prefix.
160         :type node: dict
161         :type ptype: str
162         :returns: Nothing
163         """
164         for if_key in list(node[u"interfaces"]):
165             if if_key.startswith(str(ptype)):
166                 node[u"interfaces"].pop(if_key)
167
168     @staticmethod
169     def remove_all_added_ports_on_all_duts_from_topology(nodes):
170         """Remove all added ports on all DUT nodes in the topology.
171
172         :param nodes: Nodes in the topology.
173         :type nodes: dict
174         :returns: Nothing
175         """
176         port_types = (
177             u"subinterface", u"vlan_subif", u"memif", u"tap", u"vhost",
178             u"loopback", u"gre_tunnel", u"vxlan_tunnel", u"eth_bond",
179             u"eth_avf", u"eth_rdma", u"geneve_tunnel", u"eth_af_xdp",
180             u"gtpu_tunnel"
181         )
182
183         for node_data in nodes.values():
184             if node_data[u"type"] == NodeType.DUT:
185                 for ptype in port_types:
186                     Topology.remove_all_ports(node_data, ptype)
187
188     @staticmethod
189     def remove_all_vif_ports(node):
190         """Remove all Virtual Interfaces on DUT node.
191
192         :param node: Node to remove VIF ports on.
193         :type node: dict
194         :returns: Nothing
195         """
196         reg_ex = re.compile(r"port\d+_vif\d+")
197         for if_key in list(node[u"interfaces"]):
198             if re.match(reg_ex, if_key):
199                 node[u"interfaces"].pop(if_key)
200
201     @staticmethod
202     def remove_all_added_vif_ports_on_all_duts_from_topology(nodes):
203         """Remove all added Virtual Interfaces on all DUT nodes in
204         the topology.
205
206         :param nodes: Nodes in the topology.
207         :type nodes: dict
208         :returns: Nothing
209         """
210         for node_data in nodes.values():
211             if node_data[u"type"] == NodeType.DUT:
212                 Topology.remove_all_vif_ports(node_data)
213
214     @staticmethod
215     def update_interface_sw_if_index(node, iface_key, sw_if_index):
216         """Update sw_if_index on the interface from the node.
217
218         :param node: Node to update sw_if_index on.
219         :param iface_key: Topology key of the interface.
220         :param sw_if_index: Internal index to store.
221         :type node: dict
222         :type iface_key: str
223         :type sw_if_index: int
224         """
225         node[u"interfaces"][iface_key][u"vpp_sw_index"] = int(sw_if_index)
226
227     @staticmethod
228     def update_interface_name(node, iface_key, name):
229         """Update name on the interface from the node.
230
231         :param node: Node to update name on.
232         :param iface_key: Topology key of the interface.
233         :param name: Interface name to store.
234         :type node: dict
235         :type iface_key: str
236         :type name: str
237         """
238         node[u"interfaces"][iface_key][u"name"] = str(name)
239
240     @staticmethod
241     def update_interface_mac_address(node, iface_key, mac_address):
242         """Update mac_address on the interface from the node.
243
244         :param node: Node to update MAC on.
245         :param iface_key: Topology key of the interface.
246         :param mac_address: MAC address.
247         :type node: dict
248         :type iface_key: str
249         :type mac_address: str
250         """
251         node[u"interfaces"][iface_key][u"mac_address"] = str(mac_address)
252
253     @staticmethod
254     def update_interface_pci_address(node, iface_key, pci_address):
255         """Update pci_address on the interface from the node.
256
257         :param node: Node to update PCI on.
258         :param iface_key: Topology key of the interface.
259         :param pci_address: PCI address.
260         :type node: dict
261         :type iface_key: str
262         :type pci_address: str
263         """
264         node[u"interfaces"][iface_key][u"pci_address"] = str(pci_address)
265
266     @staticmethod
267     def update_interface_vlan(node, iface_key, vlan):
268         """Update VLAN on the interface from the node.
269
270         :param node: Node to update VLAN on.
271         :param iface_key: Topology key of the interface.
272         :param vlan: VLAN ID.
273         :type node: dict
274         :type iface_key: str
275         :type vlan: str
276         """
277         node[u"interfaces"][iface_key][u"vlan"] = int(vlan)
278
279     @staticmethod
280     def update_interface_vhost_socket(node, iface_key, vhost_socket):
281         """Update vhost socket name on the interface from the node.
282
283         :param node: Node to update socket name on.
284         :param iface_key: Topology key of the interface.
285         :param vhost_socket: Path to named socket on node.
286         :type node: dict
287         :type iface_key: str
288         :type vhost_socket: str
289         """
290         node[u"interfaces"][iface_key][u"vhost_socket"] = str(vhost_socket)
291
292     @staticmethod
293     def update_interface_memif_socket(node, iface_key, memif_socket):
294         """Update memif socket name on the interface from the node.
295
296         :param node: Node to update socket name on.
297         :param iface_key: Topology key of the interface.
298         :param memif_socket: Path to named socket on node.
299         :type node: dict
300         :type iface_key: str
301         :type memif_socket: str
302         """
303         node[u"interfaces"][iface_key][u"memif_socket"] = str(memif_socket)
304
305     @staticmethod
306     def update_interface_memif_id(node, iface_key, memif_id):
307         """Update memif ID on the interface from the node.
308
309         :param node: Node to update memif ID on.
310         :param iface_key: Topology key of the interface.
311         :param memif_id: Memif interface ID.
312         :type node: dict
313         :type iface_key: str
314         :type memif_id: str
315         """
316         node[u"interfaces"][iface_key][u"memif_id"] = str(memif_id)
317
318     @staticmethod
319     def update_interface_memif_role(node, iface_key, memif_role):
320         """Update memif role on the interface from the node.
321
322         :param node: Node to update memif role on.
323         :param iface_key: Topology key of the interface.
324         :param memif_role: Memif role.
325         :type node: dict
326         :type iface_key: str
327         :type memif_role: str
328         """
329         node[u"interfaces"][iface_key][u"memif_role"] = str(memif_role)
330
331     @staticmethod
332     def update_interface_tap_dev_name(node, iface_key, dev_name):
333         """Update device name on the tap interface from the node.
334
335         :param node: Node to update tap device name on.
336         :param iface_key: Topology key of the interface.
337         :param dev_name: Device name of the tap interface.
338         :type node: dict
339         :type iface_key: str
340         :type dev_name: str
341         :returns: Nothing
342         """
343         node[u"interfaces"][iface_key][u"dev_name"] = str(dev_name)
344
345     @staticmethod
346     def get_node_by_hostname(nodes, hostname):
347         """Get node from nodes of the topology by hostname.
348
349         :param nodes: Nodes of the test topology.
350         :param hostname: Host name.
351         :type nodes: dict
352         :type hostname: str
353         :returns: Node dictionary or None if not found.
354         """
355         for node in nodes.values():
356             if node[u"host"] == hostname:
357                 return node
358
359         return None
360
361     @staticmethod
362     def get_links(nodes):
363         """Get list of links(networks) in the topology.
364
365         :param nodes: Nodes of the test topology.
366         :type nodes: dict
367         :returns: Links in the topology.
368         :rtype: list
369         """
370         links = list()
371
372         for node in nodes.values():
373             for interface in node[u"interfaces"].values():
374                 link = interface.get(u"link")
375                 if link is not None:
376                     if link not in links:
377                         links.append(link)
378
379         return links
380
381     @staticmethod
382     def _get_interface_by_key_value(node, key, value, subsequent=False):
383         """Return node interface key from topology file
384         according to key and value.
385
386         :param node: The node dictionary.
387         :param key: Key by which to select the interface.
388         :param value: Value that should be found using the key.
389         :param subsequent: Use second interface of the link. Useful for
390             back-to-back links. Default: False
391         :type node: dict
392         :type key: string
393         :type value: string
394         :type subsequent: bool
395         :returns: Interface key from topology file
396         :rtype: string
397         """
398         interfaces = node[u"interfaces"]
399         retval = None
400         for if_key, if_val in interfaces.items():
401             k_val = if_val.get(key)
402             if k_val is not None:
403                 if k_val == value:
404                     if subsequent:
405                         subsequent = False
406                     else:
407                         retval = if_key
408                         break
409         return retval
410
411     @staticmethod
412     def get_interface_by_name(node, iface_name):
413         """Return interface key based on name from DUT/TG.
414
415         This method returns interface key based on interface name
416         retrieved from the DUT, or TG.
417
418         :param node: The node topology dictionary.
419         :param iface_name: Interface name (string form).
420         :type node: dict
421         :type iface_name: string
422         :returns: Interface key.
423         :rtype: str
424         """
425         return Topology._get_interface_by_key_value(node, u"name", iface_name)
426
427     @staticmethod
428     def get_interface_by_link_name(node, link_name, subsequent=False):
429         """Return interface key of link on node.
430
431         This method returns the interface name associated with a given link
432         for a given node.
433
434         :param node: The node topology dictionary.
435         :param link_name: Name of the link that a interface is connected to.
436         :param subsequent: Use second interface of the link. Useful for
437             back-to-back links. Default: False
438         :type node: dict
439         :type link_name: string
440         :type subsequent: bool
441         :returns: Interface key of the interface connected to the given link.
442         :rtype: str
443         """
444         return Topology._get_interface_by_key_value(
445             node, u"link", link_name, subsequent=subsequent
446         )
447
448     def get_interfaces_by_link_names(self, node, link_names):
449         """Return dictionary of dictionaries {"interfaceN", interface name}.
450
451         This method returns the interface names associated with given links
452         for a given node.
453
454         :param node: The node topology directory.
455         :param link_names: List of names of the link that a interface is
456             connected to.
457         :type node: dict
458         :type link_names: list
459         :returns: Dictionary of interface names that are connected to the given
460             links.
461         :rtype: dict
462         """
463         retval = dict()
464         interface_number = 1
465         for link_name in link_names:
466             interface = self.get_interface_by_link_name(node, link_name)
467             retval[f"interface{str(interface_number)}"] = \
468                 self.get_interface_name(node, interface)
469             interface_number += 1
470         return retval
471
472     @staticmethod
473     def get_interface_by_sw_index(node, sw_if_index):
474         """Return interface name of link on node.
475
476         This method returns the interface name associated with a software
477         interface index assigned to the interface by vpp for a given node.
478
479         :param node: The node topology dictionary.
480         :param sw_if_index: sw_if_index of the link that a interface is
481             connected to.
482         :type node: dict
483         :type sw_if_index: int
484         :returns: Interface name of the interface connected to the given link.
485         :rtype: str
486         """
487         return Topology._get_interface_by_key_value(
488             node, u"vpp_sw_index", sw_if_index
489         )
490
491     @staticmethod
492     def get_interface_sw_index(node, iface_key):
493         """Get VPP sw_if_index for the interface using interface key.
494
495         :param node: Node to get interface sw_if_index on.
496         :param iface_key: Interface key from topology file, or sw_if_index.
497         :type node: dict
498         :type iface_key: str/int
499         :returns: Return sw_if_index or None if not found.
500         :rtype: int or None
501         """
502         try:
503             if isinstance(iface_key, str):
504                 return node[u"interfaces"][iface_key].get(u"vpp_sw_index")
505             # TODO: use only iface_key, do not use integer
506             return int(iface_key)
507         except (KeyError, ValueError):
508             return None
509
510     @staticmethod
511     def get_interface_sw_index_by_name(node, iface_name):
512         """Get VPP sw_if_index for the interface using interface name.
513
514         :param node: Node to get interface sw_if_index on.
515         :param iface_name: Interface name.
516         :type node: dict
517         :type iface_name: str
518         :returns: Return sw_if_index or None if not found.
519         :raises TypeError: If provided interface name is not a string.
520         """
521         try:
522             if not isinstance(iface_name, str):
523                 raise TypeError(u"Interface name must be a string.")
524             iface_key = Topology.get_interface_by_name(node, iface_name)
525             return node[u"interfaces"][iface_key].get(u"vpp_sw_index")
526         except (KeyError, ValueError):
527             return None
528
529     @staticmethod
530     def get_interface_mtu(node, iface_key):
531         """Get interface MTU.
532
533         Returns physical layer MTU (max. size of Ethernet frame).
534         :param node: Node to get interface MTU on.
535         :param iface_key: Interface key from topology file.
536         :type node: dict
537         :type iface_key: str
538         :returns: MTU or None if not found.
539         :rtype: int
540         """
541         try:
542             return node[u"interfaces"][iface_key].get(u"mtu")
543         except KeyError:
544             return None
545
546     @staticmethod
547     def get_interface_name(node, iface_key):
548         """Get interface name (retrieved from DUT/TG).
549
550         Returns name in string format, retrieved from the node.
551         :param node: Node to get interface name on.
552         :param iface_key: Interface key from topology file.
553         :type node: dict
554         :type iface_key: str
555         :returns: Interface name or None if not found.
556         :rtype: str
557         """
558         try:
559             return node[u"interfaces"][iface_key].get(u"name")
560         except KeyError:
561             return None
562
563     @staticmethod
564     def convert_interface_reference_to_key(node, interface):
565         """Takes interface reference in any format
566         (name, link name, interface key or sw_if_index)
567         and converts to interface key using Topology methods.
568
569         :param node: Node in topology.
570         :param interface: Name, sw_if_index, link name or key of an interface
571             on the node.
572         :type node: dict
573         :type interface: str or int
574
575         :returns: Interface key.
576         :rtype: str
577
578         :raises TypeError: If provided with invalid interface argument.
579         :raises RuntimeError: If the interface does not exist in topology.
580         """
581
582         if isinstance(interface, int):
583             key = Topology.get_interface_by_sw_index(node, interface)
584             if key is None:
585                 raise RuntimeError(
586                     f"Interface with sw_if_index={interface} does not exist "
587                     f"in topology."
588                 )
589         elif interface in Topology.get_node_interfaces(node):
590             key = interface
591         elif interface in Topology.get_links({u"dut": node}):
592             key = Topology.get_interface_by_link_name(node, interface)
593         elif isinstance(interface, str):
594             key = Topology.get_interface_by_name(node, interface)
595             if key is None:
596                 raise RuntimeError(
597                     f"Interface with key, name or link name \"{interface}\" "
598                     f"does not exist in topology."
599                 )
600         else:
601             raise TypeError(
602                 u"Type of interface argument must be integer or string."
603             )
604         return key
605
606     @staticmethod
607     def convert_interface_reference(node, interface, wanted_format):
608         """Takes interface reference in any format
609         (name, link name, topology key or sw_if_index) and returns
610         its equivalent in the desired format.
611
612         :param node: Node in topology.
613         :param interface: Name, sw_if_index, link name or key of an interface
614             on the node.
615         :param wanted_format: Format of return value wanted.
616             Valid options are: sw_if_index, key, name.
617         :type node: dict
618         :type interface: str or int
619         :type wanted_format: str
620         :returns: Interface name, interface key or sw_if_index.
621         :rtype: str or int
622         :raises TypeError, ValueError: If provided with invalid arguments.
623         :raises RuntimeError: If the interface does not exist in topology.
624         """
625
626         key = Topology.convert_interface_reference_to_key(node, interface)
627
628         conversions = {
629             u"key": lambda x, y: y,
630             u"name": Topology.get_interface_name,
631             u"sw_if_index": Topology.get_interface_sw_index
632         }
633
634         try:
635             return conversions[wanted_format](node, key)
636         except KeyError:
637             raise ValueError(
638                 f"Unrecognized return value wanted: {wanted_format}."
639                 f"Valid options are key, name, sw_if_index"
640             )
641
642     @staticmethod
643     def get_interface_numa_node(node, iface_key):
644         """Get interface numa node.
645
646         Returns physical relation to numa node, numa_id.
647
648         :param node: Node to get numa id on.
649         :param iface_key: Interface key from topology file.
650         :type node: dict
651         :type iface_key: str
652         :returns: numa node id, None if not available.
653         :rtype: int
654         """
655         try:
656             return node[u"interfaces"][iface_key].get(u"numa_node")
657         except KeyError:
658             return None
659
660     @staticmethod
661     def get_interfaces_numa_node(node, *iface_keys):
662         """Get numa node on which are located most of the interfaces.
663
664         Return numa node with highest count of interfaces provided as arguments.
665         Return 0 if the interface does not have numa_node information available.
666         If all interfaces have unknown location (-1), then return 0.
667         If most of interfaces have unknown location (-1), but there are
668         some interfaces with known location, then return the second most
669         location of the provided interfaces.
670
671         :param node: Node from DICT__nodes.
672         :param iface_keys: Interface keys for lookup.
673         :type node: dict
674         :type iface_keys: strings
675         :returns: Numa node of most given interfaces or 0.
676         :rtype: int
677         """
678         numa_list = []
679         for if_key in iface_keys:
680             try:
681                 numa_list.append(node[u"interfaces"][if_key].get(u"numa_node"))
682             except KeyError:
683                 pass
684
685         numa_cnt_mc = Counter(numa_list).most_common()
686
687         if numa_cnt_mc and numa_cnt_mc[0][0] != -1:
688             return numa_cnt_mc[0][0]
689         if len(numa_cnt_mc) > 1 and numa_cnt_mc[0][0] == -1:
690             return numa_cnt_mc[1][0]
691         return 0
692
693     @staticmethod
694     def get_interface_mac(node, iface_key):
695         """Get MAC address for the interface.
696
697         :param node: Node to get interface mac on.
698         :param iface_key: Interface key from topology file.
699         :type node: dict
700         :type iface_key: str
701         :returns: Return MAC or None if not found.
702         """
703         try:
704             return node[u"interfaces"][iface_key].get(u"mac_address")
705         except KeyError:
706             return None
707
708     @staticmethod
709     def get_interface_ip4(node, iface_key):
710         """Get IP4 address for the interface.
711
712         :param node: Node to get interface mac on.
713         :param iface_key: Interface key from topology file.
714         :type node: dict
715         :type iface_key: str
716         :returns: Return IP4 or None if not found.
717         """
718         try:
719             return node[u"interfaces"][iface_key].get(u"ip4_address")
720         except KeyError:
721             return None
722
723     @staticmethod
724     def get_interface_ip4_prefix_length(node, iface_key):
725         """Get IP4 address prefix length for the interface.
726
727         :param node: Node to get prefix length on.
728         :param iface_key: Interface key from topology file.
729         :type node: dict
730         :type iface_key: str
731         :returns: Prefix length from topology file or the default
732             IP4 prefix length if not found.
733         :rtype: int
734         :raises: KeyError if iface_key is not found.
735         """
736         return node[u"interfaces"][iface_key].get(u"ip4_prefix_length", \
737             Constants.DEFAULT_IP4_PREFIX_LENGTH)
738
739     @staticmethod
740     def get_adjacent_node_and_interface(nodes_info, node, iface_key):
741         """Get node and interface adjacent to specified interface
742         on local network.
743
744         :param nodes_info: Dictionary containing information on all nodes
745             in topology.
746         :param node: Node that contains specified interface.
747         :param iface_key: Interface key from topology file.
748         :type nodes_info: dict
749         :type node: dict
750         :type iface_key: str
751         :returns: Return (node, interface_key) tuple or None if not found.
752         :rtype: (dict, str)
753         """
754         link_name = None
755         # get link name where the interface belongs to
756         for if_key, if_val in node[u"interfaces"].items():
757             if if_key == u"mgmt":
758                 continue
759             if if_key == iface_key:
760                 link_name = if_val[u"link"]
761                 break
762
763         if link_name is None:
764             return None
765
766         # find link
767         for node_data in nodes_info.values():
768             # skip self
769             if node_data[u"host"] == node[u"host"]:
770                 continue
771             for if_key, if_val \
772                     in node_data[u"interfaces"].items():
773                 if u"link" not in if_val:
774                     continue
775                 if if_val[u"link"] == link_name:
776                     return node_data, if_key
777         return None
778
779     @staticmethod
780     def get_interface_pci_addr(node, iface_key):
781         """Get interface PCI address.
782
783         :param node: Node to get interface PCI address on.
784         :param iface_key: Interface key from topology file.
785         :type node: dict
786         :type iface_key: str
787         :returns: Return PCI address or None if not found.
788         """
789         try:
790             return node[u"interfaces"][iface_key].get(u"pci_address")
791         except KeyError:
792             return None
793
794     @staticmethod
795     def get_interface_driver(node, iface_key):
796         """Get interface driver.
797
798         :param node: Node to get interface driver on.
799         :param iface_key: Interface key from topology file.
800         :type node: dict
801         :type iface_key: str
802         :returns: Return interface driver or None if not found.
803         """
804         try:
805             return node[u"interfaces"][iface_key].get(u"driver")
806         except KeyError:
807             return None
808
809     @staticmethod
810     def get_interface_vlan(node, iface_key):
811         """Get interface vlan.
812
813         :param node: Node to get interface driver on.
814         :param iface_key: Interface key from topology file.
815         :type node: dict
816         :type iface_key: str
817         :returns: Return interface vlan or None if not found.
818         """
819         try:
820             return node[u"interfaces"][iface_key].get(u"vlan")
821         except KeyError:
822             return None
823
824     @staticmethod
825     def get_node_interfaces(node):
826         """Get all node interfaces.
827
828         :param node: Node to get list of interfaces from.
829         :type node: dict
830         :returns: Return list of keys of all interfaces.
831         :rtype: list
832         """
833         return node[u"interfaces"].keys()
834
835     @staticmethod
836     def get_node_link_mac(node, link_name):
837         """Return interface mac address by link name.
838
839         :param node: Node to get interface sw_if_index on.
840         :param link_name: Link name.
841         :type node: dict
842         :type link_name: str
843         :returns: MAC address string.
844         :rtype: str
845         """
846         for port in node[u"interfaces"].values():
847             if port.get(u"link") == link_name:
848                 return port.get(u"mac_address")
849         return None
850
851     @staticmethod
852     def _get_node_active_link_names(node, filter_list=None, topo_has_dut=True):
853         """Return list of link names that are other than mgmt links.
854
855         :param node: Node topology dictionary.
856         :param filter_list: Link filter criteria.
857         :param topo_has_dut: Whether we require back-to-back links.
858         :type node: dict
859         :type filter_list: list of strings
860         :type topo_has_dut: bool
861         :returns: List of link names occupied by the node.
862         :rtype: None or list of string
863         """
864         interfaces = node[u"interfaces"]
865         link_names = []
866         for interface in interfaces.values():
867             if u"link" in interface:
868                 if (filter_list is not None) and (u"model" in interface):
869                     for filt in filter_list:
870                         if filt == interface[u"model"]:
871                             link_names.append(interface[u"link"])
872                 elif (filter_list is not None) and (u"model" not in interface):
873                     logger.trace(
874                         f"Cannot apply filter on interface: {str(interface)}"
875                     )
876                 else:
877                     link_names.append(interface[u"link"])
878         if not link_names:
879             link_names = None
880         if not topo_has_dut:
881             new_link_names = list()
882             for link_name in link_names:
883                 count = 0
884                 for interface in interfaces.values():
885                     link = interface.get(u"link", None)
886                     if link == link_name:
887                         count += 1
888                 if count == 2:
889                     new_link_names.append(link_name)
890             link_names = new_link_names
891         return link_names
892
893     def get_active_connecting_links(
894             self, node1, node2, filter_list_node1=None, filter_list_node2=None):
895         """Return list of link names that connect together node1 and node2.
896
897         :param node1: Node topology dictionary.
898         :param node2: Node topology dictionary.
899         :param filter_list_node1: Link filter criteria for node1.
900         :param filter_list_node2: Link filter criteria for node2.
901         :type node1: dict
902         :type node2: dict
903         :type filter_list_node1: list of strings
904         :type filter_list_node2: list of strings
905         :returns: List of strings that represent connecting link names.
906         :rtype: list
907         """
908
909         if node1 != node2:
910             node1_links = self._get_node_active_link_names(
911                 node1, filter_list=filter_list_node1
912             )
913             node2_links = self._get_node_active_link_names(
914                 node2, filter_list=filter_list_node2
915             )
916         else:
917             # Looking for back-to-back links.
918             node1_links = self._get_node_active_link_names(
919                 node1, filter_list=filter_list_node1, topo_has_dut=False
920             )
921             node2_links = node1_links
922
923         connecting_links = None
924         if node1_links is None:
925             logger.error(u"Unable to find active links for node1")
926         elif node2_links is None:
927             logger.error(u"Unable to find active links for node2")
928         else:
929             # Not using set operations, as we need deterministic order.
930             connecting_links = [
931                 link for link in node1_links if link in node2_links
932             ]
933
934         return connecting_links
935
936     def get_first_active_connecting_link(self, node1, node2):
937         """Get first link connecting the two nodes together.
938
939         :param node1: Connected node.
940         :param node2: Connected node.
941         :type node1: dict
942         :type node2: dict
943         :returns: Name of a link connecting the two nodes together.
944         :rtype: str
945         :raises RuntimeError: If no links are found.
946         """
947         connecting_links = self.get_active_connecting_links(node1, node2)
948         if not connecting_links:
949             raise RuntimeError(u"No links connecting the nodes were found")
950         return connecting_links[0]
951
952     def get_egress_interfaces_name_for_nodes(self, node1, node2):
953         """Get egress interfaces on node1 for link with node2.
954
955         :param node1: First node, node to get egress interface on.
956         :param node2: Second node.
957         :type node1: dict
958         :type node2: dict
959         :returns: Egress interfaces.
960         :rtype: list
961         """
962         interfaces = list()
963         links = self.get_active_connecting_links(node1, node2)
964         if not links:
965             raise RuntimeError(u"No link between nodes")
966         for interface in node1[u"interfaces"].values():
967             link = interface.get(u"link")
968             if link is None:
969                 continue
970             if link in links:
971                 continue
972             name = interface.get(u"name")
973             if name is None:
974                 continue
975             interfaces.append(name)
976         return interfaces
977
978     def get_first_egress_interface_for_nodes(self, node1, node2):
979         """Get first egress interface on node1 for link with node2.
980
981         :param node1: First node, node to get egress interface name on.
982         :param node2: Second node.
983         :type node1: dict
984         :type node2: dict
985         :returns: Egress interface name.
986         :rtype: str
987         """
988         interfaces = self.get_egress_interfaces_name_for_nodes(node1, node2)
989         if not interfaces:
990             raise RuntimeError(u"No egress interface for nodes")
991         return interfaces[0]
992
993     def get_links_dict_from_nodes(self, tgen, dut1, dut2):
994         """Return link combinations used in tests in circular topology.
995
996         For the time being it returns links from the Node path:
997         TG->DUT1->DUT2->TG
998         The naming convention until changed to something more general is
999         implemented is this:
1000         DUT1_DUT2_LINK: link name between DUT! and DUT2
1001         DUT1_TG_LINK: link name between DUT1 and TG
1002         DUT2_TG_LINK: link name between DUT2 and TG
1003         TG_TRAFFIC_LINKS: list of link names that generated traffic is sent
1004         to and from
1005         DUT1_BD_LINKS: list of link names that will be connected by the bridge
1006         domain on DUT1
1007         DUT2_BD_LINKS: list of link names that will be connected by the bridge
1008         domain on DUT2
1009
1010         :param tgen: Traffic generator node data.
1011         :param dut1: DUT1 node data.
1012         :param dut2: DUT2 node data.
1013         :type tgen: dict
1014         :type dut1: dict
1015         :type dut2: dict
1016         :returns: Dictionary of possible link combinations.
1017         :rtype: dict
1018         """
1019         # TODO: replace with generic function.
1020         dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2)
1021         dut1_tg_link = self.get_first_active_connecting_link(dut1, tgen)
1022         dut2_tg_link = self.get_first_active_connecting_link(dut2, tgen)
1023         tg_traffic_links = [dut1_tg_link, dut2_tg_link]
1024         dut1_bd_links = [dut1_dut2_link, dut1_tg_link]
1025         dut2_bd_links = [dut1_dut2_link, dut2_tg_link]
1026         topology_links = {
1027             u"DUT1_DUT2_LINK": dut1_dut2_link,
1028             u"DUT1_TG_LINK": dut1_tg_link,
1029             u"DUT2_TG_LINK": dut2_tg_link,
1030             u"TG_TRAFFIC_LINKS": tg_traffic_links,
1031             u"DUT1_BD_LINKS": dut1_bd_links,
1032             u"DUT2_BD_LINKS": dut2_bd_links
1033         }
1034         return topology_links
1035
1036     @staticmethod
1037     def is_tg_node(node):
1038         """Find out whether the node is TG.
1039
1040         :param node: Node to examine.
1041         :type node: dict
1042         :returns: True if node is type of TG, otherwise False.
1043         :rtype: bool
1044         """
1045         return node[u"type"] == NodeType.TG
1046
1047     @staticmethod
1048     def get_node_hostname(node):
1049         """Return host (hostname/ip address) of the node.
1050
1051         :param node: Node created from topology.
1052         :type node: dict
1053         :returns: Hostname or IP address.
1054         :rtype: str
1055         """
1056         return node[u"host"]
1057
1058     @staticmethod
1059     def get_node_arch(node):
1060         """Return arch of the node.
1061            Default to x86_64 if no arch present
1062
1063         :param node: Node created from topology.
1064         :type node: dict
1065         :returns: Node architecture
1066         :rtype: str
1067         """
1068         try:
1069             return node[u"arch"]
1070         except KeyError:
1071             node[u"arch"] = u"x86_64"
1072             return u"x86_64"
1073
1074     @staticmethod
1075     def get_cryptodev(node):
1076         """Return Crytodev configuration of the node.
1077
1078         :param node: Node created from topology.
1079         :type node: dict
1080         :returns: Cryptodev configuration string.
1081         :rtype: str
1082         """
1083         try:
1084             return node[u"cryptodev"]
1085         except KeyError:
1086             return None
1087
1088     @staticmethod
1089     def get_uio_driver(node):
1090         """Return uio-driver configuration of the node.
1091
1092         :param node: Node created from topology.
1093         :type node: dict
1094         :returns: uio-driver configuration string.
1095         :rtype: str
1096         """
1097         try:
1098             return node[u"uio_driver"]
1099         except KeyError:
1100             return None
1101
1102     @staticmethod
1103     def set_interface_numa_node(node, iface_key, numa_node_id):
1104         """Set interface numa_node location.
1105
1106         :param node: Node to set numa_node on.
1107         :param iface_key: Interface key from topology file.
1108         :param numa_node_id: Num_node ID.
1109         :type node: dict
1110         :type iface_key: str
1111         :type numa_node_id: int
1112         :returns: Return iface_key or None if not found.
1113         """
1114         try:
1115             node[u"interfaces"][iface_key][u"numa_node"] = numa_node_id
1116             return iface_key
1117         except KeyError:
1118             return None
1119
1120     @staticmethod
1121     def add_new_socket(node, socket_type, socket_id, socket_path):
1122         """Add socket file of specific SocketType and ID to node.
1123
1124         :param node: Node to add socket on.
1125         :param socket_type: Socket type.
1126         :param socket_id: Socket id, currently equals to unique node key.
1127         :param socket_path: Socket absolute path.
1128         :type node: dict
1129         :type socket_type: SocketType
1130         :type socket_id: str
1131         :type socket_path: str
1132         """
1133         path = [u"sockets", socket_type, socket_id]
1134         Topology.add_node_item(node, socket_path, path)
1135
1136     @staticmethod
1137     def del_node_socket_id(node, socket_type, socket_id):
1138         """Delete socket of specific SocketType and ID from node.
1139
1140         :param node: Node to delete socket from.
1141         :param socket_type: Socket type.
1142         :param socket_id: Socket id, currently equals to unique node key.
1143         :type node: dict
1144         :type socket_type: SocketType
1145         :type socket_id: str
1146         """
1147         node[u"sockets"][socket_type].pop(socket_id)
1148
1149     @staticmethod
1150     def get_node_sockets(node, socket_type=None):
1151         """Get node socket files.
1152
1153         :param node: Node to get sockets from.
1154         :param socket_type: Socket type or None for all sockets.
1155         :type node: dict
1156         :type socket_type: SocketType
1157         :returns: Node sockets or None if not found.
1158         :rtype: dict
1159         """
1160         try:
1161             if socket_type:
1162                 return node[u"sockets"][socket_type]
1163             return node[u"sockets"]
1164         except KeyError:
1165             return None
1166
1167     @staticmethod
1168     def clean_sockets_on_all_nodes(nodes):
1169         """Remove temporary socket files from topology file.
1170
1171         :param nodes: SUT nodes.
1172         :type node: dict
1173         """
1174         for node in nodes.values():
1175             if u"sockets" in list(node.keys()):
1176                 # Containers are disconnected and destroyed already.
1177                 node.pop(u"sockets")