@@ -44,152 +44,123 @@ async function fetchDataForNode(apiClient, endpointId, nodeName) {
44
44
} ;
45
45
}
46
46
47
+ /**
48
+ * Fetches and processes discovery data for a single PVE endpoint.
49
+ * @param {string } endpointId - The unique ID of the PVE endpoint.
50
+ * @param {Object } apiClient - The initialized Axios client instance for this endpoint.
51
+ * @param {Object } config - The configuration object for this endpoint.
52
+ * @returns {Promise<Object> } - { nodes: Array, vms: Array, containers: Array } for this endpoint.
53
+ */
54
+ async function fetchDataForPveEndpoint ( endpointId , apiClientInstance , config ) {
55
+ const endpointName = config . name || endpointId ; // Use configured name or ID
56
+ try {
57
+ const nodesResponse = await apiClientInstance . get ( '/nodes' ) ;
58
+ const nodes = nodesResponse . data . data ;
59
+ if ( ! nodes || ! Array . isArray ( nodes ) ) {
60
+ console . warn ( `[DataFetcher - ${ endpointName } ] No nodes found or unexpected format.` ) ;
61
+ return { nodes : [ ] , vms : [ ] , containers : [ ] } ;
62
+ }
63
+
64
+ const guestPromises = nodes . map ( node => fetchDataForNode ( apiClientInstance , endpointName , node . node ) ) ;
65
+ const guestResults = await Promise . allSettled ( guestPromises ) ;
66
+
67
+ let endpointVms = [ ] ;
68
+ let endpointContainers = [ ] ;
69
+ let processedNodes = [ ] ;
70
+
71
+ guestResults . forEach ( ( result , index ) => {
72
+ const correspondingNodeInfo = nodes [ index ] ;
73
+ if ( ! correspondingNodeInfo || ! correspondingNodeInfo . node ) return ;
74
+
75
+ const finalNode = {
76
+ cpu : null , mem : null , disk : null , maxdisk : null , uptime : 0 , loadavg : null , storage : [ ] ,
77
+ node : correspondingNodeInfo . node ,
78
+ maxcpu : correspondingNodeInfo . maxcpu ,
79
+ maxmem : correspondingNodeInfo . maxmem ,
80
+ level : correspondingNodeInfo . level ,
81
+ status : correspondingNodeInfo . status || 'unknown' ,
82
+ id : `${ endpointName } -${ correspondingNodeInfo . node } ` ,
83
+ endpointId : endpointName , // Use the actual endpointName here
84
+ } ;
85
+
86
+ if ( result . status === 'fulfilled' && result . value ) {
87
+ const nodeData = result . value ;
88
+ // Use endpointName for constructing IDs
89
+ endpointVms . push ( ...( nodeData . vms || [ ] ) . map ( vm => ( { ...vm , endpointId : endpointName , id : `${ endpointName } -${ vm . node } -${ vm . vmid } ` } ) ) ) ;
90
+ endpointContainers . push ( ...( nodeData . containers || [ ] ) . map ( ct => ( { ...ct , endpointId : endpointName , id : `${ endpointName } -${ ct . node } -${ ct . vmid } ` } ) ) ) ;
91
+
92
+ if ( nodeData . nodeStatus && Object . keys ( nodeData . nodeStatus ) . length > 0 ) {
93
+ const statusData = nodeData . nodeStatus ;
94
+ finalNode . cpu = statusData . cpu ;
95
+ finalNode . mem = statusData . memory ?. used || statusData . mem ;
96
+ finalNode . disk = statusData . rootfs ?. used || statusData . disk ;
97
+ finalNode . maxdisk = statusData . rootfs ?. total || statusData . maxdisk ;
98
+ finalNode . uptime = statusData . uptime ;
99
+ finalNode . loadavg = statusData . loadavg ;
100
+ if ( statusData . uptime > 0 ) {
101
+ finalNode . status = 'online' ;
102
+ }
103
+ }
104
+ finalNode . storage = nodeData . storage && nodeData . storage . length > 0 ? nodeData . storage : finalNode . storage ;
105
+ processedNodes . push ( finalNode ) ;
106
+ } else {
107
+ if ( result . status === 'rejected' ) {
108
+ console . error ( `[DataFetcher - ${ endpointName } ] Error processing node ${ correspondingNodeInfo . node } : ${ result . reason ?. message || result . reason } ` ) ;
109
+ } else {
110
+ console . warn ( `[DataFetcher - ${ endpointName } ] Unexpected result status for node ${ correspondingNodeInfo . node } : ${ result . status } ` ) ;
111
+ }
112
+ processedNodes . push ( finalNode ) ; // Push node with defaults on failure
113
+ }
114
+ } ) ;
115
+
116
+ return { nodes : processedNodes , vms : endpointVms , containers : endpointContainers } ;
117
+
118
+ } catch ( error ) {
119
+ const status = error . response ?. status ? ` (Status: ${ error . response . status } )` : '' ;
120
+ console . error ( `[DataFetcher - ${ endpointName } ] Error fetching PVE discovery data${ status } : ${ error . message } ` ) ;
121
+ // Return empty structure on endpoint-level failure
122
+ return { nodes : [ ] , vms : [ ] , containers : [ ] } ;
123
+ }
124
+ }
125
+
126
+
47
127
/**
48
128
* Fetches structural PVE data: node list, statuses, VM/CT lists.
49
129
* @param {Object } currentApiClients - Initialized PVE API clients.
50
130
* @returns {Promise<Object> } - { nodes, vms, containers }
51
131
*/
52
132
async function fetchPveDiscoveryData ( currentApiClients ) {
53
133
const pveEndpointIds = Object . keys ( currentApiClients ) ;
54
- let tempNodes = [ ] , tempVms = [ ] , tempContainers = [ ] ;
134
+ let allNodes = [ ] , allVms = [ ] , allContainers = [ ] ;
55
135
56
136
if ( pveEndpointIds . length === 0 ) {
57
137
console . log ( "[DataFetcher] No PVE endpoints configured or initialized." ) ;
58
138
return { nodes : [ ] , vms : [ ] , containers : [ ] } ;
59
139
}
60
140
61
141
console . log ( `[DataFetcher] Fetching PVE discovery data for ${ pveEndpointIds . length } endpoints...` ) ;
62
- const pvePromises = pveEndpointIds . map ( endpointId =>
63
- ( async ( ) => {
64
- const { client : apiClientInstance , config } = currentApiClients [ endpointId ] ;
65
- const endpointName = config . name || endpointId ;
66
- try {
67
- const nodesResponse = await apiClientInstance . get ( '/nodes' ) ;
68
- const nodes = nodesResponse . data . data ;
69
- if ( ! nodes || ! Array . isArray ( nodes ) ) {
70
- console . warn ( `[DataFetcher - ${ endpointName } ] No nodes found or unexpected format.` ) ;
71
- return { endpointId : endpointName , status : 'fulfilled' , value : { nodes : [ ] , vms : [ ] , containers : [ ] } } ;
72
- }
73
142
74
- const guestPromises = nodes . map ( node => fetchDataForNode ( apiClientInstance , endpointName , node . node ) ) ;
75
- const guestResults = await Promise . allSettled ( guestPromises ) ;
76
-
77
- let endpointVms = [ ] ;
78
- let endpointContainers = [ ] ;
79
- let processedNodes = [ ] ; // New array to store results
80
-
81
- // Initialize endpointNodes with basic info from the /nodes call
82
- const defaultNode = {
83
- cpu : null ,
84
- mem : null ,
85
- disk : null ,
86
- maxdisk : null ,
87
- uptime : 0 ,
88
- loadavg : null ,
89
- status : 'unknown' , // Default status
90
- storage : [ ] , // Default storage
91
- } ;
92
-
93
- // Process guest results and merge status/storage into processedNodes
94
- guestResults . forEach ( ( result , index ) => {
95
- const correspondingNodeInfo = nodes [ index ] ; // Get the original info from /nodes
96
- if ( ! correspondingNodeInfo || ! correspondingNodeInfo . node ) return ;
97
-
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 ,
114
- } ;
115
-
116
- if ( result . status === 'fulfilled' && result . value ) {
117
- const nodeData = result . value ;
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 ) {
124
- const statusData = nodeData . nodeStatus ;
125
- finalNode . cpu = statusData . cpu ;
126
- finalNode . mem = statusData . memory ?. used || statusData . mem ;
127
- finalNode . disk = statusData . rootfs ?. used || statusData . disk ;
128
- finalNode . maxdisk = statusData . rootfs ?. total || statusData . maxdisk ;
129
- finalNode . uptime = statusData . uptime ;
130
- finalNode . loadavg = statusData . loadavg ;
131
- // Update status only if uptime indicates online, otherwise keep the status from /nodes
132
- if ( statusData . uptime > 0 ) {
133
- finalNode . status = 'online' ;
134
- }
135
- }
136
- finalNode . storage = nodeData . storage && nodeData . storage . length > 0 ? nodeData . storage : finalNode . storage ;
137
-
138
- processedNodes . push ( finalNode ) ;
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 ) ;
147
- }
148
- } ) ;
149
-
150
- // Return the newly constructed processedNodes array
151
- return { endpointId : endpointName , status : 'fulfilled' , value : { nodes : processedNodes , vms : endpointVms , containers : endpointContainers } } ;
152
- }
153
- /* istanbul ignore next */ // Ignore this catch block - tested via side effects (logging, filtering)
154
- catch ( error ) {
155
- // This catch block handles failures in the initial /nodes call
156
- const status = error . response ?. status ? ` (Status: ${ error . response . status } )` : '' ;
157
- console . error ( `[DataFetcher - ${ endpointName } ] Error fetching PVE discovery data${ status } : ${ error . message } ` ) ;
158
- // Return a specific structure indicating failure for THIS endpoint
159
- return { endpointId : endpointName , status : 'rejected' , reason : error . message || String ( error ) } ;
160
- }
161
- } ) ( )
162
- ) ;
163
-
164
- const pveOutcomes = await Promise . allSettled ( pvePromises ) ;
165
-
166
- // Aggregate results from all endpoints, including partially successful ones
167
- pveOutcomes . forEach ( endpointOutcome => {
168
- if ( endpointOutcome . status === 'fulfilled' ) {
169
- if ( endpointOutcome . value . status === 'fulfilled' && endpointOutcome . value . value ) {
170
- const { nodes, vms, containers } = endpointOutcome . value . value ;
171
- tempNodes . push ( ...nodes ) ;
172
- if ( vms && Array . isArray ( vms ) ) {
173
- vms . forEach ( vm => tempVms . push ( vm ) ) ;
174
- }
175
- if ( containers && Array . isArray ( containers ) ) {
176
- containers . forEach ( ct => tempContainers . push ( ct ) ) ;
177
- }
178
- } else if ( endpointOutcome . value . status === 'rejected' ) {
179
- // Log the reason for endpoint-level failure (e.g., /nodes failed)
180
- console . error ( `[DataFetcher] PVE discovery failed for endpoint: ${ endpointOutcome . value . endpointId } . Reason: ${ endpointOutcome . value . reason } ` ) ;
181
- }
182
- } else {
183
- // This handles cases where the outer promise itself rejected (less likely with current structure)
184
- const reason = endpointOutcome . reason ?. message || endpointOutcome . reason ;
185
- // We might not know the endpoint ID here easily
186
- console . error ( `[DataFetcher] Unhandled error processing PVE endpoint: ${ reason } ` ) ;
143
+ const pvePromises = pveEndpointIds . map ( endpointId => {
144
+ const { client : apiClientInstance , config } = currentApiClients [ endpointId ] ;
145
+ // Pass endpointId, client, and config to the helper
146
+ return fetchDataForPveEndpoint ( endpointId , apiClientInstance , config ) ;
147
+ } ) ;
148
+
149
+ const pveResults = await Promise . all ( pvePromises ) ; // Wait for all endpoint fetches
150
+
151
+ // Aggregate results from all endpoints
152
+ pveResults . forEach ( result => {
153
+ if ( result ) { // Check if result is not null/undefined (error handled in helper)
154
+ allNodes . push ( ...( result . nodes || [ ] ) ) ;
155
+ allVms . push ( ...( result . vms || [ ] ) ) ;
156
+ allContainers . push ( ...( result . containers || [ ] ) ) ;
187
157
}
188
158
} ) ;
189
159
190
- return { nodes : tempNodes , vms : tempVms , containers : tempContainers } ;
160
+ return { nodes : allNodes , vms : allVms , containers : allContainers } ;
191
161
}
192
162
163
+
193
164
// --- PBS Data Fetching Functions ---
194
165
195
166
/**
@@ -360,13 +331,17 @@ async function fetchPbsData(currentPbsApiClients) {
360
331
const allTasksResult = await fetchAllPbsTasksForProcessing ( pbsClient , nodeName ) ;
361
332
console . log ( `INFO: [DataFetcher - ${ instanceName } ] Tasks fetched. Result error: ${ allTasksResult . error } , Tasks found: ${ allTasksResult . tasks ? allTasksResult . tasks . length : 'null' } ` ) ;
362
333
363
- if ( allTasksResult . tasks ) {
334
+ if ( allTasksResult . tasks && ! allTasksResult . error ) {
364
335
console . log ( `INFO: [DataFetcher - ${ instanceName } ] Processing tasks...` ) ;
365
336
const processedTasks = processPbsTasks ( allTasksResult . tasks ) ; // Assumes processPbsTasks is imported
366
337
instanceData = { ...instanceData , ...processedTasks } ; // Merge task summaries
367
338
console . log ( `INFO: [DataFetcher - ${ instanceName } ] Tasks processed.` ) ;
368
339
} else {
369
- console . warn ( `WARN: [DataFetcher - ${ instanceName } ] No tasks to process or task fetching failed.` ) ;
340
+ console . warn ( `WARN: [DataFetcher - ${ instanceName } ] No tasks to process or task fetching failed. Error flag: ${ allTasksResult . error } , Tasks array: ${ allTasksResult . tasks === null ? 'null' : ( Array . isArray ( allTasksResult . tasks ) ? `array[${ allTasksResult . tasks . length } ]` : typeof allTasksResult . tasks ) } ` ) ;
341
+ // If tasks failed to fetch or process, ensure task-specific fields are not from a stale/default mock
342
+ // instanceData.backupTasks = instanceData.backupTasks || []; // Or undefined/null if preferred by consumers
343
+ // instanceData.verifyTasks = instanceData.verifyTasks || [];
344
+ // instanceData.gcTasks = instanceData.gcTasks || [];
370
345
}
371
346
372
347
instanceData . status = 'ok' ;
@@ -587,51 +562,6 @@ async function fetchMetricsData(runningVms, runningContainers, currentApiClients
587
562
return allMetrics ;
588
563
}
589
564
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
-
635
565
module . exports = {
636
566
fetchDiscoveryData,
637
567
fetchPbsData, // Keep exporting the real one
0 commit comments