Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(xo-web/import): ability to import multiple VMs from VMware #6718

Merged
merged 7 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [VM] Show distro icon for opensuse-microos [Forum#6965](https://xcp-ng.org/forum/topic/6965) (PR [#6746](https://github.com/vatesfr/xen-orchestra/pull/6746))
- [Backup] Display the VM name label in the log even if the VM is not currently connected
- [Backup] Display the SR name label in the log even if the SR is not currently connected
- [Import VM] Ability to import multiple VMs from ESXi (PR [#6718](https://github.com/vatesfr/xen-orchestra/pull/6718))

### Bug fixes

Expand All @@ -20,7 +21,7 @@
- [ESXI import] Fix failing imports when using non default datacenter name [Forum#59543](https://xcp-ng.org/forum/post/59543) PR [#6729](https://github.com/vatesfr/xen-orchestra/pull/6729)
- [Backup] Fix backup worker consuming too much memory and being killed by system during full VM backup to S3 compatible remote PR [#6732](https://github.com/vatesfr/xen-orchestra/pull/6732)
- [REST API] Backup jobs are now available at `/rest/v0/backups/jobs`
- [Plugin/perf-alert] Ignore special SRs (e.g. *XCP-ng Tools*, *DVD drives*, etc) as their usage is always 100% (PR [#6755](https://github.com/vatesfr/xen-orchestra/pull/6755))
- [Plugin/perf-alert] Ignore special SRs (e.g. _XCP-ng Tools_, _DVD drives_, etc) as their usage is always 100% (PR [#6755](https://github.com/vatesfr/xen-orchestra/pull/6755))
Rajaa-BARHTAOUI marked this conversation as resolved.
Show resolved Hide resolved

### Packages to release

Expand Down
2 changes: 1 addition & 1 deletion packages/xo-web/src/common/intl/locales/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -2616,7 +2616,7 @@ export default {
// Original text: "Export starting…"
startVmExport: 'Comenzando export…',

// Original text: 'N CPUs'
// Original text: 'Number of CPUs'
nCpus: undefined,

// Original text: 'Memory'
Expand Down
4 changes: 2 additions & 2 deletions packages/xo-web/src/common/intl/locales/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -2675,8 +2675,8 @@ export default {
// Original text: "Export starting…"
startVmExport: "L'export commence…",

// Original text: "N CPUs"
nCpus: 'N CPUs',
// Original text: "Number of CPUs"
nCpus: 'Nombre de CPUs',

// Original text: "Memory"
vmMemory: 'Mémoire',
Expand Down
2 changes: 1 addition & 1 deletion packages/xo-web/src/common/intl/locales/he.js
Original file line number Diff line number Diff line change
Expand Up @@ -2294,7 +2294,7 @@ export default {
// Original text: 'Export starting…'
startVmExport: undefined,

// Original text: 'N CPUs'
// Original text: 'Number of CPUs'
nCpus: undefined,

// Original text: 'Memory'
Expand Down
4 changes: 2 additions & 2 deletions packages/xo-web/src/common/intl/locales/hu.js
Original file line number Diff line number Diff line change
Expand Up @@ -2508,8 +2508,8 @@ export default {
// Original text: "Export starting…"
startVmExport: 'Exportálás indul…',

// Original text: "N CPUs"
nCpus: 'N CPUs',
// Original text: "Number of CPUs"
nCpus: undefined,

// Original text: "Memory"
vmMemory: 'Memória',
Expand Down
4 changes: 2 additions & 2 deletions packages/xo-web/src/common/intl/locales/it.js
Original file line number Diff line number Diff line change
Expand Up @@ -3779,8 +3779,8 @@ export default {
// Original text: 'VDI export starting…'
startVdiExport: "Inizio dell'esportazione VDI…",

// Original text: 'N CPUs'
nCpus: 'N CPUs',
// Original text: 'Number of CPUs'
nCpus: undefined,

// Original text: 'Memory'
vmMemory: 'Memoria',
Expand Down
4 changes: 2 additions & 2 deletions packages/xo-web/src/common/intl/locales/pl.js
Original file line number Diff line number Diff line change
Expand Up @@ -2298,8 +2298,8 @@ export default {
// Original text: "Export starting…"
startVmExport: 'Eksport rozpoczęty…',

// Original text: "N CPUs"
nCpus: 'N CPUs',
// Original text: "Number of CPUs"
nCpus: undefined,

// Original text: "Memory"
vmMemory: 'Pamieć',
Expand Down
2 changes: 1 addition & 1 deletion packages/xo-web/src/common/intl/locales/pt.js
Original file line number Diff line number Diff line change
Expand Up @@ -2297,7 +2297,7 @@ export default {
// Original text: "Export starting…"
startVmExport: 'Iniciando exportação…',

// Original text: 'N CPUs'
// Original text: 'Number of CPUs'
nCpus: undefined,

// Original text: 'Memory'
Expand Down
2 changes: 1 addition & 1 deletion packages/xo-web/src/common/intl/locales/ru.js
Original file line number Diff line number Diff line change
Expand Up @@ -2618,7 +2618,7 @@ export default {
// Original text: "Export starting…"
startVmExport: 'Начало экспорта…',

// Original text: 'N CPUs'
// Original text: 'Number of CPUs'
nCpus: undefined,

// Original text: 'Memory'
Expand Down
4 changes: 2 additions & 2 deletions packages/xo-web/src/common/intl/locales/tr.js
Original file line number Diff line number Diff line change
Expand Up @@ -3244,8 +3244,8 @@ export default {
// Original text: "Export starting…"
startVmExport: 'Dışa aktarma başlatılıyor...',

// Original text: "N CPUs"
nCpus: 'N CPU',
// Original text: 'Number of CPUs'
nCpus: undefined,

// Original text: "Memory"
vmMemory: 'Bellek',
Expand Down
6 changes: 4 additions & 2 deletions packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ const messages = {
esxiImportStopSource: 'Stop the source VM',
esxiImportStopSourceDescription:
'Source VM stopped before the last delta transfer (after final snapshot). Needed to fully transfer a running VM',

esxiImportStopOnErrorDescription: 'Stop on the first error when importing VMs',
nImportVmsInParallel: 'Number of VMs to import in parallel',
stopOnError: 'Stop on error',
vmSrUsage: 'Storage: {used} used of {total} ({free} free)',

notDefined: 'Not defined',
Expand Down Expand Up @@ -1645,7 +1647,7 @@ const messages = {
startVdiImport: 'VDI import starting…',
startVmExport: 'Export starting…',
startVdiExport: 'VDI export starting…',
nCpus: 'N CPUs',
nCpus: 'Number of CPUs',
vmMemory: 'Memory',
diskInfo: 'Disk {position} ({capacity})',
diskDescription: 'Disk description',
Expand Down
2 changes: 1 addition & 1 deletion packages/xo-web/src/common/xo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3498,4 +3498,4 @@ export const synchronizeNetbox = pools =>
export const esxiListVms = (host, user, password, sslVerify) =>
_call('esxi.listVms', { host, user, password, sslVerify })

export const importVmFromEsxi = params => _call('vm.importFromEsxi', params)
export const importVmsFromEsxi = params => _call('vm.importMultipleFromEsxi', params)
85 changes: 61 additions & 24 deletions packages/xo-web/src/xo-app/vm-import/esxi/esxi-import.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import _, { messages } from 'intl'
import ActionButton from 'action-button'
import Button from 'button'
import Collapse from 'collapse'
import Component from 'base-component'
import React from 'react'
import { connectStore, resolveId } from 'utils'
import { createGetObjectsOfType, createSelector } from 'selectors'
import { esxiListVms, importVmFromEsxi, isSrWritable } from 'xo'
import { esxiListVms, importVmsFromEsxi, isSrWritable } from 'xo'
import { find, isEmpty, keyBy, map, pick } from 'lodash'
import { injectIntl } from 'react-intl'
import { Input } from 'debounce-input-decorator'
Expand All @@ -14,8 +15,9 @@ import { Password, Select, Toggle } from 'form'
import { SelectNetwork, SelectPool, SelectSr } from 'select-objects'

import VmData from './vm-data'
import { getRedirectionUrl } from '../utils'

const getRedirectUrl = vmId => `/vms/${vmId}`
const N_IMPORT_VMS_IN_PARALLEL = 2

@injectIntl
@connectStore({
Expand All @@ -24,12 +26,14 @@ const getRedirectUrl = vmId => `/vms/${vmId}`
})
class EsxiImport extends Component {
state = {
skipSslVerify: false,
thin: false,
stopSource: false,
concurrency: N_IMPORT_VMS_IN_PARALLEL,
hostIp: '',
isConnected: false,
password: '',
thin: false,
skipSslVerify: false,
stopSource: false,
stopOnError: true,
user: '',
}

Expand Down Expand Up @@ -60,18 +64,21 @@ class EsxiImport extends Component {
poolId => (poolId === undefined ? undefined : sr => isSrWritable(sr) && sr.$poolId === poolId)
)

_importVm = () => {
const { hostIp, network, password, skipSslVerify, sr, stopSource, thin, user, vm } = this.state
return importVmFromEsxi({
_importVms = () => {
const { concurrency, hostIp, network, password, skipSslVerify, sr, stopSource, stopOnError, thin, user, vms } =
this.state
pdonias marked this conversation as resolved.
Show resolved Hide resolved
return importVmsFromEsxi({
concurrency: +concurrency,
host: hostIp,
network: network?.id ?? this._getDefaultNetwork(),
password,
sr: resolveId(sr),
sslVerify: !skipSslVerify,
stopOnError,
stopSource,
thin,
user,
vm: vm.value,
vms: vms.map(vm => vm.value),
})
}

Expand All @@ -92,8 +99,6 @@ class EsxiImport extends Component {
_resetConnectForm = () => {
this.setState({
skipSslVerify: false,
thin: false,
stopSource: false,
hostIp: '',
isConnected: false,
password: '',
Expand All @@ -103,27 +108,33 @@ class EsxiImport extends Component {

_resetImportForm = () => {
this.setState({
concurrency: N_IMPORT_VMS_IN_PARALLEL,
network: undefined,
pool: undefined,
sr: undefined,
vm: undefined,
stopSource: false,
stopOnError: true,
thin: false,
vms: undefined,
})
}

render() {
const { intl } = this.props
const {
thin,
stopSource,
concurrency,
hostIp,
isConnected,
network = this._getDefaultNetwork(),
password,
pool,
skipSslVerify,
sr,
stopSource,
stopOnError,
thin,
user,
vm,
vms,
vmsById,
} = this.state

Expand Down Expand Up @@ -184,14 +195,26 @@ class EsxiImport extends Component {
return (
<form>
<Row>
<LabelCol>{_('vm')}</LabelCol>
<LabelCol>{_('nImportVmsInParallel')}</LabelCol>
<InputCol>
<input
className='form-control'
onChange={this.linkState('concurrency')}
type='number'
value={concurrency}
/>
</InputCol>
</Row>
<Row>
<LabelCol>{_('vms')}</LabelCol>
<InputCol>
<Select
disabled={isEmpty(vmsById)}
onChange={this.linkState('vm')}
multi
onChange={this.linkState('vms')}
options={this._getSelectVmOptions()}
required
value={vm}
value={vms}
/>
</InputCol>
</Row>
Expand Down Expand Up @@ -239,21 +262,35 @@ class EsxiImport extends Component {
<small className='form-text text-muted'>{_('esxiImportStopSourceDescription')}</small>
</InputCol>
</Row>
{vm !== undefined && (
<Row>
<LabelCol>{_('stopOnError')}</LabelCol>
<InputCol>
<Toggle onChange={this.toggleState('stopOnError')} value={stopOnError} />
<small className='form-text text-muted'>{_('esxiImportStopOnErrorDescription')}</small>
</InputCol>
</Row>

{!isEmpty(vms) && (
<div>
<hr />
<h5>{_('vmsToImport', { nVms: 1 })}</h5>
<VmData data={vmsById[vm.value]} />
<h5>{_('vmsToImport', { nVms: vms.length })}</h5>
{vms.map(vm => (
<Collapse className='mt-1 mb-1' buttonText={vm.label} key={vm.value} size='small'>
<div className='mt-1'>
<VmData data={vmsById[vm.value]} />
</div>
</Collapse>
))}
</div>
)}
<div className='form-group pull-right'>
<ActionButton
btnStyle='primary'
className='mr-1'
disabled={vm === undefined}
handler={this._importVm}
disabled={isEmpty(vms)}
handler={this._importVms}
icon='import'
redirectOnSuccess={getRedirectUrl}
redirectOnSuccess={getRedirectionUrl}
type='submit'
>
{_('newImport')}
Expand Down
14 changes: 7 additions & 7 deletions packages/xo-web/src/xo-app/vm-import/esxi/vm-data.js
MathieuRA marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,25 @@ const VmData = ({ data }) => (
<div>
<Row>
<Col mediumSize={6}>
<div className='form-group'>{_('keyValue', { key: _('vmNameLabel'), value: data.nameLabel })}</div>
<div className='form-group'>
<div>{_('keyValue', { key: _('vmNameLabel'), value: data.nameLabel })}</div>
<div>
{_('keyValue', {
key: _('powerState'),
value: data.powerState === 'poweredOn' ? _('powerStateRunning') : _('powerStateHalted'),
})}
</div>
</Col>
<Col mediumSize={6}>
<div className='form-group'>{_('keyValue', { key: _('nCpus'), value: data.nCpus })}</div>
<div className='form-group'>{_('keyValue', { key: _('vmMemory'), value: formatSize(data.memory) })}</div>
<div>{_('keyValue', { key: _('nCpus'), value: data.nCpus })}</div>
<div>{_('keyValue', { key: _('vmMemory'), value: formatSize(data.memory) })}</div>
</Col>
</Row>
<Row>
<Col mediumSize={6}>
<div className='form-group'>{_('keyValue', { key: _('firmware'), value: data.firmware })}</div>
<div>{_('keyValue', { key: _('firmware'), value: data.firmware })}</div>
</Col>
<Col mediumSize={6}>
<div className='form-group'>
<div>
{_('keyValue', {
key: _('guestToolStatus'),
value: data.guestToolsInstalled ? _('noToolsInstalled') : _('toolsInstalled'),
Expand All @@ -35,7 +35,7 @@ const VmData = ({ data }) => (
</Row>
<Row>
<Col mediumSize={12}>
<div className='form-group'>
<div>
<span>
{_('vmSrUsage', {
free: formatSize(data.storage.free),
Expand Down
9 changes: 1 addition & 8 deletions packages/xo-web/src/xo-app/vm-import/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as CM from 'complex-matcher'
import * as FormGrid from 'form-grid'
import _ from 'intl'
import ActionButton from 'action-button'
Expand All @@ -22,6 +21,7 @@ import { SelectNetwork, SelectPool, SelectSr } from 'select-objects'
import parseOvaFile from './ova'

import styles from './index.css'
import { getRedirectionUrl } from './utils'

// ===================================================================

Expand Down Expand Up @@ -198,13 +198,6 @@ const parseFile = async (file, type, func) => {
}
}

const getRedirectionUrl = vms =>
vms.length === 0
? undefined // no redirect
: vms.length === 1
? `/vms/${vms[0]}`
: `/home?s=${encodeURIComponent(new CM.Property('id', new CM.Or(vms.map(_ => new CM.String(_)))).toString())}&t=VM`

export default class Import extends Component {
constructor(props) {
super(props)
Expand Down
Loading