+ If the object is namedtuple-like, its _asdict() form is returned,
+ but in the returned object __getitem__ method is wrapped
+ to dictize also any items returned.
+ If the object does not have _asdict, it will be returned without any change.
+ Integer keys still access the object as tuple.
+
+ A more useful version would be to keep obj mostly as a namedtuple,
+ just add getitem for string keys. Unfortunately, namedtuple inherits
+ from tuple, including its read-only __getitem__ attribute,
+ so we cannot monkey-patch it.
+
+ TODO: Create a proxy for namedtuple to allow that.
+
+ :param obj: Arbitrary object to dictize.
+ :type obj: object
+ :returns: Dictized object.
+ :rtype: same as obj type or collections.OrderedDict
+ """
+ if not hasattr(obj, "_asdict"):
+ return obj
+ ret = obj._asdict()
+ old_get = ret.__getitem__
+ new_get = lambda self, key: dictize(old_get(self, key))
+ ret.__getitem__ = new_get
+ return ret
+
+class PapiSocketExecutor(object):
+ """Methods for executing VPP Python API commands on forwarded socket.
+
+ The current implementation connects for the duration of resource manager.
+ Delay for accepting connection is 10s, and disconnect is explicit.
+ TODO: Decrease 10s to value that is long enough for creating connection
+ and short enough to not affect performance.
+
+ The current implementation downloads and parses .api.json files only once
+ and stores a VPPApiClient instance (disconnected) as a class variable.
+ Accessing multiple nodes with different APIs is therefore not supported.
+
+ The current implementation seems to run into read error occasionally.
+ Not sure if the error is in Python code on Robot side, ssh forwarding,
+ or socket handling at VPP side. Anyway, reconnect after some sleep
+ seems to help, hoping repeated command execution does not lead to surprises.
+ The reconnection is logged at WARN level, so it is prominently shown
+ in log.html, so we can see how frequently it happens.
+
+ TODO: Support sockets in NFs somehow.
+ TODO: Support handling of retval!=0 without try/except in caller.
+
+ Note: Use only with "with" statement, e.g.:
+
+ with PapiSocketExecutor(node) as papi_exec:
+ reply = papi_exec.add('show_version').get_reply(err_msg)
+
+ This class processes two classes of VPP PAPI methods:
+ 1. Simple request / reply: method='request'.
+ 2. Dump functions: method='dump'.
+
+ Note that access to VPP stats over socket is not supported yet.
+
+ The recommended ways of use are (examples):
+
+ 1. Simple request / reply
+
+ a. One request with no arguments:
+
+ with PapiSocketExecutor(node) as papi_exec:
+ reply = papi_exec.add('show_version').get_reply(err_msg)
+
+ 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.
+ cached_vpp_instance = None
+
+ def __init__(self, node, remote_vpp_socket="/run/vpp-api.sock"):
+ """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()
+ # The following values are set on enter, reset on exit.
+ self._temp_dir = None
+ self._ssh_control_socket = None
+ self._local_vpp_socket = None
+
+ @property
+ def vpp_instance(self):
+ """Return VPP instance with bindings to all API calls.
+
+ The returned instance is initialized for unix domain socket access,
+ it has initialized all the bindings, but it is not connected
+ (to local socket) yet.
+
+ First invocation downloads .api.json files from self._node
+ into a temporary directory.
+
+ After first invocation, the result is cached, so other calls are quick.
+ Class variable is used as the cache, but this property is defined as
+ an instance method, so that _node (for api files) is known.
+
+ :returns: Initialized but not connected VPP instance.
+ :rtype: vpp_papi.VPPApiClient
+ """
+ cls = self.__class__
+ if cls.cached_vpp_instance is not None:
+ return cls.cached_vpp_instance
+ tmp_dir = tempfile.mkdtemp(dir="/tmp")
+ package_path = "Not set yet."