mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-07-20 15:02:27 +00:00
filer: allow clearing a bucket read-only flag stuck after quota removal (#10310)
* filer: allow clearing a bucket read-only flag stuck after quota removal Once s3.bucket.quota.enforce marks a bucket read-only, removing or disabling the quota orphans the flag: enforcement skips buckets with a non-positive quota, and fs.configure merges booleans with OR so -readOnly=false could never turn it off. The only way out was deleting the whole path rule. - s3.bucket.quota -op=remove/disable and the admin server quota update now lift the read-only flag on the bucket's path rule - fs.configure now honors explicitly passed false boolean flags (-readOnly, -fsync, -worm) instead of OR-merging them away * filer: ClearBucketReadOnly reports unchanged when the save fails
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
@@ -397,6 +398,14 @@ func (s *AdminServer) SetBucketQuota(bucketName string, quotaBytes int64, quotaE
|
||||
return fmt.Errorf("failed to update bucket quota: %w", err)
|
||||
}
|
||||
|
||||
if quota <= 0 {
|
||||
// with no active quota, quota enforcement can no longer clear a
|
||||
// read-only flag it turned on, so lift it here
|
||||
if _, err := filer.ClearBucketReadOnly(context.Background(), client, "/buckets", bucketName); err != nil {
|
||||
return fmt.Errorf("failed to clear bucket read-only flag: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -262,6 +262,49 @@ func (fc *FilerConf) ApplyBucketQuotaReadOnly(locationPrefix string, usedSize, q
|
||||
return locConf.ReadOnly, true
|
||||
}
|
||||
|
||||
// ClearReadOnly clears the read-only flag on the rule at exactly locationPrefix,
|
||||
// reporting whether the flag was set. This is the explicit unlock for a flag that
|
||||
// ApplyBucketQuotaReadOnly can no longer clear once the quota is gone.
|
||||
func (fc *FilerConf) ClearReadOnly(locationPrefix string) (changed bool) {
|
||||
locConf, found := fc.GetLocationConf(locationPrefix)
|
||||
if !found || !locConf.ReadOnly {
|
||||
return false
|
||||
}
|
||||
locConf.ReadOnly = false
|
||||
fc.SetLocationConf(locConf)
|
||||
return true
|
||||
}
|
||||
|
||||
// ClearBucketReadOnly lifts the read-only flag that quota enforcement may have
|
||||
// left on the bucket's path rule, saving the updated configuration back to the
|
||||
// filer. It reports whether anything was cleared.
|
||||
func ClearBucketReadOnly(ctx context.Context, client filer_pb.SeaweedFilerClient, bucketsPath, bucketName string) (changed bool, err error) {
|
||||
data, err := ReadInsideFiler(ctx, client, DirectoryEtcSeaweedFS, FilerConfName)
|
||||
if err == filer_pb.ErrNotFound || (err == nil && len(data) == 0) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("read %s/%s: %v", DirectoryEtcSeaweedFS, FilerConfName, err)
|
||||
}
|
||||
fc := NewFilerConf()
|
||||
if err = fc.LoadFromBytes(data); err != nil {
|
||||
return false, fmt.Errorf("parse %s/%s: %v", DirectoryEtcSeaweedFS, FilerConfName, err)
|
||||
}
|
||||
// join the rule key exactly as s3.bucket.quota.enforce writes it, so the
|
||||
// exact-match lookup finds the rule even for a non-canonical bucketsPath
|
||||
if !fc.ClearReadOnly(bucketsPath + "/" + bucketName + "/") {
|
||||
return false, nil
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err = fc.ToText(&buf); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err = SaveInsideFiler(ctx, client, DirectoryEtcSeaweedFS, FilerConfName, buf.Bytes()); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (fc *FilerConf) GetCollectionTtls(collection string) (ttls map[string]string) {
|
||||
ttls = make(map[string]string)
|
||||
fc.rules.Walk(func(key []byte, value *filer_pb.FilerConf_PathConf) bool {
|
||||
|
||||
@@ -161,3 +161,24 @@ func TestApplyBucketQuotaReadOnly(t *testing.T) {
|
||||
_, changed = fc.ApplyBucketQuotaReadOnly(prefix, 50, 100)
|
||||
assert.False(t, changed)
|
||||
}
|
||||
|
||||
func TestClearReadOnly(t *testing.T) {
|
||||
const prefix = "/buckets/b/"
|
||||
|
||||
fc := NewFilerConf()
|
||||
assert.False(t, fc.ClearReadOnly(prefix), "no rule to clear")
|
||||
|
||||
// locked by quota enforcement, then quota removed: still clearable
|
||||
fc.ApplyBucketQuotaReadOnly(prefix, 150, 100)
|
||||
assert.True(t, fc.ClearReadOnly(prefix))
|
||||
assert.False(t, fc.MatchStorageRule(prefix).ReadOnly)
|
||||
assert.False(t, fc.ClearReadOnly(prefix), "already writable")
|
||||
|
||||
// clearing the flag keeps the rule's other settings
|
||||
fc = NewFilerConf()
|
||||
fc.SetLocationConf(&filer_pb.FilerConf_PathConf{LocationPrefix: prefix, Ttl: "7d", ReadOnly: true})
|
||||
assert.True(t, fc.ClearReadOnly(prefix))
|
||||
rule := fc.MatchStorageRule(prefix)
|
||||
assert.False(t, rule.ReadOnly)
|
||||
assert.Equal(t, "7d", rule.Ttl)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ func (c *commandFsConfigure) Help() string {
|
||||
# apply the changes
|
||||
fs.configure -locationPrefix=/my/folder -collection=abc -apply
|
||||
|
||||
# example: unlock a bucket that quota enforcement made read-only
|
||||
fs.configure -locationPrefix=/buckets/my_bucket/ -readOnly=false -apply
|
||||
|
||||
# delete the changes
|
||||
fs.configure -locationPrefix=/my/folder -delete -apply
|
||||
|
||||
@@ -131,6 +134,21 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io
|
||||
fc.DeleteLocationConf(*locationPrefix)
|
||||
} else {
|
||||
fc.AddLocationConf(locConf)
|
||||
// AddLocationConf merges boolean fields with OR, which can never turn
|
||||
// a flag off; let an explicitly passed false win, e.g. -readOnly=false
|
||||
// to reopen a bucket that quota enforcement locked
|
||||
if mergedConf, found := fc.GetLocationConf(*locationPrefix); found {
|
||||
fsConfigureCommand.Visit(func(f *flag.Flag) {
|
||||
switch f.Name {
|
||||
case "readOnly":
|
||||
mergedConf.ReadOnly = *isReadOnly
|
||||
case "fsync":
|
||||
mergedConf.Fsync = *fsync
|
||||
case "worm":
|
||||
mergedConf.Worm = *worm
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
)
|
||||
|
||||
@@ -25,6 +26,9 @@ func (c *commandS3BucketQuota) Help() string {
|
||||
|
||||
Example:
|
||||
s3.bucket.quota -name=<bucket_name> -op=set -sizeMB=1024
|
||||
|
||||
Removing or disabling the quota also clears the read-only flag that
|
||||
s3.bucket.quota.enforce may have set on the bucket.
|
||||
`
|
||||
}
|
||||
|
||||
@@ -65,6 +69,7 @@ func (c *commandS3BucketQuota) Do(args []string, commandEnv *CommandEnv, writer
|
||||
}
|
||||
bucketEntry := lookupResp.Entry
|
||||
|
||||
clearReadOnly := false
|
||||
switch *operationName {
|
||||
case "set":
|
||||
bucketEntry.Quota = *sizeMB * 1024 * 1024
|
||||
@@ -73,6 +78,7 @@ func (c *commandS3BucketQuota) Do(args []string, commandEnv *CommandEnv, writer
|
||||
return nil
|
||||
case "remove":
|
||||
bucketEntry.Quota = 0
|
||||
clearReadOnly = true
|
||||
case "enable":
|
||||
if bucketEntry.Quota < 0 {
|
||||
bucketEntry.Quota = -bucketEntry.Quota
|
||||
@@ -81,6 +87,7 @@ func (c *commandS3BucketQuota) Do(args []string, commandEnv *CommandEnv, writer
|
||||
if bucketEntry.Quota > 0 {
|
||||
bucketEntry.Quota = -bucketEntry.Quota
|
||||
}
|
||||
clearReadOnly = true
|
||||
}
|
||||
|
||||
if err := filer_pb.UpdateEntry(context.Background(), client, &filer_pb.UpdateEntryRequest{
|
||||
@@ -92,6 +99,18 @@ func (c *commandS3BucketQuota) Do(args []string, commandEnv *CommandEnv, writer
|
||||
|
||||
println("updated quota for bucket", *bucketName)
|
||||
|
||||
if clearReadOnly {
|
||||
// with the quota removed or disabled, s3.bucket.quota.enforce can no
|
||||
// longer clear a read-only flag it turned on, so lift it here
|
||||
cleared, err := filer.ClearBucketReadOnly(ctx, client, filerBucketsPath, *bucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cleared {
|
||||
fmt.Fprintf(writer, "cleared read-only for bucket %s\n", *bucketName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user