Skip to content

Conversation

DarkIsDude
Copy link
Contributor

@DarkIsDude DarkIsDude commented Aug 25, 2025

Description

Reactivate some tests that were disable because some edge case were not covered.

Motivation and context

Manage 2 use case under the put metadata route:

  • Manage null version when an object is created before bucket versioning and put metadata called after bucket versioning enabled
/* eslint-disable no-console */
const util = require('util');

const { makeBackbeatRequest: makeBackbeatRequestBase } = require('./tests/functional/raw-node/utils/makeRequest');
const { models } = require('arsenal');
const { ObjectMD } = models;
const BucketUtility = require('./tests/functional/aws-node-sdk/lib/utility/bucket-util');

const makeBackbeatRequest = util.promisify(makeBackbeatRequestBase);

function objectMDFromRequestBody(data) {
    const bodyStr = JSON.parse(data.body).Body;
    return new ObjectMD(JSON.parse(bodyStr));
}

async function main() {
    const OBJECT_KEY = 'test-object';
    const BUCKET_NAME = 'test-bucket';
    const TEST_DATA = 'This is a test object for replication';

    const authCredentials = {
        accessKey: 'BJ8Q0L35PRJ92ABA2K0B',
        secretKey: 'kTgcfEaLjxvrLN5EKVcTnb4Ac046FU1m=33/baf1',
    };


    const bucketUtil = new BucketUtility('local-test-vault-s3', { signatureVersion: 'v4' });
    const s3 = bucketUtil.s3;

    console.info('Starting test for object metadata replication...');
    console.info('Checking if bucket exists...');
    await bucketUtil.emptyIfExists(BUCKET_NAME);

    if (await bucketUtil.bucketExists(BUCKET_NAME)) {
        console.info('Deleting bucket...');
        await s3.deleteBucket({ Bucket: BUCKET_NAME }).promise();
    }

    console.info('Creating bucket...');
    await s3.createBucket({ Bucket: BUCKET_NAME }).promise();
 
    console.info('Putting object with versioning disabled...');
    await s3.putObject({ Bucket: BUCKET_NAME, Key: OBJECT_KEY, Body: Buffer.from(TEST_DATA) }).promise();

    console.info('Enabling versioning on bucket...');
    await s3.putBucketVersioning({ Bucket: BUCKET_NAME, VersioningConfiguration: { Status: 'Enabled' } }).promise();
    const versionId = null;
    console.info('Retrieve metadata for the object with versioning enabled...');
    const data = await makeBackbeatRequest({
        method: 'GET',
        resourceType: 'metadata',
        bucket: BUCKET_NAME,
        objectKey: OBJECT_KEY,
        queryObj: {
            versionId,
        },
        authCredentials,
    });

    const objMD = objectMDFromRequestBody(data)
        .setContentLanguage('fr-FR')
        .getSerialized();

    console.info('Object metadata retrieved successfully:', objMD);
    console.info('Updating object metadata...');

    objMD.tags = {
        'fuck': 'test-value',
    };

    await makeBackbeatRequest({
        method: 'PUT',
        resourceType: 'metadata',
        bucket: BUCKET_NAME,
        objectKey: OBJECT_KEY,
        queryObj: {
            versionId,
        },
        authCredentials,
        requestBody: objMD,
    });

    console.info('Object metadata updated successfully.');

    const versions = await s3.listObjectVersions({
        Bucket: BUCKET_NAME,
        Prefix: OBJECT_KEY,
    }).promise();

    console.info({ versions });
}

main();
  • Add missing metadata
/* eslint-disable no-console */
const util = require('util');

const { makeBackbeatRequest: makeBackbeatRequestBase } = require('./tests/functional/raw-node/utils/makeRequest');
const { models } = require('arsenal');
const { ObjectMD } = models;
const BucketUtility = require('./tests/functional/aws-node-sdk/lib/utility/bucket-util');

const makeBackbeatRequest = util.promisify(makeBackbeatRequestBase);

/**
 * The final result should be to have two versions of the object:
 * 1. The original version created when versioning was disabled (null versionId)
 * 2. A new version created when versioning was re-enabled (with a VersionId)
 * The new version should have the updated metadata (ContentLanguage set to 'fr-FR')
 * while the original version should remain unchanged.
 **/

const OBJECT_KEY_ROOT = 'test-object-root';
const OBJECT_KEY = 'test-object';
const BUCKET_NAME = 'test-bucket';
const TEST_DATA = 'This is a test object for replication';

const authCredentials = {
    accessKey: 'BJ8Q0L35PRJ92ABA2K0B',
    secretKey: 'kTgcfEaLjxvrLN5EKVcTnb4Ac046FU1m=33/baf1',
};

function objectMDFromRequestBody(data) {
    const bodyStr = JSON.parse(data.body).Body;
    return new ObjectMD(JSON.parse(bodyStr));
}

async function main() {
    const bucketUtil = new BucketUtility('local-test-vault-s3', { signatureVersion: 'v4' });
    const s3 = bucketUtil.s3;

    // await initTest(s3, bucketUtil);
    await updateObjectMetadata(s3);
}

async function initTest(s3, bucketUtil) {
    if (await bucketUtil.bucketExists(BUCKET_NAME)) {
        await bucketUtil.emptyIfExists(BUCKET_NAME);
        await s3.deleteBucket({ Bucket: BUCKET_NAME }).promise();
    }

    await s3.createBucket({ Bucket: BUCKET_NAME }).promise();
    await s3.putBucketVersioning({ Bucket: BUCKET_NAME, VersioningConfiguration: { Status: 'Enabled' } }).promise();
    await s3.putObject({ Bucket: BUCKET_NAME, Key: OBJECT_KEY_ROOT, Body: Buffer.from(TEST_DATA) }).promise();
    await s3.putBucketVersioning({ Bucket: BUCKET_NAME, VersioningConfiguration: { Status: 'Suspended' } }).promise();
    await s3.putObject({ Bucket: BUCKET_NAME, Key: OBJECT_KEY, Body: Buffer.from(TEST_DATA) }).promise();
    await s3.putBucketVersioning({ Bucket: BUCKET_NAME, VersioningConfiguration: { Status: 'Enabled' } }).promise();
}

async function updateObjectMetadata(s3) {
        const data = await makeBackbeatRequest({
        method: 'GET',
        resourceType: 'metadata',
        bucket: BUCKET_NAME,
        objectKey: OBJECT_KEY,
        queryObj: {
            versionId: null,
        },
        authCredentials,
    });

    const objMD = objectMDFromRequestBody(data)
        .setContentLanguage('fr-FR')
        .getSerialized();

    await makeBackbeatRequest({
        method: 'PUT',
        resourceType: 'metadata',
        bucket: BUCKET_NAME,
        objectKey: OBJECT_KEY,
        queryObj: {
            versionId: '393832343336313437373734363139393939393952473030312020333061396264',
        },
        authCredentials,
        requestBody: objMD,
    });

    const versions = await s3.listObjectVersions({
        Bucket: BUCKET_NAME,
        Prefix: OBJECT_KEY,
    }).promise();

    console.info({ Versions: versions.Versions });
 }

async function run() {
    try {
        await main();
    } catch (e) {
        console.error('Error running test:', e);
        process.exit(1);
    }
}

run();

Related issues

https://scality.atlassian.net/browse/CLDSRV-632
scality/Arsenal#2490

@DarkIsDude DarkIsDude self-assigned this Aug 25, 2025
@bert-e
Copy link
Contributor

bert-e commented Aug 25, 2025

Hello darkisdude,

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.

Available options
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
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/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.
Available commands
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.

Status report is not available.

@bert-e
Copy link
Contributor

bert-e commented Aug 25, 2025

Incorrect fix version

The Fix Version/s in issue CLDSRV-632 contains:

  • None

Considering where you are trying to merge, I ignored possible hotfix versions and I expected to find:

  • 9.0.23

  • 9.1.0

Please check the Fix Version/s of CLDSRV-632, or the target
branch of this pull request.

@DarkIsDude DarkIsDude force-pushed the feature/CLDSRV-632/put-metadata-edge-cases branch from 4e1523f to 300aaef Compare August 25, 2025 13:08
@DarkIsDude DarkIsDude changed the base branch from development/9.0 to development/9.1 August 25, 2025 13:08
@bert-e
Copy link
Contributor

bert-e commented Aug 25, 2025

Incorrect fix version

The Fix Version/s in issue CLDSRV-632 contains:

  • None

Considering where you are trying to merge, I ignored possible hotfix versions and I expected to find:

  • 9.1.0

Please check the Fix Version/s of CLDSRV-632, or the target
branch of this pull request.

@DarkIsDude DarkIsDude marked this pull request as draft August 25, 2025 13:09
@DarkIsDude DarkIsDude changed the title Feature/cldsrv 632/put metadata edge cases CLDSRV-632 ✨ put metadata edge cases Aug 25, 2025
Copy link

codecov bot commented Aug 25, 2025

Codecov Report

❌ Patch coverage is 92.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.74%. Comparing base (a8d5672) to head (4357e13).

Files with missing lines Patch % Lines
lib/routes/routeBackbeat.js 91.66% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

Files with missing lines Coverage Δ
lib/api/apiUtils/object/createAndStoreObject.js 95.65% <ø> (ø)
lib/api/apiUtils/object/versioning.js 97.10% <100.00%> (+0.01%) ⬆️
lib/routes/routeBackbeat.js 76.33% <91.66%> (+0.53%) ⬆️
@@                 Coverage Diff                 @@
##           development/9.1    #5913      +/-   ##
===================================================
+ Coverage            83.72%   83.74%   +0.01%     
===================================================
  Files                  191      191              
  Lines                12233    12258      +25     
===================================================
+ Hits                 10242    10265      +23     
- Misses                1991     1993       +2     
Flag Coverage Δ
ceph-backend-test 64.64% <92.00%> (+0.07%) ⬆️
file-ft-tests 66.86% <36.00%> (-0.07%) ⬇️
kmip-ft-tests 27.17% <20.00%> (-0.02%) ⬇️
mongo-v0-ft-tests 68.19% <36.00%> (-0.05%) ⬇️
mongo-v1-ft-tests 68.18% <36.00%> (-0.07%) ⬇️
multiple-backend 34.40% <92.00%> (+0.15%) ⬆️
sur-tests 34.76% <20.00%> (-0.89%) ⬇️
sur-tests-inflights 36.68% <20.00%> (-0.07%) ⬇️
unit 68.31% <68.00%> (-0.01%) ⬇️
utapi-v2-tests 33.54% <20.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@DarkIsDude DarkIsDude force-pushed the feature/CLDSRV-632/put-metadata-edge-cases branch 3 times, most recently from 0b5a099 to 9c143cb Compare September 5, 2025 07:57
@bert-e
Copy link
Contributor

bert-e commented Sep 5, 2025

Incorrect fix version

The Fix Version/s in issue CLDSRV-632 contains:

  • None

Considering where you are trying to merge, I ignored possible hotfix versions and I expected to find:

  • 9.1.1

Please check the Fix Version/s of CLDSRV-632, or the target
branch of this pull request.

@bert-e
Copy link
Contributor

bert-e commented Sep 5, 2025

Waiting for approval

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

  • the author

  • 2 peers

@DarkIsDude DarkIsDude force-pushed the feature/CLDSRV-632/put-metadata-edge-cases branch 7 times, most recently from b5c2131 to da6bfb6 Compare September 8, 2025 12:56
@DarkIsDude DarkIsDude force-pushed the feature/CLDSRV-632/put-metadata-edge-cases branch 2 times, most recently from e0ba601 to 1b518c1 Compare September 22, 2025 09:31
@DarkIsDude DarkIsDude marked this pull request as ready for review September 22, 2025 11:52
@DarkIsDude DarkIsDude force-pushed the feature/CLDSRV-632/put-metadata-edge-cases branch from 1b518c1 to dfc7d0d Compare September 22, 2025 11:55
@DarkIsDude DarkIsDude force-pushed the feature/CLDSRV-632/put-metadata-edge-cases branch from bb7c419 to dfc7d0d Compare September 23, 2025 09:13
@DarkIsDude DarkIsDude force-pushed the feature/CLDSRV-632/put-metadata-edge-cases branch 2 times, most recently from 1842539 to d04c92d Compare October 9, 2025 08:57
@DarkIsDude
Copy link
Contributor Author

somehow I am not sure this covers all cases - and not confident everything is fully tests: there are tests, but logic is quite intricate with the different versioning cases + different listings/... and all the flags: isNull, isNull2, ...

→ would be worth reviewing the tests to see if we miss some cases (e.g. all the combinations of versioning disabled/enabled/suspended, creating vs updating an object, with previous version/nullVersion/no previous version...). May help to look at putObject tests for inspiration on the test case? → since we have multiple backend, and the separation is sometimes not clear (or not clearly documented), we should make sure this test is run both with mongo and metadata backends: is it the case?

So I took time to review tests and path. With the condition if (versioning && !objMd) there is not a lot of path that can jump in that code. So I added some tests but I didn't find more of them. Keep in mind that I don't have an overview of all features / options

@DarkIsDude DarkIsDude force-pushed the feature/CLDSRV-632/put-metadata-edge-cases branch from 7e6053c to 69b41b7 Compare October 10, 2025 12:04
@DarkIsDude DarkIsDude force-pushed the feature/CLDSRV-632/put-metadata-edge-cases branch from cae1858 to 4357e13 Compare October 10, 2025 12:57
Comment on lines -522 to -527
// if the target does not exist, return an error to
// backbeat, who will have to retry the operation as a
// complete replication
return callback(errors.ObjNotFound);
}
// use original data locations and encryption info
Copy link
Contributor

Choose a reason for hiding this comment

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

these comments do not seem useless at all, they should be kept

// To prevent this, the versionId field is only included in options when it is defined.
if (versionId !== undefined) {
options.versionId = versionId;
omVal.versionId = versionId;
Copy link
Contributor

Choose a reason for hiding this comment

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

did you perform such second review? (you can resolve this comment once you confirmed)

let masterMD;

try {
masterMD = await metadata.getObjectMDPromised(bucketName, objectKey, {}, log);
Copy link
Contributor

Choose a reason for hiding this comment

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

this introduces an extra metadata IO in case of insert (first one was in standardMetadataValidateBucketAndObj, which returned -or not- the version document) : this extra I/O can be skipped if we know the expected semantics of the putMetadata call (i.e. is it an insertion → can directly lookup the "master" in standardMetadataValidateBucketAndObj ; or an update → must lookup the version) -OR- update standardMetadataValidateBucketAndObj (and everything below) to be able to return both the version and/or master...

➡ maybe acceptable to keep the extra I/O for now, but we should create a ticket for tracking this, or make a note in https://scality.atlassian.net/browse/ARTESCA-8449

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants