92ade4a7a304695e3445fe334a26012c4afaad47
[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
45 class NodeType:
46     """Defines node types used in topology dictionaries."""
47     # TODO: Two letter initialisms are well-known, but too short for pylint.
48     # Candidates: TG -> TGN, VM -> VNF.
49
50     # Device Under Test (this node has VPP running on it)
51     DUT = u"DUT"
52     # Traffic Generator (this node has traffic generator on it)
53     # pylint: disable=invalid-name
54     TG = u"TG"
55     # Virtual Machine (this node running on DUT node)
56     # pylint: disable=invalid-name
57     VM = u"VM"
58
59
60 class NodeSubTypeTG:
61     """Defines node sub-type TG - traffic generator."""
62     # T-Rex traffic generator
63     TREX = u"TREX"
64     # Moongen
65     MOONGEN = u"MOONGEN"
66     # IxNetwork
67     IXNET = u"IXNET"
68
69
70 class SocketType:
71     """Defines socket types used in topology dictionaries."""
72     # VPP Socket PAPI
73     PAPI = u"PAPI"
74     # VPP PAPI Stats (legacy option until stats are migrated to Socket PAPI)
75     STATS = u"STATS"
76
77
78 DICT__nodes = load_topo_from_yaml()
79
80
81 class Topology:
82     """Topology data manipulation and extraction methods.
83
84     Defines methods used for manipulation and extraction of data from
85     the active topology.
86
87     "Active topology" contains initially data from the topology file and can be
88     extended with additional data from the DUTs like internal interface indexes
89     or names. Additional data which can be filled to the active topology are
90
91         - additional internal representation (index, name, ...)
92         - operational data (dynamic ports)
93
94     To access the port data it is recommended to use a port key because the key
95     does not rely on the data retrieved from nodes, this allows to call most of
96     the methods without having filled active topology with internal nodes data.
97     """
98
99     def add_node_item(self, node, value, path):
100         """Add item to topology node.
101
102         :param node: Topology node.
103         :param value: Value to insert.
104         :param path: Path where to insert item.
105         :type node: dict
106         :type value: str
107         :type path: list
108         """
109         if len(path) == 1:
110             node[path[0]] = value
111             return
112         if path[0] not in node:
113             node[path[0]] = dict()
114         elif isinstance(node[path[0]], str):
115             node[path[0]] = dict() if node[path[0]] == u"" \
116                 else {node[path[0]]: u""}
117         self.add_node_item(node[path[0]], value, path[1:])
118
119     @staticmethod
120     def add_new_port(node, ptype):
121         """Add new port to the node to active topology.
122
123         :param node: Node to add new port on.
124         :param ptype: Port type, used as key prefix.
125         :type node: dict
126         :type ptype: str
127         :returns: Port key or None
128         :rtype: string or None
129         """
130         max_ports = 1000000
131         iface = None
132         for i in range(1, max_ports):
133             if node[u"interfaces"].get(str(ptype) + str(i)) is None:
134                 iface = str(ptype) + str(i)
135                 node[u"interfaces"][iface] = dict()
136                 break
137         return iface
138
139     @staticmethod
140     def remove_port(node, iface_key):
141         """Remove required port from active topology.
142
143         :param node: Node to remove port on.
144         :param: iface_key: Topology key of the interface.
145         :type node: dict
146         :type iface_key: str
147         :returns: Nothing
148         """
149         try:
150             node[u"interfaces"].pop(iface_key)
151         except KeyError:
152             pass
153
154     @staticmethod
155     def remove_all_ports(node, ptype):
156         """Remove all ports with ptype as prefix.
157
158         :param node: Node to remove ports on.
159         :param: ptype: Port type, used as key prefix.
160         :type node: dict
161         :type ptype: str
162         :returns: Nothing
163         """
164         for if_key in list(node[u"interfaces"]):
165             if if_key.startswith(str(ptype)):
166                 node[u"interfaces"].pop(if_key)
167
168     @staticmethod
169     def remove_all_added_ports_on_all_duts_from_topology(nodes):
170         """Remove all added ports on all DUT nodes in the topology.
171
172         :param nodes: Nodes in the topology.
173         :type nodes: dict
174         :returns: Nothing
175         """
176         port_types = (
177             u"subinterface", u"vlan_subif", u"memif", u"tap", u"vhost",
178             u"loopback", u"gre_tunnel", u"vxlan_tunnel", u"eth_bond", u"eth_avf"
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):
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         :type node: dict
388         :type key: string
389         :type value: string
390         :returns: Interface key from topology file
391         :rtype: string
392         """
393         interfaces = node[u"interfaces"]
394         retval = None
395         for if_key, if_val in interfaces.items():
396             k_val = if_val.get(key)
397             if k_val is not None:
398                 if k_val == value:
399                     retval = if_key
400                     break
401         return retval
402
403     @staticmethod
404     def get_interface_by_name(node, iface_name):
405         """Return interface key based on name from DUT/TG.
406
407         This method returns interface key based on interface name
408         retrieved from the DUT, or TG.
409
410         :param node: The node topology dictionary.
411         :param iface_name: Interface name (string form).
412         :type node: dict
413         :type iface_name: string
414         :returns: Interface key.
415         :rtype: str
416         """
417         return Topology._get_interface_by_key_value(node, u"name", iface_name)
418
419     @staticmethod
420     def get_interface_by_link_name(node, link_name):
421         """Return interface key of link on node.
422
423         This method returns the interface name associated with a given link
424         for a given node.
425
426         :param node: The node topology dictionary.
427         :param link_name: Name of the link that a interface is connected to.
428         :type node: dict
429         :type link_name: string
430         :returns: Interface key of the interface connected to the given link.
431         :rtype: str
432         """
433         return Topology._get_interface_by_key_value(node, u"link", link_name)
434
435     def get_interfaces_by_link_names(self, node, link_names):
436         """Return dictionary of dictionaries {"interfaceN", interface name}.
437
438         This method returns the interface names associated with given links
439         for a given node.
440
441         :param node: The node topology directory.
442         :param link_names: List of names of the link that a interface is
443             connected to.
444         :type node: dict
445         :type link_names: list
446         :returns: Dictionary of interface names that are connected to the given
447             links.
448         :rtype: dict
449         """
450         retval = dict()
451         interface_number = 1
452         for link_name in link_names:
453             interface = self.get_interface_by_link_name(node, link_name)
454             retval[f"interface{str(interface_number)}"] = \
455                 self.get_interface_name(node, interface)
456             interface_number += 1
457         return retval
458
459     @staticmethod
460     def get_interface_by_sw_index(node, sw_if_index):
461         """Return interface name of link on node.
462
463         This method returns the interface name associated with a software
464         interface index assigned to the interface by vpp for a given node.
465
466         :param node: The node topology dictionary.
467         :param sw_if_index: sw_if_index of the link that a interface is
468             connected to.
469         :type node: dict
470         :type sw_if_index: int
471         :returns: Interface name of the interface connected to the given link.
472         :rtype: str
473         """
474         return Topology._get_interface_by_key_value(
475             node, u"vpp_sw_index", sw_if_index
476         )
477
478     @staticmethod
479     def get_interface_sw_index(node, iface_key):
480         """Get VPP sw_if_index for the interface using interface key.
481
482         :param node: Node to get interface sw_if_index on.
483         :param iface_key: Interface key from topology file, or sw_if_index.
484         :type node: dict
485         :type iface_key: str/int
486         :returns: Return sw_if_index or None if not found.
487         :rtype: int or None
488         """
489         try:
490             if isinstance(iface_key, str):
491                 return node[u"interfaces"][iface_key].get(u"vpp_sw_index")
492             # TODO: use only iface_key, do not use integer
493             return int(iface_key)
494         except (KeyError, ValueError):
495             return None
496
497     @staticmethod
498     def get_interface_sw_index_by_name(node, iface_name):
499         """Get VPP sw_if_index for the interface using interface name.
500
501         :param node: Node to get interface sw_if_index on.
502         :param iface_name: Interface name.
503         :type node: dict
504         :type iface_name: str
505         :returns: Return sw_if_index or None if not found.
506         :raises TypeError: If provided interface name is not a string.
507         """
508         try:
509             if not isinstance(iface_name, str):
510                 raise TypeError(u"Interface name must be a string.")
511             iface_key = Topology.get_interface_by_name(node, iface_name)
512             return node[u"interfaces"][iface_key].get(u"vpp_sw_index")
513         except (KeyError, ValueError):
514             return None
515
516     @staticmethod
517     def get_interface_mtu(node, iface_key):
518         """Get interface MTU.
519
520         Returns physical layer MTU (max. size of Ethernet frame).
521         :param node: Node to get interface MTU on.
522         :param iface_key: Interface key from topology file.
523         :type node: dict
524         :type iface_key: str
525         :returns: MTU or None if not found.
526         :rtype: int
527         """
528         try:
529             return node[u"interfaces"][iface_key].get(u"mtu")
530         except KeyError:
531             return None
532
533     @staticmethod
534     def get_interface_name(node, iface_key):
535         """Get interface name (retrieved from DUT/TG).
536
537         Returns name in string format, retrieved from the node.
538         :param node: Node to get interface name on.
539         :param iface_key: Interface key from topology file.
540         :type node: dict
541         :type iface_key: str
542         :returns: Interface name or None if not found.
543         :rtype: str
544         """
545         try:
546             return node[u"interfaces"][iface_key].get(u"name")
547         except KeyError:
548             return None
549
550     @staticmethod
551     def convert_interface_reference_to_key(node, interface):
552         """Takes interface reference in any format
553         (name, link name, interface key or sw_if_index)
554         and converts to interface key using Topology methods.
555
556         :param node: Node in topology.
557         :param interface: Name, sw_if_index, link name or key of an interface
558             on the node.
559         :type node: dict
560         :type interface: str or int
561
562         :returns: Interface key.
563         :rtype: str
564
565         :raises TypeError: If provided with invalid interface argument.
566         :raises RuntimeError: If the interface does not exist in topology.
567         """
568
569         if isinstance(interface, int):
570             key = Topology.get_interface_by_sw_index(node, interface)
571             if key is None:
572                 raise RuntimeError(
573                     f"Interface with sw_if_index={interface} does not exist "
574                     f"in topology."
575                 )
576         elif interface in Topology.get_node_interfaces(node):
577             key = interface
578         elif interface in Topology.get_links({u"dut": node}):
579             key = Topology.get_interface_by_link_name(node, interface)
580         elif isinstance(interface, str):
581             key = Topology.get_interface_by_name(node, interface)
582             if key is None:
583                 raise RuntimeError(
584                     f"Interface with key, name or link name \"{interface}\" "
585                     f"does not exist in topology."
586                 )
587         else:
588             raise TypeError(
589                 u"Type of interface argument must be integer or string."
590             )
591         return key
592
593     @staticmethod
594     def convert_interface_reference(node, interface, wanted_format):
595         """Takes interface reference in any format
596         (name, link name, topology key or sw_if_index) and returns
597         its equivalent in the desired format.
598
599         :param node: Node in topology.
600         :param interface: Name, sw_if_index, link name or key of an interface
601             on the node.
602         :param wanted_format: Format of return value wanted.
603             Valid options are: sw_if_index, key, name.
604         :type node: dict
605         :type interface: str or int
606         :type wanted_format: str
607         :returns: Interface name, interface key or sw_if_index.
608         :rtype: str or int
609         :raises TypeError, ValueError: If provided with invalid arguments.
610         :raises RuntimeError: If the interface does not exist in topology.
611         """
612
613         key = Topology.convert_interface_reference_to_key(node, interface)
614
615         conversions = {
616             u"key": lambda x, y: y,
617             u"name": Topology.get_interface_name,
618             u"sw_if_index": Topology.get_interface_sw_index
619         }
620
621         try:
622             return conversions[wanted_format](node, key)
623         except KeyError:
624             raise ValueError(
625                 f"Unrecognized return value wanted: {wanted_format}."
626                 f"Valid options are key, name, sw_if_index"
627             )
628
629     @staticmethod
630     def get_interface_numa_node(node, iface_key):
631         """Get interface numa node.
632
633         Returns physical relation to numa node, numa_id.
634
635         :param node: Node to get numa id on.
636         :param iface_key: Interface key from topology file.
637         :type node: dict
638         :type iface_key: str
639         :returns: numa node id, None if not available.
640         :rtype: int
641         """
642         try:
643             return node[u"interfaces"][iface_key].get(u"numa_node")
644         except KeyError:
645             return None
646
647     @staticmethod
648     def get_interfaces_numa_node(node, *iface_keys):
649         """Get numa node on which are located most of the interfaces.
650
651         Return numa node with highest count of interfaces provided as arguments.
652         Return 0 if the interface does not have numa_node information available.
653         If all interfaces have unknown location (-1), then return 0.
654         If most of interfaces have unknown location (-1), but there are
655         some interfaces with known location, then return the second most
656         location of the provided interfaces.
657
658         :param node: Node from DICT__nodes.
659         :param iface_keys: Interface keys for lookup.
660         :type node: dict
661         :type iface_keys: strings
662         :returns: Numa node of most given interfaces or 0.
663         :rtype: int
664         """
665         numa_list = []
666         for if_key in iface_keys:
667             try:
668                 numa_list.append(node[u"interfaces"][if_key].get(u"numa_node"))
669             except KeyError:
670                 pass
671
672         numa_cnt_mc = Counter(numa_list).most_common()
673
674         if numa_cnt_mc and numa_cnt_mc[0][0] != -1:
675             return numa_cnt_mc[0][0]
676         if len(numa_cnt_mc) > 1 and numa_cnt_mc[0][0] == -1:
677             return numa_cnt_mc[1][0]
678         return 0
679
680     @staticmethod
681     def get_interface_mac(node, iface_key):
682         """Get MAC address for the interface.
683
684         :param node: Node to get interface mac on.
685         :param iface_key: Interface key from topology file.
686         :type node: dict
687         :type iface_key: str
688         :returns: Return MAC or None if not found.
689         """
690         try:
691             return node[u"interfaces"][iface_key].get(u"mac_address")
692         except KeyError:
693             return None
694
695     @staticmethod
696     def get_interface_ip4(node, iface_key):
697         """Get IP4 address for the interface.
698
699         :param node: Node to get interface mac on.
700         :param iface_key: Interface key from topology file.
701         :type node: dict
702         :type iface_key: str
703         :returns: Return IP4 or None if not found.
704         """
705         try:
706             return node[u"interfaces"][iface_key].get(u"ip4_address")
707         except KeyError:
708             return None
709
710     @staticmethod
711     def get_adjacent_node_and_interface(nodes_info, node, iface_key):
712         """Get node and interface adjacent to specified interface
713         on local network.
714
715         :param nodes_info: Dictionary containing information on all nodes
716             in topology.
717         :param node: Node that contains specified interface.
718         :param iface_key: Interface key from topology file.
719         :type nodes_info: dict
720         :type node: dict
721         :type iface_key: str
722         :returns: Return (node, interface_key) tuple or None if not found.
723         :rtype: (dict, str)
724         """
725         link_name = None
726         # get link name where the interface belongs to
727         for if_key, if_val in node[u"interfaces"].items():
728             if if_key == u"mgmt":
729                 continue
730             if if_key == iface_key:
731                 link_name = if_val[u"link"]
732                 break
733
734         if link_name is None:
735             return None
736
737         # find link
738         for node_data in nodes_info.values():
739             # skip self
740             if node_data[u"host"] == node[u"host"]:
741                 continue
742             for if_key, if_val \
743                     in node_data[u"interfaces"].items():
744                 if u"link" not in if_val:
745                     continue
746                 if if_val[u"link"] == link_name:
747                     return node_data, if_key
748         return None
749
750     @staticmethod
751     def get_interface_pci_addr(node, iface_key):
752         """Get interface PCI address.
753
754         :param node: Node to get interface PCI address on.
755         :param iface_key: Interface key from topology file.
756         :type node: dict
757         :type iface_key: str
758         :returns: Return PCI address or None if not found.
759         """
760         try:
761             return node[u"interfaces"][iface_key].get(u"pci_address")
762         except KeyError:
763             return None
764
765     @staticmethod
766     def get_interface_driver(node, iface_key):
767         """Get interface driver.
768
769         :param node: Node to get interface driver on.
770         :param iface_key: Interface key from topology file.
771         :type node: dict
772         :type iface_key: str
773         :returns: Return interface driver or None if not found.
774         """
775         try:
776             return node[u"interfaces"][iface_key].get(u"driver")
777         except KeyError:
778             return None
779
780     @staticmethod
781     def get_interface_vlan(node, iface_key):
782         """Get interface vlan.
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 vlan or None if not found.
789         """
790         try:
791             return node[u"interfaces"][iface_key].get(u"vlan")
792         except KeyError:
793             return None
794
795     @staticmethod
796     def get_node_interfaces(node):
797         """Get all node interfaces.
798
799         :param node: Node to get list of interfaces from.
800         :type node: dict
801         :returns: Return list of keys of all interfaces.
802         :rtype: list
803         """
804         return node[u"interfaces"].keys()
805
806     @staticmethod
807     def get_node_link_mac(node, link_name):
808         """Return interface mac address by link name.
809
810         :param node: Node to get interface sw_if_index on.
811         :param link_name: Link name.
812         :type node: dict
813         :type link_name: str
814         :returns: MAC address string.
815         :rtype: str
816         """
817         for port in node[u"interfaces"].values():
818             if port.get(u"link") == link_name:
819                 return port.get(u"mac_address")
820         return None
821
822     @staticmethod
823     def _get_node_active_link_names(node, filter_list=None):
824         """Return list of link names that are other than mgmt links.
825
826         :param node: Node topology dictionary.
827         :param filter_list: Link filter criteria.
828         :type node: dict
829         :type filter_list: list of strings
830         :returns: List of link names occupied by the node.
831         :rtype: None or list of string
832         """
833         interfaces = node[u"interfaces"]
834         link_names = []
835         for interface in interfaces.values():
836             if u"link" in interface:
837                 if (filter_list is not None) and (u"model" in interface):
838                     for filt in filter_list:
839                         if filt == interface[u"model"]:
840                             link_names.append(interface[u"link"])
841                 elif (filter_list is not None) and (u"model" not in interface):
842                     logger.trace(
843                         f"Cannot apply filter on interface: {str(interface)}"
844                     )
845                 else:
846                     link_names.append(interface[u"link"])
847         if not link_names:
848             link_names = None
849         return link_names
850
851     def get_active_connecting_links(
852             self, node1, node2, filter_list_node1=None, filter_list_node2=None):
853         """Return list of link names that connect together node1 and node2.
854
855         :param node1: Node topology dictionary.
856         :param node2: Node topology dictionary.
857         :param filter_list_node1: Link filter criteria for node1.
858         :param filter_list_node2: Link filter criteria for node2.
859         :type node1: dict
860         :type node2: dict
861         :type filter_list_node1: list of strings
862         :type filter_list_node2: list of strings
863         :returns: List of strings that represent connecting link names.
864         :rtype: list
865         """
866
867         logger.trace(f"node1: {str(node1)}")
868         logger.trace(f"node2: {str(node2)}")
869         node1_links = self._get_node_active_link_names(
870             node1, filter_list=filter_list_node1
871         )
872         node2_links = self._get_node_active_link_names(
873             node2, filter_list=filter_list_node2
874         )
875
876         connecting_links = None
877         if node1_links is None:
878             logger.error(u"Unable to find active links for node1")
879         elif node2_links is None:
880             logger.error(u"Unable to find active links for node2")
881         else:
882             connecting_links = list(set(node1_links).intersection(node2_links))
883
884         return connecting_links
885
886     def get_first_active_connecting_link(self, node1, node2):
887         """Get first link connecting the two nodes together.
888
889         :param node1: Connected node.
890         :param node2: Connected node.
891         :type node1: dict
892         :type node2: dict
893         :returns: Name of a link connecting the two nodes together.
894         :rtype: str
895         :raises RuntimeError: If no links are found.
896         """
897         connecting_links = self.get_active_connecting_links(node1, node2)
898         if not connecting_links:
899             raise RuntimeError(u"No links connecting the nodes were found")
900         return connecting_links[0]
901
902     def get_egress_interfaces_name_for_nodes(self, node1, node2):
903         """Get egress interfaces on node1 for link with node2.
904
905         :param node1: First node, node to get egress interface on.
906         :param node2: Second node.
907         :type node1: dict
908         :type node2: dict
909         :returns: Egress interfaces.
910         :rtype: list
911         """
912         interfaces = list()
913         links = self.get_active_connecting_links(node1, node2)
914         if not links:
915             raise RuntimeError(u"No link between nodes")
916         for interface in node1[u"interfaces"].values():
917             link = interface.get(u"link")
918             if link is None:
919                 continue
920             if link in links:
921                 continue
922             name = interface.get(u"name")
923             if name is None:
924                 continue
925             interfaces.append(name)
926         return interfaces
927
928     def get_first_egress_interface_for_nodes(self, node1, node2):
929         """Get first egress interface on node1 for link with node2.
930
931         :param node1: First node, node to get egress interface name on.
932         :param node2: Second node.
933         :type node1: dict
934         :type node2: dict
935         :returns: Egress interface name.
936         :rtype: str
937         """
938         interfaces = self.get_egress_interfaces_name_for_nodes(node1, node2)
939         if not interfaces:
940             raise RuntimeError(u"No egress interface for nodes")
941         return interfaces[0]
942
943     def get_links_dict_from_nodes(self, tgen, dut1, dut2):
944         """Return link combinations used in tests in circular topology.
945
946         For the time being it returns links from the Node path:
947         TG->DUT1->DUT2->TG
948         The naming convention until changed to something more general is
949         implemented is this:
950         DUT1_DUT2_LINK: link name between DUT! and DUT2
951         DUT1_TG_LINK: link name between DUT1 and TG
952         DUT2_TG_LINK: link name between DUT2 and TG
953         TG_TRAFFIC_LINKS: list of link names that generated traffic is sent
954         to and from
955         DUT1_BD_LINKS: list of link names that will be connected by the bridge
956         domain on DUT1
957         DUT2_BD_LINKS: list of link names that will be connected by the bridge
958         domain on DUT2
959
960         :param tgen: Traffic generator node data.
961         :param dut1: DUT1 node data.
962         :param dut2: DUT2 node data.
963         :type tgen: dict
964         :type dut1: dict
965         :type dut2: dict
966         :returns: Dictionary of possible link combinations.
967         :rtype: dict
968         """
969         # TODO: replace with generic function.
970         dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2)
971         dut1_tg_link = self.get_first_active_connecting_link(dut1, tgen)
972         dut2_tg_link = self.get_first_active_connecting_link(dut2, tgen)
973         tg_traffic_links = [dut1_tg_link, dut2_tg_link]
974         dut1_bd_links = [dut1_dut2_link, dut1_tg_link]
975         dut2_bd_links = [dut1_dut2_link, dut2_tg_link]
976         topology_links = {
977             u"DUT1_DUT2_LINK": dut1_dut2_link,
978             u"DUT1_TG_LINK": dut1_tg_link,
979             u"DUT2_TG_LINK": dut2_tg_link,
980             u"TG_TRAFFIC_LINKS": tg_traffic_links,
981             u"DUT1_BD_LINKS": dut1_bd_links,
982             u"DUT2_BD_LINKS": dut2_bd_links
983         }
984         return topology_links
985
986     @staticmethod
987     def is_tg_node(node):
988         """Find out whether the node is TG.
989
990         :param node: Node to examine.
991         :type node: dict
992         :returns: True if node is type of TG, otherwise False.
993         :rtype: bool
994         """
995         return node[u"type"] == NodeType.TG
996
997     @staticmethod
998     def get_node_hostname(node):
999         """Return host (hostname/ip address) of the node.
1000
1001         :param node: Node created from topology.
1002         :type node: dict
1003         :returns: Hostname or IP address.
1004         :rtype: str
1005         """
1006         return node[u"host"]
1007
1008     @staticmethod
1009     def get_node_arch(node):
1010         """Return arch of the node.
1011            Default to x86_64 if no arch present
1012
1013         :param node: Node created from topology.
1014         :type node: dict
1015         :returns: Node architecture
1016         :rtype: str
1017         """
1018         try:
1019             return node[u"arch"]
1020         except KeyError:
1021             node[u"arch"] = u"x86_64"
1022             return u"x86_64"
1023
1024     @staticmethod
1025     def get_cryptodev(node):
1026         """Return Crytodev configuration of the node.
1027
1028         :param node: Node created from topology.
1029         :type node: dict
1030         :returns: Cryptodev configuration string.
1031         :rtype: str
1032         """
1033         try:
1034             return node[u"cryptodev"]
1035         except KeyError:
1036             return None
1037
1038     @staticmethod
1039     def get_uio_driver(node):
1040         """Return uio-driver configuration of the node.
1041
1042         :param node: Node created from topology.
1043         :type node: dict
1044         :returns: uio-driver configuration string.
1045         :rtype: str
1046         """
1047         try:
1048             return node[u"uio_driver"]
1049         except KeyError:
1050             return None
1051
1052     @staticmethod
1053     def set_interface_numa_node(node, iface_key, numa_node_id):
1054         """Set interface numa_node location.
1055
1056         :param node: Node to set numa_node on.
1057         :param iface_key: Interface key from topology file.
1058         :type node: dict
1059         :type iface_key: str
1060         :returns: Return iface_key or None if not found.
1061         """
1062         try:
1063             node[u"interfaces"][iface_key][u"numa_node"] = numa_node_id
1064             return iface_key
1065         except KeyError:
1066             return None
1067
1068     def add_new_socket(self, node, socket_type, socket_id, socket_path):
1069         """Add socket file of specific SocketType and ID to node.
1070
1071         :param node: Node to add socket on.
1072         :param socket_type: Socket type.
1073         :param socket_id: Socket id.
1074         :param socket_path: Socket absolute path.
1075         :type node: dict
1076         :type socket_type: SocketType
1077         :type socket_id: str
1078         :type socket_path: str
1079         """
1080         path = [u"sockets", socket_type, socket_id]
1081         self.add_node_item(node, socket_path, path)
1082
1083     @staticmethod
1084     def get_node_sockets(node, socket_type=None):
1085         """Get node socket files.
1086
1087         :param node: Node to get sockets from.
1088         :param socket_type: Socket type or None for all sockets.
1089         :type node: dict
1090         :type socket_type: SocketType
1091         :returns: Node sockets or None if not found.
1092         :rtype: dict
1093         """
1094         try:
1095             if socket_type:
1096                 return node[u"sockets"][socket_type]
1097             return node[u"sockets"]
1098         except KeyError:
1099             return None
1100
1101     @staticmethod
1102     def clean_sockets_on_all_nodes(nodes):
1103         """Remove temporary socket files from topology file.
1104
1105         :param nodes: SUT nodes.
1106         :type node: dict
1107         """
1108         for node in nodes.values():
1109             if u"sockets" in list(node.keys()):
1110                 node.pop(u"sockets")