-
Notifications
You must be signed in to change notification settings - Fork 251
CLDSRV-632 ✨ put metadata edge cases #5913
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
base: development/9.1
Are you sure you want to change the base?
Changes from all commits
e59e6ac
f6c75df
fffb8a5
b4f9b60
40a2ad1
c3e9a00
214cb41
277c22c
ceb98aa
d6accef
618f29d
f9e0f16
f2524f9
6d8ef87
304215f
fb1e2b7
8e47a3c
504cde3
affeae3
e384352
3d7d86a
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 | ||||
---|---|---|---|---|---|---|
|
@@ -40,6 +40,13 @@ const { listLifecycleOrphanDeleteMarkers } = require('../api/backbeat/listLifecy | |||||
const { objectDeleteInternal } = require('../api/objectDelete'); | ||||||
const quotaUtils = require('../api/apiUtils/quotas/quotaUtils'); | ||||||
const { handleAuthorizationResults } = require('../api/api'); | ||||||
const { versioningPreprocessing } | ||||||
= require('../api/apiUtils/object/versioning'); | ||||||
const {promisify} = require('util'); | ||||||
|
||||||
const versioningPreprocessingPromised = promisify(versioningPreprocessing); | ||||||
metadata.getObjectMDPromised = promisify(metadata.getObjectMD); | ||||||
metadata.getBucketAndObjectMDPromised = promisify(metadata.getBucketAndObjectMD); | ||||||
|
||||||
const { CURRENT_TYPE, NON_CURRENT_TYPE, ORPHAN_DM_TYPE } = constants.lifecycleListing; | ||||||
|
||||||
|
@@ -508,23 +515,22 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { | |||||
if (err) { | ||||||
return callback(err); | ||||||
} | ||||||
|
||||||
let omVal; | ||||||
|
||||||
try { | ||||||
omVal = JSON.parse(payload); | ||||||
} catch { | ||||||
// FIXME: add error type MalformedJSON | ||||||
return callback(errors.MalformedPOSTRequest); | ||||||
} | ||||||
|
||||||
const { headers, bucketName, objectKey } = request; | ||||||
// check if it's metadata only operation | ||||||
|
||||||
if (headers['x-scal-replication-content'] === 'METADATA') { | ||||||
if (!objMd) { | ||||||
// if the target does not exist, return an error to | ||||||
// backbeat, who will have to retry the operation as a | ||||||
// complete replication | ||||||
return callback(errors.ObjNotFound); | ||||||
} | ||||||
// use original data locations and encryption info | ||||||
Comment on lines
-522
to
-527
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. these comments do not seem useless at all, they should be kept 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. The one for location and encryption don't add any information ? Reading the code is enough to understand that we reuse the objMd ? For the other one, the comment don't help to understand the code neither, he just describe what happens later. IMO explaining what happens in other component just add noise. If we do that everywhere, we'll have comment everywhere. A comment should be here to help to understand the code, some weird behaviour or exception ? |
||||||
|
||||||
[ | ||||||
'location', | ||||||
'x-amz-server-side-encryption', | ||||||
|
@@ -611,6 +617,16 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { | |||||
// To prevent this, the versionId field is only included in options when it is defined. | ||||||
if (versionId !== undefined) { | ||||||
options.versionId = versionId; | ||||||
omVal.versionId = 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. the semantics of putObject in metadata layer (either backends, i.e. metadata or mongodbClientInterface) are not clear or precisely defined : does setting this not interract (in some weird or unacceptable way?) the behavior of the 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. As far as I checked, this seems not be the case. But maybe I'm missing something here 😬. Maybe the second review will have more insight 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. did you perform such second review? (you can resolve this comment once you confirmed) |
||||||
|
||||||
if (isNull) { | ||||||
if (!nullVersionCompatMode) { | ||||||
omVal.isNull2 = true; | ||||||
} | ||||||
|
||||||
omVal.isNull = isNull; | ||||||
DarkIsDude marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
// In the MongoDB metadata backend, setting the versionId option leads to the creation | ||||||
// or update of the version object, the master object is only updated if its versionId | ||||||
// is the same as the version. This can lead to inconsistencies when replicating objects | ||||||
|
@@ -641,6 +657,7 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { | |||||
if (!request.query?.accountId) { | ||||||
return next(); | ||||||
} | ||||||
|
||||||
return getCanonicalIdsByAccountId(request.query.accountId, log, (err, res) => { | ||||||
if (err) { | ||||||
return next(err); | ||||||
|
@@ -650,6 +667,45 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { | |||||
return next(); | ||||||
}); | ||||||
}, | ||||||
async () => { | ||||||
// If we create a new version of an object (so objMd is null), | ||||||
// we should make sure that the masterVersion is versioned. | ||||||
// If an object already exists, we just want to update the metadata | ||||||
// of the existing object and not create a new one | ||||||
if (versioning && !objMd) { | ||||||
let masterMD; | ||||||
|
||||||
try { | ||||||
masterMD = await metadata.getObjectMDPromised(bucketName, objectKey, {}, log); | ||||||
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 introduces an extra metadata IO in case of insert (first one was in ➡ maybe acceptable to keep the extra I/O for now, but we should create a ticket for tracking this, or make a note in https://scality.atlassian.net/browse/ARTESCA-8449 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. Side note, if we want to get both the master and the version, then this is just moving the problem, as we will still need two queries. Today we try to get the master version only for objects like delete markers, but if we provide a versionID and it's not there, then we just return The best would be to know in advance |
||||||
} catch (err) { | ||||||
if (err.is?.NoSuchKey) { | ||||||
log.debug('no master found for versioned object', { | ||||||
method: 'putMetadata', | ||||||
bucketName, | ||||||
objectKey, | ||||||
}); | ||||||
} else { | ||||||
throw err; | ||||||
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. I'm not sure we want to throw, the current stack is not using promises yet, so we tend to use more errors and calling the callbacks with it. I fear that a throw here will make the process crash, as we do not try/catch our code?
Suggested change
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. We can throw safely. We are in an
But I'm open minded as you mention that the current stack don't use promises yet. I would like to start to change that and move to a more modern stack by starting async/await syntax, in parallel of the SDK migration that introduce a lot async/await. Let me know |
||||||
} | ||||||
} | ||||||
|
||||||
if (!masterMD) { | ||||||
return; | ||||||
} | ||||||
|
||||||
const versioningPreprocessingResult = | ||||||
await versioningPreprocessingPromised(bucketName, bucketInfo, objectKey, masterMD, log); | ||||||
|
||||||
if (versioningPreprocessingResult?.nullVersionId) { | ||||||
omVal.nullVersionId = versioningPreprocessingResult.nullVersionId; | ||||||
options.deleteNullKey = versioningPreprocessingResult.deleteNullKey; | ||||||
|
||||||
if (versioningPreprocessingResult.extraMD) { | ||||||
Object.assign(omVal, options.extraMD); | ||||||
} | ||||||
} | ||||||
} | ||||||
}, | ||||||
next => { | ||||||
log.trace('putting object version', { | ||||||
objectKey: request.objectKey, omVal, options }); | ||||||
|
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.
This will probably be automated soon with some prettier solutions
I started this but it was on hold as we wanted to finish the unification first #5877