diff --git a/constants.js b/constants.js
index 8aab74e040..535a2fb16e 100644
--- a/constants.js
+++ b/constants.js
@@ -153,7 +153,7 @@ const constants = {
// for external backends, don't call unless at least 1 minute
// (60,000 milliseconds) since last call
externalBackendHealthCheckInterval: 60000,
- versioningNotImplBackends: { azure: true, gcp: true },
+ versioningNotImplBackends: { azure: true },
mpuMDStoredExternallyBackend: { aws_s3: true, gcp: true },
skipBatchDeleteBackends: { azure: true, gcp: true },
s3HandledBackends: { azure: true, gcp: true },
diff --git a/lib/api/apiUtils/object/createAndStoreObject.js b/lib/api/apiUtils/object/createAndStoreObject.js
index 82f26ccfc5..eb4f071a4d 100644
--- a/lib/api/apiUtils/object/createAndStoreObject.js
+++ b/lib/api/apiUtils/object/createAndStoreObject.js
@@ -25,7 +25,7 @@ const {
const { externalBackends, versioningNotImplBackends } = constants;
const externalVersioningErrorMessage =
- 'We do not currently support putting a versioned object to a location-constraint of type Azure or GCP.';
+ 'We do not currently support putting a versioned object to a location-constraint of type Azure.';
/**
* Validate and compute the checksum for a zero-size object body.
@@ -322,7 +322,10 @@ function createAndStoreObject(
}
}
- const headerChecksum = areChecksumsEnabled() ? getChecksumDataFromHeaders(request.headers) : null;
+ // A delete marker has no body: an x-amz-checksum-* header
+ // describes the request payload (e.g. a multi-object delete XML).
+ const headerChecksum =
+ isDeleteMarker || !areChecksumsEnabled() ? null : getChecksumDataFromHeaders(request.headers);
if (headerChecksum && headerChecksum.error) {
return next(arsenalErrorFromChecksumError(headerChecksum));
}
diff --git a/lib/api/bucketPutVersioning.js b/lib/api/bucketPutVersioning.js
index 5f872cc0dd..695c469857 100644
--- a/lib/api/bucketPutVersioning.js
+++ b/lib/api/bucketPutVersioning.js
@@ -6,23 +6,23 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const metadata = require('../metadata/wrapper');
const { standardMetadataValidateBucket } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
-const versioningNotImplBackends =
- require('../../constants').versioningNotImplBackends;
+const versioningNotImplBackends = require('../../constants').versioningNotImplBackends;
const { config } = require('../Config');
const monitoring = require('../utilities/monitoringHandler');
-const externalVersioningErrorMessage = 'We do not currently support putting ' +
-'a versioned object to a location-constraint of type Azure or GCP.';
+const externalVersioningErrorMessage =
+ 'We do not currently support putting ' + 'a versioned object to a location-constraint of type Azure.';
-const replicationVersioningErrorMessage = 'A replication configuration is ' +
-'present on this bucket, so you cannot change the versioning state. To ' +
-'change the versioning state, first delete the replication configuration.';
+const replicationVersioningErrorMessage =
+ 'A replication configuration is ' +
+ 'present on this bucket, so you cannot change the versioning state. To ' +
+ 'change the versioning state, first delete the replication configuration.';
-const ingestionVersioningErrorMessage = 'Versioning cannot be suspended for '
-+ 'buckets setup with Out of Band updates from a location';
+const ingestionVersioningErrorMessage =
+ 'Versioning cannot be suspended for ' + 'buckets setup with Out of Band updates from a location';
-const objectLockErrorMessage = 'An Object Lock configuration is present on ' +
- 'this bucket, so the versioning state cannot be changed.';
+const objectLockErrorMessage =
+ 'An Object Lock configuration is present on ' + 'this bucket, so the versioning state cannot be changed.';
/**
* Format of xml request:
@@ -47,21 +47,17 @@ function _parseXML(request, log, cb) {
return cb(errors.MalformedXML);
}
const versioningConf = result.VersioningConfiguration;
- const status = versioningConf.Status ?
- versioningConf.Status[0] : undefined;
- const mfaDelete = versioningConf.MfaDelete ?
- versioningConf.MfaDelete[0] : undefined;
+ const status = versioningConf.Status ? versioningConf.Status[0] : undefined;
+ const mfaDelete = versioningConf.MfaDelete ? versioningConf.MfaDelete[0] : undefined;
const validStatuses = ['Enabled', 'Suspended'];
const validMfaDeletes = [undefined, 'Enabled', 'Disabled'];
- if (validStatuses.indexOf(status) < 0 ||
- validMfaDeletes.indexOf(mfaDelete) < 0) {
+ if (validStatuses.indexOf(status) < 0 || validMfaDeletes.indexOf(mfaDelete) < 0) {
log.debug('illegal versioning configuration');
return cb(errors.IllegalVersioningConfigurationException);
}
if (versioningConf && mfaDelete === 'Enabled') {
log.debug('mfa deletion is not implemented');
- return cb(errorInstances.NotImplemented
- .customizeDescription('MFA Deletion is not supported yet.'));
+ return cb(errorInstances.NotImplemented.customizeDescription('MFA Deletion is not supported yet.'));
}
return process.nextTick(() => cb(null));
});
@@ -103,90 +99,89 @@ function bucketPutVersioning(authInfo, request, log, callback) {
requestType: request.apiMethods || 'bucketPutVersioning',
request,
};
- return waterfall([
- next => _parseXML(request, log, next),
- next => standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
- (err, bucket) => next(err, bucket)), // ignore extra null object,
- (bucket, next) => parseString(request.post, (err, result) => {
- // just for linting; there should not be any parsing error here
- if (err) {
- return next(err, bucket);
- }
- // prevent enabling versioning on an nfs exported bucket
- if (bucket.isNFS()) {
- const error = new Error();
- error.code = 'NFSBUCKET';
- return next(error);
- }
- // _checkBackendVersioningImplemented returns false if versioning
- // is not implemented on the bucket backend
- if (!_checkBackendVersioningImplemented(bucket)) {
- log.debug(externalVersioningErrorMessage,
- { method: 'bucketPutVersioning',
- error: errors.NotImplemented });
- const error = errorInstances.NotImplemented.customizeDescription(
- externalVersioningErrorMessage);
- return next(error, bucket);
- }
- const versioningConfiguration = {};
- if (result.VersioningConfiguration.Status) {
- versioningConfiguration.Status =
- result.VersioningConfiguration.Status[0];
- }
- if (result.VersioningConfiguration.MfaDelete) {
- versioningConfiguration.MfaDelete =
- result.VersioningConfiguration.MfaDelete[0];
- }
- // the configuration has been checked before
- return next(null, bucket, versioningConfiguration);
- }),
- (bucket, versioningConfiguration, next) => {
- // check if replication is enabled if versioning is being suspended
- const replicationConfig = bucket.getReplicationConfiguration();
- const isIngestionBucket = bucket.isIngestionBucket && bucket.isIngestionBucket();
- const invalidAction =
- versioningConfiguration.Status === 'Suspended'
- && (isIngestionBucket || replicationConfig?.rules?.some(r => r.enabled));
- if (invalidAction) {
- const errorMsg = isIngestionBucket ?
- ingestionVersioningErrorMessage : replicationVersioningErrorMessage;
- next(errorInstances.InvalidBucketState
- .customizeDescription(errorMsg));
- return;
+ return waterfall(
+ [
+ next => _parseXML(request, log, next),
+ next =>
+ standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) =>
+ next(err, bucket),
+ ), // ignore extra null object,
+ (bucket, next) =>
+ parseString(request.post, (err, result) => {
+ // just for linting; there should not be any parsing error here
+ if (err) {
+ return next(err, bucket);
+ }
+ // prevent enabling versioning on an nfs exported bucket
+ if (bucket.isNFS()) {
+ const error = new Error();
+ error.code = 'NFSBUCKET';
+ return next(error);
+ }
+ // _checkBackendVersioningImplemented returns false if versioning
+ // is not implemented on the bucket backend
+ if (!_checkBackendVersioningImplemented(bucket)) {
+ log.debug(externalVersioningErrorMessage, {
+ method: 'bucketPutVersioning',
+ error: errors.NotImplemented,
+ });
+ const error =
+ errorInstances.NotImplemented.customizeDescription(externalVersioningErrorMessage);
+ return next(error, bucket);
+ }
+ const versioningConfiguration = {};
+ if (result.VersioningConfiguration.Status) {
+ versioningConfiguration.Status = result.VersioningConfiguration.Status[0];
+ }
+ if (result.VersioningConfiguration.MfaDelete) {
+ versioningConfiguration.MfaDelete = result.VersioningConfiguration.MfaDelete[0];
+ }
+ // the configuration has been checked before
+ return next(null, bucket, versioningConfiguration);
+ }),
+ (bucket, versioningConfiguration, next) => {
+ // check if replication is enabled if versioning is being suspended
+ const replicationConfig = bucket.getReplicationConfiguration();
+ const isIngestionBucket = bucket.isIngestionBucket && bucket.isIngestionBucket();
+ const invalidAction =
+ versioningConfiguration.Status === 'Suspended' &&
+ (isIngestionBucket || replicationConfig?.rules?.some(r => r.enabled));
+ if (invalidAction) {
+ const errorMsg = isIngestionBucket
+ ? ingestionVersioningErrorMessage
+ : replicationVersioningErrorMessage;
+ next(errorInstances.InvalidBucketState.customizeDescription(errorMsg));
+ return;
+ }
+ const objectLockEnabled = bucket.isObjectLockEnabled();
+ if (objectLockEnabled) {
+ next(errorInstances.InvalidBucketState.customizeDescription(objectLockErrorMessage));
+ return;
+ }
+ bucket.setVersioningConfiguration(versioningConfiguration);
+ // TODO all metadata updates of bucket should be using CAS
+ metadata.updateBucket(bucket.getName(), bucket, log, err => next(err, bucket));
+ },
+ ],
+ (err, bucket) => {
+ const corsHeaders = collectCorsHeaders(request.headers.origin, request.method, bucket);
+ if (err && err.code === 'NFSBUCKET') {
+ log.trace('skipping versioning for nfs exported bucket');
+ return callback(null, corsHeaders);
}
- const objectLockEnabled = bucket.isObjectLockEnabled();
- if (objectLockEnabled) {
- next(errorInstances.InvalidBucketState
- .customizeDescription(objectLockErrorMessage));
- return;
+ if (err) {
+ log.trace('error processing request', { error: err, method: 'bucketPutVersioning' });
+ monitoring.promMetrics('PUT', bucketName, err.code, 'putBucketVersioning');
+ } else {
+ pushMetric('putBucketVersioning', log, {
+ authInfo,
+ bucket: bucketName,
+ });
+ monitoring.promMetrics('PUT', bucketName, '200', 'putBucketVersioning');
}
- bucket.setVersioningConfiguration(versioningConfiguration);
- // TODO all metadata updates of bucket should be using CAS
- metadata.updateBucket(bucket.getName(), bucket, log, err =>
- next(err, bucket));
+ return callback(err, corsHeaders);
},
- ], (err, bucket) => {
- const corsHeaders = collectCorsHeaders(request.headers.origin,
- request.method, bucket);
- if (err && err.code === 'NFSBUCKET') {
- log.trace('skipping versioning for nfs exported bucket');
- return callback(null, corsHeaders);
- }
- if (err) {
- log.trace('error processing request', { error: err,
- method: 'bucketPutVersioning' });
- monitoring.promMetrics(
- 'PUT', bucketName, err.code, 'putBucketVersioning');
- } else {
- pushMetric('putBucketVersioning', log, {
- authInfo,
- bucket: bucketName,
- });
- monitoring.promMetrics(
- 'PUT', bucketName, '200', 'putBucketVersioning');
- }
- return callback(err, corsHeaders);
- });
+ );
}
module.exports = bucketPutVersioning;
diff --git a/lib/api/multiObjectDelete.js b/lib/api/multiObjectDelete.js
index 08d65c5cfa..07a55efebe 100644
--- a/lib/api/multiObjectDelete.js
+++ b/lib/api/multiObjectDelete.js
@@ -428,7 +428,7 @@ function getObjMetadataAndDelete(
overheadField,
log,
's3:ObjectRemoved:DeleteMarkerCreated',
- (err, result) => callback(err, objMD, deleteInfo, result.versionId),
+ (err, result) => callback(err, objMD, deleteInfo, result?.versionId),
);
},
],
diff --git a/lib/api/objectCopy.js b/lib/api/objectCopy.js
index 153aa21f03..9b9c2cddd1 100644
--- a/lib/api/objectCopy.js
+++ b/lib/api/objectCopy.js
@@ -40,7 +40,7 @@ const versionIdUtils = versioning.VersionID;
const locationHeader = constants.objectLocationConstraintHeader;
const versioningNotImplBackends = constants.versioningNotImplBackends;
const externalVersioningErrorMessage =
- 'We do not currently support putting a versioned object to a location-constraint of type AWS or Azure or GCP.';
+ 'We do not currently support putting a versioned object to a location-constraint of type Azure.';
/**
* Compute the prior data locations that are orphaned.
diff --git a/package.json b/package.json
index a9320e0d44..f2c20aaf65 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
"@opentelemetry/instrumentation-ioredis": "~0.64.0",
"@opentelemetry/instrumentation-mongodb": "~0.69.0",
"@smithy/node-http-handler": "^3.0.0",
- "arsenal": "git+https://github.com/scality/arsenal#8.5.15",
+ "arsenal": "git+https://github.com/scality/arsenal#2136c02ff1d3dfd43a5ff7ca59f955d105946dba",
"async": "2.6.4",
"aws-crt": "^1.24.0",
"bucketclient": "scality/bucketclient#8.2.7",
diff --git a/tests/unit/api/bucketPutVersioning.js b/tests/unit/api/bucketPutVersioning.js
index 462fadcf74..26eb07ae70 100644
--- a/tests/unit/api/bucketPutVersioning.js
+++ b/tests/unit/api/bucketPutVersioning.js
@@ -6,46 +6,44 @@ const { bucketPut } = require('../../../lib/api/bucketPut');
const bucketPutVersioning = require('../../../lib/api/bucketPutVersioning');
const bucketPutReplication = require('../../../lib/api/bucketPutReplication');
-const { cleanup,
- DummyRequestLogger,
- makeAuthInfo } = require('../helpers');
+const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../helpers');
const metadata = require('../../../lib/metadata/wrapper');
const xmlEnableVersioning =
-'' +
-'Enabled' +
-'';
+ '' +
+ 'Enabled' +
+ '';
const xmlSuspendVersioning =
-'' +
-'Suspended' +
-'';
+ '' +
+ 'Suspended' +
+ '';
const locConstraintVersioned =
-'' +
-'withversioning' +
-'';
+ '' +
+ 'withversioning' +
+ '';
const locConstraintNonVersioned =
-'' +
-'withoutversioning' +
-'';
+ '' +
+ 'withoutversioning' +
+ '';
const xmlReplicationConfiguration =
-'' +
+ '' +
'arn:aws:iam::account-id:role/src-resource' +
'' +
- '' +
- 'Enabled' +
- '' +
- 'arn:aws:s3:::destination-bucket' +
- 'us-east-2' +
- '' +
+ '' +
+ 'Enabled' +
+ '' +
+ 'arn:aws:s3:::destination-bucket' +
+ 'us-east-2' +
+ '' +
'' +
-'';
+ '';
-const externalVersioningErrorMessage = 'We do not currently support putting ' +
-'a versioned object to a location-constraint of type Azure or GCP.';
+const externalVersioningErrorMessage =
+ 'We do not currently support putting ' + 'a versioned object to a location-constraint of type Azure.';
const log = new DummyRequestLogger();
const bucketName = 'bucketname';
@@ -94,55 +92,58 @@ describe('bucketPutVersioning API', () => {
const tests = [
{
- msg: 'should successfully enable versioning on location ' +
- 'constraint with supportsVersioning set to true',
+ msg:
+ 'should successfully enable versioning on location ' +
+ 'constraint with supportsVersioning set to true',
input: xmlEnableVersioning,
output: { Status: 'Enabled' },
},
{
- msg: 'should successfully suspend versioning on location ' +
- 'constraint with supportsVersioning set to true',
+ msg:
+ 'should successfully suspend versioning on location ' +
+ 'constraint with supportsVersioning set to true',
input: xmlSuspendVersioning,
output: { Status: 'Suspended' },
},
];
- tests.forEach(test => it(test.msg, done => {
- const request = _putVersioningRequest(test.input);
- bucketPutVersioning(authInfo, request, log, err => {
- assert.ifError(err,
- `Expected success, but got err: ${err}`);
- metadata.getBucket(bucketName, log, (err, bucket) => {
- assert.ifError(err,
- `Expected success, but got err: ${err}`);
- assert.deepStrictEqual(bucket._versioningConfiguration,
- test.output);
- done();
+ tests.forEach(test =>
+ it(test.msg, done => {
+ const request = _putVersioningRequest(test.input);
+ bucketPutVersioning(authInfo, request, log, err => {
+ assert.ifError(err, `Expected success, but got err: ${err}`);
+ metadata.getBucket(bucketName, log, (err, bucket) => {
+ assert.ifError(err, `Expected success, but got err: ${err}`);
+ assert.deepStrictEqual(bucket._versioningConfiguration, test.output);
+ done();
+ });
});
- });
- }));
+ }),
+ );
it('should not suspend versioning on bucket with replication', done => {
- async.series([
- // Enable versioning to allow putting a replication config.
- next => {
- const request = _putVersioningRequest(xmlEnableVersioning);
- bucketPutVersioning(authInfo, request, log, next);
- },
- // Put the replication config on the bucket.
- next => {
- const request =
- _putReplicationRequest(xmlReplicationConfiguration);
- bucketPutReplication(authInfo, request, log, next);
- },
- // Attempt to suspend versioning.
- next => {
- const request = _putVersioningRequest(xmlSuspendVersioning);
- bucketPutVersioning(authInfo, request, log, err => {
- assert(err.is.InvalidBucketState);
- next();
- });
- },
- ], done);
+ async.series(
+ [
+ // Enable versioning to allow putting a replication config.
+ next => {
+ const request = _putVersioningRequest(xmlEnableVersioning);
+ bucketPutVersioning(authInfo, request, log, next);
+ },
+ // Put the replication config on the bucket.
+ next => {
+ const request = _putReplicationRequest(xmlReplicationConfiguration);
+ bucketPutReplication(authInfo, request, log, next);
+ },
+ // Attempt to suspend versioning.
+ next => {
+ const request = _putVersioningRequest(xmlSuspendVersioning);
+ bucketPutVersioning(authInfo, request, log, err => {
+ assert(err.is.InvalidBucketState);
+ next();
+ });
+ },
+ ],
+ done,
+ );
});
});
@@ -154,28 +155,28 @@ describe('bucketPutVersioning API', () => {
const tests = [
{
- msg: 'should return error if enabling versioning on location ' +
- 'constraint with supportsVersioning set to false',
+ msg:
+ 'should return error if enabling versioning on location ' +
+ 'constraint with supportsVersioning set to false',
input: xmlEnableVersioning,
- output: { error: errorInstances.NotImplemented.customizeDescription(
- externalVersioningErrorMessage) },
+ output: { error: errorInstances.NotImplemented.customizeDescription(externalVersioningErrorMessage) },
},
{
- msg: 'should return error if suspending versioning on ' +
- ' location constraint with supportsVersioning set to false',
+ msg:
+ 'should return error if suspending versioning on ' +
+ ' location constraint with supportsVersioning set to false',
input: xmlSuspendVersioning,
- output: { error: errorInstances.NotImplemented.customizeDescription(
- externalVersioningErrorMessage) },
+ output: { error: errorInstances.NotImplemented.customizeDescription(externalVersioningErrorMessage) },
},
];
- tests.forEach(test => it(test.msg, done => {
- const putBucketVersioningRequest =
- _putVersioningRequest(test.input);
- bucketPutVersioning(authInfo, putBucketVersioningRequest, log,
- err => {
- assert.deepStrictEqual(err, test.output.error);
- done();
- });
- }));
+ tests.forEach(test =>
+ it(test.msg, done => {
+ const putBucketVersioningRequest = _putVersioningRequest(test.input);
+ bucketPutVersioning(authInfo, putBucketVersioningRequest, log, err => {
+ assert.deepStrictEqual(err, test.output.error);
+ done();
+ });
+ }),
+ );
});
});
diff --git a/tests/unit/api/createAndStoreObject.js b/tests/unit/api/createAndStoreObject.js
index 1c28cce4f1..d30fcd9243 100644
--- a/tests/unit/api/createAndStoreObject.js
+++ b/tests/unit/api/createAndStoreObject.js
@@ -9,6 +9,7 @@ const { storage } = require('arsenal');
const sinon = require('sinon');
const { bucketPut } = require('../../../lib/api/bucketPut');
+const { data } = require('../../../lib/data/wrapper');
const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../helpers');
const metadata = require('../metadataswitch');
const rawCreateAndStoreObject = require('../../../lib/api/apiUtils/object/createAndStoreObject');
@@ -23,15 +24,16 @@ const canonicalID = authInfo.getCanonicalID();
const bucketName = 'test-bucket';
const objectKey = 'test-object';
-const getObjectMDAsync = (bucket, key, params = {}) => new Promise((resolve, reject) => {
- metadata.getObjectMD(bucket, key, params, log, (err, data) => {
- if (err) {
- reject(err);
- } else {
- resolve(data);
- }
+const getObjectMDAsync = (bucket, key, params = {}) =>
+ new Promise((resolve, reject) => {
+ metadata.getObjectMD(bucket, key, params, log, (err, data) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(data);
+ }
+ });
});
-});
describe('createAndStoreObject', () => {
let testBucket;
@@ -64,17 +66,32 @@ describe('createAndStoreObject', () => {
describe('Regular object creation', () => {
it('should create object successfully', async () => {
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: { 'content-type': 'text/plain' },
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('test data', 'utf8'),
+ );
+
+ const result = await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: { 'content-type': 'text/plain' },
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('test data', 'utf8'));
-
- const result = await createAndStoreObject(bucketName, testBucket, objectKey, null,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ null,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
assert(result.contentMD5);
@@ -83,18 +100,33 @@ describe('createAndStoreObject', () => {
});
it('should handle zero-byte object', async () => {
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: { 'content-type': 'text/plain' },
+ parsedContentLength: 0,
+ url: `/${bucketName}/${objectKey}`,
+ },
+ '',
+ );
+
+ const result = await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: { 'content-type': 'text/plain' },
- parsedContentLength: 0,
- url: `/${bucketName}/${objectKey}`,
- }, '');
-
- const result = await createAndStoreObject(bucketName, testBucket, objectKey, null,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ null,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
assert(result.contentMD5);
});
@@ -103,17 +135,32 @@ describe('createAndStoreObject', () => {
const authInfo2 = makeAuthInfo('accessKey2');
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: {},
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('test', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: {},
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('test', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, null,
- authInfo2, authInfo2.getCanonicalID(), null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ null,
+ authInfo2,
+ authInfo2.getCanonicalID(),
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const storedObjMD = getStoredObjectData();
assert.strictEqual(storedObjMD.bucketOwnerId, canonicalID);
@@ -130,15 +177,77 @@ describe('createAndStoreObject', () => {
url: `/${bucketName}/${objectKey}`,
});
- await createAndStoreObject(bucketName, testBucket, objectKey, null,
- authInfo, canonicalID, null, request, true, null,
- ['overhead'], log, 's3:ObjectRemoved:DeleteMarkerCreated');
+ await createAndStoreObject(
+ bucketName,
+ testBucket,
+ objectKey,
+ null,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ true,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectRemoved:DeleteMarkerCreated',
+ );
assert.deepStrictEqual(ds, []);
const objMD = await getObjectMDAsync(bucketName, objectKey, {});
assert(objMD.isDeleteMarker);
});
+
+ // The marker must still reach the backend, which uses it to drop the
+ // live version there.
+ it('should ignore the request checksum for a delete marker on an external location', async () => {
+ const putStub = sinon.stub(data.client, 'put').yields(null, 'key', 'versionId');
+ const externalBucket = 'test-bucket-external';
+ const bucketRequest = new DummyRequest({
+ bucketName: externalBucket,
+ namespace: 'default',
+ headers: { host: `${externalBucket}.s3.amazonaws.com` },
+ url: '/',
+ post:
+ '' +
+ '' +
+ 'awsbackend' +
+ '',
+ });
+ await promisify(bucketPut)(authInfo, bucketRequest, log);
+ const bucket = await promisify(metadata.getBucket.bind(metadata))(externalBucket, log);
+
+ const request = new DummyRequest({
+ bucketName: externalBucket,
+ namespace: 'default',
+ objectKey,
+ headers: { 'x-amz-checksum-crc32': 'AAAAAQ==' },
+ url: `/${externalBucket}/${objectKey}`,
+ });
+
+ await createAndStoreObject(
+ externalBucket,
+ bucket,
+ objectKey,
+ null,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ true,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectRemoved:DeleteMarkerCreated',
+ );
+
+ assert(putStub.calledOnce, 'delete marker must still reach the backend');
+ assert(putStub.firstCall.args[2].isDeleteMarker);
+ const objMD = await getObjectMDAsync(externalBucket, objectKey, {});
+ assert(objMD.isDeleteMarker);
+ });
});
describe('Archived object replacement', () => {
@@ -146,24 +255,39 @@ describe('createAndStoreObject', () => {
const archivedObjMD = {
'content-md5': 'abc123',
'content-length': 100,
- 'archive': {
- 'archiveInfo': { 'archiveID': 'archive-123' },
+ archive: {
+ archiveInfo: { archiveID: 'archive-123' },
},
};
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: {},
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('new data', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: {},
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('new data', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const options = getStoredOptions();
assert.strictEqual(options.needOplogUpdate, true);
@@ -174,26 +298,41 @@ describe('createAndStoreObject', () => {
const archivedObjMD = {
'content-md5': 'abc123',
'content-length': 100,
- 'versionId': 'v1',
- 'archive': {
- 'archiveInfo': { 'archiveID': 'archive-123' },
+ versionId: 'v1',
+ archive: {
+ archiveInfo: { archiveID: 'archive-123' },
},
};
sinon.stub(testBucket, 'isVersioningEnabled').returns(true);
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: {},
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('new data', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: {},
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('new data', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const options = getStoredOptions();
assert.strictEqual(options.needOplogUpdate, undefined);
@@ -212,17 +351,32 @@ describe('createAndStoreObject', () => {
sinon.stub(testBucket, 'isVersioningEnabled').returns(false);
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: {},
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('new data', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: {},
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('new data', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const options = getStoredOptions();
assert.strictEqual(options.needOplogUpdate, true);
@@ -239,17 +393,32 @@ describe('createAndStoreObject', () => {
};
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: {},
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('new data', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: {},
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('new data', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const options = getStoredOptions();
assert.strictEqual(options.needOplogUpdate, undefined);
@@ -261,34 +430,49 @@ describe('createAndStoreObject', () => {
it('should restore object with x-scal-s3-version-id header', async () => {
const now = Date.now();
const archivedObjMD = {
- 'key': objectKey,
- 'versionId': 'v123',
+ key: objectKey,
+ versionId: 'v123',
'content-md5': 'original-hash',
'content-length': 100,
'x-amz-storage-class': 'cold-location',
- 'dataStoreName': 'cold-location',
+ dataStoreName: 'cold-location',
'x-amz-meta-custom': 'preserved-value',
- 'tags': { 'tagkey': 'tagvalue' },
- 'archive': {
- 'archiveInfo': { 'archiveID': 'archive-123' },
- 'restoreRequestedAt': new Date(now).toISOString(),
- 'restoreRequestedDays': 7,
+ tags: { tagkey: 'tagvalue' },
+ archive: {
+ archiveInfo: { archiveID: 'archive-123' },
+ restoreRequestedAt: new Date(now).toISOString(),
+ restoreRequestedDays: 7,
},
};
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: { 'x-scal-s3-version-id': 'v123' },
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('restored data', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: { 'x-scal-s3-version-id': 'v123' },
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('restored data', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const storedObjMD = getStoredObjectData();
const options = getStoredOptions();
@@ -296,7 +480,7 @@ describe('createAndStoreObject', () => {
assert(storedObjMD.archive.restoreWillExpireAt, 'restoreWillExpireAt should be set');
assert.strictEqual(storedObjMD.archive.restoreRequestedDays, 7);
assert.strictEqual(storedObjMD['x-amz-meta-custom'], 'preserved-value');
- assert.deepStrictEqual(storedObjMD.tags, { 'tagkey': 'tagvalue' });
+ assert.deepStrictEqual(storedObjMD.tags, { tagkey: 'tagvalue' });
assert.strictEqual(storedObjMD.originOp, 's3:ObjectRestore:Completed');
assert.strictEqual(options.needOplogUpdate, undefined);
assert.strictEqual(options.originOp, undefined);
@@ -304,36 +488,49 @@ describe('createAndStoreObject', () => {
it('should preserve original etag for MPU restoration with different part count', async () => {
const archivedObjMD = {
- 'versionId': 'v123',
+ versionId: 'v123',
'content-md5': 'original-abc123-5', // Original had 5 parts
- 'archive': {
- 'archiveInfo': { 'archiveID': 'archive-123' },
- 'restoreRequestedAt': new Date().toISOString(),
- 'restoreRequestedDays': 7,
+ archive: {
+ archiveInfo: { archiveID: 'archive-123' },
+ restoreRequestedAt: new Date().toISOString(),
+ restoreRequestedDays: 7,
},
};
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: { 'x-scal-s3-version-id': 'v123' },
+ url: `/${bucketName}/${objectKey}`,
+ calculatedHash: 'restored-def456-3', // Restored with 3 parts
+ },
+ Buffer.from('restored data', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: { 'x-scal-s3-version-id': 'v123' },
- url: `/${bucketName}/${objectKey}`,
- calculatedHash: 'restored-def456-3', // Restored with 3 parts
- }, Buffer.from('restored data', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const storedObjMD = getStoredObjectData();
- assert.strictEqual(storedObjMD['content-md5'], 'original-abc123-5',
- 'Original etag should be preserved');
+ assert.strictEqual(storedObjMD['content-md5'], 'original-abc123-5', 'Original etag should be preserved');
assert(storedObjMD['x-amz-restore']['content-md5']);
- assert.notStrictEqual(storedObjMD['x-amz-restore']['content-md5'],
- storedObjMD['content-md5']);
+ assert.notStrictEqual(storedObjMD['x-amz-restore']['content-md5'], storedObjMD['content-md5']);
});
it('should preserve replication info during restoration', async () => {
@@ -343,156 +540,237 @@ describe('createAndStoreObject', () => {
};
const archivedObjMD = {
- 'versionId': 'v123',
+ versionId: 'v123',
replicationInfo,
- 'archive': {
- 'archiveInfo': { 'archiveID': 'archive-123' },
- 'restoreRequestedAt': new Date().toISOString(),
- 'restoreRequestedDays': 7,
+ archive: {
+ archiveInfo: { archiveID: 'archive-123' },
+ restoreRequestedAt: new Date().toISOString(),
+ restoreRequestedDays: 7,
},
};
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: { 'x-scal-s3-version-id': 'v123' },
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('restored', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: { 'x-scal-s3-version-id': 'v123' },
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('restored', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const storedObjMD = getStoredObjectData();
- assert.strictEqual(storedObjMD.replicationInfo.status, replicationInfo.status,
- 'Replication status should be preserved');
- assert.deepStrictEqual(storedObjMD.replicationInfo.backends, replicationInfo.backends,
- 'Replication backends should be preserved');
+ assert.strictEqual(
+ storedObjMD.replicationInfo.status,
+ replicationInfo.status,
+ 'Replication status should be preserved',
+ );
+ assert.deepStrictEqual(
+ storedObjMD.replicationInfo.backends,
+ replicationInfo.backends,
+ 'Replication backends should be preserved',
+ );
});
it('should preserve legal hold during restoration', async () => {
const archivedObjMD = {
- 'versionId': 'v123',
- 'legalHold': true,
- 'archive': {
- 'archiveInfo': { 'archiveID': 'archive-123' },
- 'restoreRequestedAt': new Date().toISOString(),
- 'restoreRequestedDays': 7,
+ versionId: 'v123',
+ legalHold: true,
+ archive: {
+ archiveInfo: { archiveID: 'archive-123' },
+ restoreRequestedAt: new Date().toISOString(),
+ restoreRequestedDays: 7,
},
};
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: { 'x-scal-s3-version-id': 'v123' },
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('restored', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: { 'x-scal-s3-version-id': 'v123' },
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('restored', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const storedObjMD = getStoredObjectData();
- assert.strictEqual(storedObjMD.legalHold, true,
- 'Legal hold should be preserved');
+ assert.strictEqual(storedObjMD.legalHold, true, 'Legal hold should be preserved');
});
it('should preserve ACLs during restoration', async () => {
const acl = {
- 'Canned': '',
- 'FULL_CONTROL': ['canonical-id-1'],
- 'READ': ['canonical-id-2'],
+ Canned: '',
+ FULL_CONTROL: ['canonical-id-1'],
+ READ: ['canonical-id-2'],
};
const archivedObjMD = {
- 'versionId': 'v123',
+ versionId: 'v123',
acl,
- 'archive': {
- 'archiveInfo': { 'archiveID': 'archive-123' },
- 'restoreRequestedAt': new Date().toISOString(),
- 'restoreRequestedDays': 7,
+ archive: {
+ archiveInfo: { archiveID: 'archive-123' },
+ restoreRequestedAt: new Date().toISOString(),
+ restoreRequestedDays: 7,
},
};
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: { 'x-scal-s3-version-id': 'v123' },
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('restored', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: { 'x-scal-s3-version-id': 'v123' },
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('restored', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const storedObjMD = getStoredObjectData();
- assert.deepStrictEqual(storedObjMD.acl, acl,
- 'ACLs should be preserved');
+ assert.deepStrictEqual(storedObjMD.acl, acl, 'ACLs should be preserved');
});
it('should not preserve x-amz-meta-scal-s3-restore-attempt metadata', async () => {
const archivedObjMD = {
- 'versionId': 'v123',
+ versionId: 'v123',
'x-amz-meta-custom': 'keep-this',
'x-amz-meta-scal-s3-restore-attempt': '3',
- 'archive': {
- 'archiveInfo': { 'archiveID': 'archive-123' },
- 'restoreRequestedAt': new Date().toISOString(),
- 'restoreRequestedDays': 7,
+ archive: {
+ archiveInfo: { archiveID: 'archive-123' },
+ restoreRequestedAt: new Date().toISOString(),
+ restoreRequestedDays: 7,
},
};
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: { 'x-scal-s3-version-id': 'v123' },
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('restored', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: { 'x-scal-s3-version-id': 'v123' },
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('restored', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const storedObjMD = getStoredObjectData();
- assert.strictEqual(storedObjMD['x-amz-meta-custom'], 'keep-this',
- 'Custom metadata should be preserved');
- assert.strictEqual(storedObjMD['x-amz-meta-scal-s3-restore-attempt'], undefined,
- 'Restore attempt metadata should NOT be preserved');
+ assert.strictEqual(storedObjMD['x-amz-meta-custom'], 'keep-this', 'Custom metadata should be preserved');
+ assert.strictEqual(
+ storedObjMD['x-amz-meta-scal-s3-restore-attempt'],
+ undefined,
+ 'Restore attempt metadata should NOT be preserved',
+ );
});
});
describe('MPU scenarios', () => {
it('should set oldReplayId when overwriting MPU object', async () => {
const mpuObjMD = {
- 'uploadId': 'mpu-upload-123',
+ uploadId: 'mpu-upload-123',
'content-md5': 'abc123',
};
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: {},
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('new data', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: {},
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('new data', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, mpuObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ mpuObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const options = getStoredOptions();
assert.strictEqual(options.oldReplayId, 'mpu-upload-123');
@@ -508,17 +786,32 @@ describe('createAndStoreObject', () => {
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: {},
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('new data', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: {},
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('new data', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, existingObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ existingObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const storedObjMD = getStoredObjectData();
assert.strictEqual(storedObjMD['creation-time'], '2024-01-01T00:00:00.000Z');
@@ -531,17 +824,32 @@ describe('createAndStoreObject', () => {
sinon.spy(metadata, 'putObjectMD');
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: {},
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('new data', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: {},
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('new data', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, existingObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ existingObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const storedObjMD = getStoredObjectData();
assert.strictEqual(storedObjMD['creation-time'], '2024-02-01T00:00:00.000Z');
@@ -562,17 +870,32 @@ describe('createAndStoreObject', () => {
},
};
- const request = new DummyRequest({
+ const request = new DummyRequest(
+ {
+ bucketName,
+ namespace: 'default',
+ objectKey,
+ headers: { 'x-scal-s3-version-id': putVersionId },
+ url: `/${bucketName}/${objectKey}`,
+ },
+ Buffer.from('restored', 'utf8'),
+ );
+
+ await createAndStoreObject(
bucketName,
- namespace: 'default',
+ testBucket,
objectKey,
- headers: { 'x-scal-s3-version-id': putVersionId },
- url: `/${bucketName}/${objectKey}`,
- }, Buffer.from('restored', 'utf8'));
-
- await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD,
- authInfo, canonicalID, null, request, false, null,
- ['overhead'], log, 's3:ObjectCreated:Put');
+ archivedObjMD,
+ authInfo,
+ canonicalID,
+ null,
+ request,
+ false,
+ null,
+ ['overhead'],
+ log,
+ 's3:ObjectCreated:Put',
+ );
const storedObjMD = getStoredObjectData();
assert.strictEqual(storedObjMD['x-amz-meta-scal-version-id'], putVersionId);
diff --git a/tests/unit/multipleBackend/VersioningBackendClient.js b/tests/unit/multipleBackend/VersioningBackendClient.js
index 60d8527602..b65a00b0b5 100644
--- a/tests/unit/multipleBackend/VersioningBackendClient.js
+++ b/tests/unit/multipleBackend/VersioningBackendClient.js
@@ -7,8 +7,7 @@ const DummyService = require('../DummyService');
const { DummyRequestLogger } = require('../helpers');
const missingVerIdInternalError = errorInstances.InternalError.customizeDescription(
- 'Invalid state. Please ensure versioning is enabled ' +
- 'in AWS for the location constraint and try again.'
+ 'Invalid state. Please ensure versioning is enabled ' + 'in AWS for the location constraint and try again.',
);
const log = new DummyRequestLogger();
@@ -43,8 +42,7 @@ const s3Config = {
};
const assertSuccess = (err, cb) => {
- assert.ifError(err,
- `Expected success, but got error ${err}`);
+ assert.ifError(err, `Expected success, but got error ${err}`);
cb();
};
@@ -54,26 +52,22 @@ const assertFailure = (err, cb) => {
};
const genTests = [
{
- msg: 'should return success if supportsVersioning === true ' +
- 'and backend versioning is enabled',
+ msg: 'should return success if supportsVersioning === true ' + 'and backend versioning is enabled',
input: { supportsVersioning: true, enableMockVersioning: true },
callback: assertSuccess,
},
{
- msg: 'should return success if supportsVersioning === false ' +
- 'and backend versioning is enabled',
+ msg: 'should return success if supportsVersioning === false ' + 'and backend versioning is enabled',
input: { supportsVersioning: false, enableMockVersioning: true },
callback: assertSuccess,
},
{
- msg: 'should return error if supportsVersioning === true ' +
- 'and backend versioning is disabled',
+ msg: 'should return error if supportsVersioning === true ' + 'and backend versioning is disabled',
input: { supportsVersioning: true, enableMockVersioning: false },
callback: assertFailure,
},
{
- msg: 'should return success if supportsVersioning === false ' +
- 'and backend versioning is disabled',
+ msg: 'should return success if supportsVersioning === false ' + 'and backend versioning is disabled',
input: { supportsVersioning: false, enableMockVersioning: false },
callback: assertSuccess,
},
@@ -86,12 +80,13 @@ describe('AwsClient::putObject', () => {
testClient = new AwsClient(s3Config);
testClient._client = new DummyService({ versioning: true });
});
- genTests.forEach(test => it(test.msg, done => {
- testClient._supportsVersioning = test.input.supportsVersioning;
- testClient._client.versioning = test.input.enableMockVersioning;
- testClient.put('', 0, { bucketName: bucket, objectKey: key },
- reqUID, err => test.callback(err, done));
- }));
+ genTests.forEach(test =>
+ it(test.msg, done => {
+ testClient._supportsVersioning = test.input.supportsVersioning;
+ testClient._client.versioning = test.input.enableMockVersioning;
+ testClient.put('', 0, { bucketName: bucket, objectKey: key }, reqUID, err => test.callback(err, done));
+ }),
+ );
});
describe('AwsClient::copyObject', () => {
@@ -102,13 +97,23 @@ describe('AwsClient::copyObject', () => {
testClient._client = new DummyService({ versioning: true });
});
- genTests.forEach(test => it(test.msg, done => {
- testClient._supportsVersioning = test.input.supportsVersioning;
- testClient._client.versioning = test.input.enableMockVersioning;
- testClient.copyObject(copyObjectRequest, null, key,
- sourceLocationConstraint, null, config, log,
- err => test.callback(err, done));
- }));
+ genTests.forEach(test =>
+ it(test.msg, done => {
+ testClient._supportsVersioning = test.input.supportsVersioning;
+ testClient._client.versioning = test.input.enableMockVersioning;
+ testClient.copyObject(
+ copyObjectRequest,
+ null,
+ key,
+ undefined,
+ sourceLocationConstraint,
+ null,
+ config,
+ log,
+ err => test.callback(err, done),
+ );
+ }),
+ );
});
describe('AwsClient::completeMPU', () => {
@@ -118,13 +123,14 @@ describe('AwsClient::completeMPU', () => {
testClient = new AwsClient(s3Config);
testClient._client = new DummyService({ versioning: true });
});
- genTests.forEach(test => it(test.msg, done => {
- testClient._supportsVersioning = test.input.supportsVersioning;
- testClient._client.versioning = test.input.enableMockVersioning;
- const uploadId = 'externalBackendTestUploadId';
- testClient.completeMPU(jsonList, null, key, uploadId,
- bucket, log, err => test.callback(err, done));
- }));
+ genTests.forEach(test =>
+ it(test.msg, done => {
+ testClient._supportsVersioning = test.input.supportsVersioning;
+ testClient._client.versioning = test.input.enableMockVersioning;
+ const uploadId = 'externalBackendTestUploadId';
+ testClient.completeMPU(jsonList, null, key, uploadId, bucket, log, err => test.callback(err, done));
+ }),
+ );
});
describe('AwsClient::healthcheck', () => {
@@ -159,34 +165,31 @@ describe('AwsClient::healthcheck', () => {
const tests = [
{
- msg: 'should return success if supportsVersioning === true ' +
- 'and backend versioning is enabled',
+ msg: 'should return success if supportsVersioning === true ' + 'and backend versioning is enabled',
input: { supportsVersioning: true, enableMockVersioning: true },
callback: assertSuccessVersioned,
},
{
- msg: 'should return success if supportsVersioning === false ' +
- 'and backend versioning is enabled',
+ msg: 'should return success if supportsVersioning === false ' + 'and backend versioning is enabled',
input: { supportsVersioning: false, enableMockVersioning: true },
callback: assertSuccessNonVersioned,
},
{
- msg: 'should return error if supportsVersioning === true ' +
- ' and backend versioning is disabled',
+ msg: 'should return error if supportsVersioning === true ' + ' and backend versioning is disabled',
input: { supportsVersioning: true, enableMockVersioning: false },
callback: assertFailure,
},
{
- msg: 'should return success if supportsVersioning === false ' +
- 'and backend versioning is disabled',
+ msg: 'should return success if supportsVersioning === false ' + 'and backend versioning is disabled',
input: { supportsVersioning: false, enableMockVersioning: false },
callback: assertSuccessNonVersioned,
},
];
- tests.forEach(test => it(test.msg, done => {
- testClient._supportsVersioning = test.input.supportsVersioning;
- testClient._client.versioning = test.input.enableMockVersioning;
- testClient.healthcheck('backend',
- (err, resp) => test.callback(resp.backend, done));
- }));
+ tests.forEach(test =>
+ it(test.msg, done => {
+ testClient._supportsVersioning = test.input.supportsVersioning;
+ testClient._client.versioning = test.input.enableMockVersioning;
+ testClient.healthcheck('backend', (err, resp) => test.callback(resp.backend, done));
+ }),
+ );
});
diff --git a/yarn.lock b/yarn.lock
index 854e838c90..cb7d9e6903 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5838,9 +5838,9 @@ arraybuffer.prototype.slice@^1.0.4:
optionalDependencies:
ioctl "^2.0.2"
-"arsenal@git+https://github.com/scality/arsenal#8.5.15":
+"arsenal@git+https://github.com/scality/arsenal#2136c02ff1d3dfd43a5ff7ca59f955d105946dba":
version "8.5.15"
- resolved "git+https://github.com/scality/arsenal#0bbe970dd72b2e235c47910474883af9b9c13eb1"
+ resolved "git+https://github.com/scality/arsenal#2136c02ff1d3dfd43a5ff7ca59f955d105946dba"
dependencies:
"@aws-sdk/client-kms" "^3.975.0"
"@aws-sdk/client-s3" "^3.975.0"