@xen-orchestra/rest-api 0.8.0 → 0.10.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/README.md +2 -1
- package/dist/abstract-classes/base-controller.mjs +17 -2
- package/dist/alarms/alarm.controller.mjs +3 -2
- package/dist/groups/group.controller.mjs +3 -2
- package/dist/helpers/cache.helper.mjs +52 -0
- package/dist/helpers/stream.helper.mjs +5 -0
- package/dist/helpers/utils.helper.mjs +4 -0
- package/dist/hosts/host.controller.mjs +3 -2
- package/dist/index.mjs +1 -1
- package/dist/ioc/ioc.mjs +16 -0
- package/dist/messages/message.controller.mjs +3 -2
- package/dist/middlewares/generic-error-handler.middleware.mjs +5 -1
- package/dist/networks/network.controller.mjs +18 -4
- package/dist/open-api/common/response.common.mjs +1 -1
- package/dist/open-api/oa-examples/pci.oa-example.mjs +30 -0
- package/dist/open-api/oa-examples/pgpu.oa-example.mjs +36 -0
- package/dist/open-api/oa-examples/pool.oa-example.mjs +4 -0
- package/dist/open-api/oa-examples/schedule.oa-example.mjs +3 -0
- package/dist/open-api/oa-examples/sm.oa-example.mjs +58 -0
- package/dist/open-api/oa-examples/vm-controller.oa-example.mjs +1 -1
- package/dist/open-api/oa-examples/xoa.oa-example.mjs +61 -0
- package/dist/open-api/routes/routes.js +897 -67
- package/dist/pcis/pci.controller.mjs +60 -0
- package/dist/pgpus/pgpu.controller.mjs +60 -0
- package/dist/pifs/pif.controller.mjs +3 -2
- package/dist/pools/pool.controller.mjs +215 -7
- package/dist/pools/pool.type.mjs +1 -0
- package/dist/rest-api/rest-api.mjs +3 -0
- package/dist/schedules/schedule.controller.mjs +5 -4
- package/dist/servers/server.controller.mjs +19 -6
- package/dist/sms/sm.controller.mjs +60 -0
- package/dist/srs/sr.controller.mjs +3 -2
- package/dist/users/user.controller.mjs +3 -2
- package/dist/vbds/vbd.controller.mjs +3 -2
- package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +3 -2
- package/dist/vdis/vdi.controller.mjs +3 -2
- package/dist/vifs/vif.controller.mjs +3 -2
- package/dist/vm-controller/vm-controller.controller.mjs +3 -2
- package/dist/vm-snapshots/vm-snapshot.controller.mjs +3 -2
- package/dist/vm-templates/vm-template.controller.mjs +3 -2
- package/dist/vms/vm.controller.mjs +198 -12
- package/dist/vms/vm.service.mjs +47 -0
- package/dist/xoa/xoa.controller.mjs +55 -0
- package/dist/xoa/xoa.service.mjs +488 -0
- package/dist/xoa/xoa.type.mjs +1 -0
- package/open-api/spec/swagger.json +5729 -2883
- package/package.json +12 -4
- package/tsoa.json +20 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import groupBy from 'lodash/groupBy.js';
|
|
2
|
+
import semver from 'semver';
|
|
3
|
+
import { BACKUP_TYPE, HOST_POWER_STATE, VM_POWER_STATE, } from '@vates/types';
|
|
4
|
+
import { asyncEach } from '@vates/async-each';
|
|
5
|
+
import { createLogger } from '@xen-orchestra/log';
|
|
6
|
+
import { createPredicate } from 'value-matcher';
|
|
7
|
+
import { extractIdsFromSimplePattern } from '@xen-orchestra/backups/extractIdsFromSimplePattern.mjs';
|
|
8
|
+
import { isPromise } from 'node:util/types';
|
|
9
|
+
import { noSuchObject } from 'xo-common/api-errors.js';
|
|
10
|
+
import { parse } from 'xo-remote-parser';
|
|
11
|
+
import { getFromAsyncCache } from '../helpers/cache.helper.mjs';
|
|
12
|
+
import { isReplicaVm, isSrWritable, vmContainsNoBakTag } from '../helpers/utils.helper.mjs';
|
|
13
|
+
const log = createLogger('xo:rest-api:xoa-service');
|
|
14
|
+
export class XoaService {
|
|
15
|
+
#restApi;
|
|
16
|
+
#dashboardAsyncCache = new Map();
|
|
17
|
+
#dashboardCacheOpts;
|
|
18
|
+
constructor(restApi) {
|
|
19
|
+
this.#restApi = restApi;
|
|
20
|
+
this.#dashboardCacheOpts = {
|
|
21
|
+
timeout: this.#restApi.xoApp.config.getOptionalDuration('rest-api.dashboardCacheTimeout') ?? 60000,
|
|
22
|
+
expiresIn: this.#restApi.xoApp.config.getOptionalDuration('rest-api.dashboardCacheExpiresIn'),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async #getBackupRepositoriesSizeInfo() {
|
|
26
|
+
const brResult = await getFromAsyncCache(this.#dashboardAsyncCache, 'backupRepositories', async () => {
|
|
27
|
+
const xoApp = this.#restApi.xoApp;
|
|
28
|
+
const s3Brsize = { backups: 0 };
|
|
29
|
+
const otherBrSize = {
|
|
30
|
+
available: 0,
|
|
31
|
+
backups: 0,
|
|
32
|
+
other: 0,
|
|
33
|
+
total: 0,
|
|
34
|
+
used: 0,
|
|
35
|
+
};
|
|
36
|
+
const backupRepositories = await xoApp.getAllRemotes();
|
|
37
|
+
const backupRepositoriesInfo = await xoApp.getAllRemotesInfo();
|
|
38
|
+
for (const backupRepository of backupRepositories) {
|
|
39
|
+
const { type } = parse(backupRepository.url);
|
|
40
|
+
const backupRepositoryInfo = backupRepositoriesInfo[backupRepository.id];
|
|
41
|
+
if (!backupRepository.enabled || backupRepositoryInfo === undefined) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const totalBackupSize = await xoApp.getTotalBackupSizeOnRemote(backupRepository.id);
|
|
45
|
+
const { available, size, used } = backupRepositoryInfo;
|
|
46
|
+
const isS3 = type === 's3';
|
|
47
|
+
const target = isS3 ? s3Brsize : otherBrSize;
|
|
48
|
+
target.backups += totalBackupSize.onDisk;
|
|
49
|
+
if (!isS3) {
|
|
50
|
+
const _target = target;
|
|
51
|
+
_target.available += available ?? 0;
|
|
52
|
+
_target.other += used - totalBackupSize.onDisk;
|
|
53
|
+
_target.total += size ?? 0;
|
|
54
|
+
_target.used += used;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { s3: { size: s3Brsize }, other: { size: otherBrSize } };
|
|
58
|
+
}, this.#dashboardCacheOpts);
|
|
59
|
+
if (brResult?.value !== undefined) {
|
|
60
|
+
return { ...brResult.value, isExpired: brResult.isExpired };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
#getNumberOfPools() {
|
|
64
|
+
const pools = this.#restApi.getObjectsByType('pool');
|
|
65
|
+
return Object.keys(pools).length;
|
|
66
|
+
}
|
|
67
|
+
#getNumberOfHosts() {
|
|
68
|
+
const hosts = this.#restApi.getObjectsByType('host');
|
|
69
|
+
return Object.keys(hosts).length;
|
|
70
|
+
}
|
|
71
|
+
#getResourcesOverview() {
|
|
72
|
+
const pools = Object.values(this.#restApi.getObjectsByType('pool'));
|
|
73
|
+
const hosts = Object.values(this.#restApi.getObjectsByType('host'));
|
|
74
|
+
const writableSrs = Object.values(this.#restApi.getObjectsByType('SR', {
|
|
75
|
+
filter: isSrWritable,
|
|
76
|
+
}));
|
|
77
|
+
const maxLenght = Math.max(hosts.length, writableSrs.length);
|
|
78
|
+
const resourcesOverview = { nCpus: 0, memorySize: 0, srSize: 0 };
|
|
79
|
+
for (let index = 0; index < maxLenght; index++) {
|
|
80
|
+
const pool = pools[index];
|
|
81
|
+
const host = hosts[index];
|
|
82
|
+
const sr = writableSrs[index];
|
|
83
|
+
if (pool !== undefined) {
|
|
84
|
+
resourcesOverview.nCpus += pool.cpus.cores ?? 0;
|
|
85
|
+
}
|
|
86
|
+
if (host !== undefined) {
|
|
87
|
+
resourcesOverview.memorySize += host.memory.size;
|
|
88
|
+
}
|
|
89
|
+
if (sr !== undefined) {
|
|
90
|
+
resourcesOverview.srSize += sr.size;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return resourcesOverview;
|
|
94
|
+
}
|
|
95
|
+
async #getPoolsStatus() {
|
|
96
|
+
const servers = await this.#restApi.xoApp.getAllXenServers();
|
|
97
|
+
const pools = this.#restApi.getObjectsByType('pool');
|
|
98
|
+
let nConnectedServers = 0;
|
|
99
|
+
let nUnreachableServers = 0;
|
|
100
|
+
let nUnknownServers = 0;
|
|
101
|
+
servers.forEach(server => {
|
|
102
|
+
// it may happen that some servers are marked as "connected", but no pool matches "server.pool"
|
|
103
|
+
// so they are counted as `nUnknownServers`
|
|
104
|
+
if (server.status === 'connected' && server.poolId !== undefined && pools[server.poolId] !== undefined) {
|
|
105
|
+
nConnectedServers++;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (server.status === 'disconnected' &&
|
|
109
|
+
server.error !== undefined &&
|
|
110
|
+
server.error.connectedServerId === undefined) {
|
|
111
|
+
nUnreachableServers++;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (server.status === 'disconnected') {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
nUnknownServers++;
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
connected: nConnectedServers,
|
|
121
|
+
unreachable: nUnreachableServers,
|
|
122
|
+
unknown: nUnknownServers,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async #getNumberOfEolHosts() {
|
|
126
|
+
const getHVSupportedVersions = this.#restApi.xoApp.getHVSupportedVersions;
|
|
127
|
+
if (getHVSupportedVersions === undefined) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const hvSupportedVersions = await getHVSupportedVersions();
|
|
131
|
+
const hosts = this.#restApi.getObjectsByType('host');
|
|
132
|
+
let nHostsEol = 0;
|
|
133
|
+
for (const hostId in hosts) {
|
|
134
|
+
const host = hosts[hostId];
|
|
135
|
+
if (!semver.satisfies(host.version, hvSupportedVersions[host.productBrand])) {
|
|
136
|
+
nHostsEol++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return nHostsEol;
|
|
140
|
+
}
|
|
141
|
+
async #getMissingPatchesInfo() {
|
|
142
|
+
if (!(await this.#restApi.xoApp.hasFeatureAuthorization('LIST_MISSING_PATCHES'))) {
|
|
143
|
+
return {
|
|
144
|
+
hasAuthorization: false,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const hosts = Object.values(this.#restApi.getObjectsByType('host'));
|
|
148
|
+
const poolsWithMissingPatches = new Set();
|
|
149
|
+
let nHostsWithMissingPatches = 0;
|
|
150
|
+
let nHostsFailed = 0;
|
|
151
|
+
await asyncEach(hosts, async (host) => {
|
|
152
|
+
const xapi = this.#restApi.xoApp.getXapi(host);
|
|
153
|
+
try {
|
|
154
|
+
const patches = await xapi.listMissingPatches(host.id);
|
|
155
|
+
if (patches.length > 0) {
|
|
156
|
+
nHostsWithMissingPatches++;
|
|
157
|
+
poolsWithMissingPatches.add(host.$pool);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
log.error('listMissingPatches failed', err);
|
|
162
|
+
nHostsFailed++;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
hasAuthorization: true,
|
|
167
|
+
nHostsFailed,
|
|
168
|
+
nHostsWithMissingPatches,
|
|
169
|
+
nPoolsWithMissingPatches: poolsWithMissingPatches.size,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
#isReplicaVmInVdb(vbds) {
|
|
173
|
+
for (const vbd of vbds) {
|
|
174
|
+
try {
|
|
175
|
+
const vdbObject = this.#restApi.getObject(vbd);
|
|
176
|
+
const { VM } = vdbObject;
|
|
177
|
+
const vmObject = this.#restApi.getObject(VM);
|
|
178
|
+
return isReplicaVm(vmObject);
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
if (!noSuchObject.is(err)) {
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
#calculateReplicatedSize(vdiId, cache) {
|
|
189
|
+
if (cache.has(vdiId)) {
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
let vdiObject;
|
|
193
|
+
try {
|
|
194
|
+
vdiObject = this.#restApi.getObject(vdiId);
|
|
195
|
+
cache.add(vdiId);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
if (!noSuchObject.is(err)) {
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
const { parent, usage, $VBDs } = vdiObject;
|
|
204
|
+
const replicaUsage = this.#isReplicaVmInVdb($VBDs) && usage ? usage : 0;
|
|
205
|
+
const parentUsage = parent ? this.#calculateReplicatedSize(parent, cache) : 0;
|
|
206
|
+
return replicaUsage + parentUsage;
|
|
207
|
+
}
|
|
208
|
+
#getStorageRepositoriesSizeInfo() {
|
|
209
|
+
const writableSrs = this.#restApi.getObjectsByType('SR', {
|
|
210
|
+
filter: isSrWritable,
|
|
211
|
+
});
|
|
212
|
+
let replicated = 0;
|
|
213
|
+
let total = 0;
|
|
214
|
+
let used = 0;
|
|
215
|
+
for (const srId in writableSrs) {
|
|
216
|
+
const sr = writableSrs[srId];
|
|
217
|
+
const cache = new Set();
|
|
218
|
+
const { VDIs } = sr;
|
|
219
|
+
replicated += VDIs.reduce((total, vdi) => total + this.#calculateReplicatedSize(vdi, cache), 0);
|
|
220
|
+
total += sr.size;
|
|
221
|
+
used += sr.physical_usage;
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
size: { available: total - used, other: used - replicated, replicated, total, used },
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async #getbackupsInfo() {
|
|
228
|
+
const vmIdsProtected = new Set();
|
|
229
|
+
const vmIdsUnprotected = new Set();
|
|
230
|
+
const nonReplicaVms = Object.values(this.#restApi.getObjectsByType('VM', { filter: vm => !isReplicaVm(vm) }));
|
|
231
|
+
const restApi = this.#restApi;
|
|
232
|
+
const xoApp = restApi.xoApp;
|
|
233
|
+
function _extractVmIdsFromBackupJob(job) {
|
|
234
|
+
let vmIds;
|
|
235
|
+
try {
|
|
236
|
+
vmIds = extractIdsFromSimplePattern(job.vms);
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
238
|
+
}
|
|
239
|
+
catch (_) {
|
|
240
|
+
const predicate = createPredicate(job.vms);
|
|
241
|
+
vmIds = nonReplicaVms.filter(predicate).map(vm => vm.id);
|
|
242
|
+
}
|
|
243
|
+
return vmIds;
|
|
244
|
+
}
|
|
245
|
+
function _processVmsProtection(job, isProtected) {
|
|
246
|
+
if (job.type !== BACKUP_TYPE.backup) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
_extractVmIdsFromBackupJob(job).forEach(vmId => {
|
|
250
|
+
_updateVmProtection(vmId, isProtected);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function _updateVmProtection(vmId, isProtected) {
|
|
254
|
+
if (vmIdsProtected.has(vmId) || !xoApp.hasObject(vmId, 'VM')) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const vm = restApi.getObject(vmId, 'VM');
|
|
258
|
+
if (vmContainsNoBakTag(vm)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (isProtected) {
|
|
262
|
+
vmIdsProtected.add(vmId);
|
|
263
|
+
vmIdsUnprotected.delete(vmId);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
vmIdsUnprotected.add(vmId);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function _jobHasAtLeastOneScheduleEnabled(job) {
|
|
270
|
+
for (const maybeScheduleId in job.settings) {
|
|
271
|
+
if (maybeScheduleId === '') {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const schedule = await xoApp.getSchedule(maybeScheduleId);
|
|
276
|
+
if (schedule.enabled) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
if (!noSuchObject.is(error, { id: maybeScheduleId, type: 'schedule' })) {
|
|
282
|
+
console.error(error);
|
|
283
|
+
}
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
const backupsResult = await getFromAsyncCache(this.#dashboardAsyncCache, 'backups', async () => {
|
|
290
|
+
const [logs, jobs] = await Promise.all([
|
|
291
|
+
xoApp.getBackupNgLogsSorted({
|
|
292
|
+
filter: log => log.message === 'backup' || log.message === 'metadata',
|
|
293
|
+
}),
|
|
294
|
+
Promise.all([
|
|
295
|
+
xoApp.getAllJobs('backup'),
|
|
296
|
+
xoApp.getAllJobs('mirrorBackup'),
|
|
297
|
+
xoApp.getAllJobs('metadataBackup'),
|
|
298
|
+
]).then(jobs => jobs.flat(1)),
|
|
299
|
+
]);
|
|
300
|
+
const logsByJob = groupBy(logs, 'jobId');
|
|
301
|
+
let disabledJobs = 0;
|
|
302
|
+
let failedJobs = 0;
|
|
303
|
+
let skippedJobs = 0;
|
|
304
|
+
let successfulJobs = 0;
|
|
305
|
+
const backupJobIssues = [];
|
|
306
|
+
for (const job of jobs) {
|
|
307
|
+
if (!(await _jobHasAtLeastOneScheduleEnabled(job))) {
|
|
308
|
+
_processVmsProtection(job, false);
|
|
309
|
+
disabledJobs++;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
// Get only the last 3 runs
|
|
313
|
+
const jobLogs = logsByJob[job.id]?.slice(-3).reverse();
|
|
314
|
+
if (jobLogs === undefined || jobLogs.length === 0) {
|
|
315
|
+
_processVmsProtection(job, false);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (job.type === BACKUP_TYPE.backup) {
|
|
319
|
+
const lastJobLog = jobLogs[0];
|
|
320
|
+
const { tasks, status } = lastJobLog;
|
|
321
|
+
if (tasks === undefined) {
|
|
322
|
+
_processVmsProtection(job, status === 'success');
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
// @TODO: remove as when logs are correctly typed
|
|
326
|
+
;
|
|
327
|
+
tasks.forEach(task => {
|
|
328
|
+
_updateVmProtection(task.data.id, task.status === 'success');
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const failedLog = jobLogs.find(log => log.status !== 'success');
|
|
333
|
+
if (failedLog !== undefined) {
|
|
334
|
+
const { status } = failedLog;
|
|
335
|
+
if (status === 'failure' || status === 'interrupted') {
|
|
336
|
+
failedJobs++;
|
|
337
|
+
}
|
|
338
|
+
else if (status === 'skipped') {
|
|
339
|
+
skippedJobs++;
|
|
340
|
+
}
|
|
341
|
+
backupJobIssues.push({
|
|
342
|
+
// @TODO: remove as when logs are correctly typed
|
|
343
|
+
logs: jobLogs.map(log => log.status),
|
|
344
|
+
name: job.name,
|
|
345
|
+
type: job.type,
|
|
346
|
+
uuid: job.id,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
successfulJobs++;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const nVmsProtected = vmIdsProtected.size;
|
|
354
|
+
const nVmsUnprotected = vmIdsUnprotected.size;
|
|
355
|
+
const nVmsNotInJob = nonReplicaVms.length - (nVmsProtected + nVmsUnprotected);
|
|
356
|
+
return {
|
|
357
|
+
jobs: {
|
|
358
|
+
disabled: disabledJobs,
|
|
359
|
+
failed: failedJobs,
|
|
360
|
+
skipped: skippedJobs,
|
|
361
|
+
successful: successfulJobs,
|
|
362
|
+
total: jobs.length,
|
|
363
|
+
},
|
|
364
|
+
issues: backupJobIssues,
|
|
365
|
+
vmsProtection: {
|
|
366
|
+
protected: nVmsProtected,
|
|
367
|
+
unprotected: nVmsUnprotected,
|
|
368
|
+
notInJob: nVmsNotInJob,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}, this.#dashboardCacheOpts);
|
|
372
|
+
if (backupsResult?.value !== undefined) {
|
|
373
|
+
return { ...backupsResult.value, isExpired: backupsResult.isExpired };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
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
|
+
]);
|
|
474
|
+
return {
|
|
475
|
+
nPools,
|
|
476
|
+
nHosts,
|
|
477
|
+
backupRepositories,
|
|
478
|
+
resourcesOverview,
|
|
479
|
+
poolsStatus,
|
|
480
|
+
nHostsEol,
|
|
481
|
+
missingPatches,
|
|
482
|
+
storageRepositories,
|
|
483
|
+
backups,
|
|
484
|
+
hostsStatus,
|
|
485
|
+
vmsStatus,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|