IPsec Multi-Tunnel performance test suite
[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
249     def __enter__(self):
250         return self
251
252     def __exit__(self, exc_type, exc_val, exc_tb):
253         self.vat_terminal_close()
254
255     def vat_terminal_exec_cmd(self, cmd):
256         """Execute command on the opened VAT terminal.
257
258         :param cmd: Command to be executed.
259
260         :return: Command output in python representation of JSON format or
261         None if not in JSON mode.
262         """
263         VatHistory.add_to_vat_history(self._node, cmd)
264         logger.debug("Executing command in VAT terminal: {}".format(cmd))
265         try:
266             out = self._ssh.interactive_terminal_exec_command(self._tty, cmd,
267                                                               self.__VAT_PROMPT)
268         except:
269             self._exec_failure = True
270             raise
271
272         logger.debug("VAT output: {}".format(out))
273         if self.json:
274             obj_start = out.find('{')
275             obj_end = out.rfind('}')
276             array_start = out.find('[')
277             array_end = out.rfind(']')
278
279             if obj_start == -1 and array_start == -1:
280                 raise RuntimeError("VAT: no JSON data.")
281
282             if obj_start < array_start or array_start == -1:
283                 start = obj_start
284                 end = obj_end + 1
285             else:
286                 start = array_start
287                 end = array_end + 1
288             out = out[start:end]
289             json_out = json.loads(out)
290             return json_out
291         else:
292             return None
293
294     def vat_terminal_close(self):
295         """Close VAT terminal."""
296         # interactive terminal is dead, we only need to close session
297         if not self._exec_failure:
298             self._ssh.interactive_terminal_exec_command(self._tty,
299                                                         'quit',
300                                                         self.__LINUX_PROMPT)
301         self._ssh.interactive_terminal_close(self._tty)
302
303     def vat_terminal_exec_cmd_from_template(self, vat_template_file, **args):
304         """Execute VAT script from a file.
305
306         :param vat_template_file: Template file name of a VAT script.
307         :param args: Dictionary of parameters for VAT script.
308         :return: List of JSON objects returned by VAT.
309         """
310         file_path = '{}/{}'.format(Constants.RESOURCES_TPL_VAT,
311                                    vat_template_file)
312         with open(file_path, 'r') as template_file:
313             cmd_template = template_file.readlines()
314         ret = []
315         for line_tmpl in cmd_template:
316             vat_cmd = line_tmpl.format(**args)
317             ret.append(self.vat_terminal_exec_cmd(vat_cmd.replace('\n', '')))
318         return ret