Add API's for managing bucket quota (#9379)
This PR allows setting a "hard" or "fifo" quota restriction at the bucket level. Buckets that have reached the FIFO quota configured, will automatically be cleaned up in FIFO manner until bucket usage drops to configured quota. If a bucket is configured with a "hard" quota ceiling, all further writes are disallowed.master
parent
27632ca6ec
commit
9a547dcbfb
@ -0,0 +1,150 @@ |
||||
/* |
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package cmd |
||||
|
||||
import ( |
||||
"io/ioutil" |
||||
"net/http" |
||||
"path" |
||||
|
||||
"github.com/gorilla/mux" |
||||
"github.com/minio/minio/cmd/config" |
||||
"github.com/minio/minio/pkg/env" |
||||
iampolicy "github.com/minio/minio/pkg/iam/policy" |
||||
) |
||||
|
||||
const ( |
||||
bucketQuotaConfigFile = "quota.json" |
||||
) |
||||
|
||||
// PutBucketQuotaConfigHandler - PUT Bucket quota configuration.
|
||||
// ----------
|
||||
// Places a quota configuration on the specified bucket. The quota
|
||||
// specified in the quota configuration will be applied by default
|
||||
// to enforce total quota for the specified bucket.
|
||||
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) { |
||||
ctx := newContext(r, w, "PutBucketQuotaConfig") |
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction) |
||||
if objectAPI == nil { |
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) |
||||
return |
||||
} |
||||
|
||||
vars := mux.Vars(r) |
||||
bucket := vars["bucket"] |
||||
|
||||
// Turn off quota commands if data usage info is unavailable.
|
||||
if env.Get(envDataUsageCrawlConf, config.EnableOn) == config.EnableOff { |
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminBucketQuotaDisabled), r.URL) |
||||
return |
||||
} |
||||
|
||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { |
||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL) |
||||
return |
||||
} |
||||
defer r.Body.Close() |
||||
data, err := ioutil.ReadAll(r.Body) |
||||
if err != nil { |
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL) |
||||
return |
||||
} |
||||
quotaCfg, err := parseBucketQuota(data) |
||||
if err != nil { |
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) |
||||
return |
||||
} |
||||
configFile := path.Join(bucketConfigPrefix, bucket, bucketQuotaConfigFile) |
||||
if err = saveConfig(ctx, objectAPI, configFile, data); err != nil { |
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) |
||||
return |
||||
} |
||||
if quotaCfg.Quota > 0 { |
||||
globalBucketQuotaSys.Set(bucket, quotaCfg) |
||||
globalNotificationSys.PutBucketQuotaConfig(ctx, bucket, quotaCfg) |
||||
|
||||
} else { |
||||
globalBucketQuotaSys.Remove(bucket) |
||||
globalNotificationSys.RemoveBucketQuotaConfig(ctx, bucket) |
||||
|
||||
} |
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseHeadersOnly(w) |
||||
} |
||||
|
||||
// GetBucketQuotaConfigHandler - gets bucket quota configuration
|
||||
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) { |
||||
ctx := newContext(r, w, "GetBucketQuotaConfig") |
||||
|
||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction) |
||||
if objectAPI == nil { |
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) |
||||
return |
||||
} |
||||
|
||||
vars := mux.Vars(r) |
||||
bucket := vars["bucket"] |
||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { |
||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL) |
||||
return |
||||
} |
||||
configFile := path.Join(bucketConfigPrefix, bucket, bucketQuotaConfigFile) |
||||
configData, err := readConfig(ctx, objectAPI, configFile) |
||||
if err != nil { |
||||
if err != errConfigNotFound { |
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) |
||||
return |
||||
} |
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, BucketQuotaConfigNotFound{Bucket: bucket}), r.URL) |
||||
return |
||||
} |
||||
// Write success response.
|
||||
writeSuccessResponseJSON(w, configData) |
||||
} |
||||
|
||||
// RemoveBucketQuotaConfigHandler - removes Bucket quota configuration.
|
||||
// ----------
|
||||
// Removes quota configuration on the specified bucket.
|
||||
func (a adminAPIHandlers) RemoveBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) { |
||||
ctx := newContext(r, w, "RemoveBucketQuotaConfig") |
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction) |
||||
if objectAPI == nil { |
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) |
||||
return |
||||
} |
||||
vars := mux.Vars(r) |
||||
bucket := vars["bucket"] |
||||
|
||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { |
||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL) |
||||
return |
||||
} |
||||
configFile := path.Join(bucketConfigPrefix, bucket, bucketQuotaConfigFile) |
||||
if err := deleteConfig(ctx, objectAPI, configFile); err != nil { |
||||
if err != errConfigNotFound { |
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) |
||||
return |
||||
} |
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, BucketQuotaConfigNotFound{Bucket: bucket}), r.URL) |
||||
return |
||||
} |
||||
globalBucketQuotaSys.Remove(bucket) |
||||
globalNotificationSys.RemoveBucketQuotaConfig(ctx, bucket) |
||||
// Write success response.
|
||||
writeSuccessNoContent(w) |
||||
} |
@ -0,0 +1,267 @@ |
||||
/* |
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package cmd |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"path" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/minio/minio/cmd/config" |
||||
"github.com/minio/minio/cmd/logger" |
||||
"github.com/minio/minio/pkg/env" |
||||
"github.com/minio/minio/pkg/event" |
||||
"github.com/minio/minio/pkg/madmin" |
||||
) |
||||
|
||||
// BucketQuotaSys - map of bucket and quota configuration.
|
||||
type BucketQuotaSys struct { |
||||
sync.RWMutex |
||||
quotaMap map[string]madmin.BucketQuota |
||||
} |
||||
|
||||
// Set - set quota configuration.
|
||||
func (sys *BucketQuotaSys) Set(bucketName string, q madmin.BucketQuota) { |
||||
sys.Lock() |
||||
sys.quotaMap[bucketName] = q |
||||
sys.Unlock() |
||||
} |
||||
|
||||
// Get - Get quota configuration.
|
||||
func (sys *BucketQuotaSys) Get(bucketName string) (q madmin.BucketQuota, ok bool) { |
||||
sys.RLock() |
||||
defer sys.RUnlock() |
||||
q, ok = sys.quotaMap[bucketName] |
||||
return |
||||
} |
||||
|
||||
// Remove - removes quota configuration.
|
||||
func (sys *BucketQuotaSys) Remove(bucketName string) { |
||||
sys.Lock() |
||||
delete(sys.quotaMap, bucketName) |
||||
sys.Unlock() |
||||
} |
||||
|
||||
// Exists - bucketName has Quota config set
|
||||
func (sys *BucketQuotaSys) Exists(bucketName string) bool { |
||||
sys.RLock() |
||||
_, ok := sys.quotaMap[bucketName] |
||||
sys.RUnlock() |
||||
return ok |
||||
} |
||||
|
||||
// Keys - list of buckets with quota configuration
|
||||
func (sys *BucketQuotaSys) Keys() []string { |
||||
sys.RLock() |
||||
defer sys.RUnlock() |
||||
var keys []string |
||||
for k := range sys.quotaMap { |
||||
keys = append(keys, k) |
||||
} |
||||
return keys |
||||
} |
||||
|
||||
// NewBucketQuotaSys returns initialized BucketQuotaSys
|
||||
func NewBucketQuotaSys() *BucketQuotaSys { |
||||
return &BucketQuotaSys{quotaMap: map[string]madmin.BucketQuota{}} |
||||
} |
||||
|
||||
// parseBucketQuota parses BucketQuota from json
|
||||
func parseBucketQuota(data []byte) (quotaCfg madmin.BucketQuota, err error) { |
||||
err = json.Unmarshal(data, "aCfg) |
||||
if err != nil { |
||||
return |
||||
} |
||||
if !quotaCfg.Type.IsValid() { |
||||
return quotaCfg, fmt.Errorf("Invalid quota type %s", quotaCfg.Type) |
||||
} |
||||
return |
||||
} |
||||
|
||||
type bucketStorageCache struct { |
||||
bucketsSizes map[string]uint64 |
||||
lastUpdate time.Time |
||||
mu sync.Mutex |
||||
} |
||||
|
||||
func (b *bucketStorageCache) check(ctx context.Context, q madmin.BucketQuota, bucket string, size int64) error { |
||||
b.mu.Lock() |
||||
defer b.mu.Unlock() |
||||
if time.Since(b.lastUpdate) > 10*time.Second { |
||||
dui, err := loadDataUsageFromBackend(ctx, newObjectLayerWithoutSafeModeFn()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
b.lastUpdate = time.Now() |
||||
b.bucketsSizes = dui.BucketsSizes |
||||
} |
||||
currUsage := b.bucketsSizes[bucket] |
||||
if (currUsage + uint64(size)) > q.Quota { |
||||
return BucketQuotaExceeded{Bucket: bucket} |
||||
} |
||||
return nil |
||||
} |
||||
func enforceBucketQuota(ctx context.Context, bucket string, size int64) error { |
||||
if size < 0 { |
||||
return nil |
||||
} |
||||
q, ok := globalBucketQuotaSys.Get(bucket) |
||||
if !ok { |
||||
return nil |
||||
} |
||||
|
||||
return globalBucketStorageCache.check(ctx, q, bucket, size) |
||||
} |
||||
|
||||
func initBucketQuotaSys(buckets []BucketInfo, objAPI ObjectLayer) error { |
||||
for _, bucket := range buckets { |
||||
ctx := logger.SetReqInfo(GlobalContext, &logger.ReqInfo{BucketName: bucket.Name}) |
||||
configFile := path.Join(bucketConfigPrefix, bucket.Name, bucketQuotaConfigFile) |
||||
configData, err := readConfig(ctx, objAPI, configFile) |
||||
if err != nil { |
||||
if errors.Is(err, errConfigNotFound) { |
||||
continue |
||||
} |
||||
return err |
||||
} |
||||
quotaCfg, err := parseBucketQuota(configData) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
globalBucketQuotaSys.Set(bucket.Name, quotaCfg) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
const ( |
||||
bgQuotaInterval = 1 * time.Hour |
||||
) |
||||
|
||||
// initQuotaEnforcement starts the routine that deletes objects in bucket
|
||||
// that exceeds the FIFO quota
|
||||
func initQuotaEnforcement(ctx context.Context, objAPI ObjectLayer) { |
||||
if env.Get(envDataUsageCrawlConf, config.EnableOn) == config.EnableOn { |
||||
go startBucketQuotaEnforcement(ctx, objAPI) |
||||
} |
||||
} |
||||
|
||||
func startBucketQuotaEnforcement(ctx context.Context, objAPI ObjectLayer) { |
||||
for { |
||||
select { |
||||
case <-ctx.Done(): |
||||
return |
||||
case <-time.NewTimer(bgQuotaInterval).C: |
||||
logger.LogIf(ctx, enforceFIFOQuota(ctx, objAPI)) |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
// enforceFIFOQuota deletes objects in FIFO order until sufficient objects
|
||||
// have been deleted so as to bring bucket usage within quota
|
||||
func enforceFIFOQuota(ctx context.Context, objectAPI ObjectLayer) error { |
||||
// Turn off quota enforcement if data usage info is unavailable.
|
||||
if env.Get(envDataUsageCrawlConf, config.EnableOn) == config.EnableOff { |
||||
return nil |
||||
} |
||||
for _, bucket := range globalBucketQuotaSys.Keys() { |
||||
// Check if the current bucket has quota restrictions, if not skip it
|
||||
cfg, ok := globalBucketQuotaSys.Get(bucket) |
||||
if !ok { |
||||
continue |
||||
} |
||||
if cfg.Type != madmin.FIFOQuota { |
||||
continue |
||||
} |
||||
_, bucketHasLockConfig := globalBucketObjectLockConfig.Get(bucket) |
||||
|
||||
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
var toFree uint64 |
||||
if dataUsageInfo.BucketsSizes[bucket] > cfg.Quota { |
||||
toFree = dataUsageInfo.BucketsSizes[bucket] - cfg.Quota |
||||
} |
||||
if toFree <= 0 { |
||||
continue |
||||
} |
||||
// Allocate new results channel to receive ObjectInfo.
|
||||
objInfoCh := make(chan ObjectInfo) |
||||
|
||||
// Walk through all objects
|
||||
if err := objectAPI.Walk(ctx, bucket, "", objInfoCh); err != nil { |
||||
return err |
||||
} |
||||
// reuse the fileScorer used by disk cache to score entries by
|
||||
// ModTime to find the oldest objects in bucket to delete. In
|
||||
// the context of bucket quota enforcement - number of hits are
|
||||
// irrelevant.
|
||||
scorer, err := newFileScorer(toFree, time.Now().Unix(), 1) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for obj := range objInfoCh { |
||||
// skip objects currently under retention
|
||||
if bucketHasLockConfig && enforceRetentionForDeletion(ctx, obj) { |
||||
continue |
||||
} |
||||
scorer.addFile(obj.Name, obj.ModTime, obj.Size, 1) |
||||
} |
||||
var objects []string |
||||
numKeys := len(scorer.fileNames()) |
||||
for i, key := range scorer.fileNames() { |
||||
objects = append(objects, key) |
||||
if len(objects) < maxObjectList && (i < numKeys-1) { |
||||
// skip deletion until maxObjectList or end of slice
|
||||
continue |
||||
} |
||||
|
||||
if len(objects) == 0 { |
||||
break |
||||
} |
||||
// Deletes a list of objects.
|
||||
deleteErrs, err := objectAPI.DeleteObjects(ctx, bucket, objects) |
||||
if err != nil { |
||||
logger.LogIf(ctx, err) |
||||
} else { |
||||
for i := range deleteErrs { |
||||
if deleteErrs[i] != nil { |
||||
logger.LogIf(ctx, deleteErrs[i]) |
||||
continue |
||||
} |
||||
// Notify object deleted event.
|
||||
sendEvent(eventArgs{ |
||||
EventName: event.ObjectRemovedDelete, |
||||
BucketName: bucket, |
||||
Object: ObjectInfo{ |
||||
Name: objects[i], |
||||
}, |
||||
Host: "Internal: [FIFO-QUOTA-EXPIRY]", |
||||
}) |
||||
} |
||||
objects = nil |
||||
} |
||||
} |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,56 @@ |
||||
// +build ignore
|
||||
|
||||
/* |
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"log" |
||||
|
||||
"github.com/minio/minio/pkg/madmin" |
||||
) |
||||
|
||||
func main() { |
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
var kiB int64 = 1 << 10 |
||||
ctx := context.Background() |
||||
// set bucket quota config
|
||||
if err := madmClnt.SetBucketQuota(ctx, "bucket-name", 64*kiB, HardQuota); err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
// gets bucket quota config
|
||||
quotaCfg, err := madmClnt.GetBucketQuota(ctx, "bucket-name") |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
fmt.Println(quotaCfg) |
||||
// remove bucket quota config
|
||||
if err := madmClnt.RemoveBucketQuota(ctx, "bucket-name"); err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
} |
@ -0,0 +1,140 @@ |
||||
/* |
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package madmin |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"net/url" |
||||
) |
||||
|
||||
// QuotaType represents bucket quota type
|
||||
type QuotaType string |
||||
|
||||
const ( |
||||
// HardQuota specifies a hard quota of usage for bucket
|
||||
HardQuota QuotaType = "hard" |
||||
// FIFOQuota specifies a quota limit beyond which older files are deleted from bucket
|
||||
FIFOQuota QuotaType = "fifo" |
||||
) |
||||
|
||||
// IsValid returns true if quota type is one of FIFO or Hard
|
||||
func (t QuotaType) IsValid() bool { |
||||
return t == HardQuota || t == FIFOQuota |
||||
} |
||||
|
||||
// BucketQuota holds bucket quota restrictions
|
||||
type BucketQuota struct { |
||||
Quota uint64 `json:"quota"` |
||||
Type QuotaType `json:"quotatype"` |
||||
} |
||||
|
||||
// RemoveBucketQuota - removes quota config on a bucket.
|
||||
func (adm *AdminClient) RemoveBucketQuota(ctx context.Context, bucket string) error { |
||||
|
||||
queryValues := url.Values{} |
||||
queryValues.Set("bucket", bucket) |
||||
|
||||
reqData := requestData{ |
||||
relPath: adminAPIPrefix + "/remove-bucket-quota", |
||||
queryValues: queryValues, |
||||
} |
||||
|
||||
// Execute DELETE on /minio/admin/v3/remove-bucket-quota to delete bucket quota.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData) |
||||
defer closeResponse(resp) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if resp.StatusCode != http.StatusNoContent { |
||||
return httpRespToErrorResponse(resp) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// GetBucketQuota - get info on a user
|
||||
func (adm *AdminClient) GetBucketQuota(ctx context.Context, bucket string) (q BucketQuota, err error) { |
||||
queryValues := url.Values{} |
||||
queryValues.Set("bucket", bucket) |
||||
|
||||
reqData := requestData{ |
||||
relPath: adminAPIPrefix + "/get-bucket-quota", |
||||
queryValues: queryValues, |
||||
} |
||||
|
||||
// Execute GET on /minio/admin/v3/get-quota
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData) |
||||
|
||||
defer closeResponse(resp) |
||||
if err != nil { |
||||
return q, err |
||||
} |
||||
|
||||
if resp.StatusCode != http.StatusOK { |
||||
return q, httpRespToErrorResponse(resp) |
||||
} |
||||
|
||||
b, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return q, err |
||||
} |
||||
if err = json.Unmarshal(b, &q); err != nil { |
||||
return q, err |
||||
} |
||||
|
||||
return q, nil |
||||
} |
||||
|
||||
// SetBucketQuota - sets a bucket's quota.
|
||||
func (adm *AdminClient) SetBucketQuota(ctx context.Context, bucket string, quota uint64, quotaType QuotaType) error { |
||||
|
||||
data, err := json.Marshal(BucketQuota{ |
||||
Quota: quota, |
||||
Type: quotaType, |
||||
}) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
queryValues := url.Values{} |
||||
queryValues.Set("bucket", bucket) |
||||
|
||||
reqData := requestData{ |
||||
relPath: adminAPIPrefix + "/set-bucket-quota", |
||||
queryValues: queryValues, |
||||
content: data, |
||||
} |
||||
|
||||
// Execute PUT on /minio/admin/v3/set-bucket-quota to set quota for a bucket.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) |
||||
|
||||
defer closeResponse(resp) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if resp.StatusCode != http.StatusOK { |
||||
return httpRespToErrorResponse(resp) |
||||
} |
||||
|
||||
return nil |
||||
} |
Loading…
Reference in new issue