feat(xo-server/api/sr, xo-web/dashboard/health): list coalescing VDIs (#6120)

See zammad#5224
This commit is contained in:
Mathieu 2022-02-25 10:26:47 +01:00 committed by GitHub
parent 6e6886a6ba
commit 0975863d98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 131 additions and 1 deletions

View File

@ -7,6 +7,9 @@
> Users must be able to say: “Nice enhancement, I'm eager to test it” > 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 ### Bug fixes
> Users must be able to say: “I had this issue, happy to know it's fixed” > Users must be able to say: “I had this issue, happy to know it's fixed”

View File

@ -1,4 +1,5 @@
import asyncMapSettled from '@xen-orchestra/async-map/legacy.js' import asyncMapSettled from '@xen-orchestra/async-map/legacy.js'
import filter from 'lodash/filter.js'
import some from 'lodash/some.js' import some from 'lodash/some.js'
import ensureArray from '../_ensureArray.mjs' 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 }) { export function getUnhealthyVdiChainsLength({ sr }) {
return this.getXapi(sr).getUnhealthyVdiChainsLength(sr) return this.getXapi(sr).getUnhealthyVdiChainsLength(sr)
} }

View File

@ -275,6 +275,7 @@ const messages = {
homeMissingPatches: 'Missing patches', homeMissingPatches: 'Missing patches',
homePoolMaster: 'Master:', homePoolMaster: 'Master:',
homeResourceSet: 'Resource set: {resourceSet}', homeResourceSet: 'Resource set: {resourceSet}',
homeSrVdisToCoalesce: 'Some VDIs need to be coalesced',
highAvailability: 'High Availability', highAvailability: 'High Availability',
powerState: 'Power state', powerState: 'Power state',
srSharedType: 'Shared {type}', srSharedType: 'Shared {type}',
@ -1385,6 +1386,7 @@ const messages = {
metricsLoading: 'Loading…', metricsLoading: 'Loading…',
// ----- Health ----- // ----- Health -----
length: 'Length: {length}',
deleteBackups: 'Delete backup{nBackups, plural, one {} other {s}}', deleteBackups: 'Delete backup{nBackups, plural, one {} other {s}}',
deleteBackupsMessage: deleteBackupsMessage:
'Are you sure you want to delete {nBackups, number} backup{nBackups, plural, one {} other {s}}?', 'Are you sure you want to delete {nBackups, number} backup{nBackups, plural, one {} other {s}}?',
@ -1432,6 +1434,8 @@ const messages = {
alarmObject: 'Issue on', alarmObject: 'Issue on',
alarmPool: 'Pool', alarmPool: 'Pool',
spaceLeftTooltip: '{used}% used ({free} left)', spaceLeftTooltip: '{used}% used ({free} left)',
vdisToCoalesce: 'VDIs to coalesce',
srVdisToCoalesceWarning: 'This SR has more than {limitVdis, number} VDIs to coalesce',
// ----- New VM ----- // ----- New VM -----
createVmModalTitle: 'Create VM', createVmModalTitle: 'Create VM',

View File

@ -40,6 +40,7 @@ import parseNdJson from './_parseNdJson'
// =================================================================== // ===================================================================
export const ITEMS_PER_PAGE_OPTIONS = [10, 20, 50, 100] 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 = {} const unhealthyVdiChainsLengthSubscriptionsBySr = {}
export const createSrUnhealthyVdiChainsLengthSubscription = sr => { export const createSrUnhealthyVdiChainsLengthSubscription = sr => {
sr = resolveId(sr) sr = resolveId(sr)

View File

@ -36,6 +36,8 @@ import {
createSort, createSort,
} from 'selectors' } from 'selectors'
import UnhealthyVdis from './unhealthyVdis'
const SrColContainer = connectStore(() => ({ const SrColContainer = connectStore(() => ({
container: createGetObject(), container: createGetObject(),
}))( }))(
@ -772,6 +774,7 @@ export default class Health extends Component {
</Col> </Col>
</Row> </Row>
)} )}
{props.areObjectsFetched && <UnhealthyVdis />}
<Row> <Row>
<Col> <Col>
<Card> <Card>

View File

@ -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 }) => (
<div>
<Sr id={srId} link />{' '}
{size(unhealthyVdiChainsLengthBySr[srId]) >= VDIS_TO_COALESCE_LIMIT && (
<Tooltip content={_('srVdisToCoalesceWarning', { limitVdis: VDIS_TO_COALESCE_LIMIT })}>
<span className='text-warning'>
<Icon icon='alarm' />
</span>
</Tooltip>
)}
</div>
),
name: _('sr'),
sortCriteria: 'name_label',
},
{
itemRenderer: (srId, { unhealthyVdiChainsLengthBySr }) => (
<div>
{map(unhealthyVdiChainsLengthBySr[srId], (chainLength, vdiId) => (
<SingleLineRow key={vdiId}>
<Col>
<Vdi id={vdiId} />
</Col>
<Col>
<span>{_('length', { length: chainLength })}</span>
</Col>
</SingleLineRow>
))}
</div>
),
name: _('vdisToCoalesce'),
},
]
const UnhealthyVdis = decorate([
addSubscriptions({
unhealthyVdiChainsLengthBySr: subscribeSrsUnhealthyVdiChainsLength,
}),
provideState({
computed: {
srIds: (state, { unhealthyVdiChainsLengthBySr = {} }) => Object.keys(unhealthyVdiChainsLengthBySr),
},
}),
injectState,
({ state: { srIds }, unhealthyVdiChainsLengthBySr }) => (
<Row>
<Col>
<Card>
<CardHeader>
<Icon icon='disk' /> {_('vdisToCoalesce')}
</CardHeader>
<CardBlock>
<Row>
<Col>
<SortedTable
data-unhealthyVdiChainsLengthBySr={unhealthyVdiChainsLengthBySr}
collection={srIds}
columns={COLUMNS}
stateUrlParam='s_vdis_to_coalesce'
/>
</Col>
</Row>
</CardBlock>
</Card>
</Col>
</Row>
),
])
export default UnhealthyVdis

View File

@ -18,6 +18,8 @@ import {
subscribeProxies, subscribeProxies,
subscribeProxiesApplianceUpdaterState, subscribeProxiesApplianceUpdaterState,
subscribeResourceSets, subscribeResourceSets,
subscribeSrsUnhealthyVdiChainsLength,
VDIS_TO_COALESCE_LIMIT,
} from 'xo' } from 'xo'
import { import {
createFilter, createFilter,
@ -29,7 +31,7 @@ import {
getXoaState, getXoaState,
isAdmin, isAdmin,
} from 'selectors' } 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' import styles from './index.css'
@ -67,6 +69,7 @@ const returnTrue = () => true
cb(map(proxies, 'id').sort()) cb(map(proxies, 'id').sort())
}), }),
resourceSets: subscribeResourceSets, resourceSets: subscribeResourceSets,
unhealthyVdiChainsLength: subscribeSrsUnhealthyVdiChainsLength,
}) })
@injectState @injectState
export default class Menu extends Component { export default class Menu extends Component {
@ -135,6 +138,12 @@ export default class Menu extends Component {
missingPatches => some(missingPatches, _ => _) missingPatches => some(missingPatches, _ => _)
) )
_hasUnhealthyVdis = createSelector(
() => this.state.unhealthyVdiChainsLength,
unhealthyVdiChainsLength =>
some(unhealthyVdiChainsLength, vdiChainsLength => size(vdiChainsLength) >= VDIS_TO_COALESCE_LIMIT)
)
_toggleCollapsed = event => { _toggleCollapsed = event => {
event.preventDefault() event.preventDefault()
this._removeListener() this._removeListener()
@ -211,6 +220,14 @@ export default class Menu extends Component {
</Tooltip> </Tooltip>
) : null ) : null
const unhealthyVdisWarning = this._hasUnhealthyVdis() ? (
<Tooltip content={_('homeUnhealthyVdis')}>
<span className='text-warning'>
<Icon icon='alarm' />
</span>
</Tooltip>
) : null
/* eslint-disable object-property-newline */ /* eslint-disable object-property-newline */
const items = [ const items = [
{ {
@ -247,6 +264,7 @@ export default class Menu extends Component {
to: '/dashboard/overview', to: '/dashboard/overview',
icon: 'menu-dashboard', icon: 'menu-dashboard',
label: 'dashboardPage', label: 'dashboardPage',
extra: [unhealthyVdisWarning],
subMenu: [ subMenu: [
{ {
to: '/dashboard/overview', to: '/dashboard/overview',
@ -267,6 +285,7 @@ export default class Menu extends Component {
to: '/dashboard/health', to: '/dashboard/health',
icon: 'menu-dashboard-health', icon: 'menu-dashboard-health',
label: 'overviewHealthDashboardPage', label: 'overviewHealthDashboardPage',
extra: [unhealthyVdisWarning],
}, },
], ],
}, },