From 4b9db257fda4cc3ce71392d146abbc4e30e441a2 Mon Sep 17 00:00:00 2001 From: Nicolas Raynaud Date: Tue, 19 Apr 2022 11:01:53 +0200 Subject: [PATCH] feat: initial support for OVA VM export (#6006) Co-authored-by: Florent Beauchamp --- CHANGELOG.unreleased.md | 6 + docker/Dockerfile | 2 +- packages/xo-server/src/api/vm.mjs | 18 +- packages/xo-server/src/xapi/index.mjs | 79 +- packages/xo-vmdk-to-vhd/package.json | 6 +- packages/xo-vmdk-to-vhd/src/index.js | 41 +- .../src/ova-generate.integ.spec.js | 91 ++ packages/xo-vmdk-to-vhd/src/ova-generate.js | 194 ++++ .../src/{ova.js => ova-read.js} | 2 +- .../src/ova-schema/CIM_BootConfigSetting.xsd | 144 +++ .../src/ova-schema/CIM_BootSourceSetting.xsd | 71 ++ .../CIM_EthernetPortAllocationSettingData.xsd | 372 ++++++++ .../CIM_ResourceAllocationSettingData.xsd | 220 +++++ .../CIM_StorageAllocationSettingData.xsd | 367 ++++++++ .../CIM_VirtualSystemSettingData.xsd | 147 +++ .../xo-vmdk-to-vhd/src/ova-schema/common.xsd | 229 +++++ .../src/ova-schema/dsp8023_1.1.1.xsd | 884 ++++++++++++++++++ .../src/ova-schema/xenc-schema-11.xsd | 119 +++ .../src/ova-schema/xenc-schema.xsd | 146 +++ .../xo-vmdk-to-vhd/src/ova-schema/xml.xsd | 287 ++++++ .../src/ova-schema/xmldsig-core-schema.xsd | 318 +++++++ packages/xo-vmdk-to-vhd/src/ova.integ.spec.js | 2 +- packages/xo-vmdk-to-vhd/src/vmdk-read.js | 2 + .../src/common/select-export-vm-format.js | 32 + .../src/common/xo/export-vm-modal/index.js | 29 +- packages/xo-web/src/common/xo/index.js | 4 +- 26 files changed, 3781 insertions(+), 31 deletions(-) create mode 100644 packages/xo-vmdk-to-vhd/src/ova-generate.integ.spec.js create mode 100644 packages/xo-vmdk-to-vhd/src/ova-generate.js rename packages/xo-vmdk-to-vhd/src/{ova.js => ova-read.js} (99%) create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/CIM_BootConfigSetting.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/CIM_BootSourceSetting.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/CIM_EthernetPortAllocationSettingData.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/CIM_ResourceAllocationSettingData.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/CIM_StorageAllocationSettingData.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/CIM_VirtualSystemSettingData.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/common.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/dsp8023_1.1.1.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/xenc-schema-11.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/xenc-schema.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/xml.xsd create mode 100644 packages/xo-vmdk-to-vhd/src/ova-schema/xmldsig-core-schema.xsd create mode 100644 packages/xo-web/src/common/select-export-vm-format.js diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 154c5e87a..ae5beafcd 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -7,6 +7,8 @@ > Users must be able to say: “Nice enhancement, I'm eager to test it” +- [VM export] Feat export to `ova` format (PR [#6006](https://github.com/vatesfr/xen-orchestra/pull/6006)) + ### Bug fixes > Users must be able to say: “I had this issue, happy to know it's fixed” @@ -27,3 +29,7 @@ > - major: if the change breaks compatibility > > In case of conflict, the highest (lowest in previous list) `$version` wins. + +- xo-vmdk-to-vhd minor +- xo-server minor +- xo-web minor diff --git a/docker/Dockerfile b/docker/Dockerfile index 7bf436e98..eda4ce269 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu:xenial # https://qastack.fr/programming/25899912/how-to-install-nvm-in-docker RUN apt-get update -RUN apt-get install -y curl qemu-utils blktap-utils vmdk-stream-converter git +RUN apt-get install -y curl qemu-utils blktap-utils vmdk-stream-converter git libxml2-utils ENV NVM_DIR /usr/local/nvm RUN mkdir -p /usr/local/nvm RUN cd /usr/local/nvm diff --git a/packages/xo-server/src/api/vm.mjs b/packages/xo-server/src/api/vm.mjs index 46831be7d..b4f774f3d 100644 --- a/packages/xo-server/src/api/vm.mjs +++ b/packages/xo-server/src/api/vm.mjs @@ -6,8 +6,8 @@ import getStream from 'get-stream' import hrp from 'http-request-plus' import { createLogger } from '@xen-orchestra/log' import { defer } from 'golike-defer' -import { FAIL_ON_QUEUE } from 'limit-concurrency-decorator' import { format } from 'json-rpc-peer' +import { FAIL_ON_QUEUE } from 'limit-concurrency-decorator' import { ignoreErrors } from 'promise-toolbox' import { invalidParameters, noSuchObject, operationFailed, unauthorized } from 'xo-common/api-errors.js' import { Ref } from 'xen-api' @@ -1004,10 +1004,11 @@ revert.resolve = { // ------------------------------------------------------------------- -async function handleExport(req, res, { xapi, vmRef, compress }) { - const stream = await xapi.VM_export(FAIL_ON_QUEUE, vmRef, { - compress, - }) +async function handleExport(req, res, { xapi, vmRef, compress, format = 'xva' }) { + // @todo : should we put back the handleExportFAIL_ON_QUEUE ? + const stream = + format === 'ova' ? await xapi.exportVmOva(vmRef) : await xapi.VM_export(FAIL_ON_QUEUE, vmRef, { compress }) + res.on('close', () => stream.cancel()) // Remove the filename as it is already part of the URL. stream.headers['content-disposition'] = 'attachment' @@ -1017,7 +1018,7 @@ async function handleExport(req, res, { xapi, vmRef, compress }) { } // TODO: integrate in xapi.js -async function export_({ vm, compress }) { +async function export_({ vm, compress, format = 'xva' }) { if (vm.power_state === 'Running') { await checkPermissionOnSrs.call(this, vm) } @@ -1026,11 +1027,12 @@ async function export_({ vm, compress }) { xapi: this.getXapi(vm), vmRef: vm._xapiRef, compress, + format, } return { $getFrom: await this.registerHttpRequest(handleExport, data, { - suffix: '/' + encodeURIComponent(`${safeDateFormat(new Date())} - ${vm.name_label}.xva`), + suffix: '/' + encodeURIComponent(`${safeDateFormat(new Date())} - ${vm.name_label}.${format}`), }), } } @@ -1038,6 +1040,7 @@ async function export_({ vm, compress }) { export_.params = { vm: { type: 'string' }, compress: { type: ['boolean', 'string'], optional: true }, + format: { enum: ['xva', 'ova'], optional: true }, } export_.resolve = { @@ -1060,6 +1063,7 @@ async function handleVmImport(req, res, { data, srId, type, xapi }) { // Timeout seems to be broken in Node 4. // See https://github.com/nodejs/node/issues/3319 req.setTimeout(43200000) // 12 hours + // expect "multipart/form-data; boundary=something" const contentType = req.headers['content-type'] const vm = await (contentType !== undefined && contentType.startsWith('multipart/form-data') diff --git a/packages/xo-server/src/xapi/index.mjs b/packages/xo-server/src/xapi/index.mjs index bd6a3897f..3a0926b7b 100644 --- a/packages/xo-server/src/xapi/index.mjs +++ b/packages/xo-server/src/xapi/index.mjs @@ -20,7 +20,7 @@ import semver from 'semver' import tarStream from 'tar-stream' import uniq from 'lodash/uniq.js' import { asyncMap } from '@xen-orchestra/async-map' -import { vmdkToVhd, vhdToVMDK } from 'xo-vmdk-to-vhd' +import { vmdkToVhd, vhdToVMDK, writeOvaOn } from 'xo-vmdk-to-vhd' import { cancelable, CancelToken, fromEvents, ignoreErrors, pCatch, pRetry } from 'promise-toolbox' import { createLogger } from '@xen-orchestra/log' import { decorateWith } from '@vates/decorate-with' @@ -519,6 +519,76 @@ export default class Xapi extends XapiBase { return console } + @cancelable + async exportVmOva($cancelToken, vmRef) { + const vm = this.getObject(vmRef) + const useSnapshot = isVmRunning(vm) + let exportedVm + if (useSnapshot) { + const snapshotRef = await this.VM_snapshot(vmRef, { + name_label: vm.name_label, + cancelToken: $cancelToken, + }) + exportedVm = this.getObject(snapshotRef) + } else { + exportedVm = vm + } + + const collectedDisks = [] + for (const blockDevice of exportedVm.$VBDs) { + if (blockDevice.type === 'Disk') { + const vdi = blockDevice.$VDI + collectedDisks.push({ + getStream: () => { + return this.exportVdiContent($cancelToken, vdi, VDI_FORMAT_VHD) + }, + name: vdi.name_label, + fileName: vdi.name_label + '.vmdk', + description: vdi.name_description, + capacityMB: Math.ceil(vdi.virtual_size / 1024 / 1024), + }) + } + } + const nics = [] + for (const vif of exportedVm.$VIFs) { + nics.push({ + macAddress: vif.MAC_autogenerated ? '' : vif.MAC, + networkName: this.getObject(vif.network).name_label, + }) + } + + const writeStream = new PassThrough() + writeStream.task = this.task_create('VM OVA export', exportedVm.name_label) + writeOvaOn(writeStream, { + disks: collectedDisks, + vmName: exportedVm.name_label, + vmDescription: exportedVm.name_description, + cpuCount: exportedVm.VCPUs_at_startup, + vmMemoryMB: Math.ceil(exportedVm.memory_dynamic_max / 1024 / 1024), + firmware: exportedVm.HVM_boot_params.firmware, + nics, + }) + + writeStream.statusCode = 200 + writeStream.headers = { 'content-type': 'application/ova' } + writeStream.statusMessage = 'OK' + + let destroyed = false + const destroySnapshot = () => { + if (useSnapshot && !destroyed) { + destroyed = true + this.VM_destroy(exportedVm.$ref)::ignoreErrors() + } + } + writeStream.cancel = () => { + destroySnapshot() + return writeStream.destroy() + } + writeStream.once('end', destroySnapshot) + writeStream.once('error', destroySnapshot) + return writeStream + } + // Create a snapshot (if necessary) of the VM and returns a delta export // object. @cancelable @@ -1670,8 +1740,11 @@ export default class Xapi extends XapiBase { if (base !== undefined) { params.base = base } - const vhdResult = await this.VDI_exportContent(vdi.$ref, params) - const vmdkStream = await vhdToVMDK(filename, vhdResult) + let vhdResult + const vmdkStream = await vhdToVMDK(`${vdi.name_label}.vmdk`, async () => { + vhdResult = await this.VDI_exportContent(vdi.$ref, params) + return vhdResult + }) // callers expect the stream to be an HTTP response. vmdkStream.headers = { ...vhdResult.headers, diff --git a/packages/xo-vmdk-to-vhd/package.json b/packages/xo-vmdk-to-vhd/package.json index 367daa386..4123c858d 100644 --- a/packages/xo-vmdk-to-vhd/package.json +++ b/packages/xo-vmdk-to-vhd/package.json @@ -3,7 +3,7 @@ "name": "xo-vmdk-to-vhd", "version": "2.2.0", "license": "AGPL-3.0-or-later", - "description": "JS lib streaming a vmdk file to a vhd", + "description": "JS lib reading and writing .vmdk and .ova files", "keywords": [ "vhd", "vmdk" @@ -25,6 +25,7 @@ "lodash": "^4.17.15", "pako": "^2.0.4", "promise-toolbox": "^0.21.0", + "tar-stream": "^2.2.0", "vhd-lib": "^3.1.0", "xml2js": "^0.4.23" }, @@ -39,7 +40,8 @@ "fs-extra": "^10.0.0", "get-stream": "^6.0.0", "rimraf": "^3.0.0", - "tmp": "^0.2.1" + "tmp": "^0.2.1", + "validate-with-xmllint": "^1.2.0" }, "scripts": { "build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/", diff --git a/packages/xo-vmdk-to-vhd/src/index.js b/packages/xo-vmdk-to-vhd/src/index.js index 05e805872..8deff8ae4 100644 --- a/packages/xo-vmdk-to-vhd/src/index.js +++ b/packages/xo-vmdk-to-vhd/src/index.js @@ -1,9 +1,10 @@ import asyncIteratorToStream from 'async-iterator-to-stream' import createReadableSparseStream from 'vhd-lib/createReadableSparseStream.js' -import { parseOVAFile, ParsableFile } from './ova' +import { parseOVAFile, ParsableFile } from './ova-read' import VMDKDirectParser from './vmdk-read' import { generateVmdkData } from './vmdk-generate' -import { parseVhdToBlocks } from './parseVhdToBlocks.js' +import { writeOvaOn } from './ova-generate' +import { parseVhdToBlocks } from './parseVhdToBlocks' export { default as readVmdkGrainTable, readCapacityAndGrainTable } from './vmdk-read-table' @@ -26,15 +27,43 @@ async function vmdkToVhd(vmdkReadStream, grainLogicalAddressList, grainFileOffse ) } +export async function computeVmdkLength(diskName, vhdReadStream) { + let length = 0 + for await (const b of await vhdToVMDKIterator(diskName, vhdReadStream)) { + length += b.length + } + return length +} + /** - * + * @param diskName + * @param vhdReadStreamGetter an async function whose call brings a fresh VHD readStream. + * We need to read the VHD twice when generating OVA files to get the VMDK file length. + * @param withLength if true, the returned VMDK stream will have its `length` field set. The VHD stream will be entirely read to compute it. + * @returns a readable stream representing a VMDK file + */ +export async function vhdToVMDK(diskName, vhdReadStreamGetter, withLength = false) { + let length + if (withLength) { + length = await computeVmdkLength(diskName, await vhdReadStreamGetter()) + } + const iterable = await vhdToVMDKIterator(diskName, await vhdReadStreamGetter()) + const stream = await asyncIteratorToStream(iterable) + if (withLength) { + stream.length = length + } + return stream +} + +/** + * the returned stream will have its length set IIF compress === false * @param diskName * @param vhdReadStream * @returns a readable stream representing a VMDK file */ -async function vhdToVMDK(diskName, vhdReadStream) { +export async function vhdToVMDKIterator(diskName, vhdReadStream) { const { blockSize, blocks, diskSize, geometry } = await parseVhdToBlocks(vhdReadStream) - return asyncIteratorToStream(generateVmdkData(diskName, diskSize, blockSize, blocks, geometry)) + return generateVmdkData(diskName, diskSize, blockSize, blocks, geometry) } -export { ParsableFile, parseOVAFile, vmdkToVhd, vhdToVMDK } +export { ParsableFile, parseOVAFile, vmdkToVhd, writeOvaOn } diff --git a/packages/xo-vmdk-to-vhd/src/ova-generate.integ.spec.js b/packages/xo-vmdk-to-vhd/src/ova-generate.integ.spec.js new file mode 100644 index 000000000..10702db91 --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-generate.integ.spec.js @@ -0,0 +1,91 @@ +/* eslint-env jest */ +import { validateXMLWithXSD } from 'validate-with-xmllint' +import { createReadStream, createWriteStream, readFile } from 'fs-extra' +import execa from 'execa' +import path from 'path' +import { pFromCallback, fromEvent } from 'promise-toolbox' +import tmp from 'tmp' +import rimraf from 'rimraf' +import { writeOvaOn } from './ova-generate' +import { parseOVF } from './ova-read' + +const initialDir = process.cwd() +jest.setTimeout(100000) +beforeEach(async () => { + const dir = await pFromCallback(cb => tmp.dir(cb)) + process.chdir(dir) +}) + +afterEach(async () => { + const tmpDir = process.cwd() + process.chdir(initialDir) + await pFromCallback(cb => rimraf(tmpDir, cb)) +}) + +test('An ova file is generated correctly', async () => { + const inputRawFileName1 = 'random-data1.raw' + const inputRawFileName2 = 'random-data2.raw' + const vhdFileName1 = 'random-data1.vhd' + const vhdFileName2 = 'random-data2.vhd' + const ovaFileName1 = 'random-disk1.ova' + const dataSize = 100 * 1024 * 1024 // this number is an integer head/cylinder/count equation solution + try { + await execa('base64 /dev/urandom | head -c ' + dataSize + ' > ' + inputRawFileName1, [], { shell: true }) + await execa('base64 /dev/urandom | head -c ' + dataSize + ' > ' + inputRawFileName2, [], { shell: true }) + await execa('qemu-img', ['convert', '-fraw', '-Ovpc', inputRawFileName1, vhdFileName1]) + await execa('qemu-img', ['convert', '-fraw', '-Ovpc', inputRawFileName2, vhdFileName2]) + const destination = await createWriteStream(ovaFileName1) + const diskName1 = 'disk1' + const diskName2 = 'disk2' + const vmdkDiskName1 = `${diskName1}.vmdk` + const vmdkDiskName2 = `${diskName2}.vmdk` + const pipe = await writeOvaOn(destination, { + vmName: 'vm1', + vmDescription: 'desc', + vmMemoryMB: 100, + cpuCount: 3, + nics: [{ name: 'eth12', networkName: 'BigLan' }], + disks: [ + { + name: diskName1, + fileName: 'diskName1.vmdk', + capacityMB: Math.ceil((dataSize / 1024) * 1024), + getStream: async () => { + return createReadStream(vhdFileName1) + }, + }, + { + name: diskName2, + fileName: 'diskName1.vmdk', + capacityMB: Math.ceil((dataSize / 1024) * 1024), + getStream: async () => { + return createReadStream(vhdFileName2) + }, + }, + ], + }) + await fromEvent(pipe, 'finish') + await execa('tar', ['xf', ovaFileName1, 'vm1.ovf']) + const xml = await readFile('vm1.ovf', { encoding: 'utf-8' }) + + try { + await validateXMLWithXSD(xml, path.join(__dirname, 'ova-schema', 'dsp8023_1.1.1.xsd')) + await execa('tar', ['xf', ovaFileName1, vmdkDiskName1]) + await execa('tar', ['xf', ovaFileName1, vmdkDiskName2]) + await execa('qemu-img', ['check', vmdkDiskName1]) + await execa('qemu-img', ['check', vmdkDiskName2]) + await execa('qemu-img', ['compare', inputRawFileName1, vmdkDiskName1]) + await execa('qemu-img', ['compare', inputRawFileName2, vmdkDiskName2]) + await parseOVF({ read: () => xml }, s => s) + } catch (e) { + e.xml = xml + // console.log({ xml }) + throw e + } + } catch (error) { + console.error(error.stdout) + console.error(error.stderr) + console.error(error.message) + throw error + } +}) diff --git a/packages/xo-vmdk-to-vhd/src/ova-generate.js b/packages/xo-vmdk-to-vhd/src/ova-generate.js new file mode 100644 index 000000000..35a3bdc1a --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-generate.js @@ -0,0 +1,194 @@ +import tar from 'tar-stream' +import { computeVmdkLength, vhdToVMDKIterator } from '.' +import { fromCallback } from 'promise-toolbox' + +// WE MIGHT WANT TO HAVE A LOOK HERE: https://opennodecloud.com/howto/2013/12/25/howto-ON-ovf-reference.html + +/** + * + * @param writeStream + * @param vmName + * @param vmDescription + * @param disks [{name, fileName, capacityMB, getStream}] + * @param nics [{name, networkName}] + * @param vmMemoryMB + * @param cpuCount + * @returns readStream + */ +export async function writeOvaOn( + writeStream, + { vmName, vmDescription = '', disks = [], firmware = 'bios', nics = [], vmMemoryMB = 64, cpuCount = 1 } +) { + const ovf = createOvf(vmName, vmDescription, disks, nics, vmMemoryMB, cpuCount, firmware) + const pack = tar.pack() + const pipe = pack.pipe(writeStream) + await fromCallback.call(pack, pack.entry, { name: `${vmName}.ovf` }, Buffer.from(ovf, 'utf8')) + + async function writeDisk(entry, blockIterator) { + for await (const block of blockIterator) { + entry.write(block) + } + } + + // https://github.com/mafintosh/tar-stream/issues/24#issuecomment-558358268 + async function pushDisk(disk) { + const size = await computeVmdkLength(disk.name, await disk.getStream()) + disk.fileSize = size + const blockIterator = await vhdToVMDKIterator(disk.name, await disk.getStream()) + + return new Promise((resolve, reject) => { + const entry = pack.entry({ name: `${disk.name}.vmdk`, size: size }, err => { + if (err == null) { + return resolve() + } else return reject(err) + }) + return writeDisk(entry, blockIterator).then( + () => entry.end(), + e => reject(e) + ) + }) + } + + for (const disk of disks) { + await pushDisk(disk) + } + pack.finalize() + return pipe +} + +function createDiskSections(disks) { + const fileReferences = [] + const diskFragments = [] + const diskItems = [] + for (let i = 0; i < disks.length; i++) { + const disk = disks[i] + const diskId = `vmdisk${i + 1}` + fileReferences.push(` `) + diskFragments.push( + ` ` + ) + diskItems.push(` + + ${i} + Hard Disk ${i + 1} + ovf:/disk/${diskId} + ${diskId} + 4 + 17 + + `) + } + return { + fileReferences: fileReferences.join('\n'), + diskFragments: diskFragments.join('\n'), + diskItems: diskItems.join('\n'), + } +} + +function createNicsSection(nics) { + const networks = new Set() + const nicItems = [] + for (let i = 0; i < nics.length; i++) { + const nic = nics[i] + networks.add(nic.networkName) + const text = ` + + ${i} + true + ${nic.networkName} + PCNet32 ethernet adapter on "${nic.networkName}" + Connection to ${nic.networkName} + ${'nic' + i} + PCNet32 + ${nic.macAddress} + 10 + +` + nicItems.push(text) + } + const networksElements = [] + for (const network of networks) { + networksElements.push(` `) + } + return { nicsSection: nicItems.join('\n'), networksSection: networksElements.join('\n') } +} + +function createOvf(vmName, vmDescription, disks = [], nics = [], vmMemoryMB = 64, cpuCount = 1, firmware = 'bios') { + const diskSection = createDiskSections(disks) + const networkSection = createNicsSection(nics) + let id = 1 + const nextId = () => id++ + + return ` + + + +${diskSection.fileReferences} + + + Virtual disk information +${diskSection.diskFragments} + + + The list of logical networks +${networkSection.networksSection} + + + A virtual machine + ${vmName} + + The kind of installed guest operating system + + + Virtual hardware requirements + + Virtual Hardware Family + 0 + ${vmName} + vmx-11 + + + hertz * 10^6 + Number of Virtual CPUs + ${cpuCount} virtual CPU(s) + 1 + 3 + ${cpuCount} + + + byte * 2^20 + Memory Size + ${vmMemoryMB}MB of memory + 2 + 4 + ${vmMemoryMB} + + + 0 + IDE Controller + VirtualIDEController 0 + 4 + 5 + + + false + VirtualVideoCard + 5 + 24 + +${diskSection.diskItems} +${networkSection.nicsSection} + + + A human-readable annotation + ${vmDescription} + + +` +} diff --git a/packages/xo-vmdk-to-vhd/src/ova.js b/packages/xo-vmdk-to-vhd/src/ova-read.js similarity index 99% rename from packages/xo-vmdk-to-vhd/src/ova.js rename to packages/xo-vmdk-to-vhd/src/ova-read.js index f62f79be8..335fac88b 100644 --- a/packages/xo-vmdk-to-vhd/src/ova.js +++ b/packages/xo-vmdk-to-vhd/src/ova-read.js @@ -141,7 +141,7 @@ const filterDisks = disks => { } } -async function parseOVF(fileFragment, stringDeserializer) { +export async function parseOVF(fileFragment, stringDeserializer) { const xmlString = stringDeserializer(await fileFragment.read(), 'utf-8') return new Promise((resolve, reject) => xml2js.parseString( diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_BootConfigSetting.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_BootConfigSetting.xsd new file mode 100644 index 000000000..171696f4f --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_BootConfigSetting.xsd @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_BootSourceSetting.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_BootSourceSetting.xsd new file mode 100644 index 000000000..ff0da8c4f --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_BootSourceSetting.xsd @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_EthernetPortAllocationSettingData.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_EthernetPortAllocationSettingData.xsd new file mode 100644 index 000000000..ead7871a1 --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_EthernetPortAllocationSettingData.xsd @@ -0,0 +1,372 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_ResourceAllocationSettingData.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_ResourceAllocationSettingData.xsd new file mode 100644 index 000000000..16c42cd70 --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_ResourceAllocationSettingData.xsd @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_StorageAllocationSettingData.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_StorageAllocationSettingData.xsd new file mode 100644 index 000000000..8a75aeefc --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_StorageAllocationSettingData.xsd @@ -0,0 +1,367 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_VirtualSystemSettingData.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_VirtualSystemSettingData.xsd new file mode 100644 index 000000000..7bce47505 --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/CIM_VirtualSystemSettingData.xsd @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/common.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/common.xsd new file mode 100644 index 000000000..4008c0e57 --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/common.xsd @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/dsp8023_1.1.1.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/dsp8023_1.1.1.xsd new file mode 100644 index 000000000..10be397ef --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/dsp8023_1.1.1.xsd @@ -0,0 +1,884 @@ + + + + + + + + + + Determines whether import should fail if the Section is + not understood + + + + + Space separated list of supported transport types + + + + + Root element of OVF Descriptor + + + + + Root OVF descriptor type + + + + + References to all external files + + + + + Package level meta-data + + + + + Content: A VirtualSystem or a + VirtualSystemCollection + + + + + Localized string resource bundles + + + + + + + + + + + Type for list of external resources + + + + + + + + + + Type for an external reference to a + resource + + + + + + + Reference key used in other parts of the + package + + + + + Location of external resource + + + + + Size in bytes of the files (if + known) + + + + + Compression type (gzip, bzip2, or none if empty or not + specified) + + + + + Chunk size (except for last chunk) + + + + + + + Base element for content types. This is the head the + subsitution group + + + + + Base class for content + + + + + Info element describes the meaning of the content, + this is typically shown if the type is not understood by an + application + + + + + An optional localizable display name of the content + + + + + + Content body is a list of Sections + + + + + + + + + + + Element substitutable for Content since VirtualSystem_Type + is a derivation of Content_Type + + + + + Content describing a virtual system + + + + + + + + Element substitutable for Content since VirtualSystemCollection_Type + is a derivation of Content_Type + + + + + A collection of Content. + + + + + + + + + + + + + Root element of I18N string bundle + + + + + Type for string resource bundle + + + + + Resource bundle element + + + + + + String element value + + + + String element identifier + + + + + + + + + + + + Locale for this string resource + bundle + + + + + Reference to external resource + bundle + + + + + + + + Base elements for OVF sections. This is the head of the + substitution group. + + + + + + Base type for Sections, subclassing this is the most + common form of extensibility. Subtypes define more specific + elements. + + + + Info element describes the meaning of the Section, + this is typically shown if the Section is not understood by an + application + + + + + + + + + Type for localizable string + + + + + Default string value + + + + Identifier for lookup in string resource bundle + for alternate locale + + + + + + + + + + Element substitutable for Section since + AnnotationSection_Type is a derivation of Section_Type + + + + + + User defined annotation + + + + + + + + + + + + + Element substitutable for Section since ProductSection_Type + is a derivation of Section_Type + + + + + Product information for a virtual + appliance + + + + + + + Name of product + + + + + Name of product vendor + + + + + Product version, short form + + + + + Product version, long form + + + + + URL resolving to product description + + + + + URL resolving to vendor description + + + + + Experimental: URL resolving to deployed product instance + + + + + Experimental: Display icon for product + + + + + + + + + + + + Properties for application-level + customization + + + + Property grouping + delimiter + + + + + Property element + + + + + + Short description of + property + + + + + Description of + property + + + + + Alternative default + property values for different + configuration + + + + + + Property + identifier + + + + + Property + type + + + + + A comma-separated set of type + qualifiers + + + + + Determines whether the property + value is configurable during + installation + + + + + Default value for + property + + + + + Determines whether the property + value should be obscured during deployment + + + + + + + + + + + + Property identifier prefix + + + + + Property identifier suffix + + + + + + + + Type for alternative default values for properties when + DeploymentOptionSection is used + + + + + + + Alternative default property value + + + + + Configuration from DeploymentOptionSection in which + this value is default + + + + + + + Element substitutable for Section since NetworkSection_Type + is a derivation of Section_Type + + + + + Descriptions of logical networks used within the + package + + + + + + + + + + + + + + + + + + + + + Element substitutable for Section since DiskSection_Type is + a derivation of Section_Type + + + + + Descriptions of virtual disks used within the + package + + + + + + + + + + + + + Type for virtual disk descriptor + + + + + + + Identifier for virtual disk + + + + + Reference to virtual disk content. If not specified a + blank virtual disk is created of size given by capacity + attribute + + + + + Virtual disk capacity, can be specified as either an + xs:long size or as a reference to a property using ${property_name}. + + + + + + Unit of allocation for ovf:capacity. If not specified + default value is bytes. Value shall match a recognized value for the + UNITS qualifier in DSP0004. + + + + + Format of virtual disk given as a URI that identifies + the disk type + + + + + Estimated populated size of disk in + bytes + + + + + Reference to potential parent disk + + + + + + + Element substitutable for Section since + OperatingSystemSection_Type is a derivation of Section_Type + + + + + + Specification of the operating system installed in the + guest + + + + + + + + + + Identifier defined by the + CIM_OperatingSystem.OsType enumeration + + + + + Version defined by the + CIM_OperatingSystem.Version field + + + + + + + + Element substitutable for Section since EulaSection_Type is + a derivation of Section_Type + + + + + End-User License Agreement + + + + + + + + + + + + + Element substitutable for Section since + VirtualHardwareSection_Type is a derivation of Section_Type + + + + + + Specifies virtual hardware requirements for a virtual + machine + + + + + + + + + + + Unique identifier of this VirtualHardwareSection + (within a VirtualSystem) + + + + + + + + + Element substitutable for Section since + ResourceAllocationSection_Type is a derivation of Section_Type + + + + + + Resource constraints on a + VirtualSystemCollection + + + + + + + + + + + + + Element substitutable for Section since InstallSection_Type + is a derivation of Section_Type + + + + + If present indicates that the virtual machine needs to be + initially booted to install and configure the software + + + + + + + + + Delay in seconds to wait for power off to + complete after initial boot + + + + + + + + Element substitutable for Section since StartupSection_Type + is a derivation of Section_Type + + + + + Specifies the order in which entities in a + VirtualSystemCollection are powered on and shut down + + + + + + + + + Unique identifier of + the content (within a VirtualSystemCollection) + + + + + + Startup order. Allows the package author the capability of specifying the startup order of the virtual systems. + + + + + Delay in seconds to wait for power + on to complete + + + + + Resumes power-on sequence if guest + software reports ok + + + + + Delay in seconds to wait for power + off to complete + + + + + Start action to use, valid values + are: 'powerOn', 'none' + + + + + Stop action to use, valid values + are: ''powerOff' , 'guestShutdown', + 'none' + + + + + + + + + + + + + Element substitutable for Section since + DeploymentOptionSection_Type is a derivation of Section_Type + + + + + + Enumeration of discrete deployment + options + + + + + + + + + + + + + + + + + + + + + + + Wrapper for + CIM_VirtualSystemSettingData_Type + + + + + + + + + + + Wrapper for + CIM_ResourceAllocationSettingData_Type + + + + + Determines whether import should fail if entry + is not understood + + + + + Configuration from DeploymentOptionSection this + entry is valid for + + + + + States that this entry is a range + marker + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/xenc-schema-11.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/xenc-schema-11.xsd new file mode 100644 index 000000000..4bdb95d12 --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/xenc-schema-11.xsd @@ -0,0 +1,119 @@ + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/xenc-schema.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/xenc-schema.xsd new file mode 100644 index 000000000..85af68b55 --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/xenc-schema.xsd @@ -0,0 +1,146 @@ + + + + + + ]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/xml.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/xml.xsd new file mode 100644 index 000000000..aea7d0db0 --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/xml.xsd @@ -0,0 +1,287 @@ + + + + + + +
+

About the XML namespace

+ +
+

+ This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

+

+ See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

+

+ Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

+

+ See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

+
+
+
+
+ + + + +
+ +

lang (as an attribute name)

+

+ denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

+ +
+
+

Notes

+

+ Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

+

+ See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

+

+ The union allows for the 'un-declaration' of xml:lang with + the empty string. +

+
+
+
+ + + + + + + + + +
+ + + + +
+ +

space (as an attribute name)

+

+ denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

+ +
+
+
+ + + + + + +
+ + + +
+ +

base (as an attribute name)

+

+ denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

+ +

+ See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

+
+
+
+
+ + + + +
+ +

id (as an attribute name)

+

+ denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

+ +

+ See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

+
+
+
+
+ + + + + + + + + + +
+ +

Father (in any context at all)

+ +
+

+ denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

+
+

+ In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

+
+
+
+
+
+ + + +
+

About this schema document

+ +
+

+ This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

+

+ To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

+
+          <schema . . .>
+           . . .
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+     
+

+ or +

+
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+     
+

+ Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

+
+          <type . . .>
+           . . .
+           <attributeGroup ref="xml:specialAttrs"/>
+     
+

+ will define a type which will schema-validate an instance element + with any of those attributes. +

+
+
+
+
+ + + +
+

Versioning policy for this schema document

+
+

+ In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

+

+ At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

+

+ The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

+

+ Previous dated (and unchanging) versions of this schema + document are at: +

+ +
+
+
+
+ +
+ diff --git a/packages/xo-vmdk-to-vhd/src/ova-schema/xmldsig-core-schema.xsd b/packages/xo-vmdk-to-vhd/src/ova-schema/xmldsig-core-schema.xsd new file mode 100644 index 000000000..df126b30e --- /dev/null +++ b/packages/xo-vmdk-to-vhd/src/ova-schema/xmldsig-core-schema.xsd @@ -0,0 +1,318 @@ + + + + + + ]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xo-vmdk-to-vhd/src/ova.integ.spec.js b/packages/xo-vmdk-to-vhd/src/ova.integ.spec.js index 010a52c8e..ae59b08f6 100644 --- a/packages/xo-vmdk-to-vhd/src/ova.integ.spec.js +++ b/packages/xo-vmdk-to-vhd/src/ova.integ.spec.js @@ -8,7 +8,7 @@ import { pFromCallback } from 'promise-toolbox' import rimraf from 'rimraf' import tmp from 'tmp' -import { ParsableFile, parseOVAFile } from './ova' +import { ParsableFile, parseOVAFile } from './ova-read' import readVmdkGrainTable from './vmdk-read-table' const initialDir = process.cwd() diff --git a/packages/xo-vmdk-to-vhd/src/vmdk-read.js b/packages/xo-vmdk-to-vhd/src/vmdk-read.js index 1fcf4688f..7d05a1ee9 100644 --- a/packages/xo-vmdk-to-vhd/src/vmdk-read.js +++ b/packages/xo-vmdk-to-vhd/src/vmdk-read.js @@ -185,6 +185,7 @@ export default class VMDKDirectParser { const grainPosition = this.grainFileOffsetList[tableIndex] * SECTOR_SIZE const grainSizeBytes = this.header.grainSizeSectors * SECTOR_SIZE const lba = this.grainLogicalAddressList[tableIndex] * grainSizeBytes + assert.strictEqual(grainPosition >= position, true) await this.virtualBuffer.readChunk(grainPosition - position, `blank from ${position} to ${grainPosition}`) let grain if (this.header.flags.hasMarkers) { @@ -194,5 +195,6 @@ export default class VMDKDirectParser { } yield { logicalAddressBytes: lba, data: grain } } + console.log('yielded last VMDK block') } } diff --git a/packages/xo-web/src/common/select-export-vm-format.js b/packages/xo-web/src/common/select-export-vm-format.js new file mode 100644 index 000000000..c59fbf33a --- /dev/null +++ b/packages/xo-web/src/common/select-export-vm-format.js @@ -0,0 +1,32 @@ +import _ from 'intl' +import React from 'react' +import { injectState, provideState } from 'reaclette' + +import decorate from './apply-decorators' +import { Select } from './form' + +const OPTIONS = [ + { + label: 'xva', + value: 'xva', + }, + { + label: 'ova', + value: 'ova', + }, +] + +const SelectExportFormat = decorate([ + provideState({ + computed: { + options: () => OPTIONS, + selectProps: (_, props) => props, + }, + }), + injectState, + ({ onChange, state, value }) => ( +