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 | apiErr = ErrNoSuchKey | 1956 | apiErr = ErrNoSuchKey |
| 1957 | case MethodNotAllowed: | 1957 | case MethodNotAllowed: |
| 1958 | apiErr = ErrMethodNotAllowed | 1958 | apiErr = ErrMethodNotAllowed |
| 1959 | case ObjectLocked: | ||
| 1960 | apiErr = ErrObjectLocked | ||
| 1959 | case InvalidVersionID: | 1961 | case InvalidVersionID: |
| 1960 | apiErr = ErrInvalidVersionID | 1962 | apiErr = ErrInvalidVersionID |
| 1961 | case VersionNotFound: | 1963 | case VersionNotFound: |
| @@ -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 OR | 175 | // 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. |
| 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 | 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, 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 | 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, perm | 199 | 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, 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 | 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-modes | 224 | // 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, 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 | 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, compliancePerm | 231 | switch apiErr { |
| 232 | case ErrAccessDenied: | ||
| 233 | return errAuthentication | ||
| 234 | } | ||
| 235 | return nil | ||
| 232 | } | 236 | } |
| 233 | return oi, ErrNone | 237 | 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, perm | 243 | 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 policy | 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 | // 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] = v | 918 | oi.UserDefined[xhttp.AmzBucketReplicationStatus] = string(rinfos.ReplicationStatus()) |
| 919 | } | 919 | for _, rinfo := range rinfos.Targets { |
| 920 | popts.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal | 920 | 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.ResyncTimestamp | 925 | 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)) |
| @@ -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] = v | 1458 | fi.Metadata[k] = v |
| 1451 | } | 1459 | } |
| 1452 | fi.ModTime = opts.MTime | 1460 | 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 object | 1470 | // PutObjectTags - replace or add tags to an existing object |
| @@ -291,6 +291,13 @@ func (e MethodNotAllowed) Error() string { · 290 unmodified lines | |||
| 291 | return "Method not allowed: " + e.Bucket + "/" + e.Object | 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 | // ObjectAlreadyExists object already exists. | 301 | // ObjectAlreadyExists object already exists. |
| 295 | type ObjectAlreadyExists GenericError | 302 | type ObjectAlreadyExists GenericError |
| 296 | 303 | ||
| @@ -33,6 +33,9 @@ import ( · 32 unmodified lines | |||
| 33 | // CheckPreconditionFn returns true if precondition check failed. | 33 | // CheckPreconditionFn returns true if precondition check failed. |
| 34 | type CheckPreconditionFn func(o ObjectInfo) bool | 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 | // GetObjectInfoFn is the signature of GetObjectInfo function. | 39 | // GetObjectInfoFn is the signature of GetObjectInfo function. |
| 37 | type GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) | 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 | UserDefined map[string]string // only set in case of POST/PUT operations | 53 | UserDefined map[string]string // only set in case of POST/PUT operations |
| 51 | PartNumber int // only useful in case of GetObject/HeadObject | 54 | PartNumber int // only useful in case of GetObject/HeadObject |
| 52 | CheckPrecondFn CheckPreconditionFn // only set during GetObject/HeadObject/CopyObjectPart preconditional valuation | 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 | DeleteReplication ReplicationState // Represents internal replication state needed for Delete replication | 57 | DeleteReplication ReplicationState // Represents internal replication state needed for Delete replication |
| 54 | Transition TransitionOptions | 58 | Transition TransitionOptions |
| 55 | Expiration ExpirationOptions | 59 | Expiration ExpirationOptions |
| @@ -3521,53 +3521,39 @@ func (api objectAPIHandlers) PutObjectLegalHoldHandler(w http.ResponseWriter, r · 3520 unmodified lines | |||
| 3521 | return | 3521 | 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 | return | 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 | 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 | return | 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 | 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 | return | 3689 | return |
| 3704 | } | 3690 | } |
| 3705 | 3691 | ||
| 3706 | getObjectInfo := objectAPI.GetObjectInfo | 3692 | popts := ObjectOptions{ |
| 3707 | if api.CacheAPI() != nil { | 3693 | MTime: opts.MTime, |
| 3708 | getObjectInfo = api.CacheAPI().GetObjectInfo | 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) | 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 | return | 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 | 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 | } |