From: Vratko Polak Date: Wed, 27 Feb 2019 11:55:56 +0000 (+0100) Subject: CSIT-1450: PAPI executor X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=commitdiff_plain;h=935734b04269b8fe5348e1c2b168dd5c6cd9339a;hp=ddbfd9e68e81e77bd253c8b6cd81839ec3bd2715 CSIT-1450: PAPI executor Change-Id: I4c756cc4b29901184594a728f6184c30cadd9c1a Signed-off-by: Vratko Polak --- diff --git a/resources/libraries/python/IPsecUtil.py b/resources/libraries/python/IPsecUtil.py index 2479ec1822..5c9a08fc31 100644 --- a/resources/libraries/python/IPsecUtil.py +++ b/resources/libraries/python/IPsecUtil.py @@ -20,7 +20,6 @@ from enum import Enum, IntEnum from robot.api import logger from resources.libraries.python.PapiExecutor import PapiExecutor -from resources.libraries.python.PapiErrors import PapiError from resources.libraries.python.topology import Topology from resources.libraries.python.VatExecutor import VatExecutor from resources.libraries.python.VatJsonUtil import VatJsonUtil @@ -252,36 +251,21 @@ class IPsecUtil(object): :type node: dict :type protocol: IPsecProto :type index: int + :raises RuntimeError: If failed to select IPsec backend or if no API + reply received. """ - # TODO: move composition of api data to separate method - api_data = list() - api = dict(api_name='ipsec_select_backend') - api_args = dict(protocol=protocol) - api_args['index'] = index - api['api_args'] = api_args - api_data.append(api) - - api_reply = None - with PapiExecutor(node) as papi_executor: - papi_executor.execute_papi(api_data) - try: - papi_executor.papi_should_have_passed() - except AssertionError: - raise PapiError('Failed to select IPsec backend on host {host}'. - format(host=node['host'])) - api_reply = papi_executor.get_papi_reply() - - if api_reply is not None: - api_r = api_reply[0]['api_reply']['ipsec_select_backend_reply'] - if api_r['retval'] == 0: - logger.trace('IPsec backend successfully selected on host ' - '{host}'.format(host=node['host'])) - else: - raise PapiError('Failed to select IPsec backend on host {host}'. - format(host=node['host'])) - else: - raise PapiError('No reply received for ipsec_select_backend API ' - 'command on host {host}'.format(host=node['host'])) + + cmd = 'ipsec_select_backend' + cmd_reply = 'ipsec_select_backend_reply' + err_msg = 'Failed to select IPsec backend on host {host}'.format( + host=node['host']) + args = dict(protocol=protocol, index=index) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).execute_should_pass(err_msg) + data = papi_resp.reply[0]['api_reply'][cmd_reply] + if data['retval'] != 0: + raise RuntimeError('Failed to select IPsec backend on host {host}'. + format(host=node['host'])) @staticmethod def vpp_ipsec_backend_dump(node): @@ -290,34 +274,20 @@ class IPsecUtil(object): :param node: VPP node to dump IPsec backend on. :type node: dict """ - # TODO: move composition of api data to separate method - api_data = list() - api = dict(api_name='ipsec_backend_dump') - api_args = dict() - api['api_args'] = api_args - api_data.append(api) - - api_reply = None - with PapiExecutor(node) as papi_executor: - papi_executor.execute_papi(api_data) - try: - papi_executor.papi_should_have_passed() - except AssertionError: - raise PapiError('Failed to dump IPsec backends on host {host}'. - format(host=node['host'])) - # After API change there is returned VPP internal enum object - # representing VPP IPSEC protocol instead of integer representation - # so JSON fails to decode it - we need to check if it is Python API - # bug or we need to adapt vpp_papi_provider to correctly encode - # such object into JSON - # api_reply = papi_executor.get_papi_reply() - api_reply = papi_executor.get_papi_stdout() - - if api_reply is not None: - logger.trace('IPsec backend dump\n{dump}'.format(dump=api_reply)) - else: - raise PapiError('No reply received for ipsec_select_backend API ' - 'command on host {host}'.format(host=node['host'])) + + err_msg = 'Failed to dump IPsec backends on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add('ipsec_backend_dump').execute_should_pass( + err_msg, process_reply=False) + # After API change there is returned VPP internal enum object + # representing VPP IPSEC protocol instead of integer representation + # so JSON fails to decode it - we need to check if it is Python API + # bug or we need to adapt vpp_papi_provider to correctly encode + # such object into JSON + # logger.trace('IPsec backend dump\n{dump}'. + # format(dump=papi_resp.reply)) + logger.trace('IPsec backend dump\n{dump}'.format(dump=papi_resp.stdout)) @staticmethod def vpp_ipsec_add_sad_entry(node, sad_id, spi, crypto_alg, crypto_key, diff --git a/resources/libraries/python/InterfaceUtil.py b/resources/libraries/python/InterfaceUtil.py index a73b6df8f3..939d8083dc 100644 --- a/resources/libraries/python/InterfaceUtil.py +++ b/resources/libraries/python/InterfaceUtil.py @@ -20,7 +20,6 @@ from robot.api import logger from resources.libraries.python.CpuUtils import CpuUtils from resources.libraries.python.DUTSetup import DUTSetup from resources.libraries.python.PapiExecutor import PapiExecutor -from resources.libraries.python.PapiErrors import PapiError from resources.libraries.python.IPUtil import convert_ipv4_netmask_prefix from resources.libraries.python.IPUtil import IPUtil from resources.libraries.python.parsers.JsonParser import JsonParser @@ -1637,33 +1636,19 @@ class InterfaceUtil(object): :type node: dict :returns: Thread mapping information as a list of dictionaries. :rtype: list - :raises RuntimeError: If failed to run command on host. - :raises PapiError: If no API reply received. """ - api_data = list() - for ifc in node['interfaces'].values(): - if ifc['vpp_sw_index'] is not None: - api = dict(api_name='sw_interface_rx_placement_dump') - api_args = dict(sw_if_index=ifc['vpp_sw_index']) - api['api_args'] = api_args - api_data.append(api) - - with PapiExecutor(node) as papi_executor: - papi_executor.execute_papi(api_data) - try: - papi_executor.papi_should_have_passed() - api_reply = papi_executor.get_papi_reply() - except AssertionError: - raise RuntimeError('Failed to run {api_name} on host ' - '{host}!'.format(host=node['host'], **api)) - - if api_reply: - thr_mapping = [s['sw_interface_rx_placement_details'] \ - for r in api_reply for s in r['api_reply']] - return sorted(thr_mapping, key=lambda k: k['sw_if_index']) - else: - raise PapiError('No reply received for {api_name} on host {host}!'. - format(host=node['host'], **api)) + + cmd = 'sw_interface_rx_placement_dump' + cmd_reply = 'sw_interface_rx_placement_details' + err_msg = "Failed to run '{cmd}' PAPI command on host {host}!".format( + cmd=cmd, host=node['host']) + with PapiExecutor(node) as papi_exec: + for ifc in node['interfaces'].values(): + papi_exec.add(cmd, sw_if_index=ifc['vpp_sw_index']) + papi_resp = papi_exec.execute_should_pass(err_msg) + thr_mapping = [s[cmd_reply] for r in papi_resp.reply + for s in r['api_reply']] + return sorted(thr_mapping, key=lambda k: k['sw_if_index']) @staticmethod def vpp_sw_interface_set_rx_placement(node, sw_if_index, queue_id, @@ -1678,28 +1663,23 @@ class InterfaceUtil(object): :type sw_if_index: int :type queue_id: int :type worker_id: int - :raises RuntimeError: If failed to run command on host. - :raises PapiError: If no API reply received. + :raises RuntimeError: If failed to run command on host or if no API + reply received. """ - api_data = list() - api = dict(api_name='sw_interface_set_rx_placement') - api_args = dict(sw_if_index=sw_if_index, queue_id=queue_id, - worker_id=worker_id) - api['api_args'] = api_args - api_data.append(api) - - with PapiExecutor(node) as papi_executor: - papi_executor.execute_papi(api_data) - try: - papi_executor.papi_should_have_passed() - api_reply = papi_executor.get_papi_reply() - except AssertionError: - raise RuntimeError('Failed to run {api_name} on host ' - '{host}!'.format(host=node['host'], **api)) - - if not api_reply: - raise PapiError('No reply received for {api_name} on host {host}!'. - format(host=node['host'], **api)) + + cmd = 'sw_interface_set_rx_placement' + cmd_reply = 'sw_interface_set_rx_placement_reply' + err_msg = "Failed to run '{cmd}' PAPI command on host {host}!".format( + host=node['host'], cmd=cmd) + args = dict(sw_if_index=sw_if_index, queue_id=queue_id, + worker_id=worker_id) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).execute_should_pass(err_msg) + data = papi_resp.reply[0]['api_reply'][cmd_reply] + if data['retval'] != 0: + raise RuntimeError("Failed to set interface RX placement " + "to worker on host {host}". + format(host=node['host'])) @staticmethod def vpp_round_robin_rx_placement(node, prefix): diff --git a/resources/libraries/python/PapiErrors.py b/resources/libraries/python/PapiErrors.py deleted file mode 100644 index 5afebbf2ce..0000000000 --- a/resources/libraries/python/PapiErrors.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2018 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. - -"""PAPI Errors class file.""" - -__all__ = ['PapiError', 'PapiInitError', 'PapiJsonFileError', - 'PapiCommandError', 'PapiCommandInputError'] - - -class PapiError(Exception): - """Python API error.""" - pass - - -class PapiInitError(PapiError): - """This exception is raised when construction of VPP instance failed.""" - pass - - -class PapiJsonFileError(PapiError): - """This exception is raised in case of JSON API file error.""" - pass - - -class PapiCommandError(PapiError): - """This exception is raised when PAPI command(s) execution failed.""" - pass - - -class PapiCommandInputError(PapiCommandError): - """This exception is raised when incorrect input of Python API is used.""" - pass diff --git a/resources/libraries/python/PapiExecutor.py b/resources/libraries/python/PapiExecutor.py index ad0de27b82..0313284405 100644 --- a/resources/libraries/python/PapiExecutor.py +++ b/resources/libraries/python/PapiExecutor.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Cisco and/or its affiliates. +# Copyright (c) 2019 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: @@ -16,43 +16,248 @@ import binascii import json -from paramiko.ssh_exception import SSHException from robot.api import logger from resources.libraries.python.Constants import Constants -from resources.libraries.python.PapiErrors import PapiInitError, \ - PapiJsonFileError, PapiCommandError, PapiCommandInputError -# TODO: from resources.libraries.python.PapiHistory import PapiHistory from resources.libraries.python.ssh import SSH, SSHTimeout -__all__ = ['PapiExecutor'] +__all__ = ["PapiExecutor", "PapiResponse"] + +# TODO: Implement Papi History +# from resources.libraries.python.PapiHistory import PapiHistory + + +class PapiResponse(object): + """Class for metadata specifying the Papi reply, stdout, stderr and return + code. + """ + + def __init__(self, papi_reply=None, stdout="", stderr="", ret_code=None): + """Construct the Papi response by setting the values needed. + + :param papi_reply: API reply from last executed PAPI command(s). + :param stdout: stdout from last executed PAPI command(s). + :param stderr: stderr from last executed PAPI command(s). + :param ret_code: ret_code from last executed PAPI command(s). + :type papi_reply: list + :type stdout: str + :type stderr: str + :type ret_code: int + """ + + # API reply from last executed PAPI command(s) + self.reply = papi_reply + + # stdout from last executed PAPI command(s) + self.stdout = stdout + + # stderr from last executed PAPI command(s). + self.stderr = stderr + + # return code from last executed PAPI command(s) + self.ret_code = ret_code + + def __str__(self): + """Return string with human readable description of the group. + + :returns: Readable description. + :rtype: str + """ + return ("papi_reply={papi_reply} " + "stdout={stdout} " + "stderr={stderr} " + "ret_code={ret_code}". + format(papi_reply=self.reply, + stdout=self.stdout, + stderr=self.stderr, + ret_code=self.ret_code)) + + def __repr__(self): + """Return string executable as Python constructor call. + + :returns: Executable constructor call. + :rtype: str + """ + return ("PapiResponse(papi_reply={papi_reply} " + "stdout={stdout} " + "stderr={stderr} " + "ret_code={ret_code})". + format(papi_reply=self.reply, + stdout=self.stdout, + stderr=self.stderr, + ret_code=self.ret_code)) class PapiExecutor(object): - """Contains methods for executing Python API commands on DUTs.""" + """Contains methods for executing Python API commands on DUTs. + + Use only with "with" statement, e.g.: + + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add('show_version').execute_should_pass(err_msg) + """ def __init__(self, node): - self._stdout = None - self._stderr = None - self._ret_code = None + """Initialization. + + :param node: Node to run command(s) on. + :type node: dict + """ + + # Node to run command(s) on. self._node = node - self._json_data = None - self._api_reply = list() - self._api_data = None + + # The list of PAPI commands to be executed on the node. + self._api_command_list = list() + + # The response on the PAPI commands. + self.response = PapiResponse() self._ssh = SSH() - try: - self._ssh.connect(node) - except: - raise SSHException('Cannot open SSH connection to host {host} to ' - 'execute PAPI command(s)'. - format(host=self._node['host'])) def __enter__(self): + try: + self._ssh.connect(self._node) + except IOError: + raise RuntimeError("Cannot open SSH connection to host {host} to " + "execute PAPI command(s)". + format(host=self._node["host"])) return self def __exit__(self, exc_type, exc_val, exc_tb): - pass + self._ssh.disconnect(self._node) + + def clear(self): + """Empty the internal command list; return self. + + Use when not sure whether previous usage has left something in the list. + + :returns: self, so that method chaining is possible. + :rtype: PapiExecutor + """ + self._api_command_list = list() + return self + + def add(self, command, **kwargs): + """Add next command to internal command list; return self. + + :param command: VPP API command. + :param kwargs: Optional key-value arguments. + :type command: str + :type kwargs: dict + :returns: self, so that method chaining is possible. + :rtype: PapiExecutor + """ + self._api_command_list.append(dict(api_name=command, api_args=kwargs)) + return self + + def execute(self, process_reply=True, ignore_errors=False, timeout=120): + """Turn internal command list into proper data and execute; return + PAPI response. + + This method also clears the internal command list. + + :param process_reply: Process PAPI reply if True. + :param ignore_errors: If true, the errors in the reply are ignored. + :param timeout: Timeout in seconds. + :type process_reply: bool + :type ignore_errors: bool + :type timeout: int + :returns: Papi response including: papi reply, stdout, stderr and + return code. + :rtype: PapiResponse + :raises KeyError: If the reply is not correct. + """ + + local_list = self._api_command_list + + # Clear first as execution may fail. + self.clear() + + ret_code, stdout, stderr = self._execute_papi(local_list, timeout) + + papi_reply = list() + if process_reply: + json_data = json.loads(stdout) + for data in json_data: + try: + api_reply_processed = dict( + api_name=data["api_name"], + api_reply=self._process_reply(data["api_reply"])) + except KeyError: + if ignore_errors: + continue + else: + raise + papi_reply.append(api_reply_processed) + + return PapiResponse(papi_reply=papi_reply, + stdout=stdout, + stderr=stderr, + ret_code=ret_code) + + def execute_should_pass(self, err_msg="Failed to execute PAPI command.", + process_reply=True, ignore_errors=False, + timeout=120): + """Execute the PAPI commands and check the return code. + Raise exception if the PAPI command(s) failed. + + Note: There are two exceptions raised to distinguish two situations. If + not needed, re-implement using only RuntimeError. + + :param err_msg: The message used if the PAPI command(s) execution fails. + :param process_reply: Indicate whether or not to process PAPI reply. + :param ignore_errors: If true, the errors in the reply are ignored. + :param timeout: Timeout in seconds. + :type err_msg: str + :type process_reply: bool + :type ignore_errors: bool + :type timeout: int + :returns: Papi response including: papi reply, stdout, stderr and + return code. + :rtype: PapiResponse + :raises RuntimeError: If no PAPI command(s) executed. + :raises AssertionError: If PAPI command(s) execution passed. + """ + + response = self.execute(process_reply=process_reply, + ignore_errors=ignore_errors, + timeout=timeout) + + if response.ret_code != 0: + raise AssertionError(err_msg) + return response + + def execute_should_fail(self, + err_msg="Execution of PAPI command did not fail.", + process_reply=False, ignore_errors=False, + timeout=120): + """Execute the PAPI commands and check the return code. + Raise exception if the PAPI command(s) did not fail. + + It does not return anything as we expect it fails. + + Note: There are two exceptions raised to distinguish two situations. If + not needed, re-implement using only RuntimeError. + + :param err_msg: The message used if the PAPI command(s) execution fails. + :param process_reply: Indicate whether or not to process PAPI reply. + :param ignore_errors: If true, the errors in the reply are ignored. + :param timeout: Timeout in seconds. + :type err_msg: str + :type process_reply: bool + :type ignore_errors: bool + :type timeout: int + :raises RuntimeError: If no PAPI command(s) executed. + :raises AssertionError: If PAPI command(s) execution passed. + """ + + response = self.execute(process_reply=process_reply, + ignore_errors=ignore_errors, + timeout=timeout) + + if response.ret_code == 0: + raise AssertionError(err_msg) @staticmethod def _process_api_data(api_d): @@ -68,15 +273,12 @@ class PapiExecutor(object): api_data_processed = list() for api in api_d: - api_name = api['api_name'] - api_args = api['api_args'] - api_processed = dict(api_name=api_name) api_args_processed = dict() - for a_k, a_v in api_args.iteritems(): + for a_k, a_v in api["api_args"].iteritems(): value = binascii.hexlify(a_v) if isinstance(a_v, str) else a_v api_args_processed[str(a_k)] = value - api_processed['api_args'] = api_args_processed - api_data_processed.append(api_processed) + api_data_processed.append(dict(api_name=api["api_name"], + api_args=api_args_processed)) return api_data_processed @staticmethod @@ -85,6 +287,8 @@ class PapiExecutor(object): Apply binascii.unhexlify() method for unicode values. + TODO: Remove the disabled code when definitely not needed. + :param api_r: API reply. :type api_r: dict :returns: Processed API reply / a part of API reply. @@ -112,39 +316,25 @@ class PapiExecutor(object): """ if isinstance(api_reply, list): - reverted_reply = list() - for a_r in api_reply: - reverted_reply.append(self._revert_api_reply(a_r)) + reverted_reply = [self._revert_api_reply(a_r) for a_r in api_reply] else: reverted_reply = self._revert_api_reply(api_reply) return reverted_reply - def _process_json_data(self): - """Process received JSON data.""" - - for data in self._json_data: - api_name = data['api_name'] - api_reply = data['api_reply'] - api_reply_processed = dict( - api_name=api_name, api_reply=self._process_reply(api_reply)) - self._api_reply.append(api_reply_processed) - - def execute_papi(self, api_data, timeout=120): + def _execute_papi(self, api_data, timeout=120): """Execute PAPI command(s) on remote node and store the result. :param api_data: List of APIs with their arguments. :param timeout: Timeout in seconds. :type api_data: list :type timeout: int - :raises SSHTimeout: If PAPI command(s) execution is timed out. - :raises PapiInitError: If PAPI initialization failed. - :raises PapiJsonFileError: If no api.json file found. - :raises PapiCommandError: If PAPI command(s) execution failed. - :raises PapiCommandInputError: If invalid attribute name or invalid - value is used in API call. + :raises SSHTimeout: If PAPI command(s) execution has timed out. :raises RuntimeError: If PAPI executor failed due to another reason. """ - self._api_data = api_data + + if not api_data: + RuntimeError("No API data provided.") + api_data_processed = self._process_api_data(api_data) json_data = json.dumps(api_data_processed) @@ -157,68 +347,12 @@ class PapiExecutor(object): ret_code, stdout, stderr = self._ssh.exec_command_sudo( cmd=cmd, timeout=timeout) except SSHTimeout: - logger.error('PAPI command(s) execution timeout on host {host}:' - '\n{apis}'.format(host=self._node['host'], - apis=self._api_data)) - raise - except (PapiInitError, PapiJsonFileError, PapiCommandError, - PapiCommandInputError): - logger.error('PAPI command(s) execution failed on host {host}'. - format(host=self._node['host'])) + logger.error("PAPI command(s) execution timeout on host {host}:" + "\n{apis}".format(host=self._node["host"], + apis=api_data)) raise - except: - raise RuntimeError('PAPI command(s) execution on host {host} ' - 'failed: {apis}'.format(host=self._node['host'], - apis=self._api_data)) - - self._ret_code = ret_code - self._stdout = stdout - self._stderr = stderr - - def papi_should_have_failed(self): - """Read return code from last executed script and raise exception if the - PAPI command(s) didn't fail. - - :raises RuntimeError: When no PAPI command executed. - :raises AssertionError: If PAPI command(s) execution passed. - """ - - if self._ret_code is None: - raise RuntimeError("First execute the PAPI command(s)!") - if self._ret_code == 0: - raise AssertionError( - "PAPI command(s) execution passed, but failure was expected: " - "{apis}".format(apis=self._api_data)) - - def papi_should_have_passed(self): - """Read return code from last executed script and raise exception if the - PAPI command(s) failed. - - :raises RuntimeError: When no PAPI command executed. - :raises AssertionError: If PAPI command(s) execution failed. - """ - - if self._ret_code is None: - raise RuntimeError("First execute the PAPI command(s)!") - if self._ret_code != 0: - raise AssertionError( - "PAPI command(s) execution failed, but success was expected: " - "{apis}".format(apis=self._api_data)) - - def get_papi_stdout(self): - """Returns value of stdout from last executed PAPI command(s).""" - - return self._stdout - - def get_papi_stderr(self): - """Returns value of stderr from last executed PAPI command(s).""" - - return self._stderr - - def get_papi_reply(self): - """Returns api reply from last executed PAPI command(s).""" - - self._json_data = json.loads(self._stdout) - self._process_json_data() - - return self._api_reply + except Exception: + raise RuntimeError("PAPI command(s) execution on host {host} " + "failed: {apis}".format(host=self._node["host"], + apis=api_data)) + return ret_code, stdout, stderr diff --git a/resources/libraries/python/VPPUtil.py b/resources/libraries/python/VPPUtil.py index b3f471a33d..1bc1b4358e 100644 --- a/resources/libraries/python/VPPUtil.py +++ b/resources/libraries/python/VPPUtil.py @@ -20,7 +20,6 @@ from robot.api import logger from resources.libraries.python.Constants import Constants from resources.libraries.python.DUTSetup import DUTSetup from resources.libraries.python.PapiExecutor import PapiExecutor -from resources.libraries.python.PapiErrors import PapiError from resources.libraries.python.ssh import exec_cmd, exec_cmd_no_error from resources.libraries.python.topology import NodeType from resources.libraries.python.VatExecutor import VatExecutor @@ -140,49 +139,26 @@ class VPPUtil(object): @staticmethod def vpp_show_version(node, verbose=False): - """Run "show_version" API command. + """Run "show_version" PAPI command. :param node: Node to run command on. :param verbose: Show version, compile date and compile location if True otherwise show only version. :type node: dict :type verbose: bool - :raises PapiError: If no reply received for show_version API command. """ - # TODO: move composition of api data to separate method - api_data = list() - api = dict(api_name='show_version') - api_args = dict() - api['api_args'] = api_args - api_data.append(api) - - api_reply = None - with PapiExecutor(node) as papi_executor: - papi_executor.execute_papi(api_data) - try: - papi_executor.papi_should_have_passed() - except AssertionError: - raise RuntimeError('Failed to get VPP version on host: {host}'. - format(host=node['host'])) - api_reply = papi_executor.get_papi_reply() - - if api_reply is not None: - version_data = api_reply[0]['api_reply']['show_version_reply'] - ver = version_data['version'].rstrip('\0x00') - if verbose: - date = version_data['build_date'].rstrip('\0x00') - loc = version_data['build_directory'].rstrip('\0x00') - version = \ - 'VPP Version: {ver}\n' \ - 'Compile date: {date}\n' \ - 'Compile location: {loc}\n '\ - .format(ver=ver, date=date, loc=loc) - else: - version = 'VPP version:{ver}'.format(ver=ver) - logger.info(version) - else: - raise PapiError('No reply received for show_version API command on ' - 'host {host}'.format(host=node['host'])) + + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add('show_version').execute_should_pass() + data = papi_resp.reply[0]['api_reply']['show_version_reply'] + version = ('VPP version: {ver}\n'. + format(ver=data['version'].rstrip('\0x00'))) + if verbose: + version += ('Compile date: {date}\n' + 'Compile location: {loc}\n '. + format(date=data['build_date'].rstrip('\0x00'), + loc=data['build_directory'].rstrip('\0x00'))) + logger.info(version) @staticmethod def vpp_show_version_verbose(node): @@ -326,27 +302,8 @@ class VPPUtil(object): :type node: dict :returns: VPP thread data. :rtype: list - :raises RuntimeError: If failed to run command on host. - :raises PapiError: If no API reply received. """ - api_data = list() - api = dict(api_name='show_threads') - api_args = dict() - api['api_args'] = api_args - api_data.append(api) - - with PapiExecutor(node) as papi_executor: - papi_executor.execute_papi(api_data) - try: - papi_executor.papi_should_have_passed() - api_reply = papi_executor.get_papi_reply() - except AssertionError: - raise RuntimeError('Failed to run {api_name} on host ' - '{host}!'.format(host=node['host'], **api)) - - if api_reply: - return \ - api_reply[0]['api_reply']['show_threads_reply']['thread_data'] - else: - raise PapiError('No reply received for {api_name} on host {host}!'. - format(host=node['host'], **api)) + + with PapiExecutor(node) as papi_exec: + resp = papi_exec.add('show_threads').execute_should_pass() + return resp.reply[0]['api_reply']['show_threads_reply']['thread_data'] diff --git a/resources/tools/papi/vpp_papi_provider.py b/resources/tools/papi/vpp_papi_provider.py index 845cc932ca..e5e030c6ab 100644 --- a/resources/tools/papi/vpp_papi_provider.py +++ b/resources/tools/papi/vpp_papi_provider.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2018 Cisco and/or its affiliates. +# Copyright (c) 2019 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: @@ -18,17 +18,10 @@ import argparse import binascii -import fnmatch import json import os import sys -sys.path.append('/tmp/openvpp-testing') -try: - from resources.libraries.python.PapiErrors import * -except: - raise - # Sphinx creates auto-generated documentation by importing the python source # files and collecting the docstrings from them. The NO_VPP_PAPI flag allows # the vpp_papi_provider.py file to be importable without having to build @@ -54,7 +47,7 @@ if do_import: sys.path.append(modules_path) from vpp_papi import VPP else: - raise PapiInitError('vpp_papi module not found') + raise RuntimeError('vpp_papi module not found') # client name CLIENT_NAME = 'csit_papi' @@ -75,7 +68,7 @@ def papi_init(): vpp = VPP() return vpp except Exception as err: - raise PapiInitError('PAPI init failed:\n{exc}'.format(exc=repr(err))) + raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err))) def papi_connect(vpp_client, name='vpp_api'): @@ -101,7 +94,7 @@ def papi_disconnect(vpp_client): def papi_run(vpp_client, api_name, api_args): - """api_name + """Run PAPI. :param vpp_client: VPP instance. :param api_name: VPP API name. @@ -120,7 +113,8 @@ def convert_reply(api_r): """Process API reply / a part of API reply for smooth converting to JSON string. - # Apply binascii.hexlify() method for string values. + Apply binascii.hexlify() method for string values. + :param api_r: API reply. :type api_r: Vpp_serializer reply object (named tuple) :returns: Processed API reply / a part of API reply. @@ -164,9 +158,8 @@ def process_reply(api_reply): def main(): """Main function for the Python API provider. - :raises PapiCommandInputError: If invalid attribute name or invalid value is - used in API call. - :raises PapiCommandError: If PAPI command(s) execution failed. + :raises RuntimeError: If invalid attribute name or invalid value is + used in API call or if PAPI command(s) execution failed. """ parser = argparse.ArgumentParser() @@ -201,14 +194,16 @@ def main(): reply.append(api_reply) except (AttributeError, ValueError) as err: papi_disconnect(vpp) - raise PapiCommandInputError( - 'PAPI command {api}({args}) input error:\n{exc}'.format( - api=api_name, args=api_args), exc=repr(err)) + raise RuntimeError('PAPI command {api}({args}) input error:\n{err}'. + format(api=api_name, + args=api_args, + err=repr(err))) except Exception as err: papi_disconnect(vpp) - raise PapiCommandError( - 'PAPI command {api}({args}) error:\n{exc}'.format( - api=api_name, args=api_args), exc=repr(err)) + raise RuntimeError('PAPI command {api}({args}) error:\n{exc}'. + format(api=api_name, + args=api_args, + exc=repr(err))) papi_disconnect(vpp) return json.dumps(reply)