-
Notifications
You must be signed in to change notification settings - Fork 255
Bump microVersionId and add isReplica handling #6178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development/9.4
Are you sure you want to change the base?
Changes from all commits
4ac6bc5
19bc4bf
b5e712c
3b4110b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| const { versioning } = require('arsenal'); | ||
| const { config } = require('../../../Config'); | ||
|
|
||
| /** | ||
| * Bump objectMD.microVersionId. microVersionId is a generic | ||
| * metadata-revision marker, not a CRR-specific field, but cascaded CRR | ||
| * is its only consumer today - so we gate on replicationInfo to avoid | ||
| * inflating storage on objects that wouldn't use it. The gate can be | ||
| * widened later if another consumer needs it on every object. | ||
| * Pass `force = true` to bump unconditionally. | ||
| * | ||
| * @param {object} objectMD - object MD POJO or `md.getValue()` | ||
| * @param {boolean} [force] - bump even without replicationInfo | ||
| * @return {undefined} | ||
| */ | ||
| function bumpMicroVersionId(objectMD, force) { | ||
| if (!force && !objectMD?.replicationInfo) { | ||
| return; | ||
| } | ||
|
|
||
| const { instanceId, replicationGroupId } = config; | ||
|
|
||
| // eslint-disable-next-line no-param-reassign | ||
| objectMD.microVersionId = versioning.VersionID.generateVersionId(instanceId, replicationGroupId); | ||
| } | ||
|
|
||
| module.exports = bumpMicroVersionId; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ const monitoring = require('../utilities/monitoringHandler'); | |||||||||||||||||||||||||||||||||||||||||||||||||||
| const collectCorsHeaders = require('../utilities/collectCorsHeaders'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const metadata = require('../metadata/wrapper'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const getReplicationInfo = require('./apiUtils/object/getReplicationInfo'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const bumpMicroVersionId = require('./apiUtils/object/bumpMicroVersionId'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = require('../data/wrapper'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { config } = require('../Config'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const REPLICATION_ACTION = 'DELETE_TAGGING'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -76,13 +77,18 @@ function objectDeleteTagging(authInfo, request, log, callback) { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| // eslint-disable-next-line no-param-reassign | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| objectMD.tags = {}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const params = getVersionSpecificMetadataOptions(objectMD, config.nullVersionCompatMode); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (objectMD.replicationInfo?.isReplica) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // eslint-disable-next-line no-param-reassign | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| objectMD.replicationInfo.isReplica = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const replicationInfo = getReplicationInfo(config, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| objectKey, bucket, true, 0, REPLICATION_ACTION, objectMD); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (replicationInfo) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // eslint-disable-next-line no-param-reassign | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| objectMD.replicationInfo = Object.assign({}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| objectMD.replicationInfo, replicationInfo); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| bumpMicroVersionId(objectMD); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+80
to
+91
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (same in all files...) |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // eslint-disable-next-line no-param-reassign | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| objectMD.originOp = 's3:ObjectTagging:Delete'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata.putObjectMD(bucket.getName(), objectKey, objectMD, params, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders'); | |
| const { decodeVersionId, getVersionIdResHeader, getVersionSpecificMetadataOptions } = | ||
| require('./apiUtils/object/versioning'); | ||
| const getReplicationInfo = require('./apiUtils/object/getReplicationInfo'); | ||
| const bumpMicroVersionId = require('./apiUtils/object/bumpMicroVersionId'); | ||
| const metadata = require('../metadata/wrapper'); | ||
| const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils'); | ||
| const { pushMetric } = require('../utapi/utilities'); | ||
|
|
@@ -87,13 +88,18 @@ function objectPutLegalHold(authInfo, request, log, callback) { | |
| // eslint-disable-next-line no-param-reassign | ||
| objectMD.legalHold = legalHold; | ||
| const params = getVersionSpecificMetadataOptions(objectMD, config.nullVersionCompatMode); | ||
| if (objectMD.replicationInfo?.isReplica) { | ||
| // eslint-disable-next-line no-param-reassign | ||
| objectMD.replicationInfo.isReplica = false; | ||
| } | ||
|
Comment on lines
+91
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this not be handled in |
||
| const replicationInfo = getReplicationInfo(config, | ||
| objectKey, bucket, true, 0, REPLICATION_ACTION, objectMD); | ||
| if (replicationInfo) { | ||
| // eslint-disable-next-line no-param-reassign | ||
| objectMD.replicationInfo = Object.assign({}, | ||
| objectMD.replicationInfo, replicationInfo); | ||
| } | ||
| bumpMicroVersionId(objectMD); | ||
| // eslint-disable-next-line no-param-reassign | ||
| objectMD.originOp = 's3:ObjectLegalHold:Put'; | ||
| metadata.putObjectMD(bucket.getName(), objectKey, objectMD, params, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ const metadata = require('./metadata/wrapper'); | |
| const { setObjectLockInformation } | ||
| = require('./api/apiUtils/object/objectLockHelpers'); | ||
| const removeAWSChunked = require('./api/apiUtils/object/removeAWSChunked'); | ||
| const bumpMicroVersionId = require('./api/apiUtils/object/bumpMicroVersionId'); | ||
| const { parseTagFromQuery } = s3middleware.tagging; | ||
|
|
||
| const usersBucket = constants.usersBucket; | ||
|
|
@@ -190,9 +191,7 @@ const services = { | |
| options.replayId = uploadId; | ||
| } | ||
| // update microVersionId when overwriting metadata. | ||
| if (updateMicroVersionId) { | ||
| md.updateMicroVersionId(); | ||
| } | ||
| bumpMicroVersionId(md.getValue(), updateMicroVersionId); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| // update restore | ||
| if (archive) { | ||
| md.setAmzStorageClass(amzStorageClass); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,7 +33,7 @@ | |
| "@azure/storage-blob": "^12.28.0", | ||
| "@hapi/joi": "^17.1.1", | ||
| "@smithy/node-http-handler": "^3.0.0", | ||
| "arsenal": "git+https://github.com/scality/Arsenal#8.4.2", | ||
| "arsenal": "git+https://github.com/scality/Arsenal#improvement/ARSN-578/micro-version-id", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| "async": "2.6.4", | ||
| "bucketclient": "scality/bucketclient#8.2.7", | ||
| "bufferutil": "^4.0.8", | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||
| const assert = require('assert'); | ||||||
|
|
||||||
| const bumpMicroVersionId = require('../../../../../lib/api/apiUtils/object/bumpMicroVersionId'); | ||||||
|
|
||||||
| describe('bumpMicroVersionId', () => { | ||||||
| it('sets a fresh microVersionId when replicationInfo is present', () => { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test names using
Suggested change
— Claude Code |
||||||
| const objectMD = { replicationInfo: {} }; | ||||||
| bumpMicroVersionId(objectMD); | ||||||
| assert(objectMD.microVersionId, 'expected microVersionId to be set'); | ||||||
| }); | ||||||
|
|
||||||
| it('produces a different value on each call', () => { | ||||||
| const objectMD = { replicationInfo: {} }; | ||||||
| bumpMicroVersionId(objectMD); | ||||||
| const first = objectMD.microVersionId; | ||||||
| bumpMicroVersionId(objectMD); | ||||||
| assert.notStrictEqual(objectMD.microVersionId, first); | ||||||
| }); | ||||||
|
|
||||||
| it('does nothing when replicationInfo is absent', () => { | ||||||
| const objectMD = {}; | ||||||
| bumpMicroVersionId(objectMD); | ||||||
| assert.strictEqual(objectMD.microVersionId, undefined); | ||||||
| }); | ||||||
|
|
||||||
| it('bumps unconditionally when force is true', () => { | ||||||
| const objectMD = {}; | ||||||
| bumpMicroVersionId(objectMD, true); | ||||||
| assert(objectMD.microVersionId, 'expected microVersionId to be set when force=true'); | ||||||
| }); | ||||||
| }); | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||
| const assert = require('assert'); | ||||||
| const async = require('async'); | ||||||
| const crypto = require('crypto'); | ||||||
| const { promisify } = require('util'); | ||||||
|
|
||||||
| const BucketInfo = require('arsenal').models.BucketInfo; | ||||||
|
|
||||||
|
|
@@ -718,3 +719,55 @@ describe('Replication object MD without bucket replication config', () => { | |||||
| }); | ||||||
| }); | ||||||
| }); | ||||||
|
|
||||||
| describe('microVersionId is bumped on every object metadata write', () => { | ||||||
| const getMD = key => metadata.keyMaps.get(bucketName).get(key); | ||||||
| const objectPutAsync = promisify(objectPut); | ||||||
| const objectPutTaggingAsync = promisify(objectPutTagging); | ||||||
|
|
||||||
| beforeEach(() => { | ||||||
| cleanup(); | ||||||
| createBucket(); | ||||||
| }); | ||||||
|
|
||||||
| afterEach(() => cleanup()); | ||||||
|
|
||||||
| it('sets microVersionId on objectPut', async () => { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test names using
Suggested change
— Claude Code |
||||||
| await objectPutAsync(authInfo, getObjectPutReq(keyA, true), undefined, log); | ||||||
| assert(getMD(keyA).microVersionId, 'expected microVersionId to be set'); | ||||||
| }); | ||||||
|
|
||||||
| it('bumps microVersionId on subsequent objectPutTagging', async () => { | ||||||
| await objectPutAsync(authInfo, getObjectPutReq(keyA, true), undefined, log); | ||||||
| const before = getMD(keyA).microVersionId; | ||||||
| await objectPutTaggingAsync(authInfo, taggingPutReq, log); | ||||||
| const after = getMD(keyA).microVersionId; | ||||||
| assert(after && after !== before, 'expected microVersionId to change after tagging'); | ||||||
| }); | ||||||
| }); | ||||||
|
|
||||||
| describe('isReplica is cleared on direct user writes overwriting a replica', () => { | ||||||
| const getMD = key => metadata.keyMaps.get(bucketName).get(key); | ||||||
| const objectPutAsync = promisify(objectPut); | ||||||
| const objectPutTaggingAsync = promisify(objectPutTagging); | ||||||
|
|
||||||
| beforeEach(() => { | ||||||
| cleanup(); | ||||||
| createBucket(); | ||||||
| }); | ||||||
|
|
||||||
| afterEach(() => cleanup()); | ||||||
|
|
||||||
| it('clears isReplica when prior MD has it true', async () => { | ||||||
| await objectPutAsync(authInfo, getObjectPutReq(keyA, true), undefined, log); | ||||||
| getMD(keyA).replicationInfo.isReplica = true; | ||||||
| await objectPutTaggingAsync(authInfo, taggingPutReq, log); | ||||||
| assert.strictEqual(getMD(keyA).replicationInfo.isReplica, false); | ||||||
| }); | ||||||
|
|
||||||
| it('leaves isReplica untouched when prior MD does not have it', async () => { | ||||||
| await objectPutAsync(authInfo, getObjectPutReq(keyA, true), undefined, log); | ||||||
| await objectPutTaggingAsync(authInfo, taggingPutReq, log); | ||||||
| assert.strictEqual(getMD(keyA).replicationInfo.isReplica, undefined); | ||||||
| }); | ||||||
| }); | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't there a method
ObjectMD.updateMicroVersionId()in arsenal ? should it not be used instead?(if we can't because of POJO, then I wonder if that function was really useful....)