style(ipsec): Unify mutiline strings
[csit.git] / resources / libraries / python / NodePath.py
1 # Copyright (c) 2021 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, topo_has_dut=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         :param topo_has_dut: If False we want to test back to back test on TG.
104         :type always_same_link: bool
105         :type topo_has_dut: bool
106         :raises RuntimeError: If not enough nodes for path.
107         """
108         nodes = self._nodes
109         if len(nodes) < 2 and topo_has_dut:
110             raise RuntimeError(u"Not enough nodes to compute path")
111
112         for idx in range(0, len(nodes) - 1):
113             topo = Topology()
114             node1 = nodes[idx]
115             n1_list = self._nodes_filter[idx]
116             if topo_has_dut:
117                 node2 = nodes[idx + 1]
118                 n2_list = self._nodes_filter[idx + 1]
119             else:
120                 node2 = node1
121                 n2_list = n1_list
122
123             links = topo.get_active_connecting_links(
124                 node1, node2, filter_list_node1=n1_list,
125                 filter_list_node2=n2_list
126             )
127             if not links:
128                 raise RuntimeError(
129                     f"No link between {node1[u'host']} and {node2[u'host']}"
130                 )
131
132             # Not using set operations, as we need deterministic order.
133             if always_same_link:
134                 l_set = [link for link in links if link in self._links]
135             else:
136                 l_set = [link for link in links if link not in self._links]
137                 if not l_set:
138                     raise RuntimeError(
139                         f"No free link between {node1[u'host']} and "
140                         f"{node2[u'host']}, all links already used"
141                     )
142
143             if not l_set:
144                 link = links[0]
145             else:
146                 link = l_set[0]
147
148             self._links.append(link)
149
150             use_subsequent = not topo_has_dut
151             interface1 = topo.get_interface_by_link_name(node1, link)
152             interface2 = topo.get_interface_by_link_name(node2, link,
153                                                          use_subsequent)
154             self._path.append((interface1, node1))
155             self._path.append((interface2, node2))
156
157         self._path_iter.extend(self._path)
158         self._path_iter.reverse()
159
160     def next_interface(self):
161         """Path interface iterator.
162
163         :returns: Interface and node or None if not next interface.
164         :rtype: tuple (str, dict)
165
166         .. note:: Call compute_path before.
167         """
168         if not self._path_iter:
169             return None, None
170         return self._path_iter.pop()
171
172     def first_interface(self):
173         """Return first interface on the path.
174
175         :returns: Interface and node.
176         :rtype: tuple (str, dict)
177
178         .. note:: Call compute_path before.
179         """
180         if not self._path:
181             raise RuntimeError(u"No path for topology")
182         return self._path[0]
183
184     def last_interface(self):
185         """Return last interface on the path.
186
187         :returns: Interface and node.
188         :rtype: tuple (str, dict)
189
190         .. note:: Call compute_path before.
191         """
192         if not self._path:
193             raise RuntimeError(u"No path for topology")
194         return self._path[-1]
195
196     def first_ingress_interface(self):
197         """Return first ingress interface on the path.
198
199         :returns: Interface and node.
200         :rtype: tuple (str, dict)
201
202         .. note:: Call compute_path before.
203         """
204         if not self._path:
205             raise RuntimeError(u"No path for topology")
206         return self._path[1]
207
208     def last_egress_interface(self):
209         """Return last egress interface on the path.
210
211         :returns: Interface and node.
212         :rtype: tuple (str, dict)
213
214         .. note:: Call compute_path before.
215         """
216         if not self._path:
217             raise RuntimeError(u"No path for topology")
218         return self._path[-2]
219
220     def compute_circular_topology(self, nodes, filter_list=None, nic_pfs=1,
221             always_same_link=False, topo_has_tg=True, topo_has_dut=True):
222         """Return computed circular path.
223
224         :param nodes: Nodes to append to the path.
225         :param filter_list: Filter criteria list.
226         :param nic_pfs: Number of PF of NIC.
227         :param always_same_link: If True use always same link between two nodes
228             in path. If False use different link (if available)
229             between two nodes if one link was used before.
230         :param topo_has_tg: If True, the topology has a TG node. If False,
231             the topology consists entirely of DUT nodes.
232         :param topo_has_dut: If True, the topology has a DUT node(s). If False,
233             the topology consists entirely of TG nodes.
234         :type nodes: dict
235         :type filter_list: list of strings
236         :type nic_pfs: int
237         :type always_same_link: bool
238         :type topo_has_tg: bool
239         :type topo_has_dut: bool
240         :returns: Topology information dictionary.
241         :rtype: dict
242         :raises RuntimeError: If unsupported combination of parameters.
243         """
244         t_dict = dict()
245         if topo_has_dut:
246             duts = [key for key in nodes if u"DUT" in key]
247             t_dict[u"duts"] = duts
248             t_dict[u"duts_count"] = len(duts)
249             t_dict[u"int"] = u"pf"
250
251         for _ in range(0, nic_pfs // 2):
252             if topo_has_tg:
253                 self.append_node(nodes[u"TG"])
254             if topo_has_dut:
255                 for dut in duts:
256                     self.append_node(nodes[dut], filter_list=filter_list)
257         if topo_has_tg:
258             self.append_node(nodes[u"TG"])
259         self.compute_path(always_same_link, topo_has_dut)
260
261         n_idx = 0 # node index
262         t_idx = 1 # TG interface index
263         d_idx = 0 # DUT interface index
264         prev_host = None
265         while True:
266             interface, node = self.next_interface()
267             if not interface:
268                 break
269             if topo_has_tg and node.get(u"type") == u"TG":
270                 n_pfx = f"TG" # node prefix
271                 p_pfx = f"pf{t_idx}" # physical interface prefix
272                 i_pfx = f"if{t_idx}" # [backwards compatible] interface prefix
273                 n_idx = 0
274                 t_idx = t_idx + 1
275             elif topo_has_tg and topo_has_dut:
276                 # Each node has 2 interfaces, starting with 1
277                 # Calculate prefixes appropriately for current
278                 # path topology nomenclature:
279                 #   tg1_if1 -> dut1_if1 -> dut1_if2 ->
280                 #        [dut2_if1 -> dut2_if2 ...] -> tg1_if2
281                 n_pfx = f"DUT{n_idx // 2 + 1}"
282                 p_pfx = f"pf{d_idx % 2 + t_idx - 1}"
283                 i_pfx = f"if{d_idx % 2 + t_idx - 1}"
284                 n_idx = n_idx + 1
285                 d_idx = d_idx + 1
286             elif not topo_has_tg and always_same_link:
287                 this_host = node.get(u"host")
288                 if prev_host != this_host:
289                     # When moving to a new host in the path,
290                     # increment the node index (n_idx) and
291                     # reset DUT interface index (d_idx) to 1.
292                     n_idx = n_idx + 1
293                     d_idx = 1
294                 n_pfx = f"DUT{n_idx}"
295                 p_pfx = f"pf{d_idx}"
296                 i_pfx = f"if{d_idx}"
297                 d_idx = d_idx + 1
298             else:
299                 raise RuntimeError(u"Unsupported combination of paramters")
300
301             t_dict[f"{n_pfx}"] = node
302             t_dict[f"{n_pfx}_{p_pfx}"] = [interface]
303             t_dict[f"{n_pfx}_{p_pfx}_mac"] = \
304                 [Topology.get_interface_mac(node, interface)]
305             t_dict[f"{n_pfx}_{p_pfx}_vlan"] = \
306                 [Topology.get_interface_vlan(node, interface)]
307             t_dict[f"{n_pfx}_{p_pfx}_pci"] = \
308                 [Topology.get_interface_pci_addr(node, interface)]
309             t_dict[f"{n_pfx}_{p_pfx}_ip4_addr"] = \
310                 [Topology.get_interface_ip4(node, interface)]
311             t_dict[f"{n_pfx}_{p_pfx}_ip4_prefix"] = \
312                 [Topology.get_interface_ip4_prefix_length(node, interface)]
313             if f"{n_pfx}_pf_pci" not in t_dict:
314                 t_dict[f"{n_pfx}_pf_pci"] = []
315             t_dict[f"{n_pfx}_pf_pci"].append(
316                 Topology.get_interface_pci_addr(node, interface))
317             if f"{n_pfx}_pf_keys" not in t_dict:
318                 t_dict[f"{n_pfx}_pf_keys"] = []
319             t_dict[f"{n_pfx}_pf_keys"].append(interface)
320             # Backward compatibility below
321             t_dict[f"{n_pfx.lower()}_{i_pfx}"] = interface
322             t_dict[f"{n_pfx.lower()}_{i_pfx}_mac"] = \
323                 Topology.get_interface_mac(node, interface)
324             t_dict[f"{n_pfx.lower()}_{i_pfx}_pci"] = \
325                 Topology.get_interface_pci_addr(node, interface)
326             t_dict[f"{n_pfx.lower()}_{i_pfx}_ip4_addr"] = \
327                 Topology.get_interface_ip4(node, interface)
328             t_dict[f"{n_pfx.lower()}_{i_pfx}_ip4_prefix"] = \
329                 Topology.get_interface_ip4_prefix_length(node, interface)
330
331         self.clear_path()
332         return t_dict