mirror of
https://github.com/minio/minio.git
synced 2024-12-25 06:35:56 -05:00
2b5e4b853c
* Remove lock for cached operations. * Rename "Relax" to `ReturnLastGood`. * Add `CacheError` to allow caching values even on errors. * Add NoWait that will return current value with async fetching if within 2xTTL. * Make benchmark somewhat representative. ``` Before: BenchmarkCache-12 16408370 63.12 ns/op 0 B/op After: BenchmarkCache-12 428282187 2.789 ns/op 0 B/op ``` * Remove `storageRESTClient.scanning`. Nonsensical - RPC clients will not have any idea about scanning. * Always fetch remote diskinfo metrics and cache them. Seems most calls are requesting metrics. * Do async fetching of usage caches.
145 lines
4.5 KiB
Go
145 lines
4.5 KiB
Go
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
//
|
|
// This file is part of MinIO Object Storage stack
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/minio/madmin-go/v3"
|
|
"github.com/minio/minio/internal/cachevalue"
|
|
"github.com/minio/minio/internal/logger"
|
|
)
|
|
|
|
// BucketQuotaSys - map of bucket and quota configuration.
|
|
type BucketQuotaSys struct{}
|
|
|
|
// Get - Get quota configuration.
|
|
func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) {
|
|
cfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
|
|
return cfg, err
|
|
}
|
|
|
|
// NewBucketQuotaSys returns initialized BucketQuotaSys
|
|
func NewBucketQuotaSys() *BucketQuotaSys {
|
|
return &BucketQuotaSys{}
|
|
}
|
|
|
|
var bucketStorageCache = cachevalue.New[DataUsageInfo]()
|
|
|
|
// Init initialize bucket quota.
|
|
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
|
bucketStorageCache.Once.Do(func() {
|
|
// Set this to 10 secs since its enough, as scanner
|
|
// does not update the bucket usage values frequently.
|
|
bucketStorageCache.TTL = 10 * time.Second
|
|
// Rely on older value if usage loading fails from disk.
|
|
bucketStorageCache.ReturnLastGood = true
|
|
bucketStorageCache.NoWait = true
|
|
bucketStorageCache.Update = func() (DataUsageInfo, error) {
|
|
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer done()
|
|
|
|
return loadDataUsageFromBackend(ctx, objAPI)
|
|
}
|
|
})
|
|
}
|
|
|
|
// GetBucketUsageInfo return bucket usage info for a given bucket
|
|
func (sys *BucketQuotaSys) GetBucketUsageInfo(bucket string) (BucketUsageInfo, error) {
|
|
dui, err := bucketStorageCache.Get()
|
|
timedout := OperationTimedOut{}
|
|
if err != nil && !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &timedout) {
|
|
if len(dui.BucketsUsage) > 0 {
|
|
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to retrieve usage information for bucket: %s, relying on older value cached in-memory: err(%v)", bucket, err), "bucket-usage-cache-"+bucket)
|
|
} else {
|
|
logger.LogOnceIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"), "bucket-usage-empty-"+bucket)
|
|
}
|
|
}
|
|
|
|
if len(dui.BucketsUsage) > 0 {
|
|
bui, ok := dui.BucketsUsage[bucket]
|
|
if ok {
|
|
return bui, nil
|
|
}
|
|
}
|
|
return BucketUsageInfo{}, nil
|
|
}
|
|
|
|
// parseBucketQuota parses BucketQuota from json
|
|
func parseBucketQuota(bucket string, data []byte) (quotaCfg *madmin.BucketQuota, err error) {
|
|
quotaCfg = &madmin.BucketQuota{}
|
|
if err = json.Unmarshal(data, quotaCfg); err != nil {
|
|
return quotaCfg, err
|
|
}
|
|
if !quotaCfg.IsValid() {
|
|
if quotaCfg.Type == "fifo" {
|
|
logger.LogIf(GlobalContext, errors.New("Detected older 'fifo' quota config, 'fifo' feature is removed and not supported anymore. Please clear your quota configs using 'mc admin bucket quota alias/bucket --clear' and use 'mc ilm add' for expiration of objects"))
|
|
return quotaCfg, fmt.Errorf("invalid quota type 'fifo'")
|
|
}
|
|
return quotaCfg, fmt.Errorf("Invalid quota config %#v", quotaCfg)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (sys *BucketQuotaSys) enforceQuotaHard(ctx context.Context, bucket string, size int64) error {
|
|
if size < 0 {
|
|
return nil
|
|
}
|
|
|
|
q, err := sys.Get(ctx, bucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var quotaSize uint64
|
|
if q != nil && q.Type == madmin.HardQuota {
|
|
if q.Size > 0 {
|
|
quotaSize = q.Size
|
|
} else if q.Quota > 0 {
|
|
quotaSize = q.Quota
|
|
}
|
|
}
|
|
if quotaSize > 0 {
|
|
if uint64(size) >= quotaSize { // check if file size already exceeds the quota
|
|
return BucketQuotaExceeded{Bucket: bucket}
|
|
}
|
|
|
|
bui, err := sys.GetBucketUsageInfo(bucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if bui.Size > 0 && ((bui.Size + uint64(size)) >= quotaSize) {
|
|
return BucketQuotaExceeded{Bucket: bucket}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func enforceBucketQuotaHard(ctx context.Context, bucket string, size int64) error {
|
|
if globalBucketQuotaSys == nil {
|
|
return nil
|
|
}
|
|
return globalBucketQuotaSys.enforceQuotaHard(ctx, bucket, size)
|
|
}
|