Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions lib/routes/veeam/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,25 @@ function getVeeamFile(request, response, bucketMd, log) {
const bucketKey = `${bucketMd._name}_${new Date(bucketMd._creationDate).getTime()}`;
return UtilizationService.getUtilizationMetrics('bucket', bucketKey, null, {}, (err, bucketMetrics) => {
if (err) {
// Handle errors from UtilizationService/scubaclient
// axios errors have status in err.response.status
const statusCode = err.response?.status || err.statusCode || err.code;
// Only handle 404 gracefully (no metrics available yet, e.g. post-install)
// For 404, continue with static capacity data (Used=0 from bucket metadata)
if (statusCode === 404) {
log.warn('UtilizationService returned 404 when fetching capacity metrics', {
method: 'getVeeamFile',
bucket: request.bucketName,
error: err.message || err.code,
});
return finalizeRequest();
}
log.error('error fetching capacity metrics from UtilizationService', {
method: 'getVeeamFile',
bucket: request.bucketName,
error: err.message || err.code,
statusCode,
});
return responseXMLBody(errors.InternalError, null, response, log);
}
return finalizeRequest(bucketMetrics);
Expand Down
105 changes: 105 additions & 0 deletions tests/sur/routeVeeam.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,111 @@ function makeVeeamRequest(params, callback) {
return done();
}));
});

it('GET capacity.xml should return 200 when scubaclient returns 404 (post-install scenario)', done => {
// This test simulates the post-install scenario where scubaclient returns 404
// because no metrics are available yet. By not calling scuba.incrementBytesForBucket,
// the mock scuba server will return 404 for this bucket.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do something to make sure it's the case ? My point is that if someone change the mock server, this test will continue to pass 🤷

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already tracked by this ticket: https://scality.atlassian.net/browse/CLDSRV-528
A bit too early to add scuba yet, we need to drop count items first


async.waterfall([
next => makeVeeamRequest({
method: 'PUT',
bucket: TEST_BUCKET,
objectKey: '.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml',
headers: {
'content-length': testCapacity.length,
'content-md5': testCapacityMd5,
'x-scal-canonical-id': testArn,
},
authCredentials: veeamAuthCredentials,
requestBody: testCapacity,
}, (err, response) => {
if (err) {
return done(err);
}
assert.strictEqual(response.statusCode, 200);
return next();
}),
next => makeVeeamRequest({
method: 'GET',
bucket: TEST_BUCKET,
objectKey: '.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml',
headers: {
'x-scal-canonical-id': testArn,
},
authCredentials: veeamAuthCredentials,
}, (err, response) => {
if (err) {
return done(err);
}
// Critical assertion: for 404 from scubaclient (no metrics yet),
// should return 200 with static capacity data (Used=0)
assert.strictEqual(response.statusCode, 200,
'should return 200 when scubaclient returns 404 (no metrics available)');
// Should return capacity.xml with static data
assert(response.body.includes('<CapacityInfo>'),
'should return capacity.xml content');
assert(response.body.includes('<Used>0</Used>'),
'Used should be 0 from static bucket metadata');
return next();
}),
], err => {
assert.ifError(err);
return done();
});
});

it('GET system.xml should return 200 even when scubaclient is down', done => {
Comment thread
This conversation was marked as resolved.
// system.xml doesn't use scubaclient, so it should always work
// This test stops scuba to verify system.xml is independent of utilization metrics
async.waterfall([
next => makeVeeamRequest({
method: 'PUT',
bucket: TEST_BUCKET,
objectKey: '.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml',
headers: {
'content-length': testSystem.length,
'content-md5': testSystemMd5,
'x-scal-canonical-id': testArn,
},
authCredentials: veeamAuthCredentials,
requestBody: testSystem,
}, (err, response) => {
if (err) {
return done(err);
}
assert.strictEqual(response.statusCode, 200);
return next();
}),
next => {
// Stop scuba - system.xml should still work
scuba.stop();
return next();
},
next => makeVeeamRequest({
method: 'GET',
bucket: TEST_BUCKET,
objectKey: '.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml',
headers: {
'x-scal-canonical-id': testArn,
},
authCredentials: veeamAuthCredentials,
}, (err, response) => {
if (err) {
return done(err);
}
assert.strictEqual(response.statusCode, 200,
'system.xml should always return 200 even when scuba is down');
assert.strictEqual(response.body.replaceAll(' ', ''), testSystem.replaceAll(' ', ''));
return next();
}),
], err => {
// Restart scuba for subsequent tests
scuba.start();
assert.ifError(err);
return done();
});
});
});

describe('veeam DELETE routes:', () => {
Expand Down
Loading
Loading