diff --git a/packages/xo-server/src/api/sr.js b/packages/xo-server/src/api/sr.js index 2da281717..df1b490cb 100644 --- a/packages/xo-server/src/api/sr.js +++ b/packages/xo-server/src/api/sr.js @@ -241,7 +241,7 @@ export async function createHba ({ host, nameLabel, nameDescription, scsiId }) { const xapi = this.getXapi(host) const deviceConfig = { - scsiId, + SCSIid: scsiId, } const srRef = await xapi.call( @@ -251,7 +251,7 @@ export async function createHba ({ host, nameLabel, nameDescription, scsiId }) { '0', nameLabel, nameDescription, - 'lvmoohba', // SR LVM over HBA + 'lvmohba', // SR LVM over HBA 'user', // recommended by Citrix true, {} @@ -366,7 +366,7 @@ export async function probeHba ({ host }) { let xml try { - await xapi.call('SR.probe', host._xapiRef, 'type', {}) + await xapi.call('SR.probe', host._xapiRef, {}, 'lvmohba', {}) throw new Error('the call above should have thrown an error') } catch (error) { @@ -382,7 +382,7 @@ export async function probeHba ({ host }) { hbaDevices.push({ hba: hbaDevice.hba.trim(), path: hbaDevice.path.trim(), - scsciId: hbaDevice.SCSIid.trim(), + scsiId: hbaDevice.SCSIid.trim(), size: hbaDevice.size.trim(), vendor: hbaDevice.vendor.trim(), }) @@ -668,6 +668,34 @@ probeIscsiExists.resolve = { host: ['host', 'host', 'administrate'], } +// ------------------------------------------------------------------- +// This function helps to detect if this HBA already exists in XAPI +// It returns a table of SR UUID, empty if no existing connections + +export async function probeHbaExists ({ host, scsiId }) { + const xapi = this.getXapi(host) + + const deviceConfig = { + SCSIid: scsiId, + } + + const xml = parseXml( + await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'lvmohba', {}) + ) + + // get the UUID of SR connected to this LUN + return ensureArray(xml.SRlist.SR).map(sr => ({ uuid: sr.UUID.trim() })) +} + +probeHbaExists.params = { + host: { type: 'string' }, + scsiId: { type: 'string' }, +} + +probeHbaExists.resolve = { + host: ['host', 'host', 'administrate'], +} + // ------------------------------------------------------------------- // This function helps to detect if this NFS SR already exists in XAPI // It returns a table of SR UUID, empty if no existing connections diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index e514d288b..e8d930730 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -429,6 +429,7 @@ const messages = { newSrPath: 'Path', newSrIqn: 'IQN', newSrLun: 'LUN', + newSrNoHba: 'No HBA devices', newSrAuth: 'with auth.', newSrUsername: 'User Name', newSrPassword: 'Password', diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 621a0c134..8f1e13501 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -1948,6 +1948,11 @@ export const probeSrIscsiExists = ( return _call('sr.probeIscsiExists', params) } +export const probeSrHba = host => _call('sr.probeHba', { host }) + +export const probeSrHbaExists = (host, scsiId) => + _call('sr.probeHbaExists', { host, scsiId }) + export const reattachSr = (host, uuid, nameLabel, nameDescription, type) => _call('sr.reattach', { host, uuid, nameLabel, nameDescription, type }) @@ -1985,6 +1990,9 @@ export const createSrIscsi = ( return _call('sr.createIscsi', params) } +export const createSrHba = (host, nameLabel, nameDescription, scsiId) => + _call('sr.createHba', { host, nameLabel, nameDescription, scsiId }) + export const createSrIso = ( host, nameLabel, diff --git a/packages/xo-web/src/xo-app/new/sr/index.js b/packages/xo-web/src/xo-app/new/sr/index.js index 271a69363..5753c55e6 100644 --- a/packages/xo-web/src/xo-app/new/sr/index.js +++ b/packages/xo-web/src/xo-app/new/sr/index.js @@ -16,6 +16,7 @@ import Wizard, { Section } from 'wizard' import { confirm } from 'modal' import { connectStore, formatSize } from 'utils' import { Container, Row, Col } from 'grid' +import { ignoreErrors } from 'promise-toolbox' import { injectIntl } from 'react-intl' import { Password, Select } from 'form' import { SelectHost } from 'select-objects' @@ -30,11 +31,14 @@ import { createSrIscsi, createSrLvm, createSrNfs, + createSrHba, probeSrIscsiExists, probeSrIscsiIqns, probeSrIscsiLuns, probeSrNfs, probeSrNfsExists, + probeSrHba, + probeSrHbaExists, reattachSrIso, reattachSr, } from 'xo' @@ -45,6 +49,50 @@ import { onChange: propTypes.func.isRequired, options: propTypes.array.isRequired, }) +class SelectScsiId extends Component { + _getOptions = createSelector( + () => this.props.options, + options => + map(options, ({ vendor, path, size, scsiId }) => ({ + label: `${vendor} - ${path} (${formatSize(size)})`, + value: scsiId, + })) + ) + + _handleChange = opt => { + const { props } = this + + this.setState({ value: opt.value }, () => props.onChange(opt.value)) + } + + componentDidMount () { + return this.componentDidUpdate() + } + + componentDidUpdate () { + let options + if ( + this.state.value === null && + (options = this._getOptions()).length === 1 + ) { + this._handleChange(options[0]) + } + } + + state = { value: null } + + render () { + return ( +