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]]: