parent
fcaf6b7923
commit
63c676ebfe
@ -95,6 +95,7 @@
|
|||||||
"serve-static": "^1.9.2",
|
"serve-static": "^1.9.2",
|
||||||
"stack-chain": "^1.3.3",
|
"stack-chain": "^1.3.3",
|
||||||
"struct-fu": "^1.0.0",
|
"struct-fu": "^1.0.0",
|
||||||
|
"tar-stream": "^1.5.2",
|
||||||
"through2": "^2.0.0",
|
"through2": "^2.0.0",
|
||||||
"trace": "^2.0.1",
|
"trace": "^2.0.1",
|
||||||
"ws": "^1.1.1",
|
"ws": "^1.1.1",
|
||||||
@ -102,7 +103,8 @@
|
|||||||
"xml2js": "~0.4.6",
|
"xml2js": "~0.4.6",
|
||||||
"xo-acl-resolver": "^0.2.1",
|
"xo-acl-resolver": "^0.2.1",
|
||||||
"xo-collection": "^0.4.0",
|
"xo-collection": "^0.4.0",
|
||||||
"xo-remote-parser": "^0.3"
|
"xo-remote-parser": "^0.3",
|
||||||
|
"xo-vmdk-to-vhd": "0.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^6.0.4",
|
"babel-eslint": "^6.0.4",
|
||||||
|
@ -943,13 +943,13 @@ exports.export = export_;
|
|||||||
|
|
||||||
#---------------------------------------------------------------------
|
#---------------------------------------------------------------------
|
||||||
|
|
||||||
handleVmImport = $coroutine (req, res, { xapi, srId }) ->
|
handleVmImport = $coroutine (req, res, { data, srId, type, xapi }) ->
|
||||||
# Timeout seems to be broken in Node 4.
|
# Timeout seems to be broken in Node 4.
|
||||||
# See https://github.com/nodejs/node/issues/3319
|
# See https://github.com/nodejs/node/issues/3319
|
||||||
req.setTimeout(43200000) # 12 hours
|
req.setTimeout(43200000) # 12 hours
|
||||||
|
|
||||||
try
|
try
|
||||||
vm = yield xapi.importVm(req, { srId })
|
vm = yield xapi.importVm(req, { data, srId, type })
|
||||||
res.end(format.response(0, vm.$id))
|
res.end(format.response(0, vm.$id))
|
||||||
catch e
|
catch e
|
||||||
res.writeHead(500)
|
res.writeHead(500)
|
||||||
@ -958,7 +958,7 @@ handleVmImport = $coroutine (req, res, { xapi, srId }) ->
|
|||||||
return
|
return
|
||||||
|
|
||||||
# TODO: "sr_id" can be passed in URL to target a specific SR
|
# TODO: "sr_id" can be passed in URL to target a specific SR
|
||||||
import_ = $coroutine ({host, sr}) ->
|
import_ = $coroutine ({ data, host, sr, type }) ->
|
||||||
if not sr
|
if not sr
|
||||||
if not host
|
if not host
|
||||||
throw new InvalidParameters('you must provide either host or SR')
|
throw new InvalidParameters('you must provide either host or SR')
|
||||||
@ -974,13 +974,45 @@ import_ = $coroutine ({host, sr}) ->
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
$sendTo: yield @registerHttpRequest(handleVmImport, {
|
$sendTo: yield @registerHttpRequest(handleVmImport, {
|
||||||
|
data,
|
||||||
srId: sr._xapiId,
|
srId: sr._xapiId,
|
||||||
|
type,
|
||||||
xapi
|
xapi
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
import_.params = {
|
import_.params = {
|
||||||
|
data: {
|
||||||
|
type: 'object',
|
||||||
|
optional: true,
|
||||||
|
properties: {
|
||||||
|
descriptionLabel: { type: 'string' },
|
||||||
|
disks: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
capacity: { type: 'integer' },
|
||||||
|
descriptionLabel: { type: 'string' },
|
||||||
|
nameLabel: { type: 'string' },
|
||||||
|
path: { type: 'string' },
|
||||||
|
position: { type: 'integer' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
memory: { type: 'integer' },
|
||||||
|
nameLabel: { type: 'string' },
|
||||||
|
nCpus: { type: 'integer' },
|
||||||
|
networks: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
host: { type: 'string', optional: true },
|
host: { type: 'string', optional: true },
|
||||||
|
type: { type: 'string', optional: true },
|
||||||
sr: { type: 'string', optional: true }
|
sr: { type: 'string', optional: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,9 @@ import fatfs from 'fatfs'
|
|||||||
import find from 'lodash/find'
|
import find from 'lodash/find'
|
||||||
import includes from 'lodash/includes'
|
import includes from 'lodash/includes'
|
||||||
import sortBy from 'lodash/sortBy'
|
import sortBy from 'lodash/sortBy'
|
||||||
|
import tarStream from 'tar-stream'
|
||||||
import unzip from 'julien-f-unzip'
|
import unzip from 'julien-f-unzip'
|
||||||
|
import vmdkToVhd from 'xo-vmdk-to-vhd'
|
||||||
import { defer } from 'promise-toolbox'
|
import { defer } from 'promise-toolbox'
|
||||||
import {
|
import {
|
||||||
wrapError as wrapXapiError,
|
wrapError as wrapXapiError,
|
||||||
@ -48,6 +50,7 @@ import {
|
|||||||
} from '../api-errors'
|
} from '../api-errors'
|
||||||
|
|
||||||
import mixins from './mixins'
|
import mixins from './mixins'
|
||||||
|
import OTHER_CONFIG_TEMPLATE from './other-config-template'
|
||||||
import {
|
import {
|
||||||
asBoolean,
|
asBoolean,
|
||||||
asInteger,
|
asInteger,
|
||||||
@ -1428,18 +1431,114 @@ export default class Xapi extends XapiBase {
|
|||||||
return vmRef
|
return vmRef
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@deferrable.onFailure
|
||||||
|
async _importOvaVm ($onFailure, stream, {
|
||||||
|
descriptionLabel,
|
||||||
|
disks,
|
||||||
|
memory,
|
||||||
|
nameLabel,
|
||||||
|
networks,
|
||||||
|
nCpus
|
||||||
|
}, sr) {
|
||||||
|
// 1. Create VM.
|
||||||
|
const vm = await this._getOrWaitObject(
|
||||||
|
await this._createVmRecord({
|
||||||
|
...OTHER_CONFIG_TEMPLATE,
|
||||||
|
memory_dynamic_max: memory,
|
||||||
|
memory_dynamic_min: memory,
|
||||||
|
memory_static_max: memory,
|
||||||
|
name_description: descriptionLabel,
|
||||||
|
name_label: nameLabel,
|
||||||
|
VCPUs_at_startup: nCpus,
|
||||||
|
VCPUs_max: nCpus
|
||||||
|
})
|
||||||
|
)
|
||||||
|
$onFailure(() => this._deleteVm(vm))
|
||||||
|
// Disable start and change the VM name label during import.
|
||||||
|
await Promise.all([
|
||||||
|
this.addForbiddenOperationToVm(vm.$id, 'start', 'OVA import in progress...'),
|
||||||
|
this._setObjectProperties(vm, { name_label: `[Importing...] ${nameLabel}` })
|
||||||
|
])
|
||||||
|
|
||||||
|
// 2. Create VDIs & Vifs.
|
||||||
|
const vdis = {}
|
||||||
|
const vifDevices = await this.call('VM.get_allowed_VIF_devices', vm.$ref)
|
||||||
|
await Promise.all(
|
||||||
|
map(disks, async disk => {
|
||||||
|
const vdi = vdis[disk.path] = await this.createVdi(disk.capacity, {
|
||||||
|
name_description: disk.descriptionLabel,
|
||||||
|
name_label: disk.nameLabel,
|
||||||
|
sr: sr.$ref
|
||||||
|
})
|
||||||
|
$onFailure(() => this._deleteVdi(vdi)::pCatch(noop))
|
||||||
|
|
||||||
|
return this._createVbd(vm, vdi, { position: disk.position })
|
||||||
|
}).concat(map(networks, (networkId, i) => (
|
||||||
|
this._createVif(vm, this.getObject(networkId), {
|
||||||
|
device: vifDevices[i]
|
||||||
|
})
|
||||||
|
)))
|
||||||
|
)
|
||||||
|
|
||||||
|
// 3. Import VDIs contents.
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const extract = tarStream.extract()
|
||||||
|
|
||||||
|
stream.on('error', reject)
|
||||||
|
|
||||||
|
extract.on('finish', resolve)
|
||||||
|
extract.on('error', reject)
|
||||||
|
extract.on('entry', async (entry, stream, cb) => {
|
||||||
|
// Not a disk to import.
|
||||||
|
const vdi = vdis[entry.name]
|
||||||
|
if (!vdi) {
|
||||||
|
stream.on('end', cb)
|
||||||
|
stream.resume()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const vhdStream = await vmdkToVhd(stream)
|
||||||
|
await this._importVdiContent(vdi, vhdStream, VDI_FORMAT_RAW)
|
||||||
|
|
||||||
|
// See: https://github.com/mafintosh/tar-stream#extracting
|
||||||
|
// No import parallelization.
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
stream.pipe(extract)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Enable start and restore the VM name label after import.
|
||||||
|
await Promise.all([
|
||||||
|
this.removeForbiddenOperationFromVm(vm.$id, 'start'),
|
||||||
|
this._setObjectProperties(vm, { name_label: nameLabel })
|
||||||
|
])
|
||||||
|
return vm
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: an XVA can contain multiple VMs
|
// TODO: an XVA can contain multiple VMs
|
||||||
async importVm (stream, {
|
async importVm (stream, {
|
||||||
|
data,
|
||||||
onlyMetadata = false,
|
onlyMetadata = false,
|
||||||
srId
|
srId,
|
||||||
|
type = 'xva'
|
||||||
} = {}) {
|
} = {}) {
|
||||||
|
const sr = srId && this.getObject(srId)
|
||||||
|
|
||||||
|
if (type === 'xva') {
|
||||||
return /* await */ this._getOrWaitObject(await this._importVm(
|
return /* await */ this._getOrWaitObject(await this._importVm(
|
||||||
stream,
|
stream,
|
||||||
srId && this.getObject(srId),
|
sr,
|
||||||
onlyMetadata
|
onlyMetadata
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'ova') {
|
||||||
|
return this._getOrWaitObject(await this._importOvaVm(stream, data, sr))
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`unsupported type: '${type}'`)
|
||||||
|
}
|
||||||
|
|
||||||
async migrateVm (vmId, hostXapi, hostId, {
|
async migrateVm (vmId, hostXapi, hostId, {
|
||||||
migrationNetworkId,
|
migrationNetworkId,
|
||||||
mapVifsNetworks,
|
mapVifsNetworks,
|
||||||
|
51
src/xapi/other-config-template.js
Normal file
51
src/xapi/other-config-template.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
const OTHER_CONFIG_TEMPLATE = {
|
||||||
|
actions_after_crash: 'restart',
|
||||||
|
actions_after_reboot: 'restart',
|
||||||
|
actions_after_shutdown: 'destroy',
|
||||||
|
affinity: null,
|
||||||
|
blocked_operations: {},
|
||||||
|
ha_always_run: false,
|
||||||
|
HVM_boot_params: {
|
||||||
|
order: 'cdn'
|
||||||
|
},
|
||||||
|
HVM_boot_policy: 'BIOS order',
|
||||||
|
HVM_shadow_multiplier: 1,
|
||||||
|
is_a_template: false,
|
||||||
|
memory_dynamic_max: 4294967296,
|
||||||
|
memory_dynamic_min: 4294967296,
|
||||||
|
memory_static_max: 4294967296,
|
||||||
|
memory_static_min: 134217728,
|
||||||
|
order: 0,
|
||||||
|
other_config: {
|
||||||
|
vgpu_pci: '',
|
||||||
|
base_template_name: 'Other install media',
|
||||||
|
mac_seed: '5e88eb6a-d680-c47f-a94a-028886971ba4',
|
||||||
|
'install-methods': 'cdrom'
|
||||||
|
},
|
||||||
|
PCI_bus: '',
|
||||||
|
platform: {
|
||||||
|
timeoffset: '0',
|
||||||
|
nx: 'true',
|
||||||
|
acpi: '1',
|
||||||
|
apic: 'true',
|
||||||
|
pae: 'true',
|
||||||
|
hpet: 'true',
|
||||||
|
viridian: 'true'
|
||||||
|
},
|
||||||
|
protection_policy: 'OpaqueRef:NULL',
|
||||||
|
PV_args: '',
|
||||||
|
PV_bootloader: '',
|
||||||
|
PV_bootloader_args: '',
|
||||||
|
PV_kernel: '',
|
||||||
|
PV_legacy_args: '',
|
||||||
|
PV_ramdisk: '',
|
||||||
|
recommendations: '<restrictions><restriction field="memory-static-max" max="137438953472" /><restriction field="vcpus-max" max="32" /><restriction property="number-of-vbds" max="255" /><restriction property="number-of-vifs" max="7" /><restriction field="has-vendor-device" value="false" /></restrictions>',
|
||||||
|
shutdown_delay: 0,
|
||||||
|
start_delay: 0,
|
||||||
|
user_version: 1,
|
||||||
|
VCPUs_at_startup: 1,
|
||||||
|
VCPUs_max: 1,
|
||||||
|
VCPUs_params: {},
|
||||||
|
version: 0
|
||||||
|
}
|
||||||
|
export { OTHER_CONFIG_TEMPLATE as default }
|
Loading…
Reference in New Issue
Block a user