@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.
- package/dist/hosts/host.controller.mjs +48 -3
- package/dist/open-api/oa-examples/vbd.oa-example.mjs +3 -0
- package/dist/open-api/oa-examples/xoa.oa-example.mjs +30 -40
- package/dist/open-api/routes/routes.js +263 -21
- package/dist/pools/pool.controller.mjs +46 -1
- package/dist/vbds/vbd.controller.mjs +115 -4
- package/dist/vdis/vdi.controller.mjs +39 -4
- package/dist/vms/vm.controller.mjs +3 -1
- package/dist/vms/vm.service.mjs +11 -5
- package/dist/xoa/xoa.service.mjs +112 -91
- package/open-api/spec/swagger.json +1183 -635
- package/package.json +4 -4
|
@@ -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 {
|
|
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),
|
package/dist/vms/vm.service.mjs
CHANGED
|
@@ -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'
|
|
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 }));
|