diff --git a/@xen-orchestra/xapi/host.mjs b/@xen-orchestra/xapi/host.mjs
index acadf8d2f2f..2f2344adddc 100644
--- a/@xen-orchestra/xapi/host.mjs
+++ b/@xen-orchestra/xapi/host.mjs
@@ -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
diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md
index ea3273d66d4..e00e5f5e410 100644
--- a/CHANGELOG.unreleased.md
+++ b/CHANGELOG.unreleased.md
@@ -13,6 +13,7 @@
- [Dashboard/Health] Add `BOND_STATUS_CHANGED` and `MULTIPATH_PERIODIC_ALERT` in alarms list (PR [#8199](https://github.com/vatesfr/xen-orchestra/pull/8199))
- [Signin] Start transitioning towards XO 6 Design System (PR [#8209](https://github.com/vatesfr/xen-orchestra/pull/8209))
+- [Host/Advanced] In host's advanced tab, display MDADM health information (PR [#8190](https://github.com/vatesfr/xen-orchestra/pull/8190))
### Bug fixes
diff --git a/packages/xo-server/src/api/host.mjs b/packages/xo-server/src/api/host.mjs
index 017afad0c7d..a2cfc387d49 100644
--- a/packages/xo-server/src/api/host.mjs
+++ b/packages/xo-server/src/api/host.mjs
@@ -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({
@@ -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') {
diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js
index 711f888d6bf..ae542d1ea12 100644
--- a/packages/xo-web/src/common/intl/messages.js
+++ b/packages/xo-web/src/common/intl/messages.js
@@ -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',
@@ -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 -----
diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js
index f221a4d0bc5..a09340979c9 100644
--- a/packages/xo-web/src/common/xo/index.js
+++ b/packages/xo-web/src/common/xo/index.js
@@ -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 = {}
diff --git a/packages/xo-web/src/xo-app/home/host-item.js b/packages/xo-web/src/xo-app/home/host-item.js
index c4c0f8f2889..df7ed5eded1 100644
--- a/packages/xo-web/src/xo-app/home/host-item.js
+++ b/packages/xo-web/src/xo-app/home/host-item.js
@@ -21,6 +21,7 @@ import {
startHost,
stopHost,
subscribeHvSupportedVersions,
+ subscribeMdadmHealth,
} from 'xo'
import { addSubscriptions, connectStore, formatSizeShort, hasLicenseRestrictions, osFamily } from 'utils'
import {
@@ -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(
@@ -144,6 +146,7 @@ export default class HostItem extends Component {
this._getAreHostsVersionsEqual,
() => this.props.state.hostsByPoolId[this.props.item.$pool],
() => this.state.isPubKeyTooShort,
+ () => this.props.mdadmHealth,
(
needsRestart,
host,
@@ -151,7 +154,8 @@ export default class HostItem extends Component {
isHostTimeConsistentWithXoaTime,
areHostsVersionsEqual,
poolHosts,
- isPubKeyTooShort
+ isPubKeyTooShort,
+ mdadmHealth
) => {
const alerts = []
@@ -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: (
+
+