Files
Xen-Orchestra-xen-orchestra…/@xen-orchestra/vmware-explorer/parsers/cowd.test.mjs
Florent BEAUCHAMP 3bf50c38ee fix(vmware-explorer): fixes omnibus (#10363)
This branch is a reliability overhaul of the ESXi/vSphere source side of V2V, plus a scope reduction of the package. The SOAP plumbing is rewritten:

    VimClient wraps @vates/node-vsphere-soap in a promise API with one emitter per call (no more cross-talk between concurrent calls, no listener leak) and a pTimeout;
    vim25 faults are now parsed (_parseFault.mjs) so callers get a real fault type instead of task execution failed;
    the library no longer throws from async callbacks (which killed xo-server) and its beforeExit logout hook is per-client instead of removeAllListeners.
    Request specs are built in explicit schema order (soap/specs.mjs) with xsi:type, and property values are normalized (soap/normalize.mjs) so search() and the new #retrieveProperty share one shape.
    search() now paginates, cancels the retrieval and destroys the container view.
    Datastore HTTP downloads get session-cookie reuse, retry with jittered backoff, a headers-only timeout, and range-honouring validation.
    Task waiting polls with backoff and per-operation timeouts (6 h for snapshot consolidation).
    nbdkit servers are memoized by a key that includes the export options, the readiness wait races the process death instead of sleeping 2 s
    the password file is 0600 and unlinked
    the thumbprint is read over TLS in-process instead of two openssl spawns.
    The old VHD-over-datastore readers (VhdEsxi*, Datastore*, openDeltaVmdkAsVhd) are deleted, hence the majo, rleaving the vddk/NBD path with a COWD grain-directory parser as data-map fallback.
    ~1.7k lines of unit tests are added.
2026-09-11 17:36:38 +02:00

89 lines
3.1 KiB
JavaScript

import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import {
COWD_GRAIN_TABLE_ENTRIES,
COWD_HEADER_LENGTH,
COWD_SECTOR_SIZE,
grainDirectoryToDataMap,
parseCowdHeader,
} from './cowd.mjs'
const BLOCK_LENGTH = COWD_GRAIN_TABLE_ENTRIES * COWD_SECTOR_SIZE // 2 MiB
const cowdHeader = ({
magic = 'COWD',
version = 1,
capacity = 8 * BLOCK_LENGTH,
grainSize = 1,
numGdEntries = 8,
} = {}) => {
const buffer = Buffer.alloc(COWD_HEADER_LENGTH)
buffer.write(magic, 0, 'ascii')
buffer.writeUInt32LE(version, 4)
buffer.writeUInt32LE(3, 8) // flags
buffer.writeUInt32LE(capacity / COWD_SECTOR_SIZE, 12)
buffer.writeUInt32LE(grainSize, 16)
buffer.writeUInt32LE(4, 20) // the grain directory follows the header
buffer.writeUInt32LE(numGdEntries, 24)
return buffer
}
describe('parseCowdHeader', function () {
it('reads the geometry of the extent', function () {
assert.deepEqual(parseCowdHeader(cowdHeader()), {
blockLength: BLOCK_LENGTH,
capacity: 8 * BLOCK_LENGTH,
grainDirectoryOffset: COWD_HEADER_LENGTH,
grainSize: 1,
numGdEntries: 8,
})
})
it('rejects an extent which is not COWD with a code, not an assertion', function () {
// this is what a recent host writes, and this parser is the fallback of a failure: an
// assertion error there hides the real problem
assert.throws(() => parseCowdHeader(cowdHeader({ magic: 'SESp' })), {
code: 'NO_DATA_MAP',
message: 'not a COWD extent (magic: "SESp")',
})
})
it('rejects a layout it cannot read', function () {
assert.throws(() => parseCowdHeader(cowdHeader({ version: 2 })), { code: 'NO_DATA_MAP' })
assert.throws(() => parseCowdHeader(cowdHeader({ grainSize: 8 })), { code: 'NO_DATA_MAP' })
assert.throws(() => parseCowdHeader(Buffer.alloc(8)), { code: 'NO_DATA_MAP' })
})
it('bounds the number of grain directory entries', function () {
// without a bound, this asks the host for a 16 GB range and builds a map of a billion entries
assert.throws(() => parseCowdHeader(cowdHeader({ capacity: BLOCK_LENGTH, numGdEntries: 0xffffffff })), {
code: 'NO_DATA_MAP',
message: /at most 2 are possible$/,
})
})
})
describe('grainDirectoryToDataMap', function () {
const geometry = { blockLength: BLOCK_LENGTH, numGdEntries: 4 }
it('keeps the blocks whose grain table is allocated', function () {
const grainDirectory = Buffer.alloc(4 * 4)
grainDirectory.writeUInt32LE(4, 0) // sector of the grain table of the first block
grainDirectory.writeUInt32LE(12, 2 * 4)
assert.deepEqual(grainDirectoryToDataMap(grainDirectory, geometry), [
{ offset: 0, length: BLOCK_LENGTH, type: 0 },
{ offset: 2 * BLOCK_LENGTH, length: BLOCK_LENGTH, type: 0 },
])
})
it('returns an empty map for an extent without any data', function () {
assert.deepEqual(grainDirectoryToDataMap(Buffer.alloc(4 * 4), geometry), [])
})
it('refuses a truncated grain directory', function () {
assert.throws(() => grainDirectoryToDataMap(Buffer.alloc(4), geometry), { code: 'NO_DATA_MAP' })
})
})