fix(xo-server-sdn-controller): re-watch XAPI events after reconnect

When a pool's XAPI connection drops and reconnects, xo-server destroys the
old Xapi instance and creates a new one (same pool UUID, same XAPI object
$refs). The plugin tracked managed pools by pool UUID, so _manageXapi()
short-circuited on reconnect and never attached its add/update/remove
listeners to the new Xapi instance.

From then on the plugin was blind to XAPI events: re-applying VIF traffic
rules on VM start, handling VIF/PIF plug and star-center election all
stopped silently until the plugin was reloaded.

Track the managed Xapi instances instead of pool UUIDs, and drop a dead
instance on disconnect so its replacement is managed again.
This commit is contained in:
Sebastien Rodot
2026-09-22 13:55:45 +02:00
committed by Sebastien Marie
parent 40e5ca9a73
commit d53294f15c
2 changed files with 18 additions and 7 deletions

View File

@@ -29,6 +29,7 @@
- [Host] Add possibility to emergency shutdown a host (PR [#10412](https://github.com/vatesfr/xen-orchestra/pull/10412))
- [Backup/Restore] Faster reading of the files of a remote, like the full backups (10 MiB chunks instead of 64 KiB) (PR [#10537](https://github.com/vatesfr/xen-orchestra/pull/10537))
- [Mirror] The full mirrors between unencrypted remotes reuse the checksum of the source instead of computing it again (PR [#10537](https://github.com/vatesfr/xen-orchestra/pull/10537))
- [SDN Controller] Fix events listening and stale objects on server reconnection (PR [#10440](https://github.com/vatesfr/xen-orchestra/pull/10440))
### Bug fixes
@@ -84,6 +85,7 @@
- xen-api minor
- xo-server minor
- xo-server-load-balancer minor
- xo-server-sdn-controller patch
- xo-web minor
<!--packages-end-->

View File

@@ -306,7 +306,8 @@ class SDNController extends EventEmitter {
this._tlsHelper = new TlsHelper()
this._handledTasks = []
this._managed = []
// Set() of Xapi instances (whose object events are being watched).
this._managedXapis = new Set()
}
// ---------------------------------------------------------------------------
@@ -470,11 +471,13 @@ class SDNController extends EventEmitter {
this._cleaners.forEach(cleaner => cleaner())
this._cleaners = []
this._xapiCleaners.forEach(cleaner => cleaner())
this.ovsdbClients = {}
this.ofChannels = {}
this._handledTasks = []
this._managed = []
this._managedXapis = new Set()
this._unsetApiMethods()
}
@@ -499,7 +502,7 @@ class SDNController extends EventEmitter {
return
}
this._cleaners.push(await this._manageXapi(xapi))
await this._manageXapi(xapi)
const hosts = Object.values(xapi.objects.indexes.type.host ?? {})
for (const host of hosts) {
this._getOrCreateOvsdbClient(host)
@@ -628,6 +631,11 @@ class SDNController extends EventEmitter {
_handleDisconnectedXapi(xapi) {
log.debug('xapi disconnected', { id: xapi.pool.uuid })
try {
// the Xapi instance is dead, a new one (same pool UUID, same XAPI
// object $refs) will be created on reconnection and must be managed
// again in _manageXapi
this._managedXapis.delete(xapi)
forOwn(this.privateNetworks, privateNetwork => {
privateNetwork.networks = omitBy(privateNetwork.networks, network => network.$pool.uuid === xapi.pool.uuid)
@@ -1005,8 +1013,8 @@ class SDNController extends EventEmitter {
// ---------------------------------------------------------------------------
async _manageXapi(xapi) {
if (this._managed.includes(xapi.pool.uuid)) {
return noop // pushed in _cleaners
if (this._managedXapis.has(xapi)) {
return
}
const { objects } = xapi
@@ -1017,9 +1025,10 @@ class SDNController extends EventEmitter {
objects.on('remove', objectsRemovedXapi)
await this._installCaCertificateIfNeeded(xapi)
this._managed.push(xapi.pool.uuid)
this._managedXapis.add(xapi)
return () => {
this._managedXapis.delete(xapi)
objects.removeListener('add', this._objectsAdded)
objects.removeListener('update', this._objectsUpdated)
objects.removeListener('remove', objectsRemovedXapi)
@@ -1315,7 +1324,7 @@ class SDNController extends EventEmitter {
return this._setBridgeControllerForHost(host)
})
)
this._cleaners.push(await this._manageXapi(pool.$xapi))
await this._manageXapi(pool.$xapi)
}
_setBridgeControllerForHost(host) {