History

remove double reads updating object metadata (#13542)

Removes RLock/RUnlock for updating metadata, since we already take a write lock to update metadata, this change removes reading of xl.meta as well as an additional lock, the performance gain should increase 3x theoretically for - PutObjectRetention - PutObjectLegalHold This optimization is mainly for Veeam like workloads that require a certain level of iops from these API calls, we were losing iops.

Harshavardhana committed 5 years ago

Commit 4ed0eb71 parent 2af54457 files · +117 −117Browse files

Unified · Split

7 files
  1. cmd/api-errors.go +2 −0
  2. cmd/bucket-object-lock.go +31 −23
  3. cmd/bucket-replication.go +17 −17
  4. cmd/erasure-object.go +13 −7
  5. cmd/object-api-errors.go +7 −0
  6. cmd/object-api-interface.go +4 −0
  7. cmd/object-handlers.go +43 −70
cmd/api-errors.go+2−0
@@ -1956,6 +1956,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) { · 1955 unmodified lines
1956 apiErr = ErrNoSuchKey1956 apiErr = ErrNoSuchKey
1957 case MethodNotAllowed:1957 case MethodNotAllowed:
1958 apiErr = ErrMethodNotAllowed1958 apiErr = ErrMethodNotAllowed
1959 case ObjectLocked:
1960 apiErr = ErrObjectLocked
1959 case InvalidVersionID:1961 case InvalidVersionID:
1960 apiErr = ErrInvalidVersionID1962 apiErr = ErrInvalidVersionID
1961 case VersionNotFound:1963 case VersionNotFound:
cmd/bucket-object-lock.go+31−23
@@ -175,68 +175,76 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke · 174 unmodified lines
175// For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR175// For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR
176// governance bypass headers are set and user has governance bypass permissions.176// governance bypass headers are set and user has governance bypass permissions.
177// Objects in compliance mode can be overwritten only if retention date is being extended. No mode change is permitted.177// Objects in compliance mode can be overwritten only if retention date is being extended. No mode change is permitted.
178func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, objRetention *objectlock.ObjectRetention, cred auth.Credentials, owner bool) (ObjectInfo, APIErrorCode) {178func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, oi ObjectInfo, objRetention *objectlock.ObjectRetention, cred auth.Credentials, owner bool) error {
179 byPassSet := objectlock.IsObjectLockGovernanceBypassSet(r.Header)179 byPassSet := objectlock.IsObjectLockGovernanceBypassSet(r.Header)
180 opts, err := getOpts(ctx, r, bucket, object)
181 if err != nil {
182 return ObjectInfo{}, toAPIErrorCode(ctx, err)
183 }
184
185 oi, err := getObjectInfoFn(ctx, bucket, object, opts)
186 if err != nil {
187 return oi, toAPIErrorCode(ctx, err)
188 }
189 180
190 t, err := objectlock.UTCNowNTP()181 t, err := objectlock.UTCNowNTP()
191 if err != nil {182 if err != nil {
192 logger.LogIf(ctx, err)183 logger.LogIf(ctx, err)
193 return oi, ErrObjectLocked184 return ObjectLocked{Bucket: oi.Bucket, Object: oi.Name, VersionID: oi.VersionID}
194 }185 }
195 186
196 // Pass in relative days from current time, to additionally to verify "object-lock-remaining-retention-days" policy if any.187 // Pass in relative days from current time, to additionally
188 // to verify "object-lock-remaining-retention-days" policy if any.
197 days := int(math.Ceil(math.Abs(objRetention.RetainUntilDate.Sub(t).Hours()) / 24))189 days := int(math.Ceil(math.Abs(objRetention.RetainUntilDate.Sub(t).Hours()) / 24))
198 190
199 ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)191 ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
200 if ret.Mode.Valid() {192 if ret.Mode.Valid() {
201 // Retention has expired you may change whatever you like.193 // Retention has expired you may change whatever you like.
202 if ret.RetainUntilDate.Before(t) {194 if ret.RetainUntilDate.Before(t) {
203 perm := isPutRetentionAllowed(bucket, object,195 apiErr := isPutRetentionAllowed(oi.Bucket, oi.Name,
204 days, objRetention.RetainUntilDate.Time,196 days, objRetention.RetainUntilDate.Time,
205 objRetention.Mode, byPassSet, r, cred,197 objRetention.Mode, byPassSet, r, cred,
206 owner)198 owner)
207 return oi, perm199 switch apiErr {
200 case ErrAccessDenied:
201 return errAuthentication
202 }
203 return nil
208 }204 }
209 205
210 switch ret.Mode {206 switch ret.Mode {
211 case objectlock.RetGovernance:207 case objectlock.RetGovernance:
212 govPerm := isPutRetentionAllowed(bucket, object, days,208 govPerm := isPutRetentionAllowed(oi.Bucket, oi.Name, days,
213 objRetention.RetainUntilDate.Time, objRetention.Mode,209 objRetention.RetainUntilDate.Time, objRetention.Mode,
214 byPassSet, r, cred, owner)210 byPassSet, r, cred, owner)
215 // Governance mode retention period cannot be shortened, if x-amz-bypass-governance is not set.211 // Governance mode retention period cannot be shortened, if x-amz-bypass-governance is not set.
216 if !byPassSet {212 if !byPassSet {
217 if objRetention.Mode != objectlock.RetGovernance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) {213 if objRetention.Mode != objectlock.RetGovernance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) {
218 return oi, ErrObjectLocked214 return ObjectLocked{Bucket: oi.Bucket, Object: oi.Name, VersionID: oi.VersionID}
219 }215 }
220 }216 }
221 return oi, govPerm217 switch govPerm {
218 case ErrAccessDenied:
219 return errAuthentication
220 }
221 return nil
222 case objectlock.RetCompliance:222 case objectlock.RetCompliance:
223 // Compliance retention mode cannot be changed or shortened.223 // Compliance retention mode cannot be changed or shortened.
224 // https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes224 // https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes
225 if objRetention.Mode != objectlock.RetCompliance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) {225 if objRetention.Mode != objectlock.RetCompliance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) {
226 return oi, ErrObjectLocked226 return ObjectLocked{Bucket: oi.Bucket, Object: oi.Name, VersionID: oi.VersionID}
227 }227 }
228 compliancePerm := isPutRetentionAllowed(bucket, object,228 apiErr := isPutRetentionAllowed(oi.Bucket, oi.Name,
229 days, objRetention.RetainUntilDate.Time, objRetention.Mode,229 days, objRetention.RetainUntilDate.Time, objRetention.Mode,
230 false, r, cred, owner)230 false, r, cred, owner)
231 return oi, compliancePerm231 switch apiErr {
232 case ErrAccessDenied:
233 return errAuthentication
234 }
235 return nil
232 }236 }
233 return oi, ErrNone237 return nil
234 } // No pre-existing retention metadata present.238 } // No pre-existing retention metadata present.
235 239
236 perm := isPutRetentionAllowed(bucket, object,240 apiErr := isPutRetentionAllowed(oi.Bucket, oi.Name,
237 days, objRetention.RetainUntilDate.Time,241 days, objRetention.RetainUntilDate.Time,
238 objRetention.Mode, byPassSet, r, cred, owner)242 objRetention.Mode, byPassSet, r, cred, owner)
239 return oi, perm243 switch apiErr {
244 case ErrAccessDenied:
245 return errAuthentication
246 }
247 return nil
240}248}
241 249
242// checkPutObjectLockAllowed enforces object retention policy and legal hold policy250// checkPutObjectLockAllowed enforces object retention policy and legal hold policy
cmd/bucket-replication.go+17−17
@@ -910,24 +910,24 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje · 909 unmodified lines
910 // metadata should be updated with last resync timestamp.910 // metadata should be updated with last resync timestamp.
911 if objInfo.ReplicationStatusInternal != newReplStatusInternal || rinfos.ReplicationResynced() {911 if objInfo.ReplicationStatusInternal != newReplStatusInternal || rinfos.ReplicationResynced() {
912 popts := ObjectOptions{912 popts := ObjectOptions{
913 MTime: objInfo.ModTime,913 MTime: objInfo.ModTime,
914 VersionID: objInfo.VersionID,914 VersionID: objInfo.VersionID,
915 UserDefined: make(map[string]string, len(objInfo.UserDefined)),915 EvalMetadataFn: func(oi ObjectInfo) error {
916 }916 oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal
917 for k, v := range objInfo.UserDefined {917 oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
918 popts.UserDefined[k] = v918 oi.UserDefined[xhttp.AmzBucketReplicationStatus] = string(rinfos.ReplicationStatus())
919 }919 for _, rinfo := range rinfos.Targets {
920 popts.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal920 if rinfo.ResyncTimestamp != "" {
921 popts.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)921 oi.UserDefined[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp
922 popts.UserDefined[xhttp.AmzBucketReplicationStatus] = string(rinfos.ReplicationStatus())922 }
923 for _, rinfo := range rinfos.Targets {923 }
924 if rinfo.ResyncTimestamp != "" {924 if objInfo.UserTags != "" {
925 popts.UserDefined[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp925 oi.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
926 }926 }
927 }927 return nil
928 if objInfo.UserTags != "" {928 },
929 popts.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
930 }929 }
930
931 if _, err = objectAPI.PutObjectMetadata(ctx, bucket, object, popts); err != nil {931 if _, err = objectAPI.PutObjectMetadata(ctx, bucket, object, popts); err != nil {
932 logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s/%s(%s): %w",932 logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s/%s(%s): %w",
933 bucket, objInfo.Name, objInfo.VersionID, err))933 bucket, objInfo.Name, objInfo.VersionID, err))
cmd/erasure-object.go+13−7
@@ -1440,13 +1440,21 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s · 1439 unmodified lines
1440 return ObjectInfo{}, toObjectErr(err, bucket, object)1440 return ObjectInfo{}, toObjectErr(err, bucket, object)
1441 }1441 }
1442 if fi.Deleted {1442 if fi.Deleted {
1443 if opts.VersionID == "" {
1444 return ObjectInfo{}, toObjectErr(errFileNotFound, bucket, object)
1445 }
1446 return ObjectInfo{}, toObjectErr(errMethodNotAllowed, bucket, object)1443 return ObjectInfo{}, toObjectErr(errMethodNotAllowed, bucket, object)
1447 }1444 }
1448 1445
1449 for k, v := range opts.UserDefined {1446 // if version-id is not specified retention is supposed to be set on the latest object.
1447 if opts.VersionID == "" {
1448 opts.VersionID = fi.VersionID
1449 }
1450
1451 objInfo := fi.ToObjectInfo(bucket, object)
1452 if opts.EvalMetadataFn != nil {
1453 if err := opts.EvalMetadataFn(objInfo); err != nil {
1454 return ObjectInfo{}, err
1455 }
1456 }
1457 for k, v := range objInfo.UserDefined {
1450 fi.Metadata[k] = v1458 fi.Metadata[k] = v
1451 }1459 }
1452 fi.ModTime = opts.MTime1460 fi.ModTime = opts.MTime
@@ -1456,9 +1464,7 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s · 3 unmodified lines
1456 return ObjectInfo{}, toObjectErr(err, bucket, object)1464 return ObjectInfo{}, toObjectErr(err, bucket, object)
1457 }1465 }
1458 1466
1459 objInfo := fi.ToObjectInfo(bucket, object)1467 return fi.ToObjectInfo(bucket, object), nil
1460 return objInfo, nil
1461
1462}1468}
1463 1469
1464// PutObjectTags - replace or add tags to an existing object1470// PutObjectTags - replace or add tags to an existing object
cmd/object-api-errors.go+7−0
@@ -291,6 +291,13 @@ func (e MethodNotAllowed) Error() string { · 290 unmodified lines
291 return "Method not allowed: " + e.Bucket + "/" + e.Object291 return "Method not allowed: " + e.Bucket + "/" + e.Object
292}292}
293 293
294// ObjectLocked object is currently WORM protected.
295type ObjectLocked GenericError
296
297func (e ObjectLocked) Error() string {
298 return "Object is WORM protected and cannot be overwritten: " + e.Bucket + "/" + e.Object + "(" + e.VersionID + ")"
299}
300
294// ObjectAlreadyExists object already exists.301// ObjectAlreadyExists object already exists.
295type ObjectAlreadyExists GenericError302type ObjectAlreadyExists GenericError
296 303
cmd/object-api-interface.go+4−0
@@ -33,6 +33,9 @@ import ( · 32 unmodified lines
33// CheckPreconditionFn returns true if precondition check failed.33// CheckPreconditionFn returns true if precondition check failed.
34type CheckPreconditionFn func(o ObjectInfo) bool34type CheckPreconditionFn func(o ObjectInfo) bool
35 35
36// EvalMetadataFn validates input objInfo and returns an updated metadata
37type EvalMetadataFn func(o ObjectInfo) error
38
36// GetObjectInfoFn is the signature of GetObjectInfo function.39// GetObjectInfoFn is the signature of GetObjectInfo function.
37type GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error)40type GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error)
38 41
@@ -50,6 +53,7 @@ type ObjectOptions struct { · 11 unmodified lines
50 UserDefined map[string]string // only set in case of POST/PUT operations53 UserDefined map[string]string // only set in case of POST/PUT operations
51 PartNumber int // only useful in case of GetObject/HeadObject54 PartNumber int // only useful in case of GetObject/HeadObject
52 CheckPrecondFn CheckPreconditionFn // only set during GetObject/HeadObject/CopyObjectPart preconditional valuation55 CheckPrecondFn CheckPreconditionFn // only set during GetObject/HeadObject/CopyObjectPart preconditional valuation
56 EvalMetadataFn EvalMetadataFn // only set for retention settings, meant to be used only when updating metadata in-place.
53 DeleteReplication ReplicationState // Represents internal replication state needed for Delete replication57 DeleteReplication ReplicationState // Represents internal replication state needed for Delete replication
54 Transition TransitionOptions58 Transition TransitionOptions
55 Expiration ExpirationOptions59 Expiration ExpirationOptions
cmd/object-handlers.go+43−70
@@ -3521,53 +3521,39 @@ func (api objectAPIHandlers) PutObjectLegalHoldHandler(w http.ResponseWriter, r · 3520 unmodified lines
3521 return3521 return
3522 }3522 }
3523 3523
3524 getObjectInfo := objectAPI.GetObjectInfo
3525 if api.CacheAPI() != nil {
3526 getObjectInfo = api.CacheAPI().GetObjectInfo
3527 }
3528
3529 opts, err := getOpts(ctx, r, bucket, object)3524 opts, err := getOpts(ctx, r, bucket, object)
3530 if err != nil {3525 if err != nil {
3531 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)3526 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
3532 return3527 return
3533 }3528 }
3534 3529
3535 objInfo, err := getObjectInfo(ctx, bucket, object, opts)3530 popts := ObjectOptions{
3531 MTime: opts.MTime,
3532 VersionID: opts.VersionID,
3533 EvalMetadataFn: func(oi ObjectInfo) error {
3534 oi.UserDefined[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = strings.ToUpper(string(legalHold.Status))
3535 oi.UserDefined[ReservedMetadataPrefixLower+ObjectLockLegalHoldTimestamp] = UTCNow().Format(time.RFC3339Nano)
3536
3537 dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(oi, replication.MetadataReplicationType, opts))
3538 if dsc.ReplicateAny() {
3539 oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
3540 oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = dsc.PendingStatus()
3541 }
3542 return nil
3543 },
3544 }
3545
3546 objInfo, err := objectAPI.PutObjectMetadata(ctx, bucket, object, popts)
3536 if err != nil {3547 if err != nil {
3537 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)3548 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
3538 return3549 return
3539 }3550 }
3540 if objInfo.DeleteMarker {
3541 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
3542 return
3543 }
3544 objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = strings.ToUpper(string(legalHold.Status))
3545 objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockLegalHoldTimestamp] = UTCNow().Format(time.RFC3339Nano)
3546 3551
3547 dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(objInfo, replication.MetadataReplicationType, opts))3552 dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(objInfo, replication.MetadataReplicationType, opts))
3548 if dsc.ReplicateAny() {
3549 objInfo.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
3550 objInfo.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = dsc.PendingStatus()
3551 }
3552 // if version-id is not specified retention is supposed to be set on the latest object.
3553 if opts.VersionID == "" {
3554 opts.VersionID = objInfo.VersionID
3555 }
3556 popts := ObjectOptions{
3557 MTime: opts.MTime,
3558 VersionID: opts.VersionID,
3559 UserDefined: make(map[string]string, len(objInfo.UserDefined)),
3560 }
3561 for k, v := range objInfo.UserDefined {
3562 popts.UserDefined[k] = v
3563 }
3564 if _, err = objectAPI.PutObjectMetadata(ctx, bucket, object, popts); err != nil {
3565 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
3566 return
3567 }
3568 if dsc.ReplicateAny() {3553 if dsc.ReplicateAny() {
3569 scheduleReplication(ctx, objInfo.Clone(), objectAPI, dsc, replication.MetadataReplicationType)3554 scheduleReplication(ctx, objInfo.Clone(), objectAPI, dsc, replication.MetadataReplicationType)
3570 }3555 }
3556
3571 writeSuccessResponseHeadersOnly(w)3557 writeSuccessResponseHeadersOnly(w)
3572 3558
3573 // Notify object event.3559 // Notify object event.
@@ -3703,50 +3689,37 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r · 129 unmodified lines
3703 return3689 return
3704 }3690 }
3705 3691
3706 getObjectInfo := objectAPI.GetObjectInfo3692 popts := ObjectOptions{
3707 if api.CacheAPI() != nil {3693 MTime: opts.MTime,
3708 getObjectInfo = api.CacheAPI().GetObjectInfo3694 VersionID: opts.VersionID,
3695 EvalMetadataFn: func(oi ObjectInfo) error {
3696 if err := enforceRetentionBypassForPut(ctx, r, oi, objRetention, cred, owner); err != nil {
3697 return err
3698 }
3699 if objRetention.Mode.Valid() {
3700 oi.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = string(objRetention.Mode)
3701 oi.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = objRetention.RetainUntilDate.UTC().Format(time.RFC3339)
3702 } else {
3703 oi.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = ""
3704 oi.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = ""
3705 }
3706 oi.UserDefined[ReservedMetadataPrefixLower+ObjectLockRetentionTimestamp] = UTCNow().Format(time.RFC3339Nano)
3707 dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(oi, replication.MetadataReplicationType, opts))
3708 if dsc.ReplicateAny() {
3709 oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
3710 oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = dsc.PendingStatus()
3711 }
3712 return nil
3713 },
3709 }3714 }
3710 3715
3711 objInfo, s3Err := enforceRetentionBypassForPut(ctx, r, bucket, object, getObjectInfo, objRetention, cred, owner)3716 objInfo, err := objectAPI.PutObjectMetadata(ctx, bucket, object, popts)
3712 if s3Err != ErrNone {3717 if err != nil {
3713 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)3718 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
3714 return
3715 }
3716 if objInfo.DeleteMarker {
3717 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
3718 return3719 return
3719 }3720 }
3720 if objRetention.Mode.Valid() {
3721 objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = string(objRetention.Mode)
3722 objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = objRetention.RetainUntilDate.UTC().Format(time.RFC3339)
3723 } else {
3724 objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = ""
3725 objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = ""
3726 }
3727 objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockRetentionTimestamp] = UTCNow().Format(time.RFC3339Nano)
3728 3721
3729 dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(objInfo, replication.MetadataReplicationType, opts))3722 dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(objInfo, replication.MetadataReplicationType, opts))
3730 if dsc.ReplicateAny() {
3731 objInfo.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
3732 objInfo.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = dsc.PendingStatus()
3733 }
3734 // if version-id is not specified retention is supposed to be set on the latest object.
3735 if opts.VersionID == "" {
3736 opts.VersionID = objInfo.VersionID
3737 }
3738 popts := ObjectOptions{
3739 MTime: opts.MTime,
3740 VersionID: opts.VersionID,
3741 UserDefined: make(map[string]string, len(objInfo.UserDefined)),
3742 }
3743 for k, v := range objInfo.UserDefined {
3744 popts.UserDefined[k] = v
3745 }
3746 if _, err = objectAPI.PutObjectMetadata(ctx, bucket, object, popts); err != nil {
3747 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
3748 return
3749 }
3750 if dsc.ReplicateAny() {3723 if dsc.ReplicateAny() {
3751 scheduleReplication(ctx, objInfo.Clone(), objectAPI, dsc, replication.MetadataReplicationType)3724 scheduleReplication(ctx, objInfo.Clone(), objectAPI, dsc, replication.MetadataReplicationType)
3752 }3725 }

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t