Compare commits

...

7 Commits

Author SHA1 Message Date
Pierre Donias
67f7ce40da fix(xo-web/tooltip): tooltip disappearing when children change
- Don't remove the listeners when the children change
  (The children change but the <Tooltip /> is still there)
- Don't add new listeners when the childrent change
- Update the tooltip on mouseenter so the position is correct
  even when the tooltipped elements appears under the cursor
2019-07-23 15:59:43 +02:00
badrAZ
a00e3e6f41 feat(xo-web#copyVm): only show zstd option when it's supported (#4326)
See #3892
2019-07-23 15:00:55 +02:00
BenjiReis
82ba02b4f3 chore(xo-server-sdn-controller): rework logs (#4365) 2019-07-23 11:45:51 +02:00
badrAZ
d70ae6ebe3 feat(xo-web/backup-ng/new): create schedule without mode (#4357)
Fixes #4098
2019-07-22 15:22:01 +02:00
BenjiReis
f6c411a261 fix(xo-server-sdn-controller): better monitor of host life cycle (#4314) 2019-07-22 14:53:42 +02:00
badrAZ
b606eaf9ee chore(CHANGELOG): update next 2019-07-22 10:42:04 +02:00
badrAZ
516edd1b09 feat(xo-web): 5.46.0 2019-07-22 10:29:05 +02:00
10 changed files with 386 additions and 243 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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')
}
)
}
)
}
)
}

View File

@@ -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

View File

@@ -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),

View File

@@ -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": [

View File

@@ -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 }

View File

@@ -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 = () => {

View File

@@ -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>

View File

@@ -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)