From 5f1f4178aed31ad5523e7383096be9d1df70e21d Mon Sep 17 00:00:00 2001 From: Feng Ruohang Date: Sun, 25 Oct 2026 21:01:01 +0800 Subject: [PATCH] fix: return current bucket inventory from account info Signed-off-by: Feng Ruohang (cherry picked from commit 6921f8c18693d6426a188624ffc9205f8986761a) --- cmd/admin-account-buckets_test.go | 80 +++++++++++++++++++++++++++++++ cmd/admin-handlers-users.go | 5 +- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 cmd/admin-account-buckets_test.go diff --git a/cmd/admin-account-buckets_test.go b/cmd/admin-account-buckets_test.go new file mode 100644 index 000000000..ffe7bea6b --- /dev/null +++ b/cmd/admin-account-buckets_test.go @@ -0,0 +1,80 @@ +// Copyright (c) 2026 PGSTY +// SPDX-License-Identifier: AGPL-3.0-or-later + +package cmd + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/minio/madmin-go/v3" + "github.com/minio/minio/internal/cachevalue" +) + +func TestAccountInfoReflectsBucketMutations(t *testing.T) { + ctx := t.Context() + bed, err := prepareAdminErasureTestBed(ctx) + if err != nil { + t.Fatal(err) + } + defer bed.TearDown() + previousCache := listBucketsCache + defer func() { listBucketsCache = previousCache }() + + // Hold a real pre-mutation snapshot past the assertions so the regression + // cannot disappear merely because a slow test outlasted the cache TTL. + freezeListing := func() { + t.Helper() + buckets, err := bed.objLayer.ListBuckets(ctx, BucketOptions{}) + if err != nil { + t.Fatal(err) + } + listBucketsCache = cachevalue.NewFromFunc(time.Hour, cachevalue.Opts{}, func(context.Context) ([]BucketInfo, error) { + return buckets, nil + }) + if _, err := listBucketsCache.GetWithCtx(ctx); err != nil { + t.Fatal(err) + } + } + const bucket = "account-info-freshness" + assertPresent := func(want bool) { + t.Helper() + req, err := buildAdminRequest(nil, http.MethodGet, "/accountinfo", 0, nil) + if err != nil { + t.Fatal(err) + } + rec := httptest.NewRecorder() + bed.router.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("account info: %d %s", rec.Code, rec.Body.String()) + } + var info madmin.AccountInfo + if err := json.Unmarshal(rec.Body.Bytes(), &info); err != nil { + t.Fatal(err) + } + found := false + for _, item := range info.Buckets { + if item.Name == bucket { + found = true + } + } + if found != want { + t.Errorf("account info bucket present = %v, want %v", found, want) + } + } + + freezeListing() + if err := bed.objLayer.MakeBucket(ctx, bucket, MakeBucketOptions{}); err != nil { + t.Fatal(err) + } + assertPresent(true) + freezeListing() + if err := bed.objLayer.DeleteBucket(ctx, bucket, DeleteBucketOptions{}); err != nil { + t.Fatal(err) + } + assertPresent(false) +} diff --git a/cmd/admin-handlers-users.go b/cmd/admin-handlers-users.go index 590788b37..29a240f41 100644 --- a/cmd/admin-handlers-users.go +++ b/cmd/admin-handlers-users.go @@ -1447,7 +1447,10 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ return buckets[i].Name < buckets[j].Name }) } else { - buckets, err = objectAPI.ListBuckets(ctx, BucketOptions{Cached: true}) + // Console refreshes this inventory immediately after bucket mutations. + // A stale cached list can otherwise hide a creation or retain a deletion + // indefinitely in the client's snapshot. + buckets, err = objectAPI.ListBuckets(ctx, BucketOptions{}) if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return