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