Repository: scality/cloudserver
Pull request #5777: Improvement/cldsrv 631
Author: login: ghost
Created: 2025-04-01T12:57:47Z | Merged: 2025-04-04T14:54:38Z

Use SUR backend instead of bucket MD to get the utilization metrics. This makes the Veeam SOSAPI cronjob obsolete: it will update the metrics, but this won't affect the API anymore. It means we can safely remove it later.


Diff:
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 1786898fdf..de51e3b08f 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -449,7 +449,7 @@ jobs:
         source: /tmp/artifacts
       if: always()
 
-  quota-tests:
+  sur-tests:
     runs-on: ubuntu-24.04
     needs: build
     strategy:
@@ -478,11 +478,11 @@ jobs:
     - name: Setup CI services
       run: docker compose --profile mongo up -d
       working-directory: .github/docker
-    - name: Run quota tests
+    - name: Run SUR-related tests
       run: |-
         set -ex -o pipefail;
         bash wait_for_local_port.bash 8000 40
-        yarn run test_quota | tee /tmp/artifacts/${{ github.job }}/tests.log
+        yarn run test_sur | tee /tmp/artifacts/${{ github.job }}/tests.log
     - name: Cleanup and upload coverage
       uses: ./.github/actions/cleanup-and-coverage
       with:
diff --git a/lib/api/apiUtils/quotas/quotaUtils.js b/lib/api/apiUtils/quotas/quotaUtils.js
index eb38a7befa..6c967bec94 100644
--- a/lib/api/apiUtils/quotas/quotaUtils.js
+++ b/lib/api/apiUtils/quotas/quotaUtils.js
@@ -7,7 +7,7 @@ const {
     actionWithDataDeletion,
 } = require('arsenal').policies;
 const { config } = require('../../../Config');
-const QuotaService = require('../../../quotas/quotas');
+const QuotaService = require('../../../utilization/instance');
 
 /**
  * Process the bytes to write based on the request and object metadata
diff --git a/lib/quotas/quotas.js b/lib/quotas/quotas.js
deleted file mode 100644
index f072064142..0000000000
--- a/lib/quotas/quotas.js
+++ /dev/null
@@ -1,17 +0,0 @@
-const { config } = require('../Config');
-const { ScubaClientImpl } = require('./scuba/wrapper');
-
-let instance = null;
-
-switch (config.backends.quota) {
-    case 'scuba':
-        instance = new ScubaClientImpl(config);
-    break;
-    default:
-        instance = {
-            enabled: false,
-        };
-    break;
-}
-
-module.exports = instance;
diff --git a/lib/routes/veeam/get.js b/lib/routes/veeam/get.js
index d9eb242cb0..c4a1929132 100644
--- a/lib/routes/veeam/get.js
+++ b/lib/routes/veeam/get.js
@@ -1,8 +1,9 @@
 const xml2js = require('xml2js');
 const { errors } = require('arsenal');
 const metadata = require('../../metadata/wrapper');
-const { respondWithData, buildHeadXML, getFileToBuild } = require('./utils');
+const { respondWithData, buildHeadXML, getFileToBuild, isSystemXML } = require('./utils');
 const { responseXMLBody } = require('arsenal/build/lib/s3routes/routesUtils');
+const UtilizationService = require('../../../lib/utilization/instance');
 
 /**
  * Returns system.xml or capacity.xml files for a given bucket.
@@ -25,21 +26,43 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+
+            // Extract the last modified date, but do not include it when computing
+            // the file's ETag (md5)
+            const modified = fileToBuild.value.LastModified;
+            delete fileToBuild.value.LastModified;
 
-        const builder = new xml2js.Builder({
-            headless: true,
-        });
-        return respondWithData(request, response, log, data,
-            buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
+            // The SOSAPI metrics are dynamically computed using the SUR backend.
+            if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
+                fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
+                fileToBuild.value.CapacityInfo.Available =
+                    Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
+                // TODO CLDSRV-633 when SUR backend supports realtime metrics: it will
+                // report the real last cseq/date processed by SUR, instead of the current date,
+                // ensuring no issue in a SOSAPI context. We should use this information.
+            }
+
+            const builder = new xml2js.Builder({
+                headless: true,
+            });
+            return respondWithData(request, response, log, data,
+                buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
+        };
+        if (!isSystemXML(request.objectKey)) {
+            const bucketKey = `${bucketMd._name}_${new Date(bucketMd._creationDate).getTime()}`;
+            return UtilizationService.getUtilizationMetrics('bucket', bucketKey, null, {}, (err, bucketMetrics) => {
+                if (err) {
+                    return responseXMLBody(errors.InternalError, null, response, log);
+                }
+                return finalizeRequest(bucketMetrics);
+            });
+        }
+        return finalizeRequest();
     });
 }
 
diff --git a/lib/server.js b/lib/server.js
index af63e86058..96642443bd 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -25,7 +25,7 @@ const {
 } = require('./management/agentClient');
 
 const HttpAgent = require('agentkeepalive');
-const QuotaService = require('./quotas/quotas');
+const QuotaService = require('./utilization/instance');
 const routes = arsenal.s3routes.routes;
 const { parseLC, MultipleBackendGateway } = arsenal.storage.data;
 const websiteEndpoints = _config.websiteEndpoints;
diff --git a/lib/utilization/instance.js b/lib/utilization/instance.js
new file mode 100644
index 0000000000..9ed6cd2391
--- /dev/null
+++ b/lib/utilization/instance.js
@@ -0,0 +1,6 @@
+const { config } = require('../Config');
+const { ScubaClientImpl } = require('./scuba/wrapper');
+
+const instance = new ScubaClientImpl(config);
+
+module.exports = instance;
diff --git a/lib/quotas/scuba/wrapper.js b/lib/utilization/scuba/wrapper.js
similarity index 100%
rename from lib/quotas/scuba/wrapper.js
rename to lib/utilization/scuba/wrapper.js
diff --git a/package.json b/package.json
index 61940a41cb..3c974340a5 100644
--- a/package.json
+++ b/package.json
@@ -119,7 +119,7 @@
     "test_versionid_base62": "S3_VERSION_ID_ENCODING_TYPE=base62 CI=true S3BACKEND=mem mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json --recursive tests/unit/api --exit",
     "test_legacy_location": "CI=true S3QUOTA=scuba S3_LOCATION_FILE=tests/locationConfig/locationConfigLegacy.json S3BACKEND=mem mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json --recursive tests/unit --exit",
     "test_utapi_v2": "mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json --recursive tests/utapi --exit",
-    "test_quota": "mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json --recursive tests/quota --exit",
+    "test_sur": "mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json --recursive tests/sur --exit",
     "multiple_backend_test": "CI=true S3BACKEND=mem S3METADATA=mem S3DATA=multiple mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json -t 20000 --recursive tests/multipleBackend --exit",
     "cover": "nyc --clean --silent yarn run",
     "postcover": "nyc report --report-dir ./coverage/test --reporter=lcov"
diff --git a/tests/multipleBackend/routes/routeVeeam.js b/tests/multipleBackend/routes/routeVeeam.js
deleted file mode 100644
index 55fedb30a3..0000000000
--- a/tests/multipleBackend/routes/routeVeeam.js
+++ /dev/null
@@ -1,565 +0,0 @@
-const assert = require('assert');
-const crypto = require('crypto');
-const async = require('async');
-
-const { makeRequest } = require('../../functional/raw-node/utils/makeRequest');
-const BucketUtility =
-    require('../../functional/aws-node-sdk/lib/utility/bucket-util');
-
-const ipAddress = process.env.IP ? process.env.IP : '127.0.0.1';
-
-const veeamAuthCredentials = {
-    accessKey: 'accessKey1',
-    secretKey: 'verySecretKey1',
-};
-
-const badVeeamAuthCredentials = {
-    accessKey: 'accesKey1',
-    secretKey: 'veryecretKey1',
-};
-
-const TEST_BUCKET = 'veeambucket';
-const testArn = 'aws::iam:123456789012:user/bart';
-
-const testCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
-<CapacityInfo>
-    <Capacity>1099511627776</Capacity>
-    <Available>0</Available>
-    <Used>0</Used>
-</CapacityInfo>\n`;
-
-const testCapacityMd5 = crypto.createHash('md5')
-    .update(testCapacity, 'utf-8')
-    .digest('hex');
-
-const invalidTestCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
-<CapacityInfo>
-    <Capacity>1099511627776</Capacity>
-    <Available>-5</Available>
-    <Used>0</Used>
-</CapacityInfo>\n`;
-
-const invalidTestCapacityMd5 = crypto.createHash('md5')
-    .update(invalidTestCapacity, 'utf-8')
-    .digest('hex');
-
-const testSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
-    <SystemInfo>
-       <ProtocolVersion>"1.0"</ProtocolVersion>
-       <ModelName>"ARTESCA"</ModelName>
-       <ProtocolCapabilities>
-          <CapacityInfo>true</CapacityInfo>
-          <UploadSessions>false</UploadSessions>
-          <IAMSTS>true</IAMSTS>
-       </ProtocolCapabilities>
-       <APIEndpoints>
-            <IAMEndpoint>a</IAMEndpoint>
-            <STSEndpoint>a</STSEndpoint>
-       </APIEndpoints>
-       <SystemRecommendations>
-           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
-           <S3MultiObjectDeleteLimit>1</S3MultiObjectDeleteLimit>
-           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
-           <KbBlockSize>256</KbBlockSize>
-       </SystemRecommendations>
-    </SystemInfo>\n`;
-
-const testSystemMd5 = crypto.createHash('md5')
-    .update(testSystem, 'utf-8')
-    .digest('hex');
-
-const invalidTestSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
-    <SystemInfo>
-       <ProtocolVersion>"1.0"</ProtocolVersion>
-       <ModelName>"ARTESCA"</ModelName>
-       <ProtocolCapabilities>
-          <CapacityInfo>true</CapacityInfo>
-          <UploadSessions>false</UploadSessions>
-          <IAMSTS>true</IAMSTS>
-       </ProtocolCapabilities>
-       <APIEndpoints>
-            <IAMEndpoint>a</IAMEndpoint>
-            <STSEndpoint>a</STSEndpoint>
-       </APIEndpoints>
-       <SystemRecommendations>
-           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
-           <S3MultiObjectDeleteLimit>-1</S3MultiObjectDeleteLimit>
-           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
-           <KbBlockSize>256</KbBlockSize>
-       </SystemRecommendations>
-    </SystemInfo>\n`;
-
-const invalidTestSystemMd5 = crypto.createHash('md5')
-    .update(testSystem, 'utf-8')
-    .digest('hex');
-
-let bucketUtil;
-let s3;
-
-/** makeVeeamRequest - utility function to generate a request going
- * through veeam route
- * @param {object} params - params for making request
- * @param {string} params.method - request method
- * @param {string} params.bucket - bucket name
- * @param {string} params.objectKey - object key
- * @param {object} [params.headers] - headers and their string values
- * @param {object} [params.authCredentials] - authentication credentials
- * @param {object} params.authCredentials.accessKey - access key
- * @param {object} params.authCredentials.secretKey - secret key
- * @param {string} [params.requestBody] - request body contents
- * @param {object} [params.queryObj] - request query parameters
- * @param {function} callback - with error and response parameters
- * @return {undefined} - and call callback
- */
-function makeVeeamRequest(params, callback) {
-    const { method, headers, bucket, objectKey,
-        authCredentials, requestBody, queryObj } = params;
-    const options = {
-        authCredentials,
-        hostname: ipAddress,
-        port: 8000,
-        method,
-        headers,
-        path: `/_/veeam/${bucket}/${objectKey}`,
-        urlForSignature: `/${bucket}/${objectKey}`,
-        requestBody,
-        jsonResponse: false,
-        queryObj,
-    };
-    makeRequest(options, callback);
-}
-
-describe('veeam PUT routes:', () => {
-    before(done => {
-        bucketUtil = new BucketUtility(
-            'default', { signatureVersion: 'v4' });
-        s3 = bucketUtil.s3;
-        s3.createBucket({ Bucket: TEST_BUCKET }).promise()
-            .then(() => done())
-            .catch(err => {
-                process.stdout.write(`Error creating bucket: ${err}\n`);
-                throw err;
-            });
-    });
-    after(done => {
-        bucketUtil.empty(TEST_BUCKET)
-            .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
-            .then(() => done());
-    });
-
-    [
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
-    ].forEach(key => {
-        it(`PUT ${key[0]}`, done => makeVeeamRequest({
-                method: 'PUT',
-                bucket: TEST_BUCKET,
-                objectKey: key[0],
-                headers: {
-                    'content-length': key[1].length,
-                    'content-md5': key[2],
-                    'x-scal-canonical-id': testArn,
-                },
-                authCredentials: veeamAuthCredentials,
-                requestBody: key[1],
-            }, (err, response) => {
-                if (err) {
-                    // Return the error, if any
-                    return done(err);
-                }
-                assert.strictEqual(response.statusCode, 200);
-                return done();
-            }));
-    });
-
-    [
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', invalidTestSystem, invalidTestSystemMd5],
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', invalidTestCapacity, invalidTestCapacityMd5],
-    ].forEach(key => {
-        it(`PUT ${key[0]} should fail for invalid XML`, done => makeVeeamRequest({
-                method: 'PUT',
-                bucket: TEST_BUCKET,
-                objectKey: key[0],
-                headers: {
-                    'content-length': key[1].length + 3,
-                    'content-md5': key[2],
-                    'x-scal-canonical-id': testArn,
-                },
-                authCredentials: veeamAuthCredentials,
-                requestBody: `${key[1]}gff`,
-            }, err => {
-                assert.strictEqual(err.code, 'MalformedXML');
-                return done();
-            }));
-    });
-
-    [
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
-    ].forEach(key => {
-        it(`PUT ${key[0]} should fail if invalid credentials are sent`, done => makeVeeamRequest({
-                method: 'PUT',
-                bucket: TEST_BUCKET,
-                objectKey: key[0],
-                headers: {
-                    'content-length': key[1].length + 3,
-                    'content-md5': key[2],
-                    'x-scal-canonical-id': testArn,
-                },
-                authCredentials: badVeeamAuthCredentials,
-                requestBody: `${key[1]}gff`,
-            }, err => {
-                assert.strictEqual(err.code, 'InvalidAccessKeyId');
-                return done();
-            }));
-    });
-});
-
-describe('veeam GET routes:', () => {
-    beforeEach(done => {
-        bucketUtil = new BucketUtility(
-            'default', { signatureVersion: 'v4' });
-        s3 = bucketUtil.s3;
-        s3.createBucket({ Bucket: TEST_BUCKET }).promise()
-            .then(() => done())
-            .catch(err => {
-                process.stdout.write(`Error creating bucket: ${err}\n`);
-                throw err;
-            });
-    });
-    afterEach(done => {
-        bucketUtil.empty(TEST_BUCKET)
-            .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
-            .then(() => done());
-    });
-
-    [
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
-    ].forEach(key => {
-        it(`GET ${key[0]} should return the expected XML file`, done => {
-            async.waterfall([
-                next => makeVeeamRequest({
-                    method: 'PUT',
-                    bucket: TEST_BUCKET,
-                    objectKey: key[0],
-                    headers: {
-                        'content-length': key[1].length,
-                        'content-md5': key[2],
-                        'x-scal-canonical-id': testArn,
-                    },
-                    authCredentials: veeamAuthCredentials,
-                    requestBody: key[1],
-                }, (err, response) => {
-                    if (err) {
-                        return done(err);
-                    }
-                    assert.strictEqual(response.statusCode, 200);
-                    return next();
-                }),
-                next => makeVeeamRequest({
-                    method: 'GET',
-                    bucket: TEST_BUCKET,
-                    objectKey: key[0],
-                    headers: {
-                        'x-scal-canonical-id': testArn,
-                    },
-                    authCredentials: veeamAuthCredentials,
-                }, (err, response) => {
-                    if (err) {
-                        return done(err);
-                    }
-                    assert.strictEqual(response.statusCode, 200);
-                    assert.strictEqual(response.body.replaceAll(' ', ''), key[1].replaceAll(' ', ''));
-                    return next();
-                }),
-            ], err => {
-                assert.ifError(err);
-                return done();
-            });
-        });
-    });
-
-    [
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
-    ].forEach(key => {
-        it(`GET ${key[0]} should return the expected XML file for cors requests`, done => {
-            async.waterfall([
-                next => makeVeeamRequest({
-                    method: 'PUT',
-                    bucket: TEST_BUCKET,
-                    objectKey: key[0],
-                    headers: {
-                        'origin': 'http://localhost:8000',
-                        'content-length': key[1].length,
-                        'content-md5': key[2],
-                        'x-scal-canonical-id': testArn,
-                    },
-                    authCredentials: veeamAuthCredentials,
-                    requestBody: key[1],
-                }, (err, response) => {
-                    if (err) {
-                        return done(err);
-                    }
-                    assert.strictEqual(response.statusCode, 200);
-                    return next();
-                }),
-                next => makeVeeamRequest({
-                    method: 'GET',
-                    bucket: TEST_BUCKET,
-                    objectKey: key[0],
-                    headers: {
-                        'origin': 'http://localhost:8000',
-                        'x-scal-canonical-id': testArn,
-                    },
-                    authCredentials: veeamAuthCredentials,
-                }, (err, response) => {
-                    if (err) {
-                        return done(err);
-                    }
-                    assert.strictEqual(response.statusCode, 200);
-                    assert.strictEqual(response.body.replaceAll(' ', ''), key[1].replaceAll(' ', ''));
-                    return next();
-                }),
-            ], err => {
-                assert.ifError(err);
-                return done();
-            });
-        });
-    });
-
-
-    [
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
-    ].forEach(key => {
-        it(`GET ${key[0]} should fail if no data in bucket metadata`, done => makeVeeamRequest({
-                method: 'GET',
-                bucket: TEST_BUCKET,
-                objectKey: key[0],
-                headers: {
-                    'x-scal-canonical-id': testArn,
-                },
-                authCredentials: veeamAuthCredentials,
-            }, err => {
-                assert.strictEqual(err.code, 'NoSuchKey');
-                return done();
-            }));
-    });
-});
-
-describe('veeam DELETE routes:', () => {
-    beforeEach(done => {
-        bucketUtil = new BucketUtility(
-            'default', { signatureVersion: 'v4' });
-        s3 = bucketUtil.s3;
-        s3.createBucket({ Bucket: TEST_BUCKET }).promise()
-            .then(() => done())
-            .catch(err => {
-                process.stdout.write(`Error creating bucket: ${err}\n`);
-                throw err;
-            });
-    });
-    afterEach(done => {
-        bucketUtil.empty(TEST_BUCKET)
-            .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
-            .then(() => done());
-    });
-
-    [
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
-    ].forEach(key => {
-        it(`DELETE ${key[0]} should delete the XML file`, done => {
-            async.waterfall([
-                next => makeVeeamRequest({
-                    method: 'PUT',
-                    bucket: TEST_BUCKET,
-                    objectKey: key[0],
-                    headers: {
-                        'content-length': key[1].length,
-                        'content-md5': key[2],
-                        'x-scal-canonical-id': testArn,
-                    },
-                    authCredentials: veeamAuthCredentials,
-                    requestBody: key[1],
-                }, (err, response) => {
-                    if (err) {
-                        return done(err);
-                    }
-                    assert.strictEqual(response.statusCode, 200);
-                    return next();
-                }),
-                next => makeVeeamRequest({
-                    method: 'GET',
-                    bucket: TEST_BUCKET,
-                    objectKey: key[0],
-                    headers: {
-                        'x-scal-canonical-id': testArn,
-                    },
-                    authCredentials: veeamAuthCredentials,
-                }, (err, response) => {
-                    if (err) {
-                        return done(err);
-                    }
-                    assert.strictEqual(response.statusCode, 200);
-                    assert.strictEqual(response.body.replaceAll(' ', ''), key[1].replaceAll(' ', ''));
-                    return next();
-                }),
-                next => makeVeeamRequest({
-                    method: 'DELETE',
-                    bucket: TEST_BUCKET,
-                    objectKey: key[0],
-                    headers: {
-                        'x-scal-canonical-id': testArn,
-                    },
-                    authCredentials: veeamAuthCredentials,
-                }, (err, response) => {
-                    if (err) {
-                        return done(err);
-                    }
-                    assert.strictEqual(response.statusCode, 204);
-                    return next();
-                }),
-                next => makeVeeamRequest({
-                    method: 'GET',
-                    bucket: TEST_BUCKET,
-                    objectKey: key[0],
-                    headers: {
-                        'x-scal-canonical-id': testArn,
-                    },
-                    authCredentials: veeamAuthCredentials,
-                }, err => {
-                    assert.strictEqual(err.code, 'NoSuchKey');
-                    return next();
-                }),
-            ], err => {
-                assert.ifError(err);
-                return done();
-            });
-        });
-    });
-
-    [
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
-    ].forEach(key => {
-        it(`DELETE ${key[0]} should fail if XML doesn't exist yet`, done => makeVeeamRequest({
-                method: 'DELETE',
-                bucket: TEST_BUCKET,
-                objectKey: key[0],
-                headers: {
-                    'x-scal-canonical-id': testArn,
-                },
-                authCredentials: veeamAuthCredentials,
-            }, err => {
-                assert.strictEqual(err.code, 'NoSuchKey');
-                return done();
-            }));
-    });
-});
-
-describe('veeam HEAD routes:', () => {
-    beforeEach(done => {
-        bucketUtil = new BucketUtility(
-            'default', { signatureVersion: 'v4' });
-        s3 = bucketUtil.s3;
-        s3.createBucket({ Bucket: TEST_BUCKET }).promise()
-            .then(() => done())
-            .catch(err => {
-                process.stdout.write(`Error creating bucket: ${err}\n`);
-                throw err;
-            });
-    });
-    afterEach(done => {
-        bucketUtil.empty(TEST_BUCKET)
-            .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
-            .then(() => done());
-    });
-
-    [
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
-    ].forEach(key => {
-        it(`HEAD ${key[0]} should return the existing XML file metadata`, done => {
-            async.waterfall([
-                next => makeVeeamRequest({
-                    method: 'PUT',
-                    bucket: TEST_BUCKET,
-                    objectKey: key[0],
-                    headers: {
-                        'content-length': key[1].length,
-                        'content-md5': key[2],
-                        'x-scal-canonical-id': testArn,
-                    },
-                    authCredentials: veeamAuthCredentials,
-                    requestBody: key[1],
-                }, (err, response) => {
-                    if (err) {
-                        return done(err);
-                    }
-                    assert.strictEqual(response.statusCode, 200);
-                    return next();
-                }),
-                next => makeVeeamRequest({
-                    method: 'HEAD',
-                    bucket: TEST_BUCKET,
-                    objectKey: key[0],
-                    headers: {
-                        'x-scal-canonical-id': testArn,
-                    },
-                    authCredentials: veeamAuthCredentials,
-                }, (err, response) => {
-                    if (err) {
-                        return done(err);
-                    }
-                    assert.strictEqual(response.statusCode, 200);
-                    return next();
-                }),
-            ], err => {
-                assert.ifError(err);
-                return done();
-            });
-        });
-    });
-
-    [
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
-        ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
-    ].forEach(key => {
-        it(`HEAD ${key[0]} should fail if no data in bucket metadata`, done => makeVeeamRequest({
-                method: 'HEAD',
-                bucket: TEST_BUCKET,
-                objectKey: key[0],
-                headers: {
-                    'x-scal-canonical-id': testArn,
-                },
-                authCredentials: veeamAuthCredentials,
-            }, (err, res) => {
-                assert.strictEqual(res.statusCode, 404);
-                return done();
-            }));
-    });
-});
-
-
-// TODO {test_debt} handle query params tests with signature (happy path)
-describe.skip('veeam LIST routes:', () => {
-    beforeEach(done => {
-        bucketUtil = new BucketUtility(
-            'default', { signatureVersion: 'v4' });
-        s3 = bucketUtil.s3;
-        s3.createBucket({ Bucket: TEST_BUCKET }).promise()
-            .then(() => done())
-            .catch(err => {
-                process.stdout.write(`Error creating bucket: ${err}\n`);
-                throw err;
-            });
-    });
-    afterEach(done => {
-        bucketUtil.empty(TEST_BUCKET)
-            .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
-            .then(() => done());
-    });
-});
diff --git a/tests/quota/awsNodeSdk.js b/tests/sur/quota.js
similarity index 100%
rename from tests/quota/awsNodeSdk.js
rename to tests/sur/quota.js
diff --git a/tests/sur/routeVeeam.js b/tests/sur/routeVeeam.js
new file mode 100644
index 0000000000..856f025582
--- /dev/null
+++ b/tests/sur/routeVeeam.js
@@ -0,0 +1,582 @@
+const assert = require('assert');
+const crypto = require('crypto');
+const async = require('async');
+const { Scuba: MockScuba } = require('../utilities/mock/Scuba');
+
+const { makeRequest } = require('../functional/raw-node/utils/makeRequest');
+const BucketUtility =
+    require('../functional/aws-node-sdk/lib/utility/bucket-util');
+
+const ipAddress = process.env.IP ? process.env.IP : '127.0.0.1';
+
+const veeamAuthCredentials = {
+    accessKey: 'accessKey1',
+    secretKey: 'verySecretKey1',
+};
+
+const badVeeamAuthCredentials = {
+    accessKey: 'accesKey1',
+    secretKey: 'veryecretKey1',
+};
+
+const TEST_BUCKET = 'veeambucket';
+const testArn = 'aws::iam:123456789012:user/bart';
+
+const testCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>1099511627776</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const testCapacityMd5 = crypto.createHash('md5')
+    .update(testCapacity, 'utf-8')
+    .digest('hex');
+
+const invalidTestCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>-5</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const invalidTestCapacityMd5 = crypto.createHash('md5')
+    .update(invalidTestCapacity, 'utf-8')
+    .digest('hex');
+
+const testSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const testSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+const invalidTestSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>-1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const invalidTestSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+let bucketUtil;
+let s3;
+
+/** makeVeeamRequest - utility function to generate a request going
+ * through veeam route
+ * @param {object} params - params for making request
+ * @param {string} params.method - request method
+ * @param {string} params.bucket - bucket name
+ * @param {string} params.objectKey - object key
+ * @param {object} [params.headers] - headers and their string values
+ * @param {object} [params.authCredentials] - authentication credentials
+ * @param {object} params.authCredentials.accessKey - access key
+ * @param {object} params.authCredentials.secretKey - secret key
+ * @param {string} [params.requestBody] - request body contents
+ * @param {object} [params.queryObj] - request query parameters
+ * @param {function} callback - with error and response parameters
+ * @return {undefined} - and call callback
+ */
+function makeVeeamRequest(params, callback) {
+    const { method, headers, bucket, objectKey,
+        authCredentials, requestBody, queryObj } = params;
+    const options = {
+        authCredentials,
+        hostname: ipAddress,
+        port: 8000,
+        method,
+        headers,
+        path: `/_/veeam/${bucket}/${objectKey}`,
+        urlForSignature: `/${bucket}/${objectKey}`,
+        requestBody,
+        jsonResponse: false,
+        queryObj,
+    };
+    makeRequest(options, callback);
+}
+
+(process.env.S3METADATA === 'mongodb' ? describe : describe.skip)('Veeam routes:', () => {
+    let scuba;
+
+    beforeEach(done => {
+        scuba = new MockScuba();
+        scuba.start();
+        setTimeout(done, 500);
+    });
+
+    afterEach(() => {
+        scuba.stop();
+    });
+
+    describe('veeam PUT routes:', () => {
+        before(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+                .then(() => done())
+                .catch(err => {
+                    process.stdout.write(`Error creating bucket: ${err}\n`);
+                    throw err;
+                });
+        });
+        after(done => {
+            bucketUtil.empty(TEST_BUCKET)
+                .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+                .then(() => done());
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`PUT ${key[0]}`, done => makeVeeamRequest({
+                method: 'PUT',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'content-length': key[1].length,
+                    'content-md5': key[2],
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+                requestBody: key[1],
+            }, (err, response) => {
+                if (err) {
+                    // Return the error, if any
+                    return done(err);
+                }
+                assert.strictEqual(response.statusCode, 200);
+                return done();
+            }));
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', invalidTestSystem, invalidTestSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', invalidTestCapacity, invalidTestCapacityMd5],
+        ].forEach(key => {
+            it(`PUT ${key[0]} should fail for invalid XML`, done => makeVeeamRequest({
+                method: 'PUT',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'content-length': key[1].length + 3,
+                    'content-md5': key[2],
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+                requestBody: `${key[1]}gff`,
+            }, err => {
+                assert.strictEqual(err.code, 'MalformedXML');
+                return done();
+            }));
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`PUT ${key[0]} should fail if invalid credentials are sent`, done => makeVeeamRequest({
+                method: 'PUT',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'content-length': key[1].length + 3,
+                    'content-md5': key[2],
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: badVeeamAuthCredentials,
+                requestBody: `${key[1]}gff`,
+            }, err => {
+                assert.strictEqual(err.code, 'InvalidAccessKeyId');
+                return done();
+            }));
+        });
+    });
+
+
+    describe('veeam GET routes:', () => {
+        beforeEach(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+                .then(() => done())
+                .catch(err => {
+                    process.stdout.write(`Error creating bucket: ${err}\n`);
+                    throw err;
+                });
+        });
+        afterEach(done => {
+            bucketUtil.empty(TEST_BUCKET)
+                .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+                .then(() => done());
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`GET ${key[0]} should return the expected XML file`, done => {
+                scuba.incrementBytesForBucket(TEST_BUCKET, 0);
+                async.waterfall([
+                    next => makeVeeamRequest({
+                        method: 'PUT',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'content-length': key[1].length,
+                            'content-md5': key[2],
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                        requestBody: key[1],
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        return next();
+                    }),
+                    next => makeVeeamRequest({
+                        method: 'GET',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        assert.strictEqual(response.body.replaceAll(' ', ''), key[1].replaceAll(' ', ''));
+                        return next();
+                    }),
+                ], err => {
+                    assert.ifError(err);
+                    return done();
+                });
+            });
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`GET ${key[0]} should return the expected XML file for cors requests`, done => {
+                async.waterfall([
+                    next => makeVeeamRequest({
+                        method: 'PUT',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'origin': 'http://localhost:8000',
+                            'content-length': key[1].length,
+                            'content-md5': key[2],
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                        requestBody: key[1],
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        return next();
+                    }),
+                    next => makeVeeamRequest({
+                        method: 'GET',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'origin': 'http://localhost:8000',
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        assert.strictEqual(response.body.replaceAll(' ', ''), key[1].replaceAll(' ', ''));
+                        return next();
+                    }),
+                ], err => {
+                    assert.ifError(err);
+                    return done();
+                });
+            });
+        });
+
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`GET ${key[0]} should fail if no data in bucket metadata`, done => makeVeeamRequest({
+                method: 'GET',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+            }, err => {
+                assert.strictEqual(err.code, 'NoSuchKey');
+                return done();
+            }));
+        });
+    });
+
+    describe('veeam DELETE routes:', () => {
+        beforeEach(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+                .then(() => done())
+                .catch(err => {
+                    process.stdout.write(`Error creating bucket: ${err}\n`);
+                    throw err;
+                });
+        });
+        afterEach(done => {
+            bucketUtil.empty(TEST_BUCKET)
+                .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+                .then(() => done());
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`DELETE ${key[0]} should delete the XML file`, done => {
+                async.waterfall([
+                    next => makeVeeamRequest({
+                        method: 'PUT',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'content-length': key[1].length,
+                            'content-md5': key[2],
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                        requestBody: key[1],
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        return next();
+                    }),
+                    next => makeVeeamRequest({
+                        method: 'GET',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        assert.strictEqual(response.body.replaceAll(' ', ''), key[1].replaceAll(' ', ''));
+                        return next();
+                    }),
+                    next => makeVeeamRequest({
+                        method: 'DELETE',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 204);
+                        return next();
+                    }),
+                    next => makeVeeamRequest({
+                        method: 'GET',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                    }, err => {
+                        assert.strictEqual(err.code, 'NoSuchKey');
+                        return next();
+                    }),
+                ], err => {
+                    assert.ifError(err);
+                    return done();
+                });
+            });
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`DELETE ${key[0]} should fail if XML doesn't exist yet`, done => makeVeeamRequest({
+                method: 'DELETE',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+            }, err => {
+                assert.strictEqual(err.code, 'NoSuchKey');
+                return done();
+            }));
+        });
+    });
+
+    describe('veeam HEAD routes:', () => {
+        beforeEach(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+                .then(() => done())
+                .catch(err => {
+                    process.stdout.write(`Error creating bucket: ${err}\n`);
+                    throw err;
+                });
+        });
+        afterEach(done => {
+            bucketUtil.empty(TEST_BUCKET)
+                .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+                .then(() => done());
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`HEAD ${key[0]} should return the existing XML file metadata`, done => {
+                async.waterfall([
+                    next => makeVeeamRequest({
+                        method: 'PUT',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'content-length': key[1].length,
+                            'content-md5': key[2],
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                        requestBody: key[1],
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        return next();
+                    }),
+                    next => makeVeeamRequest({
+                        method: 'HEAD',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        return next();
+                    }),
+                ], err => {
+                    assert.ifError(err);
+                    return done();
+                });
+            });
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`HEAD ${key[0]} should fail if no data in bucket metadata`, done => makeVeeamRequest({
+                method: 'HEAD',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+            }, (err, res) => {
+                assert.strictEqual(res.statusCode, 404);
+                return done();
+            }));
+        });
+    });
+});
+
+
+// TODO {test_debt} handle query params tests with signature (happy path)
+describe.skip('veeam LIST routes:', () => {
+    beforeEach(done => {
+        bucketUtil = new BucketUtility(
+            'default', { signatureVersion: 'v4' });
+        s3 = bucketUtil.s3;
+        s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+            .then(() => done())
+            .catch(err => {
+                process.stdout.write(`Error creating bucket: ${err}\n`);
+                throw err;
+            });
+    });
+    afterEach(done => {
+        bucketUtil.empty(TEST_BUCKET)
+            .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+            .then(() => done());
+    });
+});
diff --git a/tests/unit/api/apiUtils/quotas/quotaUtils.js b/tests/unit/api/apiUtils/quotas/quotaUtils.js
index 6c6f7d925e..49cbea8eab 100644
--- a/tests/unit/api/apiUtils/quotas/quotaUtils.js
+++ b/tests/unit/api/apiUtils/quotas/quotaUtils.js
@@ -6,7 +6,7 @@ const {
     processBytesToWrite,
     isMetricStale,
 } = require('../../../../../lib/api/apiUtils/quotas/quotaUtils');
-const QuotaService = require('../../../../../lib/quotas/quotas');
+const QuotaService = require('../../../../../lib/utilization/instance');
 const BucketInfo = require('arsenal').models.BucketInfo;
 
 const mockLog = {
diff --git a/tests/unit/quotas/scuba/wrapper.js b/tests/unit/quotas/scuba/wrapper.js
index 81a9a83a8f..57065ff96a 100644
--- a/tests/unit/quotas/scuba/wrapper.js
+++ b/tests/unit/quotas/scuba/wrapper.js
@@ -1,6 +1,6 @@
 const assert = require('assert');
 const sinon = require('sinon');
-const { ScubaClientImpl } = require('../../../../lib/quotas/scuba/wrapper');
+const { ScubaClientImpl } = require('../../../../lib/utilization/scuba/wrapper');
 
 describe('ScubaClientImpl', () => {
     let client;


Reviews:
user: login: welansari
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T09:05:38Z
user: login: welansari
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T09:15:16Z
user: login: welansari
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T09:24:11Z
user: login: welansari
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T09:24:51Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T09:47:19Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T09:50:11Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T10:06:32Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T10:07:05Z
user: login: welansari
state: APPROVED
author_association: NONE
submitted_at: 2025-04-02T10:24:08Z
user: login: welansari
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T10:56:03Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T14:14:54Z
user: login: francoisferrand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T15:05:36Z
user: login: francoisferrand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T15:09:04Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T15:14:52Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T15:16:32Z
user: login: francoisferrand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T14:04:05Z
user: login: francoisferrand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T14:09:16Z
user: login: francoisferrand
body: should we target 9.0 or 9.1 ?

esp. if there is some "dependency" (direct/explicit or not) on other changes, may be best to merge in 9.1. to avoid any issue and better control when we bump.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T14:10:49Z

Review comments:
diff_hunk: @@ -25,21 +26,44 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+            let modified = new Date().toISOString();
path: lib/routes/veeam/get.js
user: login: welansari
body: Just for my understanding, what's the point of having this line here since we overwrite it immediatly after ?
created_at: 2025-04-02T09:05:37Z
author_association: NONE
diff_hunk: @@ -25,21 +26,44 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+            let modified = new Date().toISOString();
+            // Extract the last modified date, but do not include it when computing
+            // the file's ETag (md5)
+            modified = fileToBuild.value.LastModified;
+            delete fileToBuild.value.LastModified;
+
+            // The SOSAPI metrics are dynamically computed using the SUR backend.
+            if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
+                fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
+                fileToBuild.value.CapacityInfo.Available =
+                    Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
+                // TODO when SUR backend supports realtime metrics: enable this, it will report the
+                // real last modified date of the bucket, instead of the current date, ensuring
+                // no issue in a SOSAPI context.
path: lib/routes/veeam/get.js
user: login: welansari
body: should have a ticket linked to this ?
created_at: 2025-04-02T09:15:15Z
author_association: NONE
diff_hunk: @@ -0,0 +1,582 @@
+const assert = require('assert');
+const crypto = require('crypto');
+const async = require('async');
+const { Scuba: MockScuba } = require('../utilities/mock/Scuba');
+
+const { makeRequest } = require('../functional/raw-node/utils/makeRequest');
+const BucketUtility =
+    require('../functional/aws-node-sdk/lib/utility/bucket-util');
+
+const ipAddress = process.env.IP ? process.env.IP : '127.0.0.1';
+
+const veeamAuthCredentials = {
+    accessKey: 'accessKey1',
+    secretKey: 'verySecretKey1',
+};
+
+const badVeeamAuthCredentials = {
+    accessKey: 'accesKey1',
+    secretKey: 'veryecretKey1',
+};
+
+const TEST_BUCKET = 'veeambucket';
+const testArn = 'aws::iam:123456789012:user/bart';
+
+const testCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>1099511627776</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const testCapacityMd5 = crypto.createHash('md5')
+    .update(testCapacity, 'utf-8')
+    .digest('hex');
+
+const invalidTestCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>-5</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const invalidTestCapacityMd5 = crypto.createHash('md5')
+    .update(invalidTestCapacity, 'utf-8')
+    .digest('hex');
+
+const testSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const testSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+const invalidTestSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>-1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const invalidTestSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+let bucketUtil;
+let s3;
+
+/** makeVeeamRequest - utility function to generate a request going
+ * through veeam route
+ * @param {object} params - params for making request
+ * @param {string} params.method - request method
+ * @param {string} params.bucket - bucket name
+ * @param {string} params.objectKey - object key
+ * @param {object} [params.headers] - headers and their string values
+ * @param {object} [params.authCredentials] - authentication credentials
+ * @param {object} params.authCredentials.accessKey - access key
+ * @param {object} params.authCredentials.secretKey - secret key
+ * @param {string} [params.requestBody] - request body contents
+ * @param {object} [params.queryObj] - request query parameters
+ * @param {function} callback - with error and response parameters
+ * @return {undefined} - and call callback
+ */
+function makeVeeamRequest(params, callback) {
+    const { method, headers, bucket, objectKey,
+        authCredentials, requestBody, queryObj } = params;
+    const options = {
+        authCredentials,
+        hostname: ipAddress,
+        port: 8000,
+        method,
+        headers,
+        path: `/_/veeam/${bucket}/${objectKey}`,
+        urlForSignature: `/${bucket}/${objectKey}`,
+        requestBody,
+        jsonResponse: false,
+        queryObj,
+    };
+    makeRequest(options, callback);
+}
+
+(process.env.S3METADATA === 'mongodb' ? describe.only : describe.skip)('Veeam routes:', () => {
+    let scuba;
+
+    beforeEach(done => {
+        scuba = new MockScuba();
+        scuba.start();
+        setTimeout(done, 500);
+    });
+
+    afterEach(() => {
+        scuba.stop();
+    });
+
+    describe('veeam PUT routes:', () => {
+        before(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+                .then(() => done())
+                .catch(err => {
+                    process.stdout.write(`Error creating bucket: ${err}\n`);
+                    throw err;
+                });
+        });
+        after(done => {
+            bucketUtil.empty(TEST_BUCKET)
+                .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+                .then(() => done());
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`PUT ${key[0]}`, done => makeVeeamRequest({
+                method: 'PUT',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'content-length': key[1].length,
+                    'content-md5': key[2],
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+                requestBody: key[1],
+            }, (err, response) => {
+                if (err) {
+                    // Return the error, if any
+                    return done(err);
+                }
+                assert.strictEqual(response.statusCode, 200);
+                return done();
+            }));
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', invalidTestSystem, invalidTestSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', invalidTestCapacity, invalidTestCapacityMd5],
+        ].forEach(key => {
+            it(`PUT ${key[0]} should fail for invalid XML`, done => makeVeeamRequest({
+                method: 'PUT',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'content-length': key[1].length + 3,
+                    'content-md5': key[2],
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+                requestBody: `${key[1]}gff`,
+            }, err => {
+                assert.strictEqual(err.code, 'MalformedXML');
+                return done();
+            }));
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`PUT ${key[0]} should fail if invalid credentials are sent`, done => makeVeeamRequest({
+                method: 'PUT',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'content-length': key[1].length + 3,
+                    'content-md5': key[2],
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: badVeeamAuthCredentials,
+                requestBody: `${key[1]}gff`,
+            }, err => {
+                assert.strictEqual(err.code, 'InvalidAccessKeyId');
+                return done();
+            }));
+        });
+    });
+
+
+    describe('veeam GET routes:', () => {
+        beforeEach(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+                .then(() => done())
+                .catch(err => {
+                    process.stdout.write(`Error creating bucket: ${err}\n`);
+                    throw err;
+                });
+        });
+        afterEach(done => {
+            bucketUtil.empty(TEST_BUCKET)
+                .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+                .then(() => done());
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`GET ${key[0]} should return the expected XML file`, done => {
+                scuba.incrementBytesForBucket(TEST_BUCKET, 0);
+                async.waterfall([
+                    next => makeVeeamRequest({
+                        method: 'PUT',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'content-length': key[1].length,
+                            'content-md5': key[2],
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                        requestBody: key[1],
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        return next();
+                    }),
+                    next => makeVeeamRequest({
+                        method: 'GET',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        assert.strictEqual(response.body.replaceAll(' ', ''), key[1].replaceAll(' ', ''));
+                        return next();
+                    }),
+                ], err => {
+                    assert.ifError(err);
+                    return done();
+                });
+            });
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`GET ${key[0]} should return the expected XML file for cors requests`, done => {
+                async.waterfall([
+                    next => makeVeeamRequest({
+                        method: 'PUT',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'origin': 'http://localhost:8000',
+                            'content-length': key[1].length,
+                            'content-md5': key[2],
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                        requestBody: key[1],
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        return next();
+                    }),
+                    next => makeVeeamRequest({
+                        method: 'GET',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'origin': 'http://localhost:8000',
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        assert.strictEqual(response.body.replaceAll(' ', ''), key[1].replaceAll(' ', ''));
+                        return next();
+                    }),
+                ], err => {
+                    assert.ifError(err);
+                    return done();
+                });
+            });
+        });
+
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`GET ${key[0]} should fail if no data in bucket metadata`, done => makeVeeamRequest({
+                method: 'GET',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+            }, err => {
+                assert.strictEqual(err.code, 'NoSuchKey');
+                return done();
+            }));
+        });
+    });
+
+    describe('veeam DELETE routes:', () => {
+        beforeEach(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+                .then(() => done())
+                .catch(err => {
+                    process.stdout.write(`Error creating bucket: ${err}\n`);
+                    throw err;
+                });
+        });
+        afterEach(done => {
+            bucketUtil.empty(TEST_BUCKET)
+                .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+                .then(() => done());
+        });
+
+        [
+            // ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
path: tests/sur/routeVeeam.js
user: login: welansari
body: was this intentional ?
created_at: 2025-04-02T09:24:10Z
author_association: NONE
diff_hunk: @@ -0,0 +1,582 @@
+const assert = require('assert');
+const crypto = require('crypto');
+const async = require('async');
+const { Scuba: MockScuba } = require('../utilities/mock/Scuba');
+
+const { makeRequest } = require('../functional/raw-node/utils/makeRequest');
+const BucketUtility =
+    require('../functional/aws-node-sdk/lib/utility/bucket-util');
+
+const ipAddress = process.env.IP ? process.env.IP : '127.0.0.1';
+
+const veeamAuthCredentials = {
+    accessKey: 'accessKey1',
+    secretKey: 'verySecretKey1',
+};
+
+const badVeeamAuthCredentials = {
+    accessKey: 'accesKey1',
+    secretKey: 'veryecretKey1',
+};
+
+const TEST_BUCKET = 'veeambucket';
+const testArn = 'aws::iam:123456789012:user/bart';
+
+const testCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>1099511627776</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const testCapacityMd5 = crypto.createHash('md5')
+    .update(testCapacity, 'utf-8')
+    .digest('hex');
+
+const invalidTestCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>-5</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const invalidTestCapacityMd5 = crypto.createHash('md5')
+    .update(invalidTestCapacity, 'utf-8')
+    .digest('hex');
+
+const testSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const testSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+const invalidTestSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>-1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const invalidTestSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+let bucketUtil;
+let s3;
+
+/** makeVeeamRequest - utility function to generate a request going
+ * through veeam route
+ * @param {object} params - params for making request
+ * @param {string} params.method - request method
+ * @param {string} params.bucket - bucket name
+ * @param {string} params.objectKey - object key
+ * @param {object} [params.headers] - headers and their string values
+ * @param {object} [params.authCredentials] - authentication credentials
+ * @param {object} params.authCredentials.accessKey - access key
+ * @param {object} params.authCredentials.secretKey - secret key
+ * @param {string} [params.requestBody] - request body contents
+ * @param {object} [params.queryObj] - request query parameters
+ * @param {function} callback - with error and response parameters
+ * @return {undefined} - and call callback
+ */
+function makeVeeamRequest(params, callback) {
+    const { method, headers, bucket, objectKey,
+        authCredentials, requestBody, queryObj } = params;
+    const options = {
+        authCredentials,
+        hostname: ipAddress,
+        port: 8000,
+        method,
+        headers,
+        path: `/_/veeam/${bucket}/${objectKey}`,
+        urlForSignature: `/${bucket}/${objectKey}`,
+        requestBody,
+        jsonResponse: false,
+        queryObj,
+    };
+    makeRequest(options, callback);
+}
+
+(process.env.S3METADATA === 'mongodb' ? describe.only : describe.skip)('Veeam routes:', () => {
path: tests/sur/routeVeeam.js
user: login: welansari
body: 
```suggestion
(process.env.S3METADATA === 'mongodb' ? describe : describe.skip)('Veeam routes:', () => {
```
created_at: 2025-04-02T09:24:50Z
author_association: NONE
diff_hunk: @@ -25,21 +26,44 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+            let modified = new Date().toISOString();
+            // Extract the last modified date, but do not include it when computing
+            // the file's ETag (md5)
+            modified = fileToBuild.value.LastModified;
+            delete fileToBuild.value.LastModified;
+
+            // The SOSAPI metrics are dynamically computed using the SUR backend.
+            if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
+                fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
+                fileToBuild.value.CapacityInfo.Available =
+                    Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
+                // TODO when SUR backend supports realtime metrics: enable this, it will report the
+                // real last modified date of the bucket, instead of the current date, ensuring
+                // no issue in a SOSAPI context.
path: lib/routes/veeam/get.js
body: Not yet, we are not done with the design...
created_at: 2025-04-02T09:47:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,582 @@
+const assert = require('assert');
+const crypto = require('crypto');
+const async = require('async');
+const { Scuba: MockScuba } = require('../utilities/mock/Scuba');
+
+const { makeRequest } = require('../functional/raw-node/utils/makeRequest');
+const BucketUtility =
+    require('../functional/aws-node-sdk/lib/utility/bucket-util');
+
+const ipAddress = process.env.IP ? process.env.IP : '127.0.0.1';
+
+const veeamAuthCredentials = {
+    accessKey: 'accessKey1',
+    secretKey: 'verySecretKey1',
+};
+
+const badVeeamAuthCredentials = {
+    accessKey: 'accesKey1',
+    secretKey: 'veryecretKey1',
+};
+
+const TEST_BUCKET = 'veeambucket';
+const testArn = 'aws::iam:123456789012:user/bart';
+
+const testCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>1099511627776</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const testCapacityMd5 = crypto.createHash('md5')
+    .update(testCapacity, 'utf-8')
+    .digest('hex');
+
+const invalidTestCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>-5</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const invalidTestCapacityMd5 = crypto.createHash('md5')
+    .update(invalidTestCapacity, 'utf-8')
+    .digest('hex');
+
+const testSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const testSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+const invalidTestSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>-1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const invalidTestSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+let bucketUtil;
+let s3;
+
+/** makeVeeamRequest - utility function to generate a request going
+ * through veeam route
+ * @param {object} params - params for making request
+ * @param {string} params.method - request method
+ * @param {string} params.bucket - bucket name
+ * @param {string} params.objectKey - object key
+ * @param {object} [params.headers] - headers and their string values
+ * @param {object} [params.authCredentials] - authentication credentials
+ * @param {object} params.authCredentials.accessKey - access key
+ * @param {object} params.authCredentials.secretKey - secret key
+ * @param {string} [params.requestBody] - request body contents
+ * @param {object} [params.queryObj] - request query parameters
+ * @param {function} callback - with error and response parameters
+ * @return {undefined} - and call callback
+ */
+function makeVeeamRequest(params, callback) {
+    const { method, headers, bucket, objectKey,
+        authCredentials, requestBody, queryObj } = params;
+    const options = {
+        authCredentials,
+        hostname: ipAddress,
+        port: 8000,
+        method,
+        headers,
+        path: `/_/veeam/${bucket}/${objectKey}`,
+        urlForSignature: `/${bucket}/${objectKey}`,
+        requestBody,
+        jsonResponse: false,
+        queryObj,
+    };
+    makeRequest(options, callback);
+}
+
+(process.env.S3METADATA === 'mongodb' ? describe.only : describe.skip)('Veeam routes:', () => {
+    let scuba;
+
+    beforeEach(done => {
+        scuba = new MockScuba();
+        scuba.start();
+        setTimeout(done, 500);
+    });
+
+    afterEach(() => {
+        scuba.stop();
+    });
+
+    describe('veeam PUT routes:', () => {
+        before(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+                .then(() => done())
+                .catch(err => {
+                    process.stdout.write(`Error creating bucket: ${err}\n`);
+                    throw err;
+                });
+        });
+        after(done => {
+            bucketUtil.empty(TEST_BUCKET)
+                .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+                .then(() => done());
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`PUT ${key[0]}`, done => makeVeeamRequest({
+                method: 'PUT',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'content-length': key[1].length,
+                    'content-md5': key[2],
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+                requestBody: key[1],
+            }, (err, response) => {
+                if (err) {
+                    // Return the error, if any
+                    return done(err);
+                }
+                assert.strictEqual(response.statusCode, 200);
+                return done();
+            }));
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', invalidTestSystem, invalidTestSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', invalidTestCapacity, invalidTestCapacityMd5],
+        ].forEach(key => {
+            it(`PUT ${key[0]} should fail for invalid XML`, done => makeVeeamRequest({
+                method: 'PUT',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'content-length': key[1].length + 3,
+                    'content-md5': key[2],
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+                requestBody: `${key[1]}gff`,
+            }, err => {
+                assert.strictEqual(err.code, 'MalformedXML');
+                return done();
+            }));
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`PUT ${key[0]} should fail if invalid credentials are sent`, done => makeVeeamRequest({
+                method: 'PUT',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'content-length': key[1].length + 3,
+                    'content-md5': key[2],
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: badVeeamAuthCredentials,
+                requestBody: `${key[1]}gff`,
+            }, err => {
+                assert.strictEqual(err.code, 'InvalidAccessKeyId');
+                return done();
+            }));
+        });
+    });
+
+
+    describe('veeam GET routes:', () => {
+        beforeEach(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+                .then(() => done())
+                .catch(err => {
+                    process.stdout.write(`Error creating bucket: ${err}\n`);
+                    throw err;
+                });
+        });
+        afterEach(done => {
+            bucketUtil.empty(TEST_BUCKET)
+                .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+                .then(() => done());
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`GET ${key[0]} should return the expected XML file`, done => {
+                scuba.incrementBytesForBucket(TEST_BUCKET, 0);
+                async.waterfall([
+                    next => makeVeeamRequest({
+                        method: 'PUT',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'content-length': key[1].length,
+                            'content-md5': key[2],
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                        requestBody: key[1],
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        return next();
+                    }),
+                    next => makeVeeamRequest({
+                        method: 'GET',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        assert.strictEqual(response.body.replaceAll(' ', ''), key[1].replaceAll(' ', ''));
+                        return next();
+                    }),
+                ], err => {
+                    assert.ifError(err);
+                    return done();
+                });
+            });
+        });
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`GET ${key[0]} should return the expected XML file for cors requests`, done => {
+                async.waterfall([
+                    next => makeVeeamRequest({
+                        method: 'PUT',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'origin': 'http://localhost:8000',
+                            'content-length': key[1].length,
+                            'content-md5': key[2],
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                        requestBody: key[1],
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        return next();
+                    }),
+                    next => makeVeeamRequest({
+                        method: 'GET',
+                        bucket: TEST_BUCKET,
+                        objectKey: key[0],
+                        headers: {
+                            'origin': 'http://localhost:8000',
+                            'x-scal-canonical-id': testArn,
+                        },
+                        authCredentials: veeamAuthCredentials,
+                    }, (err, response) => {
+                        if (err) {
+                            return done(err);
+                        }
+                        assert.strictEqual(response.statusCode, 200);
+                        assert.strictEqual(response.body.replaceAll(' ', ''), key[1].replaceAll(' ', ''));
+                        return next();
+                    }),
+                ], err => {
+                    assert.ifError(err);
+                    return done();
+                });
+            });
+        });
+
+
+        [
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
+            ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml', testCapacity, testCapacityMd5],
+        ].forEach(key => {
+            it(`GET ${key[0]} should fail if no data in bucket metadata`, done => makeVeeamRequest({
+                method: 'GET',
+                bucket: TEST_BUCKET,
+                objectKey: key[0],
+                headers: {
+                    'x-scal-canonical-id': testArn,
+                },
+                authCredentials: veeamAuthCredentials,
+            }, err => {
+                assert.strictEqual(err.code, 'NoSuchKey');
+                return done();
+            }));
+        });
+    });
+
+    describe('veeam DELETE routes:', () => {
+        beforeEach(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
+                .then(() => done())
+                .catch(err => {
+                    process.stdout.write(`Error creating bucket: ${err}\n`);
+                    throw err;
+                });
+        });
+        afterEach(done => {
+            bucketUtil.empty(TEST_BUCKET)
+                .then(() => s3.deleteBucket({ Bucket: TEST_BUCKET }).promise())
+                .then(() => done());
+        });
+
+        [
+            // ['.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml', testSystem, testSystemMd5],
path: tests/sur/routeVeeam.js
body: no :slightly_smiling_face: 
created_at: 2025-04-02T09:50:10Z
author_association: NONE
diff_hunk: @@ -25,21 +26,44 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+            let modified = new Date().toISOString();
path: lib/routes/veeam/get.js
body: It was useful at first because we could have no date, but a commit in between changed the logic but didn't simplify this line.
created_at: 2025-04-02T10:06:32Z
author_association: NONE
diff_hunk: @@ -25,21 +26,44 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+            let modified = new Date().toISOString();
+            // Extract the last modified date, but do not include it when computing
+            // the file's ETag (md5)
+            modified = fileToBuild.value.LastModified;
+            delete fileToBuild.value.LastModified;
+
+            // The SOSAPI metrics are dynamically computed using the SUR backend.
+            if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
+                fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
+                fileToBuild.value.CapacityInfo.Available =
+                    Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
+                // TODO when SUR backend supports realtime metrics: enable this, it will report the
+                // real last modified date of the bucket, instead of the current date, ensuring
+                // no issue in a SOSAPI context.
path: lib/routes/veeam/get.js
body: I added one, but it might change or we might forget it for some time, but at least we have a ticket number with context...
created_at: 2025-04-02T10:07:05Z
author_association: NONE
diff_hunk: @@ -25,21 +26,44 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+
+            // Extract the last modified date, but do not include it when computing
+            // the file's ETag (md5)
+            const modified = fileToBuild.value.LastModified;
+            delete fileToBuild.value.LastModified;
 
-        const builder = new xml2js.Builder({
-            headless: true,
-        });
-        return respondWithData(request, response, log, data,
-            buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
+            // The SOSAPI metrics are dynamically computed using the SUR backend.
+            if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
+                fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
+                fileToBuild.value.CapacityInfo.Available =
+                    Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
+                // TODO CLDSRV-633 when SUR backend supports realtime metrics: enable this, it will
+                // report the real last modified date of the bucket, instead of the current date,
+                // ensuring no issue in a SOSAPI context.
+                // modified = new Date(bucketMetrics.date).toISOString();
+            }
+
+            const builder = new xml2js.Builder({
+                headless: true,
+            });
+            return respondWithData(request, response, log, data,
+                buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
+        };
+        if (!isSystemXML(request.objectKey)) {
path: lib/routes/veeam/get.js
user: login: welansari
body: Should we also check that the scuba backend is enabled ?
created_at: 2025-04-02T10:56:02Z
author_association: NONE
diff_hunk: @@ -25,21 +26,44 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+
+            // Extract the last modified date, but do not include it when computing
+            // the file's ETag (md5)
+            const modified = fileToBuild.value.LastModified;
+            delete fileToBuild.value.LastModified;
 
-        const builder = new xml2js.Builder({
-            headless: true,
-        });
-        return respondWithData(request, response, log, data,
-            buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
+            // The SOSAPI metrics are dynamically computed using the SUR backend.
+            if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
+                fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
+                fileToBuild.value.CapacityInfo.Available =
+                    Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
+                // TODO CLDSRV-633 when SUR backend supports realtime metrics: enable this, it will
+                // report the real last modified date of the bucket, instead of the current date,
+                // ensuring no issue in a SOSAPI context.
+                // modified = new Date(bucketMetrics.date).toISOString();
+            }
+
+            const builder = new xml2js.Builder({
+                headless: true,
+            });
+            return respondWithData(request, response, log, data,
+                buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
+        };
+        if (!isSystemXML(request.objectKey)) {
path: lib/routes/veeam/get.js
body: Practically this won't change much: in this branch and the branch before, scuba is always deployed. Inflight quota service is enabled based on the scuba presence in the config. here, having no scuba would just cause an error we already handle. We do not want to default to zero metrics when there is no Scuba, so we can only return a more specific error.
created_at: 2025-04-02T14:14:54Z
author_association: NONE
diff_hunk: @@ -25,21 +26,44 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+
+            // Extract the last modified date, but do not include it when computing
+            // the file's ETag (md5)
+            const modified = fileToBuild.value.LastModified;
+            delete fileToBuild.value.LastModified;
 
-        const builder = new xml2js.Builder({
-            headless: true,
-        });
-        return respondWithData(request, response, log, data,
-            buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
+            // The SOSAPI metrics are dynamically computed using the SUR backend.
+            if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
+                fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
+                fileToBuild.value.CapacityInfo.Available =
+                    Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
+                // TODO CLDSRV-633 when SUR backend supports realtime metrics: enable this, it will
+                // report the real last modified date of the bucket, instead of the current date,
path: lib/routes/veeam/get.js
user: login: francoisferrand
body: we don't want to report the `last modified date of the bucket`, as it would indicate stale data if the bucket has not been modified in a long time: we probably want to report the `date of the last "scan" of the bucket`, i.e. (when trailing oplog) something like the date of the last metadata operation which has been computed in the utilization metrics. 
created_at: 2025-04-02T15:05:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,582 @@
+const assert = require('assert');
+const crypto = require('crypto');
+const async = require('async');
+const { Scuba: MockScuba } = require('../utilities/mock/Scuba');
+
+const { makeRequest } = require('../functional/raw-node/utils/makeRequest');
+const BucketUtility =
+    require('../functional/aws-node-sdk/lib/utility/bucket-util');
+
+const ipAddress = process.env.IP ? process.env.IP : '127.0.0.1';
+
+const veeamAuthCredentials = {
+    accessKey: 'accessKey1',
+    secretKey: 'verySecretKey1',
+};
+
+const badVeeamAuthCredentials = {
+    accessKey: 'accesKey1',
+    secretKey: 'veryecretKey1',
+};
+
+const TEST_BUCKET = 'veeambucket';
+const testArn = 'aws::iam:123456789012:user/bart';
+
+const testCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>1099511627776</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const testCapacityMd5 = crypto.createHash('md5')
+    .update(testCapacity, 'utf-8')
+    .digest('hex');
+
+const invalidTestCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>-5</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const invalidTestCapacityMd5 = crypto.createHash('md5')
+    .update(invalidTestCapacity, 'utf-8')
+    .digest('hex');
+
+const testSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const testSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+const invalidTestSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>-1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const invalidTestSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+let bucketUtil;
+let s3;
+
+/** makeVeeamRequest - utility function to generate a request going
+ * through veeam route
+ * @param {object} params - params for making request
+ * @param {string} params.method - request method
+ * @param {string} params.bucket - bucket name
+ * @param {string} params.objectKey - object key
+ * @param {object} [params.headers] - headers and their string values
+ * @param {object} [params.authCredentials] - authentication credentials
+ * @param {object} params.authCredentials.accessKey - access key
+ * @param {object} params.authCredentials.secretKey - secret key
+ * @param {string} [params.requestBody] - request body contents
+ * @param {object} [params.queryObj] - request query parameters
+ * @param {function} callback - with error and response parameters
+ * @return {undefined} - and call callback
+ */
+function makeVeeamRequest(params, callback) {
+    const { method, headers, bucket, objectKey,
+        authCredentials, requestBody, queryObj } = params;
+    const options = {
+        authCredentials,
+        hostname: ipAddress,
+        port: 8000,
+        method,
+        headers,
+        path: `/_/veeam/${bucket}/${objectKey}`,
+        urlForSignature: `/${bucket}/${objectKey}`,
+        requestBody,
+        jsonResponse: false,
+        queryObj,
+    };
+    makeRequest(options, callback);
+}
+
+(process.env.S3METADATA === 'mongodb' ? describe : describe.skip)('Veeam routes:', () => {
+    let scuba;
+
+    beforeEach(done => {
+        scuba = new MockScuba();
+        scuba.start();
+        setTimeout(done, 500);
+    });
+
+    afterEach(() => {
+        scuba.stop();
+    });
+
+    describe('veeam PUT routes:', () => {
+        before(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
path: tests/sur/routeVeeam.js
user: login: francoisferrand
body: shouldn't we use await and stop using callbacks?
created_at: 2025-04-02T15:09:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,582 @@
+const assert = require('assert');
+const crypto = require('crypto');
+const async = require('async');
+const { Scuba: MockScuba } = require('../utilities/mock/Scuba');
+
+const { makeRequest } = require('../functional/raw-node/utils/makeRequest');
+const BucketUtility =
+    require('../functional/aws-node-sdk/lib/utility/bucket-util');
+
+const ipAddress = process.env.IP ? process.env.IP : '127.0.0.1';
+
+const veeamAuthCredentials = {
+    accessKey: 'accessKey1',
+    secretKey: 'verySecretKey1',
+};
+
+const badVeeamAuthCredentials = {
+    accessKey: 'accesKey1',
+    secretKey: 'veryecretKey1',
+};
+
+const TEST_BUCKET = 'veeambucket';
+const testArn = 'aws::iam:123456789012:user/bart';
+
+const testCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>1099511627776</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const testCapacityMd5 = crypto.createHash('md5')
+    .update(testCapacity, 'utf-8')
+    .digest('hex');
+
+const invalidTestCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>-5</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const invalidTestCapacityMd5 = crypto.createHash('md5')
+    .update(invalidTestCapacity, 'utf-8')
+    .digest('hex');
+
+const testSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const testSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+const invalidTestSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>-1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const invalidTestSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+let bucketUtil;
+let s3;
+
+/** makeVeeamRequest - utility function to generate a request going
+ * through veeam route
+ * @param {object} params - params for making request
+ * @param {string} params.method - request method
+ * @param {string} params.bucket - bucket name
+ * @param {string} params.objectKey - object key
+ * @param {object} [params.headers] - headers and their string values
+ * @param {object} [params.authCredentials] - authentication credentials
+ * @param {object} params.authCredentials.accessKey - access key
+ * @param {object} params.authCredentials.secretKey - secret key
+ * @param {string} [params.requestBody] - request body contents
+ * @param {object} [params.queryObj] - request query parameters
+ * @param {function} callback - with error and response parameters
+ * @return {undefined} - and call callback
+ */
+function makeVeeamRequest(params, callback) {
+    const { method, headers, bucket, objectKey,
+        authCredentials, requestBody, queryObj } = params;
+    const options = {
+        authCredentials,
+        hostname: ipAddress,
+        port: 8000,
+        method,
+        headers,
+        path: `/_/veeam/${bucket}/${objectKey}`,
+        urlForSignature: `/${bucket}/${objectKey}`,
+        requestBody,
+        jsonResponse: false,
+        queryObj,
+    };
+    makeRequest(options, callback);
+}
+
+(process.env.S3METADATA === 'mongodb' ? describe : describe.skip)('Veeam routes:', () => {
+    let scuba;
+
+    beforeEach(done => {
+        scuba = new MockScuba();
+        scuba.start();
+        setTimeout(done, 500);
+    });
+
+    afterEach(() => {
+        scuba.stop();
+    });
+
+    describe('veeam PUT routes:', () => {
+        before(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
path: tests/sur/routeVeeam.js
body: Yes, changing the bucket util is likely a long work that I would prefer doing separately as we use it in many test files...
created_at: 2025-04-02T15:14:52Z
author_association: NONE
diff_hunk: @@ -25,21 +26,44 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+
+            // Extract the last modified date, but do not include it when computing
+            // the file's ETag (md5)
+            const modified = fileToBuild.value.LastModified;
+            delete fileToBuild.value.LastModified;
 
-        const builder = new xml2js.Builder({
-            headless: true,
-        });
-        return respondWithData(request, response, log, data,
-            buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
+            // The SOSAPI metrics are dynamically computed using the SUR backend.
+            if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
+                fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
+                fileToBuild.value.CapacityInfo.Available =
+                    Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
+                // TODO CLDSRV-633 when SUR backend supports realtime metrics: enable this, it will
+                // report the real last modified date of the bucket, instead of the current date,
path: lib/routes/veeam/get.js
body: Yes I meant the current cseq (not for a specific bucket: if the bucket is not updated, the "date of the last scan of the bucket" will never happen, unless we repair). I'll update the ticket, the comment remains fine to me.
created_at: 2025-04-02T15:16:32Z
author_association: NONE
diff_hunk: @@ -0,0 +1,582 @@
+const assert = require('assert');
+const crypto = require('crypto');
+const async = require('async');
+const { Scuba: MockScuba } = require('../utilities/mock/Scuba');
+
+const { makeRequest } = require('../functional/raw-node/utils/makeRequest');
+const BucketUtility =
+    require('../functional/aws-node-sdk/lib/utility/bucket-util');
+
+const ipAddress = process.env.IP ? process.env.IP : '127.0.0.1';
+
+const veeamAuthCredentials = {
+    accessKey: 'accessKey1',
+    secretKey: 'verySecretKey1',
+};
+
+const badVeeamAuthCredentials = {
+    accessKey: 'accesKey1',
+    secretKey: 'veryecretKey1',
+};
+
+const TEST_BUCKET = 'veeambucket';
+const testArn = 'aws::iam:123456789012:user/bart';
+
+const testCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>1099511627776</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const testCapacityMd5 = crypto.createHash('md5')
+    .update(testCapacity, 'utf-8')
+    .digest('hex');
+
+const invalidTestCapacity = `<?xmlversion="1.0"encoding="UTF-8"?>
+<CapacityInfo>
+    <Capacity>1099511627776</Capacity>
+    <Available>-5</Available>
+    <Used>0</Used>
+</CapacityInfo>\n`;
+
+const invalidTestCapacityMd5 = crypto.createHash('md5')
+    .update(invalidTestCapacity, 'utf-8')
+    .digest('hex');
+
+const testSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const testSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+const invalidTestSystem = `<?xmlversion="1.0"encoding="UTF-8"?>
+    <SystemInfo>
+       <ProtocolVersion>"1.0"</ProtocolVersion>
+       <ModelName>"ARTESCA"</ModelName>
+       <ProtocolCapabilities>
+          <CapacityInfo>true</CapacityInfo>
+          <UploadSessions>false</UploadSessions>
+          <IAMSTS>true</IAMSTS>
+       </ProtocolCapabilities>
+       <APIEndpoints>
+            <IAMEndpoint>a</IAMEndpoint>
+            <STSEndpoint>a</STSEndpoint>
+       </APIEndpoints>
+       <SystemRecommendations>
+           <S3ConcurrentTaskLimit>0</S3ConcurrentTaskLimit>
+           <S3MultiObjectDeleteLimit>-1</S3MultiObjectDeleteLimit>
+           <StorageCurrentTasksLimit>0</StorageCurrentTasksLimit>
+           <KbBlockSize>256</KbBlockSize>
+       </SystemRecommendations>
+    </SystemInfo>\n`;
+
+const invalidTestSystemMd5 = crypto.createHash('md5')
+    .update(testSystem, 'utf-8')
+    .digest('hex');
+
+let bucketUtil;
+let s3;
+
+/** makeVeeamRequest - utility function to generate a request going
+ * through veeam route
+ * @param {object} params - params for making request
+ * @param {string} params.method - request method
+ * @param {string} params.bucket - bucket name
+ * @param {string} params.objectKey - object key
+ * @param {object} [params.headers] - headers and their string values
+ * @param {object} [params.authCredentials] - authentication credentials
+ * @param {object} params.authCredentials.accessKey - access key
+ * @param {object} params.authCredentials.secretKey - secret key
+ * @param {string} [params.requestBody] - request body contents
+ * @param {object} [params.queryObj] - request query parameters
+ * @param {function} callback - with error and response parameters
+ * @return {undefined} - and call callback
+ */
+function makeVeeamRequest(params, callback) {
+    const { method, headers, bucket, objectKey,
+        authCredentials, requestBody, queryObj } = params;
+    const options = {
+        authCredentials,
+        hostname: ipAddress,
+        port: 8000,
+        method,
+        headers,
+        path: `/_/veeam/${bucket}/${objectKey}`,
+        urlForSignature: `/${bucket}/${objectKey}`,
+        requestBody,
+        jsonResponse: false,
+        queryObj,
+    };
+    makeRequest(options, callback);
+}
+
+(process.env.S3METADATA === 'mongodb' ? describe : describe.skip)('Veeam routes:', () => {
+    let scuba;
+
+    beforeEach(done => {
+        scuba = new MockScuba();
+        scuba.start();
+        setTimeout(done, 500);
+    });
+
+    afterEach(() => {
+        scuba.stop();
+    });
+
+    describe('veeam PUT routes:', () => {
+        before(done => {
+            bucketUtil = new BucketUtility(
+                'default', { signatureVersion: 'v4' });
+            s3 = bucketUtil.s3;
+            s3.createBucket({ Bucket: TEST_BUCKET }).promise()
path: tests/sur/routeVeeam.js
user: login: francoisferrand
body: sorry, i missed the rename of the file... 
(but i was not speaking so moch of changing bucket util, just changing the tests to drop the callback and use async functions in there, and await instead of `.promise().then()`)
created_at: 2025-04-04T14:04:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,21 +26,44 @@ function getVeeamFile(request, response, bucketMd, log) {
         if (err) {
             return responseXMLBody(errors.InternalError, null, response, log);
         }
-        const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
-        if (fileToBuild.error) {
-            return responseXMLBody(fileToBuild.error, null, response, log);
-        }
-        let modified = new Date().toISOString();
-        // Extract the last modified date, but do not include it when computing
-        // the file's ETag (md5)
-        modified = fileToBuild.value.LastModified;
-        delete fileToBuild.value.LastModified;
+        const finalizeRequest = bucketMetrics => {
+            const fileToBuild = getFileToBuild(request, data._capabilities?.VeeamSOSApi);
+            if (fileToBuild.error) {
+                return responseXMLBody(fileToBuild.error, null, response, log);
+            }
+
+            // Extract the last modified date, but do not include it when computing
+            // the file's ETag (md5)
+            const modified = fileToBuild.value.LastModified;
+            delete fileToBuild.value.LastModified;
 
-        const builder = new xml2js.Builder({
-            headless: true,
-        });
-        return respondWithData(request, response, log, data,
-            buildHeadXML(builder.buildObject(fileToBuild.value)), modified);
+            // The SOSAPI metrics are dynamically computed using the SUR backend.
+            if (bucketMetrics && !fileToBuild.value.CapacityInfo?.Used) {
+                fileToBuild.value.CapacityInfo.Used = Number(bucketMetrics.bytesTotal);
+                fileToBuild.value.CapacityInfo.Available =
+                    Number(fileToBuild.value.CapacityInfo.Capacity) - Number(bucketMetrics.bytesTotal);
+                // TODO CLDSRV-633 when SUR backend supports realtime metrics: enable this, it will
+                // report the real last modified date of the bucket, instead of the current date,
path: lib/routes/veeam/get.js
user: login: francoisferrand
body: i think the comment may be misleading for someone discovering the issue here: if we update the ticket, we might as well update the comment to avoid any confusion...
created_at: 2025-04-04T14:09:15Z
author_association: CONTRIBUTOR

PR comments:
user: login: bert-e
created_at: 2025-04-01T12:57:50Z
body: # Hello williamlardier,



My role is to assist you with the merge of this
pull request. Please type `@bert-e help` to get information
on this process, or consult the [user documentation](
https://bert-e.scality.net/scality/cloudserver/doc/user).


<details>
  <summary><b>Available options</b></summary>

  name   | description  | privileged   | authored
  ------ | ------------ | ------------ |----------
  `/after_pull_request` | Wait for the given pull request id to be merged before continuing with the current one. |  | 
  `/bypass_author_approval` | Bypass the pull request author's approval |  :star:  | 
  `/bypass_build_status` | Bypass the build and test status |  :star:  | 
  `/bypass_commit_size` | Bypass the check on the size of the changeset ```TBA``` |  :star:  | 
  `/bypass_incompatible_branch` | Bypass the check on the source branch prefix |  :star:  | 
  `/bypass_jira_check` | Bypass the Jira issue check |  :star:  | 
  `/bypass_peer_approval` | Bypass the pull request peers' approval |  :star:  | 
  `/bypass_leader_approval` | Bypass the pull request leaders' approval |  :star:  | 
  `/approve` | Instruct Bert-E that the author has approved the pull request. |  |  :writing_hand: 
  `/create_pull_requests` | Allow the creation of integration pull requests. |  | 
  `/create_integration_branches` | Allow the creation of integration branches. |  | 
  `/no_octopus` | Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead |  | 
  `/unanimity` | Change review acceptance criteria from `one reviewer at least` to `all reviewers` |  | 
  `/wait` | Instruct Bert-E not to run until further notice. |  | 
  

</details>



<details>
  <summary><b>Available commands</b></summary>

  name   | description  | privileged
  ------ | ------------ | ------------
  `/help` | Print Bert-E's manual in the pull request. | 
  `/status` | Print Bert-E's current status in the pull request ```TBA``` | 
  `/clear` | Remove all comments from Bert-E from the history ```TBA``` | 
  `/retry` | Re-start a fresh build ```TBA``` | 
  `/build` | Re-start a fresh build ```TBA``` | 
  `/force_reset` | Delete integration branches & pull requests, and restart merge process from the beginning. | 
  `/reset` | Try to remove integration branches unless there are commits on them which do not appear on the source branch. | 
  
</details>




*Status report is not available.*







author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-04-01T12:58:34Z
body: ## [Codecov](https://app.codecov.io/gh/scality/cloudserver/pull/5777?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) Report
Attention: Patch coverage is `96.15385%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 74.23%. Comparing base [(`99815fb`)](https://app.codecov.io/gh/scality/cloudserver/commit/99815fb7890e4e184f546303cbbda2cd8f495aac?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) to head [(`e3d4dec`)](https://app.codecov.io/gh/scality/cloudserver/commit/e3d4decff075737db3bdd5acc2970a9f4d9b30ee?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality).
> Report is 9 commits behind head on development/9.1.

:white_check_mark: All tests successful. No failed tests found.

| [Files with missing lines](https://app.codecov.io/gh/scality/cloudserver/pull/5777?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | Patch % | Lines |
|---|---|---|
| [lib/routes/veeam/get.js](https://app.codecov.io/gh/scality/cloudserver/pull/5777?src=pr&el=tree&filepath=lib%2Froutes%2Fveeam%2Fget.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality#diff-bGliL3JvdXRlcy92ZWVhbS9nZXQuanM=) | 95.00% | [1 Missing :warning: ](https://app.codecov.io/gh/scality/cloudserver/pull/5777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) |

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/scality/cloudserver/pull/5777/graphs/tree.svg?width=650&height=150&src=pr&token=zqaSCoMBBl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality)](https://app.codecov.io/gh/scality/cloudserver/pull/5777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality)

| [Files with missing lines](https://app.codecov.io/gh/scality/cloudserver/pull/5777?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | Coverage Δ | |
|---|---|---|
| [lib/api/apiUtils/quotas/quotaUtils.js](https://app.codecov.io/gh/scality/cloudserver/pull/5777?src=pr&el=tree&filepath=lib%2Fapi%2FapiUtils%2Fquotas%2FquotaUtils.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality#diff-bGliL2FwaS9hcGlVdGlscy9xdW90YXMvcXVvdGFVdGlscy5qcw==) | `100.00% <100.00%> (ø)` | |
| [lib/server.js](https://app.codecov.io/gh/scality/cloudserver/pull/5777?src=pr&el=tree&filepath=lib%2Fserver.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality#diff-bGliL3NlcnZlci5qcw==) | `73.96% <100.00%> (ø)` | |
| [lib/utilization/instance.js](https://app.codecov.io/gh/scality/cloudserver/pull/5777?src=pr&el=tree&filepath=lib%2Futilization%2Finstance.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality#diff-bGliL3V0aWxpemF0aW9uL2luc3RhbmNlLmpz) | `100.00% <100.00%> (ø)` | |
| [lib/utilization/scuba/wrapper.js](https://app.codecov.io/gh/scality/cloudserver/pull/5777?src=pr&el=tree&filepath=lib%2Futilization%2Fscuba%2Fwrapper.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality#diff-bGliL3V0aWxpemF0aW9uL3NjdWJhL3dyYXBwZXIuanM=) | `100.00% <ø> (ø)` | |
| [lib/routes/veeam/get.js](https://app.codecov.io/gh/scality/cloudserver/pull/5777?src=pr&el=tree&filepath=lib%2Froutes%2Fveeam%2Fget.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality#diff-bGliL3JvdXRlcy92ZWVhbS9nZXQuanM=) | `87.50% <95.00%> (+1.78%)` | :arrow_up: |

```diff
@@               Coverage Diff                @@
##           development/9.1    #5777   +/-   ##
================================================
  Coverage            74.23%   74.23%           
================================================
  Files                  186      186           
  Lines                11735    11741    +6     
================================================
+ Hits                  8711     8716    +5     
- Misses                3024     3025    +1     
```

| [Flag](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | Coverage Δ | |
|---|---|---|
| [ceph-backend-test](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | `42.80% <30.76%> (-1.52%)` | :arrow_down: |
| [file-ft-tests](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | `47.61% <30.76%> (+0.02%)` | :arrow_up: |
| [kmip-ft-tests](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | `26.59% <30.76%> (+0.03%)` | :arrow_up: |
| [mongo-v0-ft-tests](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | `47.91% <30.76%> (+0.02%)` | :arrow_up: |
| [mongo-v1-ft-tests](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | `47.93% <30.76%> (+0.02%)` | :arrow_up: |
| [multiple-backend](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | `30.57% <30.76%> (-1.51%)` | :arrow_down: |
| [sur-tests](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | `34.64% <96.15%> (?)` | |
| [sur-tests-inflights](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | `36.57% <96.15%> (?)` | |
| [unit](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | `65.72% <26.92%> (-0.06%)` | :arrow_down: |
| [utapi-v2-tests](https://app.codecov.io/gh/scality/cloudserver/pull/5777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality) | `33.37% <30.76%> (+0.03%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scality#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: bert-e
created_at: 2025-04-01T13:00:41Z
body: # Waiting for approval



The following approvals are needed before I can proceed with the merge:


* the author


* 2 peers












author_association: CONTRIBUTOR
user: login: bert-e
created_at: 2025-04-04T14:20:46Z
body: # Waiting for approval



The following approvals are needed before I can proceed with the merge:


* the author


* 2 peers












author_association: CONTRIBUTOR
user: login: ghost
created_at: 2025-04-04T14:20:55Z
body: /approve
author_association: NONE
user: login: bert-e
created_at: 2025-04-04T14:27:58Z
body: # In the queue



The changeset has received all authorizations and has been added to the
relevant queue(s). The queue(s) will be merged in the target development
branch(es) as soon as builds have passed.

The changeset will be merged in:


* :heavy_check_mark: `development/9.1`



The following branches will **NOT** be impacted:

* `development/7.10`
* `development/7.4`
* `development/7.70`
* `development/8.8`
* `development/9.0`



There is no action required on your side. You will be notified here once
the changeset has been merged. In the unlikely event that the changeset
fails permanently on the queue, a member of the admin team will
contact you to help resolve the matter.

*IMPORTANT*

Please do not attempt to modify this pull request.

* Any commit you add on the source branch will trigger a new cycle after the
  current queue is merged.
* Any commit you add on one of the integration branches will be **lost**.

If you need this pull request to be removed from the queue, please contact a
member of the admin team now.




*The following options are set:* **approve**


author_association: CONTRIBUTOR
user: login: bert-e
created_at: 2025-04-04T14:42:00Z
body: # Queue build failed




The corresponding build for the queue failed:

- Checkout the [status page](https://bert-e.scality.net/scality/cloudserver).
- Identify the failing build and review the logs.
- If no issue is found, re-run the build.
- If an issue is identified, checkout the steps below to remove
  the pull request from the queue for further analysis and maybe rebase/merge.

<details>
  <summary><b>Remove the pull request from the queue</b></summary>

- Add a `/wait` comment on this pull request.
- Click on login on the [status page](https://bert-e.scality.net/scality/cloudserver).
- Go into the [manage](https://bert-e.scality.net/scality/cloudserver/manage) page.
- Find the option called `Rebuild the queue` and click on it.
  Bert-E will loop again on all pull requests to put the valid ones
  in the queue again, while skipping the one with the `/wait` comment.
- Wait for the new queue to merge, then merge/rebase your pull request
  with the latest changes to then work on a proper fix.
- Once the issue is fixed, delete the `/wait` comment and
  follow the usual process to merge the pull request.

</details>







author_association: CONTRIBUTOR
user: login: bert-e
created_at: 2025-04-04T14:54:35Z
body: # 


I have successfully merged the changeset of this pull request
into targetted development branches:


* :heavy_check_mark: `development/9.1`



The following branches have **NOT** changed:

* `development/7.10`
* `development/7.4`
* `development/7.70`
* `development/8.8`
* `development/9.0`



Please check the status of the associated issue CLDSRV-631.

Goodbye williamlardier.





author_association: CONTRIBUTOR