Skip to content

Add CRR Cascaded capabilities#6179

Open
SylvainSenechal wants to merge 3 commits into
development/9.4from
improvement/CLDSRV-897
Open

Add CRR Cascaded capabilities#6179
SylvainSenechal wants to merge 3 commits into
development/9.4from
improvement/CLDSRV-897

Conversation

@SylvainSenechal

@SylvainSenechal SylvainSenechal commented May 29, 2026

Copy link
Copy Markdown
Contributor

ISSUE : CLDSRV-897

Crr cascaded design : https://github.com/scality/citadel/pull/349

Related PRs :
Arsenal : scality/Arsenal#2628
CloudserverClient : scality/cloudserverclient#24
Backbeat : scality/backbeat#2747
S3utils : scality/s3utils#395

@bert-e

bert-e commented May 29, 2026

Copy link
Copy Markdown
Contributor

Hello sylvainsenechal,

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.

/**
* @param {Object} objectMD - plain object metadata (not an ObjectMD instance)
*/
function prepareMetadataForCascadedCrr(objectMD) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Could be in arsenal 🤔

@SylvainSenechal SylvainSenechal Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

no i think its fine here (talking to myself)

Comment thread lib/routes/routeBackbeat.js Outdated
bucketName: request.bucketName,
objectKey: request.objectKey,
});
return callback(errors.OperationAborted);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

many 409 available in arsenal. we can pick this one, maybe some other ones, or create our own

// Accept both the legacy status='REPLICA' and the new isReplica flag
// so that cascade hops (which arrive with status='PENDING') are also
// recognised as replicas.
const isReplica = omVal.replicationInfo.isReplica === true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should probably use the new helper function from arsenal : getReplicationIsReplica ?

Comment thread package.json Outdated
"@hapi/joi": "^17.1.1",
"@smithy/node-http-handler": "^3.0.0",
"arsenal": "git+https://github.com/scality/Arsenal#8.4.2",
"arsenal": "file:../Arsenal",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

arsenal and @scality/cloudserverclient are pointing to local filesystem paths (file:../Arsenal, file:../cloudserverclient) instead of pinned git tags. This will break installs for anyone who does not have these directories locally, and CI will fail to resolve them.

Suggested change
"arsenal": "file:../Arsenal",
"arsenal": "git+https://github.com/scality/Arsenal#8.4.2",

— Claude Code

Comment thread package.json Outdated
"devDependencies": {
"@eslint/compat": "^1.2.2",
"@scality/cloudserverclient": "1.0.7",
"@scality/cloudserverclient": "file:../cloudserverclient",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same issue: file:../cloudserverclient should be pinned to a published version or git tag.

— Claude Code

Comment thread tests/functional/backbeat/crrCascade.js Outdated
});

describe('CRR cascade — putMetadata', () => {
it('second write with the same microVersionId returns loop-detected', async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test names using it() should start with should per project conventions.

— Claude Code

}],
},
}));
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing after() hook to clean up the test buckets (TEST_BUCKET, TEST_BUCKET_CRR, DEST_BUCKET). Without cleanup, leftover buckets accumulate across test runs and can cause name collisions or resource leaks.

— Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

bucket/keys names are randomized
I intentionnally didnt add any after to keep the test and file lighter.

Do we want to add after cleanup ?

@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown
  • Dependency pinning: arsenal and @scality/cloudserverclient in package.json use file: paths pointing to local directories. These must be pinned to git tags or published versions before merge.
    • arsenal on line 36: file:../Arsenal → should be a git tag (e.g. 8.4.2)
    • @scality/cloudserverclient on line 68: file:../cloudserverclient → should be a versioned reference
  • Test naming: it() descriptions in tests/functional/backbeat/crrCascade.js should start with should per project conventions.
  • Test cleanup: No after() hook to delete the three buckets created in before(). Leftover buckets will accumulate across test runs.

Review by Claude Code

if (nextReplInfo && nextReplInfo.backends.length > 0) {
omVal.replicationInfo = nextReplInfo;
} else {
omVal.replicationInfo = {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

maybe this should be an arsenal function as its updating metadata

Comment thread lib/routes/routeBackbeat.js Outdated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

all these worked locally, need to wait for the bumps now

Comment thread package.json Outdated
"@hapi/joi": "^17.1.1",
"@smithy/node-http-handler": "^3.0.0",
"arsenal": "git+https://github.com/scality/Arsenal#8.4.2",
"arsenal": "file:../Arsenal",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TODO 🧐

@SylvainSenechal SylvainSenechal changed the title Improvement/cldsrv 897 Add CRR Cascaded capabilities May 29, 2026
@SylvainSenechal SylvainSenechal requested review from a team, delthas and maeldonn May 29, 2026 18:11
@SylvainSenechal SylvainSenechal force-pushed the improvement/CLDSRV-897 branch from c15fb2b to 7912480 Compare May 29, 2026 18:38
@codecov

codecov Bot commented May 29, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
3684 1 3683 0
View the full list of 1 ❄️ flaky test(s)
"after each" hook for "should fail if trying to overwrite a delete marker"::MPU with x-scal-s3-version-id header With default signature "after each" hook for "should fail if trying to overwrite a delete marker"

Flake rate in main: 100.00% (Passed 0 times, Failed 11 times)

Stack Traces | 0.026s run time
We encountered an internal error. Please try again.

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Comment thread tests/functional/backbeat/crrCascade.js Outdated
});

describe('CRR cascade — putMetadata', () => {
it('second write with the same microVersionId returns loop-detected', async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test names should start with "should" per project conventions (e.g. should return loop-detected on second write with the same microVersionId).

— Claude Code

Comment thread package.json Outdated
"@hapi/joi": "^17.1.1",
"@smithy/node-http-handler": "^3.0.0",
"arsenal": "git+https://github.com/scality/Arsenal#8.4.2",
"arsenal": "file:../Arsenal",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

arsenal and @scality/cloudserverclient use file: local path references. These must be pinned to a tag or version before merging — file: references will break any install outside of your local workstation.

— Claude Code

@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown
  • package.json: arsenal and @scality/cloudserverclient use file: local path references instead of pinned tags/versions — these will break installs for anyone else and must be updated before merge
    - Pin arsenal to the Arsenal PR tag once merged (was 8.4.2)
    - Pin @scality/cloudserverclient to a published version (was 1.0.7)
    - tests/functional/backbeat/crrCascade.js: test names in it() blocks should start with should

    Review by Claude Code

Comment thread lib/routes/routeBackbeat.js Outdated
}

const encodedMicroVersionId = request.headers['x-scal-micro-version-id'];
const incomingRaw = encodedMicroVersionId ? decodeMicroVersionId(encodedMicroVersionId) : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The decode error handling here differs from putMetadata (line 574-575), which normalises the result immediately so incomingRaw is guaranteed non-Error. Here incomingRaw can hold an Error object, then is checked later with !(incomingRaw instanceof Error). Consider aligning with the putMetadata pattern for consistency.

— Claude Code

Comment thread tests/functional/backbeat/crrCascade.js
Comment thread package.json Outdated
"@hapi/joi": "^17.1.1",
"@smithy/node-http-handler": "^3.0.0",
"arsenal": "git+https://github.com/scality/Arsenal#8.4.2",
"arsenal": "file:../Arsenal",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

arsenal and @scality/cloudserverclient point to local file:../ paths. These must be pinned to git tags before merging — anyone cloning this repo won't have these sibling directories.

— Claude Code

Comment thread lib/utilities/collectResponseHeaders.js Outdated
@claude

claude Bot commented Jun 1, 2026

Copy link
Copy Markdown
  • package.json: arsenal and @scality/cloudserverclient point to local file:../ paths — must be pinned to git tags before merging.
    - lib/utilities/collectResponseHeaders.js:103: Cascade replicas without a next hop have status: '' and isReplica: true, but the outer if only checks status (falsy for empty string), so x-amz-replication-status: REPLICA header will be missing from S3 responses for these objects.
    - lib/routes/routeBackbeat.js:438: putData and putMetadata handle decodeMicroVersionId error cases differently — consider aligning for consistency.
    - tests/functional/backbeat/crrCascade.js: Missing after() hook to clean up the three test buckets created in before().

    Review by Claude Code

@SylvainSenechal SylvainSenechal force-pushed the improvement/CLDSRV-897 branch from 464e7f2 to a0f67b2 Compare June 5, 2026 09:47

@maeldonn maeldonn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we can remove the changes from this PR that are already covered in the other one. Once that's done, we can rebase this branch onto the other PR (since Prettier is already included there).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remove this file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah for testing i shipped cloudserverclient with it

Comment thread Dockerfile
node-gyp \
typescript@4.9.5
COPY package.json yarn.lock /usr/src/app/
COPY package.json yarn.lock scality-cloudserverclient-v1.0.9.tgz /usr/src/app/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Don't forget to revert, I guess it's for testing

return callback(errors.OperationAborted);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You need to use the helper writeContinue() to allow the client to streamData.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok let me see I thought this was somewhat automatic

@scality scality deleted a comment from bert-e Jun 8, 2026
@SylvainSenechal SylvainSenechal force-pushed the improvement/CLDSRV-897 branch 2 times, most recently from 3969d0e to daef2e1 Compare June 9, 2026 20:13
@bert-e

bert-e commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

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

  • the author

  • 2 peers

The following reviewers are expecting changes from the author, or must review again:

@scality scality deleted a comment from bert-e Jun 9, 2026
@SylvainSenechal SylvainSenechal requested a review from maeldonn June 9, 2026 20:13
Comment thread package.json Outdated
Comment thread package.json Outdated
"@opentelemetry/instrumentation-mongodb": "~0.69.0",
"@smithy/node-http-handler": "^3.0.0",
"arsenal": "git+https://github.com/scality/Arsenal#8.4.5",
"arsenal": "git+https://github.com/scality/Arsenal#2c429ab35a5ac82c3dafa5a0296a49a23a9c8a4a",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Arsenal is pinned to a raw commit hash instead of a version tag. Per project conventions, git-based dependencies (arsenal, vaultclient, bucketclient, werelogs, utapi, scubaclient) should pin to a tag.

— Claude Code

Comment thread tests/unit/routes/routeBackbeat.js Outdated
Comment thread lib/routes/routeBackbeat.js Outdated
Comment thread lib/routes/routeBackbeat.js
@claude

claude Bot commented Jun 9, 2026

Copy link
Copy Markdown
  • Production code requires a devDependency: lib/routes/routeBackbeat.js imports VersionIdCollisionException and StaleMicroVersionIdException from @scality/cloudserverclient, which is in devDependencies. With yarn install --production (Dockerfile), this module won't be installed and the server will crash on startup.
    • Move @scality/cloudserverclient to dependencies
  • Local filesystem path in package.json: @scality/cloudserverclient points to /Users/sylvainsenechal/Desktop/cloudserverclient/cloudserverclient.tgz, which will break on any other machine or in CI.
    • Publish the package to a registry or use a proper versioned reference
  • Binary tarball committed to repo: scality-cloudserverclient-v1.0.9.tgz should not be checked into git.
    • Remove the tarball and reference the package from a registry
  • Arsenal pinned to commit hash: arsenal is pinned to a raw commit hash instead of a version tag, violating project conventions for git-based dependencies.
    • Pin to a tagged version
  • Unit test asserts wrong status code: tests/unit/routes/routeBackbeat.js:212 asserts statusCode === 200 but the code under test calls _respondWithHeaders(..., 409). The writeHead spy sets mockResponse.statusCode from its first arg, so this test will fail.
    • Fix assertions to expect 409 and the JSON error payload
  • Trailing whitespace: lib/routes/routeBackbeat.js:443

    Review by Claude Code

@SylvainSenechal SylvainSenechal force-pushed the improvement/CLDSRV-897 branch from daef2e1 to 88edec5 Compare June 9, 2026 21:07
Comment thread package.json Outdated
"@opentelemetry/instrumentation-mongodb": "~0.69.0",
"@smithy/node-http-handler": "^3.0.0",
"arsenal": "git+https://github.com/scality/Arsenal#8.4.5",
"arsenal": "git+https://github.com/scality/Arsenal#2c429ab35a5ac82c3dafa5a0296a49a23a9c8a4a",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

arsenal is pinned to a commit hash (2c429ab) instead of a tag. Per project conventions, git-based deps must pin to a tag, not a branch or commit.

— Claude Code

Comment thread package.json Outdated
"vaultclient": "scality/vaultclient#8.5.3",
"werelogs": "scality/werelogs#8.2.2",
"ws": "^8.18.0",
"@scality/cloudserverclient": "/Users/sylvainsenechal/Desktop/cloudserverclient/cloudserverclient.tgz",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@scality/cloudserverclient points to a local filesystem path (/Users/sylvainsenechal/Desktop/...). This will break for any other developer and in CI. It should be a registry version or a git-based dependency with a tag.

Also, this was moved from devDependencies to dependencies — since it is now imported in production code (lib/routes/routeBackbeat.js), confirm this is intentional and the package should indeed be a production dependency.

— Claude Code

Comment thread Dockerfile
node-gyp \
typescript@4.9.5
COPY package.json yarn.lock /usr/src/app/
COPY package.json yarn.lock scality-cloudserverclient-v1.0.9.tgz /usr/src/app/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Binary .tgz file checked into the repository. This should not be committed — use a proper npm registry reference or git-based dependency instead.

— Claude Code

Comment thread yarn.lock
fast-xml-parser "^5.5.7"

"@scality/cloudserverclient@file:../cloudserverclient/cloudserverclient.tgz":
version "1.0.9"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The yarn.lock contains a resolved path to a local filesystem (/Users/sylvainsenechal/Desktop/...) which will not resolve for other developers or CI. This needs to be updated to use a proper registry or git reference.

— Claude Code

const obj = Object.assign({
'content-length': Buffer.byteLength(OBJECT_BODY),
'content-type': 'text/plain',
'last-modified': new Date().toISOString(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test names using it() should start with "should" per project conventions. For example: "should return loop-detected on second write with the same microVersionId".

This applies to all it() tests in this file.

— Claude Code

@@ -174,6 +175,47 @@ describe('routeBackbeat', () => {
assert.deepStrictEqual(mockResponse.body, [{}]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test name should start with "should" per project conventions.

— Claude Code

return _respondWithHeaders(response,
{ code: VersionIdCollisionException.name, message: 'version id already at destination' },
{ 'x-scal-micro-version-id': objMd.microVersionId ? encode(objMd.microVersionId) : '' },
log, callback, 409);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

putData returns a 409 response before calling writeContinue. If the client sent Expect: 100-continue with a body, returning 409 before consuming (or explicitly rejecting) the request stream may leave the connection in a broken state — the client could be waiting for 100-continue and then start sending data that nobody reads. Consider either calling request.destroy() or request.resume() before responding, or calling writeContinue and consuming the body before checking.

— Claude Code

@claude

claude Bot commented Jun 9, 2026

Copy link
Copy Markdown
  • Dependency pinning: arsenal is pinned to a commit hash instead of a tag, violating project conventions for git-based deps.
    - Local filesystem path in package.json: @scality/cloudserverclient points to /Users/sylvainsenechal/Desktop/..., which will break for other developers and CI. It was also moved from devDependencies to dependencies — verify this is intentional.
    - Binary .tgz committed: scality-cloudserverclient-v1.0.9.tgz is checked into the repo and referenced in the Dockerfile. Use a proper registry or git reference instead.
    - yarn.lock local path: The lockfile contains resolved paths to a local filesystem, which won't resolve elsewhere.
    - Stream handling in putData: The 409 early return for VersionIdCollision happens before writeContinue is called. If the client uses Expect: 100-continue, this may leave the connection in a bad state. Consider destroying or resuming the request stream.
    - Test naming: it() test descriptions should start with "should" per project conventions (both in crrCascade.js and routeBackbeat.js).

    Review by Claude Code

@SylvainSenechal SylvainSenechal force-pushed the improvement/CLDSRV-897 branch from 88edec5 to a90d5b2 Compare June 10, 2026 08:39
Comment thread package.json
"@azure/storage-blob": "^12.28.0",
"@hapi/joi": "^17.1.1",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/instrumentation-http": "~0.218.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Arsenal is pinned to a commit hash instead of a tag. Per project conventions, git-based dependencies (arsenal, vaultclient, bucketclient, werelogs, utapi, scubaclient) must pin to a tag, not a branch or commit hash.

suggestion<br> "arsenal": "git+https://github.com/scality/Arsenal#8.4.5",<br>

— Claude Code

Comment thread package.json
"vaultclient": "scality/vaultclient#8.5.3",
"werelogs": "scality/werelogs#8.2.2",
"ws": "^8.18.0",
"@scality/cloudserverclient": "./scality-cloudserverclient-v1.0.9.tgz",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@scality/cloudserverclient is vendored as a binary tarball (scality-cloudserverclient-v1.0.9.tgz) committed to the repo. Binary artifacts should be published to a package registry and referenced by version. This inflates the repo size and makes auditing the dependency harder.

— Claude Code

@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown
  • Dependency pinning: arsenal is pinned to a commit hash (83682d1...) instead of a tag. Project conventions require git-based deps to pin to a tag.
    • Pin to a released tag (e.g. 8.4.5) or create a new tag for this commit.
  • Vendored binary tarball: scality-cloudserverclient-v1.0.9.tgz is committed as a binary artifact and referenced via a local path in package.json. Binary tarballs should be published to a package registry rather than checked into the repo.
    • Publish @scality/cloudserverclient@1.0.9 to npm or GitHub Packages and reference it by version.

Review by Claude Code

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