Skip to content

Commit 87be88a

Browse files
committed
Merge remote-tracking branch 'origin/w/9.3/bugfix/CLDSRV-878/use-sur-date' into w/9.4/bugfix/CLDSRV-878/use-sur-date
2 parents 41973a7 + 0a8e82c commit 87be88a

File tree

8 files changed

+727
-361
lines changed

8 files changed

+727
-361
lines changed

lib/routes/veeam/get.js

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

85
/**
96
* Returns system.xml or capacity.xml files for a given bucket.
@@ -14,75 +11,22 @@ const UtilizationService = require('../../../lib/utilization/instance');
1411
* @param {object} log - logger object
1512
* @returns {undefined} -
1613
*/
17-
function getVeeamFile(request, response, bucketMd, log) {
14+
async function getVeeamFile(request, response, bucketMd, log) {
1815
if (!bucketMd) {
1916
return responseXMLBody(errors.NoSuchBucket, null, response, log);
2017
}
18+
2119
if ('tagging' in request.query) {
22-
return respondWithData(request, response, log, bucketMd,
20+
return await respondWithData(request, response, log, bucketMd,
2321
buildHeadXML('<Tagging><TagSet></TagSet></Tagging>'));
2422
}
25-
return metadata.getBucket(request.bucketName, log, (err, data) => {
26-
if (err) {
27-
return responseXMLBody(errors.InternalError, null, response, log);
28-
}
29-
const finalizeRequest = bucketMetrics => {
30-
const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
31-
if (fileToBuild.error) {
32-
return responseXMLBody(fileToBuild.error, null, response, log);
33-
}
34-
35-
// Extract the last modified date, but do not include it when computing
36-
// the file's ETag (md5)
37-
const modified = fileToBuild.value.LastModified;
38-
delete fileToBuild.value.LastModified;
3923

40-
// The SOSAPI metrics are dynamically computed using the SUR backend.
41-
if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
42-
fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
43-
fileToBuild.value.CapacityInfo.Available =
44-
Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
45-
// TODO CLDSRV-633 when SUR backend supports realtime metrics: it will
46-
// report the real last cseq/date processed by SUR, instead of the current date,
47-
// ensuring no issue in a SOSAPI context. We should use this information.
48-
}
49-
50-
const builder = new xml2js.Builder({
51-
headless: true,
52-
});
53-
return respondWithData(request, response, log, data,
54-
buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
55-
};
56-
if (!isSystemXML(request.objectKey)) {
57-
const bucketKey = `${bucketMd._name}_${new Date(bucketMd._creationDate).getTime()}`;
58-
return UtilizationService.getUtilizationMetrics('bucket', bucketKey, null, {}, (err, bucketMetrics) => {
59-
if (err) {
60-
// Handle errors from UtilizationService/scubaclient
61-
// axios errors have status in err.response.status
62-
const statusCode = err.response?.status || err.statusCode || err.code;
63-
// Only handle 404 gracefully (no metrics available yet, e.g. post-install)
64-
// For 404, continue with static capacity data (Used=0 from bucket metadata)
65-
if (statusCode === 404) {
66-
log.warn('UtilizationService returned 404 when fetching capacity metrics', {
67-
method: 'getVeeamFile',
68-
bucket: request.bucketName,
69-
error: err.message || err.code,
70-
});
71-
return finalizeRequest();
72-
}
73-
log.error('error fetching capacity metrics from UtilizationService', {
74-
method: 'getVeeamFile',
75-
bucket: request.bucketName,
76-
error: err.message || err.code,
77-
statusCode,
78-
});
79-
return responseXMLBody(errors.InternalError, null, response, log);
80-
}
81-
return finalizeRequest(bucketMetrics);
82-
});
83-
}
84-
return finalizeRequest();
85-
});
24+
try {
25+
const result = await buildVeeamFileData(request, bucketMd, log);
26+
return await respondWithData(request, response, log, result.bucketData, result.xmlContent, result.modified);
27+
} catch (err) {
28+
return responseXMLBody(err, null, response, log);
29+
}
8630
}
8731

8832
module.exports = getVeeamFile;

lib/routes/veeam/head.js

Lines changed: 15 additions & 25 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 } = require('./utils');
2+
const { getResponseHeader, buildVeeamFileData } = require('./utils');
53
const { responseXMLBody, responseContentHeaders } = require('arsenal/build/lib/s3routes/routesUtils');
64

75
/**
@@ -13,31 +11,23 @@ const { responseXMLBody, responseContentHeaders } = require('arsenal/build/lib/s
1311
* @param {object} log - logger object
1412
* @returns {undefined} -
1513
*/
16-
function headVeeamFile(request, response, bucketMd, log) {
14+
async function headVeeamFile(request, response, bucketMd, log) {
1715
if (!bucketMd) {
1816
return responseXMLBody(errors.NoSuchBucket, null, response, log);
1917
}
20-
return metadata.getBucket(request.bucketName, log, (err, data) => {
21-
if (err) {
22-
return responseXMLBody(errors.InternalError, null, response, log);
23-
}
24-
const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
25-
if (fileToBuild.error) {
26-
return responseXMLBody(fileToBuild.error, null, response, log);
27-
}
28-
let modified = new Date().toISOString();
29-
// Extract the last modified date, but do not include it when computing
30-
// the file's ETag (md5)
31-
modified = fileToBuild.value.LastModified;
32-
delete fileToBuild.value.LastModified;
33-
// Recompute file content to generate appropriate content-md5 header
34-
const builder = new xml2js.Builder({
35-
headless: true,
36-
});
37-
const dataBuffer = Buffer.from(buildHeadXML(builder.buildObject(fileToBuild)));
38-
return responseContentHeaders(null, {}, getResponseHeader(request, data,
39-
dataBuffer, modified, log), response, log);
40-
});
18+
19+
try {
20+
const result = await buildVeeamFileData(request, bucketMd, log);
21+
return responseContentHeaders(
22+
null,
23+
{},
24+
getResponseHeader(request, result.bucketData, result.dataBuffer, result.modified, log),
25+
response,
26+
log,
27+
);
28+
} catch (err) {
29+
return responseXMLBody(err, null, response, log);
30+
}
4131
}
4232

4333
module.exports = headVeeamFile;

lib/routes/veeam/list.js

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
const url = require('url');
2-
const xml2js = require('xml2js');
32
const { errors, errorInstances } = require('arsenal');
43
const querystring = require('querystring');
5-
const metadata = require('../../metadata/wrapper');
64
const { responseXMLBody } = require('arsenal/build/lib/s3routes/routesUtils');
7-
const { respondWithData, getResponseHeader, buildHeadXML, validPath } = require('./utils');
5+
const { respondWithData, getResponseHeader, buildXML, validPath, fetchCapacityMetrics, getBucket } = require('./utils');
86
const { processVersions, processMasterVersions } = require('../../api/bucketGet');
97

10-
118
/**
129
* Utility function to build a standard response for the LIST route.
1310
* It adds the supported path by default as a static and default file.
@@ -83,51 +80,67 @@ function buildXMLResponse(request, arrayOfFiles, versioned = false) {
8380
* @param {object} log - logger object
8481
* @returns {undefined} -
8582
*/
86-
function listVeeamFiles(request, response, bucketMd, log) {
83+
async function listVeeamFiles(request, response, bucketMd, log) {
8784
if (!bucketMd) {
8885
return responseXMLBody(errors.NoSuchBucket, null, response, log);
8986
}
87+
9088
// Only accept list-type query parameter
9189
if (!('list-type' in request.query) && !('versions' in request.query)) {
9290
return responseXMLBody(errorInstances.InvalidRequest
9391
.customizeDescription('The Veeam folder does not support this action.'), null, response, log);
9492
}
95-
return metadata.getBucket(request.bucketName, log, (err, data) => {
96-
if (err) {
93+
94+
let data;
95+
try {
96+
data = await getBucket(request.bucketName, log);
97+
} catch {
98+
return responseXMLBody(errors.InternalError, null, response, log);
99+
}
100+
101+
let bucketMetrics;
102+
if (data._capabilities?.VeeamSOSApi?.CapacityInfo) {
103+
try {
104+
bucketMetrics = await fetchCapacityMetrics(bucketMd, request, log);
105+
} catch {
97106
return responseXMLBody(errors.InternalError, null, response, log);
98107
}
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`,
105-
});
106-
}
107-
if (data._capabilities?.VeeamSOSApi?.CapacityInfo) {
108-
fieldsToGenerate.push({
109-
...data._capabilities?.VeeamSOSApi?.CapacityInfo,
110-
name: `${validPath}capacity.xml`,
111-
});
112-
}
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-
});
108+
} else {
109+
bucketMetrics = { date: new Date() };
110+
}
111+
112+
const filesToBuild = [];
113+
const fieldsToGenerate = [];
114+
if (data._capabilities?.VeeamSOSApi?.SystemInfo) {
115+
fieldsToGenerate.push({
116+
...data._capabilities?.VeeamSOSApi?.SystemInfo,
117+
name: `${validPath}system.xml`,
118+
});
119+
}
120+
if (data._capabilities?.VeeamSOSApi?.CapacityInfo) {
121+
fieldsToGenerate.push({
122+
...data._capabilities?.VeeamSOSApi?.CapacityInfo,
123+
name: `${validPath}capacity.xml`,
124+
});
125+
}
126+
fieldsToGenerate.forEach(file => {
127+
const isCapacity = file.name.endsWith('capacity.xml');
128+
const lastModified = isCapacity
129+
? bucketMetrics.date
130+
: file.LastModified;
131+
// eslint-disable-next-line no-param-reassign
132+
delete file.LastModified;
133+
const dataBuffer = Buffer.from(buildXML(file));
134+
filesToBuild.push({
135+
...getResponseHeader(request, data,
136+
dataBuffer, lastModified, log),
137+
name: file.name,
126138
});
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));
130139
});
140+
141+
// When `versions` is present, listing should return a versioned list
142+
return await respondWithData(request, response, log, data,
143+
buildXMLResponse(request, filesToBuild, 'versions' in request.query));
131144
}
132145

133146
module.exports = listVeeamFiles;

lib/routes/veeam/put.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const async = require('async');
2+
const { callbackify } = require('util');
23
const { parseString } = require('xml2js');
34
const { receiveData, isSystemXML, getFileToBuild } = require('./utils');
45
const { s3routes, errors } = require('arsenal');
@@ -28,7 +29,7 @@ function putVeeamFile(request, response, bucketMd, log) {
2829
next => {
2930
// Extract the data from the request, keep it in memory
3031
writeContinue(request, response);
31-
return receiveData(request, log, next);
32+
return callbackify(receiveData)(request, log, next);
3233
},
3334
(value, next) => parseString(value, { explicitArray: false }, (err, parsed) => {
3435
// Convert the received XML to a JS object

0 commit comments

Comments
 (0)