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