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