Skip to content

Allow enabling versioning on buckets with a GCP location constraint - #6266

Draft
maeldonn wants to merge 2 commits into
development/9.5from
improvement/CLDSRV-984
Draft

Allow enabling versioning on buckets with a GCP location constraint#6266
maeldonn wants to merge 2 commits into
development/9.5from
improvement/CLDSRV-984

Conversation

@maeldonn

Copy link
Copy Markdown
Contributor

Not handled yet

Gap Impact What it would take
S3 has a suspended versioning state, with null version ids. GCS versioning is only on or off A GCP location cannot represent a suspended bucket Decide whether to reject the configuration or emulate the state
Object versioning on the GCS bucket is a hard requirement: without it, the delete issued behind a delete marker destroys the data instead of archiving it Silent data loss Block writes on a non-versioned bucket, instead of only reporting it in the healthcheck
GCS lifecycle rules on noncurrent versions (isLive: false, daysSinceNoncurrentTime, numNewerVersions) can reap generations that S3 still serves as current Deferred and silent data loss, with no error at write time Document the precondition, and consider detecting such rules at configuration time
GCS never promotes a noncurrent generation, so the backend state diverges from the S3 view after an undelete or a version delete Confusing during support, and the root cause of the lifecycle exposure above Optional: promote by copy, accepting the new generation, the non-atomicity and the storage class reset

@bert-e

bert-e commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Hello maeldonn,

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
/bypass_source_branch_lineage Bypass the cross-branch contamination check
/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.
/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

bert-e commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Incorrect fix version

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

  • None

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

  • 9.4.3

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

Comment thread package.json
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

❌ 13 Tests Failed:

Tests completed Failed Passed Skipped
9911 13 9898 0
View the full list of 13 ❄️ flaky test(s)
"after each" hook for "should batch delete 1000 objects"::Multi-Object Delete Success "after each" hook for "should batch delete 1000 objects"

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

Stack Traces | 3.53s run time
The bucket you tried to delete is not empty.
"before each" hook for "should batch delete 1000 objects"::Multi-Object Delete Success "before each" hook for "should batch delete 1000 objects"

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

Stack Traces | 5.37s run time
Socket timed out without establishing a connection within 5000 ms
"before each" hook for "should grant read access"::With default signature PUT Bucket ACL using predefined groups - authenticated request "before each" hook for "should grant read access"

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

Stack Traces | 0.162s run time
We encountered an internal error. Please try again.
"before each" hook for "should grant read access"::With default signature PUT Bucket ACL using predefined groups - unauthenticated request "before each" hook for "should grant read access"

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

Stack Traces | 0.283s run time
We encountered an internal error. Please try again.
"before each" hook for "should grant read access"::With default signature PUT Object ACL using predefined groups - authenticated request "before each" hook for "should grant read access"

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

Stack Traces | 0.088s run time
We encountered an internal error. Please try again.
"before each" hook for "should grant read access"::With default signature PUT Object ACL using predefined groups - unauthenticated request "before each" hook for "should grant read access"

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

Stack Traces | 0.188s run time
We encountered an internal error. Please try again.
"before each" hook for "should grant read access"::With v4 signature PUT Bucket ACL using predefined groups - authenticated request "before each" hook for "should grant read access"

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

Stack Traces | 0.074s run time
We encountered an internal error. Please try again.
"before each" hook for "should grant read access"::With v4 signature PUT Bucket ACL using predefined groups - unauthenticated request "before each" hook for "should grant read access"

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

Stack Traces | 0.2s run time
We encountered an internal error. Please try again.
"before each" hook for "should grant read access"::With v4 signature PUT Object ACL using predefined groups - authenticated request "before each" hook for "should grant read access"

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

Stack Traces | 0.193s run time
We encountered an internal error. Please try again.
"before each" hook for "should grant read access"::With v4 signature PUT Object ACL using predefined groups - unauthenticated request "before each" hook for "should grant read access"

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

Stack Traces | 0.11s run time
We encountered an internal error. Please try again.
should allow action on an object satisfying the wildcard in the policy::With default signature Bucket policies with resource statement should allow action on an object satisfying the wildcard in the policy

Flake rate in main: 13.81% (Passed 668 times, Failed 107 times)

Stack Traces | 40s run time
Timeout of 40000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (.../test/bucket/bucketPolicyWithResourceStatements.js)
should allow action on an object specified in the policy::With default signature Bucket policies with resource statement should allow action on an object specified in the policy

Flake rate in main: 13.81% (Passed 668 times, Failed 107 times)

Stack Traces | 40s run time
Timeout of 40000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (.../test/bucket/bucketPolicyWithResourceStatements.js)
should deny action on an object specified in the policy::With default signature Bucket policies with resource statement should deny action on an object specified in the policy

Flake rate in main: 13.81% (Passed 668 times, Failed 107 times)

Stack Traces | 40s run time
Timeout of 40000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (.../test/bucket/bucketPolicyWithResourceStatements.js)

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

@bert-e

bert-e commented Aug 31, 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

@maeldonn
maeldonn force-pushed the improvement/CLDSRV-984 branch from 860728b to 9c78d85 Compare August 31, 2026 14:22
Comment thread lib/api/bucketPutVersioning.js Dismissed
Comment thread lib/api/bucketPutVersioning.js Dismissed
Comment thread lib/api/bucketPutVersioning.js Dismissed
Comment thread lib/api/bucketPutVersioning.js Dismissed
@maeldonn
maeldonn force-pushed the improvement/CLDSRV-984 branch from 9c78d85 to 5749a75 Compare August 31, 2026 14:29

@francoisferrand francoisferrand 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.

S3 has a suspended versioning state, with nullversion ids. GCS versioning is only on or off

Suspended versioning is really just a name : in our implementation it differs (so in particular performance may not be the same), but functionally suspended is about the same as off?
i.e. is suspended not just a different name for no versioning (no versioning → versioning ←→ suspended versioning), so we could map both no & suspended versioning on GCP's disabled versioning?

Object versioning on the GCS bucket is a hard requirement: without it, the delete issued behind a delete marker destroys the data instead of archiving it

  • not sure what you mean, there is no "archiving" in S3...
  • if using GCP versioning is not practical, another option would be to "flatten" the versions: i.e. store each version side-by-side, as <objKey>.<scalVersionId>

Block writes on a non-versioned bucket, instead of only reporting it in the healthcheck

If there a GCP "extension" to make the putObject/deleteObject conditional on versioning? Otherwise this may be very costly....

GCS lifecycle rules on noncurrent versions (isLive: false, daysSinceNoncurrentTime, numNewerVersions) can reap generations that S3 still serves as current

  • Should be part of the documentation, and may be verified when creating location and/or enabling versioning (though none of these are foolproof, and we still risk a data loss)
  • Or a reason to switch to "emulating" versions by flattening them....

GCS never promotes a noncurrent generation, so the backend state diverges from the S3 view after an undelete or a version delete

That is not really a problem for us, right: The issue is more about what the user would see in GCP's bucket?

All in all, I don't think we should merge this -and esp. in 9.4- but take the time to make a (short) design in Citadel : so we can align on the requirements and limits of the solution (i.e. do we want to keep the same "view" on GCP, or is this really just data storage, accessed only through the product ? what to do with GCP-side lifecycle rules ? ...)

Comment thread lib/api/apiUtils/object/createAndStoreObject.js Outdated
Comment thread tests/unit/api/bucketPutVersioning.js
Comment thread tests/unit/multipleBackend/VersioningBackendClient.js Outdated
Comment thread lib/api/bucketPutVersioning.js
@maeldonn
maeldonn changed the base branch from development/9.4 to development/9.5 September 1, 2026 15:00
@bert-e

bert-e commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Incorrect fix version

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

  • 9.4.3

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

  • 9.5.0

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

@maeldonn
maeldonn force-pushed the improvement/CLDSRV-984 branch from 5749a75 to 58e07b2 Compare September 1, 2026 15:08
@bert-e

bert-e commented Sep 1, 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

Comment thread lib/api/bucketPutVersioning.js Outdated
error: errors.NotImplemented,
});
const error =
errorInstances.NotImplemented.customizeDescription(externalVersioningErrorMessage);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

_checkBackendVersioningImplemented can now reject a GCP location (when supportsVersioning is false/undefined) at the second check. Before this PR, GCP was caught by the versioningNotImplBackends lookup and never reached the supportsVersioning path. Now it can, and this error message says "Azure" — confusing for a user operating on a GCP bucket.

Consider making the message generic when the rejection comes from the supportsVersioning check (e.g. return the backend type from _checkBackendVersioningImplemented and build the message accordingly), or use a backend-agnostic message like "Versioning is not supported for this location constraint."

Arsenal now supports versioning on the GCP backend, so drop gcp from
versioningNotImplBackends and stop naming it in the error messages.

Issue: CLDSRV-984
@maeldonn
maeldonn force-pushed the improvement/CLDSRV-984 branch from 58e07b2 to 39b4b41 Compare September 2, 2026 12:16

const externalVersioningErrorMessage = 'We do not currently support putting ' +
'a versioned object to a location-constraint of type Azure or GCP.';
const { locationVersioningErrorMessage } = require('../../../constants');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

require() should be grouped with the other imports at the top of the file (lines 1–10), not placed after constant declarations.

Suggested change
const { locationVersioningErrorMessage } = require('../../../constants');
const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../helpers');
const metadata = require('../../../lib/metadata/wrapper');
const { locationVersioningErrorMessage } = require('../../../constants');

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.

4 participants