Compare commits
7 Commits
xo-server-
...
pierre-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67f7ce40da | ||
|
|
a00e3e6f41 | ||
|
|
82ba02b4f3 | ||
|
|
d70ae6ebe3 | ||
|
|
f6c411a261 | ||
|
|
b606eaf9ee | ||
|
|
516edd1b09 |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -9,6 +9,12 @@
|
||||
- [VM] Permission to revert to any snapshot for VM operators [#3928](https://github.com/vatesfr/xen-orchestra/issues/3928) (PR [#4247](https://github.com/vatesfr/xen-orchestra/pull/4247))
|
||||
- [VM] Show current operations and progress [#3811](https://github.com/vatesfr/xen-orchestra/issues/3811) (PR [#3982](https://github.com/vatesfr/xen-orchestra/pull/3982))
|
||||
- [SR/General] Improve SR usage graph [#3608](https://github.com/vatesfr/xen-orchestra/issues/3608) (PR [#3830](https://github.com/vatesfr/xen-orchestra/pull/3830))
|
||||
- [Backup NG/New] Generate default schedule if no schedule is specified [#4036](https://github.com/vatesfr/xen-orchestra/issues/4036) (PR [#4183](https://github.com/vatesfr/xen-orchestra/pull/4183))
|
||||
- [Host/Advanced] Ability to edit iSCSI IQN [#4048](https://github.com/vatesfr/xen-orchestra/issues/4048) (PR [#4208](https://github.com/vatesfr/xen-orchestra/pull/4208))
|
||||
- [Backup NG] Ability to bypass unhealthy VDI chains check [#4324](https://github.com/vatesfr/xen-orchestra/issues/4324) (PR [#4340](https://github.com/vatesfr/xen-orchestra/pull/4340))
|
||||
- [Pool] Ability to add multiple hosts on the pool [#2402](https://github.com/vatesfr/xen-orchestra/issues/2402) (PR [#3716](https://github.com/vatesfr/xen-orchestra/pull/3716))
|
||||
- [VM/console] Multiline copy/pasting [#4261](https://github.com/vatesfr/xen-orchestra/issues/4261) (PR [#4341](https://github.com/vatesfr/xen-orchestra/pull/4341))
|
||||
- [VM,host] Improved state icons/pills (colors and tooltips) (PR [#4363](https://github.com/vatesfr/xen-orchestra/pull/4363))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@@ -19,14 +25,17 @@
|
||||
- [VM/tab-advanced] Fix CPU limits edition (PR [#4337](https://github.com/vatesfr/xen-orchestra/pull/4337))
|
||||
- [Remotes] Fix `EIO` errors due to massive parallel fs operations [#4323](https://github.com/vatesfr/xen-orchestra/issues/4323) (PR [#4330](https://github.com/vatesfr/xen-orchestra/pull/4330))
|
||||
- [VM/Advanced] Fix virtualization mode switch (PV/HVM) (PR [#4349](https://github.com/vatesfr/xen-orchestra/pull/4349))
|
||||
- [Task] fix hidden notification by search field [#3874](https://github.com/vatesfr/xen-orchestra/issues/3874) (PR [#4305](https://github.com/vatesfr/xen-orchestra/pull/4305)
|
||||
- [VM] Fail to change affinity (PR [#4361](https://github.com/vatesfr/xen-orchestra/pull/4361)
|
||||
- [VM] Number of CPUs not correctly changed on running VMs (PR [#4360](https://github.com/vatesfr/xen-orchestra/pull/4360)
|
||||
|
||||
### Released packages
|
||||
|
||||
- @xen-orchestra/fs v0.10.1
|
||||
- xo-server-sdn-controller v0.1.1
|
||||
- xen-api v0.27.1
|
||||
- xo-server v5.45.3
|
||||
- xo-web v5.45.1
|
||||
- xo-server v5.46.0
|
||||
- xo-web v5.46.0
|
||||
|
||||
## **5.36.0** (2019-06-27)
|
||||
|
||||
|
||||
@@ -7,20 +7,13 @@
|
||||
|
||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||
|
||||
- [Backup NG/New] Generate default schedule if no schedule is specified [#4036](https://github.com/vatesfr/xen-orchestra/issues/4036) (PR [#4183](https://github.com/vatesfr/xen-orchestra/pull/4183))
|
||||
- [Host/Advanced] Ability to edit iSCSI IQN [#4048](https://github.com/vatesfr/xen-orchestra/issues/4048) (PR [#4208](https://github.com/vatesfr/xen-orchestra/pull/4208))
|
||||
- [Backup NG] Ability to bypass unhealthy VDI chains check [#4324](https://github.com/vatesfr/xen-orchestra/issues/4324) (PR [#4340](https://github.com/vatesfr/xen-orchestra/pull/4340))
|
||||
- [Pool] Ability to add multiple hosts on the pool [#2402](https://github.com/vatesfr/xen-orchestra/issues/2402) (PR [#3716](https://github.com/vatesfr/xen-orchestra/pull/3716))
|
||||
- [VM/console] Multiline copy/pasting [#4261](https://github.com/vatesfr/xen-orchestra/issues/4261) (PR [#4341](https://github.com/vatesfr/xen-orchestra/pull/4341))
|
||||
- [VM,host] Improved state icons/pills (colors and tooltips) (PR [#4363](https://github.com/vatesfr/xen-orchestra/pull/4363))
|
||||
- [VM/copy] Only show zstd option when it's supported [#3892](https://github.com/vatesfr/xen-orchestra/issues/3892) (PR [#4326](https://github.com/vatesfr/xen-orchestra/pull/4326))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||
|
||||
- [Task] fix hidden notification by search field [#3874](https://github.com/vatesfr/xen-orchestra/issues/3874) (PR [#4305](https://github.com/vatesfr/xen-orchestra/pull/4305)
|
||||
- [VM] Fail to change affinity (PR [#4361](https://github.com/vatesfr/xen-orchestra/pull/4361)
|
||||
- [VM] Number of CPUs not correctly changed on running VMs (PR [#4360](https://github.com/vatesfr/xen-orchestra/pull/4360)
|
||||
- [SDN Controller] Better detect host shutting down to adapt network topology (PR [#4314](https://github.com/vatesfr/xen-orchestra/pull/4314))
|
||||
|
||||
### Released packages
|
||||
|
||||
@@ -29,5 +22,6 @@
|
||||
>
|
||||
> Rule of thumb: add packages on top.
|
||||
|
||||
- xo-server v5.46.0
|
||||
- xo-web v5.46.0
|
||||
- xo-server-sdn-controller v0.1.2
|
||||
- xo-server v5.47.0
|
||||
- xo-web v5.47.0
|
||||
|
||||
@@ -48,7 +48,6 @@ export const configurationSchema = {
|
||||
|
||||
async function fileWrite(path, data) {
|
||||
await fromCallback(writeFile, path, data)
|
||||
log.debug(`${path} successfully written`)
|
||||
}
|
||||
|
||||
async function fileRead(path) {
|
||||
@@ -184,9 +183,10 @@ class SDNController extends EventEmitter {
|
||||
const networks = filter(xapi.objects.all, { $type: 'network' })
|
||||
forOwn(networks, async network => {
|
||||
if (network.other_config.private_pool_wide === 'true') {
|
||||
log.debug(
|
||||
`Adding network: '${network.name_label}' for pool: '${network.$pool.name_label}' to managed networks`
|
||||
)
|
||||
log.debug('Adding network to managed networks', {
|
||||
network: network.name_label,
|
||||
pool: network.$pool.name_label,
|
||||
})
|
||||
const center = await this._electNewCenter(network, true)
|
||||
this._poolNetworks.push({
|
||||
pool: network.$pool.$ref,
|
||||
@@ -242,9 +242,10 @@ class SDNController extends EventEmitter {
|
||||
|
||||
const privateNetwork = await pool.$xapi._getOrWaitObject(privateNetworkRef)
|
||||
|
||||
log.info(
|
||||
`Private network '${privateNetwork.name_label}' has been created for pool '${pool.name_label}'`
|
||||
)
|
||||
log.info('New private network created', {
|
||||
network: privateNetwork.name_label,
|
||||
pool: pool.name_label,
|
||||
})
|
||||
|
||||
// For each pool's host, create a tunnel to the private network
|
||||
const hosts = filter(pool.$xapi.objects.all, { $type: 'host' })
|
||||
@@ -293,9 +294,10 @@ class SDNController extends EventEmitter {
|
||||
const { $type } = object
|
||||
|
||||
if ($type === 'host') {
|
||||
log.debug(
|
||||
`New host: '${object.name_label}' in pool: '${object.$pool.name_label}'`
|
||||
)
|
||||
log.debug('New host', {
|
||||
host: object.name_label,
|
||||
pool: object.$pool.name_label,
|
||||
})
|
||||
|
||||
if (find(this._newHosts, { $ref: object.$ref }) == null) {
|
||||
this._newHosts.push(object)
|
||||
@@ -315,6 +317,8 @@ class SDNController extends EventEmitter {
|
||||
await this._pifUpdated(object)
|
||||
} else if ($type === 'host') {
|
||||
await this._hostUpdated(object)
|
||||
} else if ($type === 'host_metrics') {
|
||||
await this._hostMetricsUpdated(object)
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -377,7 +381,13 @@ class SDNController extends EventEmitter {
|
||||
}
|
||||
|
||||
log.debug(
|
||||
`PIF: '${pif.device}' of network: '${pif.$network.name_label}' star-center host: '${pif.$host.name_label}' has been unplugged, electing a new host`
|
||||
'PIF of star-center host has been unplugged, electing a new star-center',
|
||||
{
|
||||
pif: pif.device,
|
||||
network: pif.$network.name_label,
|
||||
host: pif.$host.name_label,
|
||||
pool: pif.$pool.name_label,
|
||||
}
|
||||
)
|
||||
const newCenter = await this._electNewCenter(pif.$network, true)
|
||||
poolNetwork.starCenter = newCenter?.$ref
|
||||
@@ -388,16 +398,21 @@ class SDNController extends EventEmitter {
|
||||
} else {
|
||||
if (poolNetwork.starCenter == null) {
|
||||
const host = pif.$host
|
||||
log.debug(
|
||||
`First available host: '${host.name_label}' becomes star center of network: '${pif.$network.name_label}'`
|
||||
)
|
||||
log.debug('First available host becomes star center of network', {
|
||||
host: host.name_label,
|
||||
network: pif.$network.name_label,
|
||||
pool: pif.$pool.name_label,
|
||||
})
|
||||
poolNetwork.starCenter = pif.host
|
||||
this._starCenters.set(host.$id, host.$ref)
|
||||
}
|
||||
|
||||
log.debug(
|
||||
`PIF: '${pif.device}' of network: '${pif.$network.name_label}' host: '${pif.$host.name_label}' has been plugged`
|
||||
)
|
||||
log.debug('PIF plugged', {
|
||||
pif: pif.device,
|
||||
network: pif.$network.name_label,
|
||||
host: pif.$host.name_label,
|
||||
pool: pif.$pool.name_label,
|
||||
})
|
||||
|
||||
const starCenter = pif.$xapi.getObjectByRef(poolNetwork.starCenter)
|
||||
await this._addHostToNetwork(pif.$host, pif.$network, starCenter)
|
||||
@@ -412,64 +427,31 @@ class SDNController extends EventEmitter {
|
||||
return
|
||||
}
|
||||
|
||||
const tunnels = filter(xapi.objects.all, { $type: 'tunnel' })
|
||||
const newHost = find(this._newHosts, { $ref: host.$ref })
|
||||
if (newHost != null) {
|
||||
this._newHosts.splice(this._newHosts.indexOf(newHost), 1)
|
||||
try {
|
||||
await xapi.call('pool.certificate_sync')
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Couldn't sync SDN controller ca certificate in pool: '${host.$pool.name_label}' because: ${error}`
|
||||
)
|
||||
log.error('Error while syncing SDN controller CA certificate', {
|
||||
error,
|
||||
pool: host.$pool.name_label,
|
||||
})
|
||||
}
|
||||
}
|
||||
for (const tunnel of tunnels) {
|
||||
const accessPIF = xapi.getObjectByRef(tunnel.access_PIF)
|
||||
if (accessPIF.host !== host.$ref) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const poolNetwork = find(this._poolNetworks, {
|
||||
network: accessPIF.network,
|
||||
})
|
||||
if (poolNetwork == null) {
|
||||
continue
|
||||
}
|
||||
async _hostMetricsUpdated(hostMetrics) {
|
||||
const ovsdbClient = find(this._ovsdbClients, {
|
||||
hostMetricsRef: hostMetrics.$ref,
|
||||
})
|
||||
const host = ovsdbClient._host
|
||||
|
||||
if (accessPIF.currently_attached) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.debug(
|
||||
`Pluging PIF: '${accessPIF.device}' for host: '${host.name_label}' on network: '${accessPIF.$network.name_label}'`
|
||||
)
|
||||
try {
|
||||
await xapi.call('PIF.plug', accessPIF.$ref)
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`XAPI error while pluging PIF: '${accessPIF.device}' on host: '${host.name_label}' for network: '${accessPIF.$network.name_label}'`
|
||||
)
|
||||
}
|
||||
|
||||
const starCenter = host.$xapi.getObjectByRef(poolNetwork.starCenter)
|
||||
await this._addHostToNetwork(host, accessPIF.$network, starCenter)
|
||||
}
|
||||
if (hostMetrics.live) {
|
||||
await this._addHostToPoolNetworks(host)
|
||||
} else {
|
||||
const poolNetworks = filter(this._poolNetworks, { starCenter: host.$ref })
|
||||
for (const poolNetwork of poolNetworks) {
|
||||
const network = host.$xapi.getObjectByRef(poolNetwork.network)
|
||||
log.debug(
|
||||
`Star center host: '${host.name_label}' of network: '${network.name_label}' in pool: '${host.$pool.name_label}' is no longer reachable, electing a new host`
|
||||
)
|
||||
|
||||
const newCenter = await this._electNewCenter(network, true)
|
||||
poolNetwork.starCenter = newCenter?.$ref
|
||||
this._starCenters.delete(host.$id)
|
||||
if (newCenter != null) {
|
||||
this._starCenters.set(newCenter.$id, newCenter.$ref)
|
||||
}
|
||||
}
|
||||
await this._hostUnreachable(host)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,11 +466,15 @@ class SDNController extends EventEmitter {
|
||||
const controller = find(pool.$xapi.objects.all, { $type: 'SDN_controller' })
|
||||
if (controller != null) {
|
||||
await pool.$xapi.call('SDN_controller.forget', controller.$ref)
|
||||
log.debug(`Remove old SDN controller from pool: '${pool.name_label}'`)
|
||||
log.debug('Old SDN controller removed', {
|
||||
pool: pool.name_label,
|
||||
})
|
||||
}
|
||||
|
||||
await pool.$xapi.call('SDN_controller.introduce', PROTOCOL)
|
||||
log.debug(`Set SDN controller of pool: '${pool.name_label}'`)
|
||||
log.debug('SDN controller has been set', {
|
||||
pool: pool.name_label,
|
||||
})
|
||||
this._cleaners.push(await this._manageXapi(pool.$xapi))
|
||||
}
|
||||
|
||||
@@ -512,15 +498,16 @@ class SDNController extends EventEmitter {
|
||||
needInstall = true
|
||||
} else if (this._overrideCerts) {
|
||||
await xapi.call('pool.certificate_uninstall', SDN_CONTROLLER_CERT)
|
||||
log.debug(
|
||||
`Old SDN Controller CA certificate uninstalled on pool: '${xapi.pool.name_label}'`
|
||||
)
|
||||
log.debug('Old SDN controller CA certificate uninstalled', {
|
||||
pool: xapi.pool.name_label,
|
||||
})
|
||||
needInstall = true
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Couldn't retrieve certificate list of pool: '${xapi.pool.name_label}'`
|
||||
)
|
||||
log.error('Error while retrieving certificate list', {
|
||||
error,
|
||||
pool: xapi.pool.name_label,
|
||||
})
|
||||
}
|
||||
if (!needInstall) {
|
||||
return
|
||||
@@ -533,13 +520,14 @@ class SDNController extends EventEmitter {
|
||||
this._caCert.toString()
|
||||
)
|
||||
await xapi.call('pool.certificate_sync')
|
||||
log.debug(
|
||||
`SDN controller CA certificate install in pool: '${xapi.pool.name_label}'`
|
||||
)
|
||||
log.debug('SDN controller CA certficate installed', {
|
||||
pool: xapi.pool.name_label,
|
||||
})
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Couldn't install SDN controller CA certificate in pool: '${xapi.pool.name_label}' because: ${error}`
|
||||
)
|
||||
log.error('Error while installing SDN controller CA certificate', {
|
||||
error,
|
||||
pool: xapi.pool.name_label,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,38 +538,42 @@ class SDNController extends EventEmitter {
|
||||
|
||||
let newCenter = null
|
||||
const hosts = filter(pool.$xapi.objects.all, { $type: 'host' })
|
||||
|
||||
for (const host of hosts) {
|
||||
const pif = find(host.$PIFs, { network: network.$ref })
|
||||
if (pif !== undefined && pif.currently_attached && host.$metrics.live) {
|
||||
newCenter = host
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
map(hosts, async host => {
|
||||
if (resetNeeded) {
|
||||
// Clean old ports and interfaces
|
||||
const hostClient = find(this._ovsdbClients, { host: host.$ref })
|
||||
if (hostClient != null) {
|
||||
try {
|
||||
await hostClient.resetForNetwork(network.uuid, network.name_label)
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Couldn't reset network: '${network.name_label}' for host: '${host.name_label}' in pool: '${network.$pool.name_label}' because: ${error}`
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newCenter != null) {
|
||||
if (!resetNeeded) {
|
||||
return
|
||||
}
|
||||
|
||||
const pif = find(host.$PIFs, { network: network.$ref })
|
||||
if (pif != null && pif.currently_attached && host.enabled) {
|
||||
newCenter = host
|
||||
// Clean old ports and interfaces
|
||||
const hostClient = find(this._ovsdbClients, { host: host.$ref })
|
||||
if (hostClient != null) {
|
||||
try {
|
||||
await hostClient.resetForNetwork(network.uuid, network.name_label)
|
||||
} catch (error) {
|
||||
log.error('Error while resetting private network', {
|
||||
error,
|
||||
network: network.name_label,
|
||||
host: host.name_label,
|
||||
pool: network.$pool.name_label,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (newCenter == null) {
|
||||
log.error(
|
||||
`Unable to elect a new star-center host to network: '${network.name_label}' for pool: '${network.$pool.name_label}' because there's no available host`
|
||||
)
|
||||
log.error('No available host to elect new star-center', {
|
||||
network: network.name_label,
|
||||
pool: network.$pool.name_label,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -592,9 +584,11 @@ class SDNController extends EventEmitter {
|
||||
})
|
||||
)
|
||||
|
||||
log.info(
|
||||
`New star center host elected: '${newCenter.name_label}' in network: '${network.name_label}'`
|
||||
)
|
||||
log.info('New star-center elected', {
|
||||
center: newCenter.name_label,
|
||||
network: network.name_label,
|
||||
pool: network.$pool.name_label,
|
||||
})
|
||||
|
||||
return newCenter
|
||||
}
|
||||
@@ -604,16 +598,19 @@ class SDNController extends EventEmitter {
|
||||
pif => pif.physical && pif.ip_configuration_mode !== 'None'
|
||||
)
|
||||
if (pif == null) {
|
||||
log.error(
|
||||
`No PIF found to create tunnel on host: '${host.name_label}' for network: '${network.name_label}'`
|
||||
)
|
||||
log.error('No PIF found to create tunnel', {
|
||||
host: host.name_label,
|
||||
network: network.name_label,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await host.$xapi.call('tunnel.create', pif.$ref, network.$ref)
|
||||
log.debug(
|
||||
`Tunnel added on host '${host.name_label}' for network '${network.name_label}'`
|
||||
)
|
||||
log.debug('New tunnel added', {
|
||||
network: network.name_label,
|
||||
host: host.name_label,
|
||||
pool: host.$pool.name_label,
|
||||
})
|
||||
}
|
||||
|
||||
async _addHostToNetwork(host, network, starCenter) {
|
||||
@@ -626,7 +623,10 @@ class SDNController extends EventEmitter {
|
||||
host: host.$ref,
|
||||
})
|
||||
if (hostClient == null) {
|
||||
log.error(`No OVSDB client found for host: '${host.name_label}'`)
|
||||
log.error('No OVSDB client found', {
|
||||
host: host.name_label,
|
||||
pool: host.$pool.name_label,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -634,9 +634,10 @@ class SDNController extends EventEmitter {
|
||||
host: starCenter.$ref,
|
||||
})
|
||||
if (starCenterClient == null) {
|
||||
log.error(
|
||||
`No OVSDB client found for star-center host: '${starCenter.name_label}'`
|
||||
)
|
||||
log.error('No OVSDB client found for star-center', {
|
||||
host: starCenter.name_label,
|
||||
pool: starCenter.$pool.name_label,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -659,9 +660,73 @@ class SDNController extends EventEmitter {
|
||||
encapsulation
|
||||
)
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Couldn't add host: '${host.name_label}' to network: '${network.name_label}' in pool: '${host.$pool.name_label}' because: ${error}`
|
||||
)
|
||||
log.error('Error while connection host to private network', {
|
||||
error,
|
||||
network: network.name_label,
|
||||
host: host.name_label,
|
||||
pool: host.$pool.name_label,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async _addHostToPoolNetworks(host) {
|
||||
const xapi = host.$xapi
|
||||
|
||||
const tunnels = filter(xapi.objects.all, { $type: 'tunnel' })
|
||||
for (const tunnel of tunnels) {
|
||||
const accessPif = xapi.getObjectByRef(tunnel.access_PIF)
|
||||
if (accessPif.host !== host.$ref) {
|
||||
continue
|
||||
}
|
||||
|
||||
const poolNetwork = find(this._poolNetworks, {
|
||||
network: accessPif.network,
|
||||
})
|
||||
if (poolNetwork == null || accessPif.currently_attached) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
await xapi.call('PIF.plug', accessPif.$ref)
|
||||
} catch (error) {
|
||||
log.error('Error while plugging PIF', {
|
||||
error,
|
||||
pif: accessPif.device,
|
||||
network: accessPif.$network.name_label,
|
||||
host: host.name_label,
|
||||
pool: host.$pool.name_label,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
log.debug('PIF plugged', {
|
||||
pif: accessPif.device,
|
||||
network: accessPif.$network.name_label,
|
||||
host: host.name_label,
|
||||
pool: host.$pool.name_label,
|
||||
})
|
||||
|
||||
const starCenter = xapi.getObjectByRef(poolNetwork.starCenter)
|
||||
await this._addHostToNetwork(host, accessPif.$network, starCenter)
|
||||
}
|
||||
}
|
||||
|
||||
async _hostUnreachable(host) {
|
||||
const poolNetworks = filter(this._poolNetworks, { starCenter: host.$ref })
|
||||
for (const poolNetwork of poolNetworks) {
|
||||
const network = host.$xapi.getObjectByRef(poolNetwork.network)
|
||||
log.debug('Unreachable star-center, electing a new one', {
|
||||
network: network.name_label,
|
||||
center: host.name_label,
|
||||
pool: host.$pool.name_label,
|
||||
})
|
||||
|
||||
const newCenter = await this._electNewCenter(network, true)
|
||||
poolNetwork.starCenter = newCenter?.$ref
|
||||
this._starCenters.delete(host.$id)
|
||||
if (newCenter !== null) {
|
||||
this._starCenters.set(newCenter.$id, newCenter.$ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,15 +774,19 @@ class SDNController extends EventEmitter {
|
||||
subject: subject,
|
||||
}
|
||||
|
||||
openssl.generateRSAPrivateKey(rsakeyoptions, (err, cakey, cmd) => {
|
||||
if (err) {
|
||||
log.error(`Error while generating CA private key: ${err}`)
|
||||
openssl.generateRSAPrivateKey(rsakeyoptions, (error, cakey, cmd) => {
|
||||
if (error !== undefined) {
|
||||
log.error('Error while generating CA private key', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
openssl.generateCSR(cacsroptions, cakey, null, (err, csr, cmd) => {
|
||||
if (err) {
|
||||
log.error(`Error while generating CA certificate: ${err}`)
|
||||
openssl.generateCSR(cacsroptions, cakey, null, (error, csr, cmd) => {
|
||||
if (error !== undefined) {
|
||||
log.error('Error while generating CA certificate', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -726,45 +795,58 @@ class SDNController extends EventEmitter {
|
||||
cacsroptions,
|
||||
cakey,
|
||||
null,
|
||||
async (err, cacrt, cmd) => {
|
||||
if (err) {
|
||||
log.error(`Error while signing CA certificate: ${err}`)
|
||||
async (error, cacrt, cmd) => {
|
||||
if (error !== undefined) {
|
||||
log.error('Error while signing CA certificate', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await fileWrite(join(dataDir, CA_CERT), cacrt)
|
||||
openssl.generateRSAPrivateKey(
|
||||
rsakeyoptions,
|
||||
async (err, key, cmd) => {
|
||||
if (err) {
|
||||
log.error(`Error while generating private key: ${err}`)
|
||||
async (error, key, cmd) => {
|
||||
if (error !== undefined) {
|
||||
log.error('Error while generating private key', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await fileWrite(join(dataDir, CLIENT_KEY), key)
|
||||
openssl.generateCSR(csroptions, key, null, (err, csr, cmd) => {
|
||||
if (err) {
|
||||
log.error(`Error while generating certificate: ${err}`)
|
||||
return
|
||||
}
|
||||
openssl.CASignCSR(
|
||||
csr,
|
||||
cacsroptions,
|
||||
false,
|
||||
cacrt,
|
||||
cakey,
|
||||
null,
|
||||
async (err, crt, cmd) => {
|
||||
if (err) {
|
||||
log.error(`Error while signing certificate: ${err}`)
|
||||
return
|
||||
}
|
||||
|
||||
await fileWrite(join(dataDir, CLIENT_CERT), crt)
|
||||
this.emit('certWritten')
|
||||
openssl.generateCSR(
|
||||
csroptions,
|
||||
key,
|
||||
null,
|
||||
(error, csr, cmd) => {
|
||||
if (error !== undefined) {
|
||||
log.error('Error while generating certificate', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
)
|
||||
})
|
||||
openssl.CASignCSR(
|
||||
csr,
|
||||
cacsroptions,
|
||||
false,
|
||||
cacrt,
|
||||
cakey,
|
||||
null,
|
||||
async (error, crt, cmd) => {
|
||||
if (error !== undefined) {
|
||||
log.error('Error while signing certificate', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await fileWrite(join(dataDir, CLIENT_CERT), crt)
|
||||
this.emit('certWritten')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ export class OvsdbClient {
|
||||
|
||||
this.updateCertificates(clientKey, clientCert, caCert)
|
||||
|
||||
log.debug(`[${this._host.name_label}] New OVSDB client`)
|
||||
log.debug('New OVSDB client', {
|
||||
host: this._host.name_label,
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -35,12 +37,18 @@ export class OvsdbClient {
|
||||
return this._host.$id
|
||||
}
|
||||
|
||||
get hostMetricsRef() {
|
||||
return this._host.metrics
|
||||
}
|
||||
|
||||
updateCertificates(clientKey, clientCert, caCert) {
|
||||
this._clientKey = clientKey
|
||||
this._clientCert = clientCert
|
||||
this._caCert = caCert
|
||||
|
||||
log.debug(`[${this._host.name_label}] Certificates have been updated`)
|
||||
log.debug('Certificates have been updated', {
|
||||
host: this._host.name_label,
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -134,16 +142,26 @@ export class OvsdbClient {
|
||||
} while (opResult && !error)
|
||||
|
||||
if (error != null) {
|
||||
log.error(
|
||||
`[${this._host.name_label}] Error while adding port: '${portName}' and interface: '${interfaceName}' to bridge: '${bridgeName}' on network: '${networkName}' because: ${error}: ${details}`
|
||||
)
|
||||
log.error('Error while adding port and interface to bridge', {
|
||||
error,
|
||||
details,
|
||||
port: portName,
|
||||
interface: interfaceName,
|
||||
bridge: bridgeName,
|
||||
network: networkName,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
socket.destroy()
|
||||
return
|
||||
}
|
||||
|
||||
log.debug(
|
||||
`[${this._host.name_label}] Port: '${portName}' and interface: '${interfaceName}' added to bridge: '${bridgeName}' on network: '${networkName}'`
|
||||
)
|
||||
log.debug('Port and interface added to bridge', {
|
||||
port: portName,
|
||||
interface: interfaceName,
|
||||
bridge: bridgeName,
|
||||
network: networkName,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
socket.destroy()
|
||||
}
|
||||
|
||||
@@ -182,9 +200,6 @@ export class OvsdbClient {
|
||||
|
||||
forOwn(selectResult.other_config[1], config => {
|
||||
if (config[0] === 'private_pool_wide' && config[1] === 'true') {
|
||||
log.debug(
|
||||
`[${this._host.name_label}] Adding port: '${selectResult.name}' to delete list from bridge: '${bridgeName}'`
|
||||
)
|
||||
portsToDelete.push(['uuid', portUuid])
|
||||
}
|
||||
})
|
||||
@@ -210,16 +225,20 @@ export class OvsdbClient {
|
||||
return
|
||||
}
|
||||
if (jsonObjects[0].error != null) {
|
||||
log.error(
|
||||
`[${this._host.name_label}] Couldn't delete ports from bridge: '${bridgeName}' because: ${jsonObjects.error}`
|
||||
)
|
||||
log.error('Error while deleting ports from bridge', {
|
||||
error: jsonObjects.error,
|
||||
bridge: bridgeName,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
socket.destroy()
|
||||
return
|
||||
}
|
||||
|
||||
log.debug(
|
||||
`[${this._host.name_label}] Deleted ${jsonObjects[0].result[0].count} ports from bridge: '${bridgeName}'`
|
||||
)
|
||||
log.debug('Ports deleted from bridge', {
|
||||
nPorts: jsonObjects[0].result[0].count,
|
||||
bridge: bridgeName,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
socket.destroy()
|
||||
}
|
||||
|
||||
@@ -270,14 +289,15 @@ export class OvsdbClient {
|
||||
socket
|
||||
)
|
||||
if (selectResult == null) {
|
||||
log.error('No bridge found for network', {
|
||||
network: networkName,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
return [null, null]
|
||||
}
|
||||
|
||||
const bridgeUuid = selectResult._uuid[1]
|
||||
const bridgeName = selectResult.name
|
||||
log.debug(
|
||||
`[${this._host.name_label}] Found bridge: '${bridgeName}' for network: '${networkName}'`
|
||||
)
|
||||
|
||||
return [bridgeUuid, bridgeName]
|
||||
}
|
||||
@@ -383,16 +403,24 @@ export class OvsdbClient {
|
||||
}
|
||||
const jsonResult = jsonObjects[0].result[0]
|
||||
if (jsonResult.error != null) {
|
||||
log.error(
|
||||
`[${this._host.name_label}] Couldn't retrieve: '${columns}' in: '${table}' because: ${jsonResult.error}: ${jsonResult.details}`
|
||||
)
|
||||
log.error('Error while selecting columns', {
|
||||
error: jsonResult.error,
|
||||
details: jsonResult.details,
|
||||
columns,
|
||||
table,
|
||||
where,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
if (jsonResult.rows.length === 0) {
|
||||
log.error(
|
||||
`[${this._host.name_label}] No '${columns}' found in: '${table}' where: '${where}'`
|
||||
)
|
||||
log.error('No result for select', {
|
||||
columns,
|
||||
table,
|
||||
where,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -419,9 +447,10 @@ export class OvsdbClient {
|
||||
try {
|
||||
stream.write(JSON.stringify(req))
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`[${this._host.name_label}] Error while writing into stream: ${error}`
|
||||
)
|
||||
log.error('Error while writing into stream', {
|
||||
error,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -432,9 +461,10 @@ export class OvsdbClient {
|
||||
try {
|
||||
result = await fromEvent(stream, 'data', {})
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`[${this._host.name_label}] Error while waiting for stream data: ${error}`
|
||||
)
|
||||
log.error('Error while waiting for stream data', {
|
||||
error,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -462,18 +492,20 @@ export class OvsdbClient {
|
||||
try {
|
||||
await fromEvent(socket, 'secureConnect', {})
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`[${this._host.name_label}] TLS connection failed because: ${error}: ${error.code}`
|
||||
)
|
||||
log.error('TLS connection failed', {
|
||||
error,
|
||||
code: error.code,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
throw error
|
||||
}
|
||||
|
||||
log.debug(`[${this._host.name_label}] TLS connection successful`)
|
||||
|
||||
socket.on('error', error => {
|
||||
log.error(
|
||||
`[${this._host.name_label}] OVSDB client socket error: ${error} with code: ${error.code}`
|
||||
)
|
||||
log.error('Socket error', {
|
||||
error,
|
||||
code: error.code,
|
||||
host: this._host.name_label,
|
||||
})
|
||||
})
|
||||
|
||||
return socket
|
||||
|
||||
@@ -76,6 +76,7 @@ const TRANSFORMS = {
|
||||
cores: cpuInfo && +cpuInfo.cpu_count,
|
||||
sockets: cpuInfo && +cpuInfo.socket_count,
|
||||
},
|
||||
zstdSupported: obj.restrictions.restrict_zstd_export === 'false',
|
||||
|
||||
// TODO
|
||||
// - ? networks = networksByPool.items[pool.id] (network.$pool.id)
|
||||
@@ -142,6 +143,7 @@ const TRANSFORMS = {
|
||||
current_operations: obj.current_operations,
|
||||
hostname: obj.hostname,
|
||||
iscsiIqn: obj.iscsi_iqn ?? otherConfig.iscsi_iqn ?? '',
|
||||
zstdSupported: obj.license_params.restrict_zstd_export === 'false',
|
||||
license_params: obj.license_params,
|
||||
license_server: obj.license_server,
|
||||
license_expiry: toTimestamp(obj.license_params.expiry),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "xo-web",
|
||||
"version": "5.45.1",
|
||||
"version": "5.46.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Web interface client for Xen-Orchestra",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import _ from 'intl'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import Select from 'form/select'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { omit } from 'lodash'
|
||||
|
||||
import decorate from './apply-decorators'
|
||||
import { Select } from './form'
|
||||
|
||||
const OPTIONS = [
|
||||
{
|
||||
@@ -12,27 +16,40 @@ const OPTIONS = [
|
||||
label: _('chooseCompressionGzipOption'),
|
||||
value: 'native',
|
||||
},
|
||||
]
|
||||
|
||||
const OPTIONS_WITH_ZSTD = [
|
||||
...OPTIONS,
|
||||
{
|
||||
label: _('chooseCompressionZstdOption'),
|
||||
value: 'zstd',
|
||||
},
|
||||
]
|
||||
|
||||
const SelectCompression = ({ onChange, value, ...props }) => (
|
||||
<Select
|
||||
labelKey='label'
|
||||
onChange={onChange}
|
||||
options={OPTIONS}
|
||||
required
|
||||
simpleValue
|
||||
value={value}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
SelectCompression.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
const SELECT_COMPRESSION_PROP_TYPES = {
|
||||
showZstd: PropTypes.bool,
|
||||
}
|
||||
|
||||
const SelectCompression = decorate([
|
||||
provideState({
|
||||
computed: {
|
||||
options: (_, { showZstd }) => (showZstd ? OPTIONS_WITH_ZSTD : OPTIONS),
|
||||
selectProps: (_, props) =>
|
||||
omit(props, Object.keys(SELECT_COMPRESSION_PROP_TYPES)),
|
||||
},
|
||||
}),
|
||||
injectState,
|
||||
({ onChange, state, value }) => (
|
||||
<Select
|
||||
labelKey='label'
|
||||
options={state.options}
|
||||
required
|
||||
simpleValue
|
||||
{...state.selectProps}
|
||||
/>
|
||||
),
|
||||
])
|
||||
|
||||
SelectCompression.defaultProps = { showZstd: true }
|
||||
SelectCompression.propTypes = SELECT_COMPRESSION_PROP_TYPES
|
||||
export { SelectCompression as default }
|
||||
|
||||
@@ -81,18 +81,6 @@ export default class Tooltip extends Component {
|
||||
this._removeListeners()
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
if (props.children !== this.props.children) {
|
||||
this._removeListeners()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.children !== this.props.children) {
|
||||
this._addListeners()
|
||||
}
|
||||
}
|
||||
|
||||
_addListeners() {
|
||||
const node = (this._node = ReactDOM.findDOMNode(this))
|
||||
|
||||
@@ -116,15 +104,18 @@ export default class Tooltip extends Component {
|
||||
this._node = null
|
||||
}
|
||||
|
||||
_showTooltip = () => {
|
||||
_showTooltip = event => {
|
||||
const { props } = this
|
||||
|
||||
instance.setState({
|
||||
className: props.className,
|
||||
content: props.content,
|
||||
show: true,
|
||||
style: props.style,
|
||||
})
|
||||
instance.setState(
|
||||
{
|
||||
className: props.className,
|
||||
content: props.content,
|
||||
show: true,
|
||||
style: props.style,
|
||||
},
|
||||
() => this._updateTooltip(event)
|
||||
)
|
||||
}
|
||||
|
||||
_hideTooltip = () => {
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import BaseComponent from 'base-component'
|
||||
import React from 'react'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import { injectIntl } from 'react-intl'
|
||||
|
||||
import _, { messages } from '../../intl'
|
||||
import SelectCompression from '../../select-compression'
|
||||
import SingleLineRow from '../../single-line-row'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import { Col } from '../../grid'
|
||||
import { connectStore } from '../../utils'
|
||||
import { createGetObject, createSelector } from '../../selectors'
|
||||
import { SelectSr } from '../../select-objects'
|
||||
import { injectIntl } from 'react-intl'
|
||||
|
||||
@connectStore(
|
||||
{
|
||||
isZstdSupported: createSelector(
|
||||
createGetObject((_, { vm }) => vm.$container),
|
||||
container => container.zstdSupported
|
||||
),
|
||||
},
|
||||
{ withRef: true }
|
||||
)
|
||||
class CopyVmModalBody extends BaseComponent {
|
||||
state = {
|
||||
compression: '',
|
||||
@@ -27,7 +38,10 @@ class CopyVmModalBody extends BaseComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl
|
||||
const {
|
||||
intl: { formatMessage },
|
||||
isZstdSupported,
|
||||
} = this.props
|
||||
const { compression, copyMode, name, sr } = this.state
|
||||
|
||||
return process.env.XOA_PLAN > 2 ? (
|
||||
@@ -81,6 +95,7 @@ class CopyVmModalBody extends BaseComponent {
|
||||
<SelectCompression
|
||||
disabled={copyMode !== 'fullCopy'}
|
||||
onChange={this.linkState('compression')}
|
||||
showZstd={isZstdSupported}
|
||||
value={compression}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
@@ -395,7 +395,7 @@ export default decorate([
|
||||
{ saveSchedule },
|
||||
storedSchedule = DEFAULT_SCHEDULE
|
||||
) => async (
|
||||
{ copyMode, exportMode, snapshotMode },
|
||||
{ copyMode, exportMode, missingBackupMode, snapshotMode },
|
||||
{ intl: { formatMessage } }
|
||||
) => {
|
||||
const schedule = await form({
|
||||
@@ -415,6 +415,7 @@ export default decorate([
|
||||
handler: value => {
|
||||
if (
|
||||
!(
|
||||
missingBackupMode ||
|
||||
(exportMode && value.exportRetention > 0) ||
|
||||
(copyMode && value.copyRetention > 0) ||
|
||||
(snapshotMode && value.snapshotRetention > 0)
|
||||
|
||||
Reference in New Issue
Block a user