@xen-orchestra/rest-api 0.23.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": {} },
@@ -503,6 +518,11 @@ const models = {
503
518
  "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "$pool": { "dataType": "string", "required": true }, "$poolId": { "dataType": "string", "required": true }, "_xapiRef": { "dataType": "string", "required": true }, "uuid": { "dataType": "string", "required": true }, "$SR": { "dataType": "string", "required": true }, "$VBDs": { "dataType": "array", "array": { "dataType": "string" }, "required": true }, "VDI_type": { "ref": "VDI_TYPE", "required": true }, "cbt_enabled": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "current_operations": { "ref": "Record_string.VDI_OPERATIONS_", "required": true }, "missing": { "dataType": "boolean", "required": true }, "name_description": { "dataType": "string", "required": true }, "name_label": { "dataType": "string", "required": true }, "other_config": { "ref": "Record_string.string_", "required": true }, "parent": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "image_format": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "size": { "dataType": "double", "required": true }, "snapshots": { "dataType": "array", "array": { "dataType": "string" }, "required": true }, "tags": { "dataType": "array", "array": { "dataType": "string" }, "required": true }, "usage": { "dataType": "double", "required": true }, "id": { "dataType": "string", "required": true }, "type": { "dataType": "enum", "enums": ["VDI"], "required": true } }, "validators": {} },
504
519
  },
505
520
  // 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
521
+ "CreateActionReturnType__id-Unbrand_XoVdi__91_id_93___": {
522
+ "dataType": "refAlias",
523
+ "type": { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "taskId": { "dataType": "string", "required": true } } }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "id": { "dataType": "string", "required": true } } }], "validators": {} },
524
+ },
525
+ // 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
526
  "Unbrand_XoVdiSnapshot_": {
507
527
  "dataType": "refAlias",
508
528
  "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "$pool": { "dataType": "string", "required": true }, "$poolId": { "dataType": "string", "required": true }, "_xapiRef": { "dataType": "string", "required": true }, "uuid": { "dataType": "string", "required": true }, "$SR": { "dataType": "string", "required": true }, "$VBDs": { "dataType": "array", "array": { "dataType": "string" }, "required": true }, "VDI_type": { "ref": "VDI_TYPE", "required": true }, "cbt_enabled": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "current_operations": { "ref": "Record_string.VDI_OPERATIONS_", "required": true }, "missing": { "dataType": "boolean", "required": true }, "name_description": { "dataType": "string", "required": true }, "name_label": { "dataType": "string", "required": true }, "other_config": { "ref": "Record_string.string_", "required": true }, "parent": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "image_format": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "size": { "dataType": "double", "required": true }, "snapshots": { "dataType": "array", "array": { "dataType": "string" }, "required": true }, "tags": { "dataType": "array", "array": { "dataType": "string" }, "required": true }, "usage": { "dataType": "double", "required": true }, "id": { "dataType": "string", "required": true }, "snapshot_time": { "dataType": "double", "required": true }, "$snapshot_of": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "type": { "dataType": "enum", "enums": ["VDI-snapshot"], "required": true } }, "validators": {} },
@@ -520,7 +540,7 @@ const models = {
520
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
521
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__": {
522
542
  "dataType": "refAlias",
523
- "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": {} },
524
544
  },
525
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
526
546
  "Omit_Unbrand_Parameters_Xapi_91_VBD_create_93___91_0_93__.VM-or-VDI_": {
@@ -802,7 +822,7 @@ const models = {
802
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
803
823
  "Unbrand_CreateVmBody_": {
804
824
  "dataType": "refAlias",
805
- "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": {} },
806
826
  },
807
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
808
828
  "XapiHostStatsRaw": {
@@ -3049,6 +3069,60 @@ export function RegisterRoutes(app) {
3049
3069
  }
3050
3070
  });
3051
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
3052
3126
  const argsVdiController_getVdis = {
3053
3127
  req: { "in": "request", "name": "req", "required": true, "dataType": "object" },
3054
3128
  fields: { "in": "query", "name": "fields", "dataType": "string" },
@@ -3288,6 +3362,35 @@ export function RegisterRoutes(app) {
3288
3362
  }
3289
3363
  });
3290
3364
  // 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
3365
+ const argsVdiController_migrateVdi = {
3366
+ id: { "in": "path", "name": "id", "required": true, "dataType": "string" },
3367
+ body: { "in": "body", "name": "body", "required": true, "dataType": "nestedObjectLiteral", "nestedProperties": { "srId": { "dataType": "string", "required": true } } },
3368
+ sync: { "in": "query", "name": "sync", "dataType": "boolean" },
3369
+ };
3370
+ app.post('/rest/v0/vdis/:id/actions/migrate', authenticateMiddleware([{ "*": [] }]), ...(fetchMiddlewares(VdiController)), ...(fetchMiddlewares(VdiController.prototype.migrateVdi)), async function VdiController_migrateVdi(request, response, next) {
3371
+ // 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
3372
+ let validatedArgs = [];
3373
+ try {
3374
+ validatedArgs = templateService.getValidatedArgs({ args: argsVdiController_migrateVdi, request, response });
3375
+ const container = typeof iocContainer === 'function' ? iocContainer(request) : iocContainer;
3376
+ const controller = await container.get(VdiController);
3377
+ if (typeof controller['setStatus'] === 'function') {
3378
+ controller.setStatus(undefined);
3379
+ }
3380
+ await templateService.apiHandler({
3381
+ methodName: 'migrateVdi',
3382
+ controller,
3383
+ response,
3384
+ next,
3385
+ validatedArgs,
3386
+ successStatus: 202,
3387
+ });
3388
+ }
3389
+ catch (err) {
3390
+ return next(err);
3391
+ }
3392
+ });
3393
+ // 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
3291
3394
  const argsVdiController_putVdiTag = {
3292
3395
  id: { "in": "path", "name": "id", "required": true, "dataType": "string" },
3293
3396
  tag: { "in": "path", "name": "tag", "required": true, "dataType": "string" },
@@ -3818,6 +3921,62 @@ export function RegisterRoutes(app) {
3818
3921
  }
3819
3922
  });
3820
3923
  // 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
3924
+ const argsVbdController_connectVbd = {
3925
+ id: { "in": "path", "name": "id", "required": true, "dataType": "string" },
3926
+ sync: { "in": "query", "name": "sync", "dataType": "boolean" },
3927
+ };
3928
+ app.post('/rest/v0/vbds/:id/actions/connect', authenticateMiddleware([{ "*": [] }]), ...(fetchMiddlewares(VbdController)), ...(fetchMiddlewares(VbdController.prototype.connectVbd)), async function VbdController_connectVbd(request, response, next) {
3929
+ // 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
3930
+ let validatedArgs = [];
3931
+ try {
3932
+ validatedArgs = templateService.getValidatedArgs({ args: argsVbdController_connectVbd, request, response });
3933
+ const container = typeof iocContainer === 'function' ? iocContainer(request) : iocContainer;
3934
+ const controller = await container.get(VbdController);
3935
+ if (typeof controller['setStatus'] === 'function') {
3936
+ controller.setStatus(undefined);
3937
+ }
3938
+ await templateService.apiHandler({
3939
+ methodName: 'connectVbd',
3940
+ controller,
3941
+ response,
3942
+ next,
3943
+ validatedArgs,
3944
+ successStatus: 202,
3945
+ });
3946
+ }
3947
+ catch (err) {
3948
+ return next(err);
3949
+ }
3950
+ });
3951
+ // 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
3952
+ const argsVbdController_disconnectVbd = {
3953
+ id: { "in": "path", "name": "id", "required": true, "dataType": "string" },
3954
+ sync: { "in": "query", "name": "sync", "dataType": "boolean" },
3955
+ };
3956
+ app.post('/rest/v0/vbds/:id/actions/disconnect', authenticateMiddleware([{ "*": [] }]), ...(fetchMiddlewares(VbdController)), ...(fetchMiddlewares(VbdController.prototype.disconnectVbd)), async function VbdController_disconnectVbd(request, response, next) {
3957
+ // 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
3958
+ let validatedArgs = [];
3959
+ try {
3960
+ validatedArgs = templateService.getValidatedArgs({ args: argsVbdController_disconnectVbd, request, response });
3961
+ const container = typeof iocContainer === 'function' ? iocContainer(request) : iocContainer;
3962
+ const controller = await container.get(VbdController);
3963
+ if (typeof controller['setStatus'] === 'function') {
3964
+ controller.setStatus(undefined);
3965
+ }
3966
+ await templateService.apiHandler({
3967
+ methodName: 'disconnectVbd',
3968
+ controller,
3969
+ response,
3970
+ next,
3971
+ validatedArgs,
3972
+ successStatus: 202,
3973
+ });
3974
+ }
3975
+ catch (err) {
3976
+ return next(err);
3977
+ }
3978
+ });
3979
+ // 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
3821
3980
  const argsUserController_getUsers = {
3822
3981
  req: { "in": "request", "name": "req", "required": true, "dataType": "object" },
3823
3982
  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) {
@@ -15,13 +15,13 @@ import { provide } from 'inversify-binding-decorators';
15
15
  import { AlarmService } from '../alarms/alarm.service.mjs';
16
16
  import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
17
17
  import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.mjs';
18
- import { badRequestResp, createdResp, invalidParameters, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
18
+ import { asynchronousActionResp, badRequestResp, createdResp, internalServerErrorResp, invalidParameters, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
19
19
  import { BASE_URL } from '../index.mjs';
20
20
  import { partialVbds, vbd, vbdId, vbdIds } from '../open-api/oa-examples/vbd.oa-example.mjs';
21
21
  import { RestApi } from '../rest-api/rest-api.mjs';
22
22
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
23
23
  import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
24
- import { taskIds, partialTasks } from '../open-api/oa-examples/task.oa-example.mjs';
24
+ import { taskIds, taskLocation, partialTasks } from '../open-api/oa-examples/task.oa-example.mjs';
25
25
  let VbdController = class VbdController extends XapiXoController {
26
26
  #alarmService;
27
27
  constructor(restApi, alarmService) {
@@ -113,6 +113,44 @@ let VbdController = class VbdController extends XapiXoController {
113
113
  const tasks = await this.getTasksForObject(id, { filter, limit });
114
114
  return this.sendObjects(Object.values(tasks), req, 'tasks');
115
115
  }
116
+ /**
117
+ * Hotplug the VBD, dynamically attaching it to the running VM
118
+ * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
119
+ */
120
+ connectVbd(id, sync) {
121
+ const vbdId = id;
122
+ const action = async () => {
123
+ const xapiVbd = this.getXapiObject(vbdId);
124
+ await xapiVbd.$xapi.callAsync('VBD.plug', xapiVbd.$ref);
125
+ };
126
+ return this.createAction(action, {
127
+ sync,
128
+ statusCode: noContentResp.status,
129
+ taskProperties: {
130
+ name: 'connect VBD',
131
+ objectId: vbdId,
132
+ },
133
+ });
134
+ }
135
+ /**
136
+ * Hot-unplug the VBD, dynamically detaching it from the running VM
137
+ * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
138
+ */
139
+ disconnectVbd(id, sync) {
140
+ const vbdId = id;
141
+ const action = async () => {
142
+ const xapiVbd = this.getXapiObject(vbdId);
143
+ await xapiVbd.$xapi.VBD_unplug(xapiVbd.$ref);
144
+ };
145
+ return this.createAction(action, {
146
+ sync,
147
+ statusCode: noContentResp.status,
148
+ taskProperties: {
149
+ name: 'disconnect VBD',
150
+ objectId: vbdId,
151
+ },
152
+ });
153
+ }
116
154
  };
117
155
  __decorate([
118
156
  Example(vbdId),
@@ -183,6 +221,26 @@ __decorate([
183
221
  __param(4, Query()),
184
222
  __param(5, Query())
185
223
  ], VbdController.prototype, "getVbdTasks", null);
224
+ __decorate([
225
+ Example(taskLocation),
226
+ Post('{id}/actions/connect'),
227
+ SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
228
+ Response(noContentResp.status, noContentResp.description),
229
+ Response(notFoundResp.status, notFoundResp.description),
230
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
231
+ __param(0, Path()),
232
+ __param(1, Query())
233
+ ], VbdController.prototype, "connectVbd", null);
234
+ __decorate([
235
+ Example(taskLocation),
236
+ Post('{id}/actions/disconnect'),
237
+ SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
238
+ Response(noContentResp.status, noContentResp.description),
239
+ Response(notFoundResp.status, notFoundResp.description),
240
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
241
+ __param(0, Path()),
242
+ __param(1, Query())
243
+ ], VbdController.prototype, "disconnectVbd", null);
186
244
  VbdController = __decorate([
187
245
  Route('vbds'),
188
246
  Security('*'),
@@ -7,19 +7,20 @@ 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 { Delete, Example, Get, Path, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags } from 'tsoa';
10
+ import { Body, Delete, Example, Get, Middlewares, Path, Post, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags, } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
12
  import { provide } from 'inversify-binding-decorators';
13
+ import { json } from 'express';
13
14
  import { AlarmService } from '../alarms/alarm.service.mjs';
14
15
  import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
15
16
  import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.mjs';
16
- import { badRequestResp, internalServerErrorResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
17
+ import { asynchronousActionResp, badRequestResp, internalServerErrorResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
17
18
  import { RestApi } from '../rest-api/rest-api.mjs';
18
19
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
19
- import { partialVdis, vdi, vdiIds } from '../open-api/oa-examples/vdi.oa-example.mjs';
20
+ import { partialVdis, vdi, vdiId, vdiIds } from '../open-api/oa-examples/vdi.oa-example.mjs';
20
21
  import { VdiService } from './vdi.service.mjs';
21
22
  import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
22
- import { taskIds, partialTasks } from '../open-api/oa-examples/task.oa-example.mjs';
23
+ import { taskIds, partialTasks, taskLocation } from '../open-api/oa-examples/task.oa-example.mjs';
23
24
  let VdiController = class VdiController extends XapiXoController {
24
25
  #alarmService;
25
26
  #vdiService;
@@ -110,6 +111,27 @@ let VdiController = class VdiController extends XapiXoController {
110
111
  const tasks = await this.getTasksForObject(id, { filter, limit });
111
112
  return this.sendObjects(Object.values(tasks), req, 'tasks');
112
113
  }
114
+ /**
115
+ * Migrate a VDI to another SR.
116
+ *
117
+ * Note: After migration, the VDI will have a new ID. The new ID is returned in the response.
118
+ *
119
+ * @example id "c77f9955-c1d2-4b39-aa1c-73cdb2dacb7e"
120
+ * @example body { "srId": "4cb0d74e-a7c1-0b7d-46e3-09382c012abb" }
121
+ */
122
+ async migrateVdi(id, body, sync) {
123
+ const vdiId = id;
124
+ return this.createAction(async () => {
125
+ const newVdi = await this.getXapi(vdiId).moveVdi(vdiId, body.srId);
126
+ return { id: newVdi.uuid };
127
+ }, {
128
+ sync,
129
+ taskProperties: {
130
+ name: 'migrate VDI',
131
+ objectId: vdiId,
132
+ },
133
+ });
134
+ }
113
135
  /**
114
136
  * @example id "c77f9955-c1d2-4b39-aa1c-73cdb2dacb7e"
115
137
  * @example tag "from-rest-api"
@@ -206,6 +228,19 @@ __decorate([
206
228
  __param(4, Query()),
207
229
  __param(5, Query())
208
230
  ], VdiController.prototype, "getVdiTasks", null);
231
+ __decorate([
232
+ Example(taskLocation),
233
+ Example(vdiId),
234
+ Post('{id}/actions/migrate'),
235
+ Middlewares(json()),
236
+ SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
237
+ Response(200, 'Ok'),
238
+ Response(notFoundResp.status, notFoundResp.description),
239
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
240
+ __param(0, Path()),
241
+ __param(1, Body()),
242
+ __param(2, Query())
243
+ ], VdiController.prototype, "migrateVdi", null);
209
244
  __decorate([
210
245
  Put('{id}/tags/{tag}'),
211
246
  SuccessResponse(noContentResp.status, noContentResp.description),
@@ -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": [
@@ -5671,6 +5745,32 @@
5671
5745
  ],
5672
5746
  "type": "object"
5673
5747
  },
5748
+ "CreateActionReturnType__id-Unbrand_XoVdi__91_id_93___": {
5749
+ "anyOf": [
5750
+ {
5751
+ "properties": {
5752
+ "taskId": {
5753
+ "type": "string"
5754
+ }
5755
+ },
5756
+ "required": [
5757
+ "taskId"
5758
+ ],
5759
+ "type": "object"
5760
+ },
5761
+ {
5762
+ "properties": {
5763
+ "id": {
5764
+ "type": "string"
5765
+ }
5766
+ },
5767
+ "required": [
5768
+ "id"
5769
+ ],
5770
+ "type": "object"
5771
+ }
5772
+ ]
5773
+ },
5674
5774
  "Unbrand_XoVdiSnapshot_": {
5675
5775
  "properties": {
5676
5776
  "$pool": {
@@ -5809,18 +5909,18 @@
5809
5909
  "mode": {
5810
5910
  "$ref": "#/components/schemas/VBD_MODE"
5811
5911
  },
5812
- "bootable": {
5813
- "type": "boolean"
5814
- },
5815
- "empty": {
5816
- "type": "boolean"
5817
- },
5818
5912
  "qos_algorithm_params": {
5819
5913
  "$ref": "#/components/schemas/Record_string.string_"
5820
5914
  },
5821
5915
  "qos_algorithm_type": {
5822
5916
  "type": "string"
5823
5917
  },
5918
+ "bootable": {
5919
+ "type": "boolean"
5920
+ },
5921
+ "empty": {
5922
+ "type": "boolean"
5923
+ },
5824
5924
  "unpluggable": {
5825
5925
  "type": "boolean"
5826
5926
  },
@@ -7826,23 +7926,50 @@
7826
7926
  "type": "array"
7827
7927
  },
7828
7928
  "install": {
7829
- "properties": {
7830
- "repository": {
7831
- "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"
7832
7948
  },
7833
- "method": {
7834
- "type": "string",
7835
- "enum": [
7836
- "network",
7837
- "cdrom"
7838
- ]
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"
7839
7971
  }
7840
- },
7841
- "required": [
7842
- "repository",
7843
- "method"
7844
- ],
7845
- "type": "object"
7972
+ ]
7846
7973
  },
7847
7974
  "cloud_config": {
7848
7975
  "type": "string"
@@ -12026,7 +12153,7 @@
12026
12153
  },
12027
12154
  "info": {
12028
12155
  "title": "@xen-orchestra/rest-api",
12029
- "version": "0.23.0",
12156
+ "version": "0.24.1",
12030
12157
  "description": "REST API to manage your XOA",
12031
12158
  "license": {
12032
12159
  "name": "AGPL-3.0-or-later"
@@ -18013,6 +18140,74 @@
18013
18140
  "example": 42
18014
18141
  }
18015
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
+ }
18016
18211
  }
18017
18212
  },
18018
18213
  "/vifs/{id}": {
@@ -18081,6 +18276,45 @@
18081
18276
  "example": "f028c5d4-578a-332c-394e-087aaca32dd3"
18082
18277
  }
18083
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
+ ]
18084
18318
  }
18085
18319
  },
18086
18320
  "/vifs/{id}/alarms": {
@@ -18981,6 +19215,99 @@
18981
19215
  ]
18982
19216
  }
18983
19217
  },
19218
+ "/vdis/{id}/actions/migrate": {
19219
+ "post": {
19220
+ "operationId": "MigrateVdi",
19221
+ "responses": {
19222
+ "200": {
19223
+ "description": "Ok"
19224
+ },
19225
+ "202": {
19226
+ "description": "Action executed asynchronously",
19227
+ "content": {
19228
+ "application/json": {
19229
+ "schema": {
19230
+ "$ref": "#/components/schemas/CreateActionReturnType__id-Unbrand_XoVdi__91_id_93___"
19231
+ },
19232
+ "examples": {
19233
+ "Example 1": {
19234
+ "value": {
19235
+ "taskId": "0m7kl0j9l"
19236
+ }
19237
+ },
19238
+ "Example 2": {
19239
+ "value": {
19240
+ "id": "5e13f673-760e-41be-826e-620d16b7f43b"
19241
+ }
19242
+ }
19243
+ }
19244
+ }
19245
+ }
19246
+ },
19247
+ "400": {
19248
+ "description": "Bad request"
19249
+ },
19250
+ "401": {
19251
+ "description": "Authentication required"
19252
+ },
19253
+ "404": {
19254
+ "description": "Resource not found"
19255
+ },
19256
+ "500": {
19257
+ "description": "Internal server error, XenServer/XCP-ng error"
19258
+ }
19259
+ },
19260
+ "description": "Migrate a VDI to another SR.\n\nNote: After migration, the VDI will have a new ID. The new ID is returned in the response.",
19261
+ "tags": [
19262
+ "vdis"
19263
+ ],
19264
+ "security": [
19265
+ {
19266
+ "*": []
19267
+ }
19268
+ ],
19269
+ "parameters": [
19270
+ {
19271
+ "in": "path",
19272
+ "name": "id",
19273
+ "required": true,
19274
+ "schema": {
19275
+ "type": "string"
19276
+ },
19277
+ "example": "c77f9955-c1d2-4b39-aa1c-73cdb2dacb7e"
19278
+ },
19279
+ {
19280
+ "in": "query",
19281
+ "name": "sync",
19282
+ "required": false,
19283
+ "schema": {
19284
+ "type": "boolean"
19285
+ }
19286
+ }
19287
+ ],
19288
+ "requestBody": {
19289
+ "required": true,
19290
+ "content": {
19291
+ "application/json": {
19292
+ "schema": {
19293
+ "properties": {
19294
+ "srId": {
19295
+ "type": "string"
19296
+ }
19297
+ },
19298
+ "required": [
19299
+ "srId"
19300
+ ],
19301
+ "type": "object"
19302
+ },
19303
+ "example": {
19304
+ "srId": "4cb0d74e-a7c1-0b7d-46e3-09382c012abb"
19305
+ }
19306
+ }
19307
+ }
19308
+ }
19309
+ }
19310
+ },
18984
19311
  "/vdis/{id}/tags/{tag}": {
18985
19312
  "put": {
18986
19313
  "operationId": "PutVdiTag",
@@ -20357,6 +20684,140 @@
20357
20684
  ]
20358
20685
  }
20359
20686
  },
20687
+ "/vbds/{id}/actions/connect": {
20688
+ "post": {
20689
+ "operationId": "ConnectVbd",
20690
+ "responses": {
20691
+ "202": {
20692
+ "description": "Action executed asynchronously",
20693
+ "content": {
20694
+ "application/json": {
20695
+ "schema": {
20696
+ "$ref": "#/components/schemas/CreateActionReturnType_void_"
20697
+ },
20698
+ "examples": {
20699
+ "Example 1": {
20700
+ "value": {
20701
+ "taskId": "0m7kl0j9l"
20702
+ }
20703
+ }
20704
+ }
20705
+ }
20706
+ }
20707
+ },
20708
+ "204": {
20709
+ "description": "No content"
20710
+ },
20711
+ "400": {
20712
+ "description": "Bad request"
20713
+ },
20714
+ "401": {
20715
+ "description": "Authentication required"
20716
+ },
20717
+ "404": {
20718
+ "description": "Resource not found"
20719
+ },
20720
+ "500": {
20721
+ "description": "Internal server error, XenServer/XCP-ng error"
20722
+ }
20723
+ },
20724
+ "description": "Hotplug the VBD, dynamically attaching it to the running VM",
20725
+ "tags": [
20726
+ "vbds"
20727
+ ],
20728
+ "security": [
20729
+ {
20730
+ "*": []
20731
+ }
20732
+ ],
20733
+ "parameters": [
20734
+ {
20735
+ "in": "path",
20736
+ "name": "id",
20737
+ "required": true,
20738
+ "schema": {
20739
+ "type": "string"
20740
+ },
20741
+ "example": "f07ab729-c0e8-721c-45ec-f11276377030"
20742
+ },
20743
+ {
20744
+ "in": "query",
20745
+ "name": "sync",
20746
+ "required": false,
20747
+ "schema": {
20748
+ "type": "boolean"
20749
+ }
20750
+ }
20751
+ ]
20752
+ }
20753
+ },
20754
+ "/vbds/{id}/actions/disconnect": {
20755
+ "post": {
20756
+ "operationId": "DisconnectVbd",
20757
+ "responses": {
20758
+ "202": {
20759
+ "description": "Action executed asynchronously",
20760
+ "content": {
20761
+ "application/json": {
20762
+ "schema": {
20763
+ "$ref": "#/components/schemas/CreateActionReturnType_void_"
20764
+ },
20765
+ "examples": {
20766
+ "Example 1": {
20767
+ "value": {
20768
+ "taskId": "0m7kl0j9l"
20769
+ }
20770
+ }
20771
+ }
20772
+ }
20773
+ }
20774
+ },
20775
+ "204": {
20776
+ "description": "No content"
20777
+ },
20778
+ "400": {
20779
+ "description": "Bad request"
20780
+ },
20781
+ "401": {
20782
+ "description": "Authentication required"
20783
+ },
20784
+ "404": {
20785
+ "description": "Resource not found"
20786
+ },
20787
+ "500": {
20788
+ "description": "Internal server error, XenServer/XCP-ng error"
20789
+ }
20790
+ },
20791
+ "description": "Hot-unplug the VBD, dynamically detaching it from the running VM",
20792
+ "tags": [
20793
+ "vbds"
20794
+ ],
20795
+ "security": [
20796
+ {
20797
+ "*": []
20798
+ }
20799
+ ],
20800
+ "parameters": [
20801
+ {
20802
+ "in": "path",
20803
+ "name": "id",
20804
+ "required": true,
20805
+ "schema": {
20806
+ "type": "string"
20807
+ },
20808
+ "example": "f07ab729-c0e8-721c-45ec-f11276377030"
20809
+ },
20810
+ {
20811
+ "in": "query",
20812
+ "name": "sync",
20813
+ "required": false,
20814
+ "schema": {
20815
+ "type": "boolean"
20816
+ }
20817
+ }
20818
+ ]
20819
+ }
20820
+ },
20360
20821
  "/users": {
20361
20822
  "get": {
20362
20823
  "operationId": "GetUsers",
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.23.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,
@@ -35,10 +35,10 @@
35
35
  "dependencies": {
36
36
  "@vates/async-each": "^1.0.1",
37
37
  "@vates/task": "^0.6.1",
38
- "@vates/types": "^1.17.0",
39
- "@xen-orchestra/backups": "^0.68.0",
38
+ "@vates/types": "^1.18.0",
39
+ "@xen-orchestra/backups": "^0.68.1",
40
40
  "@xen-orchestra/log": "^0.7.1",
41
- "@xen-orchestra/xapi": "^8.6.4",
41
+ "@xen-orchestra/xapi": "^8.6.5",
42
42
  "complex-matcher": "^1.0.0",
43
43
  "golike-defer": "^0.5.1",
44
44
  "inversify": "^6.2.2",