parent
af00adcfcc
commit
eb3760ee4a
@ -17,6 +17,7 @@
|
|||||||
- [Notifications] New notification page to provide important information about XOA (PR [#3904](https://github.com/vatesfr/xen-orchestra/pull/3904))
|
- [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))
|
- [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))
|
- [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
|
### Bug fixes
|
||||||
|
|
||||||
|
@ -477,6 +477,7 @@ const TRANSFORMS = {
|
|||||||
host: link(obj, 'host'),
|
host: link(obj, 'host'),
|
||||||
SR: link(obj, 'SR'),
|
SR: link(obj, 'SR'),
|
||||||
device_config: obj.device_config,
|
device_config: obj.device_config,
|
||||||
|
otherConfig: obj.other_config,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -29,10 +29,14 @@ const messages = {
|
|||||||
noValue: 'None',
|
noValue: 'None',
|
||||||
compression: 'Compression',
|
compression: 'Compression',
|
||||||
multipathing: 'Multipathing',
|
multipathing: 'Multipathing',
|
||||||
|
multipathingDisabled: 'Multipathing disabled',
|
||||||
enableMultipathing: 'Enable multipathing',
|
enableMultipathing: 'Enable multipathing',
|
||||||
disableMultipathing: 'Disable multipathing',
|
disableMultipathing: 'Disable multipathing',
|
||||||
enableAllHostsMultipathing: 'Enable all hosts multipathing',
|
enableAllHostsMultipathing: 'Enable all hosts multipathing',
|
||||||
disableAllHostsMultipathing: 'Disable all hosts multipathing',
|
disableAllHostsMultipathing: 'Disable all hosts multipathing',
|
||||||
|
paths: 'Paths',
|
||||||
|
pbdDisconnected: 'PBD disconnected',
|
||||||
|
hasInactivePath: 'Has an inactive path',
|
||||||
|
|
||||||
// ----- Modals -----
|
// ----- Modals -----
|
||||||
alertOk: 'OK',
|
alertOk: 'OK',
|
||||||
@ -770,7 +774,10 @@ const messages = {
|
|||||||
hostStatus: 'Status',
|
hostStatus: 'Status',
|
||||||
hostBuildNumber: 'Build number',
|
hostBuildNumber: 'Build number',
|
||||||
hostIscsiName: 'iSCSI name',
|
hostIscsiName: 'iSCSI name',
|
||||||
|
hostNoIscsiSr: 'Not connected to an iSCSI SR',
|
||||||
hostMultipathingSrs: 'Click to see concerned SRs',
|
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:
|
hostMultipathingWarning:
|
||||||
'The host{nHosts, plural, one {} other {s}} will lose the connection to the SRs. Do you want to continue?',
|
'The host{nHosts, plural, one {} other {s}} will lose the connection to the SRs. Do you want to continue?',
|
||||||
hostXenServerVersion: 'Version',
|
hostXenServerVersion: 'Version',
|
||||||
|
@ -593,3 +593,11 @@ export const generateRandomId = () =>
|
|||||||
Math.random()
|
Math.random()
|
||||||
.toString(36)
|
.toString(36)
|
||||||
.slice(2)
|
.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) : []
|
||||||
|
}
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import _ from 'intl'
|
import _ from 'intl'
|
||||||
import Component from 'base-component'
|
import Component from 'base-component'
|
||||||
import sum from 'lodash/sum'
|
|
||||||
import Ellipsis, { EllipsisContainer } from 'ellipsis'
|
import Ellipsis, { EllipsisContainer } from 'ellipsis'
|
||||||
import Icon from 'icon'
|
import Icon from 'icon'
|
||||||
import Link, { BlockLink } from 'link'
|
import Link, { BlockLink } from 'link'
|
||||||
import map from 'lodash/map'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import SingleLineRow from 'single-line-row'
|
import SingleLineRow from 'single-line-row'
|
||||||
import size from 'lodash/size'
|
|
||||||
import Tooltip from 'tooltip'
|
import Tooltip from 'tooltip'
|
||||||
import HomeTags from 'home-tags'
|
import HomeTags from 'home-tags'
|
||||||
import { Col } from 'grid'
|
import { Col } from 'grid'
|
||||||
|
import { map, size, sum, some } from 'lodash'
|
||||||
import { Text } from 'editable'
|
import { Text } from 'editable'
|
||||||
import {
|
import {
|
||||||
createGetObject,
|
createGetObject,
|
||||||
@ -25,7 +23,7 @@ import {
|
|||||||
removeTag,
|
removeTag,
|
||||||
setDefaultSr,
|
setDefaultSr,
|
||||||
} from 'xo'
|
} from 'xo'
|
||||||
import { connectStore, formatSizeShort } from 'utils'
|
import { connectStore, formatSizeShort, getIscsiPaths } from 'utils'
|
||||||
|
|
||||||
import styles from './index.css'
|
import styles from './index.css'
|
||||||
|
|
||||||
@ -43,10 +41,11 @@ import styles from './index.css'
|
|||||||
isSrShared
|
isSrShared
|
||||||
),
|
),
|
||||||
status: createSelector(
|
status: createSelector(
|
||||||
|
(_, props) => Boolean(props.item.sm_config.multipathable),
|
||||||
createGetObjectsOfType('PBD').filter((_, props) => pbd =>
|
createGetObjectsOfType('PBD').filter((_, props) => pbd =>
|
||||||
pbd.SR === props.item.id
|
pbd.SR === props.item.id
|
||||||
),
|
),
|
||||||
pbds => {
|
(multipathable, pbds) => {
|
||||||
const nbAttached = sum(map(pbds, pbd => (pbd.attached ? 1 : 0)))
|
const nbAttached = sum(map(pbds, pbd => (pbd.attached ? 1 : 0)))
|
||||||
const nbPbds = size(pbds)
|
const nbPbds = size(pbds)
|
||||||
if (!nbPbds) {
|
if (!nbPbds) {
|
||||||
@ -55,8 +54,18 @@ import styles from './index.css'
|
|||||||
if (!nbAttached) {
|
if (!nbAttached) {
|
||||||
return 0
|
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' />
|
<Icon icon='all-connected' />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
|
case 3:
|
||||||
|
return (
|
||||||
|
<Tooltip content={_('hasInactivePath')}>
|
||||||
|
<Icon icon='some-connected' />
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
import _ from 'intl'
|
import _ from 'intl'
|
||||||
import Component from 'base-component'
|
import Component from 'base-component'
|
||||||
import Copiable from 'copiable'
|
import Copiable from 'copiable'
|
||||||
|
import decorate from 'apply-decorators'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import SelectFiles from 'select-files'
|
import SelectFiles from 'select-files'
|
||||||
import StateButton from 'state-button'
|
import StateButton from 'state-button'
|
||||||
import TabButton from 'tab-button'
|
import TabButton from 'tab-button'
|
||||||
import Upgrade from 'xoa-upgrade'
|
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 { Text } from 'editable'
|
||||||
import { Toggle } from 'form'
|
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 {
|
import {
|
||||||
detachHost,
|
detachHost,
|
||||||
disableHost,
|
disableHost,
|
||||||
@ -38,6 +41,42 @@ const formatPack = ({ name, author, description, version }, key) => (
|
|||||||
|
|
||||||
const getPackId = ({ author, name }) => `${author}\0${name}`
|
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(() => {
|
@connectStore(() => {
|
||||||
const getPgpus = createGetObjectsOfType('PGPU')
|
const getPgpus = createGetObjectsOfType('PGPU')
|
||||||
.pick((_, { host }) => host.$PGPUs)
|
.pick((_, { host }) => host.$PGPUs)
|
||||||
@ -199,6 +238,7 @@ export default class extends Component {
|
|||||||
handler={setHostsMultipathing}
|
handler={setHostsMultipathing}
|
||||||
state={host.multipathing}
|
state={host.multipathing}
|
||||||
/>
|
/>
|
||||||
|
{host.multipathing && <MultipathableSrs hostId={host.id} />}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import _ from 'intl'
|
import _ from 'intl'
|
||||||
|
import decorate from 'apply-decorators'
|
||||||
import Link from 'link'
|
import Link from 'link'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import SortedTable from 'sorted-table'
|
import SortedTable from 'sorted-table'
|
||||||
import StateButton from 'state-button'
|
import StateButton from 'state-button'
|
||||||
import { Text } from 'editable'
|
|
||||||
import { noop } from 'utils'
|
|
||||||
import { confirm } from 'modal'
|
import { confirm } from 'modal'
|
||||||
import { isEmpty, some } from 'lodash'
|
|
||||||
import { Container, Row, Col } from 'grid'
|
import { Container, Row, Col } from 'grid'
|
||||||
import { editHost, connectPbd, disconnectPbd, deletePbd, deletePbds } from 'xo'
|
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 =>
|
const forgetHost = pbd =>
|
||||||
confirm({
|
confirm({
|
||||||
@ -81,21 +84,58 @@ const HOST_ACTIONS = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export default ({ hosts, pbds }) => (
|
const HOST_WITH_PATHS_COLUMNS = [
|
||||||
<Container>
|
...HOST_COLUMNS,
|
||||||
<Row>
|
{
|
||||||
<Col>
|
name: _('paths'),
|
||||||
{!isEmpty(hosts) ? (
|
itemRenderer: (pbd, hosts) => {
|
||||||
<SortedTable
|
if (!pbd.attached) {
|
||||||
actions={HOST_ACTIONS}
|
return _('pbdDisconnected')
|
||||||
collection={pbds}
|
}
|
||||||
userData={hosts}
|
|
||||||
columns={HOST_COLUMNS}
|
if (!get(() => hosts[pbd.host].multipathing)) {
|
||||||
/>
|
return _('multipathingDisabled')
|
||||||
) : (
|
}
|
||||||
<h4 className='text-xs-center'>{_('noHost')}</h4>
|
|
||||||
)}
|
const [nActives, nPaths] = getIscsiPaths(pbd)
|
||||||
</Col>
|
return (
|
||||||
</Row>
|
nActives !== undefined &&
|
||||||
</Container>
|
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>
|
||||||
|
),
|
||||||
|
])
|
||||||
|
Loading…
Reference in New Issue
Block a user