From eb3760ee4a2bcdcea4800dee6f9254e0acfef29d Mon Sep 17 00:00:00 2001 From: badrAZ Date: Wed, 30 Jan 2019 11:02:34 +0100 Subject: [PATCH] feat(xo-web/SR): display iscsi paths (#3829) Fixes #3659 --- CHANGELOG.md | 1 + packages/xo-server/src/xapi-object-to-xo.js | 1 + packages/xo-web/src/common/intl/messages.js | 7 ++ packages/xo-web/src/common/utils.js | 8 ++ packages/xo-web/src/xo-app/home/sr-item.js | 27 ++++-- .../xo-web/src/xo-app/host/tab-advanced.js | 50 +++++++++-- packages/xo-web/src/xo-app/sr/tab-host.js | 82 ++++++++++++++----- 7 files changed, 144 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee75f62b0..471fa8312 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/xo-server/src/xapi-object-to-xo.js b/packages/xo-server/src/xapi-object-to-xo.js index 06593f141..5299668ff 100644 --- a/packages/xo-server/src/xapi-object-to-xo.js +++ b/packages/xo-server/src/xapi-object-to-xo.js @@ -477,6 +477,7 @@ const TRANSFORMS = { host: link(obj, 'host'), SR: link(obj, 'SR'), device_config: obj.device_config, + otherConfig: obj.other_config, } }, diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index abda778f0..30312a622 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -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', diff --git a/packages/xo-web/src/common/utils.js b/packages/xo-web/src/common/utils.js index d5dd50140..2eb5b9196 100644 --- a/packages/xo-web/src/common/utils.js +++ b/packages/xo-web/src/common/utils.js @@ -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) : [] +} diff --git a/packages/xo-web/src/xo-app/home/sr-item.js b/packages/xo-web/src/xo-app/home/sr-item.js index 108cae8b7..c0381cc50 100644 --- a/packages/xo-web/src/xo-app/home/sr-item.js +++ b/packages/xo-web/src/xo-app/home/sr-item.js @@ -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 { ) + case 3: + return ( + + + + ) } } diff --git a/packages/xo-web/src/xo-app/host/tab-advanced.js b/packages/xo-web/src/xo-app/host/tab-advanced.js index 685558c41..637638f46 100644 --- a/packages/xo-web/src/xo-app/host/tab-advanced.js +++ b/packages/xo-web/src/xo-app/host/tab-advanced.js @@ -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) ? ( +
{_('hostNoIscsiSr')}
+ ) : ( + + {map(pbds, pbd => { + const [nActives, nPaths] = getIscsiPaths(pbd) + return ( + + + {' '} + {nActives !== undefined && + nPaths !== undefined && + _('hostMultipathingPaths', { + nActives, + nPaths, + nSessions: pbd.otherConfig.iscsi_sessions, + })} + + + ) + })} + + ), +]) + +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 && } diff --git a/packages/xo-web/src/xo-app/sr/tab-host.js b/packages/xo-web/src/xo-app/sr/tab-host.js index 10bc0006e..c9ac980c0 100644 --- a/packages/xo-web/src/xo-app/sr/tab-host.js +++ b/packages/xo-web/src/xo-app/sr/tab-host.js @@ -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 }) => ( - - - - {!isEmpty(hosts) ? ( - - ) : ( -

{_('noHost')}

- )} - -
-
-) +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 }) => ( + + + + {!isEmpty(hosts) ? ( + + ) : ( +

{_('noHost')}

+ )} + +
+
+ ), +])