Compare commits

...

8 Commits

Author SHA1 Message Date
Pierre Donias
0fad24d757 Handle clone and copy 2019-07-23 17:05:02 +02:00
Pierre Donias
3f0878940f WiP: VM owner 2019-07-23 17:05:01 +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
13 changed files with 454 additions and 241 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

@@ -150,6 +150,8 @@ export async function create(params) {
}
const xapiVm = await xapi.createVm(template._xapiId, params, checkLimits)
await xapiVm.update_other_config('owner', user.id)
const vm = xapi.xo.addObject(xapiVm)
if (resourceSet) {
@@ -663,15 +665,16 @@ export const clone = defer(async function(
await checkPermissionOnSrs.call(this, vm)
const xapi = this.getXapi(vm)
const { $id: cloneId } = await xapi.cloneVm(vm._xapiRef, {
const xapiVm = await xapi.cloneVm(vm._xapiRef, {
nameLabel: name,
fast: !fullCopy,
})
$defer.onFailure(() => xapi.deleteVm(cloneId))
$defer.onFailure(() => xapi.deleteVm(xapiVm.$id))
await xapiVm.update_other_config('owner', this.user.id)
const isAdmin = this.user.permission === 'admin'
if (!isAdmin) {
await this.addAcl(this.user.id, cloneId, 'admin')
await this.addAcl(this.user.id, xapiVm.$id, 'admin')
}
if (vm.resourceSet !== undefined) {
@@ -682,7 +685,7 @@ export const clone = defer(async function(
)
}
return cloneId
return xapiVm.$id
})
clone.params = {
@@ -704,19 +707,26 @@ export async function copy({ compress, name: nameLabel, sr, vm }) {
await checkPermissionOnSrs.call(this, vm)
}
return this.getXapi(vm)
.copyVm(vm._xapiId, sr._xapiId, {
nameLabel,
})
.then(vm => vm.$id)
}
return this.getXapi(vm)
.remoteCopyVm(vm._xapiId, this.getXapi(sr), sr._xapiId, {
compress,
const xapiVm = await this.getXapi(vm).copyVm(vm._xapiId, sr._xapiId, {
nameLabel,
})
.then(({ vm }) => vm.$id)
await xapiVm.update_other_config('owner', this.user.id)
return xapiVm.$id
}
const { vm: xapiVm } = await this.getXapi(vm).remoteCopyVm(
vm._xapiId,
this.getXapi(sr),
sr._xapiId,
{
compress,
nameLabel,
}
)
await xapiVm.update_other_config('owner', this.user.id)
return xapiVm.$id
}
copy.params = {

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

@@ -49,6 +49,7 @@ const messages = {
backupJobs: 'Backup jobs',
iscsiSessions:
'({ nSessions, number }) iSCSI session{nSessions, plural, one {} other {s}}',
owner: 'Owner',
// ----- Modals -----
alertOk: 'OK',

View File

@@ -12,7 +12,7 @@ import Tooltip from './tooltip'
import { addSubscriptions, connectStore, formatSize } from './utils'
import { createGetObject, createSelector } from './selectors'
import { FormattedDate } from 'react-intl'
import { isSrWritable, subscribeRemotes } from './xo'
import { isSrWritable, subscribeRemotes, subscribeUsers } from './xo'
// ===================================================================
@@ -392,6 +392,40 @@ Vgpu.propTypes = {
// ===================================================================
export const User = decorate([
addSubscriptions(({ id }) => ({
user: cb => subscribeUsers(users => cb(find(users, { id }))),
})),
({ id, user, link, newTab }) => {
if (user === undefined) {
return unknowItem(id, 'user')
}
return (
<LinkWrapper
link={link}
newTab={newTab}
to={`/settings/acls?s=subject:id:${id}`}
>
<Icon icon='user' /> {user.email}
</LinkWrapper>
)
},
])
User.propTypes = {
id: PropTypes.string.isRequired,
link: PropTypes.bool,
newTab: PropTypes.bool,
}
User.defaultProps = {
link: false,
newTab: false,
}
// ===================================================================
const xoItemToRender = {
// Subscription objects.
cloudConfig: template => (

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

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

View File

@@ -6,7 +6,7 @@ import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import React from 'react'
import HomeTags from 'home-tags'
import renderXoItem from 'render-xo-item'
import renderXoItem, { User } from 'render-xo-item'
import Tooltip from 'tooltip'
import { addTag, editVm, removeTag } from 'xo'
import { BlockLink } from 'link'
@@ -19,6 +19,7 @@ import {
createGetObjectsOfType,
createGetVmLastShutdownTime,
createSelector,
isAdmin,
} from 'selectors'
import {
connectStore,
@@ -48,6 +49,7 @@ export default connectStore(() => {
)
return {
isAdmin,
lastShutdownTime: createGetVmLastShutdownTime(),
tasks: createGetObjectsOfType('task')
.pick(
@@ -63,6 +65,7 @@ export default connectStore(() => {
}
})(
({
isAdmin,
lastShutdownTime,
statsOverview,
tasks,
@@ -78,6 +81,7 @@ export default connectStore(() => {
installTime,
memory,
os_version: osVersion,
other,
power_state: powerState,
startTime,
tags,
@@ -209,6 +213,18 @@ export default connectStore(() => {
</BlockLink>
</Col>
</Row>
{isAdmin && other.owner !== undefined && (
<Row className='text-xs-center'>
<Col>
{_('keyValue', {
key: _('owner'),
value: (
<User id={other.owner} link={process.env.XOA_PLAN > 2} />
),
})}
</Col>
</Row>
)}
{!xenTools && powerState === 'Running' && (
<Row className='text-xs-center'>
<Col>