Skip to content

Commit

Permalink
fix(xo-server/rest-api): remove alarms info from the dashboard and me…
Browse files Browse the repository at this point in the history
…ssages endpoint (#7959)
  • Loading branch information
MathieuRA authored Sep 25, 2024
1 parent d6bba81 commit 971a561
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [Dashboard] Display backup issues data (PR [#7974](https://github.com/vatesfr/xen-orchestra/pull/7974))
- [REST API] Add S3 backup repository and VMs protection information in the `/rest/v0/dashboard` endpoint (PRs [#7978](https://github.com/vatesfr/xen-orchestra/pull/7978), [#7964](https://github.com/vatesfr/xen-orchestra/pull/7964))
- [Backups] Display more informations in the _Notes_ column of the backup page (PR [#7977](https://github.com/vatesfr/xen-orchestra/pull/7977))
- [REST API] Add `/alarms` endpoint and remove alarms from the `/dashboard` and `/messages` endpoints (PR [#7959](https://github.com/vatesfr/xen-orchestra/pull/7959))

### Bug fixes

Expand Down
4 changes: 4 additions & 0 deletions packages/xo-server/src/utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,7 @@ export const isReplicaVm = vm => 'start' in vm.blockedOperations && vm.other['xo

// -------------------------------------------------------------------
export const vmContainsNoBakTag = vm => vm.tags.some(t => t.split('=', 1)[0] === 'xo:no-bak')

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

export const isAlarm = alarm => alarm.type === 'message' && alarm.name === 'ALARM'
136 changes: 97 additions & 39 deletions packages/xo-server/src/xo-mixins/rest-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import * as CM from 'complex-matcher'
import { VDI_FORMAT_RAW, VDI_FORMAT_VHD } from '@xen-orchestra/xapi'
import { parse } from 'xo-remote-parser'

import { getUserPublicProperties, isReplicaVm, isSrWritable, vmContainsNoBakTag } from '../utils.mjs'
import { getUserPublicProperties, isAlarm, isReplicaVm, isSrWritable, vmContainsNoBakTag } from '../utils.mjs'
import { compileXoJsonSchema } from './_xoJsonSchema.mjs'
import { createPredicate } from 'value-matcher'

// E.g: 'value: 0.6\nconfig:\n<variable>\n<name value="cpu_usage"/>\n<alarm_trigger_level value="0.4"/>\n<alarm_trigger_period value ="60"/>\n</variable>';
const ALARM_BODY_REGEX = /^value:\s*(\d+(?:\.\d+)?)\s*config:\s*<variable>\s*<name value="(.*?)"/
const ALARM_BODY_REGEX = /^value:\s*(Infinity|NaN|-Infinity|\d+(?:\.\d+)?)\s*config:\s*<variable>\s*<name value="(.*?)"/

const { join } = path.posix
const noop = Function.prototype
Expand Down Expand Up @@ -177,7 +177,6 @@ async function _getDashboardStats(app) {
const poolIds = new Set()
const hosts = []
const writableSrs = []
const alarms = []
const nonReplicaVms = []
const vmIdsProtected = new Set()
const vmIdsUnprotected = new Set()
Expand All @@ -197,10 +196,6 @@ async function _getDashboardStats(app) {
}
}

if (obj.type === 'message' && obj.name === 'ALARM') {
alarms.push(obj)
}

if (obj.type === 'VM' && !isReplicaVm(obj)) {
nonReplicaVms.push(obj)
}
Expand Down Expand Up @@ -429,40 +424,11 @@ async function _getDashboardStats(app) {
console.error(error)
}

dashboard.alarms = alarms.reduce((acc, { $object, body, time }) => {
try {
const [, value, name] = body.match(ALARM_BODY_REGEX)

let object
try {
object = app.getObject($object)
} catch (error) {
console.error(error)
object = {
type: 'unknown',
uuid: $object,
}
}

acc.push({
name,
object: {
type: object.type,
uuid: object.uuid,
},
timestamp: time,
value: +value,
})
} catch (error) {
console.error(error)
}

return acc
}, [])
return dashboard
}
const getDashboardStats = throttle(_getDashboardStats, 6e4, { trailing: false, leading: true })

const keepNonAlarmMessages = message => message.type === 'message' && !isAlarm(message)
export default class RestApi {
#api

Expand Down Expand Up @@ -548,7 +514,7 @@ export default class RestApi {
await sendObjects(
Object.values(
app.getObjects({
filter: every(_ => _.type === 'message' && _.$object === id, handleOptionalUserFilter(query.filter)),
filter: every(_ => _.$object === id, keepNonAlarmMessages, handleOptionalUserFilter(query.filter)),
limit: ifDef(query.limit, Number),
})
),
Expand All @@ -557,10 +523,35 @@ export default class RestApi {
'/messages'
)
}

async function alarms(req, res) {
const {
object: { id },
query,
} = req
await sendObjects(
Object.values(
app.getObjects({
filter: every(_ => _.$object === id, isAlarm, handleOptionalUserFilter(query.filter)),
limit: ifDef(query.limit, Number),
})
),
req,
res,
'/alarms'
)
}

for (const type of types) {
const id = type.toLocaleLowerCase() + 's'

collections[id] = { getObject, getObjects, routes: { messages }, isCorrectType: _ => _.type === type, type }
collections[id] = {
getObject,
getObjects,
routes: { messages, alarms },
isCorrectType: _ => _.type === type,
type,
}
}

collections.hosts.routes = {
Expand Down Expand Up @@ -808,6 +799,73 @@ export default class RestApi {
},
}
collections.dashboard = {}
collections.messages = {
getObject(id) {
const message = app.getObject(id, 'message')
if (isAlarm(message)) {
throw noSuchObject(id, 'message')
}

return message
},
getObjects(filter, limit) {
return handleArray(
Object.values(
app.getObjects({
filter: every(keepNonAlarmMessages, filter),
limit,
})
)
)
},
}
collections.alarms = {
getObject(id, req) {
const alarm = app.getObject(id, 'message')
if (!isAlarm(alarm)) {
throw noSuchObject(id, 'alarm')
}

const { $object, body } = alarm
let object = {}
try {
object = app.getObject($object)
} catch (error) {
object = {
type: 'unknown',
uuid: $object,
}
}

const { baseUrl } = req
const objType = object.type.toLowerCase() + 's'
const href = collections[objType] === undefined ? undefined : `${baseUrl}/${objType}/${object.uuid}`
const [, value, name] = body.match(ALARM_BODY_REGEX)

return {
...alarm,
body: {
value, // Keep the value as a string because NaN, Infinity, -Infinity is not valid JSON
name,
},
object: {
type: object.type,
uuid: object.uuid,
href,
},
}
},
getObjects(filter, limit) {
return handleArray(
Object.values(
app.getObjects({
filter: every(isAlarm, filter),
limit,
})
)
)
},
}

// normalize collections
for (const id of Object.keys(collections)) {
Expand Down

0 comments on commit 971a561

Please sign in to comment.