diff --git a/lib/api/api.js b/lib/api/api.js index bf0d9f65d4..f1b32e61bb 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -146,6 +146,16 @@ const api = { const requestContexts = prepareRequestContexts(apiMethod, request, sourceBucket, sourceObject, sourceVersionId); + + if (apiMethod === 'completeMultipartUpload' || apiMethod === 'abortMultipartUpload') { + // Request account quotas explicitly for MPU requests, to consider parts cleanup + // NOTE: we need quota for these, but it will be evaluated at the end of the API, + // once the parts have actually been deleted (not via standardMetadataValidateBucketAndObj) + requestContexts.forEach(context => { + context._needQuota = true; // eslint-disable-line no-param-reassign + }); + } + // Extract all the _apiMethods and store them in an array const apiMethods = requestContexts ? requestContexts.map(context => context._apiMethod) : []; // Attach the names to the current request diff --git a/lib/api/apiUtils/object/abortMultipartUpload.js b/lib/api/apiUtils/object/abortMultipartUpload.js index 97eda8ff4e..c860c5a7f1 100644 --- a/lib/api/apiUtils/object/abortMultipartUpload.js +++ b/lib/api/apiUtils/object/abortMultipartUpload.js @@ -5,6 +5,7 @@ const { data } = require('../../../data/wrapper'); const locationConstraintCheck = require('../object/locationConstraintCheck'); const { standardMetadataValidateBucketAndObj } = require('../../../metadata/metadataUtils'); +const { validateQuotas } = require('../quotas/quotaUtils'); const services = require('../../../services'); const metadata = require('../../../metadata/wrapper'); @@ -138,7 +139,22 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log, } cb(); }); - }, () => next(null, mpuBucket, storedParts, destBucket)); + }, () => { + const length = locations.reduce((length, loc) => length + loc.size, 0); + return validateQuotas(request, destBucket, request.accountQuotas, + ['objectDelete'], 'objectDelete', -length, false, log, err => { + if (err) { + // Ignore error, as the data has been deleted already: only inflight count + // has not been updated, and will be eventually consistent anyway + log.warn('failed to update inflights', { + method: 'abortMultipartUpload', + locations, + error: err, + }); + } + next(null, mpuBucket, storedParts, destBucket); + }); + }); }, function deleteShadowObjectMetadata(mpuBucket, storedParts, destBucket, next) { let splitter = constants.splitter; diff --git a/lib/api/completeMultipartUpload.js b/lib/api/completeMultipartUpload.js index 0b5fc339d1..5117349a19 100644 --- a/lib/api/completeMultipartUpload.js +++ b/lib/api/completeMultipartUpload.js @@ -22,6 +22,7 @@ const locationKeysHaveChanged = require('./apiUtils/object/locationKeysHaveChanged'); const { setExpirationHeaders } = require('./apiUtils/object/expirationHeaders'); const { validatePutVersionId } = require('./apiUtils/object/coldStorage'); +const { validateQuotas } = require('./apiUtils/quotas/quotaUtils'); const versionIdUtils = versioning.VersionID; @@ -516,14 +517,27 @@ function completeMultipartUpload(authInfo, request, log, callback) { function batchDeleteExtraParts(extraPartLocations, destinationBucket, aggregateETag, generatedVersionId, next) { if (extraPartLocations && extraPartLocations.length > 0) { - return data.batchDelete(extraPartLocations, request.method, - null, log, err => { - if (err) { - return next(err); - } - return next(null, destinationBucket, aggregateETag, - generatedVersionId); + return data.batchDelete(extraPartLocations, request.method, null, log, err => { + if (err) { + return next(err); + } + + const length = extraPartLocations.reduce((length, loc) => length + loc.size, 0); + return validateQuotas(request, destinationBucket, request.accountQuotas, + ['objectDelete'], 'objectDelete', -length, false, log, err => { + if (err) { + // Ignore error, as the data has been deleted already: only inflight count + // has not been updated, and will be eventually consistent anyway + log.warn('failed to update inflights', { + method: 'completeMultipartUpload', + extraPartLocations, + error: err, + }); + } + return next(null, destinationBucket, aggregateETag, + generatedVersionId); }); + }); } return next(null, destinationBucket, aggregateETag, generatedVersionId);