From a9c123914945f72e55fb2d866e3cfccbfee3d3b1 Mon Sep 17 00:00:00 2001 From: Mathieu <70369997+MathieuRA@users.noreply.github.com> Date: Wed, 31 Aug 2022 11:35:35 +0200 Subject: [PATCH] feat(xo-server/xo-web/health): detect invalid vhd-parent VDIs (#6356) --- CHANGELOG.unreleased.md | 5 ++ packages/xo-server-audit/src/index.js | 2 +- packages/xo-server/src/api/sr.mjs | 10 ++-- .../xo-server/src/xapi/mixins/storage.mjs | 49 ++++++++++++------- packages/xo-web/src/common/intl/messages.js | 2 + packages/xo-web/src/common/xo/index.js | 2 +- .../xo-app/dashboard/health/unhealthyVdis.js | 42 +++++++++++----- packages/xo-web/src/xo-app/sr/tab-advanced.js | 6 +-- 8 files changed, 77 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index d5f47de3276..2b6038931e8 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -7,6 +7,8 @@ > Users must be able to say: “Nice enhancement, I'm eager to test it” +- [Dashboard/Health] Detect broken VHD chains and display missing parent VDIs (PR [#6356](https://github.com/vatesfr/xen-orchestra/pull/6356)) + ### Bug fixes > Users must be able to say: “I had this issue, happy to know it's fixed” @@ -27,4 +29,7 @@ +- xo-server minor +- xo-web minor + diff --git a/packages/xo-server-audit/src/index.js b/packages/xo-server-audit/src/index.js index 9d53ac52bdb..b83c2dceb83 100644 --- a/packages/xo-server-audit/src/index.js +++ b/packages/xo-server-audit/src/index.js @@ -47,7 +47,7 @@ const DEFAULT_BLOCKED_LIST = { 'session.getUser': true, 'session.signIn': true, 'sr.getAllUnhealthyVdiChainsLength': true, - 'sr.getUnhealthyVdiChainsLength': true, + 'sr.getVdiChainsInfo': true, 'sr.stats': true, 'system.getMethodsInfo': true, 'system.getServerTimezone': true, diff --git a/packages/xo-server/src/api/sr.mjs b/packages/xo-server/src/api/sr.mjs index eec20a6afdf..4bfa80a930c 100644 --- a/packages/xo-server/src/api/sr.mjs +++ b/packages/xo-server/src/api/sr.mjs @@ -872,7 +872,7 @@ probeNfsExists.resolve = { export const getAllUnhealthyVdiChainsLength = debounceWithKey(function getAllUnhealthyVdiChainsLength() { const unhealthyVdiChainsLengthBySr = {} filter(this.objects.all, obj => obj.type === 'SR' && obj.content_type !== 'iso' && obj.size > 0).forEach(sr => { - const unhealthyVdiChainsLengthByVdi = this.getXapi(sr).getUnhealthyVdiChainsLength(sr) + const unhealthyVdiChainsLengthByVdi = this.getXapi(sr).getVdiChainsInfo(sr) if (!isEmpty(unhealthyVdiChainsLengthByVdi)) { unhealthyVdiChainsLengthBySr[sr.uuid] = unhealthyVdiChainsLengthByVdi } @@ -882,15 +882,15 @@ export const getAllUnhealthyVdiChainsLength = debounceWithKey(function getAllUnh // ------------------------------------------------------------------- -export function getUnhealthyVdiChainsLength({ sr }) { - return this.getXapi(sr).getUnhealthyVdiChainsLength(sr) +export function getVdiChainsInfo({ sr }) { + return this.getXapi(sr).getVdiChainsInfo(sr) } -getUnhealthyVdiChainsLength.params = { +getVdiChainsInfo.params = { id: { type: 'string' }, } -getUnhealthyVdiChainsLength.resolve = { +getVdiChainsInfo.resolve = { sr: ['id', 'SR', 'operate'], } diff --git a/packages/xo-server/src/xapi/mixins/storage.mjs b/packages/xo-server/src/xapi/mixins/storage.mjs index a0957550097..89d549aa5b8 100644 --- a/packages/xo-server/src/xapi/mixins/storage.mjs +++ b/packages/xo-server/src/xapi/mixins/storage.mjs @@ -3,9 +3,6 @@ import forEach from 'lodash/forEach.js' import groupBy from 'lodash/groupBy.js' import { decorateWith } from '@vates/decorate-with' import { defer } from 'golike-defer' -import { createLogger } from '@xen-orchestra/log' - -const log = createLogger('xo:storage') export default { _connectAllSrPbds(sr) { @@ -52,39 +49,53 @@ export default { await this._unplugPbd(this.getObject(id)) }, - _getUnhealthyVdiChainLength(uuid, childrenMap, cache) { - let length = cache[uuid] - if (length === undefined) { + _getVdiChainsInfo(uuid, childrenMap, cache) { + let info = cache[uuid] + if (info === undefined) { const children = childrenMap[uuid] - length = children !== undefined && children.length === 1 ? 1 : 0 - try { - const parent = this.getObjectByUuid(uuid).sm_config['vhd-parent'] + const unhealthyLength = children !== undefined && children.length === 1 ? 1 : 0 + const vdi = this.getObjectByUuid(uuid, undefined) + if (vdi === undefined) { + info = { unhealthyLength, missingParent: uuid } + } else { + const parent = vdi.sm_config['vhd-parent'] if (parent !== undefined) { - length += this._getUnhealthyVdiChainLength(parent, childrenMap, cache) + info = this._getVdiChainsInfo(parent, childrenMap, cache) + info.unhealthyLength += unhealthyLength + } else { + info = { unhealthyLength } } - } catch (error) { - log.warn(`Xapi#_getUnhealthyVdiChainLength(${uuid})`, { error }) } - cache[uuid] = length + cache[uuid] = info } - return length + return info }, - getUnhealthyVdiChainsLength(sr) { + getVdiChainsInfo(sr) { const vdis = this.getObject(sr).$VDIs const unhealthyVdis = { __proto__: null } const children = groupBy(vdis, 'sm_config.vhd-parent') + const vdisWithUnknownVhdParent = { __proto__: null } + const cache = { __proto__: null } forEach(vdis, vdi => { if (vdi.managed && !vdi.is_a_snapshot) { const { uuid } = vdi - const length = this._getUnhealthyVdiChainLength(uuid, children, cache) - if (length !== 0) { - unhealthyVdis[uuid] = length + const { unhealthyLength, missingParent } = this._getVdiChainsInfo(uuid, children, cache) + + if (unhealthyLength !== 0) { + unhealthyVdis[uuid] = unhealthyLength + } + if (missingParent !== undefined) { + vdisWithUnknownVhdParent[uuid] = missingParent } } }) - return unhealthyVdis + + return { + vdisWithUnknownVhdParent, + unhealthyVdis, + } }, // This function helps to reattach a forgotten NFS/iSCSI/HBA SR diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index 92233f37e2e..0ebc52c0e8d 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -1450,7 +1450,9 @@ const messages = { alarmObject: 'Issue on', alarmPool: 'Pool', spaceLeftTooltip: '{used}% used ({free} left)', + unhealthyVdis: 'Unhealthy VDIs', vdisToCoalesce: 'VDIs to coalesce', + vdisWithInvalidVhdParent: 'VDIs with invalid parent VHD', srVdisToCoalesceWarning: 'This SR has more than {limitVdis, number} VDIs to coalesce', // ----- New VM ----- diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index a88153c5bfc..ea329602133 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -544,7 +544,7 @@ export const createSrUnhealthyVdiChainsLengthSubscription = sr => { sr = resolveId(sr) let subscription = unhealthyVdiChainsLengthSubscriptionsBySr[sr] if (subscription === undefined) { - subscription = createSubscription(() => _call('sr.getUnhealthyVdiChainsLength', { sr })) + subscription = createSubscription(() => _call('sr.getVdiChainsInfo', { sr })) unhealthyVdiChainsLengthSubscriptionsBySr[sr] = subscription } return subscription diff --git a/packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js b/packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js index 3346143b852..15923041e86 100644 --- a/packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js +++ b/packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js @@ -9,16 +9,16 @@ 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 { forEach, isEmpty, map, size } from 'lodash' import { Sr, Vdi } from 'render-xo-item' import { subscribeSrsUnhealthyVdiChainsLength, VDIS_TO_COALESCE_LIMIT } from 'xo' const COLUMNS = [ { - itemRenderer: (srId, { unhealthyVdiChainsLengthBySr }) => ( + itemRenderer: (srId, { vdisHealthBySr }) => (
{' '} - {size(unhealthyVdiChainsLengthBySr[srId]) >= VDIS_TO_COALESCE_LIMIT && ( + {size(vdisHealthBySr[srId].unhealthyVdis) >= VDIS_TO_COALESCE_LIMIT && ( @@ -31,15 +31,15 @@ const COLUMNS = [ sortCriteria: 'name_label', }, { - itemRenderer: (srId, { unhealthyVdiChainsLengthBySr }) => ( + itemRenderer: (srId, { vdisHealthBySr }) => (
- {map(unhealthyVdiChainsLengthBySr[srId], (chainLength, vdiId) => ( + {map(vdisHealthBySr[srId].unhealthyVdis, (unhealthyVdiLength, vdiId) => ( - {_('length', { length: chainLength })} + {_('length', { length: unhealthyVdiLength })} ))} @@ -47,33 +47,51 @@ const COLUMNS = [ ), name: _('vdisToCoalesce'), }, + { + itemRenderer: (srId, { vdisHealthBySr }) => ( +
+ {Object.keys(vdisHealthBySr[srId].vdisWithUnknownVhdParent).map(vdiId => ( + + ))} +
+ ), + name: _('vdisWithInvalidVhdParent'), + }, ] const UnhealthyVdis = decorate([ addSubscriptions({ - unhealthyVdiChainsLengthBySr: subscribeSrsUnhealthyVdiChainsLength, + vdisHealthBySr: subscribeSrsUnhealthyVdiChainsLength, }), provideState({ computed: { - srIds: (state, { unhealthyVdiChainsLengthBySr = {} }) => Object.keys(unhealthyVdiChainsLengthBySr), + srIds: (_, { vdisHealthBySr = {} }) => { + const srIds = [] + forEach(vdisHealthBySr, ({ unhealthyVdis, vdisWithUnknownVhdParent }, srId) => { + if (!isEmpty(unhealthyVdis) || vdisWithUnknownVhdParent.length > 0) { + srIds.push(srId) + } + }) + return srIds + }, }, }), injectState, - ({ state: { srIds }, unhealthyVdiChainsLengthBySr }) => ( + ({ state: { srIds }, vdisHealthBySr }) => ( - {_('vdisToCoalesce')} + {_('unhealthyVdis')} diff --git a/packages/xo-web/src/xo-app/sr/tab-advanced.js b/packages/xo-web/src/xo-app/sr/tab-advanced.js index aac1f5e7dd8..e253ea3d387 100644 --- a/packages/xo-web/src/xo-app/sr/tab-advanced.js +++ b/packages/xo-web/src/xo-app/sr/tab-advanced.js @@ -44,12 +44,12 @@ const UnhealthyVdiChains = flowRight( connectStore(() => ({ vdis: createGetObjectsOfType('VDI').pick(createSelector((_, props) => props.chains, keys)), })) -)(({ chains, vdis }) => +)(({ chains: { unhealthyVdis } = {}, vdis }) => isEmpty(vdis) ? null : (

-

{_('srUnhealthyVdiTitle', { total: sum(values(chains)) })}

- +

{_('srUnhealthyVdiTitle', { total: sum(values(unhealthyVdis)) })}

+
) )