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