@xen-orchestra/rest-api 0.22.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,24 +7,50 @@ 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
+ import { json } from 'express';
11
12
  import { inject } from 'inversify';
13
+ import { invalidParameters as invalidParametersError } from 'xo-common/api-errors.js';
12
14
  import { provide } from 'inversify-binding-decorators';
13
15
  import { AlarmService } from '../alarms/alarm.service.mjs';
14
16
  import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
15
17
  import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.mjs';
16
- import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
17
- import { partialVbds, vbd, vbdIds } from '../open-api/oa-examples/vbd.oa-example.mjs';
18
+ import { asynchronousActionResp, badRequestResp, createdResp, internalServerErrorResp, invalidParameters, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
19
+ import { BASE_URL } from '../index.mjs';
20
+ import { partialVbds, vbd, vbdId, vbdIds } from '../open-api/oa-examples/vbd.oa-example.mjs';
18
21
  import { RestApi } from '../rest-api/rest-api.mjs';
19
22
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
20
23
  import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
21
- 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';
22
25
  let VbdController = class VbdController extends XapiXoController {
23
26
  #alarmService;
24
27
  constructor(restApi, alarmService) {
25
28
  super('VBD', restApi);
26
29
  this.#alarmService = alarmService;
27
30
  }
31
+ /**
32
+ * Create a VBD to attach a VDI to a VM
33
+ *
34
+ * @example body { "VM": "4fe90510-8da4-1530-38e2-a7876ef374c7", "VDI": "656052a2-2e3e-467b-88ba-63a9ea5e4a54", "bootable": false, "mode": "RW" }
35
+ */
36
+ async createVbd(body) {
37
+ const xoVm = this.restApi.getObject(body.VM, 'VM');
38
+ const xoVdi = this.restApi.getObject(body.VDI, 'VDI');
39
+ if (xoVm.$pool !== xoVdi.$pool) {
40
+ throw invalidParametersError('VM and VDI must be in the same pool');
41
+ }
42
+ const xapiVm = this.restApi.getXapiObject(xoVm.id, 'VM');
43
+ const xapiVdi = this.restApi.getXapiObject(xoVdi.id, 'VDI');
44
+ const xapi = xapiVm.$xapi;
45
+ const vbdRef = await xapi.VBD_create({
46
+ ...body,
47
+ VDI: xapiVdi.$ref,
48
+ VM: xapiVm.$ref,
49
+ });
50
+ const vbdUuid = await xapi.call('VBD.get_uuid', vbdRef);
51
+ this.setHeader('Location', `${BASE_URL}/vbds/${vbdUuid}`);
52
+ return { id: vbdUuid };
53
+ }
28
54
  /**
29
55
  *
30
56
  * @example fields "device,bootable,uuid"
@@ -41,6 +67,18 @@ let VbdController = class VbdController extends XapiXoController {
41
67
  getVbd(id) {
42
68
  return this.getObject(id);
43
69
  }
70
+ /**
71
+ * Delete a VBD
72
+ *
73
+ * Removes the virtual block device, detaching the VDI from the VM.
74
+ * The VDI itself is NOT deleted.
75
+ *
76
+ * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
77
+ */
78
+ async deleteVbd(id) {
79
+ const xapiVbd = this.getXapiObject(id);
80
+ await xapiVbd.$xapi.VBD_destroy(xapiVbd.$ref);
81
+ }
44
82
  /**
45
83
  * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
46
84
  * @example fields "id,time"
@@ -75,7 +113,54 @@ let VbdController = class VbdController extends XapiXoController {
75
113
  const tasks = await this.getTasksForObject(id, { filter, limit });
76
114
  return this.sendObjects(Object.values(tasks), req, 'tasks');
77
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
+ }
78
154
  };
155
+ __decorate([
156
+ Example(vbdId),
157
+ Post(''),
158
+ Middlewares(json()),
159
+ SuccessResponse(createdResp.status, createdResp.description),
160
+ Response(notFoundResp.status, notFoundResp.description),
161
+ Response(invalidParameters.status, invalidParameters.description),
162
+ __param(0, Body())
163
+ ], VbdController.prototype, "createVbd", null);
79
164
  __decorate([
80
165
  Example(vbdIds),
81
166
  Example(partialVbds),
@@ -92,6 +177,12 @@ __decorate([
92
177
  Response(notFoundResp.status, notFoundResp.description),
93
178
  __param(0, Path())
94
179
  ], VbdController.prototype, "getVbd", null);
180
+ __decorate([
181
+ Delete('{id}'),
182
+ SuccessResponse(noContentResp.status, noContentResp.description),
183
+ Response(notFoundResp.status, notFoundResp.description),
184
+ __param(0, Path())
185
+ ], VbdController.prototype, "deleteVbd", null);
95
186
  __decorate([
96
187
  Example(genericAlarmsExample),
97
188
  Get('{id}/alarms'),
@@ -130,6 +221,26 @@ __decorate([
130
221
  __param(4, Query()),
131
222
  __param(5, Query())
132
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);
133
244
  VbdController = __decorate([
134
245
  Route('vbds'),
135
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,7 +7,8 @@ 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, Post, Query, Request, Response, Route, Security, Tags, SuccessResponse, Body, Put, Delete, } from 'tsoa';
10
+ import { Example, Get, Path, Post, Query, Request, Response, Route, Security, Tags, SuccessResponse, Body, Put, Delete, Middlewares, } from 'tsoa';
11
+ import { json } from 'express';
11
12
  import { inject } from 'inversify';
12
13
  import { incorrectState, invalidParameters } from 'xo-common/api-errors.js';
13
14
  import { provide } from 'inversify-binding-decorators';
@@ -480,6 +481,7 @@ __decorate([
480
481
  __decorate([
481
482
  Example(taskLocation),
482
483
  Post('{id}/actions/start'),
484
+ Middlewares(json()),
483
485
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
484
486
  Response(noContentResp.status, noContentResp.description),
485
487
  Response(notFoundResp.status, notFoundResp.description),
@@ -21,13 +21,17 @@ export class VmService {
21
21
  this.#backupLogService = restApi.ioc.get(BackupLogService);
22
22
  }
23
23
  async #create($defer, params) {
24
- const { pool, template, cloud_config, boot, destroy_cloud_config_vdi, network_config, ...rest } = params;
24
+ const { pool, template, cloud_config, boot, destroy_cloud_config_vdi, network_config, createVtpm, ...rest } = params;
25
25
  const xoApp = this.#restApi.xoApp;
26
26
  const xapi = xoApp.getXapi(pool);
27
27
  const currentUser = this.#restApi.getCurrentUser();
28
28
  const xapiVm = await xapi.createVm(template, rest, undefined, currentUser.id);
29
29
  $defer.onFailure(() => xapi.VM_destroy(xapiVm.$ref));
30
30
  const xoVm = this.#restApi.getObject(xapiVm.uuid, 'VM');
31
+ if (createVtpm) {
32
+ const vtpmRef = await xapi.VTPM_create({ VM: xapiVm.$ref });
33
+ $defer.onFailure(() => xapi.call('VTPM.destroy', vtpmRef));
34
+ }
31
35
  let cloudConfigVdi;
32
36
  if (cloud_config !== undefined) {
33
37
  const cloudConfigVdiUuid = await xapi.VM_createCloudInitConfig(xapiVm.$ref, cloud_config, {
@@ -88,6 +92,8 @@ export class VmService {
88
92
  }
89
93
  }
90
94
  return {
95
+ active: nRunning + nPaused,
96
+ inactive: nHalted + nSuspended,
91
97
  running: nRunning,
92
98
  halted: nHalted,
93
99
  paused: nPaused,
@@ -103,7 +109,7 @@ export class VmService {
103
109
  for (const vbdId of vm.$VBDs) {
104
110
  const vbd = getObject(vbdId, 'VBD');
105
111
  if (vbd.VDI !== undefined) {
106
- const vdi = getObject(vbd.VDI, ['VDI-snapshot', 'VDI']);
112
+ const vdi = getObject(vbd.VDI, [vmType === 'VM-snapshot' ? 'VDI-snapshot' : 'VDI']);
107
113
  vdis.push(vdi);
108
114
  }
109
115
  }
@@ -182,7 +188,7 @@ export class VmService {
182
188
  }
183
189
  }
184
190
  if (lastReplica === undefined) {
185
- return;
191
+ return {};
186
192
  }
187
193
  const vdis = this.getVmVdis(id, 'VM');
188
194
  let sr = undefined;
@@ -239,7 +245,7 @@ export class VmService {
239
245
  status,
240
246
  };
241
247
  });
242
- let vmProtection = 'not-in-job';
248
+ let vmProtection = 'not-in-active-job';
243
249
  if (!vmContainsNoBakTag(vm)) {
244
250
  const backupLogsByJob = groupBy(backupLogs, 'jobId');
245
251
  for (const backupJob of relevantJobsWithSchedule) {
@@ -272,7 +278,7 @@ export class VmService {
272
278
  const backupArchivesByVmByBr = await this.#restApi.xoApp.listVmBackupsNg(brIds, { vmId: vm.id });
273
279
  return Object.values(backupArchivesByVmByBr)
274
280
  .filter(backupArchiveByVm => backupArchiveByVm !== undefined)
275
- .flatMap(backupArchiveByVm => backupArchiveByVm[vm.id])
281
+ .flatMap(backupArchiveByVm => backupArchiveByVm[vm.id] ?? [])
276
282
  .sort((a, b) => b.timestamp - a.timestamp)
277
283
  .splice(0, 3)
278
284
  .map(ba => ({ id: ba.id, timestamp: ba.timestamp, backupRepository: ba.backupRepository, size: ba.size }));