@@ -3,8 +3,7 @@ const async = require('async');
33const constants = require ( '../../../../constants' ) ;
44const { data } = require ( '../../../data/wrapper' ) ;
55const locationConstraintCheck = require ( '../object/locationConstraintCheck' ) ;
6- const { standardMetadataValidateBucketAndObj } =
7- require ( '../../../metadata/metadataUtils' ) ;
6+ const metadataUtils = require ( '../../../metadata/metadataUtils' ) ;
87const { validateQuotas } = require ( '../quotas/quotaUtils' ) ;
98const services = require ( '../../../services' ) ;
109const metadata = require ( '../../../metadata/wrapper' ) ;
@@ -30,7 +29,7 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
3029
3130 async . waterfall ( [
3231 function checkDestBucketVal ( next ) {
33- standardMetadataValidateBucketAndObj ( metadataValParams , authzIdentityResult , log ,
32+ metadataUtils . standardMetadataValidateBucketAndObj ( metadataValParams , authzIdentityResult , log ,
3433 ( err , destinationBucket , objectMD ) => {
3534 if ( err ) {
3635 log . error ( 'error validating request' , { error : err } ) ;
@@ -61,7 +60,7 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
6160 } ) ;
6261 } ,
6362 function abortExternalMpu ( mpuBucket , mpuOverviewObj , destBucket , objectMD ,
64- next ) {
63+ next ) {
6564 const location = mpuOverviewObj . controllingLocationConstraint ;
6665 const originalIdentityAuthzResults = request . actionImplicitDenies ;
6766 // eslint-disable-next-line no-param-reassign
@@ -93,29 +92,82 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
9392 skipDataDelete ) ;
9493 } ) ;
9594 } ,
96- function deleteObjectMetadata ( mpuBucket , storedParts , destBucket , objectMD , skipDataDelete , next ) {
97- if ( ! objectMD || metadataValMPUparams . uploadId !== objectMD . uploadId ) {
95+ // During Abort, we dynamically detect if the previous CompleteMPU call
96+ // created potential object metadata wrongly, e.g. by creating
97+ // an object version when some of the parts are missing.
98+ // By passing a null objectMD, we tell the subsequent steps
99+ // to skip the cleanup.
100+ // Another approach is possible, but not supported by all backends:
101+ // to honor the uploadId filter in standardMetadataValidateBucketAndObj
102+ // ensuring the objMD returned has the right uploadId. But this is not
103+ // supported by Metadata.
104+ function findObjectToCleanup ( mpuBucket , storedParts , destBucket ,
105+ objectMD , skipDataDelete , next ) {
106+ if ( ! objectMD ) {
107+ return next ( null , mpuBucket , storedParts , destBucket , null , skipDataDelete ) ;
108+ }
109+
110+ // If objectMD exists and has matching uploadId, use it directly
111+ // This handles all non-versioned cases, and some versioned cases.
112+ if ( objectMD . uploadId === uploadId ) {
98113 return next ( null , mpuBucket , storedParts , destBucket , objectMD , skipDataDelete ) ;
99114 }
100- // In case there has been an error during cleanup after a complete MPU
101- // (e.g. failure to delete MPU MD in shadow bucket),
102- // we need to ensure that the MPU metadata is deleted.
115+
116+ // If bucket is not versioned, no need to check versions:
117+ // as the uploadId is not the same, we skip the cleanup.
118+ if ( ! destBucket . isVersioningEnabled ( ) ) {
119+ return next ( null , mpuBucket , storedParts , destBucket , null , skipDataDelete ) ;
120+ }
121+
122+ // Otherwise, list all versions to find one with a matching uploadId.
123+ return services . findObjectVersionByUploadId ( bucketName , objectKey , uploadId , log , ( err , foundVersion ) => {
124+ if ( err ) {
125+ log . warn ( 'error finding object version by uploadId, proceeding without cleanup' , {
126+ error : err ,
127+ method : 'abortMultipartUpload.findObjectToCleanup' ,
128+ } ) ;
129+ // On error, continue the abort without an objectMD to clean up.
130+ return next ( null , mpuBucket , storedParts , destBucket , null , skipDataDelete ) ;
131+ }
132+ return next ( null , mpuBucket , storedParts , destBucket , foundVersion , skipDataDelete ) ;
133+ } ) ;
134+ } ,
135+ function deleteObjectMetadata ( mpuBucket , storedParts , destBucket , objectMD ,
136+ skipDataDelete , next ) {
137+ if ( ! objectMD ) {
138+ return next ( null , mpuBucket , storedParts , destBucket , objectMD , skipDataDelete ) ;
139+ }
140+
103141 log . debug ( 'Object has existing metadata, deleting them' , {
104142 method : 'abortMultipartUpload' ,
105143 bucketName,
106144 objectKey,
107145 uploadId,
108146 versionId : objectMD . versionId ,
109147 } ) ;
110- return metadata . deleteObjectMD ( bucketName , objectKey , { versionId : objectMD . versionId } , log , err => {
148+ return metadata . deleteObjectMD ( bucketName , objectKey , {
149+ versionId : objectMD . versionId ,
150+ } , log , err => {
111151 if ( err ) {
112- log . error ( 'error deleting object metadata' , { error : err } ) ;
152+ // Handle concurrent deletion of this object metadata
153+ if ( err . is ?. NoSuchKey ) {
154+ log . debug ( 'object metadata already deleted or does not exist' , {
155+ method : 'abortMultipartUpload' ,
156+ bucketName,
157+ objectKey,
158+ versionId : objectMD . versionId ,
159+ } ) ;
160+ } else {
161+ log . error ( 'error deleting object metadata' , { error : err } ) ;
162+ }
113163 }
114- return next ( err , mpuBucket , storedParts , destBucket , objectMD , skipDataDelete ) ;
164+ // Continue with the operation regardless of deletion success/failure
165+ // The important part is that we tried to clean up
166+ return next ( null , mpuBucket , storedParts , destBucket , objectMD , skipDataDelete ) ;
115167 } ) ;
116168 } ,
117169 function deleteData ( mpuBucket , storedParts , destBucket , objectMD ,
118- skipDataDelete , next ) {
170+ skipDataDelete , next ) {
119171 if ( skipDataDelete ) {
120172 return next ( null , mpuBucket , storedParts , destBucket ) ;
121173 }
@@ -126,16 +178,18 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
126178 return next ( null , mpuBucket , storedParts , destBucket ) ;
127179 }
128180
129- if ( objectMD && objectMD . location && objectMD . uploadId === metadataValMPUparams . uploadId ) {
181+ // Add object data locations if they exist
182+ if ( objectMD ?. location ) {
130183 const existingLocations = new Set ( locations . map ( loc => loc . key ) ) ;
131- const remainingObjectLocations = objectMD . location . filter ( loc => ! existingLocations . has ( loc . key ) ) ;
184+ const remainingObjectLocations = objectMD .
185+ location . filter ( loc => ! existingLocations . has ( loc . key ) ) ;
132186 locations . push ( ...remainingObjectLocations ) ;
133187 }
134188
135189 return async . eachLimit ( locations , 5 , ( loc , cb ) => {
136190 data . delete ( loc , log , err => {
137191 if ( err ) {
138- log . fatal ( 'delete ObjectPart failed' , { err } ) ;
192+ log . warn ( 'delete ObjectPart failed' , { err } ) ;
139193 }
140194 cb ( ) ;
141195 } ) ;
0 commit comments