From 47943e1e0ea66039d58cf502bde821c6ef4421af Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Tue, 9 Jun 2026 01:28:34 -0600 Subject: [PATCH] Reconnect expired vSphere sessions instead of failing ESXi expires idle SOAP sessions, after which the cached ServiceInstance raised NotAuthenticated on every subsequent tool call until the server was restarted. ConnectionManager.get now probes currentSession on cached connections and rebuilds the connection (re-resolving datacenter, datastore, network) when the session is dead. --- src/mcvsphere/connection.py | 20 ++++++++++++++++++++ src/mcvsphere/connection_manager.py | 3 +++ 2 files changed, 23 insertions(+) diff --git a/src/mcvsphere/connection.py b/src/mcvsphere/connection.py index d94a885..9cb3274 100644 --- a/src/mcvsphere/connection.py +++ b/src/mcvsphere/connection.py @@ -61,6 +61,26 @@ class VMwareConnection: self._setup_datastore() self._setup_network() + def ensure_connected(self) -> None: + """Reconnect if the cached session has expired server-side. + + ESXi expires idle SOAP sessions; the cached ``ServiceInstance`` then + raises ``NotAuthenticated`` on the next call. Probing ``currentSession`` + is one cheap round-trip — None or a raised fault both mean "dead", so + we rebuild the connection (and re-resolve datacenter/datastore/etc). + """ + try: + if ( + self.si is not None + and self.content is not None + and self.content.sessionManager.currentSession is not None + ): + return # session is alive + except Exception: + pass # any fault here means the session is unusable + logger.info("vSphere session expired; reconnecting to %s", self.settings.vcenter_host) + self._connect() + def _setup_datacenter(self) -> None: """Find and configure the target datacenter.""" datacenters = [ diff --git a/src/mcvsphere/connection_manager.py b/src/mcvsphere/connection_manager.py index cc0fcaa..f660e38 100644 --- a/src/mcvsphere/connection_manager.py +++ b/src/mcvsphere/connection_manager.py @@ -48,6 +48,9 @@ class ConnectionManager: self._connections[target] = VMwareConnection( self._servers[target].to_settings(self._settings) ) + else: + # Cached connection may have a server-expired session; revive it. + self._connections[target].ensure_connected() return self._connections[target] def describe(self) -> list[dict[str, Any]]: