feat(xo-web,xo-server): implement ISO import (#6180)

This commit is contained in:
Florent BEAUCHAMP 2022-04-28 10:41:20 +02:00 committed by GitHub
parent af85df611c
commit 0706e6f4ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 44 additions and 23 deletions

View File

@ -11,6 +11,7 @@
- [Backup] Add _Restore Health Check_: ensure a backup is viable by doing an automatic test restore (requires guest tools in the VM) [#6148](https://github.com/vatesfr/xen-orchestra/pull/6148) - [Backup] Add _Restore Health Check_: ensure a backup is viable by doing an automatic test restore (requires guest tools in the VM) [#6148](https://github.com/vatesfr/xen-orchestra/pull/6148)
- [VM migrate] Allow to choose a private network for VIFs network (PR [#6200](https://github.com/vatesfr/xen-orchestra/pull/6200)) - [VM migrate] Allow to choose a private network for VIFs network (PR [#6200](https://github.com/vatesfr/xen-orchestra/pull/6200))
- [Proxy] Disable "Deploy proxy" button for source users (PR [#6199](https://github.com/vatesfr/xen-orchestra/pull/6199)) - [Proxy] Disable "Deploy proxy" button for source users (PR [#6199](https://github.com/vatesfr/xen-orchestra/pull/6199))
- [Import] Feat import `iso` disks (PR [#6180](https://github.com/vatesfr/xen-orchestra/pull/6180))
### Bug fixes ### Bug fixes

View File

@ -9,7 +9,7 @@ import { pipeline } from 'stream'
import { checkFooter, peekFooterFromVhdStream } from 'vhd-lib' import { checkFooter, peekFooterFromVhdStream } from 'vhd-lib'
import { vmdkToVhd } from 'xo-vmdk-to-vhd' import { vmdkToVhd } from 'xo-vmdk-to-vhd'
import { VDI_FORMAT_VHD } from '../xapi/index.mjs' import { VDI_FORMAT_VHD, VDI_FORMAT_RAW } from '../xapi/index.mjs'
const log = createLogger('xo:disk') const log = createLogger('xo:disk')
@ -182,25 +182,37 @@ async function handleImport(req, res, { type, name, description, vmdkData, srId,
})() })()
) )
} else { } else {
let diskFormat = VDI_FORMAT_VHD
await Promise.all(promises) await Promise.all(promises)
part.length = part.byteCount part.length = part.byteCount
if (type === 'vmdk') { switch (type) {
vhdStream = await vmdkToVhd(part, vmdkData.grainLogicalAddressList, vmdkData.grainFileOffsetList) case 'vmdk':
size = vmdkData.capacity vhdStream = await vmdkToVhd(part, vmdkData.grainLogicalAddressList, vmdkData.grainFileOffsetList)
} else if (type === 'vhd') { size = vmdkData.capacity
vhdStream = part break
const footer = await peekFooterFromVhdStream(vhdStream) case 'vhd':
try { {
checkFooter(footer) const footer = await peekFooterFromVhdStream(vhdStream)
} catch (e) { try {
if (e instanceof assert.AssertionError) { checkFooter(footer)
throw new JsonRpcError(`Vhd file had an invalid header ${e}`) } catch (e) {
if (e instanceof assert.AssertionError) {
throw new JsonRpcError(`Vhd file had an invalid header ${e}`)
}
}
vhdStream = part
size = footer.currentSize
} }
} break
size = footer.currentSize case 'iso':
} else { diskFormat = VDI_FORMAT_RAW
throw new JsonRpcError(`Unknown disk type, expected "vhd" or "vmdk", got ${type}`) vhdStream = part
size = part.byteCount
break
default:
throw new JsonRpcError(`Unknown disk type, expected "iso", "vhd" or "vmdk", got ${type}`)
} }
const vdi = await xapi.createVdi({ const vdi = await xapi.createVdi({
name_description: description, name_description: description,
name_label: name, name_label: name,
@ -208,7 +220,7 @@ async function handleImport(req, res, { type, name, description, vmdkData, srId,
sr: srId, sr: srId,
}) })
try { try {
await xapi.importVdiContent(vdi, vhdStream, VDI_FORMAT_VHD) await xapi.importVdiContent(vdi, vhdStream, { format: diskFormat })
res.end(format.response(0, vdi.$id)) res.end(format.response(0, vdi.$id))
} catch (e) { } catch (e) {
await vdi.$destroy() await vdi.$destroy()

View File

@ -10,13 +10,15 @@ export default class Dropzone extends Component {
onDrop: PropTypes.func, onDrop: PropTypes.func,
message: PropTypes.node, message: PropTypes.node,
multiple: PropTypes.bool, multiple: PropTypes.bool,
accept: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
} }
render() { render() {
const { onDrop, message, multiple } = this.props const { onDrop, message, multiple, accept } = this.props
return ( return (
<ReactDropzone <ReactDropzone
accept={accept}
activeClassName={styles.activeDropzone} activeClassName={styles.activeDropzone}
className={styles.dropzone} className={styles.dropzone}
multiple={multiple} multiple={multiple}

View File

@ -3819,7 +3819,7 @@ export default {
diskImportSuccess: 'Importazione del disco riuscita', diskImportSuccess: 'Importazione del disco riuscita',
// Original text: 'Drop VMDK or VHD files here to import disks.' // Original text: 'Drop VMDK or VHD files here to import disks.'
dropDisksFiles: 'Rilascia qui i file VMDK o VHD per importare i dischi.', dropDisksFiles: 'Rilascia qui i file ISO, VMDK o VHD per importare i dischi.',
// Original text: 'To SR' // Original text: 'To SR'
importToSr: 'A SR', importToSr: 'A SR',

View File

@ -1584,7 +1584,7 @@ const messages = {
// ---- Disk import --- // ---- Disk import ---
diskImportFailed: 'Disk import failed', diskImportFailed: 'Disk import failed',
diskImportSuccess: 'Disk import success', diskImportSuccess: 'Disk import success',
dropDisksFiles: 'Drop VMDK or VHD files here to import disks.', dropDisksFiles: 'Drop ISO, VMDK or VHD files here to import disks.',
importToSr: 'To SR', importToSr: 'To SR',
// ---- Tasks --- // ---- Tasks ---

View File

@ -54,6 +54,7 @@ export const XEN_VIDEORAM_VALUES = [1, 2, 4, 8, 16]
// =================================================================== // ===================================================================
export const isSrWritable = sr => sr && sr.content_type !== 'iso' && sr.size > 0 export const isSrWritable = sr => sr && sr.content_type !== 'iso' && sr.size > 0
export const isSrWritableOrIso = sr => sr && sr.size > 0
export const isSrShared = sr => sr && sr.shared export const isSrShared = sr => sr && sr.shared
export const isVmRunning = vm => vm && vm.power_state === 'Running' export const isVmRunning = vm => vm && vm.power_state === 'Running'

View File

@ -17,6 +17,7 @@ import { InputCol, LabelCol, Row } from 'form-grid'
import { map } from 'lodash' import { map } from 'lodash'
import { readCapacityAndGrainTable } from 'xo-vmdk-to-vhd' import { readCapacityAndGrainTable } from 'xo-vmdk-to-vhd'
import { SelectSr } from 'select-objects' import { SelectSr } from 'select-objects'
import { isSrWritableOrIso } from '../../common/xo'
const getInitialState = () => ({ const getInitialState = () => ({
disks: [], disks: [],
@ -40,7 +41,7 @@ const DiskImport = decorate([
if ( if (
extIndex >= 0 && extIndex >= 0 &&
(type = name.slice(extIndex + 1).toLowerCase()) && (type = name.slice(extIndex + 1).toLowerCase()) &&
(type === 'vmdk' || type === 'vhd') (type === 'vmdk' || type === 'vhd' || type === 'iso')
) { ) {
let vmdkData let vmdkData
if (type === 'vmdk') { if (type === 'vmdk') {
@ -108,12 +109,16 @@ const DiskImport = decorate([
<Row> <Row>
<LabelCol>{_('importToSr')}</LabelCol> <LabelCol>{_('importToSr')}</LabelCol>
<InputCol> <InputCol>
<SelectSr onChange={effects.onChangeSr} required value={sr} /> <SelectSr onChange={effects.onChangeSr} required value={sr} predicate={isSrWritableOrIso} />
</InputCol> </InputCol>
</Row> </Row>
{sr !== undefined && ( {sr !== undefined && (
<div> <div>
<Dropzone onDrop={effects.handleDrop} message={_('dropDisksFiles')} /> <Dropzone
onDrop={effects.handleDrop}
message={_('dropDisksFiles')}
accept={sr.content_type === 'iso' ? '.iso' : ['.vhd', '.vmdk']}
/>
{loadingDisks && <Icon icon='loading' />} {loadingDisks && <Icon icon='loading' />}
{disks.length > 0 && ( {disks.length > 0 && (
<div> <div>