Compare commits

...
7 changed files with 97 additions and 56 deletions
+4
View File
@@ -15,6 +15,9 @@
- [Proxies] Fix `xapi.getOrWaitObject is not a function` is not a function during deployment - [Proxies] Fix `xapi.getOrWaitObject is not a function` is not a function during deployment
- [REST API] Fix empty object's tasks list - [REST API] Fix empty object's tasks list
- [REST API] Fix incorrect `href` in `/:collection/:object/tasks` - [REST API] Fix incorrect `href` in `/:collection/:object/tasks`
- [VM/Migration] Fix VDIs that were not migrated to the destination SR (PR [#7360](https://github.com/vatesfr/xen-orchestra/pull/7360))
- [Home/VM] VMs migration from the home view will no longer execute a `migrate_send` unless it is necessary [Forum#8279](https://xcp-ng.org/forum/topic/8279/getting-errors-when-migrating-4-out-5-vmguest/)(PR [#7360](https://github.com/vatesfr/xen-orchestra/pull/7360))
- [VM/migration] SR is no longer required if you select a migration network (PR [#7360](https://github.com/vatesfr/xen-orchestra/pull/7360))
### Packages to release ### Packages to release
@@ -35,5 +38,6 @@
- @xen-orchestra/immutable-backups patch - @xen-orchestra/immutable-backups patch
- @xen-orchestra/xva patch - @xen-orchestra/xva patch
- xo-server patch - xo-server patch
- xo-web patch
<!--packages-end--> <!--packages-end-->
+24 -13
View File
@@ -495,10 +495,8 @@ export default class Xapi extends XapiBase {
bypassAssert = false, bypassAssert = false,
} }
) { ) {
const srRef = sr !== undefined ? hostXapi.getObject(sr).$ref : undefined
const getDefaultSrRef = once(() => { const getDefaultSrRef = once(() => {
if (sr !== undefined) {
return hostXapi.getObject(sr).$ref
}
const defaultSr = host.$pool.$default_SR const defaultSr = host.$pool.$default_SR
if (defaultSr === undefined) { if (defaultSr === undefined) {
throw new Error(`This operation requires a default SR to be set on the pool ${host.$pool.name_label}`) throw new Error(`This operation requires a default SR to be set on the pool ${host.$pool.name_label}`)
@@ -506,6 +504,28 @@ export default class Xapi extends XapiBase {
return defaultSr.$ref return defaultSr.$ref
}) })
// VDIs/SRs mapping
// For VDI:
// - If a map of VDI -> SR was explicitly passed: use it
// - Else if SR was explicitly passed: use it
// - Else if VDI SR is reachable from the destination host: use it
// - Else: use the migration main SR or the pool's default SR (error if none of them is defined)
function getMigrationSrRef(vdi) {
if (mapVdisSrs[vdi.$id] !== undefined) {
return hostXapi.getObject(mapVdisSrs[vdi.$id]).$ref
}
if (srRef !== undefined) {
return srRef
}
if (isSrConnected(vdi.$SR)) {
return vdi.$SR.$ref
}
return getDefaultSrRef()
}
const hostPbds = new Set(host.PBDs) const hostPbds = new Set(host.PBDs)
const connectedSrs = new Map() const connectedSrs = new Map()
const isSrConnected = sr => { const isSrConnected = sr => {
@@ -518,10 +538,6 @@ export default class Xapi extends XapiBase {
} }
// VDIs/SRs mapping // VDIs/SRs mapping
// For VDI:
// - If SR was explicitly passed: use it
// - Else if VDI SR is reachable from the destination host: use it
// - Else: use the migration main SR or the pool's default SR (error if none of them is defined)
// For VDI-snapshot: // For VDI-snapshot:
// - If VDI-snapshot is an orphan snapshot: same logic as a VDI // - If VDI-snapshot is an orphan snapshot: same logic as a VDI
// - Else: don't add it to the map (VDI -> SR). It will be managed by the XAPI (snapshot will be migrated to the same SR as its parent active VDI) // - Else: don't add it to the map (VDI -> SR). It will be managed by the XAPI (snapshot will be migrated to the same SR as its parent active VDI)
@@ -534,12 +550,7 @@ export default class Xapi extends XapiBase {
if (vdi.$snapshot_of !== undefined) { if (vdi.$snapshot_of !== undefined) {
continue continue
} }
vdis[vdi.$ref] = vdis[vdi.$ref] = getMigrationSrRef(vdi)
mapVdisSrs[vdi.$id] !== undefined
? hostXapi.getObject(mapVdisSrs[vdi.$id]).$ref
: isSrConnected(vdi.$SR)
? vdi.$SR.$ref
: getDefaultSrRef()
} }
} }
@@ -2046,6 +2046,7 @@ const messages = {
vmsWithDuplicatedMacAddressesMessage: vmsWithDuplicatedMacAddressesMessage:
'{nVms, number} VM{nVms, plural, one {} other {s}} contain{nVms, plural, one {s} other {}} duplicate MAC addresses or {nVms, plural, one {has} other {have}} the same MAC addresses as other running VMs. Do you want to continue?', '{nVms, number} VM{nVms, plural, one {} other {s}} contain{nVms, plural, one {s} other {}} duplicate MAC addresses or {nVms, plural, one {has} other {have}} the same MAC addresses as other running VMs. Do you want to continue?',
ignoreVdi: 'Ignore this VDI', ignoreVdi: 'Ignore this VDI',
selectDestinationSr: 'Select a destination SR',
// ----- Servers ----- // ----- Servers -----
enableServerErrorTitle: 'Enable server', enableServerErrorTitle: 'Enable server',
@@ -59,6 +59,7 @@ export default class ChooseSrForEachVdisModal extends Component {
ignorableVdis = false, ignorableVdis = false,
mainSrPredicate = isSrWritable, mainSrPredicate = isSrWritable,
placeholder, placeholder,
required,
srPredicate = mainSrPredicate, srPredicate = mainSrPredicate,
value: { mainSr, mapVdisSrs }, value: { mainSr, mapVdisSrs },
vdis, vdis,
@@ -66,15 +67,21 @@ export default class ChooseSrForEachVdisModal extends Component {
return ( return (
<div> <div>
<SelectSr <SingleLineRow>
onChange={this._onChangeMainSr} <Col size={6}>{_('selectDestinationSr')}</Col>
placeholder={placeholder !== undefined ? placeholder : _('chooseSrForEachVdisModalMainSr')} <Col size={6}>
predicate={mainSrPredicate} <SelectSr
required onChange={this._onChangeMainSr}
value={mainSr} placeholder={placeholder !== undefined ? placeholder : _('chooseSrForEachVdisModalMainSr')}
/> predicate={mainSrPredicate}
required
value={mainSr}
/>
</Col>
</SingleLineRow>
{!required && <i>{_('optionalEntry')}</i>}
<br /> <br />
{!isEmpty(vdis) && mainSr != null && ( {!isEmpty(vdis) && (
<Collapsible buttonText={_('chooseSrForEachVdisModalSelectSr')} collapsible size='small'> <Collapsible buttonText={_('chooseSrForEachVdisModalSelectSr')} collapsible size='small'>
<br /> <br />
<Container> <Container>
+8 -4
View File
@@ -1688,17 +1688,16 @@ export const migrateVm = async (vm, host) => {
return return
} }
const { migrationNetwork, sr, targetHost } = params const { sr, srRequired, targetHost } = params
if (!targetHost) { if (!targetHost) {
return error(_('migrateVmNoTargetHost'), _('migrateVmNoTargetHostMessage')) return error(_('migrateVmNoTargetHost'), _('migrateVmNoTargetHostMessage'))
} }
// Workaround to prevent VM's VDIs from unexpectedly migrating to the default SR if (srRequired && sr === undefined) {
// if migration network is defined, the SR is required.
if (migrationNetwork !== undefined && sr === undefined) {
return error(_('migrateVmNoSr'), _('migrateVmNoSrMessage')) return error(_('migrateVmNoSr'), _('migrateVmNoSrMessage'))
} }
delete params.srRequired
try { try {
await _call('vm.migrate', { vm: vm.id, ...params }) await _call('vm.migrate', { vm: vm.id, ...params })
@@ -1733,6 +1732,11 @@ export const migrateVms = vms =>
return error(_('migrateVmNoTargetHost'), _('migrateVmNoTargetHostMessage')) return error(_('migrateVmNoTargetHost'), _('migrateVmNoTargetHostMessage'))
} }
if (params.srRequired && params.sr === undefined) {
return error(_('migrateVmNoTargetHost'), _('migrateVmNoTargetHostMessage'))
}
delete params.srRequired
const { mapVmsMapVdisSrs, mapVmsMapVifsNetworks, migrationNetwork, sr, targetHost, vms } = params const { mapVmsMapVdisSrs, mapVmsMapVifsNetworks, migrationNetwork, sr, targetHost, vms } = params
Promise.all( Promise.all(
map(vms, ({ id }) => map(vms, ({ id }) =>
@@ -122,6 +122,7 @@ export default class MigrateVmModalBody extends BaseComponent {
migrationNetwork: this.state.migrationNetworkId, migrationNetwork: this.state.migrationNetworkId,
sr: resolveId(this.state.targetSrs.mainSr), sr: resolveId(this.state.targetSrs.mainSr),
targetHost: this.state.host && this.state.host.id, targetHost: this.state.host && this.state.host.id,
srRequired: !this.state.doNotMigrateVdis,
} }
} }
@@ -222,14 +223,14 @@ export default class MigrateVmModalBody extends BaseComponent {
</Col> </Col>
</SingleLineRow> </SingleLineRow>
</div> </div>
{host && (!doNotMigrateVdis || migrationNetworkId != null) && ( {host && (
<div className={styles.groupBlock}> <div className={styles.groupBlock}>
<SingleLineRow> <SingleLineRow>
<Col size={12}> <Col size={12}>
<ChooseSrForEachVdisModal <ChooseSrForEachVdisModal
mainSrPredicate={this._getSrPredicate()} mainSrPredicate={this._getSrPredicate()}
onChange={this.linkState('targetSrs')} onChange={this.linkState('targetSrs')}
required required={!doNotMigrateVdis}
value={targetSrs} value={targetSrs}
vdis={vdis} vdis={vdis}
/> />
@@ -101,10 +101,6 @@ export default class MigrateVmsModalBody extends BaseComponent {
) )
} }
componentDidMount() {
this._selectHost(this.props.host)
}
get value() { get value() {
const { host } = this.state const { host } = this.state
const vms = filter(this.props.vms, vm => vm.$container !== host.id) const vms = filter(this.props.vms, vm => vm.$container !== host.id)
@@ -112,7 +108,15 @@ export default class MigrateVmsModalBody extends BaseComponent {
return { vms } return { vms }
} }
const { networks, pifs, vbdsByVm, vifsByVm } = this.props const { networks, pifs, vbdsByVm, vifsByVm } = this.props
const { doNotMigrateVdi, doNotMigrateVmVdis, migrationNetworkId, networkId, smartVifMapping, srId } = this.state const {
doNotMigrateVdi,
doNotMigrateVmVdis,
migrationNetworkId,
noVdisMigration,
networkId,
smartVifMapping,
srId,
} = this.state
// Map VM --> ( Map VDI --> SR ) // Map VM --> ( Map VDI --> SR )
// 2021-02-16: Fill the map (VDI -> SR) with *all* the VDIs to avoid unexpectedly migrating them to the wrong SRs: // 2021-02-16: Fill the map (VDI -> SR) with *all* the VDIs to avoid unexpectedly migrating them to the wrong SRs:
@@ -124,10 +128,14 @@ export default class MigrateVmsModalBody extends BaseComponent {
forEach(vbds, vbd => { forEach(vbds, vbd => {
const vdi = vbd.VDI const vdi = vbd.VDI
if (!vbd.is_cd_drive && vdi) { if (!vbd.is_cd_drive && vdi) {
mapVdisSrs[vdi] = doNotMigrateVmVdis[vm] || doNotMigrateVdi[vdi] ? this._getObject(vdi).$SR : srId if (!doNotMigrateVmVdis[vm] && !doNotMigrateVdi[vdi]) {
mapVdisSrs[vdi] = srId
}
} }
}) })
mapVmsMapVdisSrs[vm] = mapVdisSrs if (!isEmpty(mapVdisSrs)) {
mapVmsMapVdisSrs[vm] = mapVdisSrs
}
}) })
const defaultNetwork = const defaultNetwork =
@@ -160,6 +168,7 @@ export default class MigrateVmsModalBody extends BaseComponent {
mapVmsMapVifsNetworks, mapVmsMapVifsNetworks,
migrationNetwork: migrationNetworkId, migrationNetwork: migrationNetworkId,
sr: srId, sr: srId,
srRequired: !noVdisMigration,
targetHost: host.id, targetHost: host.id,
vms, vms,
} }
@@ -212,7 +221,7 @@ export default class MigrateVmsModalBody extends BaseComponent {
networkId: defaultMigrationNetworkId, networkId: defaultMigrationNetworkId,
noVdisMigration, noVdisMigration,
smartVifMapping: true, smartVifMapping: true,
srId: defaultSrConnectedToHost ? defaultSrId : undefined, srId: !noVdisMigration && defaultSrConnectedToHost ? defaultSrId : undefined,
}) })
} }
@@ -254,27 +263,11 @@ export default class MigrateVmsModalBody extends BaseComponent {
</Col> </Col>
</SingleLineRow> </SingleLineRow>
</div> </div>
{host !== undefined && ( {host !== undefined && [
<div style={LINE_STYLE}>
<SingleLineRow>
<Col size={6}>{_('migrateVmSelectMigrationNetwork')}</Col>
<Col size={6}>
<SelectNetwork
onChange={this._selectMigrationNetwork}
predicate={this._getMigrationNetworkPredicate()}
required={!intraPool}
value={migrationNetworkId}
/>
</Col>
</SingleLineRow>
{intraPool && <i>{_('optionalEntry')}</i>}
</div>
)}
{host && (!noVdisMigration || migrationNetworkId != null) && (
<div key='sr' style={LINE_STYLE}> <div key='sr' style={LINE_STYLE}>
<SingleLineRow> <SingleLineRow>
<Col size={6}> <Col size={6}>
{!intraPool ? _('migrateVmsSelectSr') : _('migrateVmsSelectSrIntraPool')}{' '} {_('selectDestinationSr')}{' '}
{(defaultSrId === undefined || !defaultSrConnectedToHost) && ( {(defaultSrId === undefined || !defaultSrConnectedToHost) && (
<Tooltip <Tooltip
content={ content={
@@ -292,11 +285,31 @@ export default class MigrateVmsModalBody extends BaseComponent {
)} )}
</Col> </Col>
<Col size={6}> <Col size={6}>
<SelectSr onChange={this._selectSr} predicate={this._getSrPredicate()} required value={srId} /> <SelectSr
onChange={this._selectSr}
predicate={this._getSrPredicate()}
required={!noVdisMigration}
value={srId}
/>
</Col> </Col>
</SingleLineRow> </SingleLineRow>
</div> {noVdisMigration && <i>{_('optionalEntry')}</i>}
)} </div>,
<div style={LINE_STYLE} key='network'>
<SingleLineRow>
<Col size={6}>{_('migrateVmSelectMigrationNetwork')}</Col>
<Col size={6}>
<SelectNetwork
onChange={this._selectMigrationNetwork}
predicate={this._getMigrationNetworkPredicate()}
required={!intraPool}
value={migrationNetworkId}
/>
</Col>
</SingleLineRow>
{intraPool && <i>{_('optionalEntry')}</i>}
</div>,
]}
{host && !intraPool && ( {host && !intraPool && (
<div key='network' style={LINE_STYLE}> <div key='network' style={LINE_STYLE}>
<SingleLineRow> <SingleLineRow>