Dpdk in VM: Increase num_mbufs
[csit.git] / resources / libraries / python / NodePath.py
1 # Copyright (c) 2020 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 """Path utilities library for nodes in the topology."""
15
16 from resources.libraries.python.topology import Topology
17
18
19 class NodePath:
20     """Path utilities for nodes in the topology.
21
22     :Example:
23
24     node1--link1-->node2--link2-->node3--link3-->node2--link4-->node1
25     RobotFramework:
26     | Library | resources/libraries/python/NodePath.py
27
28     | Path test
29     | | [Arguments] | ${node1} | ${node2} | ${node3}
30     | | Append Node | ${nodes1}
31     | | Append Node | ${nodes2}
32     | | Append Nodes | ${nodes3} | ${nodes2}
33     | | Append Node | ${nodes1}
34     | | Compute Path | ${FALSE}
35     | | ${first_int} | ${node}= | First Interface
36     | | ${last_int} | ${node}= | Last Interface
37     | | ${first_ingress} | ${node}= | First Ingress Interface
38     | | ${last_egress} | ${node}= | Last Egress Interface
39     | | ${next} | ${node}= | Next Interface
40
41     Python:
42     >>> from NodePath import NodePath
43     >>> path = NodePath()
44     >>> path.append_node(node1)
45     >>> path.append_node(node2)
46     >>> path.append_nodes(node3, node2)
47     >>> path.append_node(node1)
48     >>> path.compute_path()
49     >>> (interface, node) = path.first_interface()
50     >>> (interface, node) = path.last_interface()
51     >>> (interface, node) = path.first_ingress_interface()
52     >>> (interface, node) = path.last_egress_interface()
53     >>> (interface, node) = path.next_interface()
54     """
55
56     def __init__(self):
57         self._nodes = []
58         self._nodes_filter = []
59         self._links = []
60         self._path = []
61         self._path_iter = []
62
63     def append_node(self, node, filter_list=None):
64         """Append node to the path.
65
66         :param node: Node to append to the path.
67         :param filter_list: Filter criteria list.
68         :type node: dict
69         :type filter_list: list of strings
70         """
71         self._nodes_filter.append(filter_list)
72         self._nodes.append(node)
73
74     def append_nodes(self, *nodes, filter_list=None):
75         """Append nodes to the path.
76
77         :param nodes: Nodes to append to the path.
78         :param filter_list: Filter criteria list.
79         :type nodes: dict
80         :type filter_list: list of strings
81
82         .. note:: Node order does matter.
83         """
84         for node in nodes:
85             self.append_node(node, filter_list=filter_list)
86
87     def clear_path(self):
88         """Clear path."""
89         self._nodes = []
90         self._nodes_filter = []
91         self._links = []
92         self._path = []
93         self._path_iter = []
94
95     def compute_path(self, always_same_link=True):
96         """Compute path for added nodes.
97
98         .. note:: First add at least two nodes to the topology.
99
100         :param always_same_link: If True use always same link between two nodes
101             in path. If False use different link (if available)
102             between two nodes if one link was used before.
103         :type always_same_link: bool
104         :raises RuntimeError: If not enough nodes for path.
105         """
106         nodes = self._nodes
107         if len(nodes) < 2:
108             raise RuntimeError(u"Not enough nodes to compute path")
109
110         for idx in range(0, len(nodes) - 1):
111             topo = Topology()
112             node1 = nodes[idx]
113             node2 = nodes[idx + 1]
114             n1_list = self._nodes_filter[idx]
115             n2_list = self._nodes_filter[idx + 1]
116             links = topo.get_active_connecting_links(
117                 node1, node2, filter_list_node1=n1_list,
118                 filter_list_node2=n2_list
119             )
120             if not links:
121                 raise RuntimeError(
122                     f"No link between {node1[u'host']} and {node2[u'host']}"
123                 )
124
125             # Not using set operations, as we need deterministic order.
126             if always_same_link:
127                 l_set = [link for link in links if link in self._links]
128             else:
129                 l_set = [link for link in links if link not in self._links]
130                 if not l_set:
131                     raise RuntimeError(
132                         f"No free link between {node1[u'host']} and "
133                         f"{node2[u'host']}, all links already used"
134                     )
135
136             if not l_set:
137                 link = links[0]
138             else:
139                 link = l_set[0]
140
141             self._links.append(link)
142             interface1 = topo.get_interface_by_link_name(node1, link)
143             interface2 = topo.get_interface_by_link_name(node2, link)
144             self._path.append((interface1, node1))
145             self._path.append((interface2, node2))
146
147         self._path_iter.extend(self._path)
148         self._path_iter.reverse()
149
150     def next_interface(self):
151         """Path interface iterator.
152
153         :returns: Interface and node or None if not next interface.
154         :rtype: tuple (str, dict)
155
156         .. note:: Call compute_path before.
157         """
158         if not self._path_iter:
159             return None, None
160         return self._path_iter.pop()
161
162     def first_interface(self):
163         """Return first interface on the path.
164
165         :returns: Interface and node.
166         :rtype: tuple (str, dict)
167
168         .. note:: Call compute_path before.
169         """
170         if not self._path:
171             raise RuntimeError(u"No path for topology")
172         return self._path[0]
173
174     def last_interface(self):
175         """Return last interface on the path.
176
177         :returns: Interface and node.
178         :rtype: tuple (str, dict)
179
180         .. note:: Call compute_path before.
181         """
182         if not self._path:
183             raise RuntimeError(u"No path for topology")
184         return self._path[-1]
185
186     def first_ingress_interface(self):
187         """Return first ingress interface on the path.
188
189         :returns: Interface and node.
190         :rtype: tuple (str, dict)
191
192         .. note:: Call compute_path before.
193         """
194         if not self._path:
195             raise RuntimeError(u"No path for topology")
196         return self._path[1]
197
198     def last_egress_interface(self):
199         """Return last egress interface on the path.
200
201         :returns: Interface and node.
202         :rtype: tuple (str, dict)
203
204         .. note:: Call compute_path before.
205         """
206         if not self._path:
207             raise RuntimeError(u"No path for topology")
208         return self._path[-2]
209
210     def compute_circular_topology(self, nodes, filter_list=None, nic_pfs=1,
211                                   always_same_link=False, topo_has_tg=True):
212         """Return computed circular path.
213
214         :param nodes: Nodes to append to the path.
215         :param filter_list: Filter criteria list.
216         :param nic_pfs: Number of PF of NIC.
217         :param always_same_link: If True use always same link between two nodes
218             in path. If False use different link (if available)
219             between two nodes if one link was used before.
220         :param topo_has_tg: If True, the topology has a TG node. If False,
221             the topology consists entirely of DUT nodes.
222         :type nodes: dict
223         :type filter_list: list of strings
224         :type nic_pfs: int
225         :type always_same_link: bool
226         :type topo_has_tg: bool
227         :returns: Topology information dictionary.
228         :rtype: dict
229         :raises RuntimeError: If unsupported combination of parameters.
230         """
231         t_dict = dict()
232         duts = [key for key in nodes if u"DUT" in key]
233         t_dict[u"duts"] = duts
234         t_dict[u"duts_count"] = len(duts)
235         t_dict[u"int"] = u"pf"
236
237         for _ in range(0, nic_pfs // 2):
238             if topo_has_tg:
239                 self.append_node(nodes[u"TG"])
240             for dut in duts:
241                 self.append_node(nodes[dut], filter_list=filter_list)
242         if topo_has_tg:
243             self.append_node(nodes[u"TG"])
244         self.compute_path(always_same_link)
245
246         n_idx = 0 # node index
247         t_idx = 1 # TG interface index
248         d_idx = 0 # DUT interface index
249         prev_host = None
250         while True:
251             interface, node = self.next_interface()
252             if not interface:
253                 break
254             if topo_has_tg and node.get(u"type") == u"TG":
255                 n_pfx = f"TG" # node prefix
256                 p_pfx = f"pf{t_idx}" # physical interface prefix
257                 i_pfx = f"if{t_idx}" # [backwards compatible] interface prefix
258                 n_idx = 0
259                 t_idx = t_idx + 1
260             elif topo_has_tg:
261                 # Each node has 2 interfaces, starting with 1
262                 # Calculate prefixes appropriately for current
263                 # path topology nomenclature:
264                 #   tg1_if1 -> dut1_if1 -> dut1_if2 ->
265                 #        [dut2_if1 -> dut2_if2 ...] -> tg1_if2
266                 n_pfx = f"DUT{n_idx // 2 + 1}"
267                 p_pfx = f"pf{d_idx % 2 + t_idx - 1}"
268                 i_pfx = f"if{d_idx % 2 + t_idx - 1}"
269                 n_idx = n_idx + 1
270                 d_idx = d_idx + 1
271             elif not topo_has_tg and always_same_link:
272                 this_host = node.get(u"host")
273                 if prev_host != this_host:
274                     # When moving to a new host in the path,
275                     # increment the node index (n_idx) and
276                     # reset DUT interface index (d_idx) to 1.
277                     n_idx = n_idx + 1
278                     d_idx = 1
279                 n_pfx = f"DUT{n_idx}"
280                 p_pfx = f"pf{d_idx}"
281                 i_pfx = f"if{d_idx}"
282                 d_idx = d_idx + 1
283             else:
284                 raise RuntimeError(u"Unsupported combination of paramters")
285
286             t_dict[f"{n_pfx}"] = node
287             t_dict[f"{n_pfx}_{p_pfx}"] = [interface]
288             t_dict[f"{n_pfx}_{p_pfx}_mac"] = \
289                 [Topology.get_interface_mac(node, interface)]
290             t_dict[f"{n_pfx}_{p_pfx}_vlan"] = \
291                 [Topology.get_interface_vlan(node, interface)]
292             t_dict[f"{n_pfx}_{p_pfx}_pci"] = \
293                 [Topology.get_interface_pci_addr(node, interface)]
294             t_dict[f"{n_pfx}_{p_pfx}_ip4_addr"] = \
295                 [Topology.get_interface_ip4(node, interface)]
296             t_dict[f"{n_pfx}_{p_pfx}_ip4_prefix"] = \
297                 [Topology.get_interface_ip4_prefix_length(node, interface)]
298             if f"{n_pfx}_pf_pci" not in t_dict:
299                 t_dict[f"{n_pfx}_pf_pci"] = []
300             t_dict[f"{n_pfx}_pf_pci"].append(
301                 Topology.get_interface_pci_addr(node, interface))
302             if f"{n_pfx}_pf_keys" not in t_dict:
303                 t_dict[f"{n_pfx}_pf_keys"] = []
304             t_dict[f"{n_pfx}_pf_keys"].append(interface)
305             # Backward compatibility below
306             t_dict[f"{n_pfx.lower()}_{i_pfx}"] = interface
307             t_dict[f"{n_pfx.lower()}_{i_pfx}_mac"] = \
308                 Topology.get_interface_mac(node, interface)
309             t_dict[f"{n_pfx.lower()}_{i_pfx}_pci"] = \
310                 Topology.get_interface_pci_addr(node, interface)
311             t_dict[f"{n_pfx.lower()}_{i_pfx}_ip4_addr"] = \
312                 Topology.get_interface_ip4(node, interface)
313             t_dict[f"{n_pfx.lower()}_{i_pfx}_ip4_prefix"] = \
314                 Topology.get_interface_ip4_prefix_length(node, interface)
315
316         self.clear_path()
317         return t_dict