feat(xo-server/xo-web/health): detect invalid vhd-parent VDIs (#6356)
This commit is contained in:
parent
cb1223f72e
commit
a9c1239149
@ -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 @@
|
||||
|
||||
<!--packages-start-->
|
||||
|
||||
- xo-server minor
|
||||
- xo-web minor
|
||||
|
||||
<!--packages-end-->
|
||||
|
@ -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,
|
||||
|
@ -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'],
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 -----
|
||||
|
@ -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
|
||||
|
@ -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 }) => (
|
||||
<div>
|
||||
<Sr id={srId} link />{' '}
|
||||
{size(unhealthyVdiChainsLengthBySr[srId]) >= VDIS_TO_COALESCE_LIMIT && (
|
||||
{size(vdisHealthBySr[srId].unhealthyVdis) >= VDIS_TO_COALESCE_LIMIT && (
|
||||
<Tooltip content={_('srVdisToCoalesceWarning', { limitVdis: VDIS_TO_COALESCE_LIMIT })}>
|
||||
<span className='text-warning'>
|
||||
<Icon icon='alarm' />
|
||||
@ -31,15 +31,15 @@ const COLUMNS = [
|
||||
sortCriteria: 'name_label',
|
||||
},
|
||||
{
|
||||
itemRenderer: (srId, { unhealthyVdiChainsLengthBySr }) => (
|
||||
itemRenderer: (srId, { vdisHealthBySr }) => (
|
||||
<div>
|
||||
{map(unhealthyVdiChainsLengthBySr[srId], (chainLength, vdiId) => (
|
||||
{map(vdisHealthBySr[srId].unhealthyVdis, (unhealthyVdiLength, vdiId) => (
|
||||
<SingleLineRow key={vdiId}>
|
||||
<Col>
|
||||
<Vdi id={vdiId} />
|
||||
</Col>
|
||||
<Col>
|
||||
<span>{_('length', { length: chainLength })}</span>
|
||||
<span>{_('length', { length: unhealthyVdiLength })}</span>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
))}
|
||||
@ -47,33 +47,51 @@ const COLUMNS = [
|
||||
),
|
||||
name: _('vdisToCoalesce'),
|
||||
},
|
||||
{
|
||||
itemRenderer: (srId, { vdisHealthBySr }) => (
|
||||
<div>
|
||||
{Object.keys(vdisHealthBySr[srId].vdisWithUnknownVhdParent).map(vdiId => (
|
||||
<Vdi id={vdiId} key={vdiId} />
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
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 }) => (
|
||||
<Row>
|
||||
<Col>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Icon icon='disk' /> {_('vdisToCoalesce')}
|
||||
<Icon icon='disk' /> {_('unhealthyVdis')}
|
||||
</CardHeader>
|
||||
<CardBlock>
|
||||
<Row>
|
||||
<Col>
|
||||
<SortedTable
|
||||
data-unhealthyVdiChainsLengthBySr={unhealthyVdiChainsLengthBySr}
|
||||
data-vdisHealthBySr={vdisHealthBySr}
|
||||
collection={srIds}
|
||||
columns={COLUMNS}
|
||||
stateUrlParam='s_vdis_to_coalesce'
|
||||
stateUrlParam='s_unhealthy_vdis'
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -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 : (
|
||||
<div>
|
||||
<hr />
|
||||
<h3>{_('srUnhealthyVdiTitle', { total: sum(values(chains)) })}</h3>
|
||||
<SortedTable collection={vdis} columns={COLUMNS} stateUrlParam='s_unhealthy_vdis' userData={chains} />
|
||||
<h3>{_('srUnhealthyVdiTitle', { total: sum(values(unhealthyVdis)) })}</h3>
|
||||
<SortedTable collection={vdis} columns={COLUMNS} stateUrlParam='s_unhealthy_vdis' userData={unhealthyVdis} />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user