+ client_instance = cls.conn_cache.get(key, None)
+ if client_instance is None:
+ return
+ logger.debug(f"Disconnecting by key: {key}")
+ client_instance.disconnect()
+ run([
+ u"ssh", u"-S", client_instance.csit_control_socket, u"-O",
+ u"exit", u"0.0.0.0"
+ ], check=False)
+ # Temp dir has autoclean, but deleting explicitly
+ # as an error can happen.
+ try:
+ client_instance.csit_temp_dir.cleanup()
+ except FileNotFoundError:
+ # There is a race condition with ssh removing its ssh.sock file.
+ # Single retry should be enough to ensure the complete removal.
+ shutil.rmtree(client_instance.csit_temp_dir.name)
+ # Finally, put disconnected clients to reuse list.
+ cls.reusable_vpp_client_list.append(client_instance)
+ # Invalidate cache last. Repeated errors are better than silent leaks.
+ del cls.conn_cache[key]
+
+ @classmethod
+ def disconnect_by_node_and_socket(
+ cls, node, remote_socket=Constants.SOCKSVR_PATH
+ ):
+ """Disconnect a connected client instance, noop it not connected.
+
+ Also remove the local sockets by deleting the temporary directory.
+ Put disconnected client instances to the reuse list.
+ The added attributes are not cleaned up,
+ as their values will get overwritten on next connect.
+
+ Call this method just before killing/restarting remote VPP instance.
+ """
+ key = cls.key_for_node_and_socket(node, remote_socket)
+ return cls.disconnect_by_key(key)
+
+ @classmethod
+ def disconnect_all_sockets_by_node(cls, node):
+ """Disconnect all socket connected client instance.
+
+ Noop if not connected.
+
+ Also remove the local sockets by deleting the temporary directory.
+ Put disconnected client instances to the reuse list.
+ The added attributes are not cleaned up,
+ as their values will get overwritten on next connect.
+
+ Call this method just before killing/restarting remote VPP instance.
+ """
+ sockets = Topology.get_node_sockets(node, socket_type=SocketType.PAPI)
+ if sockets:
+ for socket in sockets.values():
+ # TODO: Remove sockets from topology.
+ PapiSocketExecutor.disconnect_by_node_and_socket(node, socket)
+ # Always attempt to disconnect the default socket.
+ return cls.disconnect_by_node_and_socket(node)
+
+ @staticmethod
+ def disconnect_all_papi_connections():
+ """Disconnect all connected client instances, tear down the SSH tunnels.
+
+ Also remove the local sockets by deleting the temporary directory.
+ Put disconnected client instances to the reuse list.
+ The added attributes are not cleaned up,
+ as their values will get overwritten on next connect.
+
+ This should be a class method,
+ but we prefer to call static methods from Robot.
+
+ Call this method just before killing/restarting all VPP instances.
+ """
+ cls = PapiSocketExecutor
+ # Iterate over copy of entries so deletions do not mess with iterator.
+ keys_copy = list(cls.conn_cache.keys())
+ for key in keys_copy:
+ cls.disconnect_by_key(key)