Browse Source
Fix get object lock configuration handler (#6996)
Fix get object lock configuration handler (#6996)
* fix GetObjectLockConfigurationHandler * cache and use bucket object lock config * subscribe to bucket configuration changes * increase bucket config cache TTL * refactor * Update weed/s3api/s3api_server.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * avoid duplidated work * rename variable * Update s3api_object_handlers_put.go * fix routing * admin ui and api handler are consistent now * use fields instead of xml * fix test * address comments * Update weed/s3api/s3api_object_handlers_put.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update test/s3/retention/s3_retention_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update weed/s3api/object_lock_utils.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * change error style * errorf --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>pull/6422/merge
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 842 additions and 312 deletions
-
4test/s3/retention/s3_retention_test.go
-
57weed/admin/dash/admin_server.go
-
46weed/admin/dash/bucket_management.go
-
70weed/s3api/auth_credentials_subscribe.go
-
232weed/s3api/object_lock_utils.go
-
6weed/s3api/s3_constants/extend_key.go
-
37weed/s3api/s3api_bucket_config.go
-
16weed/s3api/s3api_bucket_handlers.go
-
139weed/s3api/s3api_bucket_handlers_object_lock_config.go
-
126weed/s3api/s3api_object_handlers_legal_hold.go
-
103weed/s3api/s3api_object_handlers_put.go
-
222weed/s3api/s3api_object_handlers_retention.go
-
90weed/s3api/s3api_object_lock_fix_test.go
-
6weed/s3api/s3api_server.go
@ -0,0 +1,232 @@ |
|||
package s3api |
|||
|
|||
import ( |
|||
"encoding/xml" |
|||
"fmt" |
|||
"strconv" |
|||
|
|||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" |
|||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" |
|||
) |
|||
|
|||
// ObjectLockUtils provides shared utilities for Object Lock configuration
|
|||
// These functions are used by both Admin UI and S3 API handlers to ensure consistency
|
|||
|
|||
// VersioningUtils provides shared utilities for bucket versioning configuration
|
|||
// These functions ensure Admin UI and S3 API use the same versioning keys
|
|||
|
|||
// StoreVersioningInExtended stores versioning configuration in entry extended attributes
|
|||
func StoreVersioningInExtended(entry *filer_pb.Entry, enabled bool) error { |
|||
if entry.Extended == nil { |
|||
entry.Extended = make(map[string][]byte) |
|||
} |
|||
|
|||
if enabled { |
|||
entry.Extended[s3_constants.ExtVersioningKey] = []byte(s3_constants.VersioningEnabled) |
|||
} else { |
|||
entry.Extended[s3_constants.ExtVersioningKey] = []byte(s3_constants.VersioningSuspended) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
// LoadVersioningFromExtended loads versioning configuration from entry extended attributes
|
|||
func LoadVersioningFromExtended(entry *filer_pb.Entry) (bool, bool) { |
|||
if entry == nil || entry.Extended == nil { |
|||
return false, false // not found, default to suspended
|
|||
} |
|||
|
|||
// Check for S3 API compatible key
|
|||
if versioningBytes, exists := entry.Extended[s3_constants.ExtVersioningKey]; exists { |
|||
enabled := string(versioningBytes) == s3_constants.VersioningEnabled |
|||
return enabled, true |
|||
} |
|||
|
|||
return false, false // not found
|
|||
} |
|||
|
|||
// CreateObjectLockConfiguration creates a new ObjectLockConfiguration with the specified parameters
|
|||
func CreateObjectLockConfiguration(enabled bool, mode string, days int, years int) *ObjectLockConfiguration { |
|||
if !enabled { |
|||
return nil |
|||
} |
|||
|
|||
config := &ObjectLockConfiguration{ |
|||
ObjectLockEnabled: s3_constants.ObjectLockEnabled, |
|||
} |
|||
|
|||
// Add default retention rule if mode and period are specified
|
|||
if mode != "" && (days > 0 || years > 0) { |
|||
config.Rule = &ObjectLockRule{ |
|||
DefaultRetention: &DefaultRetention{ |
|||
Mode: mode, |
|||
Days: days, |
|||
Years: years, |
|||
}, |
|||
} |
|||
} |
|||
|
|||
return config |
|||
} |
|||
|
|||
// ObjectLockConfigurationToXML converts ObjectLockConfiguration to XML bytes
|
|||
func ObjectLockConfigurationToXML(config *ObjectLockConfiguration) ([]byte, error) { |
|||
if config == nil { |
|||
return nil, fmt.Errorf("object lock configuration is nil") |
|||
} |
|||
|
|||
return xml.Marshal(config) |
|||
} |
|||
|
|||
// StoreObjectLockConfigurationInExtended stores Object Lock configuration in entry extended attributes
|
|||
func StoreObjectLockConfigurationInExtended(entry *filer_pb.Entry, config *ObjectLockConfiguration) error { |
|||
if entry.Extended == nil { |
|||
entry.Extended = make(map[string][]byte) |
|||
} |
|||
|
|||
if config == nil { |
|||
// Remove Object Lock configuration
|
|||
delete(entry.Extended, s3_constants.ExtObjectLockEnabledKey) |
|||
delete(entry.Extended, s3_constants.ExtObjectLockDefaultModeKey) |
|||
delete(entry.Extended, s3_constants.ExtObjectLockDefaultDaysKey) |
|||
delete(entry.Extended, s3_constants.ExtObjectLockDefaultYearsKey) |
|||
return nil |
|||
} |
|||
|
|||
// Store the enabled flag
|
|||
entry.Extended[s3_constants.ExtObjectLockEnabledKey] = []byte(config.ObjectLockEnabled) |
|||
|
|||
// Store default retention configuration if present
|
|||
if config.Rule != nil && config.Rule.DefaultRetention != nil { |
|||
defaultRetention := config.Rule.DefaultRetention |
|||
|
|||
// Store mode
|
|||
if defaultRetention.Mode != "" { |
|||
entry.Extended[s3_constants.ExtObjectLockDefaultModeKey] = []byte(defaultRetention.Mode) |
|||
} |
|||
|
|||
// Store days
|
|||
if defaultRetention.Days > 0 { |
|||
entry.Extended[s3_constants.ExtObjectLockDefaultDaysKey] = []byte(strconv.Itoa(defaultRetention.Days)) |
|||
} |
|||
|
|||
// Store years
|
|||
if defaultRetention.Years > 0 { |
|||
entry.Extended[s3_constants.ExtObjectLockDefaultYearsKey] = []byte(strconv.Itoa(defaultRetention.Years)) |
|||
} |
|||
} else { |
|||
// Remove default retention if not present
|
|||
delete(entry.Extended, s3_constants.ExtObjectLockDefaultModeKey) |
|||
delete(entry.Extended, s3_constants.ExtObjectLockDefaultDaysKey) |
|||
delete(entry.Extended, s3_constants.ExtObjectLockDefaultYearsKey) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
// LoadObjectLockConfigurationFromExtended loads Object Lock configuration from entry extended attributes
|
|||
func LoadObjectLockConfigurationFromExtended(entry *filer_pb.Entry) (*ObjectLockConfiguration, bool) { |
|||
if entry == nil || entry.Extended == nil { |
|||
return nil, false |
|||
} |
|||
|
|||
// Check if Object Lock is enabled
|
|||
enabledBytes, exists := entry.Extended[s3_constants.ExtObjectLockEnabledKey] |
|||
if !exists { |
|||
return nil, false |
|||
} |
|||
|
|||
enabled := string(enabledBytes) |
|||
if enabled != s3_constants.ObjectLockEnabled && enabled != "true" { |
|||
return nil, false |
|||
} |
|||
|
|||
// Create basic configuration
|
|||
config := &ObjectLockConfiguration{ |
|||
ObjectLockEnabled: s3_constants.ObjectLockEnabled, |
|||
} |
|||
|
|||
// Load default retention configuration if present
|
|||
if modeBytes, exists := entry.Extended[s3_constants.ExtObjectLockDefaultModeKey]; exists { |
|||
mode := string(modeBytes) |
|||
|
|||
// Parse days and years
|
|||
var days, years int |
|||
if daysBytes, exists := entry.Extended[s3_constants.ExtObjectLockDefaultDaysKey]; exists { |
|||
if parsed, err := strconv.Atoi(string(daysBytes)); err == nil { |
|||
days = parsed |
|||
} |
|||
} |
|||
if yearsBytes, exists := entry.Extended[s3_constants.ExtObjectLockDefaultYearsKey]; exists { |
|||
if parsed, err := strconv.Atoi(string(yearsBytes)); err == nil { |
|||
years = parsed |
|||
} |
|||
} |
|||
|
|||
// Create rule if we have a mode and at least days or years
|
|||
if mode != "" && (days > 0 || years > 0) { |
|||
config.Rule = &ObjectLockRule{ |
|||
DefaultRetention: &DefaultRetention{ |
|||
Mode: mode, |
|||
Days: days, |
|||
Years: years, |
|||
}, |
|||
} |
|||
} |
|||
} |
|||
|
|||
return config, true |
|||
} |
|||
|
|||
// ExtractObjectLockInfoFromConfig extracts basic Object Lock information from configuration
|
|||
// Returns: enabled, mode, duration (for UI display)
|
|||
func ExtractObjectLockInfoFromConfig(config *ObjectLockConfiguration) (bool, string, int32) { |
|||
if config == nil || config.ObjectLockEnabled != s3_constants.ObjectLockEnabled { |
|||
return false, "", 0 |
|||
} |
|||
|
|||
if config.Rule == nil || config.Rule.DefaultRetention == nil { |
|||
return true, "", 0 |
|||
} |
|||
|
|||
defaultRetention := config.Rule.DefaultRetention |
|||
|
|||
// Convert years to days for consistent representation
|
|||
days := defaultRetention.Days |
|||
if defaultRetention.Years > 0 { |
|||
days += defaultRetention.Years * 365 |
|||
} |
|||
|
|||
return true, defaultRetention.Mode, int32(days) |
|||
} |
|||
|
|||
// CreateObjectLockConfigurationFromParams creates ObjectLockConfiguration from individual parameters
|
|||
// This is a convenience function for Admin UI usage
|
|||
func CreateObjectLockConfigurationFromParams(enabled bool, mode string, duration int32) *ObjectLockConfiguration { |
|||
if !enabled { |
|||
return nil |
|||
} |
|||
|
|||
return CreateObjectLockConfiguration(enabled, mode, int(duration), 0) |
|||
} |
|||
|
|||
// ValidateObjectLockParameters validates Object Lock parameters before creating configuration
|
|||
func ValidateObjectLockParameters(enabled bool, mode string, duration int32) error { |
|||
if !enabled { |
|||
return nil |
|||
} |
|||
|
|||
if mode != s3_constants.RetentionModeGovernance && mode != s3_constants.RetentionModeCompliance { |
|||
return ErrInvalidObjectLockMode |
|||
} |
|||
|
|||
if duration <= 0 { |
|||
return ErrInvalidObjectLockDuration |
|||
} |
|||
|
|||
if duration > MaxRetentionDays { |
|||
return ErrObjectLockDurationExceeded |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
package s3api |
|||
|
|||
import ( |
|||
"encoding/xml" |
|||
"net/http" |
|||
|
|||
"github.com/seaweedfs/seaweedfs/weed/glog" |
|||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" |
|||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err" |
|||
stats_collect "github.com/seaweedfs/seaweedfs/weed/stats" |
|||
) |
|||
|
|||
// PutObjectLockConfigurationHandler Put object Lock configuration
|
|||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLockConfiguration.html
|
|||
func (s3a *S3ApiServer) PutObjectLockConfigurationHandler(w http.ResponseWriter, r *http.Request) { |
|||
bucket, _ := s3_constants.GetBucketAndObject(r) |
|||
glog.V(3).Infof("PutObjectLockConfigurationHandler %s", bucket) |
|||
|
|||
// Check if Object Lock is available for this bucket (requires versioning)
|
|||
if !s3a.handleObjectLockAvailabilityCheck(w, r, bucket, "PutObjectLockConfigurationHandler") { |
|||
return |
|||
} |
|||
|
|||
// Parse object lock configuration from request body
|
|||
config, err := parseObjectLockConfiguration(r) |
|||
if err != nil { |
|||
glog.Errorf("PutObjectLockConfigurationHandler: failed to parse object lock config: %v", err) |
|||
s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML) |
|||
return |
|||
} |
|||
|
|||
// Validate object lock configuration
|
|||
if err := validateObjectLockConfiguration(config); err != nil { |
|||
glog.Errorf("PutObjectLockConfigurationHandler: invalid object lock config: %v", err) |
|||
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest) |
|||
return |
|||
} |
|||
|
|||
// Set object lock configuration on the bucket
|
|||
errCode := s3a.updateBucketConfig(bucket, func(bucketConfig *BucketConfig) error { |
|||
// Set the cached Object Lock configuration
|
|||
bucketConfig.ObjectLockConfig = config |
|||
return nil |
|||
}) |
|||
|
|||
if errCode != s3err.ErrNone { |
|||
glog.Errorf("PutObjectLockConfigurationHandler: failed to set object lock config: %v", errCode) |
|||
s3err.WriteErrorResponse(w, r, errCode) |
|||
return |
|||
} |
|||
|
|||
// Record metrics
|
|||
stats_collect.RecordBucketActiveTime(bucket) |
|||
|
|||
// Return success (HTTP 200 with no body)
|
|||
w.WriteHeader(http.StatusOK) |
|||
glog.V(3).Infof("PutObjectLockConfigurationHandler: successfully set object lock config for %s", bucket) |
|||
} |
|||
|
|||
// GetObjectLockConfigurationHandler Get object Lock configuration
|
|||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectLockConfiguration.html
|
|||
func (s3a *S3ApiServer) GetObjectLockConfigurationHandler(w http.ResponseWriter, r *http.Request) { |
|||
bucket, _ := s3_constants.GetBucketAndObject(r) |
|||
glog.V(3).Infof("GetObjectLockConfigurationHandler %s", bucket) |
|||
|
|||
// Get bucket configuration
|
|||
bucketConfig, errCode := s3a.getBucketConfig(bucket) |
|||
if errCode != s3err.ErrNone { |
|||
glog.Errorf("GetObjectLockConfigurationHandler: failed to get bucket config: %v", errCode) |
|||
s3err.WriteErrorResponse(w, r, errCode) |
|||
return |
|||
} |
|||
|
|||
var configXML []byte |
|||
|
|||
// Check if we have cached Object Lock configuration
|
|||
if bucketConfig.ObjectLockConfig != nil { |
|||
// Use cached configuration and marshal it to XML for response
|
|||
marshaledXML, err := xml.Marshal(bucketConfig.ObjectLockConfig) |
|||
if err != nil { |
|||
glog.Errorf("GetObjectLockConfigurationHandler: failed to marshal cached Object Lock config: %v", err) |
|||
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|||
return |
|||
} |
|||
|
|||
// Write XML response
|
|||
w.Header().Set("Content-Type", "application/xml") |
|||
w.WriteHeader(http.StatusOK) |
|||
if _, err := w.Write([]byte(xml.Header)); err != nil { |
|||
glog.Errorf("GetObjectLockConfigurationHandler: failed to write XML header: %v", err) |
|||
return |
|||
} |
|||
if _, err := w.Write(marshaledXML); err != nil { |
|||
glog.Errorf("GetObjectLockConfigurationHandler: failed to write config XML: %v", err) |
|||
return |
|||
} |
|||
glog.V(3).Infof("GetObjectLockConfigurationHandler: successfully retrieved cached object lock config for %s", bucket) |
|||
return |
|||
} |
|||
|
|||
// Fallback: check for legacy storage in extended attributes
|
|||
if bucketConfig.Entry.Extended != nil { |
|||
// Check if Object Lock is enabled via boolean flag
|
|||
if enabledBytes, exists := bucketConfig.Entry.Extended[s3_constants.ExtObjectLockEnabledKey]; exists { |
|||
enabled := string(enabledBytes) |
|||
if enabled == s3_constants.ObjectLockEnabled || enabled == "true" { |
|||
// Generate minimal XML configuration for enabled Object Lock without retention policies
|
|||
minimalConfig := `<ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><ObjectLockEnabled>Enabled</ObjectLockEnabled></ObjectLockConfiguration>` |
|||
configXML = []byte(minimalConfig) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// If no Object Lock configuration found, return error
|
|||
if len(configXML) == 0 { |
|||
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchObjectLockConfiguration) |
|||
return |
|||
} |
|||
|
|||
// Set response headers
|
|||
w.Header().Set("Content-Type", "application/xml") |
|||
w.WriteHeader(http.StatusOK) |
|||
|
|||
// Write XML response
|
|||
if _, err := w.Write([]byte(xml.Header)); err != nil { |
|||
glog.Errorf("GetObjectLockConfigurationHandler: failed to write XML header: %v", err) |
|||
return |
|||
} |
|||
|
|||
if _, err := w.Write(configXML); err != nil { |
|||
glog.Errorf("GetObjectLockConfigurationHandler: failed to write config XML: %v", err) |
|||
return |
|||
} |
|||
|
|||
// Record metrics
|
|||
stats_collect.RecordBucketActiveTime(bucket) |
|||
|
|||
glog.V(3).Infof("GetObjectLockConfigurationHandler: successfully retrieved object lock config for %s", bucket) |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
package s3api |
|||
|
|||
import ( |
|||
"encoding/xml" |
|||
"errors" |
|||
"net/http" |
|||
|
|||
"github.com/seaweedfs/seaweedfs/weed/glog" |
|||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" |
|||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err" |
|||
stats_collect "github.com/seaweedfs/seaweedfs/weed/stats" |
|||
) |
|||
|
|||
// PutObjectLegalHoldHandler Put object Legal Hold
|
|||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLegalHold.html
|
|||
func (s3a *S3ApiServer) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) { |
|||
bucket, object := s3_constants.GetBucketAndObject(r) |
|||
glog.V(3).Infof("PutObjectLegalHoldHandler %s %s", bucket, object) |
|||
|
|||
// Check if Object Lock is available for this bucket (requires versioning)
|
|||
if !s3a.handleObjectLockAvailabilityCheck(w, r, bucket, "PutObjectLegalHoldHandler") { |
|||
return |
|||
} |
|||
|
|||
// Get version ID from query parameters
|
|||
versionId := r.URL.Query().Get("versionId") |
|||
|
|||
// Parse legal hold configuration from request body
|
|||
legalHold, err := parseObjectLegalHold(r) |
|||
if err != nil { |
|||
glog.Errorf("PutObjectLegalHoldHandler: failed to parse legal hold config: %v", err) |
|||
s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML) |
|||
return |
|||
} |
|||
|
|||
// Validate legal hold configuration
|
|||
if err := validateLegalHold(legalHold); err != nil { |
|||
glog.Errorf("PutObjectLegalHoldHandler: invalid legal hold config: %v", err) |
|||
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest) |
|||
return |
|||
} |
|||
|
|||
// Set legal hold on the object
|
|||
if err := s3a.setObjectLegalHold(bucket, object, versionId, legalHold); err != nil { |
|||
glog.Errorf("PutObjectLegalHoldHandler: failed to set legal hold: %v", err) |
|||
|
|||
// Handle specific error cases
|
|||
if errors.Is(err, ErrObjectNotFound) || errors.Is(err, ErrVersionNotFound) { |
|||
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey) |
|||
return |
|||
} |
|||
|
|||
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|||
return |
|||
} |
|||
|
|||
// Record metrics
|
|||
stats_collect.RecordBucketActiveTime(bucket) |
|||
|
|||
// Return success (HTTP 200 with no body)
|
|||
w.WriteHeader(http.StatusOK) |
|||
glog.V(3).Infof("PutObjectLegalHoldHandler: successfully set legal hold for %s/%s", bucket, object) |
|||
} |
|||
|
|||
// GetObjectLegalHoldHandler Get object Legal Hold
|
|||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectLegalHold.html
|
|||
func (s3a *S3ApiServer) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) { |
|||
bucket, object := s3_constants.GetBucketAndObject(r) |
|||
glog.V(3).Infof("GetObjectLegalHoldHandler %s %s", bucket, object) |
|||
|
|||
// Check if Object Lock is available for this bucket (requires versioning)
|
|||
if !s3a.handleObjectLockAvailabilityCheck(w, r, bucket, "GetObjectLegalHoldHandler") { |
|||
return |
|||
} |
|||
|
|||
// Get version ID from query parameters
|
|||
versionId := r.URL.Query().Get("versionId") |
|||
|
|||
// Get legal hold configuration for the object
|
|||
legalHold, err := s3a.getObjectLegalHold(bucket, object, versionId) |
|||
if err != nil { |
|||
glog.Errorf("GetObjectLegalHoldHandler: failed to get legal hold: %v", err) |
|||
|
|||
// Handle specific error cases
|
|||
if errors.Is(err, ErrObjectNotFound) || errors.Is(err, ErrVersionNotFound) { |
|||
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey) |
|||
return |
|||
} |
|||
|
|||
if errors.Is(err, ErrNoLegalHoldConfiguration) { |
|||
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchObjectLegalHold) |
|||
return |
|||
} |
|||
|
|||
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|||
return |
|||
} |
|||
|
|||
// Marshal legal hold configuration to XML
|
|||
legalHoldXML, err := xml.Marshal(legalHold) |
|||
if err != nil { |
|||
glog.Errorf("GetObjectLegalHoldHandler: failed to marshal legal hold: %v", err) |
|||
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|||
return |
|||
} |
|||
|
|||
// Set response headers
|
|||
w.Header().Set("Content-Type", "application/xml") |
|||
w.WriteHeader(http.StatusOK) |
|||
|
|||
// Write XML response
|
|||
if _, err := w.Write([]byte(xml.Header)); err != nil { |
|||
glog.Errorf("GetObjectLegalHoldHandler: failed to write XML header: %v", err) |
|||
return |
|||
} |
|||
|
|||
if _, err := w.Write(legalHoldXML); err != nil { |
|||
glog.Errorf("GetObjectLegalHoldHandler: failed to write legal hold XML: %v", err) |
|||
return |
|||
} |
|||
|
|||
// Record metrics
|
|||
stats_collect.RecordBucketActiveTime(bucket) |
|||
|
|||
glog.V(3).Infof("GetObjectLegalHoldHandler: successfully retrieved legal hold for %s/%s", bucket, object) |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
package s3api |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" |
|||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" |
|||
"github.com/stretchr/testify/assert" |
|||
) |
|||
|
|||
// TestVeeamObjectLockBugFix tests the fix for the bug where GetObjectLockConfigurationHandler
|
|||
// would return NoSuchObjectLockConfiguration for buckets with no extended attributes,
|
|||
// even when Object Lock was enabled. This caused Veeam to think Object Lock wasn't supported.
|
|||
func TestVeeamObjectLockBugFix(t *testing.T) { |
|||
|
|||
t.Run("Bug case: bucket with no extended attributes", func(t *testing.T) { |
|||
// This simulates the bug case where a bucket has no extended attributes at all
|
|||
// The old code would immediately return NoSuchObjectLockConfiguration
|
|||
// The new code correctly checks if Object Lock is enabled before returning an error
|
|||
|
|||
bucketConfig := &BucketConfig{ |
|||
Name: "test-bucket", |
|||
Entry: &filer_pb.Entry{ |
|||
Name: "test-bucket", |
|||
Extended: nil, // This is the key - no extended attributes
|
|||
}, |
|||
} |
|||
|
|||
// Simulate the isObjectLockEnabledForBucket logic
|
|||
enabled := false |
|||
if bucketConfig.Entry.Extended != nil { |
|||
if enabledBytes, exists := bucketConfig.Entry.Extended[s3_constants.ExtObjectLockEnabledKey]; exists { |
|||
enabled = string(enabledBytes) == s3_constants.ObjectLockEnabled || string(enabledBytes) == "true" |
|||
} |
|||
} |
|||
|
|||
// Should correctly return false (not enabled) - this would trigger 404 correctly
|
|||
assert.False(t, enabled, "Object Lock should not be enabled when no extended attributes exist") |
|||
}) |
|||
|
|||
t.Run("Fix verification: bucket with Object Lock enabled via boolean flag", func(t *testing.T) { |
|||
// This verifies the fix works when Object Lock is enabled via boolean flag
|
|||
|
|||
bucketConfig := &BucketConfig{ |
|||
Name: "test-bucket", |
|||
Entry: &filer_pb.Entry{ |
|||
Name: "test-bucket", |
|||
Extended: map[string][]byte{ |
|||
s3_constants.ExtObjectLockEnabledKey: []byte("true"), |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
// Simulate the isObjectLockEnabledForBucket logic
|
|||
enabled := false |
|||
if bucketConfig.Entry.Extended != nil { |
|||
if enabledBytes, exists := bucketConfig.Entry.Extended[s3_constants.ExtObjectLockEnabledKey]; exists { |
|||
enabled = string(enabledBytes) == s3_constants.ObjectLockEnabled || string(enabledBytes) == "true" |
|||
} |
|||
} |
|||
|
|||
// Should correctly return true (enabled) - this would generate minimal XML response
|
|||
assert.True(t, enabled, "Object Lock should be enabled when boolean flag is set") |
|||
}) |
|||
|
|||
t.Run("Fix verification: bucket with Object Lock enabled via Enabled constant", func(t *testing.T) { |
|||
// Test using the s3_constants.ObjectLockEnabled constant
|
|||
|
|||
bucketConfig := &BucketConfig{ |
|||
Name: "test-bucket", |
|||
Entry: &filer_pb.Entry{ |
|||
Name: "test-bucket", |
|||
Extended: map[string][]byte{ |
|||
s3_constants.ExtObjectLockEnabledKey: []byte(s3_constants.ObjectLockEnabled), |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
// Simulate the isObjectLockEnabledForBucket logic
|
|||
enabled := false |
|||
if bucketConfig.Entry.Extended != nil { |
|||
if enabledBytes, exists := bucketConfig.Entry.Extended[s3_constants.ExtObjectLockEnabledKey]; exists { |
|||
enabled = string(enabledBytes) == s3_constants.ObjectLockEnabled || string(enabledBytes) == "true" |
|||
} |
|||
} |
|||
|
|||
// Should correctly return true (enabled)
|
|||
assert.True(t, enabled, "Object Lock should be enabled when constant is used") |
|||
}) |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue