@xen-orchestra/rest-api 0.9.0 → 0.11.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/abstract-classes/base-controller.mjs +26 -1
- package/dist/alarms/alarm.controller.mjs +1 -1
- package/dist/helpers/utils.helper.mjs +1 -0
- package/dist/hosts/host.controller.mjs +30 -1
- package/dist/ioc/ioc.mjs +8 -0
- package/dist/messages/message.controller.mjs +1 -1
- package/dist/middlewares/generic-error-handler.middleware.mjs +15 -11
- package/dist/middlewares/tsoa-to-xo-error.middleware.mjs +1 -1
- package/dist/open-api/oa-examples/pool.oa-example.mjs +204 -0
- package/dist/open-api/oa-examples/sm.oa-example.mjs +58 -0
- package/dist/open-api/routes/routes.js +373 -23
- package/dist/pools/pool.controller.mjs +93 -3
- package/dist/rest-api/rest-api.mjs +3 -0
- package/dist/sms/sm.controller.mjs +60 -0
- package/dist/vms/vm.controller.mjs +127 -4
- package/dist/vms/vm.service.mjs +47 -0
- package/dist/xoa/xoa.controller.mjs +21 -5
- package/dist/xoa/xoa.service.mjs +103 -22
- package/open-api/spec/swagger.json +3854 -697
- package/package.json +5 -3
- package/tsoa.json +20 -0
|
@@ -14,12 +14,16 @@ import { json } from 'express';
|
|
|
14
14
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
15
15
|
import { asynchronousActionResp, createdResp, featureUnauthorized, internalServerErrorResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
|
|
16
16
|
import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
|
|
17
|
-
import { partialPools, pool, poolIds } from '../open-api/oa-examples/pool.oa-example.mjs';
|
|
17
|
+
import { createVm, importVm, partialPools, pool, poolIds, poolStats } from '../open-api/oa-examples/pool.oa-example.mjs';
|
|
18
18
|
import { taskLocation } from '../open-api/oa-examples/task.oa-example.mjs';
|
|
19
19
|
import { createNetwork } from '../open-api/oa-examples/schedule.oa-example.mjs';
|
|
20
|
+
import { BASE_URL } from '../index.mjs';
|
|
21
|
+
import { VmService } from '../vms/vm.service.mjs';
|
|
20
22
|
let PoolController = class PoolController extends XapiXoController {
|
|
21
|
-
|
|
23
|
+
#vmService;
|
|
24
|
+
constructor(restApi, vmService) {
|
|
22
25
|
super('pool', restApi);
|
|
26
|
+
this.#vmService = vmService;
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
29
|
*
|
|
@@ -120,6 +124,58 @@ let PoolController = class PoolController extends XapiXoController {
|
|
|
120
124
|
},
|
|
121
125
|
});
|
|
122
126
|
}
|
|
127
|
+
// For this endpoint, the requestBody type is written directly to `tsoa.json` because TSOA does not provide a decorator for "octet-stream" file uploads
|
|
128
|
+
/**
|
|
129
|
+
* Import an XVA VM into a pool
|
|
130
|
+
*
|
|
131
|
+
* @example id "355ee47d-ff4c-4924-3db2-fd86ae629677"
|
|
132
|
+
* @example sr "c787b75c-3e0d-70fa-d0c3-cbfd382d7e33"
|
|
133
|
+
*
|
|
134
|
+
*/
|
|
135
|
+
async importVm(req, id, sr) {
|
|
136
|
+
const pool = this.getXapiObject(id);
|
|
137
|
+
const xapi = pool.$xapi;
|
|
138
|
+
let srRef;
|
|
139
|
+
if (sr !== undefined) {
|
|
140
|
+
srRef = this.restApi.getXapiObject(sr, 'SR').$ref;
|
|
141
|
+
}
|
|
142
|
+
const vmRef = await xapi.VM_import(req, srRef);
|
|
143
|
+
const vmId = await xapi.getField('VM', vmRef, 'uuid');
|
|
144
|
+
this.setHeader('Location', `${BASE_URL}/vms/${vmId}`);
|
|
145
|
+
return { id: vmId };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* @example id "355ee47d-ff4c-4924-3db2-fd86ae629677"
|
|
149
|
+
* @example body {
|
|
150
|
+
* "name_label": "new VM from REST API",
|
|
151
|
+
* "template": "9bbcc5d1-ad4b-06f1-18f6-03125e809c38",
|
|
152
|
+
* "boot": true
|
|
153
|
+
* }
|
|
154
|
+
*/
|
|
155
|
+
async createVm(id, body, sync) {
|
|
156
|
+
const poolId = id;
|
|
157
|
+
const action = async () => {
|
|
158
|
+
const { affinity, template, ...rest } = body;
|
|
159
|
+
const params = { affinityHost: affinity, ...rest };
|
|
160
|
+
const vmId = await this.#vmService.create({ pool: poolId, template, ...params });
|
|
161
|
+
return { id: vmId };
|
|
162
|
+
};
|
|
163
|
+
return this.createAction(action, {
|
|
164
|
+
sync,
|
|
165
|
+
statusCode: createdResp.status,
|
|
166
|
+
taskProperties: {
|
|
167
|
+
args: body,
|
|
168
|
+
name: 'create VM',
|
|
169
|
+
objectId: poolId,
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* @example id "355ee47d-ff4c-4924-3db2-fd86ae629677"
|
|
175
|
+
*/
|
|
176
|
+
getStats(id, granularity) {
|
|
177
|
+
return this.restApi.xoApp.getXapiPoolStats(id, granularity);
|
|
178
|
+
}
|
|
123
179
|
};
|
|
124
180
|
__decorate([
|
|
125
181
|
Example(poolIds),
|
|
@@ -181,12 +237,46 @@ __decorate([
|
|
|
181
237
|
__param(0, Path()),
|
|
182
238
|
__param(1, Query())
|
|
183
239
|
], PoolController.prototype, "rollingUpdate", null);
|
|
240
|
+
__decorate([
|
|
241
|
+
Example(importVm),
|
|
242
|
+
Post('{id}/vms'),
|
|
243
|
+
Tags('vms'),
|
|
244
|
+
SuccessResponse(createdResp.status, 'VM imported'),
|
|
245
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
246
|
+
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
247
|
+
__param(0, Request()),
|
|
248
|
+
__param(1, Path()),
|
|
249
|
+
__param(2, Query())
|
|
250
|
+
], PoolController.prototype, "importVm", null);
|
|
251
|
+
__decorate([
|
|
252
|
+
Example(taskLocation),
|
|
253
|
+
Example(createVm),
|
|
254
|
+
Post('{id}/actions/create_vm'),
|
|
255
|
+
Middlewares(json()),
|
|
256
|
+
Tags('vms'),
|
|
257
|
+
SuccessResponse(createdResp.status, createdResp.description),
|
|
258
|
+
Response(asynchronousActionResp.status, asynchronousActionResp.description, asynchronousActionResp.produce),
|
|
259
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
260
|
+
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
261
|
+
__param(0, Path()),
|
|
262
|
+
__param(1, Body()),
|
|
263
|
+
__param(2, Query())
|
|
264
|
+
], PoolController.prototype, "createVm", null);
|
|
265
|
+
__decorate([
|
|
266
|
+
Example(poolStats),
|
|
267
|
+
Get('{id}/stats'),
|
|
268
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
269
|
+
Response(422, 'Invalid granularity'),
|
|
270
|
+
__param(0, Path()),
|
|
271
|
+
__param(1, Query())
|
|
272
|
+
], PoolController.prototype, "getStats", null);
|
|
184
273
|
PoolController = __decorate([
|
|
185
274
|
Route('pools'),
|
|
186
275
|
Security('*'),
|
|
187
276
|
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
188
277
|
Tags('pools'),
|
|
189
278
|
provide(PoolController),
|
|
190
|
-
__param(0, inject(RestApi))
|
|
279
|
+
__param(0, inject(RestApi)),
|
|
280
|
+
__param(1, inject(VmService))
|
|
191
281
|
], PoolController);
|
|
192
282
|
export { PoolController };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
|
|
11
|
+
import { inject } from 'inversify';
|
|
12
|
+
import { provide } from 'inversify-binding-decorators';
|
|
13
|
+
import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
14
|
+
import { partialSms, sm, smIds } from '../open-api/oa-examples/sm.oa-example.mjs';
|
|
15
|
+
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
16
|
+
import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
|
|
17
|
+
let SmController = class SmController extends XapiXoController {
|
|
18
|
+
constructor(restApi) {
|
|
19
|
+
super('SM', restApi);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @example fields "uuid,name_label,SM_type"
|
|
23
|
+
* @example filter "SM_type:ext"
|
|
24
|
+
* @example limit 42
|
|
25
|
+
*/
|
|
26
|
+
getSrs(req, fields, ndjson, filter, limit) {
|
|
27
|
+
return this.sendObjects(Object.values(this.getObjects({ filter, limit })), req);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @example id "c4284e12-37c9-7967-b9e8-83ef229c3e03"
|
|
31
|
+
*/
|
|
32
|
+
getSr(id) {
|
|
33
|
+
return this.getObject(id);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
__decorate([
|
|
37
|
+
Example(smIds),
|
|
38
|
+
Example(partialSms),
|
|
39
|
+
Get(''),
|
|
40
|
+
__param(0, Request()),
|
|
41
|
+
__param(1, Query()),
|
|
42
|
+
__param(2, Query()),
|
|
43
|
+
__param(3, Query()),
|
|
44
|
+
__param(4, Query())
|
|
45
|
+
], SmController.prototype, "getSrs", null);
|
|
46
|
+
__decorate([
|
|
47
|
+
Example(sm),
|
|
48
|
+
Get('{id}'),
|
|
49
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
50
|
+
__param(0, Path())
|
|
51
|
+
], SmController.prototype, "getSr", null);
|
|
52
|
+
SmController = __decorate([
|
|
53
|
+
Route('sms'),
|
|
54
|
+
Security('*'),
|
|
55
|
+
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
56
|
+
Tags('sms'),
|
|
57
|
+
provide(SmController),
|
|
58
|
+
__param(0, inject(RestApi))
|
|
59
|
+
], SmController);
|
|
60
|
+
export { SmController };
|
|
@@ -52,7 +52,7 @@ let VmController = class VmController extends XapiXoController {
|
|
|
52
52
|
if (incorrectState.is(error, {
|
|
53
53
|
property: 'resident_on',
|
|
54
54
|
})) {
|
|
55
|
-
|
|
55
|
+
throw invalidParameters(`VM ${id} is halted or host could not be found.`, error);
|
|
56
56
|
}
|
|
57
57
|
throw error;
|
|
58
58
|
}
|
|
@@ -104,15 +104,21 @@ let VmController = class VmController extends XapiXoController {
|
|
|
104
104
|
await this.getXapiObject(id).$call('forget_data_source_archives', dataSource);
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
|
+
* The VM must be halted
|
|
108
|
+
*
|
|
107
109
|
* @example id "f07ab729-c0e8-721c-45ec-f11276377030"
|
|
110
|
+
* @example body { "hostId": "b61a5c92-700e-4966-a13b-00633f03eea8" }
|
|
108
111
|
*/
|
|
109
|
-
async startVm(id, sync) {
|
|
112
|
+
async startVm(id, body, sync) {
|
|
110
113
|
const vmId = id;
|
|
111
|
-
const action = () =>
|
|
114
|
+
const action = async () => {
|
|
115
|
+
await this.getXapi(vmId).startVm(vmId, { startOnly: true, hostId: body?.hostId });
|
|
116
|
+
};
|
|
112
117
|
return this.createAction(action, {
|
|
113
118
|
sync,
|
|
114
119
|
statusCode: noContentResp.status,
|
|
115
120
|
taskProperties: {
|
|
121
|
+
args: body,
|
|
116
122
|
name: 'start VM',
|
|
117
123
|
objectId: vmId,
|
|
118
124
|
},
|
|
@@ -188,6 +194,82 @@ let VmController = class VmController extends XapiXoController {
|
|
|
188
194
|
},
|
|
189
195
|
});
|
|
190
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* The VM must be running
|
|
199
|
+
*
|
|
200
|
+
* @example id "f07ab729-c0e8-721c-45ec-f11276377030"
|
|
201
|
+
*/
|
|
202
|
+
async pauseVm(id, sync) {
|
|
203
|
+
const vmId = id;
|
|
204
|
+
const action = async () => {
|
|
205
|
+
await this.getXapiObject(vmId).$callAsync('pause');
|
|
206
|
+
};
|
|
207
|
+
return this.createAction(action, {
|
|
208
|
+
sync,
|
|
209
|
+
statusCode: noContentResp.status,
|
|
210
|
+
taskProperties: {
|
|
211
|
+
name: 'pause VM',
|
|
212
|
+
objectId: vmId,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The VM must be running
|
|
218
|
+
*
|
|
219
|
+
* @example id "f07ab729-c0e8-721c-45ec-f11276377030"
|
|
220
|
+
*/
|
|
221
|
+
async suspendVm(id, sync) {
|
|
222
|
+
const vmId = id;
|
|
223
|
+
const action = async () => {
|
|
224
|
+
await this.getXapiObject(vmId).$callAsync('suspend');
|
|
225
|
+
};
|
|
226
|
+
return this.createAction(action, {
|
|
227
|
+
sync,
|
|
228
|
+
statusCode: noContentResp.status,
|
|
229
|
+
taskProperties: {
|
|
230
|
+
name: 'suspend VM',
|
|
231
|
+
objectId: vmId,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* The VM must be suspended
|
|
237
|
+
*
|
|
238
|
+
* @example id "f07ab729-c0e8-721c-45ec-f11276377030"
|
|
239
|
+
*/
|
|
240
|
+
async resumeVm(id, sync) {
|
|
241
|
+
const vmId = id;
|
|
242
|
+
const action = async () => {
|
|
243
|
+
await this.getXapi(vmId).resumeVm(vmId);
|
|
244
|
+
};
|
|
245
|
+
return this.createAction(action, {
|
|
246
|
+
sync,
|
|
247
|
+
statusCode: noContentResp.status,
|
|
248
|
+
taskProperties: {
|
|
249
|
+
name: 'resume VM',
|
|
250
|
+
objectId: vmId,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* The VM must be paused
|
|
256
|
+
*
|
|
257
|
+
* @example id "f07ab729-c0e8-721c-45ec-f11276377030"
|
|
258
|
+
*/
|
|
259
|
+
async unpauseVm(id, sync) {
|
|
260
|
+
const vmId = id;
|
|
261
|
+
const action = async () => {
|
|
262
|
+
await this.getXapi(vmId).unpauseVm(vmId);
|
|
263
|
+
};
|
|
264
|
+
return this.createAction(action, {
|
|
265
|
+
sync,
|
|
266
|
+
statusCode: noContentResp.status,
|
|
267
|
+
taskProperties: {
|
|
268
|
+
name: 'unpause VM',
|
|
269
|
+
objectId: vmId,
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
}
|
|
191
273
|
/**
|
|
192
274
|
* @example id "f07ab729-c0e8-721c-45ec-f11276377030"
|
|
193
275
|
* @example body { "name_label": "my_awesome_snapshot" }
|
|
@@ -261,7 +343,8 @@ __decorate([
|
|
|
261
343
|
Response(notFoundResp.status, notFoundResp.description),
|
|
262
344
|
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
263
345
|
__param(0, Path()),
|
|
264
|
-
__param(1,
|
|
346
|
+
__param(1, Body()),
|
|
347
|
+
__param(2, Query())
|
|
265
348
|
], VmController.prototype, "startVm", null);
|
|
266
349
|
__decorate([
|
|
267
350
|
Example(taskLocation),
|
|
@@ -299,6 +382,46 @@ __decorate([
|
|
|
299
382
|
__param(0, Path()),
|
|
300
383
|
__param(1, Query())
|
|
301
384
|
], VmController.prototype, "hardRebootVm", null);
|
|
385
|
+
__decorate([
|
|
386
|
+
Example(taskLocation),
|
|
387
|
+
Post('{id}/actions/pause'),
|
|
388
|
+
SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description, asynchronousActionResp.produce),
|
|
389
|
+
Response(noContentResp.status, noContentResp.description),
|
|
390
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
391
|
+
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
392
|
+
__param(0, Path()),
|
|
393
|
+
__param(1, Query())
|
|
394
|
+
], VmController.prototype, "pauseVm", null);
|
|
395
|
+
__decorate([
|
|
396
|
+
Example(taskLocation),
|
|
397
|
+
Post('{id}/actions/suspend'),
|
|
398
|
+
SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description, asynchronousActionResp.produce),
|
|
399
|
+
Response(noContentResp.status, noContentResp.description),
|
|
400
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
401
|
+
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
402
|
+
__param(0, Path()),
|
|
403
|
+
__param(1, Query())
|
|
404
|
+
], VmController.prototype, "suspendVm", null);
|
|
405
|
+
__decorate([
|
|
406
|
+
Example(taskLocation),
|
|
407
|
+
Post('{id}/actions/resume'),
|
|
408
|
+
SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description, asynchronousActionResp.produce),
|
|
409
|
+
Response(noContentResp.status, noContentResp.description),
|
|
410
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
411
|
+
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
412
|
+
__param(0, Path()),
|
|
413
|
+
__param(1, Query())
|
|
414
|
+
], VmController.prototype, "resumeVm", null);
|
|
415
|
+
__decorate([
|
|
416
|
+
Example(taskLocation),
|
|
417
|
+
Post('{id}/actions/unpause'),
|
|
418
|
+
SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description, asynchronousActionResp.produce),
|
|
419
|
+
Response(noContentResp.status, noContentResp.description),
|
|
420
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
421
|
+
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
422
|
+
__param(0, Path()),
|
|
423
|
+
__param(1, Query())
|
|
424
|
+
], VmController.prototype, "unpauseVm", null);
|
|
302
425
|
__decorate([
|
|
303
426
|
Example(taskLocation),
|
|
304
427
|
Post('{id}/actions/snapshot'),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createLogger } from '@xen-orchestra/log';
|
|
2
|
+
import { defer } from 'golike-defer';
|
|
3
|
+
import { Task } from '@vates/task';
|
|
4
|
+
const log = createLogger('xo:rest-api:vm-service');
|
|
5
|
+
export class VmService {
|
|
6
|
+
#restApi;
|
|
7
|
+
constructor(restApi) {
|
|
8
|
+
this.#restApi = restApi;
|
|
9
|
+
}
|
|
10
|
+
async #create($defer, params) {
|
|
11
|
+
const { pool, template, cloud_config, boot, destroy_cloud_config_vdi, network_config, ...rest } = params;
|
|
12
|
+
const xoApp = this.#restApi.xoApp;
|
|
13
|
+
const xapi = xoApp.getXapi(pool);
|
|
14
|
+
const currentUser = this.#restApi.getCurrentUser();
|
|
15
|
+
const xapiVm = await xapi.createVm(template, rest, undefined, currentUser?.id);
|
|
16
|
+
$defer.onFailure(() => xapi.VM_destroy(xapiVm.$ref));
|
|
17
|
+
const xoVm = this.#restApi.getObject(xapiVm.uuid, 'VM');
|
|
18
|
+
let cloudConfigVdi;
|
|
19
|
+
if (cloud_config !== undefined) {
|
|
20
|
+
const cloudConfigVdiUuid = await xapi.VM_createCloudInitConfig(xapiVm.$ref, cloud_config, {
|
|
21
|
+
networkConfig: network_config,
|
|
22
|
+
});
|
|
23
|
+
cloudConfigVdi = xoApp.getXapiObject(cloudConfigVdiUuid, 'VDI');
|
|
24
|
+
}
|
|
25
|
+
let timeLimit;
|
|
26
|
+
if (boot) {
|
|
27
|
+
timeLimit = Date.now() + 10 * 60 * 1000;
|
|
28
|
+
await xapiVm.$callAsync('start', false, false);
|
|
29
|
+
}
|
|
30
|
+
if (destroy_cloud_config_vdi && cloudConfigVdi !== undefined && boot) {
|
|
31
|
+
Task.info('Destruction of the cloud config VDI is planned and will be done as soon as possible');
|
|
32
|
+
xapi.VDI_destroyCloudInitConfig(cloudConfigVdi.$ref, { timeLimit }).catch(error => {
|
|
33
|
+
log.error('destroy cloud init config VDI failed', {
|
|
34
|
+
error,
|
|
35
|
+
vdi: {
|
|
36
|
+
uuid: cloudConfigVdi.uuid,
|
|
37
|
+
},
|
|
38
|
+
vm: {
|
|
39
|
+
uuid: xoVm.uuid,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return xoVm.id;
|
|
45
|
+
}
|
|
46
|
+
create = defer(this.#create);
|
|
47
|
+
}
|
|
@@ -7,26 +7,42 @@ 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 { Controller, Example, Get, Response, Route, Security, Tags } from 'tsoa';
|
|
10
|
+
import { Controller, Example, Get, Query, Request, Response, Route, Security, Tags } from 'tsoa';
|
|
11
11
|
import { inject } from 'inversify';
|
|
12
|
+
import { PassThrough } from 'node:stream';
|
|
12
13
|
import { provide } from 'inversify-binding-decorators';
|
|
13
14
|
import { unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
14
15
|
import { xoaDashboard } from '../open-api/oa-examples/xoa.oa-example.mjs';
|
|
15
16
|
import { XoaService } from './xoa.service.mjs';
|
|
17
|
+
import { NDJSON_CONTENT_TYPE } from '../helpers/utils.helper.mjs';
|
|
16
18
|
let XoaController = class XoaController extends Controller {
|
|
17
19
|
#xoaService;
|
|
18
20
|
constructor(xoaService) {
|
|
19
21
|
super();
|
|
20
22
|
this.#xoaService = xoaService;
|
|
21
23
|
}
|
|
22
|
-
async getDashboard() {
|
|
23
|
-
const
|
|
24
|
-
|
|
24
|
+
async getDashboard(req, ndjson) {
|
|
25
|
+
const stream = ndjson ? new PassThrough() : undefined;
|
|
26
|
+
const isStream = ndjson && stream !== undefined;
|
|
27
|
+
if (isStream) {
|
|
28
|
+
const res = req.res;
|
|
29
|
+
res.setHeader('Content-Type', NDJSON_CONTENT_TYPE);
|
|
30
|
+
stream.pipe(res);
|
|
31
|
+
}
|
|
32
|
+
const dashboard = await this.#xoaService.getDashboard({ stream });
|
|
33
|
+
if (isStream) {
|
|
34
|
+
stream.end();
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return dashboard;
|
|
38
|
+
}
|
|
25
39
|
}
|
|
26
40
|
};
|
|
27
41
|
__decorate([
|
|
28
42
|
Example(xoaDashboard),
|
|
29
|
-
Get('dashboard')
|
|
43
|
+
Get('dashboard'),
|
|
44
|
+
__param(0, Request()),
|
|
45
|
+
__param(1, Query())
|
|
30
46
|
], XoaController.prototype, "getDashboard", null);
|
|
31
47
|
XoaController = __decorate([
|
|
32
48
|
Route(''),
|
package/dist/xoa/xoa.service.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import groupBy from 'lodash/groupBy.js';
|
|
2
2
|
import semver from 'semver';
|
|
3
|
-
import { BACKUP_TYPE, } from '@vates/types';
|
|
3
|
+
import { BACKUP_TYPE, HOST_POWER_STATE, VM_POWER_STATE, } from '@vates/types';
|
|
4
4
|
import { asyncEach } from '@vates/async-each';
|
|
5
5
|
import { createLogger } from '@xen-orchestra/log';
|
|
6
6
|
import { createPredicate } from 'value-matcher';
|
|
7
7
|
import { extractIdsFromSimplePattern } from '@xen-orchestra/backups/extractIdsFromSimplePattern.mjs';
|
|
8
|
+
import { isPromise } from 'node:util/types';
|
|
8
9
|
import { noSuchObject } from 'xo-common/api-errors.js';
|
|
9
10
|
import { parse } from 'xo-remote-parser';
|
|
10
11
|
import { getFromAsyncCache } from '../helpers/cache.helper.mjs';
|
|
@@ -17,7 +18,7 @@ export class XoaService {
|
|
|
17
18
|
constructor(restApi) {
|
|
18
19
|
this.#restApi = restApi;
|
|
19
20
|
this.#dashboardCacheOpts = {
|
|
20
|
-
timeout: this.#restApi.xoApp.config.getOptionalDuration('rest-api.dashboardCacheTimeout'),
|
|
21
|
+
timeout: this.#restApi.xoApp.config.getOptionalDuration('rest-api.dashboardCacheTimeout') ?? 60000,
|
|
21
22
|
expiresIn: this.#restApi.xoApp.config.getOptionalDuration('rest-api.dashboardCacheExpiresIn'),
|
|
22
23
|
};
|
|
23
24
|
}
|
|
@@ -372,26 +373,104 @@ export class XoaService {
|
|
|
372
373
|
return { ...backupsResult.value, isExpired: backupsResult.isExpired };
|
|
373
374
|
}
|
|
374
375
|
}
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
376
|
+
#getHostsStatus() {
|
|
377
|
+
const hosts = this.#restApi.getObjectsByType('host');
|
|
378
|
+
let nRunning = 0;
|
|
379
|
+
let nHalted = 0;
|
|
380
|
+
let nUnknown = 0;
|
|
381
|
+
let total = 0;
|
|
382
|
+
for (const id in hosts) {
|
|
383
|
+
total++;
|
|
384
|
+
const host = hosts[id];
|
|
385
|
+
switch (host.power_state) {
|
|
386
|
+
case HOST_POWER_STATE.RUNNING:
|
|
387
|
+
nRunning++;
|
|
388
|
+
break;
|
|
389
|
+
case HOST_POWER_STATE.HALTED:
|
|
390
|
+
nHalted++;
|
|
391
|
+
break;
|
|
392
|
+
default:
|
|
393
|
+
nUnknown++;
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
running: nRunning,
|
|
399
|
+
halted: nHalted,
|
|
400
|
+
unknown: nUnknown,
|
|
401
|
+
total,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
#getVmsStatus() {
|
|
405
|
+
const vms = this.#restApi.getObjectsByType('VM');
|
|
406
|
+
let nActive = 0;
|
|
407
|
+
let nInactive = 0;
|
|
408
|
+
let nUnknown = 0;
|
|
409
|
+
let total = 0;
|
|
410
|
+
for (const id in vms) {
|
|
411
|
+
total++;
|
|
412
|
+
const vm = vms[id];
|
|
413
|
+
switch (vm.power_state) {
|
|
414
|
+
case VM_POWER_STATE.RUNNING:
|
|
415
|
+
case VM_POWER_STATE.PAUSED:
|
|
416
|
+
nActive++;
|
|
417
|
+
break;
|
|
418
|
+
case VM_POWER_STATE.HALTED:
|
|
419
|
+
case VM_POWER_STATE.SUSPENDED:
|
|
420
|
+
nInactive++;
|
|
421
|
+
break;
|
|
422
|
+
default:
|
|
423
|
+
nUnknown++;
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
active: nActive,
|
|
429
|
+
inactive: nInactive,
|
|
430
|
+
unknown: nUnknown,
|
|
431
|
+
total,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
async getDashboard({ stream } = {}) {
|
|
435
|
+
async function promiseWriteInStream(maybePromise, key) {
|
|
436
|
+
let data;
|
|
437
|
+
if (isPromise(maybePromise)) {
|
|
438
|
+
data = await maybePromise;
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
data = maybePromise;
|
|
442
|
+
}
|
|
443
|
+
if (stream !== undefined) {
|
|
444
|
+
if (stream.writableNeedDrain) {
|
|
445
|
+
await new Promise(resolve => stream.once('drain', resolve));
|
|
446
|
+
}
|
|
447
|
+
stream.write(JSON.stringify({ [key]: data }) + '\n');
|
|
448
|
+
}
|
|
449
|
+
return data;
|
|
450
|
+
}
|
|
451
|
+
const [nPools, nHosts, hostsStatus, resourcesOverview, vmsStatus, storageRepositories, poolsStatus, missingPatches, backupRepositories, nHostsEol, backups,] = await Promise.all([
|
|
452
|
+
promiseWriteInStream(this.#getNumberOfPools(), 'nPools'),
|
|
453
|
+
promiseWriteInStream(this.#getNumberOfHosts(), 'nHosts'),
|
|
454
|
+
promiseWriteInStream(this.#getHostsStatus(), 'hostsStatus'),
|
|
455
|
+
promiseWriteInStream(this.#getResourcesOverview(), 'resourcesOverview'),
|
|
456
|
+
promiseWriteInStream(this.#getVmsStatus(), 'vmsStatus'),
|
|
457
|
+
promiseWriteInStream(this.#getStorageRepositoriesSizeInfo(), 'storageRepositories'),
|
|
458
|
+
promiseWriteInStream(this.#getPoolsStatus(), 'poolsStatus'),
|
|
459
|
+
promiseWriteInStream(this.#getMissingPatchesInfo(), 'missingPatches'),
|
|
460
|
+
promiseWriteInStream(this.#getBackupRepositoriesSizeInfo().catch(err => {
|
|
461
|
+
log.error('#getBackupRepositoriesSizeInfo failed', err);
|
|
462
|
+
// explicitly return undefined because typescript understand it as void instead of undefined
|
|
463
|
+
return undefined;
|
|
464
|
+
}), 'backupRepositories'),
|
|
465
|
+
promiseWriteInStream(this.#getNumberOfEolHosts().catch(err => {
|
|
466
|
+
log.error('#getNumberOfEolHosts failed', err);
|
|
467
|
+
return undefined;
|
|
468
|
+
}), 'nHostsEol'),
|
|
469
|
+
promiseWriteInStream(this.#getbackupsInfo().catch(err => {
|
|
470
|
+
log.error('#getbackupsInfo failed', err);
|
|
471
|
+
return undefined;
|
|
472
|
+
}), 'backups'),
|
|
473
|
+
]);
|
|
395
474
|
return {
|
|
396
475
|
nPools,
|
|
397
476
|
nHosts,
|
|
@@ -402,6 +481,8 @@ export class XoaService {
|
|
|
402
481
|
missingPatches,
|
|
403
482
|
storageRepositories,
|
|
404
483
|
backups,
|
|
484
|
+
hostsStatus,
|
|
485
|
+
vmsStatus,
|
|
405
486
|
};
|
|
406
487
|
}
|
|
407
488
|
}
|