@xen-orchestra/rest-api 0.24.0 → 0.24.1

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.
@@ -19,31 +19,35 @@ export class Subscriber {
19
19
  get connection() {
20
20
  return this.#connection;
21
21
  }
22
- constructor(res, manager) {
22
+ constructor(connection, manager) {
23
23
  this.#id = crypto.randomUUID();
24
- const headers = new Headers({
25
- 'Content-Type': 'text/event-stream',
26
- Connection: 'keep-alive',
27
- 'Cache-Control': 'no-cache, no-transform',
28
- });
29
- res.setHeaders(headers);
30
- res.on('close', () => this.clear());
24
+ connection.on('close', () => this.clear());
31
25
  manager.addSubscriber(this);
32
- this.#connection = res;
26
+ this.#connection = connection;
33
27
  this.#manager = manager;
34
28
  this.#isAlive = true;
35
29
  }
30
+ #safeWrite(payload) {
31
+ const ok = this.#connection.write(payload);
32
+ if (!ok) {
33
+ log.error(`Too much data in queue for the client ${this.id} (${Math.round(this.#connection.writableLength / 1024 / 1024)} MB). The connection is going to be destroyed`);
34
+ this.clear();
35
+ }
36
+ }
36
37
  broadcast(event, data) {
37
38
  if (!this.#isAlive) {
38
39
  log.warn('broadcast called on a subscriber that is not alive, but still in memory! Force clear and do nothing');
39
40
  this.clear();
40
41
  return;
41
42
  }
42
- this.#connection.write(`event:${event}\n`);
43
- this.#connection.write(`data:${JSON.stringify(data)}\n\n`);
43
+ const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
44
+ this.#safeWrite(payload);
44
45
  }
45
46
  clear() {
46
47
  this.#isAlive = false;
48
+ if (!this.#connection.closed || !this.#connection.destroyed) {
49
+ this.#connection.destroy();
50
+ }
47
51
  this.#manager.removeSubscriber(this.id);
48
52
  }
49
53
  }
@@ -58,20 +62,25 @@ export class XoListener extends Listener {
58
62
  handleData({ fields, event }, object, previousObj) {
59
63
  let _object = object;
60
64
  let _prevObject = previousObj;
61
- if (this.#type === 'alarm') {
62
- if (object !== undefined &&
63
- 'type' in object &&
64
- object.type === 'message' &&
65
- this.#alarmService?.isAlarm(object)) {
66
- _object = this.#alarmService.parseAlarm(object);
65
+ if (this.#type === 'alarm' || this.#type === 'message') {
66
+ const isAlarm = (object) => object !== undefined && 'type' in object && object.type === 'message' && this.#alarmService.isAlarm(object);
67
+ const objectIsAlarm = isAlarm(object);
68
+ const prevObjectIsAlarm = isAlarm(previousObj);
69
+ // If we are in an alarm listener and the objects are messages
70
+ // we clean them to ensure they are not sent via the SSE
71
+ // Same if we are in a message listener and the objects are alarms
72
+ if (this.#type === 'alarm') {
73
+ _object = objectIsAlarm ? this.#alarmService.parseAlarm(object) : undefined;
74
+ _prevObject = prevObjectIsAlarm ? this.#alarmService.parseAlarm(previousObj) : undefined;
67
75
  }
68
- if (previousObj !== undefined &&
69
- 'type' in previousObj &&
70
- previousObj.type === 'message' &&
71
- this.#alarmService?.isAlarm(previousObj)) {
72
- _prevObject = this.#alarmService.parseAlarm(previousObj);
76
+ else {
77
+ _object = objectIsAlarm ? undefined : object;
78
+ _prevObject = prevObjectIsAlarm ? undefined : object;
73
79
  }
74
80
  }
81
+ if (_object === undefined && _prevObject === undefined) {
82
+ return;
83
+ }
75
84
  if (fields !== '*') {
76
85
  if (_object !== undefined) {
77
86
  _object = pick(_object, fields);
@@ -1,4 +1,6 @@
1
+ import os from 'node:os';
1
2
  import { createLogger } from '@xen-orchestra/log';
3
+ import { PassThrough, pipeline } from 'node:stream';
2
4
  import { PingListener, Subscriber, SubscriberManager, XoListener } from './event.class.mjs';
3
5
  import { AlarmService } from '../alarms/alarm.service.mjs';
4
6
  const log = createLogger('xo:rest-api:event-service');
@@ -26,7 +28,7 @@ export class EventService {
26
28
  listener = new PingListener();
27
29
  }
28
30
  else {
29
- const isAlarm = type === 'alarm';
31
+ const isMessage = type === 'alarm' || type === 'message';
30
32
  let eventEmitter;
31
33
  if (type === 'task') {
32
34
  eventEmitter = this.#restApi.xoApp.tasks;
@@ -34,15 +36,30 @@ export class EventService {
34
36
  else {
35
37
  // alarm is purely XO-related; it doesn't exist at the XAPI level.
36
38
  // alarm is a message with parsed values. So, in the case of an alarm listener, it listens for message collection.
37
- eventEmitter = this.#restApi.xoApp.objects.allIndexes.type.getEventEmitterByType(isAlarm ? 'message' : type);
39
+ eventEmitter = this.#restApi.xoApp.objects.allIndexes.type.getEventEmitterByType(isMessage ? 'message' : type);
38
40
  }
39
- listener = new XoListener(type, eventEmitter, isAlarm ? this.#alarmService : undefined);
41
+ listener = new XoListener(type, eventEmitter, isMessage ? this.#alarmService : undefined);
40
42
  }
41
43
  this.#listeners.set(type, listener);
42
44
  return listener;
43
45
  }
44
46
  createSseSubscriber(res) {
45
- const subscriber = new Subscriber(res, this.#subscriberManager);
47
+ const headers = new Headers({
48
+ 'Content-Type': 'text/event-stream',
49
+ Connection: 'keep-alive',
50
+ 'Cache-Control': 'no-cache, no-transform',
51
+ });
52
+ res.setHeaders(headers);
53
+ const maxRam = this.#restApi.xoApp.config.get('rest-api.percentOfRamAllocatedPerSseClient');
54
+ const connection = new PassThrough({
55
+ highWaterMark: Math.round(os.totalmem() * (maxRam / 100)),
56
+ });
57
+ pipeline(connection, res, error => {
58
+ if (error?.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
59
+ log.error(error);
60
+ }
61
+ });
62
+ const subscriber = new Subscriber(connection, this.#subscriberManager);
46
63
  subscriber.broadcast('init', { id: subscriber.id });
47
64
  this.addListenerFor(subscriber.id, { type: 'ping' });
48
65
  log.debug(`new SSE subscriber added: ${subscriber.id}`);
@@ -1,7 +1,5 @@
1
- import path from 'node:path';
2
1
  import pick from 'lodash/pick.js';
3
2
  import { BASE_URL } from '../index.mjs';
4
- const { join } = path.posix;
5
3
  export function makeObjectMapper(req, path) {
6
4
  const makeUrl = (obj) => {
7
5
  let _path;
@@ -9,9 +7,16 @@ export function makeObjectMapper(req, path) {
9
7
  _path = req.path;
10
8
  }
11
9
  else {
12
- _path = `${BASE_URL}/${typeof path === 'string' ? path : path(obj)}`;
10
+ let tmpPath = typeof path === 'string' ? path : path(obj);
11
+ if (tmpPath.startsWith('/')) {
12
+ tmpPath = tmpPath.slice(1);
13
+ }
14
+ if (tmpPath.endsWith('/')) {
15
+ tmpPath = tmpPath.slice(0, -1);
16
+ }
17
+ _path = `${BASE_URL}/${tmpPath}`;
13
18
  }
14
- return join(_path, typeof obj.id === 'number' ? String(obj.id) : obj.id);
19
+ return `${_path}/${String(obj.id)}`;
15
20
  };
16
21
  let objectMapper;
17
22
  const { query } = req;
@@ -51,7 +51,7 @@ export function setupApiContext(xoApp) {
51
51
  res.locals.authType = 'basic';
52
52
  }
53
53
  try {
54
- const { user } = await xoApp.authenticateUser(credentials, { ip });
54
+ const { user } = await xoApp.authenticateUser(credentials, { ip }, { bypassTaskCreation: hasToken });
55
55
  return xoApp.runWithApiContext(user, next);
56
56
  }
57
57
  catch (error) {
@@ -2,6 +2,7 @@ export const vifIds = [
2
2
  '/rest/v0/vifs/f028c5d4-578a-332c-394e-087aaca32dd3',
3
3
  '/rest/v0/vifs/9cc245bf-8dac-8550-e1ae-54bc679b68d9',
4
4
  ];
5
+ export const vifId = { id: 'f028c5d4-578a-332c-394e-087aaca32dd3' };
5
6
  export const partialVifs = [
6
7
  {
7
8
  attached: true,
@@ -493,6 +493,21 @@ const models = {
493
493
  "type": { "ref": "Unbrand_XoVif_", "validators": {} },
494
494
  },
495
495
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
496
+ "Pick_CreateVifParams_91_0_93_.Exclude_keyofCreateVifParams_91_0_93_.network-or-VM__": {
497
+ "dataType": "refAlias",
498
+ "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "other_config": { "dataType": "union", "subSchemas": [{ "ref": "Record_string.string_" }, { "dataType": "undefined" }] }, "device": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "MTU": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "currently_attached": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "ipv4_allowed": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "string" } }, { "dataType": "undefined" }] }, "ipv6_allowed": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "string" } }, { "dataType": "undefined" }] }, "locking_mode": { "dataType": "union", "subSchemas": [{ "ref": "VIF_LOCKING_MODE" }, { "dataType": "undefined" }] }, "qos_algorithm_params": { "dataType": "union", "subSchemas": [{ "ref": "Record_string.string_" }, { "dataType": "undefined" }] }, "qos_algorithm_type": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] } }, "validators": {} },
499
+ },
500
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
501
+ "Omit_CreateVifParams_91_0_93_.network-or-VM_": {
502
+ "dataType": "refAlias",
503
+ "type": { "ref": "Pick_CreateVifParams_91_0_93_.Exclude_keyofCreateVifParams_91_0_93_.network-or-VM__", "validators": {} },
504
+ },
505
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
506
+ "CreateVifBody": {
507
+ "dataType": "refAlias",
508
+ "type": { "dataType": "intersection", "subSchemas": [{ "ref": "Omit_CreateVifParams_91_0_93_.network-or-VM_" }, { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "MAC": { "dataType": "string" } } }, { "dataType": "undefined" }] }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "vmId": { "dataType": "string", "required": true }, "networkId": { "dataType": "string", "required": true } } }], "validators": {} },
509
+ },
510
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
496
511
  "Exclude_SUPPORTED_VDI_FORMAT.qcow2_": {
497
512
  "dataType": "refAlias",
498
513
  "type": { "dataType": "union", "subSchemas": [{ "dataType": "enum", "enums": ["raw"] }, { "dataType": "enum", "enums": ["vhd"] }], "validators": {} },
@@ -525,7 +540,7 @@ const models = {
525
540
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
526
541
  "Pick_Unbrand_Parameters_Xapi_91_VBD_create_93___91_0_93__.Exclude_keyofUnbrand_Parameters_Xapi_91_VBD_create_93___91_0_93__.VM-or-VDI__": {
527
542
  "dataType": "refAlias",
528
- "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "type": { "dataType": "union", "subSchemas": [{ "ref": "VBD_TYPE" }, { "dataType": "undefined" }] }, "other_config": { "dataType": "union", "subSchemas": [{ "ref": "Record_string.string_" }, { "dataType": "undefined" }] }, "mode": { "dataType": "union", "subSchemas": [{ "ref": "VBD_MODE" }, { "dataType": "undefined" }] }, "bootable": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "empty": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "qos_algorithm_params": { "dataType": "union", "subSchemas": [{ "ref": "Record_string.string_" }, { "dataType": "undefined" }] }, "qos_algorithm_type": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "unpluggable": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "userdevice": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] } }, "validators": {} },
543
+ "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "type": { "dataType": "union", "subSchemas": [{ "ref": "VBD_TYPE" }, { "dataType": "undefined" }] }, "other_config": { "dataType": "union", "subSchemas": [{ "ref": "Record_string.string_" }, { "dataType": "undefined" }] }, "mode": { "dataType": "union", "subSchemas": [{ "ref": "VBD_MODE" }, { "dataType": "undefined" }] }, "qos_algorithm_params": { "dataType": "union", "subSchemas": [{ "ref": "Record_string.string_" }, { "dataType": "undefined" }] }, "qos_algorithm_type": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "bootable": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "empty": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "unpluggable": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "userdevice": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] } }, "validators": {} },
529
544
  },
530
545
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
531
546
  "Omit_Unbrand_Parameters_Xapi_91_VBD_create_93___91_0_93__.VM-or-VDI_": {
@@ -807,7 +822,7 @@ const models = {
807
822
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
808
823
  "Unbrand_CreateVmBody_": {
809
824
  "dataType": "refAlias",
810
- "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "memory": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "name_description": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "name_label": { "dataType": "string", "required": true }, "secureBoot": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "clone": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "gpuGroup": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "vgpuType": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "autoPoweron": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "vifs": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "network": { "dataType": "string", "required": true }, "mtu": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "mac": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "ipv6_allowed": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "string" } }, { "dataType": "undefined" }] }, "ipv4_allowed": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "string" } }, { "dataType": "undefined" }] }, "device": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] } } }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "device": { "dataType": "string", "required": true }, "destroy": { "dataType": "enum", "enums": [true], "required": true } } }] } }, { "dataType": "undefined" }] }, "copyHostBiosStrings": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "hvmBootFirmware": { "dataType": "union", "subSchemas": [{ "dataType": "enum", "enums": ["uefi"] }, { "dataType": "enum", "enums": ["bios"] }, { "dataType": "undefined" }] }, "template": { "dataType": "string", "required": true }, "affinity": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "vdis": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "name_description": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "sr": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "size": { "dataType": "double", "required": true }, "name_label": { "dataType": "string", "required": true } } }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "name_description": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "sr": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "size": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "name_label": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "userdevice": { "dataType": "string", "required": true } } }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "userdervice": { "dataType": "string", "required": true }, "destroy": { "dataType": "enum", "enums": [true], "required": true } } }] } }, { "dataType": "undefined" }] }, "install": { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "repository": { "dataType": "string", "required": true }, "method": { "dataType": "union", "subSchemas": [{ "dataType": "enum", "enums": ["network"] }, { "dataType": "enum", "enums": ["cdrom"] }], "required": true } } }, { "dataType": "undefined" }] }, "cloud_config": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "network_config": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "boot": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "destroy_cloud_config_vdi": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "createVtpm": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] } }, "validators": {} },
825
+ "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "memory": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "name_description": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "name_label": { "dataType": "string", "required": true }, "secureBoot": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "clone": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "gpuGroup": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "vgpuType": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "autoPoweron": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "vifs": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "network": { "dataType": "string", "required": true }, "mtu": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "mac": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "ipv6_allowed": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "string" } }, { "dataType": "undefined" }] }, "ipv4_allowed": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "string" } }, { "dataType": "undefined" }] }, "device": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] } } }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "device": { "dataType": "string", "required": true }, "destroy": { "dataType": "enum", "enums": [true], "required": true } } }] } }, { "dataType": "undefined" }] }, "copyHostBiosStrings": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "hvmBootFirmware": { "dataType": "union", "subSchemas": [{ "dataType": "enum", "enums": ["uefi"] }, { "dataType": "enum", "enums": ["bios"] }, { "dataType": "undefined" }] }, "template": { "dataType": "string", "required": true }, "affinity": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "vdis": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "name_description": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "sr": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "size": { "dataType": "double", "required": true }, "name_label": { "dataType": "string", "required": true } } }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "name_description": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "sr": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "size": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "name_label": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "userdevice": { "dataType": "string", "required": true } } }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "userdervice": { "dataType": "string", "required": true }, "destroy": { "dataType": "enum", "enums": [true], "required": true } } }] } }, { "dataType": "undefined" }] }, "install": { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "repository": { "dataType": "string", "required": true }, "method": { "dataType": "enum", "enums": ["cdrom"], "required": true } } }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "repository": { "dataType": "enum", "enums": [""], "required": true }, "method": { "dataType": "enum", "enums": ["network"], "required": true } } }, { "dataType": "undefined" }] }, "cloud_config": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "network_config": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "boot": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "destroy_cloud_config_vdi": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "createVtpm": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] } }, "validators": {} },
811
826
  },
812
827
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
813
828
  "XapiHostStatsRaw": {
@@ -3054,6 +3069,60 @@ export function RegisterRoutes(app) {
3054
3069
  }
3055
3070
  });
3056
3071
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
3072
+ const argsVifController_createVif = {
3073
+ body: { "in": "body", "name": "body", "required": true, "ref": "CreateVifBody" },
3074
+ };
3075
+ app.post('/rest/v0/vifs', authenticateMiddleware([{ "*": [] }]), ...(fetchMiddlewares(VifController)), ...(fetchMiddlewares(VifController.prototype.createVif)), async function VifController_createVif(request, response, next) {
3076
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
3077
+ let validatedArgs = [];
3078
+ try {
3079
+ validatedArgs = templateService.getValidatedArgs({ args: argsVifController_createVif, request, response });
3080
+ const container = typeof iocContainer === 'function' ? iocContainer(request) : iocContainer;
3081
+ const controller = await container.get(VifController);
3082
+ if (typeof controller['setStatus'] === 'function') {
3083
+ controller.setStatus(undefined);
3084
+ }
3085
+ await templateService.apiHandler({
3086
+ methodName: 'createVif',
3087
+ controller,
3088
+ response,
3089
+ next,
3090
+ validatedArgs,
3091
+ successStatus: 201,
3092
+ });
3093
+ }
3094
+ catch (err) {
3095
+ return next(err);
3096
+ }
3097
+ });
3098
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
3099
+ const argsVifController_destroyVif = {
3100
+ id: { "in": "path", "name": "id", "required": true, "dataType": "string" },
3101
+ };
3102
+ app.delete('/rest/v0/vifs/:id', authenticateMiddleware([{ "*": [] }]), ...(fetchMiddlewares(VifController)), ...(fetchMiddlewares(VifController.prototype.destroyVif)), async function VifController_destroyVif(request, response, next) {
3103
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
3104
+ let validatedArgs = [];
3105
+ try {
3106
+ validatedArgs = templateService.getValidatedArgs({ args: argsVifController_destroyVif, request, response });
3107
+ const container = typeof iocContainer === 'function' ? iocContainer(request) : iocContainer;
3108
+ const controller = await container.get(VifController);
3109
+ if (typeof controller['setStatus'] === 'function') {
3110
+ controller.setStatus(undefined);
3111
+ }
3112
+ await templateService.apiHandler({
3113
+ methodName: 'destroyVif',
3114
+ controller,
3115
+ response,
3116
+ next,
3117
+ validatedArgs,
3118
+ successStatus: 204,
3119
+ });
3120
+ }
3121
+ catch (err) {
3122
+ return next(err);
3123
+ }
3124
+ });
3125
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
3057
3126
  const argsVdiController_getVdis = {
3058
3127
  req: { "in": "request", "name": "req", "required": true, "dataType": "object" },
3059
3128
  fields: { "in": "query", "name": "fields", "dataType": "string" },
@@ -166,9 +166,18 @@ let PoolController = class PoolController extends XapiXoController {
166
166
  async createVm(id, body, sync) {
167
167
  const poolId = id;
168
168
  const action = async () => {
169
- const { affinity, template, ...rest } = body;
170
- const params = { affinityHost: affinity, ...rest };
171
- const vmId = await this.#vmService.create({ pool: poolId, template, ...params });
169
+ const { affinity, template, install, vgpuType, gpuGroup, vdis, ...rest } = body;
170
+ // rebrand all branded type
171
+ const vmId = await this.#vmService.create({
172
+ affinityHost: affinity,
173
+ installRepository: install?.repository,
174
+ pool: poolId,
175
+ template: template,
176
+ vdis: vdis,
177
+ vgpuType: vgpuType,
178
+ gpuGroup: gpuGroup,
179
+ ...rest,
180
+ });
172
181
  return { id: vmId };
173
182
  };
174
183
  return this.createAction(action, {
@@ -163,7 +163,7 @@ export class PoolService {
163
163
  #getCpuProvisioning(poolId) {
164
164
  const pool = this.#restApi.getObject(poolId, 'pool');
165
165
  const vms = this.#restApi.getObjectsByType('VM', {
166
- filter: vm => vm.$pool === poolId,
166
+ filter: vm => vm.$pool === poolId && vm.power_state === VM_POWER_STATE.RUNNING,
167
167
  });
168
168
  let assignedVcpu = 0;
169
169
  for (const id in vms) {
@@ -7,14 +7,16 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
- import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
10
+ import { Body, Delete, Example, Get, Middlewares, Path, Post, Query, Request, Response, Route, Security, SuccessResponse, Tags, } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
+ import { json } from 'express';
12
13
  import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
13
14
  import { provide } from 'inversify-binding-decorators';
14
15
  import { RestApi } from '../rest-api/rest-api.mjs';
15
- import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
16
+ import { badRequestResp, createdResp, internalServerErrorResp, invalidParameters as invalidParametersResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
17
+ import { invalidParameters } from 'xo-common/api-errors.js';
16
18
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
17
- import { partialVifs, vif, vifIds } from '../open-api/oa-examples/vif.oa-example.mjs';
19
+ import { partialVifs, vif, vifId, vifIds } from '../open-api/oa-examples/vif.oa-example.mjs';
18
20
  import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.mjs';
19
21
  import { AlarmService } from '../alarms/alarm.service.mjs';
20
22
  import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
@@ -73,6 +75,37 @@ let VifController = class VifController extends XapiXoController {
73
75
  const tasks = await this.getTasksForObject(id, { filter, limit });
74
76
  return this.sendObjects(Object.values(tasks), req, 'tasks');
75
77
  }
78
+ /**
79
+ * @example body {
80
+ * "networkId": "6b6ca0f5-6611-0636-4b0a-1fb1c1e96414",
81
+ * "vmId": "613f541c-4bed-fc77-7ca8-2db6b68f079c"
82
+ * }
83
+ */
84
+ async createVif(body) {
85
+ const { MAC, vmId, networkId, ...rest } = body;
86
+ const vm = this.restApi.getObject(vmId, 'VM');
87
+ const network = this.restApi.getObject(networkId, 'network');
88
+ if (vm.$pool !== network.$pool) {
89
+ throw invalidParameters(`the VM ${vmId} and network ${networkId} do not belong to the same pool`);
90
+ }
91
+ const xapi = this.getXapi(vmId);
92
+ const vifRef = await xapi.VIF_create({
93
+ ...rest,
94
+ VM: vm._xapiRef,
95
+ network: network._xapiRef,
96
+ }, {
97
+ MAC,
98
+ });
99
+ const xapiVif = await xapi.barrier(vifRef);
100
+ return { id: xapiVif.uuid };
101
+ }
102
+ /**
103
+ * @example id "6b6ca0f5-6611-0636-4b0a-1fb1c1e96414"
104
+ */
105
+ async destroyVif(id) {
106
+ const xapi = this.getXapi(id);
107
+ await xapi.deleteVif(id);
108
+ }
76
109
  };
77
110
  __decorate([
78
111
  Example(vifIds),
@@ -128,6 +161,23 @@ __decorate([
128
161
  __param(4, Query()),
129
162
  __param(5, Query())
130
163
  ], VifController.prototype, "getVifTasks", null);
164
+ __decorate([
165
+ Example(vifId),
166
+ Post(''),
167
+ Middlewares(json()),
168
+ SuccessResponse(createdResp.status, createdResp.description),
169
+ Response(notFoundResp.status, notFoundResp.description),
170
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
171
+ Response(invalidParametersResp.status, invalidParametersResp.description),
172
+ __param(0, Body())
173
+ ], VifController.prototype, "createVif", null);
174
+ __decorate([
175
+ Delete('{id}'),
176
+ SuccessResponse(noContentResp.status, noContentResp.description),
177
+ Response(notFoundResp.status, notFoundResp.description),
178
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
179
+ __param(0, Path())
180
+ ], VifController.prototype, "destroyVif", null);
131
181
  VifController = __decorate([
132
182
  Route('vifs'),
133
183
  Security('*'),
@@ -5559,6 +5559,80 @@
5559
5559
  "UnbrandedXoVif": {
5560
5560
  "$ref": "#/components/schemas/Unbrand_XoVif_"
5561
5561
  },
5562
+ "Pick_CreateVifParams_91_0_93_.Exclude_keyofCreateVifParams_91_0_93_.network-or-VM__": {
5563
+ "properties": {
5564
+ "other_config": {
5565
+ "$ref": "#/components/schemas/Record_string.string_"
5566
+ },
5567
+ "device": {
5568
+ "type": "string"
5569
+ },
5570
+ "MTU": {
5571
+ "type": "number",
5572
+ "format": "double"
5573
+ },
5574
+ "currently_attached": {
5575
+ "type": "boolean"
5576
+ },
5577
+ "ipv4_allowed": {
5578
+ "items": {
5579
+ "type": "string"
5580
+ },
5581
+ "type": "array"
5582
+ },
5583
+ "ipv6_allowed": {
5584
+ "items": {
5585
+ "type": "string"
5586
+ },
5587
+ "type": "array"
5588
+ },
5589
+ "locking_mode": {
5590
+ "$ref": "#/components/schemas/VIF_LOCKING_MODE"
5591
+ },
5592
+ "qos_algorithm_params": {
5593
+ "$ref": "#/components/schemas/Record_string.string_"
5594
+ },
5595
+ "qos_algorithm_type": {
5596
+ "type": "string"
5597
+ }
5598
+ },
5599
+ "type": "object",
5600
+ "description": "From T, pick a set of properties whose keys are in the union K"
5601
+ },
5602
+ "Omit_CreateVifParams_91_0_93_.network-or-VM_": {
5603
+ "$ref": "#/components/schemas/Pick_CreateVifParams_91_0_93_.Exclude_keyofCreateVifParams_91_0_93_.network-or-VM__",
5604
+ "description": "Construct a type with the properties of T except for those in type K."
5605
+ },
5606
+ "CreateVifBody": {
5607
+ "allOf": [
5608
+ {
5609
+ "$ref": "#/components/schemas/Omit_CreateVifParams_91_0_93_.network-or-VM_"
5610
+ },
5611
+ {
5612
+ "properties": {
5613
+ "MAC": {
5614
+ "type": "string"
5615
+ }
5616
+ },
5617
+ "type": "object"
5618
+ },
5619
+ {
5620
+ "properties": {
5621
+ "vmId": {
5622
+ "type": "string"
5623
+ },
5624
+ "networkId": {
5625
+ "type": "string"
5626
+ }
5627
+ },
5628
+ "required": [
5629
+ "vmId",
5630
+ "networkId"
5631
+ ],
5632
+ "type": "object"
5633
+ }
5634
+ ]
5635
+ },
5562
5636
  "Exclude_SUPPORTED_VDI_FORMAT.qcow2_": {
5563
5637
  "type": "string",
5564
5638
  "enum": [
@@ -5835,18 +5909,18 @@
5835
5909
  "mode": {
5836
5910
  "$ref": "#/components/schemas/VBD_MODE"
5837
5911
  },
5838
- "bootable": {
5839
- "type": "boolean"
5840
- },
5841
- "empty": {
5842
- "type": "boolean"
5843
- },
5844
5912
  "qos_algorithm_params": {
5845
5913
  "$ref": "#/components/schemas/Record_string.string_"
5846
5914
  },
5847
5915
  "qos_algorithm_type": {
5848
5916
  "type": "string"
5849
5917
  },
5918
+ "bootable": {
5919
+ "type": "boolean"
5920
+ },
5921
+ "empty": {
5922
+ "type": "boolean"
5923
+ },
5850
5924
  "unpluggable": {
5851
5925
  "type": "boolean"
5852
5926
  },
@@ -7852,23 +7926,50 @@
7852
7926
  "type": "array"
7853
7927
  },
7854
7928
  "install": {
7855
- "properties": {
7856
- "repository": {
7857
- "type": "string"
7929
+ "anyOf": [
7930
+ {
7931
+ "properties": {
7932
+ "repository": {
7933
+ "type": "string"
7934
+ },
7935
+ "method": {
7936
+ "type": "string",
7937
+ "enum": [
7938
+ "cdrom"
7939
+ ],
7940
+ "nullable": false
7941
+ }
7942
+ },
7943
+ "required": [
7944
+ "repository",
7945
+ "method"
7946
+ ],
7947
+ "type": "object"
7858
7948
  },
7859
- "method": {
7860
- "type": "string",
7861
- "enum": [
7862
- "network",
7863
- "cdrom"
7864
- ]
7949
+ {
7950
+ "properties": {
7951
+ "repository": {
7952
+ "type": "string",
7953
+ "enum": [
7954
+ ""
7955
+ ],
7956
+ "nullable": false
7957
+ },
7958
+ "method": {
7959
+ "type": "string",
7960
+ "enum": [
7961
+ "network"
7962
+ ],
7963
+ "nullable": false
7964
+ }
7965
+ },
7966
+ "required": [
7967
+ "repository",
7968
+ "method"
7969
+ ],
7970
+ "type": "object"
7865
7971
  }
7866
- },
7867
- "required": [
7868
- "repository",
7869
- "method"
7870
- ],
7871
- "type": "object"
7972
+ ]
7872
7973
  },
7873
7974
  "cloud_config": {
7874
7975
  "type": "string"
@@ -12052,7 +12153,7 @@
12052
12153
  },
12053
12154
  "info": {
12054
12155
  "title": "@xen-orchestra/rest-api",
12055
- "version": "0.24.0",
12156
+ "version": "0.24.1",
12056
12157
  "description": "REST API to manage your XOA",
12057
12158
  "license": {
12058
12159
  "name": "AGPL-3.0-or-later"
@@ -18039,6 +18140,74 @@
18039
18140
  "example": 42
18040
18141
  }
18041
18142
  ]
18143
+ },
18144
+ "post": {
18145
+ "operationId": "CreateVif",
18146
+ "responses": {
18147
+ "201": {
18148
+ "description": "Resource created",
18149
+ "content": {
18150
+ "application/json": {
18151
+ "schema": {
18152
+ "properties": {
18153
+ "id": {
18154
+ "type": "string"
18155
+ }
18156
+ },
18157
+ "required": [
18158
+ "id"
18159
+ ],
18160
+ "type": "object"
18161
+ },
18162
+ "examples": {
18163
+ "Example 1": {
18164
+ "value": {
18165
+ "id": "f028c5d4-578a-332c-394e-087aaca32dd3"
18166
+ }
18167
+ }
18168
+ }
18169
+ }
18170
+ }
18171
+ },
18172
+ "400": {
18173
+ "description": "Bad request"
18174
+ },
18175
+ "401": {
18176
+ "description": "Authentication required"
18177
+ },
18178
+ "404": {
18179
+ "description": "Resource not found"
18180
+ },
18181
+ "422": {
18182
+ "description": "Invalid parameters"
18183
+ },
18184
+ "500": {
18185
+ "description": "Internal server error, XenServer/XCP-ng error"
18186
+ }
18187
+ },
18188
+ "tags": [
18189
+ "vifs"
18190
+ ],
18191
+ "security": [
18192
+ {
18193
+ "*": []
18194
+ }
18195
+ ],
18196
+ "parameters": [],
18197
+ "requestBody": {
18198
+ "required": true,
18199
+ "content": {
18200
+ "application/json": {
18201
+ "schema": {
18202
+ "$ref": "#/components/schemas/CreateVifBody"
18203
+ },
18204
+ "example": {
18205
+ "networkId": "6b6ca0f5-6611-0636-4b0a-1fb1c1e96414",
18206
+ "vmId": "613f541c-4bed-fc77-7ca8-2db6b68f079c"
18207
+ }
18208
+ }
18209
+ }
18210
+ }
18042
18211
  }
18043
18212
  },
18044
18213
  "/vifs/{id}": {
@@ -18107,6 +18276,45 @@
18107
18276
  "example": "f028c5d4-578a-332c-394e-087aaca32dd3"
18108
18277
  }
18109
18278
  ]
18279
+ },
18280
+ "delete": {
18281
+ "operationId": "DestroyVif",
18282
+ "responses": {
18283
+ "204": {
18284
+ "description": "No content"
18285
+ },
18286
+ "400": {
18287
+ "description": "Bad request"
18288
+ },
18289
+ "401": {
18290
+ "description": "Authentication required"
18291
+ },
18292
+ "404": {
18293
+ "description": "Resource not found"
18294
+ },
18295
+ "500": {
18296
+ "description": "Internal server error, XenServer/XCP-ng error"
18297
+ }
18298
+ },
18299
+ "tags": [
18300
+ "vifs"
18301
+ ],
18302
+ "security": [
18303
+ {
18304
+ "*": []
18305
+ }
18306
+ ],
18307
+ "parameters": [
18308
+ {
18309
+ "in": "path",
18310
+ "name": "id",
18311
+ "required": true,
18312
+ "schema": {
18313
+ "type": "string"
18314
+ },
18315
+ "example": "6b6ca0f5-6611-0636-4b0a-1fb1c1e96414"
18316
+ }
18317
+ ]
18110
18318
  }
18111
18319
  },
18112
18320
  "/vifs/{id}/alarms": {
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "main": "./dist/index.mjs",
7
7
  "name": "@xen-orchestra/rest-api",
8
8
  "homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/rest-api",
9
- "version": "0.24.0",
9
+ "version": "0.24.1",
10
10
  "description": "REST API to manage your XOA",
11
11
  "license": "AGPL-3.0-or-later",
12
12
  "private": false,
@@ -36,7 +36,7 @@
36
36
  "@vates/async-each": "^1.0.1",
37
37
  "@vates/task": "^0.6.1",
38
38
  "@vates/types": "^1.18.0",
39
- "@xen-orchestra/backups": "^0.68.0",
39
+ "@xen-orchestra/backups": "^0.68.1",
40
40
  "@xen-orchestra/log": "^0.7.1",
41
41
  "@xen-orchestra/xapi": "^8.6.5",
42
42
  "complex-matcher": "^1.0.0",