1 # Copyright (c) 2018 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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """VAT executor library."""
18 from paramiko.ssh_exception import SSHException
19 from robot.api import logger
21 from resources.libraries.python.ssh import SSH, SSHTimeout
22 from resources.libraries.python.constants import Constants
23 from resources.libraries.python.VatHistory import VatHistory
25 __all__ = ['VatExecutor']
28 def cleanup_vat_json_output(json_output, vat_name=None):
29 """Return VAT JSON output cleaned from VAT clutter.
31 Clean up VAT JSON output from clutter like vat# prompts and such.
33 :param json_output: Cluttered JSON output.
34 :param vat_name: Name of the VAT script.
35 :type json_output: JSON
37 :returns: Cleaned up output JSON string.
42 clutter = ['vat#', 'dump_interface_table error: Misc']
44 remote_file_path = '{0}/{1}/{2}'.format(Constants.REMOTE_FW_DIR,
45 Constants.RESOURCES_TPL_VAT,
47 clutter.append("{0}(2):".format(remote_file_path))
48 for garbage in clutter:
49 retval = retval.replace(garbage, '')
53 def get_vpp_pid(node):
54 """Get PID of running VPP process.
56 :param node: DUT node.
58 :returns: PID of VPP process / List of PIDs if more VPP processes are
59 running on the DUT node.
62 import resources.libraries.python.DUTSetup as PidLib
63 pid = PidLib.DUTSetup.get_vpp_pid(node)
67 class VatExecutor(object):
68 """Contains methods for executing VAT commands on DUTs."""
73 self._script_name = None
75 def execute_script(self, vat_name, node, timeout=120, json_out=True,
76 copy_on_execute=False):
77 """Execute VAT script on remote node, and store the result. There is an
78 option to copy script from local host to remote host before execution.
79 Path is defined automatically.
81 :param vat_name: Name of the vat script file. Only the file name of
82 the script is required, the resources path is prepended
84 :param node: Node to execute the VAT script on.
85 :param timeout: Seconds to allow the script to run.
86 :param json_out: Require JSON output.
87 :param copy_on_execute: If true, copy the file from local host to remote
93 :type copy_on_execute: bool
94 :raises SSHException: If cannot open connection for VAT.
95 :raises SSHTimeout: If VAT execution is timed out.
96 :raises RuntimeError: If VAT script execution fails.
102 raise SSHException("Cannot open SSH connection to execute VAT "
103 "command(s) from vat script {name}"
104 .format(name=vat_name))
107 ssh.scp(vat_name, vat_name)
108 remote_file_path = vat_name
109 with open(vat_name, 'r') as vat_file:
110 for line in vat_file:
111 VatHistory.add_to_vat_history(node, line.replace('\n', ''))
113 remote_file_path = '{0}/{1}/{2}'.format(Constants.REMOTE_FW_DIR,
114 Constants.RESOURCES_TPL_VAT,
117 cmd = "{vat_bin} {json} in {vat_path} script".format(
118 vat_bin=Constants.VAT_BIN_NAME,
119 json="json" if json_out is True else "",
120 vat_path=remote_file_path)
123 ret_code, stdout, stderr = ssh.exec_command_sudo(cmd=cmd,
126 logger.error("VAT script execution timeout: {0}".format(cmd))
129 raise RuntimeError("VAT script execution failed: {0}".format(cmd))
131 self._ret_code = ret_code
132 self._stdout = stdout
133 self._stderr = stderr
134 self._script_name = vat_name
136 def execute_script_json_out(self, vat_name, node, timeout=120):
137 """Pass all arguments to 'execute_script' method, then cleanup returned
140 :param vat_name: Name of the vat script file. Only the file name of
141 the script is required, the resources path is prepended
143 :param node: Node to execute the VAT script on.
144 :param timeout: Seconds to allow the script to run.
149 self.execute_script(vat_name, node, timeout, json_out=True)
150 self._stdout = cleanup_vat_json_output(self._stdout, vat_name=vat_name)
152 def script_should_have_failed(self):
153 """Read return code from last executed script and raise exception if the
154 script didn't fail."""
155 if self._ret_code is None:
156 raise Exception("First execute the script!")
157 if self._ret_code == 0:
158 raise AssertionError(
159 "VAT Script execution passed, but failure was expected: {cmd}"
160 .format(cmd=self._script_name))
162 def script_should_have_passed(self):
163 """Read return code from last executed script and raise exception if the
165 if self._ret_code is None:
166 raise Exception("First execute the script!")
167 if self._ret_code != 0:
168 raise AssertionError(
169 "VAT Script execution failed, but success was expected: {cmd}"
170 .format(cmd=self._script_name))
172 def get_script_stdout(self):
173 """Returns value of stdout from last executed script."""
176 def get_script_stderr(self):
177 """Returns value of stderr from last executed script."""
181 def cmd_from_template(node, vat_template_file, **vat_args):
182 """Execute VAT script on specified node. This method supports
183 script templates with parameters.
185 :param node: Node in topology on witch the script is executed.
186 :param vat_template_file: Template file of VAT script.
187 :param vat_args: Arguments to the template file.
188 :returns: List of JSON objects returned by VAT.
190 with VatTerminal(node) as vat:
191 return vat.vat_terminal_exec_cmd_from_template(vat_template_file,
195 class VatTerminal(object):
196 """VAT interactive terminal.
198 :param node: Node to open VAT terminal on.
199 :param json_param: Defines if outputs from VAT are in JSON format.
202 :type json_param: bool
206 __VAT_PROMPT = ("vat# ", )
207 __LINUX_PROMPT = (":~# ", ":~$ ", "~]$ ", "~]# ")
209 def __init__(self, node, json_param=True):
210 json_text = ' json' if json_param else ''
211 self.json = json_param
214 self._ssh.connect(self._node)
216 self._tty = self._ssh.interactive_terminal_open()
218 raise RuntimeError("Cannot open interactive terminal on node {0}".
223 self._ssh.interactive_terminal_exec_command(
225 'sudo -S {0}{1}'.format(Constants.VAT_BIN_NAME, json_text),
232 vpp_pid = get_vpp_pid(self._node)
234 if isinstance(vpp_pid, int):
235 logger.trace("VPP running on node {0}".
236 format(self._node['host']))
238 logger.error("More instances of VPP running on node {0}.".
239 format(self._node['host']))
241 logger.error("VPP not running on node {0}.".
242 format(self._node['host']))
243 raise RuntimeError("Failed to open VAT console on node {0}".
244 format(self._node['host']))
246 self._exec_failure = False
247 self.vat_stdout = None
252 def __exit__(self, exc_type, exc_val, exc_tb):
253 self.vat_terminal_close()
255 def vat_terminal_exec_cmd(self, cmd):
256 """Execute command on the opened VAT terminal.
258 :param cmd: Command to be executed.
260 :returns: Command output in python representation of JSON format or
261 None if not in JSON mode.
263 VatHistory.add_to_vat_history(self._node, cmd)
264 logger.debug("Executing command in VAT terminal: {0}".format(cmd))
266 out = self._ssh.interactive_terminal_exec_command(self._tty, cmd,
268 self.vat_stdout = out
270 self._exec_failure = True
271 vpp_pid = get_vpp_pid(self._node)
273 if isinstance(vpp_pid, int):
274 raise RuntimeError("VPP running on node {0} but VAT command"
275 " {1} execution failed.".
276 format(self._node['host'], cmd))
278 raise RuntimeError("More instances of VPP running on node "
279 "{0}. VAT command {1} execution failed.".
280 format(self._node['host'], cmd))
282 raise RuntimeError("VPP not running on node {0}. VAT command "
283 "{1} execution failed.".
284 format(self._node['host'], cmd))
286 logger.debug("VAT output: {0}".format(out))
288 obj_start = out.find('{')
289 obj_end = out.rfind('}')
290 array_start = out.find('[')
291 array_end = out.rfind(']')
293 if obj_start == -1 and array_start == -1:
294 raise RuntimeError("VAT command {0}: no JSON data.".format(cmd))
296 if obj_start < array_start or array_start == -1:
303 json_out = json.loads(out)
308 def vat_terminal_close(self):
309 """Close VAT terminal."""
310 # interactive terminal is dead, we only need to close session
311 if not self._exec_failure:
313 self._ssh.interactive_terminal_exec_command(self._tty,
317 vpp_pid = get_vpp_pid(self._node)
319 if isinstance(vpp_pid, int):
320 logger.trace("VPP running on node {0}.".
321 format(self._node['host']))
323 logger.error("More instances of VPP running on node "
324 "{0}.".format(self._node['host']))
326 logger.error("VPP not running on node {0}.".
327 format(self._node['host']))
328 raise RuntimeError("Failed to close VAT console on node {0}".
329 format(self._node['host']))
331 self._ssh.interactive_terminal_close(self._tty)
333 raise RuntimeError("Cannot close interactive terminal on node {0}".
334 format(self._node['host']))
336 def vat_terminal_exec_cmd_from_template(self, vat_template_file, **args):
337 """Execute VAT script from a file.
339 :param vat_template_file: Template file name of a VAT script.
340 :param args: Dictionary of parameters for VAT script.
341 :returns: List of JSON objects returned by VAT.
343 file_path = '{}/{}'.format(Constants.RESOURCES_TPL_VAT,
345 with open(file_path, 'r') as template_file:
346 cmd_template = template_file.readlines()
348 for line_tmpl in cmd_template:
349 vat_cmd = line_tmpl.format(**args)
350 ret.append(self.vat_terminal_exec_cmd(vat_cmd.replace('\n', '')))