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

View File

@ -180,9 +180,17 @@ function _parseXml(xmlToParse, next) {
*/
function getObjMetadataAndDelete(authInfo, canonicalID, request,
bucketName, bucket, quietSetting, errorResults, inPlay, log, next) {
const deletedObjStats = {
requesterIsObjOwner: {
totalContentLengthDeleted: 0,
numOfObjectsRemoved: 0,
},
requesterNotObjOwner: {
totalContentLengthDeleted: 0,
numOfObjectsRemoved: 0,
},
};
const successfullyDeleted = [];
let totalContentLengthDeleted = 0;
let numOfObjectsRemoved = 0;
const skipError = new Error('skip');
// 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 });
return moveOn();
}
const reqObjDiffOwners = objMD && canonicalID !== objMD['owner-id'];
if (deleteInfo.deleted && objMD['content-length']) {
numOfObjectsRemoved++;
if (reqObjDiffOwners) {
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 deleteMarkerVersionId;
@ -269,7 +287,11 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request,
deleteMarkerVersionId = versionIdUtils.encode(versionId);
// In this case we are putting a new object (i.e., the delete
// 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
// deleteMarker's versionID and DeleteMarker equals true
} else if (objMD && objMD.isDeleteMarker) {
@ -283,9 +305,13 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request,
},
// end of forEach func
err => {
log.trace('finished deleting objects', { numOfObjectsRemoved });
return next(err, quietSetting, errorResults, numOfObjectsRemoved,
successfullyDeleted, totalContentLengthDeleted, bucket);
const totalObjectsRemoved = deletedObjStats.requesterIsObjOwner.
numOfObjectsRemoved + deletedObjStats.requesterNotObjOwner.
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,
log, next);
},
], (err, quietSetting, errorResults, numOfObjectsRemoved,
successfullyDeleted, totalContentLengthDeleted, bucket) => {
], (err, quietSetting, errorResults, deletedObjStats,
successfullyDeleted, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket);
if (err) {
@ -483,13 +509,30 @@ function multiObjectDelete(authInfo, request, log, callback) {
const xml = _formatXML(quietSetting, errorResults,
successfullyDeleted);
const deletedKeys = successfullyDeleted.map(item => item.key);
if (deletedObjStats.requesterIsObjOwner.numOfObjectsRemoved > 0) {
pushMetric('multiObjectDelete', log, {
authInfo,
bucket: bucketName,
keys: deletedKeys,
byteLength: Number.parseInt(totalContentLengthDeleted, 10),
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);
});
}

View File

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

View File

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

View File

@ -196,7 +196,8 @@ function listMetrics(metricType) {
*/
function pushMetric(action, log, metricObj) {
const { bucket, keys, byteLength, newByteLength,
oldByteLength, numberOfObjects, authInfo, canonicalID } = metricObj;
oldByteLength, numberOfObjects, authInfo, canonicalID,
objOwnerCanonicalID } = metricObj;
const utapiObj = {
bucket,
keys,
@ -205,10 +206,14 @@ function pushMetric(action, log, metricObj) {
oldByteLength,
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
// account-level metrics and the shortId for user-level metrics. Otherwise
// 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.userId = authInfo.isRequesterAnIAMUser() ?
authInfo.getShortid() : undefined;

View File

@ -9,53 +9,63 @@ const { ds } = require('../../../lib/data/in_memory/backend');
const DummyRequest = require('../DummyRequest');
const { bucketPut } = require('../../../lib/api/bucketPut');
const objectPut = require('../../../lib/api/objectPut');
const bucketPutACL = require('../../../lib/api/bucketPutACL');
const log = new DummyRequestLogger();
const canonicalID = 'accessKey1';
const altCanonicalID = 'accessKey2';
const authInfo = makeAuthInfo(canonicalID);
const altAuthInfo = makeAuthInfo(altCanonicalID);
const namespace = 'default';
const bucketName = 'bucketname';
const postBody = Buffer.from('I am a body', 'utf8');
const contentLength = 2 * postBody.length;
const objectKey1 = 'objectName1';
const objectKey2 = 'objectName2';
const objectKey3 = 'objectName3';
const testBucketPutRequest = new DummyRequest({
bucketName,
namespace,
headers: {},
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', () => {
let testPutObjectRequest1;
let testPutObjectRequest2;
const request = new DummyRequest({
headers: {},
parsedContentLength: contentLength,
}, postBody);
const bucket = { getVersioningConfiguration: () => null };
describe('bucket and objects belong to same account', () => {
beforeEach(done => {
cleanup();
testPutObjectRequest1 = new DummyRequest({
bucketName,
namespace,
objectKey: objectKey1,
headers: {},
url: `/${bucketName}/${objectKey1}`,
}, postBody);
testPutObjectRequest2 = new DummyRequest({
bucketName,
namespace,
objectKey: objectKey2,
headers: {},
url: `/${bucketName}/${objectKey2}`,
}, postBody);
bucketPut(authInfo, testBucketPutRequest, log, () => {
objectPut(authInfo, testPutObjectRequest1,
undefined, log, () => {
objectPut(authInfo, testPutObjectRequest2,
undefined, log, () => {
objectPut(authInfo, testPutObjectRequest1, undefined,
log, () => {
objectPut(authInfo, testPutObjectRequest2, undefined,
log, () => {
assert.strictEqual(metadata.keyMaps
.get(bucketName)
.has(objectKey1), true);
@ -70,15 +80,21 @@ describe('getObjMetadataAndDelete function for multiObjectDelete', () => {
it('should successfully get object metadata and then ' +
'delete metadata and data', done => {
getObjMetadataAndDelete(authInfo, 'foo', request, bucketName, bucket,
true, [], [{ key: objectKey1 }, { key: objectKey2 }], log,
(err, quietSetting, errorResults, numOfObjects,
successfullyDeleted, totalContentLengthDeleted) => {
getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
request, bucketName, bucket, true, [],
[{ key: objectKey1 }, { key: objectKey2 }], log,
(err, quietSetting, errorResults, deletedObjStats) => {
assert.ifError(err);
assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, []);
assert.strictEqual(numOfObjects, 2);
assert.strictEqual(totalContentLengthDeleted, contentLength);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
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)
.has(objectKey1), false);
assert.strictEqual(metadata.keyMaps.get(bucketName)
@ -94,16 +110,21 @@ describe('getObjMetadataAndDelete function for multiObjectDelete', () => {
});
it('should return success results if no such key', done => {
getObjMetadataAndDelete(authInfo, 'foo', request, bucketName, bucket,
true, [], [{ key: 'madeup1' }, { key: 'madeup2' }], log,
(err, quietSetting, errorResults, numOfObjects,
successfullyDeleted, totalContentLengthDeleted) => {
getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
request, bucketName, bucket, true, [],
[{ key: 'madeup1' }, { key: 'madeup2' }], log,
(err, quietSetting, errorResults, deletedObjStats) => {
assert.ifError(err);
assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, []);
assert.strictEqual(numOfObjects, 0);
assert.strictEqual(totalContentLengthDeleted,
0);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
numOfObjectsRemoved, 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)
.has(objectKey1), true);
assert.strictEqual(metadata.keyMaps.get(bucketName)
@ -118,10 +139,10 @@ describe('getObjMetadataAndDelete function for multiObjectDelete', () => {
// even though the getObjMetadataAndDelete function would
// never be called if there was no bucket (would error out earlier
// in API)
getObjMetadataAndDelete(authInfo, 'foo', request, 'madeupbucket',
bucket, true, [], [{ key: objectKey1 }, { key: objectKey2 }], log,
(err, quietSetting, errorResults, numOfObjects,
successfullyDeleted, totalContentLengthDeleted) => {
getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
request, 'madeupbucket', bucket, true, [],
[{ key: objectKey1 }, { key: objectKey2 }], log,
(err, quietSetting, errorResults, deletedObjStats) => {
assert.ifError(err);
assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, [
@ -134,8 +155,10 @@ describe('getObjMetadataAndDelete function for multiObjectDelete', () => {
error: errors.NoSuchBucket,
},
]);
assert.strictEqual(totalContentLengthDeleted,
0);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
totalContentLengthDeleted, 0);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
totalContentLengthDeleted, 0);
assert.strictEqual(metadata.keyMaps.get(bucketName)
.has(objectKey1), true);
assert.strictEqual(metadata.keyMaps.get(bucketName)
@ -146,16 +169,20 @@ describe('getObjMetadataAndDelete function for multiObjectDelete', () => {
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) => {
getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
request, bucketName, bucket, true, [], [], log,
(err, quietSetting, errorResults, deletedObjStats) => {
assert.ifError(err);
assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, []);
assert.strictEqual(numOfObjects, 0);
assert.strictEqual(totalContentLengthDeleted,
0);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
numOfObjectsRemoved, 0);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
numOfObjectsRemoved, 0);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
totalContentLengthDeleted, 0);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
totalContentLengthDeleted, 0);
done();
});
});
@ -171,17 +198,108 @@ describe('getObjMetadataAndDelete function for multiObjectDelete', () => {
error: errors.AccessDenied,
},
];
getObjMetadataAndDelete(authInfo, 'foo', request, bucketName, bucket,
true, errorResultsSample,
getObjMetadataAndDelete(authInfo, authInfo.getCanonicalID(),
request, bucketName, bucket, true, errorResultsSample,
[{ key: objectKey1 }, { key: objectKey2 }], log,
(err, quietSetting, errorResults, numOfObjects,
successfullyDeleted, totalContentLengthDeleted) => {
(err, quietSetting, errorResults, deletedObjStats) => {
assert.ifError(err);
assert.strictEqual(quietSetting, true);
assert.deepStrictEqual(errorResults, errorResultsSample);
assert.strictEqual(numOfObjects, 2);
assert.strictEqual(totalContentLengthDeleted, contentLength);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
numOfObjectsRemoved, 2);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
numOfObjectsRemoved, 0);
assert.strictEqual(deletedObjStats.requesterIsObjOwner.
totalContentLengthDeleted, contentLength);
assert.strictEqual(deletedObjStats.requesterNotObjOwner.
totalContentLengthDeleted, 0);
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);
});
});
});
});