UTI: Export results
[csit.git] / resources / libraries / python / VPPUtil.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 """VPP util library."""
15
16 from robot.api import logger
17
18 from resources.libraries.python.Constants import Constants
19 from resources.libraries.python.DUTSetup import DUTSetup
20 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
21 from resources.libraries.python.model.ExportResult import (
22     export_dut_type_and_version
23 )
24 from resources.libraries.python.ssh import exec_cmd_no_error, exec_cmd
25 from resources.libraries.python.topology import Topology, SocketType, NodeType
26
27
28 class VPPUtil:
29     """General class for any VPP related methods/functions."""
30
31     @staticmethod
32     def show_vpp_settings(node, *additional_cmds):
33         """Print default VPP settings. In case others are needed, can be
34         accepted as next parameters (each setting one parameter), preferably
35         in form of a string.
36
37         :param node: VPP node.
38         :param additional_cmds: Additional commands that the vpp should print
39             settings for.
40         :type node: dict
41         :type additional_cmds: tuple
42         """
43         def_setting_tb_displayed = {
44             u"IPv6 FIB": u"ip6 fib",
45             u"IPv4 FIB": u"ip fib",
46             u"Interface IP": u"int addr",
47             u"Interfaces": u"int",
48             u"ARP": u"ip arp",
49             u"Errors": u"err"
50         }
51
52         if additional_cmds:
53             for cmd in additional_cmds:
54                 def_setting_tb_displayed[f"Custom Setting: {cmd}"] = cmd
55
56         for _, cmd in def_setting_tb_displayed.items():
57             command = f"vppctl sh {cmd}"
58             exec_cmd_no_error(node, command, timeout=30, sudo=True)
59
60     @staticmethod
61     def restart_vpp_service(node, node_key=None):
62         """Restart VPP service on the specified topology node.
63
64         Disconnect possibly connected PAPI executor.
65
66         :param node: Topology node.
67         :param node_key: Topology node key.
68         :type node: dict
69         :type node_key: str
70         """
71         # Containers have a separate lifecycle, but better be safe.
72         PapiSocketExecutor.disconnect_all_sockets_by_node(node)
73         DUTSetup.restart_service(node, Constants.VPP_UNIT)
74         if node_key:
75             Topology.add_new_socket(
76                 node, SocketType.PAPI, node_key, Constants.SOCKSVR_PATH)
77             Topology.add_new_socket(
78                 node, SocketType.STATS, node_key, Constants.SOCKSTAT_PATH)
79
80     @staticmethod
81     def restart_vpp_service_on_all_duts(nodes):
82         """Restart VPP service on all DUT nodes.
83
84         :param nodes: Topology nodes.
85         :type nodes: dict
86         """
87         for node_key, node in nodes.items():
88             if node[u"type"] == NodeType.DUT:
89                 VPPUtil.restart_vpp_service(node, node_key)
90
91     @staticmethod
92     def stop_vpp_service(node, node_key=None):
93         """Stop VPP service on the specified topology node.
94
95         Disconnect possibly connected PAPI executor.
96
97         :param node: Topology node.
98         :param node_key: Topology node key.
99         :type node: dict
100         :type node_key: str
101         """
102         # Containers have a separate lifecycle, but better be safe.
103         PapiSocketExecutor.disconnect_all_sockets_by_node(node)
104         DUTSetup.stop_service(node, Constants.VPP_UNIT)
105         if node_key:
106             Topology.del_node_socket_id(node, SocketType.PAPI, node_key)
107             Topology.del_node_socket_id(node, SocketType.STATS, node_key)
108
109     @staticmethod
110     def stop_vpp_service_on_all_duts(nodes):
111         """Stop VPP service on all DUT nodes.
112
113         :param nodes: Topology nodes.
114         :type nodes: dict
115         """
116         for node_key, node in nodes.items():
117             if node[u"type"] == NodeType.DUT:
118                 VPPUtil.stop_vpp_service(node, node_key)
119
120     @staticmethod
121     def verify_vpp_installed(node):
122         """Verify that VPP is installed on the specified topology node.
123
124         :param node: Topology node.
125         :type node: dict
126         """
127         DUTSetup.verify_program_installed(node, u"vpp")
128
129     @staticmethod
130     def adjust_privileges(node):
131         """Adjust privileges to control VPP without sudo.
132
133         :param node: Topology node.
134         :type node: dict
135         """
136         cmd = u"chmod -R o+rwx /run/vpp"
137         exec_cmd_no_error(
138             node, cmd, sudo=True, message=u"Failed to adjust privileges!",
139             retries=120)
140
141     @staticmethod
142     def verify_vpp_started(node):
143         """Verify that VPP is started on the specified topology node.
144
145         :param node: Topology node.
146         :type node: dict
147         """
148         cmd = u"echo \"show pci\" | sudo socat - UNIX-CONNECT:/run/vpp/cli.sock"
149         exec_cmd_no_error(
150             node, cmd, sudo=False, message=u"VPP failed to start!", retries=120
151         )
152
153         cmd = u"vppctl show pci 2>&1 | fgrep -v \"Connection refused\" | " \
154               u"fgrep -v \"No such file or directory\""
155         exec_cmd_no_error(
156             node, cmd, sudo=True, message=u"VPP failed to start!", retries=120
157         )
158
159         # Properly enable cards in case they were disabled. This will be
160         # followed in https://jira.fd.io/browse/VPP-1934.
161         cmd = u"for i in $(sudo vppctl sho int | grep Eth | cut -d' ' -f1); do"\
162               u" sudo vppctl set int sta $i up; done"
163         exec_cmd(node, cmd, sudo=False)
164
165     @staticmethod
166     def verify_vpp(node):
167         """Verify that VPP is installed and started on the specified topology
168         node. Adjust privileges so user can connect without sudo.
169
170         :param node: Topology node.
171         :type node: dict
172         :raises RuntimeError: If VPP service fails to start.
173         """
174         DUTSetup.verify_program_installed(node, 'vpp')
175         try:
176             # Verify responsiveness of vppctl.
177             VPPUtil.verify_vpp_started(node)
178             # Adjust privileges.
179             VPPUtil.adjust_privileges(node)
180             # Verify responsiveness of PAPI.
181             VPPUtil.show_log(node)
182             VPPUtil.vpp_show_version(node)
183         finally:
184             DUTSetup.get_service_logs(node, Constants.VPP_UNIT)
185
186     @staticmethod
187     def verify_vpp_on_all_duts(nodes):
188         """Verify that VPP is installed and started on all DUT nodes.
189
190         :param nodes: Nodes in the topology.
191         :type nodes: dict
192         """
193         for node in nodes.values():
194             if node[u"type"] == NodeType.DUT:
195                 VPPUtil.verify_vpp(node)
196
197     @staticmethod
198     def vpp_show_version(
199             node, remote_vpp_socket=Constants.SOCKSVR_PATH, log=True):
200         """Run "show_version" PAPI command.
201
202         Socket is configurable, so VPP inside container can be accessed.
203         The result is exported to JSON UTI output as "dut-version".
204
205         :param node: Node to run command on.
206         :param remote_vpp_socket: Path to remote socket to target VPP.
207         :param log: If true, show the result in Robot log.
208         :type node: dict
209         :type remote_vpp_socket: str
210         :type log: bool
211         :returns: VPP version.
212         :rtype: str
213         :raises RuntimeError: If PAPI connection fails.
214         :raises AssertionError: If PAPI retcode is nonzero.
215         """
216         cmd = u"show_version"
217         with PapiSocketExecutor(node, remote_vpp_socket) as papi_exec:
218             reply = papi_exec.add(cmd).get_reply()
219         if log:
220             logger.info(f"VPP version: {reply[u'version']}\n")
221         version = f"{reply[u'version']}"
222         export_dut_type_and_version(u"VPP", version)
223         return version
224
225     @staticmethod
226     def show_vpp_version_on_all_duts(nodes):
227         """Show VPP version verbose on all DUTs.
228
229         :param nodes: Nodes in the topology.
230         :type nodes: dict
231         """
232         for node in nodes.values():
233             if node[u"type"] == NodeType.DUT:
234                 VPPUtil.vpp_show_version(node)
235
236     @staticmethod
237     def vpp_show_interfaces(node):
238         """Run "show interface" CLI command.
239
240         :param node: Node to run command on.
241         :type node: dict
242         """
243
244         cmd = u"sw_interface_dump"
245         args = dict(
246             name_filter_valid=False,
247             name_filter=u""
248         )
249         err_msg = f"Failed to get interface dump on host {node[u'host']}"
250         with PapiSocketExecutor(node) as papi_exec:
251             details = papi_exec.add(cmd, **args).get_details(err_msg)
252
253         for if_dump in details:
254             if_dump[u"l2_address"] = str(if_dump[u"l2_address"])
255             if_dump[u"b_dmac"] = str(if_dump[u"b_dmac"])
256             if_dump[u"b_smac"] = str(if_dump[u"b_smac"])
257             if_dump[u"flags"] = if_dump[u"flags"].value
258             if_dump[u"type"] = if_dump[u"type"].value
259             if_dump[u"link_duplex"] = if_dump[u"link_duplex"].value
260             if_dump[u"sub_if_flags"] = if_dump[u"sub_if_flags"].value \
261                 if hasattr(if_dump[u"sub_if_flags"], u"value") \
262                 else int(if_dump[u"sub_if_flags"])
263         # TODO: return only base data
264         logger.trace(f"Interface data of host {node[u'host']}:\n{details}")
265
266     @staticmethod
267     def vpp_enable_traces_on_dut(node, fail_on_error=False):
268         """Enable vpp packet traces on the DUT node.
269
270         :param node: DUT node to set up.
271         :param fail_on_error: If True, keyword fails if an error occurs,
272             otherwise passes.
273         :type node: dict
274         :type fail_on_error: bool
275         """
276         cmds = [
277             u"trace add dpdk-input 50",
278             u"trace add vhost-user-input 50",
279             u"trace add memif-input 50",
280             u"trace add avf-input 50"
281         ]
282
283         for cmd in cmds:
284             try:
285                 PapiSocketExecutor.run_cli_cmd_on_all_sockets(node, cmd)
286             except AssertionError:
287                 if fail_on_error:
288                     raise
289
290     @staticmethod
291     def vpp_enable_traces_on_all_duts(nodes, fail_on_error=False):
292         """Enable vpp packet traces on all DUTs in the given topology.
293
294         :param nodes: Nodes in the topology.
295         :param fail_on_error: If True, keyword fails if an error occurs,
296             otherwise passes.
297         :type nodes: dict
298         :type fail_on_error: bool
299         """
300         for node in nodes.values():
301             if node[u"type"] == NodeType.DUT:
302                 VPPUtil.vpp_enable_traces_on_dut(node, fail_on_error)
303
304     @staticmethod
305     def vpp_enable_elog_traces(node):
306         """Enable API/CLI/Barrier traces on the specified topology node.
307
308         :param node: Topology node.
309         :type node: dict
310         """
311         try:
312             PapiSocketExecutor.run_cli_cmd_on_all_sockets(
313                 node, u"event-logger trace api cli barrier")
314         except AssertionError:
315             # Perhaps an older VPP build is tested.
316             PapiSocketExecutor.run_cli_cmd_on_all_sockets(
317                 node, u"elog trace api cli barrier")
318
319     @staticmethod
320     def vpp_enable_elog_traces_on_all_duts(nodes):
321         """Enable API/CLI/Barrier traces on all DUTs in the given topology.
322
323         :param nodes: Nodes in the topology.
324         :type nodes: dict
325         """
326         for node in nodes.values():
327             if node[u"type"] == NodeType.DUT:
328                 VPPUtil.vpp_enable_elog_traces(node)
329
330     @staticmethod
331     def show_event_logger(node):
332         """Show event logger on the specified topology node.
333
334         :param node: Topology node.
335         :type node: dict
336         """
337         PapiSocketExecutor.run_cli_cmd_on_all_sockets(
338             node, u"show event-logger")
339
340     @staticmethod
341     def show_event_logger_on_all_duts(nodes):
342         """Show event logger on all DUTs in the given topology.
343
344         :param nodes: Nodes in the topology.
345         :type nodes: dict
346         """
347         for node in nodes.values():
348             if node[u"type"] == NodeType.DUT:
349                 VPPUtil.show_event_logger(node)
350
351     @staticmethod
352     def show_log(node):
353         """Show logging on the specified topology node.
354
355         :param node: Topology node.
356         :type node: dict
357         """
358         PapiSocketExecutor.run_cli_cmd(node, u"show logging")
359
360     @staticmethod
361     def show_log_on_all_duts(nodes):
362         """Show logging on all DUTs in the given topology.
363
364         :param nodes: Nodes in the topology.
365         :type nodes: dict
366         """
367         for node in nodes.values():
368             if node[u"type"] == NodeType.DUT:
369                 VPPUtil.show_log(node)
370
371     @staticmethod
372     def vpp_show_threads(node):
373         """Show VPP threads on node.
374
375         :param node: Node to run command on.
376         :type node: dict
377         :returns: VPP thread data.
378         :rtype: list
379         """
380         cmd = u"show_threads"
381         with PapiSocketExecutor(node) as papi_exec:
382             reply = papi_exec.add(cmd).get_reply()
383
384         threads_data = reply[u"thread_data"]
385         logger.trace(f"show threads:\n{threads_data}")
386
387         return threads_data
388
389     @staticmethod
390     def vpp_add_graph_node_next(node, graph_node_name, graph_next_name):
391         """Set the next node for a given node.
392
393         :param node: Node to run command on.
394         :param graph_node_name: Graph node to add the next node on.
395         :param graph_next_name: Graph node to add as the next node.
396         :type node: dict
397         :type graph_node_name: str
398         :type graph_next_name: str
399         :returns: The index of the next graph node.
400         :rtype: int
401         """
402         cmd = u"add_node_next"
403         args = dict(
404             node_name=graph_node_name,
405             next_name=graph_next_name
406         )
407         with PapiSocketExecutor(node) as papi_exec:
408             reply = papi_exec.add(cmd, **args).get_reply()
409
410         return reply[u"next_index"]