Skip to content

Commit 43736fa

Browse files
committed
Merge remote-tracking branch 'origin/bugfix/ARSN-506' into w/8.2/bugfix/ARSN-506
2 parents b28d6d2 + b0cd656 commit 43736fa

File tree

3 files changed

+73
-8
lines changed

3 files changed

+73
-8
lines changed

lib/storage/metadata/mongoclient/MongoClientInterface.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -843,14 +843,11 @@ class MongoClientInterface {
843843
const masterKey = formatMasterKey(objName, params.vFormat);
844844
// initiating array of operations with version creation
845845
const ops: AnyBulkWriteOperation<ObjectMetastoreDocument>[] = [{
846-
updateOne: {
847-
filter: {
846+
insertOne: {
847+
document: {
848848
_id: versionKey,
849-
},
850-
update: {
851-
$set: { _id: versionKey, value: objVal },
852-
},
853-
upsert: true,
849+
value: objVal,
850+
} as ObjectMetastoreDocument,
854851
},
855852
}];
856853
// filter to get master

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"engines": {
44
"node": ">=20"
55
},
6-
"version": "8.2.24",
6+
"version": "8.2.25",
77
"description": "Common utilities for the S3 project components",
88
"main": "build/index.js",
99
"repository": {

tests/functional/metadata/mongodb/putObject.spec.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
const async = require('async');
22
const assert = require('assert');
3+
const sinon = require('sinon');
34
const werelogs = require('werelogs');
45
const { MongoMemoryReplSet } = require('mongodb-memory-server');
56
const { errors, versioning } = require('../../../../index');
67
const logger = new werelogs.Logger('MongoClientInterface', 'debug', 'debug');
78
const BucketInfo = require('../../../../lib/models/BucketInfo').default;
89
const MetadataWrapper =
910
require('../../../../lib/storage/metadata/MetadataWrapper');
11+
const { VersionID } = require('../../../../lib/versioning');
1012
const { BucketVersioningKeyFormat } = versioning.VersioningConstants;
1113

1214
const IMPL_NAME = 'mongodb';
@@ -128,6 +130,7 @@ describe('MongoClientInterface:metadata.putObjectMD', () => {
128130
});
129131

130132
afterEach(done => {
133+
sinon.restore();
131134
metadata.deleteBucket(BUCKET_NAME, logger, done);
132135
});
133136

@@ -354,6 +357,71 @@ describe('MongoClientInterface:metadata.putObjectMD', () => {
354357
], done);
355358
});
356359

360+
it(`should fail on versionID conflict when putting a new version ${variation.it}`, done => {
361+
const objVal = {
362+
key: OBJECT_NAME,
363+
};
364+
const params = {
365+
versioning: true,
366+
versionId: null,
367+
repairMaster: null,
368+
};
369+
370+
// simulate a versionId collision by always generating the same versionId
371+
const genVID = sinon.stub(VersionID, 'generateUniqueVersionId')
372+
.returns('test-version-id');
373+
374+
async.series([
375+
// We first create a master and a version
376+
next => metadata.putObjectMD(BUCKET_NAME, OBJECT_NAME, objVal, params, logger, next),
377+
// We put another version of the object with the same versionId
378+
next => metadata.putObjectMD(BUCKET_NAME, OBJECT_NAME, objVal, params, logger, next),
379+
], err => {
380+
assert(err.is.InternalError,
381+
`expected InternalError, got ${err.name} with message: ${err.message}`,
382+
);
383+
// make sure the retry triggered on the first collision detection
384+
assert(genVID.calledThrice,
385+
`expected generateUniqueVersionId to be called thrice, got ${genVID.callCount} times`);
386+
done();
387+
});
388+
});
389+
390+
it(`should succeed on version creation after a versionId collision ${variation.it}`, done => {
391+
const objVal = {
392+
key: OBJECT_NAME,
393+
};
394+
const params = {
395+
versioning: true,
396+
versionId: null,
397+
repairMaster: null,
398+
};
399+
400+
// simulate a versionId collision by always generating the same versionId
401+
const genVID = sinon.stub(VersionID, 'generateUniqueVersionId')
402+
.onFirstCall().returns('test-version-id')
403+
.onSecondCall().returns('test-version-id') // trigger collision
404+
.onThirdCall().returns('test-version-id-retry'); // change versionId on retry
405+
406+
async.series([
407+
// We first create a master and a version
408+
next => metadata.putObjectMD(BUCKET_NAME, OBJECT_NAME, objVal, params, logger, next),
409+
// We put another version of the object with the same versionId
410+
next => metadata.putObjectMD(BUCKET_NAME, OBJECT_NAME, objVal, params, logger, next),
411+
], (err, res) => {
412+
assert.ifError(err, `expected no error, got ${err}`);
413+
// make sure the retry triggered on the first collision detection
414+
assert(genVID.calledThrice,
415+
`expected generateUniqueVersionId to be called thrice, got ${genVID.callCount} times`);
416+
// make sure the last call returned a different versionId
417+
const vid1 = JSON.parse(res[0]).versionId;
418+
const vid2 = JSON.parse(res[1]).versionId;
419+
assert.notStrictEqual(vid1, vid2,
420+
`expected different versionIds, got ${vid1} and ${vid2}`);
421+
done();
422+
});
423+
});
424+
357425
itOnlyInV1(`Should delete master when last version is delete marker ${variation.it}`, done => {
358426
const objVal = {
359427
key: OBJECT_NAME,

0 commit comments

Comments
 (0)