CSIT-651 Add keywords and template for memif
[csit.git] / resources / libraries / python / VatExecutor.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 """VAT executor library."""
15
16 import json
17
18 from robot.api import logger
19
20 from resources.libraries.python.ssh import SSH
21 from resources.libraries.python.constants import Constants
22 from resources.libraries.python.VatHistory import VatHistory
23
24
25 __all__ = ['VatExecutor']
26
27
28 def cleanup_vat_json_output(json_output):
29     """Return VAT JSON output cleaned from VAT clutter.
30
31     Clean up VAT JSON output from clutter like vat# prompts and such.
32
33     :param json_output: Cluttered JSON output.
34     :returns: Cleaned up output JSON string.
35     """
36
37     retval = json_output
38     clutter = ['vat#', 'dump_interface_table error: Misc']
39     for garbage in clutter:
40         retval = retval.replace(garbage, '')
41     return retval
42
43
44 class VatExecutor(object):
45     """Contains methods for executing VAT commands on DUTs."""
46     def __init__(self):
47         self._stdout = None
48         self._stderr = None
49         self._ret_code = None
50
51     def execute_script(self, vat_name, node, timeout=10, json_out=True):
52         """Copy local_path script to node, execute it and return result.
53
54         :param vat_name: Name of the vat script file. Only the file name of
55         the script is required, the resources path is prepended automatically.
56         :param node: Node to execute the VAT script on.
57         :param timeout: Seconds to allow the script to run.
58         :param json_out: Require JSON output.
59         :return: (rc, stdout, stderr) tuple.
60         """
61
62         ssh = SSH()
63         ssh.connect(node)
64
65         remote_file_path = '{0}/{1}/{2}'.format(Constants.REMOTE_FW_DIR,
66                                                 Constants.RESOURCES_TPL_VAT,
67                                                 vat_name)
68         # TODO this overwrites the output if the vat script has been used twice
69         # remote_file_out = remote_file_path + ".out"
70
71         cmd = "sudo -S {vat} {json} < {input}".format(
72             vat=Constants.VAT_BIN_NAME,
73             json="json" if json_out is True else "",
74             input=remote_file_path)
75         (ret_code, stdout, stderr) = ssh.exec_command(cmd, timeout)
76         self._ret_code = ret_code
77         self._stdout = stdout
78         self._stderr = stderr
79
80         logger.trace("Command '{0}' returned {1}'".format(cmd, self._ret_code))
81         logger.trace("stdout: '{0}'".format(self._stdout))
82         logger.trace("stderr: '{0}'".format(self._stderr))
83
84         # TODO: download vpp_api_test output file
85         # self._delete_files(node, remote_file_path, remote_file_out)
86
87     def scp_and_execute_script(self, vat_name, node, timeout=10, json_out=True):
88         """Copy vat_name script to node, execute it and return result.
89
90         :param vat_name: Name of the vat script file.
91         Full path and name of the script is required.
92         :param node: Node to execute the VAT script on.
93         :param timeout: Seconds to allow the script to run.
94         :param json_out: Require JSON output.
95         :type vat_name: str
96         :type node: dict
97         :type timeout: int
98         :type json_out: bool
99         :returns: Status code, stdout and stderr of executed script
100         :rtype: tuple
101         """
102
103         ssh = SSH()
104         ssh.connect(node)
105
106         ssh.scp(vat_name, vat_name)
107
108         cmd = "sudo -S {vat} {json} < {input}".format(
109             json="json" if json_out is True else "",
110             vat=Constants.VAT_BIN_NAME,
111             input=vat_name)
112         (ret_code, stdout, stderr) = ssh.exec_command(cmd, timeout)
113         self._ret_code = ret_code
114         self._stdout = stdout
115         self._stderr = stderr
116
117         logger.trace("Command '{0}' returned {1}'".format(cmd, self._ret_code))
118         logger.trace("stdout: '{0}'".format(self._stdout))
119         logger.trace("stderr: '{0}'".format(self._stderr))
120
121         self._delete_files(node, vat_name)
122
123     def scp_and_execute_cli_script(self, fname, node, timeout=10,
124                                    json_out=True):
125         """Copy vat_name script to node, execute it and return result.
126
127         :param fname: Name of the VPP script file.
128         Full path and name of the script is required.
129         :param node: Node to execute the VPP script on.
130         :param timeout: Seconds to allow the script to run.
131         :param json_out: Require JSON output.
132         :type vat_name: str
133         :type node: dict
134         :type timeout: int
135         :type json_out: bool
136         :returns: Status code, stdout and stderr of executed script
137         :rtype: tuple
138         """
139
140         ssh = SSH()
141         ssh.connect(node)
142
143         ssh.scp(fname, fname)
144
145         cmd = "{vat} {json}".format(json="json" if json_out is True else "",
146                                     vat=Constants.VAT_BIN_NAME)
147         cmd_input = "exec exec {0}".format(fname)
148         (ret_code, stdout, stderr) = ssh.exec_command_sudo(cmd, cmd_input,
149                                                            timeout)
150         self._ret_code = ret_code
151         self._stdout = stdout
152         self._stderr = stderr
153
154         logger.trace("Command '{0}' returned {1}'".format(cmd, self._ret_code))
155         logger.trace("stdout: '{0}'".format(self._stdout))
156         logger.trace("stderr: '{0}'".format(self._stderr))
157
158         self._delete_files(node, fname)
159
160     def execute_script_json_out(self, vat_name, node, timeout=10):
161         """Pass all arguments to 'execute_script' method, then cleanup returned
162         json output."""
163         self.execute_script(vat_name, node, timeout, json_out=True)
164         self._stdout = cleanup_vat_json_output(self._stdout)
165
166     @staticmethod
167     def _delete_files(node, *files):
168         """Use SSH to delete the specified files on node.
169
170         :param node: Node in topology.
171         :param files: Files to delete.
172         :type node: dict
173         :type files: iterable
174         """
175
176         ssh = SSH()
177         ssh.connect(node)
178         files = " ".join([str(x) for x in files])
179         ssh.exec_command("rm {0}".format(files))
180
181     def script_should_have_failed(self):
182         """Read return code from last executed script and raise exception if the
183         script didn't fail."""
184         if self._ret_code is None:
185             raise Exception("First execute the script!")
186         if self._ret_code == 0:
187             raise AssertionError(
188                 "Script execution passed, but failure was expected")
189
190     def script_should_have_passed(self):
191         """Read return code from last executed script and raise exception if the
192         script failed."""
193         if self._ret_code is None:
194             raise Exception("First execute the script!")
195         if self._ret_code != 0:
196             raise AssertionError(
197                 "Script execution failed, but success was expected")
198
199     def get_script_stdout(self):
200         """Returns value of stdout from last executed script."""
201         return self._stdout
202
203     def get_script_stderr(self):
204         """Returns value of stderr from last executed script."""
205         return self._stderr
206
207     @staticmethod
208     def cmd_from_template(node, vat_template_file, **vat_args):
209         """Execute VAT script on specified node. This method supports
210         script templates with parameters.
211
212         :param node: Node in topology on witch the script is executed.
213         :param vat_template_file: Template file of VAT script.
214         :param vat_args: Arguments to the template file.
215         :return: List of JSON objects returned by VAT.
216         """
217         with VatTerminal(node) as vat:
218             return vat.vat_terminal_exec_cmd_from_template(vat_template_file,
219                                                            **vat_args)
220
221
222 class VatTerminal(object):
223     """VAT interactive terminal.
224
225     :param node: Node to open VAT terminal on.
226     :param json_param: Defines if outputs from VAT are in JSON format.
227     Default is True.
228     :type node: dict
229     :type json_param: bool
230
231     """
232
233     __VAT_PROMPT = ("vat# ", )
234     __LINUX_PROMPT = (":~$ ", "~]$ ")
235
236     def __init__(self, node, json_param=True):
237         json_text = ' json' if json_param else ''
238         self.json = json_param
239         self._node = node
240         self._ssh = SSH()
241         self._ssh.connect(self._node)
242         self._tty = self._ssh.interactive_terminal_open()
243         self._ssh.interactive_terminal_exec_command(
244             self._tty,
245             'sudo -S {}{}'.format(Constants.VAT_BIN_NAME, json_text),
246             self.__VAT_PROMPT)
247         self._exec_failure = False
248         self.vat_stdout = None
249
250     def __enter__(self):
251         return self
252
253     def __exit__(self, exc_type, exc_val, exc_tb):
254         self.vat_terminal_close()
255
256     def vat_terminal_exec_cmd(self, cmd):
257         """Execute command on the opened VAT terminal.
258
259         :param cmd: Command to be executed.
260
261         :return: Command output in python representation of JSON format or
262         None if not in JSON mode.
263         """
264         VatHistory.add_to_vat_history(self._node, cmd)
265         logger.debug("Executing command in VAT terminal: {}".format(cmd))
266         try:
267             out = self._ssh.interactive_terminal_exec_command(self._tty, cmd,
268                                                               self.__VAT_PROMPT)
269             self.vat_stdout = out
270         except:
271             self._exec_failure = True
272             raise
273
274         logger.debug("VAT output: {}".format(out))
275         if self.json:
276             obj_start = out.find('{')
277             obj_end = out.rfind('}')
278             array_start = out.find('[')
279             array_end = out.rfind(']')
280
281             if obj_start == -1 and array_start == -1:
282                 raise RuntimeError("VAT: no JSON data.")
283
284             if obj_start < array_start or array_start == -1:
285                 start = obj_start
286                 end = obj_end + 1
287             else:
288                 start = array_start
289                 end = array_end + 1
290             out = out[start:end]
291             json_out = json.loads(out)
292             return json_out
293         else:
294             return None
295
296     def vat_terminal_close(self):
297         """Close VAT terminal."""
298         # interactive terminal is dead, we only need to close session
299         if not self._exec_failure:
300             self._ssh.interactive_terminal_exec_command(self._tty,
301                                                         'quit',
302                                                         self.__LINUX_PROMPT)
303         self._ssh.interactive_terminal_close(self._tty)
304
305     def vat_terminal_exec_cmd_from_template(self, vat_template_file, **args):
306         """Execute VAT script from a file.
307
308         :param vat_template_file: Template file name of a VAT script.
309         :param args: Dictionary of parameters for VAT script.
310         :return: List of JSON objects returned by VAT.
311         """
312         file_path = '{}/{}'.format(Constants.RESOURCES_TPL_VAT,
313                                    vat_template_file)
314         with open(file_path, 'r') as template_file:
315             cmd_template = template_file.readlines()
316         ret = []
317         for line_tmpl in cmd_template:
318             vat_cmd = line_tmpl.format(**args)
319             ret.append(self.vat_terminal_exec_cmd(vat_cmd.replace('\n', '')))
320         return ret