Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions tests/functional/aws-node-sdk/test/object/objectOverwrite.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
const assert = require('assert');
const { promisify } = require('util');
const {
PutObjectCommand,
PutBucketVersioningCommand,
HeadObjectCommand,
GetObjectCommand,
} = require('@aws-sdk/client-s3');

const withV4 = require('../support/withV4');
const BucketUtility = require('../../lib/utility/bucket-util');
const { fakeMetadataArchive, fakeMetadataTransition, getMetadata, initMetadata } = require('../utils/init');
const fakeMetadataArchivePromise = promisify(fakeMetadataArchive);
const fakeMetadataTransitionPromise = promisify(fakeMetadataTransition);
const getMetadataPromise = promisify(getMetadata);
const initMetadataPromise = promisify(initMetadata);

const objectName = 'someObject';
const firstPutMetadata = {
Expand All @@ -30,6 +37,7 @@ describe('Put object with same key as prior object', () => {
bucketUtil = new BucketUtility('default', sigCfg);
s3 = bucketUtil.s3;
bucketName = await bucketUtil.createRandom(1);
await initMetadataPromise();
});

beforeEach(async () => {
Expand Down Expand Up @@ -66,5 +74,138 @@ describe('Put object with same key as prior object', () => {
const bodyText = await res.Body.transformToString();
assert.deepStrictEqual(bodyText, 'Much different');
});

[
{
name: 'transition in progress',
setup: () => fakeMetadataTransitionPromise(bucketName, objectName, undefined),
},
{
name: 'archived',
setup: () => fakeMetadataArchivePromise(bucketName, objectName, undefined, {
archiveInfo: { archiveId: 'archive-1' },
restoreRequestedAt: new Date(0).toISOString(),
restoreRequestedDays: 5,
}),
},
{
name: 'restored (not expired)',
setup: () => fakeMetadataArchivePromise(bucketName, objectName, undefined, {
archiveInfo: { archiveId: 'archive-restored' },
restoreRequestedAt: new Date(0).toISOString(),
restoreRequestedDays: 5,
restoreCompletedAt: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
restoreWillExpireAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
}),
},
{
name: 'restored (expired)',
setup: () => fakeMetadataArchivePromise(bucketName, objectName, undefined, {
archiveInfo: { archiveId: 'archive-expired' },
restoreRequestedAt: new Date(0).toISOString(),
restoreRequestedDays: 5,
restoreCompletedAt: new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString(),
restoreWillExpireAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
}),
},
].forEach(({ name, setup }) => {
it(`should replace object with cold-state metadata (${name}) in non-versioned bucket`, async () => {
await setup();

await s3.send(new PutObjectCommand({
Bucket: bucketName,
Key: objectName,
Body: `overwrite cold state ${name}`,
Metadata: secondPutMetadata,
}));

const currentMD = await getMetadataPromise(bucketName, objectName, undefined);
assert.strictEqual(currentMD.archive, undefined);
assert.strictEqual(currentMD['x-amz-scal-transition-in-progress'], undefined);
});
});

it('should create a new version when replacing archived current object in versioned bucket', async () => {
await bucketUtil.empty(bucketName);

await s3.send(new PutBucketVersioningCommand({
Bucket: bucketName,
VersioningConfiguration: { Status: 'Enabled' },
}));

const firstPutRes = await s3.send(new PutObjectCommand({
Bucket: bucketName,
Key: objectName,
Body: 'versioned first payload',
Metadata: firstPutMetadata,
}));
assert(firstPutRes.VersionId);

await fakeMetadataArchivePromise(bucketName, objectName, undefined, {
archiveInfo: { archiveId: 'archive-versioned-current' },
restoreRequestedAt: new Date(0).toISOString(),
restoreRequestedDays: 5,
});

const secondPutRes = await s3.send(new PutObjectCommand({
Bucket: bucketName,
Key: objectName,
Body: 'versioned second payload',
Metadata: secondPutMetadata,
}));
assert(secondPutRes.VersionId);
assert.notStrictEqual(secondPutRes.VersionId, firstPutRes.VersionId);

const headRes = await s3.send(new HeadObjectCommand({
Bucket: bucketName,
Key: objectName,
}));
assert.deepStrictEqual(headRes.Metadata, secondPutMetadata);

const currentMD = await getMetadataPromise(bucketName, objectName, undefined);
assert.strictEqual(currentMD.archive, undefined);
});

it('should replace archived current null version in version-suspended bucket', async () => {
await bucketUtil.empty(bucketName);

await s3.send(new PutBucketVersioningCommand({
Bucket: bucketName,
VersioningConfiguration: { Status: 'Enabled' },
}));
await s3.send(new PutObjectCommand({
Bucket: bucketName,
Key: objectName,
Body: 'enabled-version-payload',
}));
await s3.send(new PutBucketVersioningCommand({
Bucket: bucketName,
VersioningConfiguration: { Status: 'Suspended' },
}));

await s3.send(new PutObjectCommand({
Bucket: bucketName,
Key: objectName,
Body: 'null-current-before-archive',
}));

await fakeMetadataArchivePromise(bucketName, objectName, undefined, {
archiveInfo: { archiveId: 'archive-null-current' },
restoreRequestedAt: new Date(0).toISOString(),
restoreRequestedDays: 5,
});

await s3.send(new PutObjectCommand({
Bucket: bucketName,
Key: objectName,
Body: 'replace archived null current',
Metadata: secondPutMetadata,
}));

const currentMD = await getMetadataPromise(bucketName, objectName, undefined);
assert.strictEqual(currentMD.archive, undefined);
assert.deepStrictEqual(currentMD['x-amz-meta-secondput'], secondPutMetadata.secondput);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel we are missing may test cases : e.g.

  • bucket can be version suspended
  • previous version can be a "null version" (created before versioning was enabled)
  • there is PutObject, but also our own extension "PutObject with version id" which is used when restoring an object : which creates many of the code paths in this function. (not sure how much is really tested, and probably should not go in "objectOverwrite" case : but we need to review if such cases are missing)

(but code coverage says we cover all these, so must be tested somewhere already, somehow...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Covered now through the combined suite:

  • version-suspended/null-current behavior in objectOverwrite,
  • restore-specific PutObject with x-scal-s3-version-id scenarios in putVersion, and unit assertions for restore metadata behavior (originOp, metadata preservation, restore-attempt cleanup, ingestion version-id handling).


});
});
66 changes: 66 additions & 0 deletions tests/functional/aws-node-sdk/test/object/putVersion.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const assert = require('assert');
const async = require('async');
const { promisify } = require('util');

const withV4 = require('../support/withV4');
const BucketUtility = require('../../lib/utility/bucket-util');
Expand Down Expand Up @@ -956,6 +957,71 @@ describe('PUT object with x-scal-s3-version-id header', () => {
},
], done);
});

it('should set restore originOp and drop restore-attempt metadata', done => {
const params = { Bucket: bucketName, Key: objectName };

async.series([
next => s3.send(new PutObjectCommand({
...params,
Metadata: {
'custom-md': 'preserved-value',
},
})).then(() => next()).catch(next),
next => fakeMetadataArchive(bucketName, objectName, undefined, archive, next),
next => getMetadata(bucketName, objectName, undefined, (err, objMD) => {
if (err) {
return next(err);
}
/* eslint-disable no-param-reassign */
objMD['x-amz-meta-scal-s3-restore-attempt'] = '3';
/* eslint-enable no-param-reassign */
return metadata.putObjectMD(bucketName, objectName, objMD, undefined, log, next);
}),
next => putObjectVersion(s3, params, '', next),
next => getMetadata(bucketName, objectName, undefined, (err, objMD) => {
if (err) {
return next(err);
}
assert.strictEqual(objMD.originOp, 's3:ObjectRestore:Completed');
assert.strictEqual(objMD['x-amz-meta-custom-md'], 'preserved-value');
assert.strictEqual(objMD['x-amz-meta-scal-s3-restore-attempt'], undefined);
return next();
}),
], done);
});

it('should keep x-amz-meta-scal-version-id when restoring on ingestion bucket', async () => {
const ingestionBucketName = `ingestion-restore-${Date.now()}`;
const params = { Bucket: ingestionBucketName, Key: objectName };
let putVersionId;
const fakeMetadataArchivePromise = promisify(fakeMetadataArchive);
const putObjectVersionPromise = promisify(putObjectVersion);
const getMetadataPromise = promisify(getMetadata);
try {
await s3.send(new CreateBucketCommand({
Bucket: ingestionBucketName,
CreateBucketConfiguration: {
LocationConstraint: 'us-east-2:ingest',
},
}));

const putRes = await s3.send(new PutObjectCommand(params));
putVersionId = putRes.VersionId;

await fakeMetadataArchivePromise(ingestionBucketName, objectName, putVersionId, archive);

await putObjectVersionPromise(s3, params, putVersionId);

const restoredObjMD = await getMetadataPromise(
ingestionBucketName, objectName, putVersionId);

assert.strictEqual(restoredObjMD['x-amz-meta-scal-version-id'], putVersionId);
} finally {
await bucketUtil.emptyMany([ingestionBucketName]).catch(() => {});
await bucketUtil.deleteMany([ingestionBucketName]).catch(() => {});
}
});
});
});
});
Loading
Loading