feat(xo-web/new-vm): possibility to destroy cloud config drive after first boot (#6486)

Fixes #6438
This commit is contained in:
rajaa-b 2022-10-31 12:25:55 +01:00 committed by GitHub
parent b63c4a0d4f
commit 2eb3b15930
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 103 additions and 39 deletions

View File

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

View File

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

View File

@ -1287,12 +1287,17 @@ export default class Xapi extends XapiBase {
const host = this.pool.$master
const sr = this.getObject(srId)
// 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)

View File

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

View File

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

View File

@ -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' />
&nbsp;
@ -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'>
&nbsp;
{_('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>