Skip to content

Commit bed3bca

Browse files
committed
🐛 call UtilizationService on HEAD and LIST veeam routes
Call UtilizationService.getUtilizationMetrics for capacity.xml on the HEAD and LIST routes, mirroring the existing pattern from GET. This ensures Last-Modified is derived from SUR metrics (not stale metadata) on all three routes. Fall back to new Date() when metrics are unavailable (404) or absent. Also use new Date() as the fallback in GET instead of the stored LastModified value. Issue: CLDSRV-878
1 parent d9a5aed commit bed3bca

File tree

4 files changed

+224
-54
lines changed

4 files changed

+224
-54
lines changed

lib/routes/veeam/get.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function getVeeamFile(request, response, bucketMd, log) {
3434

3535
// Extract the last modified date, but do not include it when computing
3636
// the file's ETag (md5)
37-
const modified = bucketMetrics?.date || fileToBuild.value.LastModified;
37+
const modified = bucketMetrics?.date || new Date();
3838
delete fileToBuild.value.LastModified;
3939

4040
// The SOSAPI metrics are dynamically computed using the SUR backend.

lib/routes/veeam/head.js

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
const xml2js = require('xml2js');
22
const { errors } = require('arsenal');
33
const metadata = require('../../metadata/wrapper');
4-
const { getResponseHeader, buildHeadXML, getFileToBuild } = require('./utils');
4+
const { getResponseHeader, buildHeadXML, getFileToBuild, isSystemXML } = require('./utils');
55
const { responseXMLBody, responseContentHeaders } = require('arsenal/build/lib/s3routes/routesUtils');
6+
const UtilizationService = require('../../../lib/utilization/instance');
67

78
/**
89
* Returns system.xml or capacity.xml files metadata for a given bucket.
@@ -23,27 +24,58 @@ function headVeeamFile(request, response, bucketMd, log) {
2324
return responseXMLBody(errors.InternalError, null, response, log);
2425
}
2526

26-
const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
27-
if (fileToBuild.error) {
28-
return responseXMLBody(fileToBuild.error, null, response, log);
29-
}
27+
const finalizeRequest = bucketMetrics => {
28+
const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
29+
if (fileToBuild.error) {
30+
return responseXMLBody(fileToBuild.error, null, response, log);
31+
}
32+
33+
// Extract the last modified date, but do not include it when computing the file's ETag (md5)
34+
const modified = bucketMetrics?.date || new Date();
35+
delete fileToBuild.value.LastModified;
36+
const builder = new xml2js.Builder({
37+
headless: true,
38+
});
39+
const dataBuffer = Buffer.from(buildHeadXML(builder.buildObject(fileToBuild)));
40+
41+
return responseContentHeaders(
42+
null,
43+
{},
44+
getResponseHeader(request, data, dataBuffer, modified, log),
45+
response,
46+
log,
47+
);
48+
};
49+
50+
if (!isSystemXML(request.objectKey)) {
51+
const bucketKey = `${bucketMd._name}_${new Date(bucketMd._creationDate).getTime()}`;
52+
return UtilizationService.getUtilizationMetrics('bucket', bucketKey, null, {}, (utilizationErr, bucketMetrics) => {
53+
if (utilizationErr) {
54+
const statusCode = utilizationErr.response?.status || utilizationErr.statusCode || utilizationErr.code;
3055

31-
// Extract the last modified date, but do not include it when computing the file's ETag (md5)
32-
const modified = fileToBuild.value.LastModified;
33-
delete fileToBuild.value.LastModified;
34-
// Recompute file content to generate appropriate content-md5 header
35-
const builder = new xml2js.Builder({
36-
headless: true,
37-
});
38-
const dataBuffer = Buffer.from(buildHeadXML(builder.buildObject(fileToBuild)));
39-
40-
return responseContentHeaders(
41-
null,
42-
{},
43-
getResponseHeader(request, data, dataBuffer, modified, log),
44-
response,
45-
log,
46-
);
56+
if (statusCode === 404) {
57+
log.warn('UtilizationService returned 404 when fetching capacity metrics', {
58+
method: 'headVeeamFile',
59+
bucket: request.bucketName,
60+
error: utilizationErr.message || utilizationErr.code,
61+
});
62+
return finalizeRequest();
63+
}
64+
65+
log.error('error fetching capacity metrics from UtilizationService', {
66+
method: 'headVeeamFile',
67+
bucket: request.bucketName,
68+
error: utilizationErr.message || utilizationErr.code,
69+
statusCode,
70+
});
71+
72+
return responseXMLBody(errors.InternalError, null, response, log);
73+
}
74+
75+
return finalizeRequest(bucketMetrics);
76+
});
77+
}
78+
return finalizeRequest();
4779
});
4880
}
4981

lib/routes/veeam/list.js

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const metadata = require('../../metadata/wrapper');
66
const { responseXMLBody } = require('arsenal/build/lib/s3routes/routesUtils');
77
const { respondWithData, getResponseHeader, buildHeadXML, validPath } = require('./utils');
88
const { processVersions, processMasterVersions } = require('../../api/bucketGet');
9+
const UtilizationService = require('../../../lib/utilization/instance');
910

1011

1112
/**
@@ -96,37 +97,73 @@ function listVeeamFiles(request, response, bucketMd, log) {
9697
if (err) {
9798
return responseXMLBody(errors.InternalError, null, response, log);
9899
}
99-
const filesToBuild = [];
100-
const fieldsToGenerate = [];
101-
if (data._capabilities?.VeeamSOSApi?.SystemInfo) {
102-
fieldsToGenerate.push({
103-
...data._capabilities?.VeeamSOSApi?.SystemInfo,
104-
name: `${validPath}system.xml`,
100+
101+
const buildAndRespond = bucketMetrics => {
102+
const filesToBuild = [];
103+
const fieldsToGenerate = [];
104+
if (data._capabilities?.VeeamSOSApi?.SystemInfo) {
105+
fieldsToGenerate.push({
106+
...data._capabilities?.VeeamSOSApi?.SystemInfo,
107+
name: `${validPath}system.xml`,
108+
});
109+
}
110+
if (data._capabilities?.VeeamSOSApi?.CapacityInfo) {
111+
fieldsToGenerate.push({
112+
...data._capabilities?.VeeamSOSApi?.CapacityInfo,
113+
name: `${validPath}capacity.xml`,
114+
});
115+
}
116+
fieldsToGenerate.forEach(file => {
117+
const isCapacity = file.name.endsWith('capacity.xml');
118+
const lastModified = isCapacity
119+
? (bucketMetrics?.date || new Date())
120+
: file.LastModified;
121+
// eslint-disable-next-line no-param-reassign
122+
delete file.LastModified;
123+
const builder = new xml2js.Builder({
124+
headless: true,
125+
});
126+
const dataBuffer = Buffer.from(buildHeadXML(builder.buildObject(file)));
127+
filesToBuild.push({
128+
...getResponseHeader(request, data,
129+
dataBuffer, lastModified, log),
130+
name: file.name,
131+
});
105132
});
106-
}
133+
// When `versions` is present, listing should return a versioned list
134+
return respondWithData(request, response, log, data,
135+
buildXMLResponse(request, filesToBuild, 'versions' in request.query));
136+
};
137+
107138
if (data._capabilities?.VeeamSOSApi?.CapacityInfo) {
108-
fieldsToGenerate.push({
109-
...data._capabilities?.VeeamSOSApi?.CapacityInfo,
110-
name: `${validPath}capacity.xml`,
139+
const bucketKey = `${bucketMd._name}_${new Date(bucketMd._creationDate).getTime()}`;
140+
return UtilizationService.getUtilizationMetrics('bucket', bucketKey, null, {}, (utilizationErr, bucketMetrics) => {
141+
if (utilizationErr) {
142+
const statusCode = utilizationErr.response?.status || utilizationErr.statusCode || utilizationErr.code;
143+
144+
if (statusCode === 404) {
145+
log.warn('UtilizationService returned 404 when fetching capacity metrics', {
146+
method: 'listVeeamFiles',
147+
bucket: request.bucketName,
148+
error: utilizationErr.message || utilizationErr.code,
149+
});
150+
return buildAndRespond();
151+
}
152+
153+
log.error('error fetching capacity metrics from UtilizationService', {
154+
method: 'listVeeamFiles',
155+
bucket: request.bucketName,
156+
error: utilizationErr.message || utilizationErr.code,
157+
statusCode,
158+
});
159+
160+
return responseXMLBody(errors.InternalError, null, response, log);
161+
}
162+
163+
return buildAndRespond(bucketMetrics);
111164
});
112165
}
113-
fieldsToGenerate.forEach(file => {
114-
const lastModified = file.LastModified;
115-
// eslint-disable-next-line no-param-reassign
116-
delete file.LastModified;
117-
const builder = new xml2js.Builder({
118-
headless: true,
119-
});
120-
const dataBuffer = Buffer.from(buildHeadXML(builder.buildObject(file)));
121-
filesToBuild.push({
122-
...getResponseHeader(request, data,
123-
dataBuffer, lastModified, log),
124-
name: file.name,
125-
});
126-
});
127-
// When `versions` is present, listing should return a versioned list
128-
return respondWithData(request, response, log, data,
129-
buildXMLResponse(request, filesToBuild, 'versions' in request.query));
166+
return buildAndRespond();
130167
});
131168
}
132169

0 commit comments

Comments
 (0)