feat(xo-web/SR): display iscsi paths (#3829)

Fixes #3659
This commit is contained in:
badrAZ 2019-01-30 11:02:34 +01:00 committed by Pierre Donias
parent af00adcfcc
commit eb3760ee4a
7 changed files with 144 additions and 32 deletions

View File

@ -17,6 +17,7 @@
- [Notifications] New notification page to provide important information about XOA (PR [#3904](https://github.com/vatesfr/xen-orchestra/pull/3904))
- [VM] Ability to export a VM with zstd compression [#3773](https://github.com/vatesfr/xen-orchestra/issues/3773) (PR [#3891](https://github.com/vatesfr/xen-orchestra/pull/3891))
- [Host/network] Display PIF speed [#3887](https://github.com/vatesfr/xen-orchestra/issues/3887) (PR [#3901](https://github.com/vatesfr/xen-orchestra/pull/3901))
- [SR] Display iscsi paths and mark the SR with a yellow dot if one path is not available. [#3659](https://github.com/vatesfr/xen-orchestra/issues/3659) (PR [#3829](https://github.com/vatesfr/xen-orchestra/pull/3829))
### Bug fixes

View File

@ -477,6 +477,7 @@ const TRANSFORMS = {
host: link(obj, 'host'),
SR: link(obj, 'SR'),
device_config: obj.device_config,
otherConfig: obj.other_config,
}
},

View File

@ -29,10 +29,14 @@ const messages = {
noValue: 'None',
compression: 'Compression',
multipathing: 'Multipathing',
multipathingDisabled: 'Multipathing disabled',
enableMultipathing: 'Enable multipathing',
disableMultipathing: 'Disable multipathing',
enableAllHostsMultipathing: 'Enable all hosts multipathing',
disableAllHostsMultipathing: 'Disable all hosts multipathing',
paths: 'Paths',
pbdDisconnected: 'PBD disconnected',
hasInactivePath: 'Has an inactive path',
// ----- Modals -----
alertOk: 'OK',
@ -770,7 +774,10 @@ const messages = {
hostStatus: 'Status',
hostBuildNumber: 'Build number',
hostIscsiName: 'iSCSI name',
hostNoIscsiSr: 'Not connected to an iSCSI SR',
hostMultipathingSrs: 'Click to see concerned SRs',
hostMultipathingPaths:
'{nActives, number} of {nPaths, number} path{nPaths, plural, one {} other {s}} ({ nSessions, number } iSCSI session{nSessions, plural, one {} other {s}})',
hostMultipathingWarning:
'The host{nHosts, plural, one {} other {s}} will lose the connection to the SRs. Do you want to continue?',
hostXenServerVersion: 'Version',

View File

@ -593,3 +593,11 @@ export const generateRandomId = () =>
Math.random()
.toString(36)
.slice(2)
// ===================================================================
// it returns [nActivePaths, nPaths]
export const getIscsiPaths = pbd => {
const pathsInfo = pbd.otherConfig[`mpath-${pbd.device_config.SCSIid}`]
return pathsInfo !== undefined ? JSON.parse(pathsInfo) : []
}

View File

@ -1,16 +1,14 @@
import _ from 'intl'
import Component from 'base-component'
import sum from 'lodash/sum'
import Ellipsis, { EllipsisContainer } from 'ellipsis'
import Icon from 'icon'
import Link, { BlockLink } from 'link'
import map from 'lodash/map'
import React from 'react'
import SingleLineRow from 'single-line-row'
import size from 'lodash/size'
import Tooltip from 'tooltip'
import HomeTags from 'home-tags'
import { Col } from 'grid'
import { map, size, sum, some } from 'lodash'
import { Text } from 'editable'
import {
createGetObject,
@ -25,7 +23,7 @@ import {
removeTag,
setDefaultSr,
} from 'xo'
import { connectStore, formatSizeShort } from 'utils'
import { connectStore, formatSizeShort, getIscsiPaths } from 'utils'
import styles from './index.css'
@ -43,10 +41,11 @@ import styles from './index.css'
isSrShared
),
status: createSelector(
(_, props) => Boolean(props.item.sm_config.multipathable),
createGetObjectsOfType('PBD').filter((_, props) => pbd =>
pbd.SR === props.item.id
),
pbds => {
(multipathable, pbds) => {
const nbAttached = sum(map(pbds, pbd => (pbd.attached ? 1 : 0)))
const nbPbds = size(pbds)
if (!nbPbds) {
@ -55,8 +54,18 @@ import styles from './index.css'
if (!nbAttached) {
return 0
}
if (nbAttached < nbPbds) {
return 1
}
return nbAttached < nbPbds ? 1 : 2
const hasInactivePath =
multipathable &&
some(pbds, pbd => {
const [nActives, nPaths] = getIscsiPaths(pbd)
return nActives !== nPaths
})
return hasInactivePath ? 3 : 2
}
),
})
@ -99,6 +108,12 @@ export default class SrItem extends Component {
<Icon icon='all-connected' />
</Tooltip>
)
case 3:
return (
<Tooltip content={_('hasInactivePath')}>
<Icon icon='some-connected' />
</Tooltip>
)
}
}

View File

@ -1,18 +1,21 @@
import _ from 'intl'
import Component from 'base-component'
import Copiable from 'copiable'
import decorate from 'apply-decorators'
import PropTypes from 'prop-types'
import React from 'react'
import SelectFiles from 'select-files'
import StateButton from 'state-button'
import TabButton from 'tab-button'
import Upgrade from 'xoa-upgrade'
import { compareVersions, connectStore, getIscsiPaths } from 'utils'
import { Container, Row, Col } from 'grid'
import { createGetObjectsOfType, createSelector } from 'selectors'
import { forEach, map, noop, isEmpty } from 'lodash'
import { FormattedRelative, FormattedTime } from 'react-intl'
import { Sr } from 'render-xo-item'
import { Text } from 'editable'
import { Toggle } from 'form'
import { compareVersions, connectStore } from 'utils'
import { FormattedRelative, FormattedTime } from 'react-intl'
import { Container, Row, Col } from 'grid'
import { forEach, map, noop } from 'lodash'
import { createGetObjectsOfType, createSelector } from 'selectors'
import {
detachHost,
disableHost,
@ -38,6 +41,42 @@ const formatPack = ({ name, author, description, version }, key) => (
const getPackId = ({ author, name }) => `${author}\0${name}`
const MultipathableSrs = decorate([
connectStore({
pbds: createGetObjectsOfType('PBD').filter((_, { hostId }) => pbd =>
pbd.host === hostId && Boolean(pbd.otherConfig.multipathed)
),
}),
({ pbds }) =>
isEmpty(pbds) ? (
<div>{_('hostNoIscsiSr')}</div>
) : (
<Container>
{map(pbds, pbd => {
const [nActives, nPaths] = getIscsiPaths(pbd)
return (
<Row key={pbd.id}>
<Col>
<Sr id={pbd.SR} link newTab container={false} />{' '}
{nActives !== undefined &&
nPaths !== undefined &&
_('hostMultipathingPaths', {
nActives,
nPaths,
nSessions: pbd.otherConfig.iscsi_sessions,
})}
</Col>
</Row>
)
})}
</Container>
),
])
MultipathableSrs.propTypes = {
hostId: PropTypes.string.isRequired,
}
@connectStore(() => {
const getPgpus = createGetObjectsOfType('PGPU')
.pick((_, { host }) => host.$PGPUs)
@ -199,6 +238,7 @@ export default class extends Component {
handler={setHostsMultipathing}
state={host.multipathing}
/>
{host.multipathing && <MultipathableSrs hostId={host.id} />}
</td>
</tr>
<tr>

View File

@ -1,14 +1,17 @@
import _ from 'intl'
import decorate from 'apply-decorators'
import Link from 'link'
import React from 'react'
import SortedTable from 'sorted-table'
import StateButton from 'state-button'
import { Text } from 'editable'
import { noop } from 'utils'
import { confirm } from 'modal'
import { isEmpty, some } from 'lodash'
import { Container, Row, Col } from 'grid'
import { editHost, connectPbd, disconnectPbd, deletePbd, deletePbds } from 'xo'
import { get } from '@xen-orchestra/defined'
import { getIscsiPaths, noop } from 'utils'
import { isEmpty, some } from 'lodash'
import { provideState, injectState } from 'reaclette'
import { Text } from 'editable'
const forgetHost = pbd =>
confirm({
@ -81,21 +84,58 @@ const HOST_ACTIONS = [
},
]
export default ({ hosts, pbds }) => (
<Container>
<Row>
<Col>
{!isEmpty(hosts) ? (
<SortedTable
actions={HOST_ACTIONS}
collection={pbds}
userData={hosts}
columns={HOST_COLUMNS}
/>
) : (
<h4 className='text-xs-center'>{_('noHost')}</h4>
)}
</Col>
</Row>
</Container>
)
const HOST_WITH_PATHS_COLUMNS = [
...HOST_COLUMNS,
{
name: _('paths'),
itemRenderer: (pbd, hosts) => {
if (!pbd.attached) {
return _('pbdDisconnected')
}
if (!get(() => hosts[pbd.host].multipathing)) {
return _('multipathingDisabled')
}
const [nActives, nPaths] = getIscsiPaths(pbd)
return (
nActives !== undefined &&
nPaths !== undefined &&
_('hostMultipathingPaths', {
nActives,
nPaths,
nSessions: pbd.otherConfig.iscsi_sessions,
})
)
},
sortCriteria: (pbd, hosts) => get(() => hosts[pbd.host].multipathing),
},
]
export default decorate([
provideState({
computed: {
columns: (_, { sr }) =>
sr.sm_config.multipathable ? HOST_WITH_PATHS_COLUMNS : HOST_COLUMNS,
},
}),
injectState,
({ state, hosts, pbds }) => (
<Container>
<Row>
<Col>
{!isEmpty(hosts) ? (
<SortedTable
actions={HOST_ACTIONS}
collection={pbds}
columns={state.columns}
userData={hosts}
/>
) : (
<h4 className='text-xs-center'>{_('noHost')}</h4>
)}
</Col>
</Row>
</Container>
),
])