Skip to content

Commit 48e4d3e

Browse files
committed
♻️ factorise logic in get and head lib/route/veeam
Issue: CLDSRV-878
1 parent b779f1f commit 48e4d3e

File tree

5 files changed

+89
-89
lines changed

5 files changed

+89
-89
lines changed

lib/routes/veeam/get.js

Lines changed: 6 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
const xml2js = require('xml2js');
21
const { errors } = require('arsenal');
3-
const metadata = require('../../metadata/wrapper');
4-
const { respondWithData, buildHeadXML, getFileToBuild, isSystemXML, fetchCapacityMetrics } = require('./utils');
2+
const { respondWithData, buildHeadXML, buildVeeamFileData } = require('./utils');
53
const { responseXMLBody } = require('arsenal/build/lib/s3routes/routesUtils');
64

75
/**
@@ -17,51 +15,18 @@ function getVeeamFile(request, response, bucketMd, log) {
1715
if (!bucketMd) {
1816
return responseXMLBody(errors.NoSuchBucket, null, response, log);
1917
}
18+
2019
if ('tagging' in request.query) {
2120
return respondWithData(request, response, log, bucketMd,
2221
buildHeadXML('<Tagging><TagSet></TagSet></Tagging>'));
2322
}
24-
return metadata.getBucket(request.bucketName, log, (err, data) => {
23+
24+
return buildVeeamFileData(request, bucketMd, log, 'getVeeamFile', (err, result) => {
2525
if (err) {
26-
return responseXMLBody(errors.InternalError, null, response, log);
26+
return responseXMLBody(err, null, response, log);
2727
}
28-
const finalizeRequest = bucketMetrics => {
29-
const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
30-
if (fileToBuild.error) {
31-
return responseXMLBody(fileToBuild.error, null, response, log);
32-
}
33-
34-
// Extract the last modified date, but do not include it when computing
35-
// the file's ETag (md5)
36-
const modified = bucketMetrics?.date || new Date();
37-
delete fileToBuild.value.LastModified;
3828

39-
// The SOSAPI metrics are dynamically computed using the SUR backend.
40-
if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
41-
fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
42-
fileToBuild.value.CapacityInfo.Available =
43-
Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
44-
// TODO CLDSRV-633 when SUR backend supports realtime metrics: it will
45-
// report the real last cseq/date processed by SUR, instead of the current date,
46-
// ensuring no issue in a SOSAPI context. We should use this information.
47-
}
48-
49-
const builder = new xml2js.Builder({
50-
headless: true,
51-
});
52-
return respondWithData(request, response, log, data,
53-
buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
54-
};
55-
56-
if (!isSystemXML(request.objectKey)) {
57-
return fetchCapacityMetrics(bucketMd, request, log, 'getVeeamFile', (err, bucketMetrics) => {
58-
if (err) {
59-
return responseXMLBody(errors.InternalError, null, response, log);
60-
}
61-
return finalizeRequest(bucketMetrics);
62-
});
63-
}
64-
return finalizeRequest();
29+
return respondWithData(request, response, log, result.bucketData, result.xmlContent, result.modified);
6530
});
6631
}
6732

lib/routes/veeam/head.js

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
const xml2js = require('xml2js');
21
const { errors } = require('arsenal');
3-
const metadata = require('../../metadata/wrapper');
4-
const { getResponseHeader, buildHeadXML, getFileToBuild, isSystemXML, fetchCapacityMetrics } = require('./utils');
2+
const { getResponseHeader, buildVeeamFileData } = require('./utils');
53
const { responseXMLBody, responseContentHeaders } = require('arsenal/build/lib/s3routes/routesUtils');
64

75
/**
@@ -18,44 +16,18 @@ function headVeeamFile(request, response, bucketMd, log) {
1816
return responseXMLBody(errors.NoSuchBucket, null, response, log);
1917
}
2018

21-
return metadata.getBucket(request.bucketName, log, (err, data) => {
19+
return buildVeeamFileData(request, bucketMd, log, 'headVeeamFile', (err, result) => {
2220
if (err) {
23-
return responseXMLBody(errors.InternalError, null, response, log);
21+
return responseXMLBody(err, null, response, log);
2422
}
2523

26-
const finalizeRequest = bucketMetrics => {
27-
const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
28-
if (fileToBuild.error) {
29-
return responseXMLBody(fileToBuild.error, null, response, log);
30-
}
31-
32-
// Extract the last modified date, but do not include it when computing the file's ETag (md5)
33-
const modified = bucketMetrics?.date || new Date();
34-
delete fileToBuild.value.LastModified;
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-
);
47-
};
48-
49-
if (!isSystemXML(request.objectKey)) {
50-
return fetchCapacityMetrics(bucketMd, request, log, 'headVeeamFile', (err, bucketMetrics) => {
51-
if (err) {
52-
return responseXMLBody(errors.InternalError, null, response, log);
53-
}
54-
return finalizeRequest(bucketMetrics);
55-
});
56-
}
57-
58-
return finalizeRequest();
24+
return responseContentHeaders(
25+
null,
26+
{},
27+
getResponseHeader(request, result.bucketData, result.dataBuffer, result.modified, log),
28+
response,
29+
log,
30+
);
5931
});
6032
}
6133

lib/routes/veeam/list.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,13 @@ function listVeeamFiles(request, response, bucketMd, log) {
8787
if (!bucketMd) {
8888
return responseXMLBody(errors.NoSuchBucket, null, response, log);
8989
}
90+
9091
// Only accept list-type query parameter
9192
if (!('list-type' in request.query) && !('versions' in request.query)) {
9293
return responseXMLBody(errorInstances.InvalidRequest
9394
.customizeDescription('The Veeam folder does not support this action.'), null, response, log);
9495
}
96+
9597
return metadata.getBucket(request.bucketName, log, (err, data) => {
9698
if (err) {
9799
return responseXMLBody(errors.InternalError, null, response, log);
@@ -115,7 +117,7 @@ function listVeeamFiles(request, response, bucketMd, log) {
115117
fieldsToGenerate.forEach(file => {
116118
const isCapacity = file.name.endsWith('capacity.xml');
117119
const lastModified = isCapacity
118-
? (bucketMetrics?.date || new Date())
120+
? bucketMetrics.date
119121
: file.LastModified;
120122
// eslint-disable-next-line no-param-reassign
121123
delete file.LastModified;
@@ -129,6 +131,7 @@ function listVeeamFiles(request, response, bucketMd, log) {
129131
name: file.name,
130132
});
131133
});
134+
132135
// When `versions` is present, listing should return a versioned list
133136
return respondWithData(request, response, log, data,
134137
buildXMLResponse(request, filesToBuild, 'versions' in request.query));
@@ -142,7 +145,8 @@ function listVeeamFiles(request, response, bucketMd, log) {
142145
return buildAndRespond(bucketMetrics);
143146
});
144147
}
145-
return buildAndRespond();
148+
149+
return buildAndRespond({ date: new Date() });
146150
});
147151
}
148152

lib/routes/veeam/utils.js

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
const xml2js = require('xml2js');
12
const { errors, errorInstances, jsutil } = require('arsenal');
23
const { Readable } = require('stream');
34
const collectResponseHeaders = require('../../utilities/collectResponseHeaders');
45
const collectCorsHeaders = require('../../utilities/collectCorsHeaders');
56
const crypto = require('crypto');
67
const { prepareStream } = require('arsenal/build/lib/s3middleware/prepareStream');
78
const UtilizationService = require('../../utilization/instance');
9+
const metadata = require('../../metadata/wrapper');
810

911
/**
1012
* Decodes an URI and return the result.
@@ -195,7 +197,6 @@ function getFileToBuild(request, data, inlineLastModified = false) {
195197
return {
196198
value: {
197199
[fieldName]: fileToBuild,
198-
LastModified: modified,
199200
},
200201
fieldName,
201202
};
@@ -204,14 +205,16 @@ function getFileToBuild(request, data, inlineLastModified = false) {
204205

205206
/**
206207
* Fetches capacity metrics from UtilizationService for a bucket.
207-
* Handles 404 gracefully (no metrics available yet, e.g. post-install).
208+
* Handles 404 gracefully (no metrics available yet, e.g. post-install),
209+
* returning a default bucketMetrics with the current date so callers always
210+
* receive a usable object.
208211
*
209212
* @param {object} bucketMd - bucket metadata
210213
* @param {object} request - incoming request
211214
* @param {object} log - logger object
212215
* @param {string} method - calling method name for log context
213-
* @param {function} callback - (err, bucketMetrics) where bucketMetrics
214-
* is undefined when metrics are not available (404)
216+
* @param {function} callback - (err, bucketMetrics) where bucketMetrics always
217+
* has at least a `date` field; on a real 404 the date defaults to new Date()
215218
* @returns {undefined}
216219
*/
217220
function fetchCapacityMetrics(bucketMd, request, log, method, callback) {
@@ -225,7 +228,7 @@ function fetchCapacityMetrics(bucketMd, request, log, method, callback) {
225228
bucket: request.bucketName,
226229
error: err.message || err.code,
227230
});
228-
return callback(null);
231+
return callback(null, { date: new Date() });
229232
}
230233
log.error('error fetching capacity metrics from UtilizationService', {
231234
method,
@@ -239,6 +242,61 @@ function fetchCapacityMetrics(bucketMd, request, log, method, callback) {
239242
});
240243
}
241244

245+
/**
246+
* Builds Veeam file data (XML content + response metadata) for a given request.
247+
*
248+
* @param {object} request - incoming request
249+
* @param {object} bucketMd - bucket metadata from the router
250+
* @param {object} log - logger object
251+
* @param {string} name - calling method name (for log context)
252+
* @param {function} callback - (err, result) where result is { xmlContent, dataBuffer, modified, bucketData }
253+
* @returns {undefined}
254+
*/
255+
function buildVeeamFileData(request, bucketMd, log, name, callback) {
256+
return metadata.getBucket(request.bucketName, log, (err, data) => {
257+
if (err) {
258+
return callback(errors.InternalError);
259+
}
260+
261+
const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
262+
263+
if (fileToBuild.error) {
264+
return callback(fileToBuild.error);
265+
}
266+
267+
const finalize = bucketMetrics => {
268+
const modified = bucketMetrics.date;
269+
if (
270+
bucketMetrics.bytesTotal !== undefined
271+
&& fileToBuild.value.CapacityInfo
272+
&& !fileToBuild.value.CapacityInfo.Used
273+
) {
274+
fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
275+
fileToBuild.value.CapacityInfo.Available =
276+
Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
277+
// TODO CLDSRV-633 when SUR backend supports realtime metrics: it will
278+
// report the real last cseq/date processed by SUR, instead of the current date,
279+
// ensuring no issue in a SOSAPI context. We should use this information.
280+
}
281+
282+
const builder = new xml2js.Builder({ headless: true });
283+
const xmlContent = buildHeadXML(builder.buildObject(fileToBuild.value));
284+
const dataBuffer = Buffer.from(xmlContent);
285+
return callback(null, { xmlContent, dataBuffer, modified, bucketData: data });
286+
};
287+
288+
if (!isSystemXML(request.objectKey)) {
289+
return fetchCapacityMetrics(bucketMd, request, log, name, (fetchErr, bucketMetrics) => {
290+
if (fetchErr) {
291+
return callback(errors.InternalError);
292+
}
293+
return finalize(bucketMetrics);
294+
});
295+
}
296+
return finalize({ date: new Date() });
297+
});
298+
}
299+
242300
module.exports = {
243301
_decodeURI,
244302
receiveData,
@@ -249,4 +307,5 @@ module.exports = {
249307
isSystemXML,
250308
getFileToBuild,
251309
fetchCapacityMetrics,
310+
buildVeeamFileData,
252311
};

tests/unit/routes/veeam-utils.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ describe('fetchCapacityMetrics', () => {
5757
});
5858
});
5959

60-
it('should call back with no error and no metrics on 404', done => {
60+
it('should call back with no error and a default date on 404', done => {
6161
const error404 = new Error('Not Found');
6262
error404.response = { status: 404 };
6363
utilizationStub.callsArgWith(4, error404);
6464

6565
fetchCapacityMetrics(bucketMd, request, log, 'testMethod', (err, metrics) => {
6666
assert.ifError(err);
67-
assert.strictEqual(metrics, undefined);
67+
assert(metrics && metrics.date instanceof Date, 'metrics should have a Date for date');
6868
assert(logWarnSpy.calledOnce);
6969
assert(logWarnSpy.getCall(0).args[0].includes('404'));
7070
assert.strictEqual(logWarnSpy.getCall(0).args[1].method, 'testMethod');
@@ -81,7 +81,7 @@ describe('fetchCapacityMetrics', () => {
8181

8282
fetchCapacityMetrics(bucketMd, request, log, 'testMethod', (err, metrics) => {
8383
assert.ifError(err);
84-
assert.strictEqual(metrics, undefined);
84+
assert(metrics && metrics.date instanceof Date, 'metrics should have a Date for date');
8585
assert(logWarnSpy.calledOnce);
8686
done();
8787
});

0 commit comments

Comments
 (0)