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