@ -22,9 +22,7 @@ import (
"fmt"
"fmt"
"time"
"time"
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/env"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/madmin"
)
)
@ -120,149 +118,112 @@ func enforceBucketQuota(ctx context.Context, bucket string, size int64) error {
return globalBucketQuotaSys . check ( ctx , bucket , size )
return globalBucketQuotaSys . check ( ctx , bucket , size )
}
}
const (
// enforceFIFOQuota deletes objects in FIFO order until sufficient objects
bgQuotaInterval = 1 * time . Hour
// have been deleted so as to bring bucket usage within quota.
)
func enforceFIFOQuotaBucket ( ctx context . Context , objectAPI ObjectLayer , bucket string , bui BucketUsageInfo ) {
// Check if the current bucket has quota restrictions, if not skip it
// initQuotaEnforcement starts the routine that deletes objects in bucket
cfg , err := globalBucketQuotaSys . Get ( bucket )
// that exceeds the FIFO quota
if err != nil {
func initQuotaEnforcement ( ctx context . Context , objAPI ObjectLayer ) {
return
if env . Get ( envDataUsageCrawlConf , config . EnableOn ) == config . EnableOn {
go startBucketQuotaEnforcement ( ctx , objAPI )
}
}
}
func startBucketQuotaEnforcement ( ctx context . Context , objAPI ObjectLayer ) {
if cfg . Type != madmin . FIFOQuota {
for {
return
select {
}
case <- ctx . Done ( ) :
return
case <- time . NewTimer ( bgQuotaInterval ) . C :
enforceFIFOQuota ( ctx , objAPI )
}
var toFree uint64
if bui . Size > cfg . Quota && cfg . Quota > 0 {
toFree = bui . Size - cfg . Quota
}
}
}
// enforceFIFOQuota deletes objects in FIFO order until sufficient objects
if toFree <= 0 {
// have been deleted so as to bring bucket usage within quota
func enforceFIFOQuota ( ctx context . Context , objectAPI ObjectLayer ) {
// Turn off quota enforcement if data usage info is unavailable.
if env . Get ( envDataUsageCrawlConf , config . EnableOn ) == config . EnableOff {
return
return
}
}
buckets , err := objectAPI . ListBuckets ( ctx )
// Allocate new results channel to receive ObjectInfo.
if err != nil {
objInfoCh := make ( chan ObjectInfo )
versioned := globalBucketVersioningSys . Enabled ( bucket )
// Walk through all objects
if err := objectAPI . Walk ( ctx , bucket , "" , objInfoCh , ObjectOptions { WalkVersions : versioned } ) ; err != nil {
logger . LogIf ( ctx , err )
logger . LogIf ( ctx , err )
return
return
}
}
dataUsageInfo , err := loadDataUsageFromBackend ( ctx , objectAPI )
// 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 {
if err != nil {
logger . LogIf ( ctx , err )
logger . LogIf ( ctx , err )
return
return
}
}
for _ , binfo := range buckets {
rcfg , _ := globalBucketObjectLockSys . Get ( bucket )
bucket := binfo . Name
for obj := range objInfoCh {
if obj . DeleteMarker {
bui , ok := dataUsageInfo . BucketsUsage [ bucket ]
// Delete markers are automatically added for FIFO purge.
if ! ok {
scorer . addFileWithObjInfo ( obj , 1 )
// bucket doesn't exist anymore, or we
// do not have any information to proceed.
continue
}
// Check if the current bucket has quota restrictions, if not skip it
cfg , err := globalBucketQuotaSys . Get ( bucket )
if err != nil {
continue
}
if cfg . Type != madmin . FIFOQuota {
continue
continue
}
}
// skip objects currently under retention
var toFree uint64
if rcfg . LockEnabled && enforceRetentionForDeletion ( ctx , obj ) {
if bui . Size > cfg . Quota && cfg . Quota > 0 {
toFree = bui . Size - cfg . Quota
}
if toFree == 0 {
continue
continue
}
}
scorer . addFileWithObjInfo ( obj , 1 )
}
// Allocate new results channel to receive ObjectInfo.
// If we saw less than quota we are good.
objInfoCh := make ( chan ObjectInfo )
if scorer . seenBytes <= cfg . Quota {
return
versioned := globalBucketVersioningSys . Enabled ( bucket )
}
// Calculate how much we want to delete now.
// Walk through all objects
toFreeNow := scorer . seenBytes - cfg . Quota
if err := objectAPI . Walk ( ctx , bucket , "" , objInfoCh , ObjectOptions { WalkVersions : versioned } ) ; err != nil {
// We were less over quota than we thought. Adjust so we delete less.
logger . LogIf ( ctx , err )
// If we are more over, leave it for the next run to pick up.
continue
if toFreeNow < toFree {
if ! scorer . adjustSaveBytes ( int64 ( toFreeNow ) - int64 ( toFree ) ) {
// We got below or at quota.
return
}
}
}
// reuse the fileScorer used by disk cache to score entries by
var objects [ ] ObjectToDelete
// ModTime to find the oldest objects in bucket to delete. In
numKeys := len ( scorer . fileObjInfos ( ) )
// the context of bucket quota enforcement - number of hits are
for i , obj := range scorer . fileObjInfos ( ) {
// irrelevant.
objects = append ( objects , ObjectToDelete {
scorer , err := newFileScorer ( toFree , time . Now ( ) . Unix ( ) , 1 )
ObjectName : obj . Name ,
if err != nil {
VersionID : obj . VersionID ,
logger . LogIf ( ctx , err )
} )
if len ( objects ) < maxDeleteList && ( i < numKeys - 1 ) {
// skip deletion until maxDeleteList or end of slice
continue
continue
}
}
rcfg , _ := globalBucketObjectLockSys . Get ( bucket )
if len ( objects ) == 0 {
for obj := range objInfoCh {
break
if obj . DeleteMarker {
// Delete markers are automatically added for FIFO purge.
scorer . addFileWithObjInfo ( obj , 1 )
continue
}
// skip objects currently under retention
if rcfg . LockEnabled && enforceRetentionForDeletion ( ctx , obj ) {
continue
}
scorer . addFileWithObjInfo ( obj , 1 )
}
}
var objects [ ] ObjectToDelete
// Deletes a list of objects.
numKeys := len ( scorer . fileObjInfos ( ) )
_ , deleteErrs := objectAPI . DeleteObjects ( ctx , bucket , objects , ObjectOptions {
for i , obj := range scorer . fileObjInfos ( ) {
Versioned : versioned ,
objects = append ( objects , ObjectToDelete {
} )
ObjectName : obj . Name ,
for i := range deleteErrs {
VersionID : obj . VersionID ,
if deleteErrs [ i ] != nil {
} )
logger . LogIf ( ctx , deleteErrs [ i ] )
if len ( objects ) < maxDeleteList && ( i < numKeys - 1 ) {
// skip deletion until maxDeleteList or end of slice
continue
continue
}
}
if len ( objects ) == 0 {
// Notify object deleted event.
break
sendEvent ( eventArgs {
}
EventName : event . ObjectRemovedDelete ,
BucketName : bucket ,
// Deletes a list of objects.
Object : obj ,
_ , deleteErrs := objectAPI . DeleteObjects ( ctx , bucket , objects , ObjectOptions {
Host : "Internal: [FIFO-QUOTA-EXPIRY]" ,
Versioned : versioned ,
} )
} )
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 : obj ,
Host : "Internal: [FIFO-QUOTA-EXPIRY]" ,
} )
}
objects = nil
}
}
objects = nil
}
}
}
}