diff --git a/@vates/nbd-client/index.mjs b/@vates/nbd-client/index.mjs index b7edc5eb8d..debc74e085 100644 --- a/@vates/nbd-client/index.mjs +++ b/@vates/nbd-client/index.mjs @@ -378,11 +378,13 @@ export default class NbdClient { `nbd://${this.#serverAddress}:${this.#serverPort}/${encodeURIComponent(this.#exportName)}`, ]) let text = '' + let errText = '' process.stdout.on('data', data => (text += data)) + process.stderr.on('data', data => (errText += data)) process.on('error', reject) process.on('close', code => { if (code !== 0) { - return reject(new Error(`process ended with code ${code}`)) + return reject(new Error(`Error during getMap (code: ${code}): ${errText}`)) } try { const json = JSON.parse(text) diff --git a/@xen-orchestra/vmware-explorer/checks.mjs b/@xen-orchestra/vmware-explorer/checks.mjs index bca2d9b30f..8e4f8bfbe4 100644 --- a/@xen-orchestra/vmware-explorer/checks.mjs +++ b/@xen-orchestra/vmware-explorer/checks.mjs @@ -2,18 +2,19 @@ import { exec } from 'node:child_process' import semver from 'semver' import fs from 'node:fs/promises' +const NBDKIT_VERSION_VDDK9 = '1.42.5' + /** * * @returns {Promise} */ -/* async */ function nbdInfos() { +/* async */ function nbdInfo() { return new Promise(function (resolve, reject) { const expectedVersion = '1.23.4' exec('nbdinfo --version', (error, stdout, stderr) => { if (error) { return resolve({ - installed: false, - error, + error: `exit code ${error.code}`, status: 'error', }) } @@ -28,32 +29,43 @@ import fs from 'node:fs/promises' }) }) } -/** - * - * @returns {Promise} - */ -/* async */ function nbdKit() { - const expectedVersion = '1.45' + +async function getNbdKitVersion() { return new Promise(function (resolve) { exec('nbdkit --version', (error, stdout, stderr) => { if (error) { return resolve({ - installed: false, - error, + error: `exit code ${error.code}`, status: 'error', }) } const matches = stdout.match(/nbdkit ([0-9.]+)/) const version = matches?.[1] ?? '' - resolve({ - installed: true, - version, - status: semver.satisfies(version, `>=${expectedVersion}`) ? 'success' : 'alarm', - expectedVersion, - }) + resolve(version) }) }) } +/** + * + * @returns {Promise} + */ +async function nbdKit() { + const expectedVersion = '1.42' + try { + const version = await getNbdKitVersion() + return { + installed: true, + version, + status: semver.satisfies(version, `>=${expectedVersion}`) ? 'success' : 'alarm', + expectedVersion, + } + } catch (error) { + return { + error: `exit code ${error.code}`, + status: 'error', + } + } +} /** * @@ -82,7 +94,27 @@ import fs from 'node:fs/promises' async function vddk() { try { await fs.stat('/usr/local/lib/vddk/vmware-vix-disklib-distrib/lib64/libvixDiskLib.so') - return { status: 'success' } + + try { + const isV9 = await fs.exists('/usr/local/lib/vddk/vmware-vix-disklib-distrib/lib64/libvixDiskLib.so.9') + const nbdKitVersion = await getNbdKitVersion() + if (isV9) { + if (!semver.satisfies(nbdKitVersion, `>=${NBDKIT_VERSION_VDDK9}`)) { + return { + status: 'warning', + expectedVersion: '1.42.5', + version: nbdKitVersion, + } + } + } + return { status: 'success' } + } catch (error) { + return { + status: 'warning', + expectedVersion: '1.42.5', + version: 'unknown', + } + } } catch (error) { return { status: 'error', @@ -97,9 +129,9 @@ async function vddk() { */ export async function checkVddkDependencies() { return { - nbdInfos: await nbdInfos(), + nbdinfo: await nbdInfo(), nbdkit: await nbdKit(), - nbdKitVddk: await nbdKitVddk(), + nbdkitPluginVddk: await nbdKitVddk(), vddk: await vddk(), } } diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 4b2017f58b..bbc515cfd6 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -11,6 +11,8 @@ > Users must be able to say: “Nice enhancement, I'm eager to test it” +- [V2V] Auto install library (PR [#8911](https://github.com/vatesfr/pull/8911)) + ### Bug fixes > Users must be able to say: “I had this issue, happy to know it's fixed” @@ -31,7 +33,11 @@ +- @vates/nbd-client patch - @xen-orchestra/web minor - @xen-orchestra/web-core minor +- @xen-orchestra/vmware-explorer patch +- xo-server minor +- xo-web minor diff --git a/packages/xo-server/src/api/esxi.mjs b/packages/xo-server/src/api/esxi.mjs index a967213f52..f386cf128a 100644 --- a/packages/xo-server/src/api/esxi.mjs +++ b/packages/xo-server/src/api/esxi.mjs @@ -5,6 +5,18 @@ import { createWriteStream } from 'node:fs' import { VDDK_LIB_DIR } from '@xen-orchestra/vmware-explorer/esxi.mjs' import { exec } from 'node:child_process' +function execPromise(command, opts = []) { + return new Promise((resolve, reject) => { + exec(command, { maxBuffer: 10 * 1024 * 1024, ...opts }, (error, stdout) => { + if (error) { + reject(error) + } else { + resolve(stdout) + } + }) + }) +} + export function listVms({ host, password, sslVerify = true, user }) { return this.connectToEsxiAndList({ host, user, password, sslVerify }) } @@ -35,16 +47,7 @@ async function handleImport(req, res) { req.on('end', resolve) req.on('error', reject) }) - return new Promise((resolve, reject) => { - exec(`tar xfz ${tempFilePath} -C ${VDDK_LIB_DIR}`, { maxBuffer: 10 * 1024 * 1024 }, err => { - if (err) { - reject(err) - } else { - res.end() - return resolve() - } - }) - }) + await execPromise(`tar xfz ${tempFilePath} -C ${VDDK_LIB_DIR}`) } export async function installVddkLib() { return { @@ -54,3 +57,50 @@ export async function installVddkLib() { installVddkLib.params = {} installVddkLib.permission = 'admin' + +async function install({ repository, version, executable }) { + const tmpDir = await fs.mkdtemp(join(tmpdir(), 'xo-server')) + const libPath = await execPromise(`which ${executable}`).catch(() => {}) + if (libPath) { + throw new Error( + `${executable} is already installed at ${libPath} on this system please uninstall before rerunning this script` + ) + } + let id + if ((id = (await execPromise('id -u')).trim() !== '0')) { + throw new Error(`install script can only be run as root ${id}`) + } + try { + await execPromise('apt-get --version') + } catch (err) { + const error = new Error(`The ${executable} auto install can only be run on a debian based system`) + error.cause = err + throw err + } + + await execPromise('apt-get install -y git dh-autoreconf pkg-config make libxml2-dev ocaml libc-bin') + + await execPromise(`git clone ${repository} ${tmpDir}`) + await execPromise(`git checkout ${version} `, { cwd: tmpDir }) + + await execPromise('autoreconf -i > autoreconf.log', { cwd: tmpDir }) + await execPromise('./configure > autoreconf.log', { cwd: tmpDir }) + await execPromise('make', { cwd: tmpDir }) + await execPromise('make install', { cwd: tmpDir }) + await execPromise('ldconfig', { cwd: tmpDir }) + await execPromise(`${executable} --version`) +} + +export async function installNbdInfoFromSource() { + await install({ executable: 'nbdinfo', repository: 'https://gitlab.com/nbdkit/libnbd.git', version: 'v1.23.4' }) +} + +installNbdInfoFromSource.params = {} +installNbdInfoFromSource.permission = 'admin' + +export async function installNbdKitFromSource() { + await install({ executable: 'nbdkit', repository: 'https://gitlab.com/nbdkit/nbdkit.git', version: 'v1.44.3' }) +} + +installNbdKitFromSource.params = {} +installNbdKitFromSource.permission = 'admin' diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index 6dba7604fb..68f18274dd 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -20,14 +20,20 @@ const messages = { esxiLibraryInfo: 'The V2V tool need the vddk library, provided by Broadcom. Please download it from their website', esxiLibraryLink: 'Donwload link', - esxiLibrary: 'Drop the tar.gz file of the vddk library (linux)', - esxiLibraryImport: 'Import and install library', + esxiVddkLibrary: 'Drop the tar.gz file of the vddk library (linux)', + esxiVddkLibraryImport: 'Import and install the Vddk library. VDDK9 need nbdkit 1.42+', + esxiLibraryManualInstall: + 'For other systems, you can install manually from https://gitlab.com/nbdkit/nbdkit . For reference the list of packages need for a debian 13 is **git dh-autoreconf pkg-config make libxml2-dev ocaml libc-bin**', + esxiLibraryAutoInstall: 'install {library} (debian based system)', + esxiLibraryNotInstalled: '{library} is not installed', + + esxiLibraryOutdated: + '{library} library is outdated expecting {expectedVersion}, got {version}. Please uninstall it and install the required version.', esxiCheckingPrerequisite: 'Checking prerequisite on XO', esxiCheckedPrerequisite: 'Result of the prerequisite check on XO', esxiCheckingPrerequisiteError: 'Must be corrected before importing VM', - // eslint-disable-next-line no-template-curly-in-string - esxiCheckedPrerequisiteVersion: 'expected version ${expectedVersion} , ${version} installed', + esxiCheckedPrerequisiteVersion: 'expected version {expectedVersion} , {version} installed', esxiImportSslCertificate: 'Skip SSL check', esxiImportThin: 'Thin mode', esxiImportThinDescription: diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 49eaf5d44b..edb45d562f 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -4316,6 +4316,24 @@ export const importVddkLib = file => { }) }) } +export const installNbdInfo = file => { + return _call('esxi.installNbdInfoFromSource') + .then(() => { + success('nbdInfo successfullly installed successfully installed') + }) + .catch(err => { + error('fail to install nbdInfo', err) + }) +} +export const installNbdKit = file => { + return _call('esxi.installNbdKitFromSource') + .then(() => { + success('nbdkit successfullly installed successfully installed') + }) + .catch(err => { + error('fail to install nbdkit', err) + }) +} // GitHub API --------------------------------------------------------------- const _callGithubApi = async (endpoint = '') => { const url = new URL('https://api.github.com/repos/vatesfr/xen-orchestra') diff --git a/packages/xo-web/src/xo-app/vm-import/esxi/esxi-import.js b/packages/xo-web/src/xo-app/vm-import/esxi/esxi-import.js index 971b98d67a..f4e8fc6d02 100644 --- a/packages/xo-web/src/xo-app/vm-import/esxi/esxi-import.js +++ b/packages/xo-web/src/xo-app/vm-import/esxi/esxi-import.js @@ -8,7 +8,15 @@ import Icon from 'icon' import React from 'react' import { connectStore, resolveId } from 'utils' import { createGetObjectsOfType, createSelector } from 'selectors' -import { esxiCheckInstall, esxiListVms, importVddkLib, importVmsFromEsxi, isSrWritable } from 'xo' +import { + esxiCheckInstall, + esxiListVms, + importVddkLib, + importVmsFromEsxi, + installNbdInfo, + installNbdKit, + isSrWritable, +} from 'xo' import { find, isEmpty, keyBy, map, pick } from 'lodash' import { injectIntl } from 'react-intl' import { Input } from 'debounce-input-decorator' @@ -27,9 +35,11 @@ function EsxiCheckResults({ esxiCheck }) { const { status, error, version, expectedVersion } = value return (
  • - - {name} :{status === 'success' && ' ok'} - {status === 'error' && error} + +   + {name} : {status === 'success' && ' ok'} + {status === 'error' && `"${error}"`} +   {status === 'error' && _('esxiCheckingPrerequisiteError')} {version && status === 'alarm' && _('esxiCheckedPrerequisiteVersion', { version, expectedVersion })}
  • @@ -79,6 +89,14 @@ class EsxiImport extends Component { } return this._esxiCheck() } + _installNbdInfo = async () => { + await installNbdInfo() + return this._esxiCheck() + } + _installNbKit = async () => { + await installNbdKit() + return this._esxiCheck() + } _getDefaultNetwork = createSelector( () => this.state.pool?.master, () => this.props.hostsById, @@ -184,6 +202,37 @@ class EsxiImport extends Component { return
    checking
    } + // cehck nbdkit, nbdinfo, nbdkit plugin vddk + for (const [library, fn] of [ + ['nbdinfo', this._installNbdInfo], + ['nbdkit', this._installNbKit], + ['nbdkitPluginVddk', this._installNbKit], + ]) { + const check = esxiCheck[library] + if (check.status !== 'success') { + return ( +
    + {check.version === undefined && ( +
    +

    {_('esxiLibraryNotInstalled', { library })}

    +
    + + {_('esxiLibraryAutoInstall', { library })} + +

    {_('esxiLibraryManualInstall')}

    +
    +
    + )} + {check.version !== undefined && ( +

    + {_('esxiLibraryOutdated', { library, expectedVersion: check.expectedVersion, version: check.version })} +

    + )} +
    + ) + } + } + if (esxiCheck.vddk?.status === 'error') { return (
    @@ -197,21 +246,17 @@ class EsxiImport extends Component { {_('esxiLibraryLink')}

    - + {vddkFile && (
    - {_('esxiLibraryImport')} + {_('esxiVddkLibraryImport')}
    )}
    ) } - if (Object.values(esxiCheck).some(({ status }) => status === 'error')) { - // do not show connection form is some prerequisites are in error - return - } if (!isConnected) { return (