@xen-orchestra/rest-api 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/abstract-classes/base-controller.mjs +2 -2
  2. package/dist/alarms/alarm.controller.mjs +10 -60
  3. package/dist/alarms/alarm.service.mjs +67 -0
  4. package/dist/backup-repositories/backup-repositories.controller.mjs +61 -0
  5. package/dist/groups/group.controller.mjs +15 -2
  6. package/dist/helpers/object-wrapper.helper.mjs +8 -1
  7. package/dist/helpers/utils.helper.mjs +74 -1
  8. package/dist/hosts/host.controller.mjs +57 -2
  9. package/dist/hosts/host.service.mjs +78 -0
  10. package/dist/ioc/ioc.mjs +25 -1
  11. package/dist/messages/message.controller.mjs +1 -1
  12. package/dist/networks/network.controller.mjs +34 -2
  13. package/dist/open-api/oa-examples/alarm.oa-example.mjs +12 -0
  14. package/dist/open-api/oa-examples/backup-repository.oa-example.mjs +31 -0
  15. package/dist/open-api/oa-examples/pool.oa-example.mjs +201 -0
  16. package/dist/open-api/oa-examples/user.oa-example.mjs +1 -0
  17. package/dist/open-api/routes/routes.js +585 -76
  18. package/dist/pifs/pif.controller.mjs +34 -2
  19. package/dist/pools/pool.controller.mjs +70 -3
  20. package/dist/pools/pool.service.mjs +212 -0
  21. package/dist/rest-api/rest-api.mjs +6 -1
  22. package/dist/srs/sr.controller.mjs +34 -2
  23. package/dist/users/user.controller.mjs +32 -3
  24. package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +34 -2
  25. package/dist/vdis/vdi.controller.mjs +34 -2
  26. package/dist/vm-templates/vm-template.controller.mjs +34 -2
  27. package/dist/vms/vm.service.mjs +40 -0
  28. package/dist/xoa/xoa.service.mjs +96 -110
  29. package/open-api/spec/swagger.json +3248 -1133
  30. package/package.json +3 -3
@@ -10,13 +10,18 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
10
10
  import { Example, Get, Security, Query, Request, Response, Route, Tags, Path } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
12
  import { provide } from 'inversify-binding-decorators';
13
+ import { AlarmService } from '../alarms/alarm.service.mjs';
14
+ import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
15
+ import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.mjs';
13
16
  import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
14
17
  import { RestApi } from '../rest-api/rest-api.mjs';
15
18
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
16
19
  import { partialVmTemplates, vmTemplate, vmTemplateIds } from '../open-api/oa-examples/vm-template.oa-example.mjs';
17
20
  let VmTemplateController = class VmTemplateController extends XapiXoController {
18
- constructor(restApi) {
21
+ #alarmService;
22
+ constructor(restApi, alarmService) {
19
23
  super('VM-template', restApi);
24
+ this.#alarmService = alarmService;
20
25
  }
21
26
  /**
22
27
  * @example fields "id,isDefaultTemplate,name_label"
@@ -32,6 +37,20 @@ let VmTemplateController = class VmTemplateController extends XapiXoController {
32
37
  getVmTemplate(id) {
33
38
  return this.getObject(id);
34
39
  }
40
+ /**
41
+ * @example id "b7569d99-30f8-178a-7d94-801de3e29b5b-f873abe0-b138-4995-8f6f-498b423d234d"
42
+ * @example fields "id,time"
43
+ * @example filter "time:>1747053793"
44
+ * @example limit 42
45
+ */
46
+ getVmTemplateAlarms(req, id, fields, ndjson, filter, limit) {
47
+ const vmTemplate = this.getObject(id);
48
+ const alarms = this.#alarmService.getAlarms({
49
+ filter: `${escapeUnsafeComplexMatcher(filter) ?? ''} object:uuid:${vmTemplate.uuid}`,
50
+ limit,
51
+ });
52
+ return this.sendObjects(Object.values(alarms), req, 'alarms');
53
+ }
35
54
  };
36
55
  __decorate([
37
56
  Example(vmTemplateIds),
@@ -49,12 +68,25 @@ __decorate([
49
68
  Response(notFoundResp.status, notFoundResp.description),
50
69
  __param(0, Path())
51
70
  ], VmTemplateController.prototype, "getVmTemplate", null);
71
+ __decorate([
72
+ Example(genericAlarmsExample),
73
+ Get('{id}/alarms'),
74
+ Tags('alarms'),
75
+ Response(notFoundResp.status, notFoundResp.description),
76
+ __param(0, Request()),
77
+ __param(1, Path()),
78
+ __param(2, Query()),
79
+ __param(3, Query()),
80
+ __param(4, Query()),
81
+ __param(5, Query())
82
+ ], VmTemplateController.prototype, "getVmTemplateAlarms", null);
52
83
  VmTemplateController = __decorate([
53
84
  Route('vm-templates'),
54
85
  Security('*'),
55
86
  Response(unauthorizedResp.status, unauthorizedResp.description),
56
87
  Tags('vms'),
57
88
  provide(VmTemplateController),
58
- __param(0, inject(RestApi))
89
+ __param(0, inject(RestApi)),
90
+ __param(1, inject(AlarmService))
59
91
  ], VmTemplateController);
60
92
  export { VmTemplateController };
@@ -1,6 +1,7 @@
1
1
  import { createLogger } from '@xen-orchestra/log';
2
2
  import { defer } from 'golike-defer';
3
3
  import { Task } from '@vates/task';
4
+ import { VM_POWER_STATE, } from '@vates/types';
4
5
  const log = createLogger('xo:rest-api:vm-service');
5
6
  export class VmService {
6
7
  #restApi;
@@ -44,4 +45,43 @@ export class VmService {
44
45
  return xoVm.id;
45
46
  }
46
47
  create = defer(this.#create);
48
+ getVmsStatus(opts) {
49
+ const vms = this.#restApi.getObjectsByType('VM', opts);
50
+ let nRunning = 0;
51
+ let nPaused = 0;
52
+ let nSuspended = 0;
53
+ let nHalted = 0;
54
+ let nUnknown = 0;
55
+ let total = 0;
56
+ for (const id in vms) {
57
+ total++;
58
+ const vm = vms[id];
59
+ switch (vm.power_state) {
60
+ case VM_POWER_STATE.RUNNING:
61
+ nRunning++;
62
+ break;
63
+ case VM_POWER_STATE.HALTED:
64
+ nHalted++;
65
+ break;
66
+ case VM_POWER_STATE.PAUSED:
67
+ nPaused++;
68
+ break;
69
+ case VM_POWER_STATE.SUSPENDED:
70
+ nSuspended++;
71
+ break;
72
+ default:
73
+ log.warn('Invalid VM power_state', vm.id, vm.power_state);
74
+ nUnknown++;
75
+ break;
76
+ }
77
+ }
78
+ return {
79
+ running: nRunning,
80
+ halted: nHalted,
81
+ paused: nPaused,
82
+ suspended: nSuspended,
83
+ unknown: nUnknown,
84
+ total,
85
+ };
86
+ }
47
87
  }
@@ -1,22 +1,23 @@
1
1
  import groupBy from 'lodash/groupBy.js';
2
2
  import semver from 'semver';
3
- import { BACKUP_TYPE, HOST_POWER_STATE, VM_POWER_STATE, } from '@vates/types';
4
- import { asyncEach } from '@vates/async-each';
3
+ import { BACKUP_TYPE, VM_POWER_STATE, } from '@vates/types';
5
4
  import { createLogger } from '@xen-orchestra/log';
6
5
  import { createPredicate } from 'value-matcher';
7
6
  import { extractIdsFromSimplePattern } from '@xen-orchestra/backups/extractIdsFromSimplePattern.mjs';
8
- import { isPromise } from 'node:util/types';
9
7
  import { noSuchObject } from 'xo-common/api-errors.js';
10
8
  import { parse } from 'xo-remote-parser';
11
9
  import { getFromAsyncCache } from '../helpers/cache.helper.mjs';
12
- import { isReplicaVm, isSrWritable, vmContainsNoBakTag } from '../helpers/utils.helper.mjs';
10
+ import { isReplicaVm, isSrWritableOrIso, promiseWriteInStream, vmContainsNoBakTag } from '../helpers/utils.helper.mjs';
11
+ import { HostService } from '../hosts/host.service.mjs';
13
12
  const log = createLogger('xo:rest-api:xoa-service');
14
13
  export class XoaService {
15
14
  #restApi;
15
+ #hostService;
16
16
  #dashboardAsyncCache = new Map();
17
17
  #dashboardCacheOpts;
18
18
  constructor(restApi) {
19
19
  this.#restApi = restApi;
20
+ this.#hostService = restApi.ioc.get(HostService);
20
21
  this.#dashboardCacheOpts = {
21
22
  timeout: this.#restApi.xoApp.config.getOptionalDuration('rest-api.dashboardCacheTimeout') ?? 60000,
22
23
  expiresIn: this.#restApi.xoApp.config.getOptionalDuration('rest-api.dashboardCacheExpiresIn'),
@@ -25,15 +26,12 @@ export class XoaService {
25
26
  async #getBackupRepositoriesSizeInfo() {
26
27
  const brResult = await getFromAsyncCache(this.#dashboardAsyncCache, 'backupRepositories', async () => {
27
28
  const xoApp = this.#restApi.xoApp;
28
- const s3Brsize = { backups: 0 };
29
- const otherBrSize = {
30
- available: 0,
31
- backups: 0,
32
- other: 0,
33
- total: 0,
34
- used: 0,
35
- };
29
+ let s3Brsize;
30
+ let otherBrSize;
36
31
  const backupRepositories = await xoApp.getAllRemotes();
32
+ if (backupRepositories.length === 0) {
33
+ return undefined;
34
+ }
37
35
  const backupRepositoriesInfo = await xoApp.getAllRemotesInfo();
38
36
  for (const backupRepository of backupRepositories) {
39
37
  const { type } = parse(backupRepository.url);
@@ -44,17 +42,48 @@ export class XoaService {
44
42
  const totalBackupSize = await xoApp.getTotalBackupSizeOnRemote(backupRepository.id);
45
43
  const { available, size, used } = backupRepositoryInfo;
46
44
  const isS3 = type === 's3';
47
- const target = isS3 ? s3Brsize : otherBrSize;
48
- target.backups += totalBackupSize.onDisk;
49
- if (!isS3) {
50
- const _target = target;
51
- _target.available += available ?? 0;
52
- _target.other += used - totalBackupSize.onDisk;
53
- _target.total += size ?? 0;
54
- _target.used += used;
45
+ if (isS3) {
46
+ if (s3Brsize === undefined) {
47
+ s3Brsize = { size: { backups: 0 } };
48
+ }
49
+ s3Brsize.size.backups += totalBackupSize.onDisk;
50
+ }
51
+ else {
52
+ if (otherBrSize === undefined) {
53
+ otherBrSize = {
54
+ size: {
55
+ backups: 0,
56
+ available: undefined,
57
+ other: undefined,
58
+ total: undefined,
59
+ used: undefined,
60
+ },
61
+ };
62
+ }
63
+ if (available === undefined || size === undefined || used === undefined) {
64
+ log.info('#getBackupRepositoriesSizeInfo missing info for BR:', backupRepository.id);
65
+ }
66
+ otherBrSize.size.backups += totalBackupSize.onDisk;
67
+ if (available !== undefined) {
68
+ otherBrSize.size.available = (otherBrSize.size.available ?? 0) + available;
69
+ }
70
+ if (used !== undefined) {
71
+ otherBrSize.size.used = (otherBrSize.size.used ?? 0) + used;
72
+ otherBrSize.size.other = (otherBrSize.size.other ?? 0) + (used - totalBackupSize.onDisk);
73
+ }
74
+ if (size !== undefined) {
75
+ otherBrSize.size.total = (otherBrSize.size.total ?? 0) + size;
76
+ }
55
77
  }
56
78
  }
57
- return { s3: { size: s3Brsize }, other: { size: otherBrSize } };
79
+ const result = {};
80
+ if (s3Brsize !== undefined) {
81
+ result.s3 = s3Brsize;
82
+ }
83
+ if (otherBrSize !== undefined) {
84
+ result.other = otherBrSize;
85
+ }
86
+ return result;
58
87
  }, this.#dashboardCacheOpts);
59
88
  if (brResult?.value !== undefined) {
60
89
  return { ...brResult.value, isExpired: brResult.isExpired };
@@ -71,15 +100,15 @@ export class XoaService {
71
100
  #getResourcesOverview() {
72
101
  const pools = Object.values(this.#restApi.getObjectsByType('pool'));
73
102
  const hosts = Object.values(this.#restApi.getObjectsByType('host'));
74
- const writableSrs = Object.values(this.#restApi.getObjectsByType('SR', {
75
- filter: isSrWritable,
103
+ const srs = Object.values(this.#restApi.getObjectsByType('SR', {
104
+ filter: isSrWritableOrIso,
76
105
  }));
77
- const maxLenght = Math.max(hosts.length, writableSrs.length);
106
+ const maxLenght = Math.max(hosts.length, srs.length);
78
107
  const resourcesOverview = { nCpus: 0, memorySize: 0, srSize: 0 };
79
108
  for (let index = 0; index < maxLenght; index++) {
80
109
  const pool = pools[index];
81
110
  const host = hosts[index];
82
- const sr = writableSrs[index];
111
+ const sr = srs[index];
83
112
  if (pool !== undefined) {
84
113
  resourcesOverview.nCpus += pool.cpus.cores ?? 0;
85
114
  }
@@ -139,34 +168,16 @@ export class XoaService {
139
168
  return nHostsEol;
140
169
  }
141
170
  async #getMissingPatchesInfo() {
142
- if (!(await this.#restApi.xoApp.hasFeatureAuthorization('LIST_MISSING_PATCHES'))) {
143
- return {
144
- hasAuthorization: false,
145
- };
171
+ const missingPatchesInfo = await this.#hostService.getMissingPatchesInfo();
172
+ if (!missingPatchesInfo.hasAuthorization) {
173
+ return { hasAuthorization: false };
146
174
  }
147
- const hosts = Object.values(this.#restApi.getObjectsByType('host'));
148
- const poolsWithMissingPatches = new Set();
149
- let nHostsWithMissingPatches = 0;
150
- let nHostsFailed = 0;
151
- await asyncEach(hosts, async (host) => {
152
- const xapi = this.#restApi.xoApp.getXapi(host);
153
- try {
154
- const patches = await xapi.listMissingPatches(host.id);
155
- if (patches.length > 0) {
156
- nHostsWithMissingPatches++;
157
- poolsWithMissingPatches.add(host.$pool);
158
- }
159
- }
160
- catch (err) {
161
- log.error('listMissingPatches failed', err);
162
- nHostsFailed++;
163
- }
164
- });
175
+ const { hasAuthorization, nHostsFailed, nHostsWithMissingPatches, nPoolsWithMissingPatches } = missingPatchesInfo;
165
176
  return {
166
- hasAuthorization: true,
177
+ hasAuthorization,
167
178
  nHostsFailed,
168
179
  nHostsWithMissingPatches,
169
- nPoolsWithMissingPatches: poolsWithMissingPatches.size,
180
+ nPoolsWithMissingPatches,
170
181
  };
171
182
  }
172
183
  #isReplicaVmInVdb(vbds) {
@@ -207,7 +218,7 @@ export class XoaService {
207
218
  }
208
219
  #getStorageRepositoriesSizeInfo() {
209
220
  const writableSrs = this.#restApi.getObjectsByType('SR', {
210
- filter: isSrWritable,
221
+ filter: isSrWritableOrIso,
211
222
  });
212
223
  let replicated = 0;
213
224
  let total = 0;
@@ -374,30 +385,11 @@ export class XoaService {
374
385
  }
375
386
  }
376
387
  #getHostsStatus() {
377
- const hosts = this.#restApi.getObjectsByType('host');
378
- let nRunning = 0;
379
- let nHalted = 0;
380
- let nUnknown = 0;
381
- let total = 0;
382
- for (const id in hosts) {
383
- total++;
384
- const host = hosts[id];
385
- switch (host.power_state) {
386
- case HOST_POWER_STATE.RUNNING:
387
- nRunning++;
388
- break;
389
- case HOST_POWER_STATE.HALTED:
390
- nHalted++;
391
- break;
392
- default:
393
- nUnknown++;
394
- break;
395
- }
396
- }
388
+ const { running, halted, total, unknown } = this.#hostService.getHostsStatus();
397
389
  return {
398
- running: nRunning,
399
- halted: nHalted,
400
- unknown: nUnknown,
390
+ running,
391
+ halted,
392
+ unknown,
401
393
  total,
402
394
  };
403
395
  }
@@ -432,44 +424,38 @@ export class XoaService {
432
424
  };
433
425
  }
434
426
  async getDashboard({ stream } = {}) {
435
- async function promiseWriteInStream(maybePromise, key) {
436
- let data;
437
- if (isPromise(maybePromise)) {
438
- data = await maybePromise;
439
- }
440
- else {
441
- data = maybePromise;
442
- }
443
- if (stream !== undefined) {
444
- if (stream.writableNeedDrain) {
445
- await new Promise(resolve => stream.once('drain', resolve));
446
- }
447
- stream.write(JSON.stringify({ [key]: data }) + '\n');
448
- }
449
- return data;
450
- }
451
427
  const [nPools, nHosts, hostsStatus, resourcesOverview, vmsStatus, storageRepositories, poolsStatus, missingPatches, backupRepositories, nHostsEol, backups,] = await Promise.all([
452
- promiseWriteInStream(this.#getNumberOfPools(), 'nPools'),
453
- promiseWriteInStream(this.#getNumberOfHosts(), 'nHosts'),
454
- promiseWriteInStream(this.#getHostsStatus(), 'hostsStatus'),
455
- promiseWriteInStream(this.#getResourcesOverview(), 'resourcesOverview'),
456
- promiseWriteInStream(this.#getVmsStatus(), 'vmsStatus'),
457
- promiseWriteInStream(this.#getStorageRepositoriesSizeInfo(), 'storageRepositories'),
458
- promiseWriteInStream(this.#getPoolsStatus(), 'poolsStatus'),
459
- promiseWriteInStream(this.#getMissingPatchesInfo(), 'missingPatches'),
460
- promiseWriteInStream(this.#getBackupRepositoriesSizeInfo().catch(err => {
461
- log.error('#getBackupRepositoriesSizeInfo failed', err);
462
- // explicitly return undefined because typescript understand it as void instead of undefined
463
- return undefined;
464
- }), 'backupRepositories'),
465
- promiseWriteInStream(this.#getNumberOfEolHosts().catch(err => {
466
- log.error('#getNumberOfEolHosts failed', err);
467
- return undefined;
468
- }), 'nHostsEol'),
469
- promiseWriteInStream(this.#getbackupsInfo().catch(err => {
470
- log.error('#getbackupsInfo failed', err);
471
- return undefined;
472
- }), 'backups'),
428
+ promiseWriteInStream({ maybePromise: this.#getNumberOfPools(), path: 'nPools', stream }),
429
+ promiseWriteInStream({ maybePromise: this.#getNumberOfHosts(), path: 'nHosts', stream }),
430
+ promiseWriteInStream({ maybePromise: this.#getHostsStatus(), path: 'hostsStatus', stream }),
431
+ promiseWriteInStream({ maybePromise: this.#getResourcesOverview(), path: 'resourcesOverview', stream }),
432
+ promiseWriteInStream({ maybePromise: this.#getVmsStatus(), path: 'vmsStatus', stream }),
433
+ promiseWriteInStream({
434
+ maybePromise: this.#getStorageRepositoriesSizeInfo(),
435
+ path: 'storageRepositories',
436
+ stream,
437
+ handleError: true,
438
+ }),
439
+ promiseWriteInStream({ maybePromise: this.#getPoolsStatus(), path: 'poolsStatus', stream }),
440
+ promiseWriteInStream({ maybePromise: this.#getMissingPatchesInfo(), path: 'missingPatches', stream }),
441
+ promiseWriteInStream({
442
+ maybePromise: this.#getBackupRepositoriesSizeInfo(),
443
+ path: 'backupRepositories',
444
+ stream,
445
+ handleError: true,
446
+ }),
447
+ promiseWriteInStream({
448
+ maybePromise: this.#getNumberOfEolHosts(),
449
+ path: 'nHostsEol',
450
+ stream,
451
+ handleError: true,
452
+ }),
453
+ promiseWriteInStream({
454
+ maybePromise: this.#getbackupsInfo(),
455
+ path: 'backups',
456
+ stream,
457
+ handleError: true,
458
+ }),
473
459
  ]);
474
460
  return {
475
461
  nPools,