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