Skip to content

Commit 32a732d

Browse files
committed
Feat(dataFetcher): Add QEMU agent memory fetching & refactor node data retrieval
1 parent 1ea9433 commit 32a732d

File tree

1 file changed

+139
-106
lines changed

1 file changed

+139
-106
lines changed

server/dataFetcher.js

Lines changed: 139 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,46 @@
11
const { processPbsTasks } = require('./pbsUtils'); // Assuming pbsUtils.js exists or will be created
22

3-
// Helper function reused from index.js (or import if shared)
4-
async function fetchDataForNode(apiClient, endpointId, nodeName) {
5-
const nodeData = {
6-
vms: [],
7-
containers: [],
8-
nodeStatus: {},
9-
storage: []
10-
};
11-
12-
// Fetch node status
3+
// Helper function to fetch data and handle common errors/warnings
4+
async function fetchNodeResource(apiClient, endpointId, nodeName, resourcePath, resourceName, expectArray = false, transformFn = null) {
135
try {
14-
const statusResponse = await apiClient.get(`/nodes/${nodeName}/status`);
15-
if (statusResponse.data?.data) {
16-
nodeData.nodeStatus = statusResponse.data.data;
17-
} else {
18-
console.warn(`[DataFetcher - ${endpointId}-${nodeName}] Node status data missing or invalid format.`);
19-
}
20-
} catch (error) {
21-
console.error(`[DataFetcher - ${endpointId}-${nodeName}] Error fetching node status: ${error.message}`);
22-
// Allow proceeding even if status fails
23-
}
6+
const response = await apiClient.get(`/nodes/${nodeName}/${resourcePath}`);
7+
const data = response.data?.data;
248

25-
// Fetch node storage
26-
try {
27-
const storageResponse = await apiClient.get(`/nodes/${nodeName}/storage`);
28-
if (storageResponse.data?.data && Array.isArray(storageResponse.data.data)) {
29-
nodeData.storage = storageResponse.data.data;
9+
if (data) {
10+
if (expectArray && !Array.isArray(data)) {
11+
console.warn(`[DataFetcher - ${endpointId}-${nodeName}] ${resourceName} data is not an array as expected.`);
12+
return expectArray ? [] : null;
13+
}
14+
return transformFn ? transformFn(data) : data;
3015
} else {
31-
console.warn(`[DataFetcher - ${endpointId}-${nodeName}] Node storage data missing or invalid format.`);
16+
console.warn(`[DataFetcher - ${endpointId}-${nodeName}] ${resourceName} data missing or invalid format.`);
17+
return expectArray ? [] : null;
3218
}
3319
} catch (error) {
34-
console.error(`[DataFetcher - ${endpointId}-${nodeName}] Error fetching node storage: ${error.message}`);
35-
// Allow proceeding even if storage fails
20+
console.error(`[DataFetcher - ${endpointId}-${nodeName}] Error fetching ${resourceName}: ${error.message}`);
21+
return expectArray ? [] : null; // Allow proceeding even if this resource fails
3622
}
23+
}
3724

38-
39-
// --- Fetch VMs ---
40-
try {
41-
const vmsResponse = await apiClient.get(`/nodes/${nodeName}/qemu`);
42-
if (vmsResponse.data?.data && Array.isArray(vmsResponse.data.data)) {
43-
nodeData.vms = vmsResponse.data.data.map(vm => ({
44-
...vm, node: nodeName, endpointId: endpointId, type: 'qemu'
45-
}));
46-
}
47-
} catch (error) {
48-
console.error(`[DataFetcher - ${endpointId}-${nodeName}] Error fetching VMs (qemu): ${error.message}`);
49-
// Proceed without VMs if fetch fails
50-
}
25+
async function fetchDataForNode(apiClient, endpointId, nodeName) {
26+
const nodeStatus = await fetchNodeResource(apiClient, endpointId, nodeName, 'status', 'Node status');
27+
const storage = await fetchNodeResource(apiClient, endpointId, nodeName, 'storage', 'Node storage', true);
5128

52-
// --- Fetch containers ---
53-
try {
54-
const ctsResponse = await apiClient.get(`/nodes/${nodeName}/lxc`);
55-
if (ctsResponse.data?.data && Array.isArray(ctsResponse.data.data)) {
56-
nodeData.containers = ctsResponse.data.data.map(ct => ({
57-
...ct, node: nodeName, endpointId: endpointId, type: 'lxc'
58-
}));
59-
}
60-
} catch (error) {
61-
console.error(`[DataFetcher - ${endpointId}-${nodeName}] Error fetching Containers (lxc): ${error.message}`);
62-
// Proceed without containers if fetch fails
63-
}
29+
const vms = await fetchNodeResource(
30+
apiClient, endpointId, nodeName, 'qemu', 'VMs (qemu)', true,
31+
(data) => data.map(vm => ({ ...vm, node: nodeName, endpointId: endpointId, type: 'qemu' }))
32+
);
6433

34+
const containers = await fetchNodeResource(
35+
apiClient, endpointId, nodeName, 'lxc', 'Containers (lxc)', true,
36+
(data) => data.map(ct => ({ ...ct, node: nodeName, endpointId: endpointId, type: 'lxc' }))
37+
);
6538

66-
// Return all collected data, even if some parts failed.
6739
return {
68-
vms: nodeData.vms,
69-
containers: nodeData.containers,
70-
nodeStatus: nodeData.nodeStatus,
71-
storage: nodeData.storage,
40+
vms: vms || [],
41+
containers: containers || [],
42+
nodeStatus: nodeStatus || {},
43+
storage: storage || [],
7244
};
7345
}
7446

@@ -123,62 +95,55 @@ async function fetchPveDiscoveryData(currentApiClients) {
12395
const correspondingNodeInfo = nodes[index]; // Get the original info from /nodes
12496
if (!correspondingNodeInfo || !correspondingNodeInfo.node) return;
12597

126-
const baseNode = {
127-
// Explicit Defaults first:
128-
cpu: null,
129-
mem: null,
130-
disk: null,
131-
maxdisk: null,
132-
uptime: 0,
133-
loadavg: null,
134-
status: 'unknown',
135-
storage: [],
136-
// Explicitly copy known/expected fields from correspondingNodeInfo:
137-
node: correspondingNodeInfo.node,
138-
maxcpu: correspondingNodeInfo.maxcpu, // Assuming these exist
139-
maxmem: correspondingNodeInfo.maxmem,
140-
level: correspondingNodeInfo.level,
141-
// Set status based on correspondingNodeInfo, falling back to the default above:
142-
status: correspondingNodeInfo.status || 'unknown',
143-
// Set IDs:
144-
id: `${endpointName}-${correspondingNodeInfo.node}`,
145-
endpointId: endpointName,
98+
// Initialize finalNode with defaults and info from /nodes endpoint
99+
const finalNode = {
100+
cpu: null,
101+
mem: null,
102+
disk: null,
103+
maxdisk: null,
104+
uptime: 0,
105+
loadavg: null,
106+
storage: [],
107+
node: correspondingNodeInfo.node,
108+
maxcpu: correspondingNodeInfo.maxcpu,
109+
maxmem: correspondingNodeInfo.maxmem,
110+
level: correspondingNodeInfo.level,
111+
status: correspondingNodeInfo.status || 'unknown',
112+
id: `${endpointName}-${correspondingNodeInfo.node}`,
113+
endpointId: endpointName,
146114
};
147115

148116
if (result.status === 'fulfilled' && result.value) {
149-
// --- Guest fetch succeeded ---
150117
const nodeData = result.value;
151-
const currentEndpointId = endpointId;
152-
endpointVms.push(...nodeData.vms.map(vm => ({...vm, endpointId: currentEndpointId, id: `${endpointName}-${vm.node}-${vm.vmid}`})));
153-
endpointContainers.push(...nodeData.containers.map(ct => ({...ct, endpointId: currentEndpointId, id: `${endpointName}-${ct.node}-${ct.vmid}`})));
154-
155-
// Build the final node object, merging status/storage or keeping defaults
156-
let finalNode = { ...baseNode }; // Copy base node
157-
// Only merge status if nodeData.nodeStatus is not empty
158-
if(nodeData.nodeStatus && Object.keys(nodeData.nodeStatus).length > 0) {
118+
const currentEndpointId = endpointId; // Renamed from endpointId to avoid conflict in map
119+
120+
endpointVms.push(...(nodeData.vms || []).map(vm => ({ ...vm, endpointId: currentEndpointId, id: `${endpointName}-${vm.node}-${vm.vmid}` })));
121+
endpointContainers.push(...(nodeData.containers || []).map(ct => ({ ...ct, endpointId: currentEndpointId, id: `${endpointName}-${ct.node}-${ct.vmid}` })));
122+
123+
if (nodeData.nodeStatus && Object.keys(nodeData.nodeStatus).length > 0) {
159124
const statusData = nodeData.nodeStatus;
160125
finalNode.cpu = statusData.cpu;
161126
finalNode.mem = statusData.memory?.used || statusData.mem;
162127
finalNode.disk = statusData.rootfs?.used || statusData.disk;
163128
finalNode.maxdisk = statusData.rootfs?.total || statusData.maxdisk;
164129
finalNode.uptime = statusData.uptime;
165130
finalNode.loadavg = statusData.loadavg;
166-
finalNode.status = statusData.uptime > 0 ? 'online' : baseNode.status; // Use baseNode status if uptime is 0
131+
// Update status only if uptime indicates online, otherwise keep the status from /nodes
132+
if (statusData.uptime > 0) {
133+
finalNode.status = 'online';
134+
}
167135
}
168-
finalNode.storage = nodeData.storage || baseNode.storage; // Use baseNode storage if nodeData.storage is missing
136+
finalNode.storage = nodeData.storage && nodeData.storage.length > 0 ? nodeData.storage : finalNode.storage;
169137

170138
processedNodes.push(finalNode);
171-
172-
} else { // Includes result.status === 'rejected' or other unexpected cases
173-
// --- Guest fetch failed OR nodeData missing ---
174-
if (result.status === 'rejected') {
175-
console.error(`[DataFetcher - ${endpointName}] Error processing node ${correspondingNodeInfo.node}: ${result.reason?.message || result.reason}`);
176-
} else {
177-
// Handle cases where status is fulfilled but value might be invalid
178-
console.warn(`[DataFetcher - ${endpointName}] Unexpected result status for node ${correspondingNodeInfo.node}: ${result.status}`);
179-
}
180-
// Push the base node object (which has defaults correctly applied)
181-
processedNodes.push(baseNode);
139+
} else {
140+
if (result.status === 'rejected') {
141+
console.error(`[DataFetcher - ${endpointName}] Error processing node ${correspondingNodeInfo.node}: ${result.reason?.message || result.reason}`);
142+
} else {
143+
console.warn(`[DataFetcher - ${endpointName}] Unexpected result status for node ${correspondingNodeInfo.node}: ${result.status}`);
144+
}
145+
// Push the node with defaults if fetching detailed data failed
146+
processedNodes.push(finalNode);
182147
}
183148
});
184149

@@ -363,38 +328,61 @@ async function fetchPbsData(currentPbsApiClients) {
363328
const pbsPromises = pbsClientIds.map(async (pbsClientId) => {
364329
const pbsClient = currentPbsApiClients[pbsClientId]; // { client, config }
365330
const instanceName = pbsClient.config.name;
366-
let instanceData = { /* Initial structure */ };
331+
// Initialize status and include identifiers early
332+
let instanceData = {
333+
pbsEndpointId: pbsClientId,
334+
pbsInstanceName: instanceName,
335+
status: 'pending_initialization'
336+
};
367337

368338
try {
339+
console.log(`INFO: [DataFetcher - ${instanceName}] Starting fetch. Initial status: ${instanceData.status}`);
340+
369341
const nodeName = pbsClient.config.nodeName || await fetchPbsNodeName(pbsClient);
342+
console.log(`INFO: [DataFetcher - ${instanceName}] Determined nodeName: '${nodeName}'. Configured nodeName: '${pbsClient.config.nodeName}'`);
343+
370344
if (nodeName && nodeName !== 'localhost' && !pbsClient.config.nodeName) {
371345
pbsClient.config.nodeName = nodeName; // Store detected name back
346+
console.log(`INFO: [DataFetcher - ${instanceName}] Stored detected nodeName: '${nodeName}' to config.`);
372347
}
373348

374349
if (nodeName && nodeName !== 'localhost') {
350+
console.log(`INFO: [DataFetcher - ${instanceName}] Node name '${nodeName}' is valid. Proceeding with data fetch.`);
351+
375352
const datastoresResult = await fetchPbsDatastoreData(pbsClient);
376353
const snapshotFetchPromises = datastoresResult.map(async (ds) => {
377354
ds.snapshots = await fetchPbsDatastoreSnapshots(pbsClient, ds.name);
378355
return ds;
379356
});
380357
instanceData.datastores = await Promise.all(snapshotFetchPromises);
381-
358+
console.log(`INFO: [DataFetcher - ${instanceName}] Datastores and snapshots fetched. Number of datastores: ${instanceData.datastores ? instanceData.datastores.length : 'N/A'}`);
359+
382360
const allTasksResult = await fetchAllPbsTasksForProcessing(pbsClient, nodeName);
361+
console.log(`INFO: [DataFetcher - ${instanceName}] Tasks fetched. Result error: ${allTasksResult.error}, Tasks found: ${allTasksResult.tasks ? allTasksResult.tasks.length : 'null'}`);
362+
383363
if (allTasksResult.tasks) {
364+
console.log(`INFO: [DataFetcher - ${instanceName}] Processing tasks...`);
384365
const processedTasks = processPbsTasks(allTasksResult.tasks); // Assumes processPbsTasks is imported
385366
instanceData = { ...instanceData, ...processedTasks }; // Merge task summaries
367+
console.log(`INFO: [DataFetcher - ${instanceName}] Tasks processed.`);
368+
} else {
369+
console.warn(`WARN: [DataFetcher - ${instanceName}] No tasks to process or task fetching failed.`);
386370
}
371+
387372
instanceData.status = 'ok';
388373
instanceData.nodeName = nodeName; // Ensure nodeName is set
374+
console.log(`INFO: [DataFetcher - ${instanceName}] Successfully fetched all data. Status set to: ${instanceData.status}`);
389375
} else {
376+
console.warn(`WARN: [DataFetcher - ${instanceName}] Node name '${nodeName}' is invalid or 'localhost'. Throwing error.`);
390377
throw new Error(`Could not determine node name for PBS instance ${instanceName}`);
391378
}
392379
} catch (pbsError) {
393-
console.error(`ERROR: [DataFetcher] PBS fetch failed for ${instanceName}: ${pbsError.message}`);
380+
console.error(`ERROR: [DataFetcher - ${instanceName}] PBS fetch failed (outer catch): ${pbsError.message}. Stack: ${pbsError.stack}`);
394381
instanceData.status = 'error';
382+
console.log(`INFO: [DataFetcher - ${instanceName}] Status set to '${instanceData.status}' due to error.`);
395383
}
396-
instanceData.pbsEndpointId = pbsClientId;
397-
instanceData.pbsInstanceName = instanceName;
384+
// pbsEndpointId and pbsInstanceName are already part of instanceData from initialization
385+
console.log(`INFO: [DataFetcher - ${instanceName}] Finalizing instance data. Status: ${instanceData.status}, NodeName: ${instanceData.nodeName || 'N/A'}`);
398386
return instanceData;
399387
});
400388

@@ -599,6 +587,51 @@ async function fetchMetricsData(runningVms, runningContainers, currentApiClients
599587
return allMetrics;
600588
}
601589

590+
async function fetchQemuAgentMemoryInfo(apiClient, nodeName, vmid, guestName, endpointName, currentMetrics) {
591+
try {
592+
const agentMemInfoResponse = await apiClient.post(`/nodes/${nodeName}/qemu/${vmid}/agent/get-memory-block-info`, {});
593+
594+
if (agentMemInfoResponse?.data?.data?.result) {
595+
const agentMem = agentMemInfoResponse.data.data.result;
596+
let guestMemoryDetails = null;
597+
598+
if (Array.isArray(agentMem) && agentMem.length > 0 && agentMem[0].hasOwnProperty('total') && agentMem[0].hasOwnProperty('free')) {
599+
guestMemoryDetails = agentMem[0];
600+
} else if (typeof agentMem === 'object' && agentMem !== null && agentMem.hasOwnProperty('total') && agentMem.hasOwnProperty('free')) {
601+
guestMemoryDetails = agentMem;
602+
}
603+
604+
if (guestMemoryDetails) {
605+
currentMetrics.guest_mem_total_bytes = guestMemoryDetails.total;
606+
currentMetrics.guest_mem_free_bytes = guestMemoryDetails.free;
607+
currentMetrics.guest_mem_available_bytes = guestMemoryDetails.available;
608+
currentMetrics.guest_mem_cached_bytes = guestMemoryDetails.cached;
609+
currentMetrics.guest_mem_buffers_bytes = guestMemoryDetails.buffers;
610+
611+
if (guestMemoryDetails.available !== undefined) {
612+
currentMetrics.guest_mem_actual_used_bytes = guestMemoryDetails.total - guestMemoryDetails.available;
613+
} else if (guestMemoryDetails.cached !== undefined && guestMemoryDetails.buffers !== undefined) {
614+
currentMetrics.guest_mem_actual_used_bytes = guestMemoryDetails.total - guestMemoryDetails.free - guestMemoryDetails.cached - guestMemoryDetails.buffers;
615+
} else {
616+
currentMetrics.guest_mem_actual_used_bytes = guestMemoryDetails.total - guestMemoryDetails.free;
617+
}
618+
console.log(`[Metrics Cycle - ${endpointName}] VM ${vmid} (${guestName}): Guest agent memory fetched: Actual Used: ${((currentMetrics.guest_mem_actual_used_bytes || 0) / (1024*1024)).toFixed(0)}MB`);
619+
} else {
620+
console.warn(`[Metrics Cycle - ${endpointName}] VM ${vmid} (${guestName}): Guest agent memory command 'get-memory-block-info' response format not as expected. Data:`, agentMemInfoResponse.data.data);
621+
}
622+
} else {
623+
console.warn(`[Metrics Cycle - ${endpointName}] VM ${vmid} (${guestName}): Guest agent memory command 'get-memory-block-info' did not return expected data structure. Response:`, agentMemInfoResponse.data);
624+
}
625+
} catch (agentError) {
626+
if (agentError.response && agentError.response.status === 500 && agentError.response.data && agentError.response.data.data && agentError.response.data.data.exitcode === -2) {
627+
console.log(`[Metrics Cycle - ${endpointName}] VM ${vmid} (${guestName}): QEMU Guest Agent not responsive or command 'get-memory-block-info' not available/supported. Error: ${agentError.message}`);
628+
} else {
629+
console.warn(`[Metrics Cycle - ${endpointName}] VM ${vmid} (${guestName}): Error fetching guest agent memory info: ${agentError.message}. Status: ${agentError.response?.status}`);
630+
}
631+
}
632+
return currentMetrics;
633+
}
634+
602635
module.exports = {
603636
fetchDiscoveryData,
604637
fetchPbsData, // Keep exporting the real one
@@ -607,4 +640,4 @@ module.exports = {
607640
// fetchPbsNodeName,
608641
// fetchPbsDatastoreData,
609642
// fetchAllPbsTasksForProcessing
610-
};
643+
};

0 commit comments

Comments
 (0)