From 0975863d98b5248f57fb6440b47e14579463c478 Mon Sep 17 00:00:00 2001 From: Mathieu <70369997+MathieuRA@users.noreply.github.com> Date: Fri, 25 Feb 2022 10:26:47 +0100 Subject: [PATCH] feat(xo-server/api/sr, xo-web/dashboard/health): list coalescing VDIs (#6120) See zammad#5224 --- CHANGELOG.unreleased.md | 3 + packages/xo-server/src/api/sr.mjs | 11 +++ packages/xo-web/src/common/intl/messages.js | 4 + packages/xo-web/src/common/xo/index.js | 3 + .../src/xo-app/dashboard/health/index.js | 3 + .../xo-app/dashboard/health/unhealthyVdis.js | 87 +++++++++++++++++++ packages/xo-web/src/xo-app/menu/index.js | 21 ++++- 7 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 03ccea22ab0..977e443a7e4 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -7,6 +7,9 @@ > Users must be able to say: “Nice enhancement, I'm eager to test it” +- [Dashboad/Health] List all VDIs that need coalescing (PR [#6120](https://github.com/vatesfr/xen-orchestra/pull/6120)) +- [Menu] Show a warning icon when some SRs have more than 10 VDIs to coalesce (PR [#6120](https://github.com/vatesfr/xen-orchestra/pull/6120)) + ### Bug fixes > Users must be able to say: “I had this issue, happy to know it's fixed” diff --git a/packages/xo-server/src/api/sr.mjs b/packages/xo-server/src/api/sr.mjs index dd2c793e576..500c56ebb5b 100644 --- a/packages/xo-server/src/api/sr.mjs +++ b/packages/xo-server/src/api/sr.mjs @@ -1,4 +1,5 @@ import asyncMapSettled from '@xen-orchestra/async-map/legacy.js' +import filter from 'lodash/filter.js' import some from 'lodash/some.js' import ensureArray from '../_ensureArray.mjs' @@ -864,6 +865,16 @@ probeNfsExists.resolve = { // ------------------------------------------------------------------- +export function getAllUnhealthyVdiChainsLength() { + const unhealthyVdiChainsLengthBySr = {} + filter(this.objects.all, obj => obj.type === 'SR' && obj.content_type !== 'iso' && obj.size > 0).forEach(sr => { + unhealthyVdiChainsLengthBySr[sr.uuid] = this.getXapi(sr).getUnhealthyVdiChainsLength(sr) + }) + return unhealthyVdiChainsLengthBySr +} + +// ------------------------------------------------------------------- + export function getUnhealthyVdiChainsLength({ sr }) { return this.getXapi(sr).getUnhealthyVdiChainsLength(sr) } diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index 06e96429e2b..da9163d3c58 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -275,6 +275,7 @@ const messages = { homeMissingPatches: 'Missing patches', homePoolMaster: 'Master:', homeResourceSet: 'Resource set: {resourceSet}', + homeSrVdisToCoalesce: 'Some VDIs need to be coalesced', highAvailability: 'High Availability', powerState: 'Power state', srSharedType: 'Shared {type}', @@ -1385,6 +1386,7 @@ const messages = { metricsLoading: 'Loading…', // ----- Health ----- + length: 'Length: {length}', deleteBackups: 'Delete backup{nBackups, plural, one {} other {s}}', deleteBackupsMessage: 'Are you sure you want to delete {nBackups, number} backup{nBackups, plural, one {} other {s}}?', @@ -1432,6 +1434,8 @@ const messages = { alarmObject: 'Issue on', alarmPool: 'Pool', spaceLeftTooltip: '{used}% used ({free} left)', + vdisToCoalesce: 'VDIs to coalesce', + srVdisToCoalesceWarning: 'This SR has more than {limitVdis, number} VDIs to coalesce', // ----- New VM ----- createVmModalTitle: 'Create VM', diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 6da72d7dfb7..5940e133c7a 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -40,6 +40,7 @@ import parseNdJson from './_parseNdJson' // =================================================================== export const ITEMS_PER_PAGE_OPTIONS = [10, 20, 50, 100] +export const VDIS_TO_COALESCE_LIMIT = 10 // =================================================================== @@ -524,6 +525,8 @@ subscribeVolumeInfo.forceRefresh = (() => { } })() +export const subscribeSrsUnhealthyVdiChainsLength = createSubscription(() => _call('sr.getAllUnhealthyVdiChainsLength')) + const unhealthyVdiChainsLengthSubscriptionsBySr = {} export const createSrUnhealthyVdiChainsLengthSubscription = sr => { sr = resolveId(sr) diff --git a/packages/xo-web/src/xo-app/dashboard/health/index.js b/packages/xo-web/src/xo-app/dashboard/health/index.js index 3420bbc7ee8..e55dfc9e239 100644 --- a/packages/xo-web/src/xo-app/dashboard/health/index.js +++ b/packages/xo-web/src/xo-app/dashboard/health/index.js @@ -36,6 +36,8 @@ import { createSort, } from 'selectors' +import UnhealthyVdis from './unhealthyVdis' + const SrColContainer = connectStore(() => ({ container: createGetObject(), }))( @@ -772,6 +774,7 @@ export default class Health extends Component { )} + {props.areObjectsFetched && } diff --git a/packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js b/packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js new file mode 100644 index 00000000000..3346143b852 --- /dev/null +++ b/packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js @@ -0,0 +1,87 @@ +import _ from 'intl' +import addSubscriptions from 'add-subscriptions' +import decorate from 'apply-decorators' +import Icon from 'icon' +import React from 'react' +import SingleLineRow from 'single-line-row' +import SortedTable from 'sorted-table' +import Tooltip from 'tooltip' +import { Card, CardHeader, CardBlock } from 'card' +import { Col, Row } from 'grid' +import { injectState, provideState } from 'reaclette' +import { map, size } from 'lodash' +import { Sr, Vdi } from 'render-xo-item' +import { subscribeSrsUnhealthyVdiChainsLength, VDIS_TO_COALESCE_LIMIT } from 'xo' + +const COLUMNS = [ + { + itemRenderer: (srId, { unhealthyVdiChainsLengthBySr }) => ( +
+ {' '} + {size(unhealthyVdiChainsLengthBySr[srId]) >= VDIS_TO_COALESCE_LIMIT && ( + + + + + + )} +
+ ), + name: _('sr'), + sortCriteria: 'name_label', + }, + { + itemRenderer: (srId, { unhealthyVdiChainsLengthBySr }) => ( +
+ {map(unhealthyVdiChainsLengthBySr[srId], (chainLength, vdiId) => ( + + + + + + {_('length', { length: chainLength })} + + + ))} +
+ ), + name: _('vdisToCoalesce'), + }, +] + +const UnhealthyVdis = decorate([ + addSubscriptions({ + unhealthyVdiChainsLengthBySr: subscribeSrsUnhealthyVdiChainsLength, + }), + provideState({ + computed: { + srIds: (state, { unhealthyVdiChainsLengthBySr = {} }) => Object.keys(unhealthyVdiChainsLengthBySr), + }, + }), + injectState, + ({ state: { srIds }, unhealthyVdiChainsLengthBySr }) => ( + + + + + {_('vdisToCoalesce')} + + + + + + + + + + + + ), +]) + +export default UnhealthyVdis diff --git a/packages/xo-web/src/xo-app/menu/index.js b/packages/xo-web/src/xo-app/menu/index.js index a6e27aabe04..e894fdef804 100644 --- a/packages/xo-web/src/xo-app/menu/index.js +++ b/packages/xo-web/src/xo-app/menu/index.js @@ -18,6 +18,8 @@ import { subscribeProxies, subscribeProxiesApplianceUpdaterState, subscribeResourceSets, + subscribeSrsUnhealthyVdiChainsLength, + VDIS_TO_COALESCE_LIMIT, } from 'xo' import { createFilter, @@ -29,7 +31,7 @@ import { getXoaState, isAdmin, } from 'selectors' -import { every, forEach, identity, isEmpty, isEqual, map, pick, some } from 'lodash' +import { every, forEach, identity, isEmpty, isEqual, map, pick, size, some } from 'lodash' import styles from './index.css' @@ -67,6 +69,7 @@ const returnTrue = () => true cb(map(proxies, 'id').sort()) }), resourceSets: subscribeResourceSets, + unhealthyVdiChainsLength: subscribeSrsUnhealthyVdiChainsLength, }) @injectState export default class Menu extends Component { @@ -135,6 +138,12 @@ export default class Menu extends Component { missingPatches => some(missingPatches, _ => _) ) + _hasUnhealthyVdis = createSelector( + () => this.state.unhealthyVdiChainsLength, + unhealthyVdiChainsLength => + some(unhealthyVdiChainsLength, vdiChainsLength => size(vdiChainsLength) >= VDIS_TO_COALESCE_LIMIT) + ) + _toggleCollapsed = event => { event.preventDefault() this._removeListener() @@ -211,6 +220,14 @@ export default class Menu extends Component { ) : null + const unhealthyVdisWarning = this._hasUnhealthyVdis() ? ( + + + + + + ) : null + /* eslint-disable object-property-newline */ const items = [ { @@ -247,6 +264,7 @@ export default class Menu extends Component { to: '/dashboard/overview', icon: 'menu-dashboard', label: 'dashboardPage', + extra: [unhealthyVdisWarning], subMenu: [ { to: '/dashboard/overview', @@ -267,6 +285,7 @@ export default class Menu extends Component { to: '/dashboard/health', icon: 'menu-dashboard-health', label: 'overviewHealthDashboardPage', + extra: [unhealthyVdisWarning], }, ], },