diff --git a/@xen-orchestra/disk-transform/src/demo.mts b/@xen-orchestra/disk-transform/src/demo.mts index 6968b180897..3d7fd005fc9 100644 --- a/@xen-orchestra/disk-transform/src/demo.mts +++ b/@xen-orchestra/disk-transform/src/demo.mts @@ -1,20 +1,20 @@ import { getSyncedHandler } from '@xen-orchestra/fs' -import { VhdRemote, RemoteMetadata } from './from/VhdRemote.mjs' -import { FileAccessor } from './file-accessor/FileAccessor.mjs' +import { VhdRemote } from './from/VhdRemote.mts' +import { FileAccessor } from './file-accessor/FileAccessor.mts' +import { writeVhdFileToRemote } from './to/VhdRemote.mts' async function run() { - const { value: handler } = await getSyncedHandler({ url: 'file:///mnt/ssd/vhdblock' }) + const { value: handlerSource } = await getSyncedHandler({ url: 'file:///mnt/ssd/vhdblock' }) + const { value: handlerTarget } = await getSyncedHandler({ url: 'file:///mnt/ssd/out' }) const metadataPath = './xo-vm-backups/cbb46b48-12aa-59dc-4039-8a587fdc67d5/20230831T100000Z.json' const vhd = new VhdRemote({ - handler: handler as FileAccessor, + handler: handlerSource as FileAccessor, metadataPath, diskUuid: '1282b678-cb12-4b13-ab17-7a4fdac403d8', }) - const { value: iterator } = await vhd.getBlockIterator() - for await (const block of iterator) { - console.log(block) - } + + await writeVhdFileToRemote(handlerTarget, 'out.vhd', vhd) } run() diff --git a/@xen-orchestra/disk-transform/src/file-accessor/InBrowserFileAccessor.mts b/@xen-orchestra/disk-transform/src/file-accessor/InBrowserFileAccessor.mts index 4e7008054ed..494159bd8ff 100644 --- a/@xen-orchestra/disk-transform/src/file-accessor/InBrowserFileAccessor.mts +++ b/@xen-orchestra/disk-transform/src/file-accessor/InBrowserFileAccessor.mts @@ -1,4 +1,4 @@ -import { FileAccessor, FileDescriptor } from './FileAccessor.mjs' +import { FileAccessor, type FileDescriptor } from './FileAccessor.mts' // handle file access inside a form // to transform disks into an acceptable format from the browser diff --git a/@xen-orchestra/disk-transform/src/file-accessor/RemoteFileAccessor.mts b/@xen-orchestra/disk-transform/src/file-accessor/RemoteFileAccessor.mts index 7ea6cfb6428..12a8bf55d45 100644 --- a/@xen-orchestra/disk-transform/src/file-accessor/RemoteFileAccessor.mts +++ b/@xen-orchestra/disk-transform/src/file-accessor/RemoteFileAccessor.mts @@ -1,4 +1,4 @@ -import { FileAccessor, FileDescriptor } from './FileAccessor.mjs' +import { FileAccessor, type FileDescriptor } from './FileAccessor.mts' export class RemoteFileAccessor extends FileAccessor { getSize: () => Promise diff --git a/@xen-orchestra/disk-transform/src/from/VhdRemote.mts b/@xen-orchestra/disk-transform/src/from/VhdRemote.mts index a52ae4f3720..7511798859d 100644 --- a/@xen-orchestra/disk-transform/src/from/VhdRemote.mts +++ b/@xen-orchestra/disk-transform/src/from/VhdRemote.mts @@ -1,15 +1,15 @@ import { openVhd } from 'vhd-lib' import { - DiskBlock, - DiskBlockData, + type DiskBlock, + type DiskBlockData, DiskBlockGenerator, - Disposable, + type Disposable, PortableDifferencingDisk, PortableDiskMetadata, - Uuid, -} from '../PortableDifferencingDisk.mjs' -import { FileAccessor } from '../file-accessor/FileAccessor.mjs' -import { dirname, join, relative } from 'path' + type Uuid, +} from '../PortableDifferencingDisk.mts' +import { type FileAccessor } from '../file-accessor/FileAccessor.mts' +import { dirname, join } from 'path' type VhdBlock = { id: number @@ -26,8 +26,9 @@ export type Vhd = { readHeaderAndFooter(): Promise readBlockAllocationTable(): Promise readBlock(index: number): Promise - blocks(): AsyncIterator - writeHeaderAndFooter(): Promise + blocks(): AsyncGenerator + writeHeader(): Promise + writeFooter(): Promise writeBlockAllocationTable(): Promise writeEntireBlock(block: VhdBlock): Promise } @@ -49,15 +50,12 @@ class VhdFileGenerator extends DiskBlockGenerator { } async *[Symbol.asyncIterator](): AsyncIterator { const blockIterator = this.#vhd.blocks() - let res: { value: VhdBlock; done?: boolean } - do { - res = await blockIterator.next(this) - this.consumedBlocks++ + for await (const { id, data } of blockIterator) { yield { - index: res.value.id, - data: res.value.data, + index: id, + data: data, } - } while (!res.done) + } } } diff --git a/@xen-orchestra/disk-transform/src/from/utils.mts b/@xen-orchestra/disk-transform/src/from/utils.mts index c7accc9ff55..f87a54a4baf 100644 --- a/@xen-orchestra/disk-transform/src/from/utils.mts +++ b/@xen-orchestra/disk-transform/src/from/utils.mts @@ -1,4 +1,4 @@ -import { PortableDiskMetadata } from '../PortableDifferencingDisk.mjs' +import { type PortableDiskMetadata } from '../PortableDifferencingDisk.mts' export async function getXapiMetadata(xapi: any, uuid: string): Promise { return Promise.resolve({ diff --git a/@xen-orchestra/disk-transform/src/to/VhdFileRemote.mts b/@xen-orchestra/disk-transform/src/to/VhdFileRemote.mts deleted file mode 100644 index 0677977a51b..00000000000 --- a/@xen-orchestra/disk-transform/src/to/VhdFileRemote.mts +++ /dev/null @@ -1,32 +0,0 @@ -import { VhdFile } from 'vhd-lib' -import { DiskBlockGenerator, PortableDifferencingDisk } from '../PortableDifferencingDisk.mjs' -import { Disposable } from 'promise-toolbox' -import { Vhd } from '../from/VhdRemote.mjs' - -async function writeVhdToRemote(targetVhd: Vhd, disk: PortableDifferencingDisk): Promise { - return Disposable.use(disk.getBlockIterator(), async (blockIterator: DiskBlockGenerator): Promise => { - // @todo : create header/footer from size/label/parent - const metada = await disk.getMetadata() - const bitmap = Buffer.alloc(255, 512) - for await (const block of blockIterator) { - await targetVhd.writeEntireBlock({ - id: block.index, - bitmap, - data: block.data, - buffer: Buffer.concat([bitmap, block.data]), - }) - } - await targetVhd.writeHeaderAndFooter() - await targetVhd.writeBlockAllocationTable() - }) -} - -export async function writeVhdFileToRemote(remote, path: string, disk: PortableDifferencingDisk) { - const handler = remote._handler - return Disposable.use(VhdFile.create(handler, path), async (vhd: Vhd) => { - // @todo : precompute target bat to ensure we can write the block without updating the bat at each block - return writeVhdToRemote(vhd, disk) - }) -} - -// @todo: vhddirectory diff --git a/@xen-orchestra/disk-transform/src/to/VhdRemote.mts b/@xen-orchestra/disk-transform/src/to/VhdRemote.mts new file mode 100644 index 00000000000..8581ff4c622 --- /dev/null +++ b/@xen-orchestra/disk-transform/src/to/VhdRemote.mts @@ -0,0 +1,52 @@ +import { VhdFile } from 'vhd-lib' +import { DiskBlockGenerator, PortableDifferencingDisk } from '../PortableDifferencingDisk.mts' +import { Disposable } from 'promise-toolbox' +import { type Vhd } from '../from/VhdRemote.mts' +import { type FileAccessor } from '../file-accessor/FileAccessor.mts' +import { createFooter, createHeader } from 'vhd-lib/_createFooterHeader.js' +import { unpackFooter, unpackHeader } from 'vhd-lib/Vhd/_utils.js' +import { DISK_TYPES, FOOTER_SIZE } from 'vhd-lib/_constants.js' +import _computeGeometryForSize from 'vhd-lib/_computeGeometryForSize.js' + +async function writeVhdToRemote(targetVhd: Vhd, disk: PortableDifferencingDisk): Promise { + return Disposable.use(disk.getBlockIterator(), async (blockIterator: DiskBlockGenerator): Promise => { + // @todo : handle differencing disk parent + + const metada = await disk.getMetadata() + console.log('metadata', { metada }) + targetVhd.footer = unpackFooter( + // length can be smaller than disk capacity due to alignment to head/cylinder/sector + createFooter( + metada.virtualSize, + Math.floor(Date.now() / 1000), + _computeGeometryForSize(metada.virtualSize), + FOOTER_SIZE, + DISK_TYPES.DYNAMIC + ) + ) + console.log('got footer ', targetVhd.footer, Math.ceil((metada.virtualSize / 2) * 1024 * 1024)) + targetVhd.header = unpackHeader(createHeader(Math.ceil(metada.virtualSize / (2 * 1024 * 1024)))) + const bitmap = Buffer.alloc(255, 512) + + for await (const block of blockIterator) { + await targetVhd.writeEntireBlock({ + id: block.index, + bitmap, + data: block.data, + buffer: Buffer.concat([bitmap, block.data]), + }) + } + await targetVhd.writeFooter() + await targetVhd.writeHeader() + await targetVhd.writeBlockAllocationTable() + }) +} + +export async function writeVhdFileToRemote(handler: FileAccessor, path: string, disk: PortableDifferencingDisk) { + return Disposable.use(VhdFile.create(handler, path), async (vhd: Vhd) => { + // @todo : precompute target bat to ensure we can write the block without updating the bat at each block + return writeVhdToRemote(vhd, disk) + }) +} + +// @todo: vhddirectory diff --git a/@xen-orchestra/disk-transform/tsconfig.json b/@xen-orchestra/disk-transform/tsconfig.json index dac85e3f5db..3cce3385188 100644 --- a/@xen-orchestra/disk-transform/tsconfig.json +++ b/@xen-orchestra/disk-transform/tsconfig.json @@ -9,7 +9,10 @@ "module": "NodeNext", "moduleResolution": "nodenext", "declaration": true, // Generates `.d.ts` files for type safety - "outDir": "dist" // Outputs compiled code to the `dist` directory + "outDir": "dist", // Outputs compiled code to the `dist` directory + "verbatimModuleSyntax": true, + "allowImportingTsExtensions": true, + "emitDeclarationOnly": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] diff --git a/packages/vhd-lib/Vhd/VhdNbd.js b/packages/vhd-lib/Vhd/VhdNbd.js index d62865b0f5a..46261875195 100644 --- a/packages/vhd-lib/Vhd/VhdNbd.js +++ b/packages/vhd-lib/Vhd/VhdNbd.js @@ -85,8 +85,8 @@ exports.VhdNbd = class VhdNbd extends VhdAbstract { this.#header = unpackHeader(createHeader(Math.ceil(size / DEFAULT_BLOCK_SIZE))) const geometry = _computeGeometryForSize(size) - // changed block can only be used to compute a differencing disk - const diskType = DISK_TYPES.DIFFERENCING + + const diskType = parentUuid !== undefined ? DISK_TYPES.DIFFERENCING : DISK_TYPES.DYNAMIC this.#footer = unpackFooter(createFooter(size, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, diskType)) this.#footer.uuid = packUuid(uuid) if (parentUuid) { diff --git a/packages/vhd-lib/Vhd/_utils.js b/packages/vhd-lib/Vhd/_utils.js index 062863792d6..c0dfe9c5f73 100644 --- a/packages/vhd-lib/Vhd/_utils.js +++ b/packages/vhd-lib/Vhd/_utils.js @@ -44,7 +44,7 @@ exports.BUF_BLOCK_UNUSED = BUF_BLOCK_UNUSED * @param {Object} footer * @returns {Object} the parsed header */ -exports.unpackHeader = (bufHeader, footer) => { +exports.unpackHeader = (bufHeader, footer = undefined) => { assertChecksum('header', bufHeader, fuHeader) const header = fuHeader.unpack(bufHeader)