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/host): display 2CRSi informations in general tab #7838

Merged
merged 12 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions @xen-orchestra/xapi/host.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import { defer } from 'golike-defer'
import { incorrectState, operationFailed } from 'xo-common/api-errors.js'

julien-f marked this conversation as resolved.
Show resolved Hide resolved
import { getCurrentVmUuid } from './_XenStore.mjs'
import {
addIpmiSensorDataType,
containsDigit,
IPMI_SENSOR_DATA_TYPE,
IPMI_SENSOR_REGEX_BY_DATA_TYPE_BY_SUPPORTED_PRODUCT_NAME,
isRelevantIpmiSensor,
} from './host/_ipmi.mjs'

const waitAgentRestart = (xapi, hostRef, prevAgentStartTime) =>
new Promise(resolve => {
Expand Down Expand Up @@ -120,6 +127,41 @@ class Host {
await this.callAsync('host.reboot', ref)
await waitAgentRestart(this, ref, agentStartTime)
}

async getIpmiSensors(ref, { cache } = {}) {
const productName = (await this.call(cache, 'host.get_bios_strings', ref))['system-product-name']?.toLowerCase()

if (IPMI_SENSOR_REGEX_BY_DATA_TYPE_BY_SUPPORTED_PRODUCT_NAME[productName] === undefined) {
return {}
}

const callSensorPlugin = fn => this.call(cache, 'host.call_plugin', ref, '2crsi-sensors.py', fn, {})
julien-f marked this conversation as resolved.
Show resolved Hide resolved
// https://github.com/AtaxyaNetwork/xcp-ng-xapi-plugins/tree/ipmi-sensors?tab=readme-ov-file#ipmi-sensors-parser
const [stringifiedIpmiSensors, ip] = await Promise.all([callSensorPlugin('get_info'), callSensorPlugin('get_ip')])
const ipmiSensors = JSON.parse(stringifiedIpmiSensors)

const ipmiSensorsByDataType = {}
for (const ipmiSensor of ipmiSensors) {
if (!isRelevantIpmiSensor(ipmiSensor, productName)) {
continue
}

addIpmiSensorDataType(ipmiSensor, productName)
const dataType = ipmiSensor.dataType

if (ipmiSensorsByDataType[dataType] === undefined) {
ipmiSensorsByDataType[dataType] = containsDigit(ipmiSensor.Name) ? [] : ipmiSensor
}

if (Array.isArray(ipmiSensorsByDataType[ipmiSensor.dataType])) {
ipmiSensorsByDataType[dataType].push(ipmiSensor)
}
fbeauchamp marked this conversation as resolved.
Show resolved Hide resolved
}

ipmiSensorsByDataType[IPMI_SENSOR_DATA_TYPE.generalInfo] = { ip }

return ipmiSensorsByDataType
}
}
export default Host

Expand Down
54 changes: 54 additions & 0 deletions @xen-orchestra/xapi/host/_ipmi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export const IPMI_SENSOR_DATA_TYPE = {
totalPower: 'totalPower',
outletTemp: 'outletTemp',
bmcStatus: 'bmcStatus',
inletTemp: 'inletTemp',
cpuTemp: 'cpuTemp',
fanStatus: 'fanStatus',
fanSpeed: 'fanSpeed',
psuStatus: 'psuStatus',
generalInfo: 'generalInfo',
unknown: 'unknown',
}

export const IPMI_SENSOR_REGEX_BY_DATA_TYPE_BY_SUPPORTED_PRODUCT_NAME = {
'mona_1.44gg': {
[IPMI_SENSOR_DATA_TYPE.totalPower]: /^total_power$/i,
[IPMI_SENSOR_DATA_TYPE.outletTemp]: /^outlet_temp$/i,
[IPMI_SENSOR_DATA_TYPE.bmcStatus]: /^bmc_status$/i,
[IPMI_SENSOR_DATA_TYPE.inletTemp]: /^psu_inlet_temp$/i,
[IPMI_SENSOR_DATA_TYPE.cpuTemp]: /^cpu[0-9]+_temp$/i,
[IPMI_SENSOR_DATA_TYPE.fanStatus]: /^fan[0-9]+_status$/i,
[IPMI_SENSOR_DATA_TYPE.fanSpeed]: /^fan[0-9]+_r_speed$/i,
[IPMI_SENSOR_DATA_TYPE.psuStatus]: /^psu[0-9]+_status$/i,
},
}
const IPMI_SENSOR_REGEX_BY_PRODUCT_NAME = Object.keys(IPMI_SENSOR_REGEX_BY_DATA_TYPE_BY_SUPPORTED_PRODUCT_NAME).reduce(
(acc, productName) => {
const regexes = Object.values(IPMI_SENSOR_REGEX_BY_DATA_TYPE_BY_SUPPORTED_PRODUCT_NAME[productName])
const combinedRegex = new RegExp(regexes.map(regex => regex.source).join('|'), 'i')
acc[productName] = combinedRegex
return acc
},
{}
)

export const isRelevantIpmiSensor = (data, productName) =>
IPMI_SENSOR_REGEX_BY_PRODUCT_NAME[productName].test(data.Name)

export const containsDigit = str => /\d/.test(str)

export const addIpmiSensorDataType = (data, productName) => {
const name = data.Name
const ipmiRegexByDataType = IPMI_SENSOR_REGEX_BY_DATA_TYPE_BY_SUPPORTED_PRODUCT_NAME[productName]

for (const dataType in ipmiRegexByDataType) {
const regex = ipmiRegexByDataType[dataType]
if (regex.test(name)) {
data.dataType = dataType
return
}
}

data.dataType = IPMI_SENSOR_DATA_TYPE.unknown
}
2 changes: 2 additions & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
> Users must be able to say: “Nice enhancement, I'm eager to test it”

- [VM/Advanced] Display an accurate secure boot status and allow user to propagate certificates from pool to VM [#7495](https://github.com/vatesfr/xen-orchestra/issues/7495) (PR [#7751](https://github.com/vatesfr/xen-orchestra/pull/7751))
- [Host/General] Display additional hardware data for 2CRSi server [#7816](https://github.com/vatesfr/xen-orchestra/issues/7816) (PR [#7838](https://github.com/vatesfr/xen-orchestra/pull/7838))

### Bug fixes

Expand All @@ -33,6 +34,7 @@

<!--packages-start-->

- @xen-orchestra/xapi minor
- xo-server minor
- xo-server-audit 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
@@ -1,3 +1,4 @@
import TTLCache from '@isaacs/ttlcache'
import semver from 'semver'
import { createLogger } from '@xen-orchestra/log'
import assert from 'assert'
Expand All @@ -6,6 +7,12 @@ import { incorrectState } from 'xo-common/api-errors.js'

import backupGuard from './_backupGuard.mjs'

const IPMI_CACHE_TTL = 6e4
const IPMI_CACHE = new TTLCache({
ttl: IPMI_CACHE_TTL,
max: 1000,
})

const log = createLogger('xo:api:host')

// ===================================================================
Expand Down Expand Up @@ -592,3 +599,13 @@ getBlockdevices.params = {
getBlockdevices.resolve = {
host: ['id', 'host', 'administrate'],
}

export function getIpmiSensors({ host }) {
return this.getXapi(host).host_getIpmiSensors(host._xapiRef, { cache: IPMI_CACHE })
}
getIpmiSensors.params = {
id: { type: 'string' },
}
getIpmiSensors.resolve = {
host: ['id', 'host', 'administrate'],
}
10 changes: 10 additions & 0 deletions packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -2858,6 +2858,16 @@ const messages = {
secondsFormat: '{seconds, plural, one {# second} other {# seconds}}',
durationFormat:
'{days, plural, =0 {} one {# day } other {# days }}{hours, plural, =0 {} one {# hour } other {# hours }}{minutes, plural, =0 {} one {# minute } other {# minutes }}{seconds, plural, =0 {} one {# second} other {# seconds}}',

// ----- IPMI -----
highestCpuTemperature: '{n, number}x CPU{n, plural, one {} other {s}} (highest: {degres})',
highestFanSpeed: '{n, number}x fan{n, plural, one {} other {s}} (highest: {speed})',
inletTemperature: 'Inlet temperature',
ipmi: 'IPMI',
nFanStatus: '{n, number}x fan{n, plural, one {} other {s}} status: {status}',
nPsuStatus: '{n, number}x PSU{n, plural, one {} other {s}} status: {status}',
outletTemperature: 'Outlet temperature',
totalPower: 'Total power',
}
forEach(messages, function (message, id) {
if (typeof message === 'string') {
Expand Down
27 changes: 24 additions & 3 deletions packages/xo-web/src/common/xo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,29 @@ subscribeXostorInterfaces.forceRefresh = sr => {
subscription?.forceRefresh()
}


const subscribeHostsIpmiSensors = {}
export const subscribeIpmiSensors = host => {
const _isAdmin = isAdmin(store.getState())
const hostId = resolveId(host)

if (subscribeHostsIpmiSensors[hostId] === undefined) {
subscribeHostsIpmiSensors[hostId] = createSubscription(
async () =>
_isAdmin
? await _call('host.getIpmiSensors', {
id: hostId,
})
: undefined,
{
polling: 6e4,
}
)
}

return subscribeHostsIpmiSensors[hostId]
}

const subscribeVmSecurebootReadiness = {}
export const subscribeSecurebootReadiness = id => {
const vmId = resolveId(id)
Expand Down Expand Up @@ -2455,9 +2478,7 @@ export const migrateVdi = (vdi, sr, resourceSet) =>
sr_id: resolveId(sr),
})

export const setCbt = (vdi, cbt) =>
_call('vdi.set', { id: resolveId(vdi), cbt })

export const setCbt = (vdi, cbt) => _call('vdi.set', { id: resolveId(vdi), cbt })

// VBD ---------------------------------------------------------------

Expand Down
34 changes: 34 additions & 0 deletions packages/xo-web/src/icons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,40 @@
@extend .fa-share;
}

// IPMI
&-cpu-temperature {
@extend .fa;
@extend .fa-thermometer-full;
}
&-fan-speed {
@extend .fa;
@extend .fa-circle-o-notch;
}
&-fan-status {
@extend .fa;
@extend .fa-medkit;
}
&-inlet {
@extend .fa;
@extend .fa-sign-in;
}
&-ipmi {
@extend .fa;
@extend .fa-wrench;
}
&-outlet {
@extend .fa;
@extend .fa-sign-out;
}
&-psu {
@extend .fa;
@extend .fa-plug;
}
&-total-power {
@extend .fa;
@extend .fa-tachometer;
}

// VM
&-vm {
// States
Expand Down
Loading
Loading