feat(xo-web/new-vm): possibility to destroy cloud config drive after first boot (#6486)
Fixes #6438
This commit is contained in:
parent
b63c4a0d4f
commit
2eb3b15930
@ -9,6 +9,7 @@
|
||||
|
||||
- [Delta Backup] Use [NBD](https://en.wikipedia.org/wiki/Network_block_device) to download disks (PR [#6461](https://github.com/vatesfr/xen-orchestra/pull/6461))
|
||||
- [License] Possibility to bind XCP-ng license to hosts at pool level (PR [#6453](https://github.com/vatesfr/xen-orchestra/pull/6453))
|
||||
- [New VM] Ability to destroy the cloud configuration disk after the first boot [#6438](https://github.com/vatesfr/xen-orchestra/issues/6438) (PR [#6486](https://github.com/vatesfr/xen-orchestra/pull/6486))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
@ -48,6 +48,39 @@ const extract = (obj, prop) => {
|
||||
return value
|
||||
}
|
||||
|
||||
const startVmAndDestroyCloudConfigVdi = async (xapi, vm, vdiUuid, params) => {
|
||||
try {
|
||||
const timeLimit = Date.now() + 10 * 60 * 1000
|
||||
await xapi.startVm(vm.uuid)
|
||||
|
||||
if (params.destroyCloudConfigVdiAfterBoot && vdiUuid !== undefined) {
|
||||
// wait for the 'Running' event to be really stored in local xapi object cache
|
||||
await xapi.waitObjectState(vm.uuid, vm => vm.power_state === 'Running', { timeout: timeLimit - Date.now() })
|
||||
|
||||
// wait for the guest tool version to be defined
|
||||
await xapi
|
||||
.waitObjectState(
|
||||
xapi.getObjectByRef(vm.$ref).guest_metrics,
|
||||
gm => gm?.PV_drivers_version?.major !== undefined,
|
||||
{ timeout: timeLimit - Date.now() }
|
||||
)
|
||||
.catch(error => {
|
||||
log.warn('startVmAndDestroyCloudConfigVdi: failed to wait guest metrics, consider VM as started', {
|
||||
error,
|
||||
vm: { uuid: vm.uuid },
|
||||
})
|
||||
})
|
||||
|
||||
// destroy cloud config drive
|
||||
const vdi = xapi.getObjectByUuid(vdiUuid)
|
||||
await vdi.$VBDs[0].$unplug()
|
||||
await vdi.$destroy()
|
||||
}
|
||||
} catch (error) {
|
||||
log.warn('startVmAndDestroyCloudConfigVdi', { error, vdi: { uuid: vdiUuid }, vm: { uuid: vm.uuid } })
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement ACLs
|
||||
export const create = defer(async function ($defer, params) {
|
||||
const { user } = this.apiContext
|
||||
@ -156,6 +189,30 @@ export const create = defer(async function ($defer, params) {
|
||||
|
||||
const vm = xapi.xo.addObject(xapiVm)
|
||||
|
||||
// create cloud config drive
|
||||
let cloudConfigVdiUuid
|
||||
if (params.cloudConfig != null) {
|
||||
// Find the SR of the first VDI.
|
||||
let srId
|
||||
forEach(vm.$VBDs, vbdId => {
|
||||
const vbd = this.getObject(vbdId)
|
||||
const vdiId = vbd.VDI
|
||||
if (!vbd.is_cd_drive && vdiId !== undefined) {
|
||||
srId = this.getObject(vdiId).$SR
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
cloudConfigVdiUuid = params.coreOs
|
||||
? await xapi.createCoreOsCloudInitConfigDrive(vm.id, srId, params.cloudConfig)
|
||||
: await xapi.createCloudInitConfigDrive(vm.id, srId, params.cloudConfig, params.networkConfig)
|
||||
} catch (error) {
|
||||
log.warn('vm.create', { vmId: vm.id, srId, error })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceSet) {
|
||||
await Promise.all([
|
||||
params.share
|
||||
@ -173,7 +230,7 @@ export const create = defer(async function ($defer, params) {
|
||||
}
|
||||
|
||||
if (params.bootAfterCreate) {
|
||||
ignoreErrors.call(xapi.startVm(vm._xapiId))
|
||||
startVmAndDestroyCloudConfigVdi(xapi, xapiVm, cloudConfigVdiUuid, params)
|
||||
}
|
||||
|
||||
return vm.id
|
||||
@ -217,6 +274,11 @@ create.params = {
|
||||
optional: true,
|
||||
},
|
||||
|
||||
destroyCloudConfigVdiAfterBoot: {
|
||||
type: 'boolean',
|
||||
optional: true,
|
||||
},
|
||||
|
||||
installation: {
|
||||
type: 'object',
|
||||
optional: true,
|
||||
|
@ -1287,12 +1287,17 @@ export default class Xapi extends XapiBase {
|
||||
const host = this.pool.$master
|
||||
const sr = this.getObject(srId)
|
||||
|
||||
await this.call('host.call_plugin', host.$ref, 'xscontainer', 'create_config_drive', {
|
||||
vmuuid: vm.uuid,
|
||||
sruuid: sr.uuid,
|
||||
configuration: config,
|
||||
})
|
||||
// See https://github.com/xenserver/xscontainer/blob/master/src/scripts/xscontainer-pluginexample
|
||||
const vdiUuid = (
|
||||
await this.call('host.call_plugin', host.$ref, 'xscontainer', 'create_config_drive', {
|
||||
vmuuid: vm.uuid,
|
||||
sruuid: sr.uuid,
|
||||
configuration: config,
|
||||
})
|
||||
).replace(/^True/, '')
|
||||
await this.registerDockerContainer(vmId)
|
||||
|
||||
return vdiUuid
|
||||
}
|
||||
|
||||
// Generic Config Drive
|
||||
@ -1343,6 +1348,8 @@ export default class Xapi extends XapiBase {
|
||||
})
|
||||
|
||||
await this.VBD_create({ VDI: vdi.$ref, VM: vm.$ref })
|
||||
|
||||
return vdi.uuid
|
||||
}
|
||||
|
||||
@decorateWith(deferrable)
|
||||
|
@ -11,7 +11,7 @@ import { defer as deferrable } from 'golike-defer'
|
||||
import { ignoreErrors, pCatch } from 'promise-toolbox'
|
||||
import { Ref } from 'xen-api'
|
||||
|
||||
import { forEach, parseSize } from '../../utils.mjs'
|
||||
import { parseSize } from '../../utils.mjs'
|
||||
|
||||
import { isVmHvm, isVmRunning, makeEditObject } from '../utils.mjs'
|
||||
|
||||
@ -38,10 +38,6 @@ export default {
|
||||
vifs = undefined,
|
||||
existingVdis = undefined,
|
||||
|
||||
coreOs = false,
|
||||
cloudConfig = undefined,
|
||||
networkConfig = undefined,
|
||||
|
||||
vgpuType = undefined,
|
||||
gpuGroup = undefined,
|
||||
|
||||
@ -92,7 +88,7 @@ export default {
|
||||
// installation.
|
||||
await this.callAsync('VM.provision', vmRef)
|
||||
|
||||
let vm = await this._getOrWaitObject(vmRef)
|
||||
const vm = await this._getOrWaitObject(vmRef)
|
||||
|
||||
// Set VMs params.
|
||||
await this._editVm(vm, props, checkLimits)
|
||||
@ -212,28 +208,6 @@ export default {
|
||||
await this.createVgpu(vm, gpuGroup, vgpuType)
|
||||
}
|
||||
|
||||
if (cloudConfig != null) {
|
||||
// Refresh the record.
|
||||
await this.barrier(vm.$ref)
|
||||
vm = this.getObjectByRef(vm.$ref)
|
||||
|
||||
// Find the SR of the first VDI.
|
||||
let srRef
|
||||
forEach(vm.$VBDs, vbd => {
|
||||
let vdi
|
||||
if (vbd.type === 'Disk' && (vdi = vbd.$VDI)) {
|
||||
srRef = vdi.SR
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (coreOs) {
|
||||
await this.createCoreOsCloudInitConfigDrive(vm.$id, srRef, cloudConfig)
|
||||
} else {
|
||||
await this.createCloudInitConfigDrive(vm.$id, srRef, cloudConfig, networkConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// wait for the record with all the VBDs and VIFs
|
||||
return this.barrier(vm.$ref)
|
||||
},
|
||||
|
@ -1553,6 +1553,7 @@ const messages = {
|
||||
templateHasBiosStrings: 'The template already contains the BIOS strings',
|
||||
secureBootLinkToDocumentationMessage: 'Click for more information about Guest UEFI Secure Boot.',
|
||||
vmBootFirmwareIsUefi: 'The boot firmware is UEFI',
|
||||
destroyCloudConfigVdiAfterBoot: 'Destroy cloud config drive after first boot',
|
||||
|
||||
// ----- Self -----
|
||||
resourceSets: 'Resource sets',
|
||||
|
@ -305,6 +305,7 @@ export default class NewVm extends BaseComponent {
|
||||
cpuCap: '',
|
||||
cpusMax: '',
|
||||
cpuWeight: '',
|
||||
destroyCloudConfigVdiAfterBoot: false,
|
||||
existingDisks: {},
|
||||
fastClone: true,
|
||||
hvmBootFirmware: '',
|
||||
@ -465,6 +466,7 @@ export default class NewVm extends BaseComponent {
|
||||
bootAfterCreate: state.bootAfterCreate,
|
||||
copyHostBiosStrings:
|
||||
state.hvmBootFirmware !== 'uefi' && !this._templateHasBiosStrings() && state.copyHostBiosStrings,
|
||||
destroyCloudConfigVdiAfterBoot: state.destroyCloudConfigVdiAfterBoot,
|
||||
secureBoot: state.secureBoot,
|
||||
share: state.share,
|
||||
cloudConfig,
|
||||
@ -1498,7 +1500,9 @@ export default class NewVm extends BaseComponent {
|
||||
cpuCap,
|
||||
cpusMax,
|
||||
cpuWeight,
|
||||
destroyCloudConfigVdiAfterBoot,
|
||||
hvmBootFirmware,
|
||||
installMethod,
|
||||
memoryDynamicMin,
|
||||
memoryDynamicMax,
|
||||
memoryStaticMax,
|
||||
@ -1538,8 +1542,8 @@ export default class NewVm extends BaseComponent {
|
||||
</Button>
|
||||
</SectionContent>
|
||||
{showAdvanced && [
|
||||
<hr />,
|
||||
<SectionContent>
|
||||
<hr key='hr' />,
|
||||
<SectionContent key='advanced'>
|
||||
<Item>
|
||||
<input checked={bootAfterCreate} onChange={this._linkState('bootAfterCreate')} type='checkbox' />
|
||||
|
||||
@ -1554,6 +1558,21 @@ export default class NewVm extends BaseComponent {
|
||||
<Tags labels={tags} onChange={this._linkState('tags')} />
|
||||
</Item>
|
||||
</SectionContent>,
|
||||
<SectionContent key='destroyCloudConfigVdi'>
|
||||
<Item>
|
||||
<input
|
||||
checked={destroyCloudConfigVdiAfterBoot}
|
||||
disabled={installMethod === 'noConfigDrive' || !bootAfterCreate}
|
||||
id='destroyCloudConfigDisk'
|
||||
onChange={this._toggleState('destroyCloudConfigVdiAfterBoot')}
|
||||
type='checkbox'
|
||||
/>
|
||||
<label htmlFor='destroyCloudConfigDisk'>
|
||||
|
||||
{_('destroyCloudConfigVdiAfterBoot')}
|
||||
</label>
|
||||
</Item>
|
||||
</SectionContent>,
|
||||
this._getResourceSet() !== undefined && (
|
||||
<SectionContent>
|
||||
<Item>
|
||||
@ -1563,7 +1582,7 @@ export default class NewVm extends BaseComponent {
|
||||
</Item>
|
||||
</SectionContent>
|
||||
),
|
||||
<SectionContent>
|
||||
<SectionContent key='newVmCpu'>
|
||||
<Item label={_('newVmCpuWeightLabel')}>
|
||||
<DebounceInput
|
||||
className='form-control'
|
||||
@ -1598,7 +1617,7 @@ export default class NewVm extends BaseComponent {
|
||||
/>
|
||||
</Item>
|
||||
</SectionContent>,
|
||||
<SectionContent>
|
||||
<SectionContent key='newVmDynamic'>
|
||||
<Item label={_('newVmDynamicMinLabel')}>
|
||||
<SizeInput
|
||||
value={defined(memoryDynamicMin, null)}
|
||||
@ -1621,7 +1640,7 @@ export default class NewVm extends BaseComponent {
|
||||
/>
|
||||
</Item>
|
||||
</SectionContent>,
|
||||
<SectionContent>
|
||||
<SectionContent key='newVmMultipleVms'>
|
||||
<Item label={_('newVmMultipleVms')}>
|
||||
<Toggle value={multipleVms} onChange={this._linkState('multipleVms')} />
|
||||
</Item>
|
||||
|
Loading…
Reference in New Issue
Block a user