diff options
Diffstat (limited to 'resources/libraries')
-rw-r--r-- | resources/libraries/python/Constants.py | 6 | ||||
-rw-r--r-- | resources/libraries/python/PapiHistory.py | 24 | ||||
-rw-r--r-- | resources/libraries/python/SetupFramework.py | 8 | ||||
-rw-r--r-- | resources/libraries/python/VatExecutor.py | 397 | ||||
-rw-r--r-- | resources/libraries/python/VatJsonUtil.py | 218 |
5 files changed, 10 insertions, 643 deletions
diff --git a/resources/libraries/python/Constants.py b/resources/libraries/python/Constants.py index 89a302b41d..4cf6681324 100644 --- a/resources/libraries/python/Constants.py +++ b/resources/libraries/python/Constants.py @@ -143,9 +143,6 @@ class Constants: # Templates location RESOURCES_TPL = u"resources/templates" - # vat templates location - RESOURCES_TPL_VAT = u"resources/templates/vat" - # Kubernetes templates location RESOURCES_TPL_K8S = u"resources/templates/kubernetes" @@ -161,9 +158,6 @@ class Constants: # VPP Communications Library LD_PRELOAD library VCL_LDPRELOAD_LIBRARY = u"/usr/lib/x86_64-linux-gnu/libvcl_ldpreload.so" - # OpenVPP VAT binary name - VAT_BIN_NAME = u"vpp_api_test" - # VPP service unit name VPP_UNIT = u"vpp" diff --git a/resources/libraries/python/PapiHistory.py b/resources/libraries/python/PapiHistory.py index 32429c4f64..18b2774908 100644 --- a/resources/libraries/python/PapiHistory.py +++ b/resources/libraries/python/PapiHistory.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Cisco and/or its affiliates. +# Copyright (c) 2023 Cisco and/or its affiliates. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: @@ -48,7 +48,7 @@ class PapiHistory: PapiHistory.reset_papi_history(node) @staticmethod - def add_to_papi_history(node, csit_papi_command, papi=True, **kwargs): + def add_to_papi_history(node, csit_papi_command, **kwargs): """Add command to PAPI command history on DUT node. Repr strings are used for argument values. @@ -70,29 +70,17 @@ class PapiHistory: VPP Stats: vpp-stats(path=['^/if', '/err/ip4-input', '/sys/node/ip4-input']) - VAT: - sw_interface_set_flags sw_if_index 3 admin-up link-up - :param node: DUT node to add command to PAPI command history for. :param csit_papi_command: Command to be added to PAPI command history. - :param papi: Says if the command to store is PAPi or VAT. Remove when - VAT executor is completely removed. :param kwargs: Optional key-value arguments. :type node: dict :type csit_papi_command: str - :type papi: bool :type kwargs: dict """ - if papi: - args = list() - for key, val in kwargs.items(): - args.append(f"{key}={val!r}") - item = f"{csit_papi_command}({u','.join(args)})" - else: - # This else part is here to store VAT commands. - # VAT history is not used. - # TODO: Remove when VatExecutor is completely removed. - item = f"{csit_papi_command}" + args = list() + for key, val in kwargs.items(): + args.append(f"{key}={val!r}") + item = f"{csit_papi_command}({u','.join(args)})" DICT__DUTS_PAPI_HISTORY[node[u"host"]].append(item) @staticmethod diff --git a/resources/libraries/python/SetupFramework.py b/resources/libraries/python/SetupFramework.py index bde018a4e0..3c381166c2 100644 --- a/resources/libraries/python/SetupFramework.py +++ b/resources/libraries/python/SetupFramework.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 Cisco and/or its affiliates. +# Copyright (c) 2023 Cisco and/or its affiliates. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: @@ -259,9 +259,9 @@ def cleanup_node(node, results=None, logs=None): class SetupFramework: """Setup suite run on topology nodes. - Many VAT/CLI based tests need the scripts at remote hosts before executing - them. This class packs the whole testing directory and copies it over - to all nodes in topology under /tmp/ + Some tests need the scripts at remote hosts before executing them. + This class packs the whole testing directory and copies it over + to all nodes in topology under /tmp/. """ @staticmethod diff --git a/resources/libraries/python/VatExecutor.py b/resources/libraries/python/VatExecutor.py deleted file mode 100644 index 63f46c8b6d..0000000000 --- a/resources/libraries/python/VatExecutor.py +++ /dev/null @@ -1,397 +0,0 @@ -# Copyright (c) 2021 Cisco and/or its affiliates. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at: -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""VAT executor library.""" - -import json - -from os import remove - -from paramiko.ssh_exception import SSHException -from robot.api import logger - -import resources.libraries.python.DUTSetup as PidLib - -from resources.libraries.python.Constants import Constants -from resources.libraries.python.PapiHistory import PapiHistory -from resources.libraries.python.ssh import SSH, SSHTimeout - -__all__ = [u"VatExecutor"] - - -def cleanup_vat_json_output(json_output, vat_name=None): - """Return VAT JSON output cleaned from VAT clutter. - - Clean up VAT JSON output from clutter like vat# prompts and such. - - :param json_output: Cluttered JSON output. - :param vat_name: Name of the VAT script. - :type json_output: JSON - :type vat_name: str - :returns: Cleaned up output JSON string. - :rtype: JSON - """ - - retval = json_output - clutter = [u"vat#", u"dump_interface_table error: Misc"] - if vat_name: - remote_file_path = f"{Constants.REMOTE_FW_DIR}/" \ - f"{Constants.RESOURCES_TPL_VAT}/{vat_name}" - clutter.append(f"{remote_file_path}(2):") - for garbage in clutter: - retval = retval.replace(garbage, u"") - return retval - - -def get_vpp_pid(node): - """Get PID of running VPP process. - - :param node: DUT node. - :type node: dict - :returns: PID of VPP process / List of PIDs if more VPP processes are - running on the DUT node. - :rtype: int or list - """ - pid = PidLib.DUTSetup.get_pid(node, u"vpp") - return pid - - -class VatExecutor: - """Contains methods for executing VAT commands on DUTs.""" - def __init__(self): - self._stdout = None - self._stderr = None - self._ret_code = None - self._script_name = None - - def execute_script( - self, vat_name, node, timeout=120, json_out=True, - copy_on_execute=False, history=True): - """Execute VAT script on remote node, and store the result. There is an - option to copy script from local host to remote host before execution. - Path is defined automatically. - - :param vat_name: Name of the vat script file. Only the file name of - the script is required, the resources path is prepended - automatically. - :param node: Node to execute the VAT script on. - :param timeout: Seconds to allow the script to run. - :param json_out: Require JSON output. - :param copy_on_execute: If true, copy the file from local host to remote - before executing. - :param history: If true, add command to history. - :type vat_name: str - :type node: dict - :type timeout: int - :type json_out: bool - :type copy_on_execute: bool - :type history: bool - :raises SSHException: If cannot open connection for VAT. - :raises SSHTimeout: If VAT execution is timed out. - :raises RuntimeError: If VAT script execution fails. - """ - ssh = SSH() - try: - ssh.connect(node) - except: - raise SSHException( - f"Cannot open SSH connection to execute VAT command(s) " - f"from vat script {vat_name}" - ) - - if copy_on_execute: - ssh.scp(vat_name, vat_name) - remote_file_path = vat_name - if history: - with open(vat_name, u"rt") as vat_file: - for line in vat_file: - PapiHistory.add_to_papi_history( - node, line.replace(u"\n", u""), papi=False - ) - else: - remote_file_path = f"{Constants.REMOTE_FW_DIR}/" \ - f"{Constants.RESOURCES_TPL_VAT}/{vat_name}" - - cmd = f"{Constants.VAT_BIN_NAME}" \ - f"{u' json' if json_out is True else u''} " \ - f"in {remote_file_path} script" - try: - ret_code, stdout, stderr = ssh.exec_command_sudo( - cmd=cmd, timeout=timeout - ) - except SSHTimeout: - logger.error(f"VAT script execution timeout: {cmd}") - raise - except Exception: - raise RuntimeError(f"VAT script execution failed: {cmd}") - - self._ret_code = ret_code - self._stdout = stdout - self._stderr = stderr - self._script_name = vat_name - - def write_and_execute_script( - self, node, tmp_fn, commands, timeout=300, json_out=False): - """Write VAT commands to the script, copy it to node and execute it. - - :param node: VPP node. - :param tmp_fn: Path to temporary file script. - :param commands: VAT command list. - :param timeout: Seconds to allow the script to run. - :param json_out: Require JSON output. - :type node: dict - :type tmp_fn: str - :type commands: list - :type timeout: int - :type json_out: bool - """ - with open(tmp_fn, u"wt") as tmp_f: - tmp_f.writelines(commands) - - self.execute_script( - tmp_fn, node, timeout=timeout, json_out=json_out, - copy_on_execute=True - ) - remove(tmp_fn) - - def execute_script_json_out(self, vat_name, node, timeout=120): - """Pass all arguments to 'execute_script' method, then cleanup returned - json output. - - :param vat_name: Name of the vat script file. Only the file name of - the script is required, the resources path is prepended - automatically. - :param node: Node to execute the VAT script on. - :param timeout: Seconds to allow the script to run. - :type vat_name: str - :type node: dict - :type timeout: int - """ - self.execute_script(vat_name, node, timeout, json_out=True) - self._stdout = cleanup_vat_json_output(self._stdout, vat_name=vat_name) - - def script_should_have_failed(self): - """Read return code from last executed script and raise exception if the - script didn't fail.""" - if self._ret_code is None: - raise Exception(u"First execute the script!") - if self._ret_code == 0: - raise AssertionError( - f"VAT Script execution passed, but failure was expected: " - f"{self._script_name}" - ) - - def script_should_have_passed(self): - """Read return code from last executed script and raise exception if the - script failed.""" - if self._ret_code is None: - raise Exception(u"First execute the script!") - if self._ret_code != 0: - raise AssertionError( - f"VAT Script execution failed, but success was expected: " - f"{self._script_name}" - ) - - def get_script_stdout(self): - """Returns value of stdout from last executed script.""" - return self._stdout - - def get_script_stderr(self): - """Returns value of stderr from last executed script.""" - return self._stderr - - @staticmethod - def cmd_from_template(node, vat_template_file, json_param=True, **vat_args): - """Execute VAT script on specified node. This method supports - script templates with parameters. - - :param node: Node in topology on witch the script is executed. - :param vat_template_file: Template file of VAT script. - :param json_param: Require JSON mode. - :param vat_args: Arguments to the template file. - :returns: List of JSON objects returned by VAT. - """ - with VatTerminal(node, json_param=json_param) as vat: - return vat.vat_terminal_exec_cmd_from_template( - vat_template_file, **vat_args - ) - - -class VatTerminal: - """VAT interactive terminal. - - :param node: Node to open VAT terminal on. - :param json_param: Defines if outputs from VAT are in JSON format. - Default is True. - :type node: dict - :type json_param: bool - - """ - - __VAT_PROMPT = (u"vat# ", ) - __LINUX_PROMPT = (u":~# ", u":~$ ", u"~]$ ", u"~]# ") - - def __init__(self, node, json_param=True): - json_text = u" json" if json_param else u"" - self.json = json_param - self._node = node - self._ssh = SSH() - self._ssh.connect(self._node) - try: - self._tty = self._ssh.interactive_terminal_open() - except Exception: - raise RuntimeError( - f"Cannot open interactive terminal on node " - f"{self._node[u'host']}" - ) - - for _ in range(3): - try: - self._ssh.interactive_terminal_exec_command( - self._tty, f"sudo -S {Constants.VAT_BIN_NAME}{json_text}", - self.__VAT_PROMPT - ) - except Exception: - continue - else: - break - else: - vpp_pid = get_vpp_pid(self._node) - if vpp_pid: - if isinstance(vpp_pid, int): - logger.trace(f"VPP running on node {self._node[u'host']}") - else: - logger.error( - f"More instances of VPP running " - f"on node {self._node[u'host']}." - ) - else: - logger.error(f"VPP not running on node {self._node[u'host']}.") - raise RuntimeError( - f"Failed to open VAT console on node {self._node[u'host']}" - ) - - self._exec_failure = False - self.vat_stdout = None - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.vat_terminal_close() - - def vat_terminal_exec_cmd(self, cmd): - """Execute command on the opened VAT terminal. - - :param cmd: Command to be executed. - - :returns: Command output in python representation of JSON format or - None if not in JSON mode. - """ - PapiHistory.add_to_papi_history(self._node, cmd, papi=False) - logger.debug(f"Executing command in VAT terminal: {cmd}") - try: - out = self._ssh.interactive_terminal_exec_command( - self._tty, cmd, self.__VAT_PROMPT - ) - self.vat_stdout = out - except Exception: - self._exec_failure = True - vpp_pid = get_vpp_pid(self._node) - if vpp_pid: - if isinstance(vpp_pid, int): - msg = f"VPP running on node {self._node[u'host']} " \ - f"but VAT command {cmd} execution failed." - else: - msg = f"More instances of VPP running on node " \ - f"{self._node[u'host']}. VAT command {cmd} " \ - f"execution failed." - else: - msg = f"VPP not running on node {self._node[u'host']}. " \ - f"VAT command {cmd} execution failed." - raise RuntimeError(msg) - - logger.debug(f"VAT output: {out}") - if self.json: - obj_start = out.find(u"{") - obj_end = out.rfind(u"}") - array_start = out.find(u"[") - array_end = out.rfind(u"]") - - if obj_start == -1 and array_start == -1: - raise RuntimeError(f"VAT command {cmd}: no JSON data.") - - if obj_start < array_start or array_start == -1: - start = obj_start - end = obj_end + 1 - else: - start = array_start - end = array_end + 1 - out = out[start:end] - json_out = json.loads(out) - return json_out - - return None - - def vat_terminal_close(self): - """Close VAT terminal.""" - # interactive terminal is dead, we only need to close session - if not self._exec_failure: - try: - self._ssh.interactive_terminal_exec_command( - self._tty, u"quit", self.__LINUX_PROMPT - ) - except Exception: - vpp_pid = get_vpp_pid(self._node) - if vpp_pid: - if isinstance(vpp_pid, int): - logger.trace( - f"VPP running on node {self._node[u'host']}." - ) - else: - logger.error( - f"More instances of VPP running " - f"on node {self._node[u'host']}." - ) - else: - logger.error( - f"VPP not running on node {self._node[u'host']}." - ) - raise RuntimeError( - f"Failed to close VAT console " - f"on node {self._node[u'host']}" - ) - try: - self._ssh.interactive_terminal_close(self._tty) - except Exception: - raise RuntimeError( - f"Cannot close interactive terminal " - f"on node {self._node[u'host']}" - ) - - def vat_terminal_exec_cmd_from_template(self, vat_template_file, **args): - """Execute VAT script from a file. - - :param vat_template_file: Template file name of a VAT script. - :param args: Dictionary of parameters for VAT script. - :returns: List of JSON objects returned by VAT. - """ - file_path = f"{Constants.RESOURCES_TPL_VAT}/{vat_template_file}" - - with open(file_path, u"rt") as template_file: - cmd_template = template_file.readlines() - ret = list() - for line_tmpl in cmd_template: - vat_cmd = line_tmpl.format(**args) - ret.append(self.vat_terminal_exec_cmd(vat_cmd.replace(u"\n", u""))) - return ret diff --git a/resources/libraries/python/VatJsonUtil.py b/resources/libraries/python/VatJsonUtil.py deleted file mode 100644 index 3e956e790d..0000000000 --- a/resources/libraries/python/VatJsonUtil.py +++ /dev/null @@ -1,218 +0,0 @@ -# Copyright (c) 2021 Cisco and/or its affiliates. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at: -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Utilities to work with JSON data format from VAT.""" - -from robot.api import logger - -from resources.libraries.python.parsers.JsonParser import JsonParser - - -class VatJsonUtil: - """Utilities to work with JSON data format from VAT.""" - - @staticmethod - def _convert_mac_to_number_list(mac_address): - """Convert MAC address string to list of decimal numbers. - - Converts a ":" separated MAC address to decimal number list as used - in JSON interface dump. - - :param mac_address: MAC address. - :type mac_address: str - :returns: List representation of MAC address. - :rtype: list - """ - list_mac = list() - for num in mac_address.split(u":"): - list_mac.append(int(num, 16)) - return list_mac - - @staticmethod - def get_vpp_interface_by_mac(interfaces_list, mac_address): - """Return interface dictionary from interface_list by MAC address. - - Extracts interface dictionary from all of the interfaces in interfaces - list parsed from JSON according to mac_address of the interface. - - :param interfaces_list: Interfaces parsed from JSON. - :param mac_address: MAC address of interface we are looking for. - :type interfaces_list: dict - :type mac_address: str - :returns: Interface from JSON. - :rtype: dict - """ - interface_dict = dict() - list_mac_address = VatJsonUtil._convert_mac_to_number_list(mac_address) - logger.trace( - f"MAC address {mac_address} converted to list {list_mac_address}." - ) - for interface in interfaces_list: - # TODO: create vat json integrity checking and move there - if u"l2_address" not in interface: - raise KeyError( - u"key l2_address not found in interface dict." - u"Probably input list is not parsed from correct VAT " - u"json output." - ) - if u"l2_address_length" not in interface: - raise KeyError( - u"key l2_address_length not found in interface " - u"dict. Probably input list is not parsed from correct " - u"VAT json output." - ) - mac_from_json = interface[u"l2_address"][:6] - if mac_from_json == list_mac_address: - if interface[u"l2_address_length"] != 6: - raise ValueError(u"l2_address_length value is not 6.") - interface_dict = interface - break - return interface_dict - - @staticmethod - def update_vpp_interface_data_from_json(node, interface_dump_json): - """Update vpp node data in node__DICT from JSON interface dump. - - This method updates vpp interface names and sw if indexes according to - interface MAC addresses found in interface_dump_json. - - :param node: Node dictionary. - :param interface_dump_json: JSON output from dump_interface_list VAT - command. - :type node: dict - :type interface_dump_json: str - """ - interface_list = JsonParser().parse_data(interface_dump_json) - for ifc in node[u"interfaces"].values(): - if_mac = ifc[u"mac_address"] - interface_dict = VatJsonUtil.get_vpp_interface_by_mac( - interface_list, if_mac - ) - if not interface_dict: - logger.trace(f"Interface {ifc} not found by MAC {if_mac}") - ifc[u"vpp_sw_index"] = None - continue - ifc[u"name"] = interface_dict[u"interface_name"] - ifc[u"vpp_sw_index"] = interface_dict[u"sw_if_index"] - ifc[u"mtu"] = interface_dict[u"mtu"] - - @staticmethod - def get_interface_sw_index_from_json(interface_dump_json, interface_name): - """Get sw_if_index from given JSON output by interface name. - - :param interface_dump_json: JSON output from dump_interface_list VAT - command. - :param interface_name: Interface name. - :type interface_dump_json: str - :type interface_name: str - :returns: SW interface index. - :rtype: int - :raises ValueError: If interface not found in interface_dump_json. - """ - logger.trace(interface_dump_json) - interface_list = JsonParser().parse_data(interface_dump_json) - for interface in interface_list: - try: - if interface[u"interface_name"] == interface_name: - index = interface[u"sw_if_index"] - logger.debug( - f"Interface with name {interface_name} " - f"has sw_if_index {index}." - ) - return index - except KeyError: - pass - raise ValueError(f"Interface with name {interface_name} not found.") - - @staticmethod - def get_interface_name_from_json(interface_dump_json, sw_if_index): - """Get interface name from given JSON output by sw_if_index. - - :param interface_dump_json: JSON output from dump_interface_list VAT - command. - :param sw_if_index: SW interface index. - :type interface_dump_json: str - :type sw_if_index: int - :returns: Interface name. - :rtype: str - :raises ValueError: If interface not found in interface_dump_json. - """ - logger.trace(interface_dump_json) - interface_list = JsonParser().parse_data(interface_dump_json) - for interface in interface_list: - try: - if interface[u"sw_if_index"] == sw_if_index: - interface_name = interface[u"interface_name"] - logger.debug( - f"Interface with sw_if_index {sw_if_index} " - f"has name {interface_name}." - ) - return interface_name - except KeyError: - pass - raise ValueError(f"Interface with sw_if_index {sw_if_index} not found.") - - @staticmethod - def get_interface_mac_from_json(interface_dump_json, sw_if_index): - """Get interface MAC address from given JSON output by sw_if_index. - - :param interface_dump_json: JSON output from dump_interface_list VAT - command. - :param sw_if_index: SW interface index. - :type interface_dump_json: str - :type sw_if_index: int - :returns: Interface MAC address. - :rtype: str - :raises ValueError: If interface not found in interface_dump_json. - """ - logger.trace(interface_dump_json) - interface_list = JsonParser().parse_data(interface_dump_json) - for interface in interface_list: - try: - if interface[u"sw_if_index"] == sw_if_index: - mac_from_json = interface[u"l2_address"][:6] \ - if u"l2_address" in list(interface.keys()) else u"" - mac_address = u":".join( - f"{item:02x}" for item in mac_from_json - ) - logger.debug( - f"Interface with sw_if_index {sw_if_index} " - f"has MAC address {mac_address}." - ) - return mac_address - except KeyError: - pass - raise ValueError(f"Interface with sw_if_index {sw_if_index} not found.") - - @staticmethod - def verify_vat_retval(vat_out, exp_retval=0, err_msg=u"VAT cmd failed"): - """Verify return value of VAT command. - - VAT command JSON output should be object (dict in python) or array. We - are looking for something like this: { "retval": 0 }. Verification is - skipped if VAT output does not contain return value element or root - elemet is array. - - :param vat_out: VAT command output in python representation of JSON. - :param exp_retval: Expected return value (default 0). - :err_msg: Message to be displayed in case of error (optional). - :type vat_out: dict or list - :type exp_retval: int - :type err_msg: str - :raises RuntimeError: If VAT command return value is incorrect. - """ - if isinstance(vat_out, dict): - retval = vat_out.get(u"retval") - if retval is not None: - if retval != exp_retval: - raise RuntimeError(err_msg) |