feat(@xen-orchestra/rest-api): expose VM stats (#8359)

This commit is contained in:
Mathieu
2025-03-06 15:06:28 +01:00
committed by GitHub
parent a415c452ef
commit 034d9b5eca
10 changed files with 229 additions and 11 deletions

View File

@@ -636,3 +636,31 @@ export const CERTIFICATE_TYPE = {
} as const
export type CERTIFICATE_TYPE = (typeof CERTIFICATE_TYPE)[keyof typeof CERTIFICATE_TYPE]
// ----- XAPI Stats
type XapiStatsResponse<T> = {
endTimestamp: number
interval: number
stats: T
}
export type XapiStatsGranularity = 'seconds' | 'minutes' | 'hours' | 'days'
export type XapiVmStats = XapiStatsResponse<{
cpus: Record<string, number[]>
iops: {
r: Record<string, number[]>
w: Record<string, number[]>
}
memory: number[]
memoryFree?: number[]
vifs: {
rx: Record<string, number[]>
tx: Record<string, number[]>
}
xvds: {
w: Record<string, number[]>
r: Record<string, number[]>
}
}>

View File

@@ -10,6 +10,7 @@ The REST API is based on the `TSOA` framework and therefore we use decorators a
@Routes('foo')
@Security('*')
@Response(401)
@Tags('vms')
@provide(Foo)
class Foo extends Controller {}
```

View File

@@ -28,6 +28,7 @@ The REST API is based on the `TSOA` framework and therefore we use decorators a
@Routes('foo')
@Security('*')
@Response(401)
@Tags('vms')
@provide(Foo)
class Foo extends Controller {}
```

View File

@@ -102,3 +102,148 @@ export const vm = {
$poolId: 'b7569d99-30f8-178a-7d94-801de3e29b5b',
_xapiRef: 'OpaqueRef:ffdf8863-5331-9394-5c1b-d1db7de20a76',
}
export const vmStatsExample = {
endTimestamp: 1739958540,
interval: 60,
stats: {
cpus: {
'0': [
0.10329, 0.11014, 0.11465, 0.11352000000000001, 0.10727, 0.10522999999999999, 0.14641, 0.10748, 0.12263,
0.12427, 0.10453000000000001, 0.08896699999999999, 0.10916000000000001, 0.088654, 0.10563, 0.11789000000000001,
0.15555, 0.11686, 0.12934, 0.11566, 0.11984999999999998, 0.11495, 0.11615, 0.11151, 0.10779, 0.1222, 0.14765,
0.10556, 0.094178, 0.10196000000000001, 0.11102, 0.10920999999999999, 0.11478000000000001, 0.11789000000000001,
0.11563000000000001, 0.11739000000000001, 0.1362, 0.11498000000000001, 0.11574000000000001, 0.11762999999999998,
0.13917000000000002, 0.11061, 0.13446, 0.14161, 0.12666000000000002, 0.11344, 0.1374, 0.10191, 0.10052,
0.10721000000000001, 0.1026, 0.088527, 0.11909, 0.09958600000000001, 0.11494000000000001, 0.11477, 0.12858,
0.12194, 0.09911099999999999, 0.10887, 0.10215999999999999, 0.10981, 0.12030999999999999, 0.10596,
0.09993999999999999, 0.093929, 0.12344999999999999, 0.11797, 0.10663999999999998, 0.10486, 0.12799, 0.12128,
0.12048, 0.12692, 0.14813, 0.12057999999999999, 0.15755999999999998, 0.1343, 0.10422000000000001, 0.11711,
0.12649, 0.12182, 0.12440000000000001, 0.12294000000000001, 0.11839999999999999, 0.10195, 0.1127,
0.09663300000000001, 0.11427, 0.08722099999999999, 0.098883, 0.10908999999999999, 0.10545000000000002, 0.11312,
0.11949, 0.13102999999999998, 0.13713, 0.13815, 0.14027, 0.11937, 0.13204, 0.11929, 0.12465,
0.11314999999999999, 0.11397, 0.11687, 0.13404, 0.11865, 0.13029000000000002, 0.11449999999999999, 0.11681,
0.17134, 0.31911, 0.18656, 0.21658, 0.23914000000000002, 0.16117, 0.13316999999999998, 0.13163,
],
'1': [
0.29756, 0.30574, 0.29464, 0.30307, 0.30457, 0.29107, 0.33127999999999996, 0.30188, 0.3016, 0.32508, 0.29493,
0.31295, 0.28245000000000003, 0.28689000000000003, 0.29069999999999996, 0.29743, 0.2773, 0.27088, 0.27829,
0.28284, 0.30478, 0.28864, 0.3065, 0.3035, 0.3188, 0.34719, 0.3156, 0.28662000000000004, 0.29383, 0.29981,
0.31473999999999996, 0.29885, 0.27696, 0.28219, 0.27029, 0.29085, 0.2726, 0.28369, 0.26852, 0.27536, 0.27914,
0.28652, 0.29397999999999996, 0.28976, 0.27662, 0.29937, 0.30713, 0.31584999999999996, 0.30047, 0.30318,
0.30081, 0.33138, 0.30329, 0.30674999999999997, 0.30323, 0.30611, 0.30973, 0.30456, 0.29344000000000003,
0.31641, 0.33455, 0.31020000000000003, 0.28782, 0.31499, 0.29937, 0.30573, 0.33871, 0.31035, 0.29512, 0.29625,
0.28441, 0.27936, 0.28203999999999996, 0.26668, 0.27676, 0.28300000000000003, 0.28148, 0.28251, 0.30144,
0.28032, 0.26444, 0.28741, 0.28525, 0.27945000000000003, 0.27417, 0.33799, 0.32702, 0.32241000000000003,
0.30667, 0.32703, 0.31335999999999997, 0.30452999999999997, 0.32867999999999997, 0.29619, 0.28617, 0.27939,
0.28831999999999997, 0.27728, 0.28192999999999996, 0.27025, 0.26878, 0.28253, 0.28008, 0.26922999999999997,
0.29331999999999997, 0.29295, 0.28464, 0.2788, 0.28493, 0.29115, 0.28254999999999997, 0.28454999999999997,
0.27237, 0.28496, 0.28065999999999997, 0.27951, 0.2867, 0.30019999999999997, 0.28556,
],
},
memory: [
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000,
],
iops: {
r: {
a: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
],
},
w: {
a: [
0.097648, 0.081883, 0.081339, 0.080419, 0.080809, 0.081243, 0.081491, 0.081343, 0.1398, 0.18972, 0.10195,
0.060924, 0.08152, 0.081328, 0.08151, 0.081099, 0.13029, 0.098147, 0.08134, 0.08152, 0.081328, 0.081515,
0.081338, 0.08153, 0.081341, 0.1315, 0.18377, 0.095813, 0.080532, 0.080775, 0.081524, 0.08133, 0.081519,
0.081842, 0.083333, 0.095709, 0.081514, 0.081345, 0.081507, 0.08132, 0.11194, 0.033333, 0.083333, 0.13333,
0.093697, 0.081357, 0.081485, 0.081353, 0.081502, 0.081338, 0.082602, 0.083333, 0.083333, 0.092391, 0.080733,
0.11021, 0.052649, 0.081509, 0.082509, 0.083333, 0.083333, 0.083333, 0.091228, 0.08151, 0.081315, 0.081363,
0.081251, 0.083333, 0.083333, 0.083333, 0.1406, 0.081517, 0.081355, 0.081508, 0.081682, 0.083333, 0.083333,
0.13333, 0.16667, 0.0887, 0.081485, 0.081352, 0.081796, 0.083333, 0.083333, 0.13786, 0.19548, 0.083333,
0.088326, 0.081065, 0.088574, 0.075369, 0.083333, 0.13333, 0.033333, 0.083333, 0.087691, 0.081366, 0.1315,
0.082773, 0.1, 0.083333, 0.13333, 0.033333, 0.083333, 0.085806, 0.081349, 0.082845, 0.083333, 0.083333,
0.083333, 0.13333, 0.033333, 0.083333, 0.085255, 0.10573, 0.075677, 0.083333, 0.083333,
],
},
},
xvds: {
r: {
a: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
],
},
w: {
a: [
468.23, 403.66, 401.43, 397.66, 399.26, 401.04, 402.05, 401.45, 1187, 981.89, 554.12, 317.81, 402.17, 401.39,
402.13, 412.93, 862.5, 470.28, 401.44, 402.17, 401.39, 402.15, 401.43, 402.21, 401.44, 675.17, 1503.7, 460.72,
398.12, 399.12, 402.19, 401.39, 402.17, 403.49, 409.6, 460.29, 402.15, 401.46, 402.12, 401.35, 595.03, 204.8,
409.6, 887.47, 452.05, 401.5, 402.03, 401.49, 402.1, 401.43, 406.6, 409.6, 409.6, 446.7, 398.95, 587.95,
283.92, 402.13, 406.22, 409.6, 409.6, 409.6, 441.94, 402.13, 401.33, 401.53, 401.07, 409.6, 409.6, 409.6,
917.25, 402.16, 401.5, 402.12, 402.83, 409.6, 409.6, 1092.3, 887.47, 431.58, 402.03, 401.49, 403.3, 409.6,
409.6, 1048.4, 1204.4, 409.6, 430.05, 400.31, 431.07, 376.98, 409.6, 614.4, 204.8, 409.6, 427.45, 411.33,
870.18, 407.31, 546.13, 409.6, 614.4, 204.8, 409.6, 419.73, 401.47, 407.6, 409.6, 409.6, 409.6, 614.4, 204.8,
409.6, 417.47, 569.62, 378.24, 409.6, 409.6,
],
},
},
vifs: {
rx: {
'0': [
26.093, 32.014, 67.697, 58.753, 33.467, 37.491, 58.597, 30.445, 35.183, 80.516, 24.93, 31.059, 30.958, 82.292,
41.095, 42.146, 72.437, 79.315, 27.17, 34.682, 29.654, 31.031, 38.662, 83.563, 32.857, 45.96, 62.557, 29.964,
30.536, 30.067, 75.823, 37.306, 32.438, 34.156, 50.525, 41.277, 84.548, 39.422, 35.256, 43.333, 37.533,
30.967, 87.266, 52.716, 34.614, 35.031, 127.45, 33.611, 32.06, 32.095, 102.09, 37.166, 36.9, 39.71, 32.681,
28.122, 49.495, 90.197, 28.328, 32.967, 35.467, 33.679, 38.205, 43.533, 33.467, 29.404, 62.77, 84.119, 30.865,
32.131, 35.253, 32.386, 29.647, 79.757, 45.637, 31.853, 63.045, 43.291, 35.722, 31.07, 36.9, 35.9, 28.501,
81.165, 34.123, 32.011, 68.544, 43.598, 42.074, 26.279, 106.37, 41.084, 26.758, 41.401, 42.847, 40.596,
56.853, 37.168, 97.783, 30.315, 33.118, 38.833, 42.22, 52.069, 36.294, 37.828, 107.76, 39.728, 46.087, 31.574,
34.889, 89.279, 624.37, 128.66, 116.32, 120.88, 76.055, 57.768, 57.251,
],
},
tx: {
'0': [
0, 0, 0, 0, 0, 1.7333, 1.7333, 0, 1.7333, 1.7333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.7333, 1.2667, 0,
5.9211, 3.0122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2.6608, 0.80588, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 3, 0, 0, 1.7333, 1.7333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.9737, 0.026261, 0, 1.7333, 1.7333, 0, 0,
0, 0, 0, 0, 3.475, 2.4583, 0, 0, 0, 0, 1.7333, 1.2667, 0, 1.4958, 1.9709, 0, 0, 0, 0, 1.7333, 0, 0, 0, 0, 0,
0, 0, 1.7333, 1.7333, 0, 1.4832, 0.25009, 1.7333, 0, 0, 0, 0, 1.7333,
],
},
},
memoryFree: [
1952358400, 1952358400, 1952358400, 1952256000, 1952460800, 1952563200, 1952460800, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952256000, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952256000, 1952051200, 1952051200, 1952051200, 1952256000,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952256000, 1952051200, 1952256000,
1952358400, 1952256000, 1952051200, 1952256000, 1952358400, 1952153600, 1952051200, 1952256000, 1952358400,
1952358400, 1952358400, 1952256000, 1952051200, 1952256000, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952256000, 1952153600,
1952256000, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952256000, 1952153600,
],
},
}

View File

@@ -9,6 +9,10 @@ export class RestApi {
this.#xoApp = xoApp
}
get xoApp() {
return this.#xoApp
}
authenticateUser(...args: Parameters<XoApp['authenticateUser']>) {
return this.#xoApp.authenticateUser(...args)
}

View File

@@ -1,4 +1,5 @@
import type { XoUser, XapiXoRecord } from '@vates/types/xo'
import type { XapiVmStats, XapiStatsGranularity } from '@vates/types/common'
import type { XoUser, XapiXoRecord, XoVm } from '@vates/types/xo'
export type XoApp = {
authenticateUser: (
@@ -11,5 +12,6 @@ export type XoApp = {
type: T['type'],
opts?: { filter?: string; limit?: number }
) => Record<T['id'], T>
getXapiVmStats: (vmId: XoVm['id'], granularity?: XapiStatsGranularity) => Promise<XapiVmStats>
runWithApiContext: (user: XoUser, fn: () => void) => Promise<unknown>
}

View File

@@ -1,17 +1,19 @@
import { Example, Get, Path, Query, Request, Response, Route, Security } from 'tsoa'
import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa'
import { Request as ExRequest } from 'express'
import { inject } from 'inversify'
import { incorrectState, invalidParameters } from 'xo-common/api-errors.js'
import { provide } from 'inversify-binding-decorators'
import type { XoVm } from '@vates/types'
import type { XapiStatsGranularity, XapiVmStats, XoVm } from '@vates/types'
import { partialVms, vm, vmIds } from '../open-api/oa-examples/vm.oa-example.mjs'
import { partialVms, vm, vmIds, vmStatsExample } from '../open-api/oa-examples/vm.oa-example.mjs'
import { RestApi } from '../rest-api/rest-api.mjs'
import type { Unbrand, WithHref } from '../helpers/helper.type.mjs'
import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs'
@Route('vms')
@Security('*')
@Response(401)
@Response(401, 'Authentication required')
@Tags('vms')
// the `provide` decorator is mandatory on class that injects/receives dependencies.
// It automatically bind the class to the IOC container that handles dependency injection
@provide(VmController)
@@ -44,8 +46,33 @@ export class VmController extends XapiXoController<XoVm> {
*/
@Example(vm)
@Get('{id}')
@Response(404)
@Response(404, 'VM not found')
getVm(@Path() id: string): Unbrand<XoVm> {
return this.getObject(id as XoVm['id'])
}
/**
*
* VM must be running
*
* @example id "f07ab729-c0e8-721c-45ec-f11276377030"
*/
@Example(vmStatsExample)
@Get('{id}/stats')
@Response(404, 'VM not found')
@Response(422, 'Invalid granularity, VM is halted or host could not be found')
async getVmStats(@Path() id: string, @Query() granularity?: XapiStatsGranularity): Promise<XapiVmStats> {
try {
return await this.restApi.xoApp.getXapiVmStats(id as XoVm['id'], granularity)
} catch (error) {
if (
incorrectState.is(error, {
property: 'resident_on',
})
) {
/* throw */ invalidParameters(`VM ${id} is halted or host could not be found.`, error)
}
throw error
}
}
}

View File

@@ -11,6 +11,9 @@
> Users must be able to say: “Nice enhancement, I'm eager to test it”
- **Migrated REST API endpoints**:
- `/rest/v0/vms/<vm-id>/stats` (PR [#8359](https://github.com/vatesfr/xen-orchestra/pull/8359))
### Bug fixes
> Users must be able to say: “I had this issue, happy to know it's fixed”
@@ -31,6 +34,8 @@
<!--packages-start-->
- @xen-orchestra/rest-api patch
- @vates/types minor
- @xen-orchestra/rest-api minor
- xo-server patch
<!--packages-end-->

View File

@@ -9,6 +9,7 @@ import sum from 'lodash/sum.js'
import uniq from 'lodash/uniq.js'
import zipWith from 'lodash/zipWith.js'
import { BaseError } from 'make-error'
import { incorrectState } from 'xo-common/api-errors.js'
import { parseDateTime } from '@xen-orchestra/xapi'
export class FaultyGranularity extends BaseError {}
@@ -388,7 +389,12 @@ export default class XapiStats {
const vm = xapi.getObject(vmId)
const host = vm.$resident_on
if (!host) {
throw new Error(`VM ${vmId} is halted or host could not be found.`)
/* throw */ incorrectState({
actual: host,
expected: '<host-uuid>',
object: vm,
property: 'resident_on',
})
}
return this._getAndUpdateStats(xapi, {

View File

@@ -1357,13 +1357,12 @@ export default class RestApi {
)
api.get(
'/:collection(vms|hosts)/:object/stats',
'/:collection(hosts)/:object/stats',
wrap(async (req, res) => {
const object = req.object
const method = object.type === 'VM' ? 'getXapiVmStats' : 'getXapiHostStats'
const granularity = req.query.granularity
const result = await app[method](object.id, granularity)
const result = await app.getXapiHostStats(object.id, granularity)
return res.json(result)
})
)