Skip to content

Commit

Permalink
keep original MPU object etag after restore
Browse files Browse the repository at this point in the history
When restoring an MPU, the object data might be PUT with a
different number of parts, which in turn changes the etag of
the object as it is calculated based on the etag of each uploaded
part.

We now keep the same etag in case of a restore to avoid breaking the
expiration of the restored object as it checks the etag of the object
before expiting.

The new etag is kept in the metadata of the object and can be viewed
with a head-object operation when passing the "x-amz-scal-archive-info"
header.

Issue: CLDSRV-564
  • Loading branch information
Kerkesni committed Oct 1, 2024
1 parent 46ae534 commit 7e210b6
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 2 deletions.
2 changes: 2 additions & 0 deletions lib/api/apiUtils/object/coldStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function setArchiveInfoHeaders(objMD) {
if (objMD.archive.restoreCompletedAt) {
headers['x-amz-scal-restore-completed-at'] = new Date(objMD.archive.restoreCompletedAt).toUTCString();
headers['x-amz-scal-restore-will-expire-at'] = new Date(objMD.archive.restoreWillExpireAt).toUTCString();
headers['x-amz-scal-restored-etag'] = objMD['x-amz-restore'].etag;
}
}

Expand Down Expand Up @@ -190,6 +191,7 @@ function _updateObjectExpirationDate(objectMD, log) {
objectMD['x-amz-restore'] = {
'ongoing-request': false,
'expiry-date': expiryDate,
'etag': objectMD['x-amz-restore'].etag,
};
/* eslint-enable no-param-reassign */
}
Expand Down
8 changes: 8 additions & 0 deletions lib/api/apiUtils/object/versioning.js
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,14 @@ function overwritingVersioning(objMD, metadataStoreParams) {
// set correct originOp
metadataStoreParams.originOp = 's3:ObjectRestore:Completed';

// When restoring an MPU with different number of parts the etag changes
// as it's calculated based on the etags of the parts. We keep the original
// etag to allow proper exxpiration of the restored object.
if (metadataStoreParams.contentMD5 !== objMD['content-md5']) {
metadataStoreParams.restoredEtag = metadataStoreParams.contentMD5;
metadataStoreParams.contentMD5 = objMD['content-md5'];
}

// update restore
const days = objMD.archive?.restoreRequestedDays;
const now = Date.now();
Expand Down
4 changes: 3 additions & 1 deletion lib/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ const services = {
tagging, taggingCopy, replicationInfo, defaultRetention,
dataStoreName, creationTime, retentionMode, retentionDate,
legalHold, originOp, updateMicroVersionId, archive, oldReplayId,
deleteNullKey, amzStorageClass, overheadField, needOplogUpdate } = params;
deleteNullKey, amzStorageClass, overheadField, needOplogUpdate,
restoredEtag } = params;
log.trace('storing object in metadata');
assert.strictEqual(typeof bucketName, 'string');
const md = new ObjectMD();
Expand Down Expand Up @@ -200,6 +201,7 @@ const services = {
md.setAmzRestore({
'ongoing-request': false,
'expiry-date': archive.restoreWillExpireAt,
'etag': restoredEtag,
});
}

Expand Down
43 changes: 42 additions & 1 deletion tests/unit/api/apiUtils/versioning.js
Original file line number Diff line number Diff line change
Expand Up @@ -985,14 +985,55 @@ describe('versioning helpers', () => {
'restoreCompletedAt': new Date(now),
'restoreWillExpireAt': new Date(now + (days * scaledMsPerDay)),
}
}
},
},
{
description: 'Should keep contentMD5 of the original object',
objMD: {
'versionId': '2345678',
'creation-time': now,
'last-modified': now,
'originOp': 's3:PutObject',
'x-amz-storage-class': 'cold-location',
'content-md5': '123456789-5',
'acl': {},
'archive': {
'restoreRequestedDays': days,
'restoreRequestedAt': now,
archiveInfo
}
},
metadataStoreParams: {
'contentMD5': '987654321-3',
},
expectedRes: {
'creationTime': now,
'lastModifiedDate': now,
'updateMicroVersionId': true,
'originOp': 's3:ObjectRestore:Completed',
'contentMD5': '123456789-5',
'restoredEtag': '987654321-3',
'acl': {},
'taggingCopy': undefined,
'amzStorageClass': 'cold-location',
'archive': {
archiveInfo,
'restoreRequestedDays': 3,
'restoreRequestedAt': now,
'restoreCompletedAt': new Date(now),
'restoreWillExpireAt': new Date(now + (days * scaledMsPerDay)),
}
}
},
].forEach(testCase => {
it(testCase.description, () => {
const metadataStoreParams = {};
if (testCase.hasUserMD) {
metadataStoreParams.metaHeaders = {};
}
if (testCase.metadataStoreParams) {
Object.assign(metadataStoreParams, testCase.metadataStoreParams);
}
const options = overwritingVersioning(testCase.objMD, metadataStoreParams);
assert.deepStrictEqual(options.versionId, testCase.objMD.versionId);
assert.deepStrictEqual(metadataStoreParams, testCase.expectedRes);
Expand Down

0 comments on commit 7e210b6

Please sign in to comment.