1 # Copyright (c) 2020 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."""
20 from paramiko.ssh_exception import SSHException
21 from robot.api import logger
23 import resources.libraries.python.DUTSetup as PidLib
25 from resources.libraries.python.Constants import Constants
26 from resources.libraries.python.PapiHistory import PapiHistory
27 from resources.libraries.python.ssh import SSH, SSHTimeout
29 __all__ = [u"VatExecutor"]
32 def cleanup_vat_json_output(json_output, vat_name=None):
33 """Return VAT JSON output cleaned from VAT clutter.
35 Clean up VAT JSON output from clutter like vat# prompts and such.
37 :param json_output: Cluttered JSON output.
38 :param vat_name: Name of the VAT script.
39 :type json_output: JSON
41 :returns: Cleaned up output JSON string.
46 clutter = [u"vat#", u"dump_interface_table error: Misc"]
48 remote_file_path = f"{Constants.REMOTE_FW_DIR}/" \
49 f"{Constants.RESOURCES_TPL_VAT}/{vat_name}"
50 clutter.append(f"{remote_file_path}(2):")
51 for garbage in clutter:
52 retval = retval.replace(garbage, u"")
56 def get_vpp_pid(node):
57 """Get PID of running VPP process.
59 :param node: DUT node.
61 :returns: PID of VPP process / List of PIDs if more VPP processes are
62 running on the DUT node.
65 pid = PidLib.DUTSetup.get_pid(node, u"vpp")
70 """Contains methods for executing VAT commands on DUTs."""
75 self._script_name = None
78 self, vat_name, node, timeout=120, json_out=True,
79 copy_on_execute=False, history=True):
80 """Execute VAT script on remote node, and store the result. There is an
81 option to copy script from local host to remote host before execution.
82 Path is defined automatically.
84 :param vat_name: Name of the vat script file. Only the file name of
85 the script is required, the resources path is prepended
87 :param node: Node to execute the VAT script on.
88 :param timeout: Seconds to allow the script to run.
89 :param json_out: Require JSON output.
90 :param copy_on_execute: If true, copy the file from local host to remote
92 :param history: If true, add command to history.
97 :type copy_on_execute: bool
99 :raises SSHException: If cannot open connection for VAT.
100 :raises SSHTimeout: If VAT execution is timed out.
101 :raises RuntimeError: If VAT script execution fails.
108 f"Cannot open SSH connection to execute VAT command(s) "
109 f"from vat script {vat_name}"
113 ssh.scp(vat_name, vat_name)
114 remote_file_path = vat_name
116 with open(vat_name, u"rt") as vat_file:
117 for line in vat_file:
118 PapiHistory.add_to_papi_history(
119 node, line.replace(u"\n", u""), papi=False
122 remote_file_path = f"{Constants.REMOTE_FW_DIR}/" \
123 f"{Constants.RESOURCES_TPL_VAT}/{vat_name}"
125 cmd = f"{Constants.VAT_BIN_NAME}" \
126 f"{u' json' if json_out is True else u''} " \
127 f"in {remote_file_path} script"
129 ret_code, stdout, stderr = ssh.exec_command_sudo(
130 cmd=cmd, timeout=timeout
133 logger.error(f"VAT script execution timeout: {cmd}")
136 raise RuntimeError(f"VAT script execution failed: {cmd}")
138 self._ret_code = ret_code
139 self._stdout = stdout
140 self._stderr = stderr
141 self._script_name = vat_name
143 def write_and_execute_script(
144 self, node, tmp_fn, commands, timeout=300, json_out=False):
145 """Write VAT commands to the script, copy it to node and execute it.
147 :param node: VPP node.
148 :param tmp_fn: Path to temporary file script.
149 :param commands: VAT command list.
150 :param timeout: Seconds to allow the script to run.
151 :param json_out: Require JSON output.
158 with open(tmp_fn, u"wt") as tmp_f:
159 tmp_f.writelines(commands)
162 tmp_fn, node, timeout=timeout, json_out=json_out,
167 def execute_script_json_out(self, vat_name, node, timeout=120):
168 """Pass all arguments to 'execute_script' method, then cleanup returned
171 :param vat_name: Name of the vat script file. Only the file name of
172 the script is required, the resources path is prepended
174 :param node: Node to execute the VAT script on.
175 :param timeout: Seconds to allow the script to run.
180 self.execute_script(vat_name, node, timeout, json_out=True)
181 self._stdout = cleanup_vat_json_output(self._stdout, vat_name=vat_name)
183 def script_should_have_failed(self):
184 """Read return code from last executed script and raise exception if the
185 script didn't fail."""
186 if self._ret_code is None:
187 raise Exception(u"First execute the script!")
188 if self._ret_code == 0:
189 raise AssertionError(
190 f"VAT Script execution passed, but failure was expected: "
191 f"{self._script_name}"
194 def script_should_have_passed(self):
195 """Read return code from last executed script and raise exception if the
197 if self._ret_code is None:
198 raise Exception(u"First execute the script!")
199 if self._ret_code != 0:
200 raise AssertionError(
201 f"VAT Script execution failed, but success was expected: "
202 f"{self._script_name}"
205 def get_script_stdout(self):
206 """Returns value of stdout from last executed script."""
209 def get_script_stderr(self):
210 """Returns value of stderr from last executed script."""
214 def cmd_from_template(node, vat_template_file, json_param=True, **vat_args):
215 """Execute VAT script on specified node. This method supports
216 script templates with parameters.
218 :param node: Node in topology on witch the script is executed.
219 :param vat_template_file: Template file of VAT script.
220 :param json_param: Require JSON mode.
221 :param vat_args: Arguments to the template file.
222 :returns: List of JSON objects returned by VAT.
224 with VatTerminal(node, json_param=json_param) as vat:
225 return vat.vat_terminal_exec_cmd_from_template(
226 vat_template_file, **vat_args
231 """VAT interactive terminal.
233 :param node: Node to open VAT terminal on.
234 :param json_param: Defines if outputs from VAT are in JSON format.
237 :type json_param: bool
241 __VAT_PROMPT = (u"vat# ", )
242 __LINUX_PROMPT = (u":~# ", u":~$ ", u"~]$ ", u"~]# ")
244 def __init__(self, node, json_param=True):
245 json_text = u" json" if json_param else u""
246 self.json = json_param
249 self._ssh.connect(self._node)
251 self._tty = self._ssh.interactive_terminal_open()
254 f"Cannot open interactive terminal on node "
255 f"{self._node[u'host']}"
260 self._ssh.interactive_terminal_exec_command(
261 self._tty, f"sudo -S {Constants.VAT_BIN_NAME}{json_text}",
269 vpp_pid = get_vpp_pid(self._node)
271 if isinstance(vpp_pid, int):
272 logger.trace(f"VPP running on node {self._node[u'host']}")
275 f"More instances of VPP running "
276 f"on node {self._node[u'host']}."
279 logger.error(f"VPP not running on node {self._node[u'host']}.")
281 f"Failed to open VAT console on node {self._node[u'host']}"
284 self._exec_failure = False
285 self.vat_stdout = None
290 def __exit__(self, exc_type, exc_val, exc_tb):
291 self.vat_terminal_close()
293 def vat_terminal_exec_cmd(self, cmd):
294 """Execute command on the opened VAT terminal.
296 :param cmd: Command to be executed.
298 :returns: Command output in python representation of JSON format or
299 None if not in JSON mode.
301 PapiHistory.add_to_papi_history(self._node, cmd, papi=False)
302 logger.debug(f"Executing command in VAT terminal: {cmd}")
304 out = self._ssh.interactive_terminal_exec_command(
305 self._tty, cmd, self.__VAT_PROMPT
307 self.vat_stdout = out
309 self._exec_failure = True
310 vpp_pid = get_vpp_pid(self._node)
312 if isinstance(vpp_pid, int):
313 msg = f"VPP running on node {self._node[u'host']} " \
314 f"but VAT command {cmd} execution failed."
316 msg = f"More instances of VPP running on node " \
317 f"{self._node[u'host']}. VAT command {cmd} " \
320 msg = f"VPP not running on node {self._node[u'host']}. " \
321 f"VAT command {cmd} execution failed."
322 raise RuntimeError(msg)
324 logger.debug(f"VAT output: {out}")
326 obj_start = out.find(u"{")
327 obj_end = out.rfind(u"}")
328 array_start = out.find(u"[")
329 array_end = out.rfind(u"]")
331 if obj_start == -1 and array_start == -1:
332 raise RuntimeError(f"VAT command {cmd}: no JSON data.")
334 if obj_start < array_start or array_start == -1:
341 json_out = json.loads(out)
346 def vat_terminal_close(self):
347 """Close VAT terminal."""
348 # interactive terminal is dead, we only need to close session
349 if not self._exec_failure:
351 self._ssh.interactive_terminal_exec_command(
352 self._tty, u"quit", self.__LINUX_PROMPT
355 vpp_pid = get_vpp_pid(self._node)
357 if isinstance(vpp_pid, int):
359 f"VPP running on node {self._node[u'host']}."
363 f"More instances of VPP running "
364 f"on node {self._node[u'host']}."
368 f"VPP not running on node {self._node[u'host']}."
371 f"Failed to close VAT console "
372 f"on node {self._node[u'host']}"
375 self._ssh.interactive_terminal_close(self._tty)
378 f"Cannot close interactive terminal "
379 f"on node {self._node[u'host']}"
382 def vat_terminal_exec_cmd_from_template(self, vat_template_file, **args):
383 """Execute VAT script from a file.
385 :param vat_template_file: Template file name of a VAT script.
386 :param args: Dictionary of parameters for VAT script.
387 :returns: List of JSON objects returned by VAT.
389 file_path = f"{Constants.RESOURCES_TPL_VAT}/{vat_template_file}"
391 with open(file_path, u"rt") as template_file:
392 cmd_template = template_file.readlines()
394 for line_tmpl in cmd_template:
395 vat_cmd = line_tmpl.format(**args)
396 ret.append(self.vat_terminal_exec_cmd(vat_cmd.replace(u"\n", u"")))