9db53d3a3679abba3f8d874e45c2705f18563b82
[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 execute_script_json_out(self, vat_name, node, timeout=10):
88         """Pass all arguments to 'execute_script' method, then cleanup returned
89         json output."""
90         self.execute_script(vat_name, node, timeout, json_out=True)
91         self._stdout = cleanup_vat_json_output(self._stdout)
92
93     @staticmethod
94     def _delete_files(node, *files):
95         """Use SSH to delete the specified files on node.
96
97         :param node: Node in topology.
98         :param files: Files to delete.
99         :type node: dict
100         :type files: iterable
101         """
102
103         ssh = SSH()
104         ssh.connect(node)
105         files = " ".join([str(x) for x in files])
106         ssh.exec_command("rm {0}".format(files))
107
108     def script_should_have_failed(self):
109         """Read return code from last executed script and raise exception if the
110         script didn't fail."""
111         if self._ret_code is None:
112             raise Exception("First execute the script!")
113         if self._ret_code == 0:
114             raise AssertionError(
115                 "Script execution passed, but failure was expected")
116
117     def script_should_have_passed(self):
118         """Read return code from last executed script and raise exception if the
119         script failed."""
120         if self._ret_code is None:
121             raise Exception("First execute the script!")
122         if self._ret_code != 0:
123             raise AssertionError(
124                 "Script execution failed, but success was expected")
125
126     def get_script_stdout(self):
127         """Returns value of stdout from last executed script."""
128         return self._stdout
129
130     def get_script_stderr(self):
131         """Returns value of stderr from last executed script."""
132         return self._stderr
133
134     @staticmethod
135     def cmd_from_template(node, vat_template_file, **vat_args):
136         """Execute VAT script on specified node. This method supports
137         script templates with parameters.
138
139         :param node: Node in topology on witch the script is executed.
140         :param vat_template_file: Template file of VAT script.
141         :param vat_args: Arguments to the template file.
142         :return: List of JSON objects returned by VAT.
143         """
144         with VatTerminal(node) as vat:
145             return vat.vat_terminal_exec_cmd_from_template(vat_template_file,
146                                                            **vat_args)
147
148
149 class VatTerminal(object):
150     """VAT interactive terminal.
151
152     :param node: Node to open VAT terminal on.
153     :param json_param: Defines if outputs from VAT are in JSON format.
154     Default is True.
155     :type node: dict
156     :type json_param: bool
157
158     """
159
160     __VAT_PROMPT = ("vat# ", )
161     __LINUX_PROMPT = (":~$ ", "~]$ ")
162
163     def __init__(self, node, json_param=True):
164         json_text = ' json' if json_param else ''
165         self.json = json_param
166         self._node = node
167         self._ssh = SSH()
168         self._ssh.connect(self._node)
169         self._tty = self._ssh.interactive_terminal_open()
170         self._ssh.interactive_terminal_exec_command(
171             self._tty,
172             'sudo -S {}{}'.format(Constants.VAT_BIN_NAME, json_text),
173             self.__VAT_PROMPT)
174         self._exec_failure = False
175
176     def __enter__(self):
177         return self
178
179     def __exit__(self, exc_type, exc_val, exc_tb):
180         self.vat_terminal_close()
181
182     def vat_terminal_exec_cmd(self, cmd):
183         """Execute command on the opened VAT terminal.
184
185         :param cmd: Command to be executed.
186
187         :return: Command output in python representation of JSON format or
188         None if not in JSON mode.
189         """
190         VatHistory.add_to_vat_history(self._node, cmd)
191         logger.debug("Executing command in VAT terminal: {}".format(cmd))
192         try:
193             out = self._ssh.interactive_terminal_exec_command(self._tty, cmd,
194                                                               self.__VAT_PROMPT)
195         except:
196             self._exec_failure = True
197             raise
198
199         logger.debug("VAT output: {}".format(out))
200         if self.json:
201             obj_start = out.find('{')
202             obj_end = out.rfind('}')
203             array_start = out.find('[')
204             array_end = out.rfind(']')
205
206             if obj_start == -1 and array_start == -1:
207                 raise RuntimeError("VAT: no JSON data.")
208
209             if obj_start < array_start or array_start == -1:
210                 start = obj_start
211                 end = obj_end + 1
212             else:
213                 start = array_start
214                 end = array_end + 1
215             out = out[start:end]
216             json_out = json.loads(out)
217             return json_out
218         else:
219             return None
220
221     def vat_terminal_close(self):
222         """Close VAT terminal."""
223         # interactive terminal is dead, we only need to close session
224         if not self._exec_failure:
225             self._ssh.interactive_terminal_exec_command(self._tty,
226                                                         'quit',
227                                                         self.__LINUX_PROMPT)
228         self._ssh.interactive_terminal_close(self._tty)
229
230     def vat_terminal_exec_cmd_from_template(self, vat_template_file, **args):
231         """Execute VAT script from a file.
232
233         :param vat_template_file: Template file name of a VAT script.
234         :param args: Dictionary of parameters for VAT script.
235         :return: List of JSON objects returned by VAT.
236         """
237         file_path = '{}/{}'.format(Constants.RESOURCES_TPL_VAT,
238                                    vat_template_file)
239         with open(file_path, 'r') as template_file:
240             cmd_template = template_file.readlines()
241         ret = []
242         for line_tmpl in cmd_template:
243             vat_cmd = line_tmpl.format(**args)
244             ret.append(self.vat_terminal_exec_cmd(vat_cmd.replace('\n', '')))
245         return ret