@xen-orchestra/rest-api 0.8.0 → 0.9.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.
Files changed (42) hide show
  1. package/README.md +2 -1
  2. package/dist/abstract-classes/base-controller.mjs +14 -2
  3. package/dist/alarms/alarm.controller.mjs +3 -2
  4. package/dist/groups/group.controller.mjs +3 -2
  5. package/dist/helpers/cache.helper.mjs +52 -0
  6. package/dist/helpers/stream.helper.mjs +5 -0
  7. package/dist/helpers/utils.helper.mjs +3 -0
  8. package/dist/hosts/host.controller.mjs +3 -2
  9. package/dist/index.mjs +1 -1
  10. package/dist/ioc/ioc.mjs +8 -0
  11. package/dist/messages/message.controller.mjs +3 -2
  12. package/dist/middlewares/generic-error-handler.middleware.mjs +5 -1
  13. package/dist/networks/network.controller.mjs +18 -4
  14. package/dist/open-api/common/response.common.mjs +1 -1
  15. package/dist/open-api/oa-examples/pci.oa-example.mjs +30 -0
  16. package/dist/open-api/oa-examples/pgpu.oa-example.mjs +36 -0
  17. package/dist/open-api/oa-examples/schedule.oa-example.mjs +3 -0
  18. package/dist/open-api/oa-examples/vm-controller.oa-example.mjs +1 -1
  19. package/dist/open-api/oa-examples/xoa.oa-example.mjs +61 -0
  20. package/dist/open-api/routes/routes.js +589 -23
  21. package/dist/pcis/pci.controller.mjs +60 -0
  22. package/dist/pgpus/pgpu.controller.mjs +60 -0
  23. package/dist/pifs/pif.controller.mjs +3 -2
  24. package/dist/pools/pool.controller.mjs +136 -4
  25. package/dist/pools/pool.type.mjs +1 -0
  26. package/dist/schedules/schedule.controller.mjs +5 -4
  27. package/dist/servers/server.controller.mjs +19 -6
  28. package/dist/srs/sr.controller.mjs +3 -2
  29. package/dist/users/user.controller.mjs +3 -2
  30. package/dist/vbds/vbd.controller.mjs +3 -2
  31. package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +3 -2
  32. package/dist/vdis/vdi.controller.mjs +3 -2
  33. package/dist/vifs/vif.controller.mjs +3 -2
  34. package/dist/vm-controller/vm-controller.controller.mjs +3 -2
  35. package/dist/vm-snapshots/vm-snapshot.controller.mjs +3 -2
  36. package/dist/vm-templates/vm-template.controller.mjs +3 -2
  37. package/dist/vms/vm.controller.mjs +72 -9
  38. package/dist/xoa/xoa.controller.mjs +39 -0
  39. package/dist/xoa/xoa.service.mjs +407 -0
  40. package/dist/xoa/xoa.type.mjs +1 -0
  41. package/open-api/spec/swagger.json +4611 -2867
  42. package/package.json +10 -4
package/README.md CHANGED
@@ -42,13 +42,14 @@ class Foo extends Controller {
42
42
 
43
43
 
44
44
  /**
45
- * any jsdoc anotations
45
+ * any jsdoc annotations
46
46
  * @example id 1234
47
47
  */
48
48
  @Example(['foo', 'bar'])
49
49
  @Get('{id}')
50
50
  @Security('*')
51
51
  @Middlewares(json())
52
+ @Tags('foo')
52
53
  @SuccessResponse(202)
53
54
  @Response(404)
54
55
  getFoo(@Path() id: string) {
@@ -1,7 +1,10 @@
1
1
  import { Controller } from 'tsoa';
2
+ import { Readable } from 'node:stream';
2
3
  import { BASE_URL } from '../index.mjs';
4
+ import { makeNdJsonStream } from '../helpers/stream.helper.mjs';
3
5
  import { makeObjectMapper } from '../helpers/object-wrapper.helper.mjs';
4
6
  const noop = () => { };
7
+ const NDJSON_CONTENT_TYPE = 'application/x-ndjson';
5
8
  export class BaseController extends Controller {
6
9
  restApi;
7
10
  constructor(restApi) {
@@ -11,7 +14,16 @@ export class BaseController extends Controller {
11
14
  sendObjects(objects, req) {
12
15
  const mapper = makeObjectMapper(req);
13
16
  const mappedObjects = objects.map(mapper);
14
- return mappedObjects;
17
+ if (req.query.ndjson === 'true') {
18
+ const res = req.res;
19
+ res.setHeader('Content-Type', NDJSON_CONTENT_TYPE);
20
+ const stream = Readable.from(makeNdJsonStream(mappedObjects));
21
+ stream.pipe(res);
22
+ return stream;
23
+ }
24
+ else {
25
+ return mappedObjects;
26
+ }
15
27
  }
16
28
  /**
17
29
  * statusCode must represent the status code in case of a synchronous request. Default 200
@@ -20,7 +32,7 @@ export class BaseController extends Controller {
20
32
  taskProperties.name = 'REST API: ' + taskProperties.name;
21
33
  taskProperties.type = 'xo:rest-api:action';
22
34
  const task = this.restApi.tasks.create(taskProperties);
23
- const pResult = task.run(cb);
35
+ const pResult = task.run(() => cb(task));
24
36
  if (sync) {
25
37
  this.setStatus(statusCode);
26
38
  return pResult;
@@ -94,7 +94,7 @@ let AlarmController = class AlarmController extends XapiXoController {
94
94
  * @example filter "body:name:physical_utilisation"
95
95
  * @example limit 42
96
96
  */
97
- getAlarms(req, fields, filter, limit) {
97
+ getAlarms(req, fields, ndjson, filter, limit) {
98
98
  return this.sendObjects(Object.values(this.getObjects({ filter, limit })), req);
99
99
  }
100
100
  /**
@@ -111,7 +111,8 @@ __decorate([
111
111
  __param(0, Request()),
112
112
  __param(1, Query()),
113
113
  __param(2, Query()),
114
- __param(3, Query())
114
+ __param(3, Query()),
115
+ __param(4, Query())
115
116
  ], AlarmController.prototype, "getAlarms", null);
116
117
  __decorate([
117
118
  Example(alarm),
@@ -25,7 +25,7 @@ let GroupController = class GroupController extends XoController {
25
25
  * @example filter "users:length:>0"
26
26
  * @example limit 42
27
27
  */
28
- async getGroups(req, fields, filter, limit) {
28
+ async getGroups(req, fields, ndjson, filter, limit) {
29
29
  return this.sendObjects(Object.values(await this.getObjects({ filter, limit })), req);
30
30
  }
31
31
  /**
@@ -42,7 +42,8 @@ __decorate([
42
42
  __param(0, Request()),
43
43
  __param(1, Query()),
44
44
  __param(2, Query()),
45
- __param(3, Query())
45
+ __param(3, Query()),
46
+ __param(4, Query())
46
47
  ], GroupController.prototype, "getGroups", null);
47
48
  __decorate([
48
49
  Example(group),
@@ -0,0 +1,52 @@
1
+ /**
2
+ *
3
+ * If the value is cached and not expired, it will return
4
+ * the cached value immediately. If expired or not present, it will invoke the provided
5
+ * function to fetch the value, cache it, and return it.
6
+ *
7
+ * The function also handles timeout for fetching the value, ensuring that if fetching
8
+ * takes too long, it resolves to `undefined` or returns the expired value based on the
9
+ * cache's state.
10
+ *
11
+ */
12
+ export async function getFromAsyncCache(cache, key, fn, { expiresIn = 60000, timeout = 5000, forceRefresh = false } = {}) {
13
+ if (forceRefresh) {
14
+ cache.delete(key);
15
+ }
16
+ const { current, expires } = cache.get(key) ?? {};
17
+ if (current === undefined || (expires ?? 0) < Date.now()) {
18
+ const _promise = fn();
19
+ const promise = _promise.then(result => {
20
+ cache.set(key, {
21
+ current: result,
22
+ expires: Date.now() + expiresIn,
23
+ previous: undefined,
24
+ });
25
+ return result;
26
+ });
27
+ cache.set(key, {
28
+ current: promise,
29
+ previous: current,
30
+ expires: undefined,
31
+ });
32
+ }
33
+ let timeoutId;
34
+ const timeoutPromise = new Promise((resolve, reject) => (timeoutId = setTimeout(() => reject(new Error('Promise timed out', { cause: 'ERR_TIMEOUT' })), timeout)));
35
+ const result = {};
36
+ try {
37
+ result.value = await Promise.race([timeoutPromise, cache.get(key).current]);
38
+ }
39
+ catch (error) {
40
+ if (error instanceof Error && error.cause !== 'ERR_TIMEOUT') {
41
+ throw error;
42
+ }
43
+ result.value = await cache.get(key).previous;
44
+ if (result.value !== undefined) {
45
+ result.isExpired = true;
46
+ }
47
+ }
48
+ finally {
49
+ clearTimeout(timeoutId);
50
+ }
51
+ return result;
52
+ }
@@ -0,0 +1,5 @@
1
+ export function* makeNdJsonStream(array) {
2
+ for (const object of array) {
3
+ yield JSON.stringify(object) + '\n';
4
+ }
5
+ }
@@ -0,0 +1,3 @@
1
+ export const isSrWritable = (sr) => sr.content_type !== 'iso' && sr.size > 0;
2
+ export const isReplicaVm = (vm) => 'start' in vm.blockedOperations && vm.other['xo:backup:job'] !== undefined;
3
+ export const vmContainsNoBakTag = (vm) => vm.tags.some(t => t.split('=', 1)[0] === 'xo:no-bak');
@@ -23,7 +23,7 @@ let HostController = class HostController extends XapiXoController {
23
23
  * @example filter "productBrand:XCP-ng"
24
24
  * @example limit 42
25
25
  */
26
- getHosts(req, fields, filter, limit) {
26
+ getHosts(req, fields, ndjson, filter, limit) {
27
27
  return this.sendObjects(Object.values(this.getObjects({ filter, limit })), req);
28
28
  }
29
29
  /**
@@ -47,7 +47,8 @@ __decorate([
47
47
  __param(0, Request()),
48
48
  __param(1, Query()),
49
49
  __param(2, Query()),
50
- __param(3, Query())
50
+ __param(3, Query()),
51
+ __param(4, Query())
51
52
  ], HostController.prototype, "getHosts", null);
52
53
  __decorate([
53
54
  Example(host),
package/dist/index.mjs CHANGED
@@ -32,7 +32,7 @@ const SWAGGER_UI_OPTIONS = {
32
32
  export default function setupRestApi(express, xoApp) {
33
33
  setupContainer(xoApp);
34
34
  RegisterRoutes(express);
35
- // do not register the doc at the root level, or it may lead to unwated behaviour
35
+ // do not register the doc at the root level, or it may lead to unwanted behaviour
36
36
  // uncomment when all endpoints are migrated to this API
37
37
  // express.get('/rest/v0', (_req, res) => res.redirect('/rest/v0/docs'))
38
38
  express.use(`${BASE_URL}/docs`, swaggerUi.serve, swaggerUi.setup(swaggerOpenApiSpec, SWAGGER_UI_OPTIONS));
package/dist/ioc/ioc.mjs CHANGED
@@ -2,6 +2,7 @@ import { buildProviderModule } from 'inversify-binding-decorators';
2
2
  import { Container, decorate, injectable } from 'inversify';
3
3
  import { Controller } from 'tsoa';
4
4
  import { RestApi } from '../rest-api/rest-api.mjs';
5
+ import { XoaService } from '../xoa/xoa.service.mjs';
5
6
  const iocContainer = new Container();
6
7
  decorate(injectable(), Controller);
7
8
  iocContainer.load(buildProviderModule());
@@ -13,5 +14,12 @@ export function setupContainer(xoApp) {
13
14
  .bind(RestApi)
14
15
  .toDynamicValue(() => new RestApi(xoApp))
15
16
  .inSingletonScope();
17
+ iocContainer
18
+ .bind(XoaService)
19
+ .toDynamicValue(ctx => {
20
+ const restApi = ctx.container.get(RestApi);
21
+ return new XoaService(restApi);
22
+ })
23
+ .inSingletonScope();
16
24
  }
17
25
  export { iocContainer };
@@ -47,7 +47,7 @@ let MessageController = class MessageController extends XapiXoController {
47
47
  * @example filter "name:VM_STARTED"
48
48
  * @example limit 42
49
49
  */
50
- getMessages(req, fields, filter, limit) {
50
+ getMessages(req, fields, ndjson, filter, limit) {
51
51
  return this.sendObjects(Object.values(this.getObjects({ filter, limit })), req);
52
52
  }
53
53
  /**
@@ -64,7 +64,8 @@ __decorate([
64
64
  __param(0, Request()),
65
65
  __param(1, Query()),
66
66
  __param(2, Query()),
67
- __param(3, Query())
67
+ __param(3, Query()),
68
+ __param(4, Query())
68
69
  ], MessageController.prototype, "getMessages", null);
69
70
  __decorate([
70
71
  Example(message),
@@ -13,9 +13,13 @@ export default function genericErrorHandler(error, req, res, _next) {
13
13
  if (noSuchObject.is(error)) {
14
14
  res.status(404);
15
15
  }
16
- else if (unauthorized.is(error) || forbiddenOperation.is(error) || featureUnauthorized.is(error)) {
16
+ else if (unauthorized.is(error) || forbiddenOperation.is(error)) {
17
17
  res.status(403);
18
18
  }
19
+ else if (featureUnauthorized.is(error)) {
20
+ res.status(403);
21
+ responseError.data = error.data;
22
+ }
19
23
  else if (invalidCredentials.is(error)) {
20
24
  res.status(401);
21
25
  }
@@ -7,11 +7,11 @@ 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, Response, Request, Route, Security, Tags } from 'tsoa';
10
+ import { Example, Get, Path, Query, Response, Request, Route, Security, Tags, Delete, SuccessResponse } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
12
  import { provide } from 'inversify-binding-decorators';
13
13
  import { network, networkIds, partialNetworks } from '../open-api/oa-examples/network.oa-example.mjs';
14
- import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
14
+ import { noContentResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
15
15
  import { RestApi } from '../rest-api/rest-api.mjs';
16
16
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
17
17
  let NetworkController = class NetworkController extends XapiXoController {
@@ -23,7 +23,7 @@ let NetworkController = class NetworkController extends XapiXoController {
23
23
  * @example filter "nbd?"
24
24
  * @example limit 42
25
25
  */
26
- getNetworks(req, fields, filter, limit) {
26
+ getNetworks(req, fields, ndjson, filter, limit) {
27
27
  return this.sendObjects(Object.values(this.getObjects({ filter, limit })), req);
28
28
  }
29
29
  /**
@@ -32,6 +32,13 @@ let NetworkController = class NetworkController extends XapiXoController {
32
32
  getNetwork(id) {
33
33
  return this.getObject(id);
34
34
  }
35
+ /**
36
+ * @example id "593c39a5-9c56-28eb-969b-255b2f53791b"
37
+ */
38
+ async deleteNetwork(id) {
39
+ const networkId = id;
40
+ await this.getXapiObject(networkId).$xapi.deleteNetwork(networkId);
41
+ }
35
42
  };
36
43
  __decorate([
37
44
  Example(networkIds),
@@ -40,7 +47,8 @@ __decorate([
40
47
  __param(0, Request()),
41
48
  __param(1, Query()),
42
49
  __param(2, Query()),
43
- __param(3, Query())
50
+ __param(3, Query()),
51
+ __param(4, Query())
44
52
  ], NetworkController.prototype, "getNetworks", null);
45
53
  __decorate([
46
54
  Example(network),
@@ -48,6 +56,12 @@ __decorate([
48
56
  Response(notFoundResp.status, notFoundResp.description),
49
57
  __param(0, Path())
50
58
  ], NetworkController.prototype, "getNetwork", null);
59
+ __decorate([
60
+ Delete('{id}'),
61
+ SuccessResponse(noContentResp.status, noContentResp.description),
62
+ Response(notFoundResp.status, notFoundResp.description),
63
+ __param(0, Path())
64
+ ], NetworkController.prototype, "deleteNetwork", null);
51
65
  NetworkController = __decorate([
52
66
  Route('networks'),
53
67
  Security('*'),
@@ -2,7 +2,7 @@ export const createdResp = {
2
2
  status: 201,
3
3
  description: 'Resource created',
4
4
  };
5
- export const actionAsyncroneResp = {
5
+ export const asynchronousActionResp = {
6
6
  status: 202,
7
7
  description: 'Action executed asynchronously',
8
8
  produce: 'text/plain',
@@ -0,0 +1,30 @@
1
+ export const pciIds = [
2
+ '/rest/v0/pcis/9377b642-cc71-8749-1e71-308898b652da',
3
+ '/rest/v0/pcis/9de8f35c-5b51-e8b6-5f48-0942231f8610',
4
+ ];
5
+ export const partialPcis = [
6
+ {
7
+ class_name: 'Non-Volatile memory controller',
8
+ device_name: 'XG5 NVMe SSD Controller',
9
+ id: '9377b642-cc71-8749-1e71-308898b652da',
10
+ href: '/rest/v0/pcis/9377b642-cc71-8749-1e71-308898b652da',
11
+ },
12
+ {
13
+ class_name: 'Non-Volatile memory controller',
14
+ device_name: 'XG5 NVMe SSD Controller',
15
+ id: '9de8f35c-5b51-e8b6-5f48-0942231f8610',
16
+ href: '/rest/v0/pcis/9de8f35c-5b51-e8b6-5f48-0942231f8610',
17
+ },
18
+ ];
19
+ export const pci = {
20
+ type: 'PCI',
21
+ class_name: 'Non-Volatile memory controller',
22
+ device_name: 'XG5 NVMe SSD Controller',
23
+ pci_id: '0000:0d:00.0',
24
+ $host: '669df518-4e5d-4d84-b93a-9be2cdcdfca1',
25
+ id: '9377b642-cc71-8749-1e71-308898b652da',
26
+ uuid: '9377b642-cc71-8749-1e71-308898b652da',
27
+ $pool: 'b7569d99-30f8-178a-7d94-801de3e29b5b',
28
+ $poolId: 'b7569d99-30f8-178a-7d94-801de3e29b5b',
29
+ _xapiRef: 'OpaqueRef:783001df-4ab3-1c47-be37-7d9314337577',
30
+ };
@@ -0,0 +1,36 @@
1
+ export const pgpuIds = [
2
+ '/rest/v0/pgpus/838335fa-ee21-15e1-760a-a37a3a4ef1db',
3
+ '/rest/v0/pgpus/4062d698-50aa-c53a-9974-9806bb38bf8d',
4
+ ];
5
+ export const partialPgpus = [
6
+ {
7
+ id: '838335fa-ee21-15e1-760a-a37a3a4ef1db',
8
+ dom0Access: 'enabled',
9
+ gpuGroup: '8f77aa2b-db69-a6b7-b36e-597aafe40f05',
10
+ href: '/rest/v0/pgpus/838335fa-ee21-15e1-760a-a37a3a4ef1db',
11
+ },
12
+ {
13
+ id: '4062d698-50aa-c53a-9974-9806bb38bf8d',
14
+ dom0Access: 'enabled',
15
+ gpuGroup: '8f77aa2b-db69-a6b7-b36e-597aafe40f05',
16
+ href: '/rest/v0/pgpus/4062d698-50aa-c53a-9974-9806bb38bf8d',
17
+ },
18
+ ];
19
+ export const pgpu = {
20
+ type: 'PGPU',
21
+ dom0Access: 'enabled',
22
+ enabledVgpuTypes: [],
23
+ gpuGroup: '8f77aa2b-db69-a6b7-b36e-597aafe40f05',
24
+ isSystemDisplayDevice: true,
25
+ pci: '8b515224-0c6c-c498-5ece-39f64d9b7b20',
26
+ supportedVgpuTypes: [],
27
+ host: '84e555d8-267a-4720-aa5f-fd19035aadae',
28
+ $host: '84e555d8-267a-4720-aa5f-fd19035aadae',
29
+ vgpus: [],
30
+ $vgpus: [],
31
+ id: '838335fa-ee21-15e1-760a-a37a3a4ef1db',
32
+ uuid: '838335fa-ee21-15e1-760a-a37a3a4ef1db',
33
+ $pool: 'b7569d99-30f8-178a-7d94-801de3e29b5b',
34
+ $poolId: 'b7569d99-30f8-178a-7d94-801de3e29b5b',
35
+ _xapiRef: 'OpaqueRef:c842190a-381d-1616-f363-c031a54c3526',
36
+ };
@@ -27,3 +27,6 @@ export const schedule = {
27
27
  userId: 'd558dd75-c928-45f6-b8e3-4375bdda59f8',
28
28
  id: 'cf7249f8-d20b-494f-97f4-b1f32f94e780',
29
29
  };
30
+ export const createNetwork = {
31
+ id: '9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f',
32
+ };
@@ -57,7 +57,7 @@ export const vmController = {
57
57
  VTPMs: [],
58
58
  virtualizationMode: 'pv',
59
59
  xenTools: false,
60
- $containe: 'b61a5c92-700e-4966-a13b-00633f03eea8',
60
+ $container: 'b61a5c92-700e-4966-a13b-00633f03eea8',
61
61
  $VBDs: [],
62
62
  VGPUs: [],
63
63
  $VGPUs: [],
@@ -0,0 +1,61 @@
1
+ export const xoaDashboard = {
2
+ nPools: 2,
3
+ nHosts: 5,
4
+ backupRepositories: {
5
+ s3: {
6
+ size: {
7
+ backups: 286295393792,
8
+ },
9
+ },
10
+ other: {
11
+ size: {
12
+ available: 62630354944,
13
+ backups: 20684251648,
14
+ other: 66875031040,
15
+ total: 150189637632,
16
+ used: 87559282688,
17
+ },
18
+ },
19
+ },
20
+ resourcesOverview: {
21
+ nCpus: 52,
22
+ memorySize: 107374182400,
23
+ srSize: 751123595264,
24
+ },
25
+ poolsStatus: {
26
+ connected: 2,
27
+ unreachable: 7,
28
+ unknown: 0,
29
+ },
30
+ nHostsEol: 0,
31
+ missingPatches: {
32
+ hasAuthorization: true,
33
+ nHostsFailed: 1,
34
+ nHostsWithMissingPatches: 4,
35
+ nPoolsWithMissingPatches: 2,
36
+ },
37
+ storageRepositories: {
38
+ size: {
39
+ available: 628454834176,
40
+ other: 122641256960,
41
+ replicated: 27504128,
42
+ total: 751123595264,
43
+ used: 122668761088,
44
+ },
45
+ },
46
+ backups: {
47
+ jobs: {
48
+ disabled: 8,
49
+ failed: 0,
50
+ skipped: 0,
51
+ successful: 0,
52
+ total: 8,
53
+ },
54
+ issues: [],
55
+ vmsProtection: {
56
+ protected: 0,
57
+ unprotected: 0,
58
+ notInJob: 20,
59
+ },
60
+ },
61
+ };