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.
Unified · Split
| @@ -1956,6 +1956,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) { · 1955 unmodified lines | ||
| 1956 | 1956 | apiErr = ErrNoSuchKey |
| 1957 | 1957 | case MethodNotAllowed: |
| 1958 | 1958 | apiErr = ErrMethodNotAllowed |
| 1959 | + case ObjectLocked: | |
| 1960 | + apiErr = ErrObjectLocked | |
| 1959 | 1961 | case InvalidVersionID: |
| 1960 | 1962 | apiErr = ErrInvalidVersionID |
| 1961 | 1963 | case VersionNotFound: |
| @@ -175,68 +175,76 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke · 174 unmodified lines | ||
| 175 | 175 | // For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR |
| 176 | 176 | // governance bypass headers are set and user has governance bypass permissions. |
| 177 | 177 | // Objects in compliance mode can be overwritten only if retention date is being extended. No mode change is permitted. |
| 178 | −func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, objRetention *objectlock.ObjectRetention, cred auth.Credentials, owner bool) (ObjectInfo, APIErrorCode) { | |
| 178 | +func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, oi ObjectInfo, objRetention *objectlock.ObjectRetention, cred auth.Credentials, owner bool) error { | |
| 179 | 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 | 181 | t, err := objectlock.UTCNowNTP() |
| 191 | 182 | if err != nil { |
| 192 | 183 | logger.LogIf(ctx, err) |
| 193 | − return oi, ErrObjectLocked | |
| 184 | + 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 | 189 | days := int(math.Ceil(math.Abs(objRetention.RetainUntilDate.Sub(t).Hours()) / 24)) |
| 198 | 190 | |
| 199 | 191 | ret := objectlock.GetObjectRetentionMeta(oi.UserDefined) |
| 200 | 192 | if ret.Mode.Valid() { |
| 201 | 193 | // Retention has expired you may change whatever you like. |
| 202 | 194 | if ret.RetainUntilDate.Before(t) { |
| 203 | − perm := isPutRetentionAllowed(bucket, object, | |
| 195 | + apiErr := isPutRetentionAllowed(oi.Bucket, oi.Name, | |
| 204 | 196 | days, objRetention.RetainUntilDate.Time, |
| 205 | 197 | objRetention.Mode, byPassSet, r, cred, |
| 206 | 198 | owner) |
| 207 | − return oi, perm | |
| 199 | + switch apiErr { | |
| 200 | + case ErrAccessDenied: | |
| 201 | + return errAuthentication | |
| 202 | + } | |
| 203 | + return nil | |
| 208 | 204 | } |
| 209 | 205 | |
| 210 | 206 | switch ret.Mode { |
| 211 | 207 | case objectlock.RetGovernance: |
| 212 | − govPerm := isPutRetentionAllowed(bucket, object, days, | |
| 208 | + govPerm := isPutRetentionAllowed(oi.Bucket, oi.Name, days, | |
| 213 | 209 | objRetention.RetainUntilDate.Time, objRetention.Mode, |
| 214 | 210 | byPassSet, r, cred, owner) |
| 215 | 211 | // Governance mode retention period cannot be shortened, if x-amz-bypass-governance is not set. |
| 216 | 212 | if !byPassSet { |
| 217 | 213 | if objRetention.Mode != objectlock.RetGovernance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) { |
| 218 | − return oi, ErrObjectLocked | |
| 214 | + return ObjectLocked{Bucket: oi.Bucket, Object: oi.Name, VersionID: oi.VersionID} | |
| 219 | 215 | } |
| 220 | 216 | } |
| 221 | − return oi, govPerm | |
| 217 | + switch govPerm { | |
| 218 | + case ErrAccessDenied: | |
| 219 | + return errAuthentication | |
| 220 | + } | |
| 221 | + return nil | |
| 222 | 222 | case objectlock.RetCompliance: |
| 223 | 223 | // Compliance retention mode cannot be changed or shortened. |
| 224 | 224 | // https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes |
| 225 | 225 | if objRetention.Mode != objectlock.RetCompliance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) { |
| 226 | − return oi, ErrObjectLocked | |
| 226 | + 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 | 229 | days, objRetention.RetainUntilDate.Time, objRetention.Mode, |
| 230 | 230 | false, r, cred, owner) |
| 231 | − return oi, compliancePerm | |
| 231 | + switch apiErr { | |
| 232 | + case ErrAccessDenied: | |
| 233 | + return errAuthentication | |
| 234 | + } | |
| 235 | + return nil | |
| 232 | 236 | } |
| 233 | − return oi, ErrNone | |
| 237 | + return nil | |
| 234 | 238 | } // No pre-existing retention metadata present. |
| 235 | 239 | |
| 236 | − perm := isPutRetentionAllowed(bucket, object, | |
| 240 | + apiErr := isPutRetentionAllowed(oi.Bucket, oi.Name, | |
| 237 | 241 | days, objRetention.RetainUntilDate.Time, |
| 238 | 242 | objRetention.Mode, byPassSet, r, cred, owner) |
| 239 | − return oi, perm | |
| 243 | + switch apiErr { | |
| 244 | + case ErrAccessDenied: | |
| 245 | + return errAuthentication | |
| 246 | + } | |
| 247 | + return nil | |
| 240 | 248 | } |
| 241 | 249 | |
| 242 | 250 | // checkPutObjectLockAllowed enforces object retention policy and legal hold policy |
| @@ -910,24 +910,24 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje · 909 unmodified lines | ||
| 910 | 910 | // metadata should be updated with last resync timestamp. |
| 911 | 911 | if objInfo.ReplicationStatusInternal != newReplStatusInternal || rinfos.ReplicationResynced() { |
| 912 | 912 | popts := ObjectOptions{ |
| 913 | − MTime: objInfo.ModTime, | |
| 914 | − VersionID: objInfo.VersionID, | |
| 915 | − UserDefined: make(map[string]string, len(objInfo.UserDefined)), | |
| 916 | − } | |
| 917 | − for k, v := range objInfo.UserDefined { | |
| 918 | − popts.UserDefined[k] = v | |
| 919 | − } | |
| 920 | − popts.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal | |
| 921 | − popts.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano) | |
| 922 | − popts.UserDefined[xhttp.AmzBucketReplicationStatus] = string(rinfos.ReplicationStatus()) | |
| 923 | − for _, rinfo := range rinfos.Targets { | |
| 924 | − if rinfo.ResyncTimestamp != "" { | |
| 925 | − popts.UserDefined[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp | |
| 926 | − } | |
| 927 | − } | |
| 928 | − if objInfo.UserTags != "" { | |
| 929 | − popts.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags | |
| 913 | + MTime: objInfo.ModTime, | |
| 914 | + VersionID: objInfo.VersionID, | |
| 915 | + EvalMetadataFn: func(oi ObjectInfo) error { | |
| 916 | + oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal | |
| 917 | + oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano) | |
| 918 | + oi.UserDefined[xhttp.AmzBucketReplicationStatus] = string(rinfos.ReplicationStatus()) | |
| 919 | + for _, rinfo := range rinfos.Targets { | |
| 920 | + if rinfo.ResyncTimestamp != "" { | |
| 921 | + oi.UserDefined[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp | |
| 922 | + } | |
| 923 | + } | |
| 924 | + if objInfo.UserTags != "" { | |
| 925 | + oi.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags | |
| 926 | + } | |
| 927 | + return nil | |
| 928 | + }, | |
| 930 | 929 | } |
| 930 | + | |
| 931 | 931 | if _, err = objectAPI.PutObjectMetadata(ctx, bucket, object, popts); err != nil { |
| 932 | 932 | logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s/%s(%s): %w", |
| 933 | 933 | bucket, objInfo.Name, objInfo.VersionID, err)) |
| @@ -1440,13 +1440,21 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s · 1439 unmodified lines | ||
| 1440 | 1440 | return ObjectInfo{}, toObjectErr(err, bucket, object) |
| 1441 | 1441 | } |
| 1442 | 1442 | if fi.Deleted { |
| 1443 | − if opts.VersionID == "" { | |
| 1444 | − return ObjectInfo{}, toObjectErr(errFileNotFound, bucket, object) | |
| 1445 | − } | |
| 1446 | 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 | 1458 | fi.Metadata[k] = v |
| 1451 | 1459 | } |
| 1452 | 1460 | fi.ModTime = opts.MTime |
| @@ -1456,9 +1464,7 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s · 3 unmodified lines | ||
| 1456 | 1464 | return ObjectInfo{}, toObjectErr(err, bucket, object) |
| 1457 | 1465 | } |
| 1458 | 1466 | |
| 1459 | − objInfo := fi.ToObjectInfo(bucket, object) | |
| 1460 | − return objInfo, nil | |
| 1461 | − | |
| 1467 | + return fi.ToObjectInfo(bucket, object), nil | |
| 1462 | 1468 | } |
| 1463 | 1469 | |
| 1464 | 1470 | // PutObjectTags - replace or add tags to an existing object |
| @@ -291,6 +291,13 @@ func (e MethodNotAllowed) Error() string { · 290 unmodified lines | ||
| 291 | 291 | return "Method not allowed: " + e.Bucket + "/" + e.Object |
| 292 | 292 | } |
| 293 | 293 | |
| 294 | +// ObjectLocked object is currently WORM protected. | |
| 295 | +type ObjectLocked GenericError | |
| 296 | + | |
| 297 | +func (e ObjectLocked) Error() string { | |
| 298 | + return "Object is WORM protected and cannot be overwritten: " + e.Bucket + "/" + e.Object + "(" + e.VersionID + ")" | |
| 299 | +} | |
| 300 | + | |
| 294 | 301 | // ObjectAlreadyExists object already exists. |
| 295 | 302 | type ObjectAlreadyExists GenericError |
| 296 | 303 | |
| @@ -33,6 +33,9 @@ import ( · 32 unmodified lines | ||
| 33 | 33 | // CheckPreconditionFn returns true if precondition check failed. |
| 34 | 34 | type CheckPreconditionFn func(o ObjectInfo) bool |
| 35 | 35 | |
| 36 | +// EvalMetadataFn validates input objInfo and returns an updated metadata | |
| 37 | +type EvalMetadataFn func(o ObjectInfo) error | |
| 38 | + | |
| 36 | 39 | // GetObjectInfoFn is the signature of GetObjectInfo function. |
| 37 | 40 | type 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 | 53 | UserDefined map[string]string // only set in case of POST/PUT operations |
| 51 | 54 | PartNumber int // only useful in case of GetObject/HeadObject |
| 52 | 55 | 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 | 57 | DeleteReplication ReplicationState // Represents internal replication state needed for Delete replication |
| 54 | 58 | Transition TransitionOptions |
| 55 | 59 | Expiration ExpirationOptions |
| @@ -3521,53 +3521,39 @@ func (api objectAPIHandlers) PutObjectLegalHoldHandler(w http.ResponseWriter, r · 3520 unmodified lines | ||
| 3521 | 3521 | return |
| 3522 | 3522 | } |
| 3523 | 3523 | |
| 3524 | − getObjectInfo := objectAPI.GetObjectInfo | |
| 3525 | − if api.CacheAPI() != nil { | |
| 3526 | − getObjectInfo = api.CacheAPI().GetObjectInfo | |
| 3527 | − } | |
| 3528 | − | |
| 3529 | 3524 | opts, err := getOpts(ctx, r, bucket, object) |
| 3530 | 3525 | if err != nil { |
| 3531 | 3526 | writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) |
| 3532 | 3527 | 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 | 3547 | if err != nil { |
| 3537 | 3548 | writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) |
| 3538 | 3549 | 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 | 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 | 3553 | if dsc.ReplicateAny() { |
| 3569 | 3554 | scheduleReplication(ctx, objInfo.Clone(), objectAPI, dsc, replication.MetadataReplicationType) |
| 3570 | 3555 | } |
| 3556 | + | |
| 3571 | 3557 | writeSuccessResponseHeadersOnly(w) |
| 3572 | 3558 | |
| 3573 | 3559 | // Notify object event. |
| @@ -3703,50 +3689,37 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r · 129 unmodified lines | ||
| 3703 | 3689 | return |
| 3704 | 3690 | } |
| 3705 | 3691 | |
| 3706 | − getObjectInfo := objectAPI.GetObjectInfo | |
| 3707 | − if api.CacheAPI() != nil { | |
| 3708 | − getObjectInfo = api.CacheAPI().GetObjectInfo | |
| 3692 | + popts := ObjectOptions{ | |
| 3693 | + MTime: opts.MTime, | |
| 3694 | + 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) | |
| 3712 | − if s3Err != ErrNone { | |
| 3713 | − writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL) | |
| 3714 | − return | |
| 3715 | − } | |
| 3716 | − if objInfo.DeleteMarker { | |
| 3717 | − writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL) | |
| 3716 | + objInfo, err := objectAPI.PutObjectMetadata(ctx, bucket, object, popts) | |
| 3717 | + if err != nil { | |
| 3718 | + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) | |
| 3718 | 3719 | 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 | 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 | 3723 | if dsc.ReplicateAny() { |
| 3751 | 3724 | scheduleReplication(ctx, objInfo.Clone(), objectAPI, dsc, replication.MetadataReplicationType) |
| 3752 | 3725 | } |