Compare commits

...

1 Commits

Author SHA1 Message Date
Dora Korpar 9e2252d26a bf: S3C 2544 correct metrics if obj owner diff from delete requester 2020-02-06 15:08:37 -08:00
6 changed files with 313 additions and 137 deletions

View File

@ -27,9 +27,9 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
async.waterfall([ async.waterfall([
function checkDestBucketVal(next) { function checkDestBucketVal(next) {
metadataValidateBucketAndObj(metadataValParams, log, metadataValidateBucketAndObj(metadataValParams, log,
(err, destinationBucket) => { (err, destinationBucket, objMD) => {
if (err) { if (err) {
return next(err, destinationBucket); return next(err, destinationBucket, 0, objMD);
} }
if (destinationBucket.policies) { if (destinationBucket.policies) {
// TODO: Check bucket policies to see if user is granted // TODO: Check bucket policies to see if user is granted
@ -41,21 +41,22 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
metadataValMPUparams.requestType = metadataValMPUparams.requestType =
'bucketPolicyGoAhead'; 'bucketPolicyGoAhead';
} }
return next(null, destinationBucket); return next(null, destinationBucket, objMD);
}); });
}, },
function checkMPUval(destBucket, next) { function checkMPUval(destBucket, objMD, next) {
metadataValParams.log = log; metadataValParams.log = log;
services.metadataValidateMultipart(metadataValParams, services.metadataValidateMultipart(metadataValParams,
(err, mpuBucket, mpuOverviewObj) => { (err, mpuBucket, mpuOverviewObj) => {
if (err) { if (err) {
return next(err, destBucket); return next(err, destBucket, 0, objMD);
} }
return next(err, mpuBucket, mpuOverviewObj, destBucket); return next(err, mpuBucket, mpuOverviewObj, destBucket,
objMD);
}); });
}, },
function ifMultipleBackend(mpuBucket, mpuOverviewObj, destBucket, function ifMultipleBackend(mpuBucket, mpuOverviewObj, destBucket,
next) { objMD, next) {
if (config.backends.data === 'multiple') { if (config.backends.data === 'multiple') {
let location; let location;
// if controlling location constraint is not stored in object // if controlling location constraint is not stored in object
@ -66,7 +67,7 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
null, destBucket, log); null, destBucket, log);
if (backendInfoObj.err) { if (backendInfoObj.err) {
return process.nextTick(() => { return process.nextTick(() => {
next(backendInfoObj.err, destBucket); next(backendInfoObj.err, destBucket, 0, objMD);
}); });
} }
location = backendInfoObj.controllingLC; location = backendInfoObj.controllingLC;
@ -76,37 +77,37 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
return multipleBackendGateway.abortMPU(objectKey, uploadId, return multipleBackendGateway.abortMPU(objectKey, uploadId,
location, bucketName, log, (err, skipDataDelete) => { location, bucketName, log, (err, skipDataDelete) => {
if (err) { if (err) {
return next(err, destBucket); return next(err, destBucket, 0, objMD);
} }
return next(null, mpuBucket, destBucket, return next(null, mpuBucket, destBucket,
skipDataDelete); skipDataDelete, objMD);
}); });
} }
return next(null, mpuBucket, destBucket, false); return next(null, mpuBucket, destBucket, false, objMD);
}, },
function getPartLocations(mpuBucket, destBucket, skipDataDelete, function getPartLocations(mpuBucket, destBucket, skipDataDelete,
next) { objMD, next) {
services.getMPUparts(mpuBucket.getName(), uploadId, log, services.getMPUparts(mpuBucket.getName(), uploadId, log,
(err, result) => { (err, result) => {
if (err) { if (err) {
return next(err, destBucket); return next(err, destBucket, 0, objMD);
} }
const storedParts = result.Contents; const storedParts = result.Contents;
return next(null, mpuBucket, storedParts, destBucket, return next(null, mpuBucket, storedParts, destBucket,
skipDataDelete); skipDataDelete, objMD);
}); });
}, },
function deleteData(mpuBucket, storedParts, destBucket, function deleteData(mpuBucket, storedParts, destBucket,
skipDataDelete, next) { skipDataDelete, objMD, next) {
// for Azure we do not need to delete data // for Azure we do not need to delete data
if (skipDataDelete) { if (skipDataDelete) {
return next(null, mpuBucket, storedParts, destBucket); return next(null, mpuBucket, storedParts, destBucket, objMD);
} }
// The locations were sent to metadata as an array // The locations were sent to metadata as an array
// under partLocations. Pull the partLocations. // under partLocations. Pull the partLocations.
let locations = storedParts.map(item => item.value.partLocations); let locations = storedParts.map(item => item.value.partLocations);
if (locations.length === 0) { if (locations.length === 0) {
return next(null, mpuBucket, storedParts, destBucket); return next(null, mpuBucket, storedParts, destBucket, objMD);
} }
// flatten the array // flatten the array
locations = [].concat(...locations); locations = [].concat(...locations);
@ -117,9 +118,10 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
} }
cb(); cb();
}); });
}, () => next(null, mpuBucket, storedParts, destBucket)); }, () => next(null, mpuBucket, storedParts, destBucket, objMD));
}, },
function deleteMetadata(mpuBucket, storedParts, destBucket, next) { function deleteMetadata(mpuBucket, storedParts, destBucket, objMD,
next) {
let splitter = constants.splitter; let splitter = constants.splitter;
// BACKWARD: Remove to remove the old splitter // BACKWARD: Remove to remove the old splitter
if (mpuBucket.getMdBucketModelVersion() < 2) { if (mpuBucket.getMdBucketModelVersion() < 2) {
@ -135,7 +137,8 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
const keysToDelete = storedParts.map(item => item.key); const keysToDelete = storedParts.map(item => item.key);
keysToDelete.push(mpuOverviewKey); keysToDelete.push(mpuOverviewKey);
services.batchDeleteObjectMetadata(mpuBucket.getName(), services.batchDeleteObjectMetadata(mpuBucket.getName(),
keysToDelete, log, err => next(err, destBucket, partSizeSum)); keysToDelete, log, err => next(err, destBucket, partSizeSum,
objMD));
}, },
], callback); ], callback);
} }

View File

@ -180,9 +180,17 @@ function _parseXml(xmlToParse, next) {
*/ */
function getObjMetadataAndDelete(authInfo, canonicalID, request, function getObjMetadataAndDelete(authInfo, canonicalID, request,
bucketName, bucket, quietSetting, errorResults, inPlay, log, next) { bucketName, bucket, quietSetting, errorResults, inPlay, log, next) {
const deletedObjStats = {
requesterIsObjOwner: {
totalContentLengthDeleted: 0,
numOfObjectsRemoved: 0,
},
requesterNotObjOwner: {
totalContentLengthDeleted: 0,
numOfObjectsRemoved: 0,
},
};
const successfullyDeleted = []; const successfullyDeleted = [];
let totalContentLengthDeleted = 0;
let numOfObjectsRemoved = 0;
const skipError = new Error('skip'); const skipError = new Error('skip');
// doing 5 requests at a time. note that the data wrapper // doing 5 requests at a time. note that the data wrapper
@ -253,9 +261,19 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request,
errorResults.push({ entry, error: err }); errorResults.push({ entry, error: err });
return moveOn(); return moveOn();
} }
const reqObjDiffOwners = objMD && canonicalID !== objMD['owner-id'];
if (deleteInfo.deleted && objMD['content-length']) { if (deleteInfo.deleted && objMD['content-length']) {
numOfObjectsRemoved++; if (reqObjDiffOwners) {
totalContentLengthDeleted += objMD['content-length']; deletedObjStats.requesterNotObjOwner.numOfObjectsRemoved++;
deletedObjStats.requesterNotObjOwner.
totalContentLengthDeleted += objMD['content-length'];
deletedObjStats.requesterNotObjOwner.objOwnerCanonicalID =
objMD['owner-id'];
} else {
deletedObjStats.requesterIsObjOwner.numOfObjectsRemoved++;
deletedObjStats.requesterIsObjOwner.
totalContentLengthDeleted += objMD['content-length'];
}
} }
let isDeleteMarker; let isDeleteMarker;
let deleteMarkerVersionId; let deleteMarkerVersionId;
@ -269,7 +287,11 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request,
deleteMarkerVersionId = versionIdUtils.encode(versionId); deleteMarkerVersionId = versionIdUtils.encode(versionId);
// In this case we are putting a new object (i.e., the delete // In this case we are putting a new object (i.e., the delete
// marker), so we decrement the numOfObjectsRemoved value. // marker), so we decrement the numOfObjectsRemoved value.
numOfObjectsRemoved--; if (reqObjDiffOwners) {
deletedObjStats.requesterNotObjOwner.numOfObjectsRemoved--;
} else {
deletedObjStats.requesterIsObjOwner.numOfObjectsRemoved--;
}
// If trying to delete a delete marker, DeleteMarkerVersionId equals // If trying to delete a delete marker, DeleteMarkerVersionId equals
// deleteMarker's versionID and DeleteMarker equals true // deleteMarker's versionID and DeleteMarker equals true
} else if (objMD && objMD.isDeleteMarker) { } else if (objMD && objMD.isDeleteMarker) {
@ -283,9 +305,13 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request,
}, },
// end of forEach func // end of forEach func
err => { err => {
log.trace('finished deleting objects', { numOfObjectsRemoved }); const totalObjectsRemoved = deletedObjStats.requesterIsObjOwner.
return next(err, quietSetting, errorResults, numOfObjectsRemoved, numOfObjectsRemoved + deletedObjStats.requesterNotObjOwner.
successfullyDeleted, totalContentLengthDeleted, bucket); numberOfObjectsRemoved;
log.trace('finished deleting objects',
{ numOfObjectsRemoved: totalObjectsRemoved });
return next(err, quietSetting, errorResults, deletedObjStats,
successfullyDeleted, bucket);
}); });
} }
@ -473,8 +499,8 @@ function multiObjectDelete(authInfo, request, log, callback) {
bucketName, bucket, quietSetting, errorResults, inPlay, bucketName, bucket, quietSetting, errorResults, inPlay,
log, next); log, next);
}, },
], (err, quietSetting, errorResults, numOfObjectsRemoved, ], (err, quietSetting, errorResults, deletedObjStats,
successfullyDeleted, totalContentLengthDeleted, bucket) => { successfullyDeleted, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (err) { if (err) {
@ -483,13 +509,30 @@ function multiObjectDelete(authInfo, request, log, callback) {
const xml = _formatXML(quietSetting, errorResults, const xml = _formatXML(quietSetting, errorResults,
successfullyDeleted); successfullyDeleted);
const deletedKeys = successfullyDeleted.map(item => item.key); const deletedKeys = successfullyDeleted.map(item => item.key);
pushMetric('multiObjectDelete', log, { if (deletedObjStats.requesterIsObjOwner.numOfObjectsRemoved > 0) {
authInfo, pushMetric('multiObjectDelete', log, {
bucket: bucketName, authInfo,
keys: deletedKeys, bucket: bucketName,
byteLength: Number.parseInt(totalContentLengthDeleted, 10), keys: deletedKeys,
numberOfObjects: numOfObjectsRemoved, byteLength: Number.parseInt(deletedObjStats.requesterIsObjOwner.
}); totalContentLengthDeleted, 10),
numberOfObjects: deletedObjStats.requesterIsObjOwner.
numOfObjectsRemoved,
});
}
if (deletedObjStats.requesterNotObjOwner.numOfObjectsRemoved > 0) {
pushMetric('multiObjectDelete', log, {
authInfo,
bucket: bucketName,
keys: deletedKeys,
byteLength: Number.parseInt(deletedObjStats.
requesterNotObjOwner.totalContentLengthDeleted, 10),
numberOfObjects: deletedObjStats.requesterNotObjOwner.
numOfObjectsRemoved,
objOwnerCanonicalID: deletedObjStats.requesterNotObjOwner.
objOwnerCanonicalID,
});
}
return callback(null, xml, corsHeaders); return callback(null, xml, corsHeaders);
}); });
} }

View File

@ -24,7 +24,7 @@ function multipartDelete(authInfo, request, log, callback) {
const uploadId = request.query.uploadId; const uploadId = request.query.uploadId;
abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log, abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
(err, destinationBucket, partSizeSum) => { (err, destinationBucket, partSizeSum, objMD) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, destinationBucket); request.method, destinationBucket);
const location = destinationBucket ? const location = destinationBucket ?
@ -49,6 +49,7 @@ function multipartDelete(authInfo, request, log, callback) {
bucket: bucketName, bucket: bucketName,
keys: [objectKey], keys: [objectKey],
byteLength: partSizeSum, byteLength: partSizeSum,
objOwnerCanonicalID: objMD ? objMD['owner-id'] : '',
}); });
} }
return callback(null, corsHeaders); return callback(null, corsHeaders);

View File

@ -146,8 +146,12 @@ function objectDelete(authInfo, request, log, cb) {
resHeaders['x-amz-version-id'] = result.versionId === 'null' ? resHeaders['x-amz-version-id'] = result.versionId === 'null' ?
result.versionId : versionIdUtils.encode(result.versionId); result.versionId : versionIdUtils.encode(result.versionId);
} }
pushMetric('putDeleteMarkerObject', log, { authInfo, pushMetric('putDeleteMarkerObject', log, {
bucket: bucketName, keys: [objectKey] }); authInfo,
bucket: bucketName,
keys: [objectKey],
objOwnerCanonicalID: objectMD ? objectMD['owner-id'] : '',
});
} else { } else {
log.end().addDefaultFields({ log.end().addDefaultFields({
contentLength: objectMD['content-length'], contentLength: objectMD['content-length'],
@ -157,7 +161,9 @@ function objectDelete(authInfo, request, log, cb) {
bucket: bucketName, bucket: bucketName,
keys: [objectKey], keys: [objectKey],
byteLength: Number.parseInt(objectMD['content-length'], 10), byteLength: Number.parseInt(objectMD['content-length'], 10),
numberOfObjects: 1 }); numberOfObjects: 1,
objOwnerCanonicalID: objectMD ? objectMD['owner-id'] : '',
});
} }
return cb(err, resHeaders); return cb(err, resHeaders);
}); });

View File

@ -196,7 +196,8 @@ function listMetrics(metricType) {
*/ */
function pushMetric(action, log, metricObj) { function pushMetric(action, log, metricObj) {
const { bucket, keys, byteLength, newByteLength, const { bucket, keys, byteLength, newByteLength,
oldByteLength, numberOfObjects, authInfo, canonicalID } = metricObj; oldByteLength, numberOfObjects, authInfo, canonicalID,
objOwnerCanonicalID } = metricObj;
const utapiObj = { const utapiObj = {
bucket, bucket,
keys, keys,
@ -205,10 +206,14 @@ function pushMetric(action, log, metricObj) {
oldByteLength, oldByteLength,
numberOfObjects, numberOfObjects,
}; };
// If objOwnerCanonicalID is included, there is possible discrepancy between
// object owner and requester, so use objOwnerCanonicalID for metrics
// If `authInfo` is included by the API, get the account's canonical ID for // If `authInfo` is included by the API, get the account's canonical ID for
// account-level metrics and the shortId for user-level metrics. Otherwise // account-level metrics and the shortId for user-level metrics. Otherwise
// check if the canonical ID is already provided for account-level metrics. // check if the canonical ID is already provided for account-level metrics.
if (authInfo) { if (objOwnerCanonicalID) {
utapiObj.accountId = objOwnerCanonicalID;
} else if (authInfo) {
utapiObj.accountId = authInfo.getCanonicalID(); utapiObj.accountId = authInfo.getCanonicalID();
utapiObj.userId = authInfo.isRequesterAnIAMUser() ? utapiObj.userId = authInfo.isRequesterAnIAMUser() ?
authInfo.getShortid() : undefined; authInfo.getShortid() : undefined;

View File

@ -9,76 +9,92 @@ const { ds } = require('../../../lib/data/in_memory/backend');
const DummyRequest = require('../DummyRequest'); const DummyRequest = require('../DummyRequest');
const { bucketPut } = require('../../../lib/api/bucketPut'); const { bucketPut } = require('../../../lib/api/bucketPut');
const objectPut = require('../../../lib/api/objectPut'); const objectPut = require('../../../lib/api/objectPut');
const bucketPutACL = require('../../../lib/api/bucketPutACL');
const log = new DummyRequestLogger(); const log = new DummyRequestLogger();
const canonicalID = 'accessKey1'; const canonicalID = 'accessKey1';
const altCanonicalID = 'accessKey2';
const authInfo = makeAuthInfo(canonicalID); const authInfo = makeAuthInfo(canonicalID);
const altAuthInfo = makeAuthInfo(altCanonicalID);
const namespace = 'default'; const namespace = 'default';
const bucketName = 'bucketname'; const bucketName = 'bucketname';
const postBody = Buffer.from('I am a body', 'utf8'); const postBody = Buffer.from('I am a body', 'utf8');
const contentLength = 2 * postBody.length; const contentLength = 2 * postBody.length;
const objectKey1 = 'objectName1'; const objectKey1 = 'objectName1';
const objectKey2 = 'objectName2'; const objectKey2 = 'objectName2';
const objectKey3 = 'objectName3';
const testBucketPutRequest = new DummyRequest({ const testBucketPutRequest = new DummyRequest({
bucketName, bucketName,
namespace, namespace,
headers: {}, headers: {},
url: `/${bucketName}`, url: `/${bucketName}`,
}); });
const testPutObjectRequest1 = new DummyRequest({
bucketName,
namespace,
objectKey: objectKey1,
headers: {},
url: `/${bucketName}/${objectKey1}`,
}, postBody);
const testPutObjectRequest2 = new DummyRequest({
bucketName,
namespace,
objectKey: objectKey2,
headers: {},
url: `/${bucketName}/${objectKey2}`,
}, postBody);
const testPutObjectRequest3 = new DummyRequest({
bucketName,
namespace,
objectKey: objectKey3,
headers: {},
url: `/${bucketName}/${objectKey3}`,
}, postBody);
describe('getObjMetadataAndDelete function for multiObjectDelete', () => { describe('getObjMetadataAndDelete function for multiObjectDelete', () => {
let testPutObjectRequest1;
let testPutObjectRequest2;
const request = new DummyRequest({ const request = new DummyRequest({
headers: {}, headers: {},
parsedContentLength: contentLength, parsedContentLength: contentLength,
}, postBody); }, postBody);
const bucket = { getVersioningConfiguration: () => null }; const bucket = { getVersioningConfiguration: () => null };
beforeEach(done => { describe('bucket and objects belong to same account', () => {
cleanup(); beforeEach(done => {
testPutObjectRequest1 = new DummyRequest({ cleanup();
bucketName, bucketPut(authInfo, testBucketPutRequest, log, () => {
namespace, objectPut(authInfo, testPutObjectRequest1, undefined,
objectKey: objectKey1, log, () => {
headers: {}, objectPut(authInfo, testPutObjectRequest2, undefined,
url: `/${bucketName}/${objectKey1}`, log, () => {
}, postBody); assert.strictEqual(metadata.keyMaps
testPutObjectRequest2 = new DummyRequest({ .get(bucketName)
bucketName, .has(objectKey1), true);
namespace, assert.strictEqual(metadata.keyMaps
objectKey: objectKey2, .get(bucketName)
headers: {}, .has(objectKey2), true);
url: `/${bucketName}/${objectKey2}`, done();
}, postBody); });
bucketPut(authInfo, testBucketPutRequest, log, () => {
objectPut(authInfo, testPutObjectRequest1,
undefined, log, () => {
objectPut(authInfo, testPutObjectRequest2,
undefined, log, () => {
assert.strictEqual(metadata.keyMaps
.get(bucketName)
.has(objectKey1), true);
assert.strictEqual(metadata.keyMaps
.get(bucketName)
.has(objectKey2), true);
done();
});
}); });
});
}); });
});
it('should successfully get object metadata and then ' + it('should successfully get object metadata and then ' +
'delete metadata and data', done => { 'delete metadata and data', done => {
getObjMetadataAndDelete(authInfo, 'foo', request, bucketName, bucket, getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
true, [], [{ key: objectKey1 }, { key: objectKey2 }], log, request, bucketName, bucket, true, [],
(err, quietSetting, errorResults, numOfObjects, [{ key: objectKey1 }, { key: objectKey2 }], log,
successfullyDeleted, totalContentLengthDeleted) => { (err, quietSetting, errorResults, deletedObjStats) => {
assert.ifError(err); assert.ifError(err);
assert.strictEqual(quietSetting, true); assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, []); assert.deepStrictEqual(errorResults, []);
assert.strictEqual(numOfObjects, 2); assert.strictEqual(deletedObjStats.requesterIsObjOwner.
assert.strictEqual(totalContentLengthDeleted, contentLength); numOfObjectsRemoved, 2);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
numOfObjectsRemoved, 0);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
totalContentLengthDeleted, contentLength);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
totalContentLengthDeleted, 0);
assert.strictEqual(metadata.keyMaps.get(bucketName) assert.strictEqual(metadata.keyMaps.get(bucketName)
.has(objectKey1), false); .has(objectKey1), false);
assert.strictEqual(metadata.keyMaps.get(bucketName) assert.strictEqual(metadata.keyMaps.get(bucketName)
@ -91,37 +107,42 @@ describe('getObjMetadataAndDelete function for multiObjectDelete', () => {
done(); done();
}, 20); }, 20);
}); });
}); });
it('should return success results if no such key', done => { it('should return success results if no such key', done => {
getObjMetadataAndDelete(authInfo, 'foo', request, bucketName, bucket, getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
true, [], [{ key: 'madeup1' }, { key: 'madeup2' }], log, request, bucketName, bucket, true, [],
(err, quietSetting, errorResults, numOfObjects, [{ key: 'madeup1' }, { key: 'madeup2' }], log,
successfullyDeleted, totalContentLengthDeleted) => { (err, quietSetting, errorResults, deletedObjStats) => {
assert.ifError(err); assert.ifError(err);
assert.strictEqual(quietSetting, true); assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, []); assert.deepStrictEqual(errorResults, []);
assert.strictEqual(numOfObjects, 0); assert.strictEqual(deletedObjStats.requesterIsObjOwner.
assert.strictEqual(totalContentLengthDeleted, numOfObjectsRemoved, 0);
0); assert.strictEqual(deletedObjStats.requesterNotObjOwner.
numOfObjectsRemoved, 0);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
totalContentLengthDeleted, 0);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
totalContentLengthDeleted, 0);
assert.strictEqual(metadata.keyMaps.get(bucketName) assert.strictEqual(metadata.keyMaps.get(bucketName)
.has(objectKey1), true); .has(objectKey1), true);
assert.strictEqual(metadata.keyMaps.get(bucketName) assert.strictEqual(metadata.keyMaps.get(bucketName)
.has(objectKey2), true); .has(objectKey2), true);
done(); done();
}); });
}); });
it('should return error results if err from metadata getting object' + it('should return error results if err from metadata getting object' +
'is error other than NoSuchKey', done => { 'is error other than NoSuchKey', done => {
// we fake an error by calling on an imaginary bucket // we fake an error by calling on an imaginary bucket
// even though the getObjMetadataAndDelete function would // even though the getObjMetadataAndDelete function would
// never be called if there was no bucket (would error out earlier // never be called if there was no bucket (would error out earlier
// in API) // in API)
getObjMetadataAndDelete(authInfo, 'foo', request, 'madeupbucket', getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
bucket, true, [], [{ key: objectKey1 }, { key: objectKey2 }], log, request, 'madeupbucket', bucket, true, [],
(err, quietSetting, errorResults, numOfObjects, [{ key: objectKey1 }, { key: objectKey2 }], log,
successfullyDeleted, totalContentLengthDeleted) => { (err, quietSetting, errorResults, deletedObjStats) => {
assert.ifError(err); assert.ifError(err);
assert.strictEqual(quietSetting, true); assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, [ assert.deepStrictEqual(errorResults, [
@ -134,54 +155,151 @@ describe('getObjMetadataAndDelete function for multiObjectDelete', () => {
error: errors.NoSuchBucket, error: errors.NoSuchBucket,
}, },
]); ]);
assert.strictEqual(totalContentLengthDeleted, assert.strictEqual(deletedObjStats.requesterIsObjOwner.
0); totalContentLengthDeleted, 0);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
totalContentLengthDeleted, 0);
assert.strictEqual(metadata.keyMaps.get(bucketName) assert.strictEqual(metadata.keyMaps.get(bucketName)
.has(objectKey1), true); .has(objectKey1), true);
assert.strictEqual(metadata.keyMaps.get(bucketName) assert.strictEqual(metadata.keyMaps.get(bucketName)
.has(objectKey2), true); .has(objectKey2), true);
done(); done();
}); });
});
it('should return no error or success results if no objects in play',
done => {
getObjMetadataAndDelete(authInfo, 'foo', request, bucketName,
bucket, true, [], [], log,
(err, quietSetting, errorResults, numOfObjects,
successfullyDeleted, totalContentLengthDeleted) => {
assert.ifError(err);
assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, []);
assert.strictEqual(numOfObjects, 0);
assert.strictEqual(totalContentLengthDeleted,
0);
done();
});
}); });
it('should pass along error results', done => { it('should return no error or success results if no objects in play',
const errorResultsSample = [ done => {
{ getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
key: 'somekey1', request, bucketName, bucket, true, [], [], log,
error: errors.AccessDenied, (err, quietSetting, errorResults, deletedObjStats) => {
}, assert.ifError(err);
{ assert.strictEqual(quietSetting, true);
key: 'somekey2', assert.deepStrictEqual(errorResults, []);
error: errors.AccessDenied, assert.strictEqual(deletedObjStats.requesterIsObjOwner.
}, numOfObjectsRemoved, 0);
]; assert.strictEqual(deletedObjStats.requesterIsObjOwner.
getObjMetadataAndDelete(authInfo, 'foo', request, bucketName, bucket, numOfObjectsRemoved, 0);
true, errorResultsSample, assert.strictEqual(deletedObjStats.requesterIsObjOwner.
totalContentLengthDeleted, 0);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
totalContentLengthDeleted, 0);
done();
});
});
it('should pass along error results', done => {
const errorResultsSample = [
{
key: 'somekey1',
error: errors.AccessDenied,
},
{
key: 'somekey2',
error: errors.AccessDenied,
},
];
getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
request, bucketName, bucket, true, errorResultsSample,
[{ key: objectKey1 }, { key: objectKey2 }], log, [{ key: objectKey1 }, { key: objectKey2 }], log,
(err, quietSetting, errorResults, numOfObjects, (err, quietSetting, errorResults, deletedObjStats) => {
successfullyDeleted, totalContentLengthDeleted) => {
assert.ifError(err); assert.ifError(err);
assert.strictEqual(quietSetting, true); assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, errorResultsSample); assert.deepStrictEqual(errorResults, errorResultsSample);
assert.strictEqual(numOfObjects, 2); assert.strictEqual(deletedObjStats.requesterIsObjOwner.
assert.strictEqual(totalContentLengthDeleted, contentLength); numOfObjectsRemoved, 2);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
numOfObjectsRemoved, 0);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
totalContentLengthDeleted, contentLength);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
totalContentLengthDeleted, 0);
done(); done();
}); });
});
});
describe('bucket and objects belong to different accounts', () => {
beforeEach(done => {
cleanup();
bucketPut(authInfo, testBucketPutRequest, log, () => {
const testACLRequest = {
bucketName,
namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` },
post: '<AccessControlPolicy xmlns=' +
'"http://s3.amazonaws.com/doc/2006-03-01/">' +
'<Owner>' +
`<ID>${authInfo.getCanonicalID()}</ID>` +
'<DisplayName>OwnerDisplayName</DisplayName>' +
'</Owner>' +
'<AccessControlList>' +
'<Grant>' +
'<Grantee xsi:type="CanonicalUser">' +
`<ID>${altAuthInfo.getCanonicalID()}</ID>` +
'<DisplayName>OwnerDisplayName</DisplayName>' +
'</Grantee>' +
'<Permission>WRITE</Permission>' +
'</Grant>' +
'</AccessControlList>' +
'</AccessControlPolicy>',
url: '/?acl',
query: { acl: '' },
};
bucketPutACL(authInfo, testACLRequest, log, () => {
objectPut(authInfo, testPutObjectRequest1, undefined,
log, () => {
objectPut(altAuthInfo, testPutObjectRequest2, undefined,
log, () => {
objectPut(altAuthInfo, testPutObjectRequest3,
undefined, log, () => {
assert.strictEqual(metadata.keyMaps
.get(bucketName)
.has(objectKey1), true);
assert.strictEqual(metadata.keyMaps
.get(bucketName)
.has(objectKey2), true);
assert.strictEqual(metadata.keyMaps
.get(bucketName)
.has(objectKey3), true);
done();
});
});
});
});
});
});
it('should successfully get object metadata and then ' +
'delete metadata and data', done => {
getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
request, bucketName, bucket, true, [],
[{ key: objectKey1 }, { key: objectKey2 }, { key: objectKey3 }],
log, (err, quietSetting, errorResults, deletedObjStats) => {
assert.ifError(err);
assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, []);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
numOfObjectsRemoved, 1);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
numOfObjectsRemoved, 2);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
totalContentLengthDeleted, postBody.length);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
totalContentLengthDeleted, contentLength);
assert.strictEqual(metadata.keyMaps.get(bucketName)
.has(objectKey1), false);
assert.strictEqual(metadata.keyMaps.get(bucketName)
.has(objectKey2), false);
assert.strictEqual(metadata.keyMaps.get(bucketName)
.has(objectKey3), false);
// call to delete data is async so wait 20 ms to check
// that data deleted
setTimeout(() => {
// eslint-disable-next-line
assert.deepStrictEqual(ds, [ , , , , ]);
done();
}, 20);
});
});
}); });
}); });