S3: load bucket object locking configuration if not found in cache (#7422)
* load bucket object locking configuration if not found in cache * fix cache building, more specific error, add back metrics
This commit is contained in:
@@ -109,6 +109,9 @@ func (s3a *S3ApiServer) updateBucketConfigCacheFromEntry(entry *filer_pb.Entry)
|
|||||||
|
|
||||||
bucket := entry.Name
|
bucket := entry.Name
|
||||||
|
|
||||||
|
glog.V(3).Infof("updateBucketConfigCacheFromEntry: called for bucket %s, ExtObjectLockEnabledKey=%s",
|
||||||
|
bucket, string(entry.Extended[s3_constants.ExtObjectLockEnabledKey]))
|
||||||
|
|
||||||
// Create new bucket config from the entry
|
// Create new bucket config from the entry
|
||||||
config := &BucketConfig{
|
config := &BucketConfig{
|
||||||
Name: bucket,
|
Name: bucket,
|
||||||
@@ -138,7 +141,9 @@ func (s3a *S3ApiServer) updateBucketConfigCacheFromEntry(entry *filer_pb.Entry)
|
|||||||
// Parse Object Lock configuration if present
|
// Parse Object Lock configuration if present
|
||||||
if objectLockConfig, found := LoadObjectLockConfigurationFromExtended(entry); found {
|
if objectLockConfig, found := LoadObjectLockConfigurationFromExtended(entry); found {
|
||||||
config.ObjectLockConfig = objectLockConfig
|
config.ObjectLockConfig = objectLockConfig
|
||||||
glog.V(2).Infof("updateBucketConfigCacheFromEntry: cached Object Lock configuration for bucket %s", bucket)
|
glog.V(2).Infof("updateBucketConfigCacheFromEntry: cached Object Lock configuration for bucket %s: %+v", bucket, objectLockConfig)
|
||||||
|
} else {
|
||||||
|
glog.V(3).Infof("updateBucketConfigCacheFromEntry: no Object Lock configuration found for bucket %s", bucket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +161,7 @@ func (s3a *S3ApiServer) updateBucketConfigCacheFromEntry(entry *filer_pb.Entry)
|
|||||||
config.LastModified = time.Now()
|
config.LastModified = time.Now()
|
||||||
|
|
||||||
// Update cache
|
// Update cache
|
||||||
|
glog.V(3).Infof("updateBucketConfigCacheFromEntry: updating cache for bucket %s, ObjectLockConfig=%+v", bucket, config.ObjectLockConfig)
|
||||||
s3a.bucketConfigCache.Set(bucket, config)
|
s3a.bucketConfigCache.Set(bucket, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -350,6 +350,8 @@ func (s3a *S3ApiServer) getBucketConfig(bucket string) (*BucketConfig, s3err.Err
|
|||||||
|
|
||||||
// Extract configuration from extended attributes
|
// Extract configuration from extended attributes
|
||||||
if entry.Extended != nil {
|
if entry.Extended != nil {
|
||||||
|
glog.V(3).Infof("getBucketConfig: checking extended attributes for bucket %s, ExtObjectLockEnabledKey value=%s",
|
||||||
|
bucket, string(entry.Extended[s3_constants.ExtObjectLockEnabledKey]))
|
||||||
if versioning, exists := entry.Extended[s3_constants.ExtVersioningKey]; exists {
|
if versioning, exists := entry.Extended[s3_constants.ExtVersioningKey]; exists {
|
||||||
config.Versioning = string(versioning)
|
config.Versioning = string(versioning)
|
||||||
}
|
}
|
||||||
@@ -370,7 +372,9 @@ func (s3a *S3ApiServer) getBucketConfig(bucket string) (*BucketConfig, s3err.Err
|
|||||||
// Parse Object Lock configuration if present
|
// Parse Object Lock configuration if present
|
||||||
if objectLockConfig, found := LoadObjectLockConfigurationFromExtended(entry); found {
|
if objectLockConfig, found := LoadObjectLockConfigurationFromExtended(entry); found {
|
||||||
config.ObjectLockConfig = objectLockConfig
|
config.ObjectLockConfig = objectLockConfig
|
||||||
glog.V(2).Infof("getBucketConfig: cached Object Lock configuration for bucket %s", bucket)
|
glog.V(3).Infof("getBucketConfig: loaded Object Lock config from extended attributes for bucket %s: %+v", bucket, objectLockConfig)
|
||||||
|
} else {
|
||||||
|
glog.V(3).Infof("getBucketConfig: no Object Lock config found in extended attributes for bucket %s", bucket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,20 +430,26 @@ func (s3a *S3ApiServer) updateBucketConfig(bucket string, updateFn func(*BucketC
|
|||||||
}
|
}
|
||||||
// Update Object Lock configuration
|
// Update Object Lock configuration
|
||||||
if config.ObjectLockConfig != nil {
|
if config.ObjectLockConfig != nil {
|
||||||
|
glog.V(3).Infof("updateBucketConfig: storing Object Lock config for bucket %s: %+v", bucket, config.ObjectLockConfig)
|
||||||
if err := StoreObjectLockConfigurationInExtended(config.Entry, config.ObjectLockConfig); err != nil {
|
if err := StoreObjectLockConfigurationInExtended(config.Entry, config.ObjectLockConfig); err != nil {
|
||||||
glog.Errorf("updateBucketConfig: failed to store Object Lock configuration for bucket %s: %v", bucket, err)
|
glog.Errorf("updateBucketConfig: failed to store Object Lock configuration for bucket %s: %v", bucket, err)
|
||||||
return s3err.ErrInternalError
|
return s3err.ErrInternalError
|
||||||
}
|
}
|
||||||
|
glog.V(3).Infof("updateBucketConfig: stored Object Lock config in extended attributes for bucket %s, key=%s, value=%s",
|
||||||
|
bucket, s3_constants.ExtObjectLockEnabledKey, string(config.Entry.Extended[s3_constants.ExtObjectLockEnabledKey]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to filer
|
// Save to filer
|
||||||
|
glog.V(3).Infof("updateBucketConfig: saving entry to filer for bucket %s", bucket)
|
||||||
err := s3a.updateEntry(s3a.option.BucketsPath, config.Entry)
|
err := s3a.updateEntry(s3a.option.BucketsPath, config.Entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("updateBucketConfig: failed to update bucket entry for %s: %v", bucket, err)
|
glog.Errorf("updateBucketConfig: failed to update bucket entry for %s: %v", bucket, err)
|
||||||
return s3err.ErrInternalError
|
return s3err.ErrInternalError
|
||||||
}
|
}
|
||||||
|
glog.V(3).Infof("updateBucketConfig: saved entry to filer for bucket %s", bucket)
|
||||||
|
|
||||||
// Update cache
|
// Update cache
|
||||||
|
glog.V(3).Infof("updateBucketConfig: updating cache for bucket %s, ObjectLockConfig=%+v", bucket, config.ObjectLockConfig)
|
||||||
s3a.bucketConfigCache.Set(bucket, config)
|
s3a.bucketConfigCache.Set(bucket, config)
|
||||||
|
|
||||||
return s3err.ErrNone
|
return s3err.ErrNone
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
// Set the cached Object Lock configuration
|
// Set the cached Object Lock configuration
|
||||||
bucketConfig.ObjectLockConfig = objectLockConfig
|
bucketConfig.ObjectLockConfig = objectLockConfig
|
||||||
|
glog.V(3).Infof("PutBucketHandler: set ObjectLockConfig for bucket %s: %+v", bucket, objectLockConfig)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package s3api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||||
stats_collect "github.com/seaweedfs/seaweedfs/weed/stats"
|
stats_collect "github.com/seaweedfs/seaweedfs/weed/stats"
|
||||||
@@ -82,7 +82,7 @@ func (s3a *S3ApiServer) GetObjectLockConfigurationHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var configXML []byte
|
glog.V(3).Infof("GetObjectLockConfigurationHandler: retrieved bucket config for %s, ObjectLockConfig=%+v", bucket, bucketConfig.ObjectLockConfig)
|
||||||
|
|
||||||
// Check if we have cached Object Lock configuration
|
// Check if we have cached Object Lock configuration
|
||||||
if bucketConfig.ObjectLockConfig != nil {
|
if bucketConfig.ObjectLockConfig != nil {
|
||||||
@@ -105,46 +105,67 @@ func (s3a *S3ApiServer) GetObjectLockConfigurationHandler(w http.ResponseWriter,
|
|||||||
glog.Errorf("GetObjectLockConfigurationHandler: failed to write config XML: %v", err)
|
glog.Errorf("GetObjectLockConfigurationHandler: failed to write config XML: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record metrics
|
||||||
|
stats_collect.RecordBucketActiveTime(bucket)
|
||||||
|
|
||||||
glog.V(3).Infof("GetObjectLockConfigurationHandler: successfully retrieved cached object lock config for %s", bucket)
|
glog.V(3).Infof("GetObjectLockConfigurationHandler: successfully retrieved cached object lock config for %s", bucket)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: check for legacy storage in extended attributes
|
// If no cached Object Lock configuration, reload entry from filer to get the latest extended attributes
|
||||||
if bucketConfig.Entry.Extended != nil {
|
// This handles cases where the cache might have a stale entry due to timing issues with metadata updates
|
||||||
// Check if Object Lock is enabled via boolean flag
|
glog.V(3).Infof("GetObjectLockConfigurationHandler: no cached ObjectLockConfig, reloading entry from filer for %s", bucket)
|
||||||
if enabledBytes, exists := bucketConfig.Entry.Extended[s3_constants.ExtObjectLockEnabledKey]; exists {
|
freshEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
|
||||||
enabled := string(enabledBytes)
|
if err != nil {
|
||||||
if enabled == s3_constants.ObjectLockEnabled || enabled == "true" {
|
if errors.Is(err, filer_pb.ErrNotFound) {
|
||||||
// Generate minimal XML configuration for enabled Object Lock without retention policies
|
glog.V(1).Infof("GetObjectLockConfigurationHandler: bucket %s not found while reloading entry", bucket)
|
||||||
minimalConfig := `<ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><ObjectLockEnabled>Enabled</ObjectLockEnabled></ObjectLockConfiguration>`
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
||||||
configXML = []byte(minimalConfig)
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
glog.Errorf("GetObjectLockConfigurationHandler: failed to reload bucket entry: %v", err)
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
||||||
// If no Object Lock configuration found, return error
|
|
||||||
if len(configXML) == 0 {
|
|
||||||
s3err.WriteErrorResponse(w, r, s3err.ErrObjectLockConfigurationNotFoundError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set response headers
|
// Try to load Object Lock configuration from the fresh entry
|
||||||
w.Header().Set("Content-Type", "application/xml")
|
// LoadObjectLockConfigurationFromExtended already checks ExtObjectLockEnabledKey and returns
|
||||||
w.WriteHeader(http.StatusOK)
|
// a basic configuration even when there's no default retention policy
|
||||||
|
if objectLockConfig, found := LoadObjectLockConfigurationFromExtended(freshEntry); found {
|
||||||
|
glog.V(3).Infof("GetObjectLockConfigurationHandler: loaded Object Lock config from fresh entry for %s: %+v", bucket, objectLockConfig)
|
||||||
|
|
||||||
// Write XML response
|
// Rebuild the entire cached config from the fresh entry to maintain cache coherence
|
||||||
if _, err := w.Write([]byte(xml.Header)); err != nil {
|
// This ensures all fields (Versioning, Owner, ACL, IsPublicRead, CORS, etc.) are up-to-date,
|
||||||
glog.Errorf("GetObjectLockConfigurationHandler: failed to write XML header: %v", err)
|
// not just ObjectLockConfig, before resetting the TTL
|
||||||
|
s3a.updateBucketConfigCacheFromEntry(freshEntry)
|
||||||
|
|
||||||
|
// Marshal and return the configuration
|
||||||
|
marshaledXML, err := xml.Marshal(objectLockConfig)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("GetObjectLockConfigurationHandler: failed to marshal Object Lock config: %v", err)
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record metrics
|
||||||
|
stats_collect.RecordBucketActiveTime(bucket)
|
||||||
|
|
||||||
|
glog.V(3).Infof("GetObjectLockConfigurationHandler: successfully retrieved object lock config from fresh entry for %s", bucket)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := w.Write(configXML); err != nil {
|
// No Object Lock configuration found
|
||||||
glog.Errorf("GetObjectLockConfigurationHandler: failed to write config XML: %v", err)
|
glog.V(3).Infof("GetObjectLockConfigurationHandler: no Object Lock configuration found for %s", bucket)
|
||||||
return
|
s3err.WriteErrorResponse(w, r, s3err.ErrObjectLockConfigurationNotFoundError)
|
||||||
}
|
|
||||||
|
|
||||||
// Record metrics
|
|
||||||
stats_collect.RecordBucketActiveTime(bucket)
|
|
||||||
|
|
||||||
glog.V(3).Infof("GetObjectLockConfigurationHandler: successfully retrieved object lock config for %s", bucket)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user