chore(backups): use set_memory_limits instead of convoluted code (#10286)

This commit is contained in:
Bastien Nollet
2026-08-21 14:36:22 +02:00
committed by GitHub
parent 249137793f
commit 46b0dc67bb
3 changed files with 7 additions and 155 deletions

View File

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

View File

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

View File

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