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/xo-web): in host advanced tab, display mdadm status #8190

Merged
merged 9 commits into from
Dec 20, 2024
15 changes: 15 additions & 0 deletions @xen-orchestra/xapi/host.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,21 @@ class Host {

return ipmiSensorsByDataType
}

async getMdadmHealth(ref) {
try {
const result = await this.callAsync('host.call_plugin', ref, 'raid.py', 'check_raid_pool', {})
const parsedResult = JSON.parse(result)

return parsedResult
} catch (error) {
if (error.code === 'XENAPI_MISSING_PLUGIN' || error.code === 'UNKNOWN_XENAPI_PLUGIN_FUNCTION') {
return null
} else {
throw error
}
}
}
}
export default Host

Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

- [REST/Dashboard] Add the number of connected/unreachable/unknown pools in the `/dashboard` endpoint (PR [#8203](https://github.com/vatesfr/xen-orchestra/pull/8203))
- [VM/Advanced] Ability to create/edit/delete XenStore entries (PR [#8174](https://github.com/vatesfr/xen-orchestra/pull/8174))
- [Host/Advanced] In host's advanced tab, display MDADM health information (PR [#8190](https://github.com/vatesfr/xen-orchestra/pull/8190))

### Bug fixes

Expand All @@ -35,6 +36,7 @@
<!--packages-start-->

- @xen-orchestra/web-core minor
- @xen-orchestra/xapi minor
- xo-server minor
- xo-web minor

Expand Down
17 changes: 17 additions & 0 deletions packages/xo-server/src/api/host.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { X509Certificate } from 'node:crypto'
import backupGuard from './_backupGuard.mjs'
import { asyncEach } from '@vates/async-each'

import { debounceWithKey } from '../_pDebounceWithKey.mjs'

const CERT_PUBKEY_MIN_SIZE = 2048
const IPMI_CACHE_TTL = 6e4
const IPMI_CACHE = new TTLCache({
Expand Down Expand Up @@ -624,6 +626,21 @@ getSmartctlInformation.resolve = {
host: ['id', 'host', 'view'],
}

function _getMdadmHealth({ host }) {
return this.getXapi(host).host_getMdadmHealth(host._xapiRef)
}
export const getMdadmHealth = debounceWithKey(_getMdadmHealth, 6e5, ({ host }) => host.id)

getMdadmHealth.description = 'retrieve the mdadm RAID health information'

getMdadmHealth.params = {
id: { type: 'string' },
}

getMdadmHealth.resolve = {
host: ['id', 'host', 'view'],
}

export async function getBlockdevices({ host }) {
const xapi = this.getXapi(host)
if (host.productBrand !== 'XCP-ng') {
Expand Down
5 changes: 5 additions & 0 deletions packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,8 @@ const messages = {
hostIommu: 'IOMMU',
hostNoCertificateInstalled: 'No certificates installed on this host',
'onlyAvailableXcp8.3OrHigher': 'Only available for XCP-ng 8.3.0 or higher',
installRaidPlugin: 'To display RAID info, install raid.py plugin',
noRaidInformationAvailable: 'No RAID information available',
pciDevices: 'PCI Devices',
pciId: 'PCI ID',
pcisEnable: 'PCI{nPcis, plural, one {} other {s}} enable',
Expand All @@ -1148,6 +1150,9 @@ const messages = {
supplementalPackInstallSuccessTitle: 'Installation success',
supplementalPackInstallSuccessMessage: 'Supplemental pack successfully installed.',
systemDisksHealth: 'System disks health',
raidHealthy: 'All mdadm RAID are healthy ✅',
raidStateWarning: 'RAID state needs your attention: {state}',
raidStatus: 'RAID Status',
uniqueHostIscsiIqnInfo: 'The iSCSI IQN must be unique. ',
vendorId: 'Vendor ID',
// ----- Host net tabs -----
Expand Down
11 changes: 11 additions & 0 deletions packages/xo-web/src/common/xo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,17 @@ export const subscribeIpmiSensors = host => {
return subscribeHostsIpmiSensors[hostId]
}

const subscribeHostsMdadmHealth = {}
export const subscribeMdadmHealth = host => {
const hostId = resolveId(host)

if (subscribeHostsMdadmHealth[hostId] === undefined) {
subscribeHostsMdadmHealth[hostId] = createSubscription(() => _call('host.getMdadmHealth', { id: hostId }))
}

return subscribeHostsMdadmHealth[hostId]
}

export const getHostBiosInfo = host => _call('host.getBiosInfo', { id: resolveId(host) })

const subscribeVmSecurebootReadiness = {}
Expand Down
21 changes: 18 additions & 3 deletions packages/xo-web/src/xo-app/home/host-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
startHost,
stopHost,
subscribeHvSupportedVersions,
subscribeMdadmHealth,
} from 'xo'
import { addSubscriptions, connectStore, formatSizeShort, hasLicenseRestrictions, osFamily } from 'utils'
import {
Expand All @@ -40,9 +41,10 @@ import BulkIcons from '../../common/bulk-icons'
import { LICENSE_WARNING_BODY } from '../host/license-warning'
import { getXoaPlan, SOURCES } from '../../common/xoa-plans'

@addSubscriptions({
@addSubscriptions(props => ({
hvSupportedVersions: subscribeHvSupportedVersions,
})
mdadmHealth: subscribeMdadmHealth(props.item),
}))
@connectStore(() => ({
container: createGetObject((_, props) => props.item.$pool),
isPubKeyTooShort: createSelector(
Expand Down Expand Up @@ -144,14 +146,16 @@ export default class HostItem extends Component {
this._getAreHostsVersionsEqual,
() => this.props.state.hostsByPoolId[this.props.item.$pool],
() => this.state.isPubKeyTooShort,
() => this.props.mdadmHealth,
(
needsRestart,
host,
isMaintained,
isHostTimeConsistentWithXoaTime,
areHostsVersionsEqual,
poolHosts,
isPubKeyTooShort
isPubKeyTooShort,
mdadmHealth
) => {
const alerts = []

Expand Down Expand Up @@ -262,6 +266,17 @@ export default class HostItem extends Component {
})
}

if (mdadmHealth?.raid?.State !== undefined && !['clean', 'active'].includes(mdadmHealth.raid.State)) {
alerts.push({
level: 'danger',
render: (
<span>
<Icon icon='alarm' className='text-danger' /> {_('raidStateWarning', { state: mdadmHealth.raid.State })}
</span>
),
})
}

return alerts
}
)
Expand Down
22 changes: 22 additions & 0 deletions packages/xo-web/src/xo-app/host/tab-advanced.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
setHostsMultipathing,
setRemoteSyslogHost,
setSchedulerGranularity,
subscribeMdadmHealth,
subscribeSchedulerGranularity,
toggleMaintenanceMode,
} from 'xo'
Expand Down Expand Up @@ -248,6 +249,7 @@ MultipathableSrs.propTypes = {

@addSubscriptions(props => ({
schedGran: cb => subscribeSchedulerGranularity(props.host.id, cb),
mdadmHealth: subscribeMdadmHealth(props.host),
}))
@connectStore(() => {
const getControlDomain = createGetObject((_, { host }) => host.controlDomain)
Expand Down Expand Up @@ -347,6 +349,22 @@ export default class extends Component {
}
)

displayMdadmStatus = createSelector(
() => this.props.mdadmHealth,
mdadmHealth => {
if (mdadmHealth == null) {
return _('installRaidPlugin')
}

const raidState = mdadmHealth.raid?.State
if (raidState === undefined) {
return _('noRaidInformationAvailable')
}

return ['clean', 'active'].includes(raidState) ? _('raidHealthy') : _('raidStateWarning', { state: raidState })
}
)

_setSchedulerGranularity = value => setSchedulerGranularity(this.props.host.id, value)

_setHostIscsiIqn = iscsiIqn =>
Expand Down Expand Up @@ -688,6 +706,10 @@ export default class extends Component {
))}
</td>
</tr>
<tr>
<th>{_('raidStatus')}</th>
<td>{this.displayMdadmStatus()}</td>
</tr>
</tbody>
</table>
<br />
Expand Down
Loading