-
Notifications
You must be signed in to change notification settings - Fork 241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bugfix/cldsrv 514 handling of metadata storage errors #5547
base: development/8.7
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -197,6 +197,8 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo, | |
/* eslint-disable camelcase */ | ||
const dontSkipBackend = externalBackends; | ||
/* eslint-enable camelcase */ | ||
let dataGetInfoArr; | ||
let needsToCleanStorage = false; | ||
|
||
const requestLogger = | ||
logger.newRequestLoggerFromSerializedUids(log.getSerializedUids()); | ||
|
@@ -250,7 +252,7 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo, | |
const prefixedDataStoreETag = dataStoreETag | ||
? `1:${dataStoreETag}` | ||
: `1:${calculatedHash}`; | ||
const dataGetInfoArr = [{ key, size, start: 0, dataStoreName, | ||
dataGetInfoArr = [{ key, size, start: 0, dataStoreName, | ||
dataStoreType, dataStoreETag: prefixedDataStoreETag, | ||
dataStoreVersionId }]; | ||
if (cipherBundle) { | ||
|
@@ -294,11 +296,30 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo, | |
if (options.extraMD) { | ||
Object.assign(metadataStoreParams, options.extraMD); | ||
} | ||
return _storeInMDandDeleteData(bucketName, infoArr, | ||
return _storeInMDandDeleteData(bucketName, dataGetInfoArr, | ||
cipherBundle, metadataStoreParams, | ||
options.dataToDelete, requestLogger, requestMethod, next); | ||
options.dataToDelete, requestLogger, requestMethod, (err, data) => { | ||
if (err) { | ||
needsToCleanStorage = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of introducing 2 variables, it seems we simply need to batchDelete here : since this is the only case where cleanup needs to happen... |
||
} | ||
return next(err, data); | ||
}); | ||
}, | ||
], callback); | ||
], (err, result) => { | ||
if (needsToCleanStorage && dataGetInfoArr) { | ||
return data.batchDelete(dataGetInfoArr, requestMethod, null, | ||
requestLogger, _err => { | ||
if (_err) { | ||
log.warn('potential orphan in storage', { | ||
error: _err, | ||
objects: dataGetInfoArr, | ||
}); | ||
} | ||
return callback(err, result); | ||
}); | ||
} | ||
return callback(err, result); | ||
}); | ||
} | ||
|
||
module.exports = createAndStoreObject; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -364,7 +364,7 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request, | |
objMD, authInfo, canonicalID, null, request, | ||
deleteInfo.newDeleteMarker, null, overheadField, log, | ||
's3:ObjectRemoved:DeleteMarkerCreated', (err, result) => | ||
callback(err, objMD, deleteInfo, result.versionId)); | ||
callback(err, objMD, deleteInfo, result?.versionId)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note for reviewers: this code is not strictly related to this PR, but when the function returns an error, |
||
}, | ||
], (err, objMD, deleteInfo, versionId) => { | ||
if (err === skipError) { | ||
|
@@ -428,7 +428,15 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request, | |
} | ||
|
||
return async.each(chunks, (chunk, done) => data.batchDelete(chunk, null, null, | ||
logger.newRequestLoggerFromSerializedUids(log.getSerializedUids()), done), | ||
logger.newRequestLoggerFromSerializedUids(log.getSerializedUids()), err => { | ||
if (err) { | ||
log.warn('potential orphan in storage', { | ||
error: err, | ||
objects: chunk, | ||
}); | ||
} | ||
return done(); | ||
}), | ||
err => { | ||
if (err) { | ||
log.error('error deleting objects from data backend', { error: err }); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,6 +90,8 @@ function objectPutCopyPart(authInfo, request, sourceBucket, | |
partNumber: paddedPartNumber, | ||
uploadId, | ||
}; | ||
let needsToCleanStorage = false; | ||
let partLocations; | ||
|
||
return async.waterfall([ | ||
function checkDestAuth(next) { | ||
|
@@ -279,6 +281,7 @@ function objectPutCopyPart(authInfo, request, sourceBucket, | |
} | ||
return next(error, destBucketMD); | ||
} | ||
partLocations = locations; | ||
return next(null, destBucketMD, locations, eTag, | ||
copyObjectSize, sourceVerId, serverSideEncryption, | ||
lastModified, splitter); | ||
|
@@ -331,6 +334,7 @@ function objectPutCopyPart(authInfo, request, sourceBucket, | |
if (err) { | ||
log.debug('error storing new metadata', | ||
{ error: err, method: 'storeNewPartMetadata' }); | ||
needsToCleanStorage = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is the only case, might as well cleanup here: avoiding the global variables and keeping the existing waterfall logic... |
||
return next(err); | ||
} | ||
return next(null, locations, oldLocations, destBucketMD, totalHash, | ||
|
@@ -411,6 +415,20 @@ function objectPutCopyPart(authInfo, request, sourceBucket, | |
{ error: err }); | ||
monitoring.promMetrics('PUT', destBucketName, err.code, | ||
'putObjectCopyPart'); | ||
if (needsToCleanStorage && partLocations) { | ||
const delLog = logger.newRequestLoggerFromSerializedUids( | ||
log.getSerializedUids()); | ||
return data.batchDelete(partLocations, request.method, null, delLog, | ||
_err => { | ||
if (_err) { | ||
log.warn('potential orphan in storage', { | ||
error: _err, | ||
objects: partLocations, | ||
}); | ||
} | ||
return callback(err, null, corsHeaders); | ||
}); | ||
} | ||
return callback(err, null, corsHeaders); | ||
} | ||
const xml = [ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,6 +104,9 @@ function objectPutPart(authInfo, request, streamingV4Params, log, | |
const mpuBucketName = `${constants.mpuBucketPrefix}${bucketName}`; | ||
const { objectKey } = request; | ||
const originalIdentityAuthzResults = request.actionImplicitDenies; | ||
let needsToCleanStorage = false; | ||
let partLocations; | ||
let objectLocationConstraint; | ||
|
||
return async.waterfall([ | ||
// Get the destination bucket. | ||
|
@@ -195,7 +198,7 @@ function objectPutPart(authInfo, request, streamingV4Params, log, | |
return next(errors.AccessDenied, destinationBucket); | ||
} | ||
|
||
const objectLocationConstraint = | ||
objectLocationConstraint = | ||
res.controllingLocationConstraint; | ||
return next(null, destinationBucket, | ||
objectLocationConstraint, | ||
|
@@ -306,7 +309,7 @@ function objectPutPart(authInfo, request, streamingV4Params, log, | |
prevObjectSize, oldLocations, objectLocationConstraint, splitter, next) => { | ||
// Use an array to be consistent with objectPutCopyPart where there | ||
// could be multiple locations. | ||
const partLocations = [dataGetInfo]; | ||
partLocations = [dataGetInfo]; | ||
if (cipherBundle) { | ||
const { algorithm, masterKeyId, cryptoScheme, | ||
cipheredDataKey } = cipherBundle; | ||
|
@@ -333,6 +336,7 @@ function objectPutPart(authInfo, request, streamingV4Params, log, | |
error: err, | ||
method: 'objectPutPart::metadata.putObjectMD', | ||
}); | ||
needsToCleanStorage = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same, best to cleanup here |
||
return next(err, destinationBucket); | ||
} | ||
return next(null, partLocations, oldLocations, objectLocationConstraint, | ||
|
@@ -413,6 +417,20 @@ function objectPutPart(authInfo, request, streamingV4Params, log, | |
}); | ||
monitoring.promMetrics('PUT', bucketName, err.code, | ||
'putObjectPart'); | ||
if (needsToCleanStorage && partLocations) { | ||
const delLog = logger.newRequestLoggerFromSerializedUids( | ||
log.getSerializedUids()); | ||
return data.batchDelete(partLocations, request.method, objectLocationConstraint, delLog, | ||
_err => { | ||
if (_err) { | ||
log.warn('potential orphan in storage', { | ||
error: _err, | ||
objects: partLocations, | ||
}); | ||
} | ||
return cb(err, null, corsHeaders); | ||
}); | ||
} | ||
return cb(err, null, corsHeaders); | ||
} | ||
pushMetric('uploadPart', log, { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -363,12 +363,24 @@ const services = { | |
} | ||
|
||
if (!Array.isArray(objectMD.location)) { | ||
data.delete(objectMD.location, deleteLog); | ||
return cb(null, res); | ||
return data.delete(objectMD.location, deleteLog, err => { | ||
if (err) { | ||
log.warn('potential orphan in storage', { | ||
object: objectMD.location, | ||
error: err, | ||
}); | ||
return cb(err); | ||
} | ||
return cb(null, res); | ||
}); | ||
Comment on lines
+366
to
+375
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note for reviewers: this code was not looking for any error when deleting data from storage. In case of error, that would create both orphans and invisible error codes for the user. Let me know if that should not be changed due to some non-documented reason(s). |
||
} | ||
|
||
return data.batchDelete(objectMD.location, null, null, deleteLog, err => { | ||
if (err) { | ||
log.warn('potential orphan in storage', { | ||
objects: objectMD.location, | ||
error: err, | ||
}); | ||
return cb(err); | ||
} | ||
return cb(null, res); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you make this variable global, you need to remove it from the variables which trickle through the waterfall... (e.g. infoArr).... and it should problably be renamed, since it is not really "dataGet" anymore