-
Notifications
You must be signed in to change notification settings - Fork 270
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(xva-generator): split index.js
- Loading branch information
1 parent
ef81621
commit cddb316
Showing
7 changed files
with
265 additions
and
253 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export function isNotEmptyRef(val) { | ||
const EMPTY = 'OpaqueRef:NULL' | ||
const PREFIX = 'OpaqueRef:' | ||
return val !== EMPTY && typeof val === 'string' && val.startsWith(PREFIX) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { fromCallback } from 'promise-toolbox' | ||
import { readChunkStrict } from '@vates/read-chunk' | ||
import { XXHash64 } from 'xxhash' | ||
|
||
async function writeBlock(pack, data, name) { | ||
await fromCallback.call(pack, pack.entry, { name }, data) | ||
const hasher = new XXHash64(0) | ||
hasher.update(data) | ||
// weirdly, ocaml and xxhash return the bytes in reverse order to each other | ||
const hash = hasher.digest().reverse().toString('hex').toUpperCase() | ||
await fromCallback.call(pack, pack.entry, { name: `${name}.xxhash` }, Buffer.from(hash, 'utf8')) | ||
} | ||
export default async function addDisk(pack, vhd, basePath) { | ||
let counter = 0 | ||
let written | ||
const chunk_length = 1024 * 1024 | ||
const empty = Buffer.alloc(chunk_length, 0) | ||
const stream = await vhd.rawContent() | ||
let lastBlockLength | ||
const diskSize = vhd.footer.currentSize | ||
let remaining = diskSize | ||
while (remaining > 0) { | ||
const data = await readChunkStrict(stream, Math.min(chunk_length, remaining)) | ||
lastBlockLength = data.length | ||
remaining -= lastBlockLength | ||
|
||
if (counter === 0 || !data.equals(empty)) { | ||
written = true | ||
await writeBlock(pack, data, `${basePath}/${('' + counter).padStart(8, '0')}`) | ||
} else { | ||
written = false | ||
} | ||
counter++ | ||
} | ||
if (!written) { | ||
// last block must be present | ||
writeBlock(pack, empty.slice(0, lastBlockLength), `${basePath}/${counter}`) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import assert from 'node:assert' | ||
|
||
import { fromCallback } from 'promise-toolbox' | ||
import { v4 as uuid } from 'uuid' | ||
import defaultsDeep from 'lodash.defaultsdeep' | ||
|
||
import { DEFAULT_VBD } from './templates/vbd.mjs' | ||
import { DEFAULT_VDI } from './templates/vdi.mjs' | ||
import { DEFAULT_VIF } from './templates/vif.mjs' | ||
import { DEFAULT_VM } from './templates/vm.mjs' | ||
import toOvaxml from './_toOvaXml.mjs' | ||
|
||
export default async function writeOvaXml( | ||
pack, | ||
{ memory, networks, nCpus, firmware, vdis, vhds, ...vmSnapshot }, | ||
{ sr, network } | ||
) { | ||
let refId = 0 | ||
function nextRef() { | ||
return 'Ref:' + String(refId++).padStart(3, '0') | ||
} | ||
const data = { | ||
version: { | ||
hostname: 'localhost', | ||
date: '2022-01-01', | ||
product_version: '8.2.1', | ||
product_brand: 'XCP-ng', | ||
build_number: 'release/yangtze/master/58', | ||
xapi_major: 1, | ||
xapi_minor: 20, | ||
export_vsn: 2, | ||
}, | ||
objects: [], | ||
} | ||
const vm = defaultsDeep( | ||
{ | ||
id: nextRef(), | ||
// you can pass a full snapshot and nothing more to do | ||
snapshot: vmSnapshot, | ||
}, | ||
{ | ||
// some data need a little more work to be usable | ||
// if they are not already in vm | ||
snapshot: { | ||
HVM_boot_params: { | ||
firmware, | ||
}, | ||
memory_static_max: memory, | ||
memory_static_min: memory, | ||
memory_dynamic_max: memory, | ||
memory_dynamic_min: memory, | ||
other_config: { | ||
mac_seed: uuid(), | ||
}, | ||
uuid: uuid(), | ||
VCPUs_at_startup: nCpus, | ||
VCPUs_max: nCpus, | ||
}, | ||
}, | ||
DEFAULT_VM | ||
) | ||
|
||
data.objects.push(vm) | ||
const srObj = defaultsDeep( | ||
{ | ||
class: 'SR', | ||
id: nextRef(), | ||
snapshot: sr, | ||
}, | ||
{ | ||
snapshot: { | ||
VDIs: [], | ||
}, | ||
} | ||
) | ||
|
||
data.objects.push(srObj) | ||
assert.strictEqual(vhds.length, vdis.length) | ||
for (let index = 0; index < vhds.length; index++) { | ||
const userdevice = index + 1 | ||
const vhd = vhds[index] | ||
const vdi = defaultsDeep( | ||
{ | ||
id: nextRef(), | ||
// overwrite SR from an opaqref to a ref: | ||
snapshot: { ...vdis[index], SR: srObj.id }, | ||
}, | ||
{ | ||
snapshot: { | ||
uuid: uuid(), | ||
}, | ||
}, | ||
DEFAULT_VDI | ||
) | ||
|
||
data.objects.push(vdi) | ||
srObj.snapshot.VDIs.push(vdi.id) | ||
vhd.ref = vdi.id | ||
|
||
const vbd = defaultsDeep( | ||
{ | ||
id: nextRef(), | ||
snapshot: { | ||
device: `xvd${String.fromCharCode('a'.charCodeAt(0) + index)}`, | ||
uuid: uuid(), | ||
userdevice, | ||
VM: vm.id, | ||
VDI: vdi.id, | ||
}, | ||
}, | ||
DEFAULT_VBD | ||
) | ||
data.objects.push(vbd) | ||
vdi.snapshot.vbds.push(vbd.id) | ||
vm.snapshot.VBDs.push(vbd.id) | ||
} | ||
|
||
if (network && networks?.length) { | ||
const networkObj = defaultsDeep( | ||
{ | ||
class: 'network', | ||
id: nextRef(), | ||
snapshot: network, | ||
}, | ||
{ | ||
snapshot: { | ||
vifs: [], | ||
}, | ||
} | ||
) | ||
data.objects.push(networkObj) | ||
let vifIndex = 0 | ||
for (const sourceNetwork of networks) { | ||
const vif = defaultsDeep( | ||
{ | ||
id: nextRef(), | ||
snapshot: { | ||
device: ++vifIndex, | ||
MAC: sourceNetwork.macAddress, | ||
MAC_autogenerated: sourceNetwork.isGenerated, | ||
uuid: uuid(), | ||
VM: vm.id, | ||
network: networkObj.id, | ||
}, | ||
}, | ||
DEFAULT_VIF | ||
) | ||
data.objects.push(vif) | ||
networkObj.snapshot.vifs.push(vif.id) | ||
} | ||
} | ||
const xml = toOvaxml(data) | ||
await fromCallback.call(pack, pack.entry, { name: `ova.xml` }, xml) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { isNotEmptyRef } from './_isNotEmptyRef.mjs' | ||
import { importVm } from './importVm.mjs' | ||
|
||
export async function importVdi(vdi, vhd, xapi, sr) { | ||
// create a fake VM | ||
const vmRef = await importVm( | ||
{ | ||
name_label: `[xva-disp-import]${vdi.name_label}`, | ||
memory: 1024 * 1024 * 32, | ||
nCpus: 1, | ||
firmware: 'bios', | ||
vdis: [vdi], | ||
vhds: [vhd], | ||
}, | ||
xapi, | ||
sr | ||
) | ||
// ensure VM is laoded | ||
await xapi.getRecord('VM', vmRef) | ||
const vbdRefs = await xapi.getField('VM', vmRef, 'VBDs') | ||
// get the disk | ||
const disks = { __proto__: null } | ||
;(await xapi.getRecords('VBD', vbdRefs)).forEach(vbd => { | ||
if (vbd.type === 'Disk' && isNotEmptyRef(vbd.VDI)) { | ||
disks[vbd.VDI] = true | ||
} | ||
}) | ||
// destroy the VM and VBD | ||
await xapi.call('VM.destroy', vmRef) | ||
return await xapi.getRecord('VDI', Object.keys(disks)[0]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import tar from 'tar-stream' | ||
|
||
import writeOvaXml from './_writeOvaXml.mjs' | ||
import writeDisk from './_writeDisk.mjs' | ||
|
||
export async function importVm(vm, xapi, sr, network) { | ||
const pack = tar.pack() | ||
const taskRef = await xapi.task_create('VM import') | ||
const query = { | ||
sr_id: sr.$ref, | ||
} | ||
|
||
const promise = xapi | ||
.putResource(pack, '/import/', { | ||
query, | ||
task: taskRef, | ||
}) | ||
.catch(err => console.error(err)) | ||
|
||
await writeOvaXml(pack, vm, { sr, network }) | ||
for (const vhd of vm.vhds) { | ||
await writeDisk(pack, vhd, vhd.ref) | ||
} | ||
pack.finalize() | ||
const str = await promise | ||
const matches = /OpaqueRef:[0-9a-z-]+/.exec(str) | ||
if (!matches) { | ||
const error = new Error('no opaque ref found') | ||
error.haystack = str | ||
throw error | ||
} | ||
return matches[0] | ||
} |
Oops, something went wrong.