+ b. Three requests with arguments, the second and the third ones are the same
+ but with different arguments.
+
+ with PapiSocketExecutor(node) as papi_exec:
+ replies = papi_exec.add(cmd1, **args1).add(cmd2, **args2).\
+ add(cmd2, **args3).get_replies(err_msg)
+
+ 2. Dump functions
+
+ cmd = 'sw_interface_rx_placement_dump'
+ with PapiSocketExecutor(node) as papi_exec:
+ details = papi_exec.add(cmd, sw_if_index=ifc['vpp_sw_index']).\
+ get_details(err_msg)
+ """
+
+ # Class cache for reuse between instances.
+ api_root_dir = None
+ """We copy .api json files and PAPI code from DUT to robot machine.
+ This class variable holds temporary directory once created.
+ When python exits, the directory is deleted, so no downloaded file leaks.
+ The value will be set to TemporaryDirectory class instance (not string path)
+ to ensure deletion at exit."""
+ api_json_path = None
+ """String path to .api.json files, a directory somewhere in api_root_dir."""
+ api_package_path = None
+ """String path to PAPI code, a different directory under api_root_dir."""
+ crc_checker = None
+ """Accesses .api.json files at creation, caching speeds up accessing it."""
+ reusable_vpp_client_list = list()
+ """Each connection needs a separate client instance,
+ and each client instance creation needs to parse all .api files,
+ which takes time. If a client instance disconnects, it is put here,
+ so on next connect we can reuse intead of creating new."""
+ conn_cache = dict()
+ """Mapping from node key to connected client instance."""
+
+ def __init__(self, node, remote_vpp_socket=Constants.SOCKSVR_PATH):
+ """Store the given arguments, declare managed variables.
+
+ :param node: Node to connect to and forward unix domain socket from.
+ :param remote_vpp_socket: Path to remote socket to tunnel to.
+ :type node: dict
+ :type remote_vpp_socket: str
+ """
+ self._node = node
+ self._remote_vpp_socket = remote_vpp_socket
+ # The list of PAPI commands to be executed on the node.
+ self._api_command_list = list()
+
+ def ensure_api_dirs(self):
+ """Copy files from DUT to local temporary directory.
+
+ If the directory is still there, do not copy again.
+ If copying, also initialize CRC checker (this also performs
+ static checks), and remember PAPI package path.
+ Do not add that to PATH yet.
+ """
+ cls = self.__class__
+ if cls.api_package_path:
+ return
+ cls.api_root_dir = tempfile.TemporaryDirectory(dir=u"/tmp")
+ root_path = cls.api_root_dir.name
+ # Pack, copy and unpack Python part of VPP installation from _node.
+ # TODO: Use rsync or recursive version of ssh.scp_node instead?
+ node = self._node
+ exec_cmd_no_error(node, [u"rm", u"-rf", u"/tmp/papi.txz"])
+ # Papi python version depends on OS (and time).
+ # Python 2.7 or 3.4, site-packages or dist-packages.
+ installed_papi_glob = u"/usr/lib/python3*/*-packages/vpp_papi"
+ # We need to wrap this command in bash, in order to expand globs,
+ # and as ssh does join, the inner command has to be quoted.
+ inner_cmd = u" ".join([
+ u"tar", u"cJf", u"/tmp/papi.txz", u"--exclude=*.pyc",
+ installed_papi_glob, u"/usr/share/vpp/api"
+ ])
+ exec_cmd_no_error(node, [u"bash", u"-c", u"'" + inner_cmd + u"'"])
+ scp_node(node, root_path + u"/papi.txz", u"/tmp/papi.txz", get=True)
+ run([u"tar", u"xf", root_path + u"/papi.txz", u"-C", root_path])
+ cls.api_json_path = root_path + u"/usr/share/vpp/api"
+ # Perform initial checks before .api.json files are gone,
+ # by creating the checker instance.
+ cls.crc_checker = VppApiCrcChecker(cls.api_json_path)
+ # When present locally, we finally can find the installation path.
+ cls.api_package_path = glob.glob(root_path + installed_papi_glob)[0]
+ # Package path has to be one level above the vpp_papi directory.
+ cls.api_package_path = cls.api_package_path.rsplit(u"/", 1)[0]
+
+ def ensure_vpp_instance(self):
+ """Create or reuse a closed client instance, return it.
+
+ The instance is initialized for unix domain socket access,
+ it has initialized all the bindings, it is removed from the internal
+ list of disconnected instances, but it is not connected
+ (to a local socket) yet.
+
+ :returns: VPP client instance ready for connect.
+ :rtype: vpp_papi.VPPApiClient
+ """
+ self.ensure_api_dirs()
+ cls = self.__class__
+ if cls.reusable_vpp_client_list:
+ # Reuse in LIFO fashion.
+ *cls.reusable_vpp_client_list, ret = cls.reusable_vpp_client_list
+ return ret
+ # Creating an instance leads to dynamic imports from VPP PAPI code,
+ # so the package directory has to be present until the instance.
+ # But it is simpler to keep the package dir around.
+ try:
+ sys.path.append(cls.api_package_path)
+ # TODO: Pylint says import-outside-toplevel and import-error.
+ # It is right, we should refactor the code and move initialization
+ # of package outside.
+ from vpp_papi.vpp_papi import VPPApiClient as vpp_class
+ vpp_class.apidir = cls.api_json_path
+ # We need to create instance before removing from sys.path.
+ vpp_instance = vpp_class(
+ use_socket=True, server_address=u"TBD", async_thread=False,
+ read_timeout=14, logger=FilteredLogger(logger, u"INFO")
+ )
+ # Cannot use loglevel parameter, robot.api.logger lacks support.
+ # TODO: Stop overriding read_timeout when VPP-1722 is fixed.
+ finally:
+ if sys.path[-1] == cls.api_package_path:
+ sys.path.pop()
+ return vpp_instance
+
+ @classmethod
+ def key_for_node_and_socket(cls, node, remote_socket):
+ """Return a hashable object to distinguish nodes.
+
+ The usual node object (of "dict" type) is not hashable,
+ and can contain mutable information (mostly virtual interfaces).
+ Use this method to get an object suitable for being a key in dict.
+
+ The fields to include are chosen by what ssh needs.
+
+ This class method is needed, for disconnect.
+
+ :param node: The node object to distinguish.
+ :param remote_socket: Path to remote socket.
+ :type node: dict
+ :type remote_socket: str
+ :return: Tuple of values distinguishing this node from similar ones.
+ :rtype: tuple of str