diff --git a/@xen-orchestra/backups/_incrementalVm.mjs b/@xen-orchestra/backups/_incrementalVm.mjs index e2d2d3b084..5f11bfe52d 100644 --- a/@xen-orchestra/backups/_incrementalVm.mjs +++ b/@xen-orchestra/backups/_incrementalVm.mjs @@ -16,42 +16,15 @@ import { toQcow2Stream } from '@xen-orchestra/qcow2' const ensureArray = value => (value === undefined ? [] : Array.isArray(value) ? value : [value]) -const orderedMemoryLimits = ['memory_static_min', 'memory_dynamic_min', 'memory_dynamic_max', 'memory_static_max'] - // The dynamic memory range MUST respect this inequality at any moment: static_min <= dynamic_min <= dynamic_max <= static_max. -// We must update these properties in the right order to avoid XAPI error. -// The order depends on the values. It can be an increase, a decrease or a mix of both, so any order could be required. +// VM.set_memory_limits sets all four fields atomically, so there is no intermediate state for XAPI to reject export async function updateMemoryFields(xapi, targetVm, vmRecord) { - const memoryValues = {} - for (const key of orderedMemoryLimits) { - memoryValues[key] = { - currentValue: targetVm[key], - newValue: vmRecord[key] ?? targetVm[key], - } - } + const staticMin = vmRecord.memory_static_min ?? targetVm.memory_static_min + const staticMax = vmRecord.memory_static_max ?? targetVm.memory_static_max + const dynamicMin = vmRecord.memory_dynamic_min ?? targetVm.memory_dynamic_min + const dynamicMax = vmRecord.memory_dynamic_max ?? targetVm.memory_dynamic_max - while (await updateNextMemoryField(xapi, memoryValues, targetVm.$ref)) { - /* execute until all memory fields are updated */ - } -} - -// Update one more memory field if needed, then return a boolean describing if a field was updated or if all fields are up to date -async function updateNextMemoryField(xapi, memoryValues, vmRef) { - for (let i = 0; i < orderedMemoryLimits.length; i++) { - const currentField = memoryValues[orderedMemoryLimits[i]] - const nextField = i === orderedMemoryLimits.length - 1 ? undefined : memoryValues[orderedMemoryLimits[i + 1]] - if ( - currentField.newValue !== currentField.currentValue && - (nextField === undefined || currentField.newValue <= nextField.currentValue) - ) { - // no need to check that previousField.currentValue <= currentField.newValue, as we can deduce it - await xapi.setField('VM', vmRef, orderedMemoryLimits[i], currentField.newValue) - await xapi.barrier() - currentField.currentValue = currentField.newValue - return true - } - } - return false + await xapi.call('VM.set_memory_limits', targetVm.$ref, staticMin, staticMax, dynamicMin, dynamicMax) } export async function exportIncrementalVm( diff --git a/@xen-orchestra/backups/_incrementalVm.test.mjs b/@xen-orchestra/backups/_incrementalVm.test.mjs deleted file mode 100644 index 6a238ce0ad..0000000000 --- a/@xen-orchestra/backups/_incrementalVm.test.mjs +++ /dev/null @@ -1,122 +0,0 @@ -import { describe, it } from 'node:test' -import assert from 'node:assert/strict' - -import { updateMemoryFields } from './_incrementalVm.mjs' - -const MOCK_REF = 'mockRef' - -function mockXapiVm(initialMemoryValues) { - const res = { - ...initialMemoryValues, - updates: [], - barrier: () => {}, - setField: (type, ref, field, value) => { - assert(type === 'VM') - assert(ref === MOCK_REF) - res.updates.push({ field, value }) - res[field] = value - assert(res.memory_static_min <= res.memory_dynamic_min) - assert(res.memory_dynamic_min <= res.memory_dynamic_max) - assert(res.memory_dynamic_max <= res.memory_static_max) - }, - } - return res -} - -const tests = [ - { - label: 'handles a memory increase', - targetVm: { - memory_static_min: 2, - memory_dynamic_min: 4, - memory_dynamic_max: 4, - memory_static_max: 8, - }, - vmRecord: { - memory_static_min: 16, - memory_dynamic_min: 16, - memory_dynamic_max: 16, - memory_static_max: 16, - }, - expectedUpdates: [ - { field: 'memory_static_max', value: 16 }, - { field: 'memory_dynamic_max', value: 16 }, - { field: 'memory_dynamic_min', value: 16 }, - { field: 'memory_static_min', value: 16 }, - ], - }, - { - label: 'handles a memory decrease', - targetVm: { - memory_static_min: 4, - memory_dynamic_min: 8, - memory_dynamic_max: 16, - memory_static_max: 16, - }, - vmRecord: { - memory_static_min: 2, - memory_dynamic_min: 2, - memory_dynamic_max: 2, - memory_static_max: 2, - }, - expectedUpdates: [ - { field: 'memory_static_min', value: 2 }, - { field: 'memory_dynamic_min', value: 2 }, - { field: 'memory_dynamic_max', value: 2 }, - { field: 'memory_static_max', value: 2 }, - ], - }, - { - label: 'handles a more complex case', - targetVm: { - memory_static_min: 1, - memory_dynamic_min: 2, - memory_dynamic_max: 8, - memory_static_max: 16, - }, - vmRecord: { - memory_static_min: 4, - memory_dynamic_min: 4, - memory_dynamic_max: 4, - memory_static_max: 4, - }, - expectedUpdates: [ - { field: 'memory_dynamic_min', value: 4 }, - { field: 'memory_static_min', value: 4 }, - { field: 'memory_dynamic_max', value: 4 }, - { field: 'memory_static_max', value: 4 }, - ], - }, - { - label: 'avoids updating identical value', - targetVm: { - memory_static_min: 1, - memory_dynamic_min: 2, - memory_dynamic_max: 4, - memory_static_max: 8, - }, - vmRecord: { - memory_static_min: 1, - memory_dynamic_min: 2, - memory_dynamic_max: 4, - memory_static_max: 8, - }, - expectedUpdates: [], - }, -] - -describe('updateMemoryFields() should update in the right order', () => { - for (const test of tests) { - it(test.label, async () => { - const mock = mockXapiVm(test.targetVm) - test.targetVm.$ref = MOCK_REF - await updateMemoryFields(mock, test.targetVm, test.vmRecord) - - assert.equal(mock.updates.length, test.expectedUpdates.length) - for (let i = 0; i < mock.updates.length; i++) { - assert.equal(mock.updates[i].value, test.expectedUpdates[i].value) - assert.equal(mock.updates[i].field, test.expectedUpdates[i].field) - } - }) - } -}) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index d747a2b603..84a37abcb1 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -59,6 +59,7 @@ - @vates/types patch - @xen-orchestra/acl minor - @xen-orchestra/async-map patch +- @xen-orchestra/backups patch - @xen-orchestra/proxy-cli patch - @xen-orchestra/rest-api minor - @xen-orchestra/upload-ova patch