Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 14 commits into from
Feb 25, 2022
4 changes: 4 additions & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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”
Expand Down Expand Up @@ -36,3 +39,4 @@
- @xen-orchestra/proxy patch
- xo-cli minor
- xo-server minor
- xo-web minor
11 changes: 11 additions & 0 deletions packages/xo-server/src/api/sr.mjs
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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)
julien-f marked this conversation as resolved.
Show resolved Hide resolved
})
return unhealthyVdiChainsLengthBySr
}

// -------------------------------------------------------------------

export function getUnhealthyVdiChainsLength({ sr }) {
return this.getXapi(sr).getUnhealthyVdiChainsLength(sr)
}
Expand Down
4 changes: 4 additions & 0 deletions packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}',
Expand Down Expand Up @@ -1381,6 +1382,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}}?',
Expand Down Expand Up @@ -1428,6 +1430,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',
Expand Down
3 changes: 3 additions & 0 deletions packages/xo-web/src/common/xo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

// ===================================================================

Expand Down Expand Up @@ -524,6 +525,8 @@ subscribeVolumeInfo.forceRefresh = (() => {
}
})()

export const subscribeSrsUnhealthyVdiChainsLength = createSubscription(() => _call('sr.getAllUnhealthyVdiChainsLength'))

const unhealthyVdiChainsLengthSubscriptionsBySr = {}
export const createSrUnhealthyVdiChainsLengthSubscription = sr => {
sr = resolveId(sr)
Expand Down
3 changes: 3 additions & 0 deletions packages/xo-web/src/xo-app/dashboard/health/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
createSort,
} from 'selectors'

import UnhealthyVdis from './unhealthyVdis'

const SrColContainer = connectStore(() => ({
container: createGetObject(),
}))(
Expand Down Expand Up @@ -772,6 +774,7 @@ export default class Health extends Component {
</Col>
</Row>
)}
{props.areObjectsFetched && <UnhealthyVdis />}
<Row>
<Col>
<Card>
Expand Down
87 changes: 87 additions & 0 deletions packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js
Original file line number Diff line number Diff line change
@@ -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
21 changes: 20 additions & 1 deletion packages/xo-web/src/xo-app/menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
subscribeProxies,
subscribeProxiesApplianceUpdaterState,
subscribeResourceSets,
subscribeSrsUnhealthyVdiChainsLength,
VDIS_TO_COALESCE_LIMIT,
} from 'xo'
import {
createFilter,
Expand All @@ -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'

Expand Down Expand Up @@ -67,6 +69,7 @@ const returnTrue = () => true
cb(map(proxies, 'id').sort())
}),
resourceSets: subscribeResourceSets,
unhealthyVdiChainsLength: subscribeSrsUnhealthyVdiChainsLength,
})
@injectState
export default class Menu extends Component {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -211,6 +220,14 @@ export default class Menu extends Component {
</Tooltip>
) : null

const unhealthyVdisWarning = this._hasUnhealthyVdis() ? (
<Tooltip content={_('homeUnhealthyVdis')}>
<span className='text-warning'>
<Icon icon='alarm' />
</span>
</Tooltip>
) : null

/* eslint-disable object-property-newline */
const items = [
{
Expand Down Expand Up @@ -247,6 +264,7 @@ export default class Menu extends Component {
to: '/dashboard/overview',
icon: 'menu-dashboard',
label: 'dashboardPage',
extra: [unhealthyVdisWarning],
subMenu: [
{
to: '/dashboard/overview',
Expand All @@ -267,6 +285,7 @@ export default class Menu extends Component {
to: '/dashboard/health',
icon: 'menu-dashboard-health',
label: 'overviewHealthDashboardPage',
extra: [unhealthyVdisWarning],
},
],
},
Expand Down