Browse Source

admin ui and api handler are consistent now

pull/6996/head
chrislu 5 months ago
parent
commit
0d4eeb67be
  1. 57
      weed/admin/dash/admin_server.go
  2. 46
      weed/admin/dash/bucket_management.go
  3. 228
      weed/s3api/object_lock_utils.go

57
weed/admin/dash/admin_server.go

@ -5,7 +5,6 @@ import (
"context"
"fmt"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
@ -24,6 +23,8 @@ import (
"github.com/seaweedfs/seaweedfs/weed/util"
"github.com/seaweedfs/seaweedfs/weed/wdclient"
"google.golang.org/grpc"
"github.com/seaweedfs/seaweedfs/weed/s3api"
)
type AdminServer struct {
@ -293,20 +294,11 @@ func (s *AdminServer) GetS3Buckets() ([]S3Bucket, error) {
var objectLockDuration int32 = 0
if resp.Entry.Extended != nil {
if versioningBytes, exists := resp.Entry.Extended["s3.versioning"]; exists {
versioningEnabled = string(versioningBytes) == "Enabled"
}
if objectLockBytes, exists := resp.Entry.Extended["s3.objectlock"]; exists {
objectLockEnabled = string(objectLockBytes) == "Enabled"
}
if objectLockModeBytes, exists := resp.Entry.Extended["s3.objectlock.mode"]; exists {
objectLockMode = string(objectLockModeBytes)
}
if objectLockDurationBytes, exists := resp.Entry.Extended["s3.objectlock.duration"]; exists {
if duration, err := strconv.ParseInt(string(objectLockDurationBytes), 10, 32); err == nil {
objectLockDuration = int32(duration)
}
}
// Use shared utility to extract versioning information
versioningEnabled = extractVersioningFromEntry(resp.Entry)
// Use shared utility to extract Object Lock information
objectLockEnabled, objectLockMode, objectLockDuration = extractObjectLockInfoFromEntry(resp.Entry)
}
bucket := S3Bucket{
@ -379,20 +371,11 @@ func (s *AdminServer) GetBucketDetails(bucketName string) (*BucketDetails, error
var objectLockDuration int32 = 0
if bucketResp.Entry.Extended != nil {
if versioningBytes, exists := bucketResp.Entry.Extended["s3.versioning"]; exists {
versioningEnabled = string(versioningBytes) == "Enabled"
}
if objectLockBytes, exists := bucketResp.Entry.Extended["s3.objectlock"]; exists {
objectLockEnabled = string(objectLockBytes) == "Enabled"
}
if objectLockModeBytes, exists := bucketResp.Entry.Extended["s3.objectlock.mode"]; exists {
objectLockMode = string(objectLockModeBytes)
}
if objectLockDurationBytes, exists := bucketResp.Entry.Extended["s3.objectlock.duration"]; exists {
if duration, err := strconv.ParseInt(string(objectLockDurationBytes), 10, 32); err == nil {
objectLockDuration = int32(duration)
}
}
// Use shared utility to extract versioning information
versioningEnabled = extractVersioningFromEntry(bucketResp.Entry)
// Use shared utility to extract Object Lock information
objectLockEnabled, objectLockMode, objectLockDuration = extractObjectLockInfoFromEntry(bucketResp.Entry)
}
details.Bucket.VersioningEnabled = versioningEnabled
@ -1502,3 +1485,19 @@ func (s *AdminServer) Shutdown() {
glog.V(1).Infof("Admin server shutdown complete")
}
// Function to extract Object Lock information from bucket entry using shared utilities
func extractObjectLockInfoFromEntry(entry *filer_pb.Entry) (bool, string, int32) {
// Try to load Object Lock configuration using shared utility
if config, found := s3api.LoadObjectLockConfigurationFromExtended(entry); found {
return s3api.ExtractObjectLockInfoFromConfig(config)
}
return false, "", 0
}
// Function to extract versioning information from bucket entry using shared utilities
func extractVersioningFromEntry(entry *filer_pb.Entry) bool {
enabled, _ := s3api.LoadVersioningFromExtended(entry)
return enabled
}

46
weed/admin/dash/bucket_management.go

@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api"
)
// S3 Bucket management data structures for templates
@ -340,32 +341,43 @@ func (s *AdminServer) CreateS3BucketWithObjectLock(bucketName string, quotaBytes
TtlSec: 0,
}
// Create extended attributes map for versioning and object lock
// Create extended attributes map for versioning
extended := make(map[string][]byte)
if versioningEnabled {
extended["s3.versioning"] = []byte("Enabled")
} else {
extended["s3.versioning"] = []byte("Suspended")
// Create bucket entry
bucketEntry := &filer_pb.Entry{
Name: bucketName,
IsDirectory: true,
Attributes: attributes,
Extended: extended,
Quota: quota,
}
// Handle versioning using shared utilities
if err := s3api.StoreVersioningInExtended(bucketEntry, versioningEnabled); err != nil {
return fmt.Errorf("failed to store versioning configuration: %w", err)
}
// Handle Object Lock configuration using shared utilities
if objectLockEnabled {
extended["s3.objectlock"] = []byte("Enabled")
extended["s3.objectlock.mode"] = []byte(objectLockMode)
extended["s3.objectlock.duration"] = []byte(fmt.Sprintf("%d", objectLockDuration))
} else {
extended["s3.objectlock"] = []byte("Disabled")
// Validate Object Lock parameters
if err := s3api.ValidateObjectLockParameters(objectLockEnabled, objectLockMode, objectLockDuration); err != nil {
return fmt.Errorf("invalid Object Lock parameters: %w", err)
}
// Create Object Lock configuration using shared utility
objectLockConfig := s3api.CreateObjectLockConfigurationFromParams(objectLockEnabled, objectLockMode, objectLockDuration)
// Store Object Lock configuration in extended attributes using shared utility
if err := s3api.StoreObjectLockConfigurationInExtended(bucketEntry, objectLockConfig); err != nil {
return fmt.Errorf("failed to store Object Lock configuration: %w", err)
}
}
// Create bucket directory under /buckets
_, err = client.CreateEntry(context.Background(), &filer_pb.CreateEntryRequest{
Directory: "/buckets",
Entry: &filer_pb.Entry{
Name: bucketName,
IsDirectory: true,
Attributes: attributes,
Extended: extended,
Quota: quota,
},
Entry: bucketEntry,
})
if err != nil {
return fmt.Errorf("failed to create bucket directory: %w", err)

228
weed/s3api/object_lock_utils.go

@ -0,0 +1,228 @@
package s3api
import (
"encoding/xml"
"fmt"
"strconv"
"strings"
"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)
}
// XMLToObjectLockConfiguration parses XML bytes to ObjectLockConfiguration
func XMLToObjectLockConfiguration(xmlData []byte) (*ObjectLockConfiguration, error) {
if len(xmlData) == 0 {
return nil, fmt.Errorf("XML data is empty")
}
var config ObjectLockConfiguration
if err := xml.Unmarshal(xmlData, &config); err != nil {
return nil, fmt.Errorf("failed to parse Object Lock configuration XML: %w", err)
}
return &config, nil
}
// 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.ExtObjectLockConfigKey)
return nil
}
// Store the enabled flag
entry.Extended[s3_constants.ExtObjectLockEnabledKey] = []byte(config.ObjectLockEnabled)
// Store the full XML configuration
configXML, err := ObjectLockConfigurationToXML(config)
if err != nil {
return fmt.Errorf("failed to marshal Object Lock configuration: %w", err)
}
entry.Extended[s3_constants.ExtObjectLockConfigKey] = configXML
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
}
// Try to load full XML configuration
if configXML, exists := entry.Extended[s3_constants.ExtObjectLockConfigKey]; exists {
if config, err := XMLToObjectLockConfiguration(configXML); err == nil {
return config, true
}
}
// Fallback: create minimal configuration for enabled Object Lock
return &ObjectLockConfiguration{
ObjectLockEnabled: s3_constants.ObjectLockEnabled,
}, 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 fmt.Errorf("invalid object lock mode: %s, must be GOVERNANCE or COMPLIANCE", mode)
}
if duration <= 0 {
return fmt.Errorf("object lock duration must be greater than 0 days")
}
if duration > MaxRetentionDays {
return fmt.Errorf("object lock duration exceeds maximum allowed days: %d", MaxRetentionDays)
}
return nil
}
// SimpleXMLParseObjectLockMode extracts mode from XML string using simple string parsing
// This is used as a fallback when full XML parsing is not needed
func SimpleXMLParseObjectLockMode(xmlStr string) string {
if strings.Contains(xmlStr, "<Mode>GOVERNANCE</Mode>") {
return "GOVERNANCE"
} else if strings.Contains(xmlStr, "<Mode>COMPLIANCE</Mode>") {
return "COMPLIANCE"
}
return ""
}
// SimpleXMLParseObjectLockDays extracts days from XML string using simple string parsing
// This is used as a fallback when full XML parsing is not needed
func SimpleXMLParseObjectLockDays(xmlStr string) int32 {
if daysStart := strings.Index(xmlStr, "<Days>"); daysStart != -1 {
daysStart += 6 // length of "<Days>"
if daysEnd := strings.Index(xmlStr[daysStart:], "</Days>"); daysEnd != -1 {
if duration, err := strconv.ParseInt(xmlStr[daysStart:daysStart+daysEnd], 10, 32); err == nil {
return int32(duration)
}
}
}
return 0
}
Loading…
Cancel
Save