1
1
const { processPbsTasks } = require ( './pbsUtils' ) ; // Assuming pbsUtils.js exists or will be created
2
2
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 ) {
13
5
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 ;
24
8
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 ;
30
15
} 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 ;
32
18
}
33
19
} 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
36
22
}
23
+ }
37
24
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 ) ;
51
28
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
+ ) ;
64
33
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
+ ) ;
65
38
66
- // Return all collected data, even if some parts failed.
67
39
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 || [ ] ,
72
44
} ;
73
45
}
74
46
@@ -123,62 +95,55 @@ async function fetchPveDiscoveryData(currentApiClients) {
123
95
const correspondingNodeInfo = nodes [ index ] ; // Get the original info from /nodes
124
96
if ( ! correspondingNodeInfo || ! correspondingNodeInfo . node ) return ;
125
97
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 ,
146
114
} ;
147
115
148
116
if ( result . status === 'fulfilled' && result . value ) {
149
- // --- Guest fetch succeeded ---
150
117
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 ) {
159
124
const statusData = nodeData . nodeStatus ;
160
125
finalNode . cpu = statusData . cpu ;
161
126
finalNode . mem = statusData . memory ?. used || statusData . mem ;
162
127
finalNode . disk = statusData . rootfs ?. used || statusData . disk ;
163
128
finalNode . maxdisk = statusData . rootfs ?. total || statusData . maxdisk ;
164
129
finalNode . uptime = statusData . uptime ;
165
130
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
+ }
167
135
}
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 ;
169
137
170
138
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 ) ;
182
147
}
183
148
} ) ;
184
149
@@ -363,38 +328,61 @@ async function fetchPbsData(currentPbsApiClients) {
363
328
const pbsPromises = pbsClientIds . map ( async ( pbsClientId ) => {
364
329
const pbsClient = currentPbsApiClients [ pbsClientId ] ; // { client, config }
365
330
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
+ } ;
367
337
368
338
try {
339
+ console . log ( `INFO: [DataFetcher - ${ instanceName } ] Starting fetch. Initial status: ${ instanceData . status } ` ) ;
340
+
369
341
const nodeName = pbsClient . config . nodeName || await fetchPbsNodeName ( pbsClient ) ;
342
+ console . log ( `INFO: [DataFetcher - ${ instanceName } ] Determined nodeName: '${ nodeName } '. Configured nodeName: '${ pbsClient . config . nodeName } '` ) ;
343
+
370
344
if ( nodeName && nodeName !== 'localhost' && ! pbsClient . config . nodeName ) {
371
345
pbsClient . config . nodeName = nodeName ; // Store detected name back
346
+ console . log ( `INFO: [DataFetcher - ${ instanceName } ] Stored detected nodeName: '${ nodeName } ' to config.` ) ;
372
347
}
373
348
374
349
if ( nodeName && nodeName !== 'localhost' ) {
350
+ console . log ( `INFO: [DataFetcher - ${ instanceName } ] Node name '${ nodeName } ' is valid. Proceeding with data fetch.` ) ;
351
+
375
352
const datastoresResult = await fetchPbsDatastoreData ( pbsClient ) ;
376
353
const snapshotFetchPromises = datastoresResult . map ( async ( ds ) => {
377
354
ds . snapshots = await fetchPbsDatastoreSnapshots ( pbsClient , ds . name ) ;
378
355
return ds ;
379
356
} ) ;
380
357
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
+
382
360
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
+
383
363
if ( allTasksResult . tasks ) {
364
+ console . log ( `INFO: [DataFetcher - ${ instanceName } ] Processing tasks...` ) ;
384
365
const processedTasks = processPbsTasks ( allTasksResult . tasks ) ; // Assumes processPbsTasks is imported
385
366
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.` ) ;
386
370
}
371
+
387
372
instanceData . status = 'ok' ;
388
373
instanceData . nodeName = nodeName ; // Ensure nodeName is set
374
+ console . log ( `INFO: [DataFetcher - ${ instanceName } ] Successfully fetched all data. Status set to: ${ instanceData . status } ` ) ;
389
375
} else {
376
+ console . warn ( `WARN: [DataFetcher - ${ instanceName } ] Node name '${ nodeName } ' is invalid or 'localhost'. Throwing error.` ) ;
390
377
throw new Error ( `Could not determine node name for PBS instance ${ instanceName } ` ) ;
391
378
}
392
379
} 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 } ` ) ;
394
381
instanceData . status = 'error' ;
382
+ console . log ( `INFO: [DataFetcher - ${ instanceName } ] Status set to '${ instanceData . status } ' due to error.` ) ;
395
383
}
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' } ` ) ;
398
386
return instanceData ;
399
387
} ) ;
400
388
@@ -599,6 +587,51 @@ async function fetchMetricsData(runningVms, runningContainers, currentApiClients
599
587
return allMetrics ;
600
588
}
601
589
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
+
602
635
module . exports = {
603
636
fetchDiscoveryData,
604
637
fetchPbsData, // Keep exporting the real one
@@ -607,4 +640,4 @@ module.exports = {
607
640
// fetchPbsNodeName,
608
641
// fetchPbsDatastoreData,
609
642
// fetchAllPbsTasksForProcessing
610
- } ;
643
+ } ;
0 commit comments