github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/bucket-metadata-sys.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"context"
    22  	"encoding/xml"
    23  	"errors"
    24  	"fmt"
    25  	"math/rand"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/minio/madmin-go/v3"
    30  	"github.com/minio/minio-go/v7/pkg/set"
    31  	"github.com/minio/minio-go/v7/pkg/tags"
    32  	bucketsse "github.com/minio/minio/internal/bucket/encryption"
    33  	"github.com/minio/minio/internal/bucket/lifecycle"
    34  	objectlock "github.com/minio/minio/internal/bucket/object/lock"
    35  	"github.com/minio/minio/internal/bucket/replication"
    36  	"github.com/minio/minio/internal/bucket/versioning"
    37  	"github.com/minio/minio/internal/event"
    38  	"github.com/minio/minio/internal/kms"
    39  	"github.com/minio/minio/internal/logger"
    40  	"github.com/minio/pkg/v2/policy"
    41  	"github.com/minio/pkg/v2/sync/errgroup"
    42  )
    43  
    44  // BucketMetadataSys captures all bucket metadata for a given cluster.
    45  type BucketMetadataSys struct {
    46  	objAPI ObjectLayer
    47  
    48  	sync.RWMutex
    49  	metadataMap map[string]BucketMetadata
    50  }
    51  
    52  // Count returns number of bucket metadata map entries.
    53  func (sys *BucketMetadataSys) Count() int {
    54  	sys.RLock()
    55  	defer sys.RUnlock()
    56  
    57  	return len(sys.metadataMap)
    58  }
    59  
    60  // Remove bucket metadata from memory.
    61  func (sys *BucketMetadataSys) Remove(buckets ...string) {
    62  	sys.Lock()
    63  	for _, bucket := range buckets {
    64  		delete(sys.metadataMap, bucket)
    65  		globalBucketMonitor.DeleteBucket(bucket)
    66  	}
    67  	sys.Unlock()
    68  }
    69  
    70  // RemoveStaleBuckets removes all stale buckets in memory that are not on disk.
    71  func (sys *BucketMetadataSys) RemoveStaleBuckets(diskBuckets set.StringSet) {
    72  	sys.Lock()
    73  	defer sys.Unlock()
    74  
    75  	for bucket := range sys.metadataMap {
    76  		if diskBuckets.Contains(bucket) {
    77  			continue
    78  		} // doesn't exist on disk remove from memory.
    79  		delete(sys.metadataMap, bucket)
    80  		globalBucketMonitor.DeleteBucket(bucket)
    81  	}
    82  }
    83  
    84  // Set - sets a new metadata in-memory.
    85  // Only a shallow copy is saved and fields with references
    86  // cannot be modified without causing a race condition,
    87  // so they should be replaced atomically and not appended to, etc.
    88  // Data is not persisted to disk.
    89  func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) {
    90  	if !isMinioMetaBucketName(bucket) {
    91  		sys.Lock()
    92  		sys.metadataMap[bucket] = meta
    93  		sys.Unlock()
    94  	}
    95  }
    96  
    97  func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string, configFile string, configData []byte, parse bool) (updatedAt time.Time, err error) {
    98  	objAPI := newObjectLayerFn()
    99  	if objAPI == nil {
   100  		return updatedAt, errServerNotInitialized
   101  	}
   102  
   103  	if isMinioMetaBucketName(bucket) {
   104  		return updatedAt, errInvalidArgument
   105  	}
   106  
   107  	meta, err := loadBucketMetadataParse(ctx, objAPI, bucket, parse)
   108  	if err != nil {
   109  		if !globalIsErasure && !globalIsDistErasure && errors.Is(err, errVolumeNotFound) {
   110  			// Only single drive mode needs this fallback.
   111  			meta = newBucketMetadata(bucket)
   112  		} else {
   113  			return updatedAt, err
   114  		}
   115  	}
   116  	updatedAt = UTCNow()
   117  	switch configFile {
   118  	case bucketPolicyConfig:
   119  		meta.PolicyConfigJSON = configData
   120  		meta.PolicyConfigUpdatedAt = updatedAt
   121  	case bucketNotificationConfig:
   122  		meta.NotificationConfigXML = configData
   123  	case bucketLifecycleConfig:
   124  		meta.LifecycleConfigXML = configData
   125  		meta.LifecycleConfigUpdatedAt = updatedAt
   126  	case bucketSSEConfig:
   127  		meta.EncryptionConfigXML = configData
   128  		meta.EncryptionConfigUpdatedAt = updatedAt
   129  	case bucketTaggingConfig:
   130  		meta.TaggingConfigXML = configData
   131  		meta.TaggingConfigUpdatedAt = updatedAt
   132  	case bucketQuotaConfigFile:
   133  		meta.QuotaConfigJSON = configData
   134  		meta.QuotaConfigUpdatedAt = updatedAt
   135  	case objectLockConfig:
   136  		meta.ObjectLockConfigXML = configData
   137  		meta.ObjectLockConfigUpdatedAt = updatedAt
   138  	case bucketVersioningConfig:
   139  		meta.VersioningConfigXML = configData
   140  		meta.VersioningConfigUpdatedAt = updatedAt
   141  	case bucketReplicationConfig:
   142  		meta.ReplicationConfigXML = configData
   143  		meta.ReplicationConfigUpdatedAt = updatedAt
   144  	case bucketTargetsFile:
   145  		meta.BucketTargetsConfigJSON, meta.BucketTargetsConfigMetaJSON, err = encryptBucketMetadata(ctx, meta.Name, configData, kms.Context{
   146  			bucket:            meta.Name,
   147  			bucketTargetsFile: bucketTargetsFile,
   148  		})
   149  		if err != nil {
   150  			return updatedAt, fmt.Errorf("Error encrypting bucket target metadata %w", err)
   151  		}
   152  	default:
   153  		return updatedAt, fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
   154  	}
   155  
   156  	err = sys.save(ctx, meta)
   157  	return updatedAt, err
   158  }
   159  
   160  func (sys *BucketMetadataSys) save(ctx context.Context, meta BucketMetadata) error {
   161  	objAPI := newObjectLayerFn()
   162  	if objAPI == nil {
   163  		return errServerNotInitialized
   164  	}
   165  
   166  	if isMinioMetaBucketName(meta.Name) {
   167  		return errInvalidArgument
   168  	}
   169  
   170  	if err := meta.Save(ctx, objAPI); err != nil {
   171  		return err
   172  	}
   173  
   174  	sys.Set(meta.Name, meta)
   175  	globalNotificationSys.LoadBucketMetadata(bgContext(ctx), meta.Name) // Do not use caller context here
   176  	return nil
   177  }
   178  
   179  // Delete delete the bucket metadata for the specified bucket.
   180  // must be used by all callers instead of using Update() with nil configData.
   181  func (sys *BucketMetadataSys) Delete(ctx context.Context, bucket string, configFile string) (updatedAt time.Time, err error) {
   182  	if configFile == bucketLifecycleConfig {
   183  		// Get bucket config from current site
   184  		meta, e := globalBucketMetadataSys.GetConfigFromDisk(ctx, bucket)
   185  		if e != nil && !errors.Is(e, errConfigNotFound) {
   186  			return updatedAt, e
   187  		}
   188  		var expiryRuleRemoved bool
   189  		if len(meta.LifecycleConfigXML) > 0 {
   190  			var lcCfg lifecycle.Lifecycle
   191  			if err := xml.Unmarshal(meta.LifecycleConfigXML, &lcCfg); err != nil {
   192  				return updatedAt, err
   193  			}
   194  			// find a single expiry rule set the flag
   195  			for _, rl := range lcCfg.Rules {
   196  				if !rl.Expiration.IsNull() || !rl.NoncurrentVersionExpiration.IsNull() {
   197  					expiryRuleRemoved = true
   198  					break
   199  				}
   200  			}
   201  		}
   202  
   203  		// Form empty ILM details with `ExpiryUpdatedAt` field and save
   204  		var cfgData []byte
   205  		if expiryRuleRemoved {
   206  			var lcCfg lifecycle.Lifecycle
   207  			currtime := time.Now()
   208  			lcCfg.ExpiryUpdatedAt = &currtime
   209  			cfgData, err = xml.Marshal(lcCfg)
   210  			if err != nil {
   211  				return updatedAt, err
   212  			}
   213  		}
   214  		return sys.updateAndParse(ctx, bucket, configFile, cfgData, false)
   215  	}
   216  	return sys.updateAndParse(ctx, bucket, configFile, nil, false)
   217  }
   218  
   219  // Update update bucket metadata for the specified bucket.
   220  // The configData data should not be modified after being sent here.
   221  func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configFile string, configData []byte) (updatedAt time.Time, err error) {
   222  	return sys.updateAndParse(ctx, bucket, configFile, configData, true)
   223  }
   224  
   225  // Get metadata for a bucket.
   226  // If no metadata exists errConfigNotFound is returned and a new metadata is returned.
   227  // Only a shallow copy is returned, so referenced data should not be modified,
   228  // but can be replaced atomically.
   229  //
   230  // This function should only be used with
   231  // - GetBucketInfo
   232  // - ListBuckets
   233  // For all other bucket specific metadata, use the relevant
   234  // calls implemented specifically for each of those features.
   235  func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
   236  	if isMinioMetaBucketName(bucket) {
   237  		return newBucketMetadata(bucket), errConfigNotFound
   238  	}
   239  
   240  	sys.RLock()
   241  	defer sys.RUnlock()
   242  
   243  	meta, ok := sys.metadataMap[bucket]
   244  	if !ok {
   245  		return newBucketMetadata(bucket), errConfigNotFound
   246  	}
   247  
   248  	return meta, nil
   249  }
   250  
   251  // GetVersioningConfig returns configured versioning config
   252  // The returned object may not be modified.
   253  func (sys *BucketMetadataSys) GetVersioningConfig(bucket string) (*versioning.Versioning, time.Time, error) {
   254  	meta, _, err := sys.GetConfig(GlobalContext, bucket)
   255  	if err != nil {
   256  		if errors.Is(err, errConfigNotFound) {
   257  			return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, meta.Created, nil
   258  		}
   259  		return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, time.Time{}, err
   260  	}
   261  	return meta.versioningConfig, meta.VersioningConfigUpdatedAt, nil
   262  }
   263  
   264  // GetTaggingConfig returns configured tagging config
   265  // The returned object may not be modified.
   266  func (sys *BucketMetadataSys) GetTaggingConfig(bucket string) (*tags.Tags, time.Time, error) {
   267  	meta, _, err := sys.GetConfig(GlobalContext, bucket)
   268  	if err != nil {
   269  		if errors.Is(err, errConfigNotFound) {
   270  			return nil, time.Time{}, BucketTaggingNotFound{Bucket: bucket}
   271  		}
   272  		return nil, time.Time{}, err
   273  	}
   274  	if meta.taggingConfig == nil {
   275  		return nil, time.Time{}, BucketTaggingNotFound{Bucket: bucket}
   276  	}
   277  	return meta.taggingConfig, meta.TaggingConfigUpdatedAt, nil
   278  }
   279  
   280  // GetObjectLockConfig returns configured object lock config
   281  // The returned object may not be modified.
   282  func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Config, time.Time, error) {
   283  	meta, _, err := sys.GetConfig(GlobalContext, bucket)
   284  	if err != nil {
   285  		if errors.Is(err, errConfigNotFound) {
   286  			return nil, time.Time{}, BucketObjectLockConfigNotFound{Bucket: bucket}
   287  		}
   288  		return nil, time.Time{}, err
   289  	}
   290  	if meta.objectLockConfig == nil {
   291  		return nil, time.Time{}, BucketObjectLockConfigNotFound{Bucket: bucket}
   292  	}
   293  	return meta.objectLockConfig, meta.ObjectLockConfigUpdatedAt, nil
   294  }
   295  
   296  // GetLifecycleConfig returns configured lifecycle config
   297  // The returned object may not be modified.
   298  func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Lifecycle, time.Time, error) {
   299  	meta, _, err := sys.GetConfig(GlobalContext, bucket)
   300  	if err != nil {
   301  		if errors.Is(err, errConfigNotFound) {
   302  			return nil, time.Time{}, BucketLifecycleNotFound{Bucket: bucket}
   303  		}
   304  		return nil, time.Time{}, err
   305  	}
   306  	// there could be just `ExpiryUpdatedAt` field populated as part
   307  	// of last delete all. Treat this situation as not lifecycle configuration
   308  	// available
   309  	if meta.lifecycleConfig == nil || len(meta.lifecycleConfig.Rules) == 0 {
   310  		return nil, time.Time{}, BucketLifecycleNotFound{Bucket: bucket}
   311  	}
   312  	return meta.lifecycleConfig, meta.LifecycleConfigUpdatedAt, nil
   313  }
   314  
   315  // GetNotificationConfig returns configured notification config
   316  // The returned object may not be modified.
   317  func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Config, error) {
   318  	meta, _, err := sys.GetConfig(GlobalContext, bucket)
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  	return meta.notificationConfig, nil
   323  }
   324  
   325  // GetSSEConfig returns configured SSE config
   326  // The returned object may not be modified.
   327  func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEConfig, time.Time, error) {
   328  	meta, _, err := sys.GetConfig(GlobalContext, bucket)
   329  	if err != nil {
   330  		if errors.Is(err, errConfigNotFound) {
   331  			return nil, time.Time{}, BucketSSEConfigNotFound{Bucket: bucket}
   332  		}
   333  		return nil, time.Time{}, err
   334  	}
   335  	if meta.sseConfig == nil {
   336  		return nil, time.Time{}, BucketSSEConfigNotFound{Bucket: bucket}
   337  	}
   338  	return meta.sseConfig, meta.EncryptionConfigUpdatedAt, nil
   339  }
   340  
   341  // CreatedAt returns the time of creation of bucket
   342  func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) {
   343  	meta, _, err := sys.GetConfig(GlobalContext, bucket)
   344  	if err != nil {
   345  		return time.Time{}, err
   346  	}
   347  	return meta.Created.UTC(), nil
   348  }
   349  
   350  // GetPolicyConfig returns configured bucket policy
   351  // The returned object may not be modified.
   352  func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.BucketPolicy, time.Time, error) {
   353  	meta, _, err := sys.GetConfig(GlobalContext, bucket)
   354  	if err != nil {
   355  		if errors.Is(err, errConfigNotFound) {
   356  			return nil, time.Time{}, BucketPolicyNotFound{Bucket: bucket}
   357  		}
   358  		return nil, time.Time{}, err
   359  	}
   360  	if meta.policyConfig == nil {
   361  		return nil, time.Time{}, BucketPolicyNotFound{Bucket: bucket}
   362  	}
   363  	return meta.policyConfig, meta.PolicyConfigUpdatedAt, nil
   364  }
   365  
   366  // GetQuotaConfig returns configured bucket quota
   367  // The returned object may not be modified.
   368  func (sys *BucketMetadataSys) GetQuotaConfig(ctx context.Context, bucket string) (*madmin.BucketQuota, time.Time, error) {
   369  	meta, _, err := sys.GetConfig(ctx, bucket)
   370  	if err != nil {
   371  		if errors.Is(err, errConfigNotFound) {
   372  			return nil, time.Time{}, BucketQuotaConfigNotFound{Bucket: bucket}
   373  		}
   374  		return nil, time.Time{}, err
   375  	}
   376  	return meta.quotaConfig, meta.QuotaConfigUpdatedAt, nil
   377  }
   378  
   379  // GetReplicationConfig returns configured bucket replication config
   380  // The returned object may not be modified.
   381  func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket string) (*replication.Config, time.Time, error) {
   382  	meta, reloaded, err := sys.GetConfig(ctx, bucket)
   383  	if err != nil {
   384  		if errors.Is(err, errConfigNotFound) {
   385  			return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
   386  		}
   387  		return nil, time.Time{}, err
   388  	}
   389  
   390  	if meta.replicationConfig == nil {
   391  		return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
   392  	}
   393  	if reloaded {
   394  		globalBucketTargetSys.set(BucketInfo{
   395  			Name: bucket,
   396  		}, meta)
   397  	}
   398  	return meta.replicationConfig, meta.ReplicationConfigUpdatedAt, nil
   399  }
   400  
   401  // GetBucketTargetsConfig returns configured bucket targets for this bucket
   402  // The returned object may not be modified.
   403  func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.BucketTargets, error) {
   404  	meta, reloaded, err := sys.GetConfig(GlobalContext, bucket)
   405  	if err != nil {
   406  		if errors.Is(err, errConfigNotFound) {
   407  			return nil, BucketRemoteTargetNotFound{Bucket: bucket}
   408  		}
   409  		return nil, err
   410  	}
   411  	if meta.bucketTargetConfig == nil {
   412  		return nil, BucketRemoteTargetNotFound{Bucket: bucket}
   413  	}
   414  	if reloaded {
   415  		globalBucketTargetSys.set(BucketInfo{
   416  			Name: bucket,
   417  		}, meta)
   418  	}
   419  	return meta.bucketTargetConfig, nil
   420  }
   421  
   422  // GetConfigFromDisk read bucket metadata config from disk.
   423  func (sys *BucketMetadataSys) GetConfigFromDisk(ctx context.Context, bucket string) (BucketMetadata, error) {
   424  	objAPI := newObjectLayerFn()
   425  	if objAPI == nil {
   426  		return newBucketMetadata(bucket), errServerNotInitialized
   427  	}
   428  
   429  	if isMinioMetaBucketName(bucket) {
   430  		return newBucketMetadata(bucket), errInvalidArgument
   431  	}
   432  
   433  	return loadBucketMetadata(ctx, objAPI, bucket)
   434  }
   435  
   436  // GetConfig returns a specific configuration from the bucket metadata.
   437  // The returned object may not be modified.
   438  // reloaded will be true if metadata refreshed from disk
   439  func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (meta BucketMetadata, reloaded bool, err error) {
   440  	objAPI := newObjectLayerFn()
   441  	if objAPI == nil {
   442  		return newBucketMetadata(bucket), reloaded, errServerNotInitialized
   443  	}
   444  
   445  	if isMinioMetaBucketName(bucket) {
   446  		return newBucketMetadata(bucket), reloaded, errInvalidArgument
   447  	}
   448  
   449  	sys.RLock()
   450  	meta, ok := sys.metadataMap[bucket]
   451  	sys.RUnlock()
   452  	if ok {
   453  		return meta, reloaded, nil
   454  	}
   455  	meta, err = loadBucketMetadata(ctx, objAPI, bucket)
   456  	if err != nil {
   457  		return meta, reloaded, err
   458  	}
   459  	sys.Lock()
   460  	sys.metadataMap[bucket] = meta
   461  	sys.Unlock()
   462  
   463  	return meta, true, nil
   464  }
   465  
   466  // Init - initializes bucket metadata system for all buckets.
   467  func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error {
   468  	if objAPI == nil {
   469  		return errServerNotInitialized
   470  	}
   471  
   472  	sys.objAPI = objAPI
   473  
   474  	// Load bucket metadata sys.
   475  	sys.init(ctx, buckets)
   476  	return nil
   477  }
   478  
   479  // concurrently load bucket metadata to speed up loading bucket metadata.
   480  func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, failedBuckets map[string]struct{}) {
   481  	g := errgroup.WithNErrs(len(buckets))
   482  	bucketMetas := make([]BucketMetadata, len(buckets))
   483  	for index := range buckets {
   484  		index := index
   485  		g.Go(func() error {
   486  			// Sleep and stagger to avoid blocked CPU and thundering
   487  			// herd upon start up sequence.
   488  			time.Sleep(25*time.Millisecond + time.Duration(rand.Int63n(int64(100*time.Millisecond))))
   489  
   490  			_, _ = sys.objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{Recreate: true})
   491  			meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[index].Name)
   492  			if err != nil {
   493  				return err
   494  			}
   495  			bucketMetas[index] = meta
   496  			return nil
   497  		}, index)
   498  	}
   499  
   500  	errs := g.Wait()
   501  	for _, err := range errs {
   502  		if err != nil {
   503  			logger.LogIf(ctx, err)
   504  		}
   505  	}
   506  
   507  	// Hold lock here to update in-memory map at once,
   508  	// instead of serializing the Go routines.
   509  	sys.Lock()
   510  	for i, meta := range bucketMetas {
   511  		if errs[i] != nil {
   512  			continue
   513  		}
   514  		sys.metadataMap[buckets[i].Name] = meta
   515  	}
   516  	sys.Unlock()
   517  
   518  	for i, meta := range bucketMetas {
   519  		if errs[i] != nil {
   520  			if failedBuckets == nil {
   521  				failedBuckets = make(map[string]struct{})
   522  			}
   523  			failedBuckets[buckets[i].Name] = struct{}{}
   524  			continue
   525  		}
   526  		globalEventNotifier.set(buckets[i], meta)   // set notification targets
   527  		globalBucketTargetSys.set(buckets[i], meta) // set remote replication targets
   528  	}
   529  }
   530  
   531  func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context, failedBuckets map[string]struct{}) {
   532  	const bucketMetadataRefresh = 15 * time.Minute
   533  
   534  	sleeper := newDynamicSleeper(2, 150*time.Millisecond, false)
   535  
   536  	t := time.NewTimer(bucketMetadataRefresh)
   537  	defer t.Stop()
   538  	for {
   539  		select {
   540  		case <-ctx.Done():
   541  			return
   542  		case <-t.C:
   543  			buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{})
   544  			if err != nil {
   545  				logger.LogIf(ctx, err)
   546  				break
   547  			}
   548  
   549  			// Handle if we have some buckets in-memory those are stale.
   550  			// first delete them and then replace the newer state()
   551  			// from disk.
   552  			diskBuckets := set.CreateStringSet()
   553  			for _, bucket := range buckets {
   554  				diskBuckets.Add(bucket.Name)
   555  			}
   556  			sys.RemoveStaleBuckets(diskBuckets)
   557  
   558  			for i := range buckets {
   559  				wait := sleeper.Timer(ctx)
   560  
   561  				meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[i].Name)
   562  				if err != nil {
   563  					logger.LogIf(ctx, err)
   564  					wait() // wait to proceed to next entry.
   565  					continue
   566  				}
   567  
   568  				sys.Lock()
   569  				sys.metadataMap[buckets[i].Name] = meta
   570  				sys.Unlock()
   571  
   572  				// Initialize the failed buckets
   573  				if _, ok := failedBuckets[buckets[i].Name]; ok {
   574  					globalEventNotifier.set(buckets[i], meta)
   575  					globalBucketTargetSys.set(buckets[i], meta)
   576  					delete(failedBuckets, buckets[i].Name)
   577  				}
   578  
   579  				wait() // wait to proceed to next entry.
   580  			}
   581  		}
   582  		t.Reset(bucketMetadataRefresh)
   583  	}
   584  }
   585  
   586  // Loads bucket metadata for all buckets into BucketMetadataSys.
   587  func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
   588  	count := 100 // load 100 bucket metadata at a time.
   589  	failedBuckets := make(map[string]struct{})
   590  	for {
   591  		if len(buckets) < count {
   592  			sys.concurrentLoad(ctx, buckets, failedBuckets)
   593  			break
   594  		}
   595  		sys.concurrentLoad(ctx, buckets[:count], failedBuckets)
   596  		buckets = buckets[count:]
   597  	}
   598  
   599  	if globalIsDistErasure {
   600  		go sys.refreshBucketsMetadataLoop(ctx, failedBuckets)
   601  	}
   602  }
   603  
   604  // Reset the state of the BucketMetadataSys.
   605  func (sys *BucketMetadataSys) Reset() {
   606  	sys.Lock()
   607  	for k := range sys.metadataMap {
   608  		delete(sys.metadataMap, k)
   609  	}
   610  	sys.Unlock()
   611  }
   612  
   613  // NewBucketMetadataSys - creates new policy system.
   614  func NewBucketMetadataSys() *BucketMetadataSys {
   615  	return &BucketMetadataSys{
   616  		metadataMap: make(map[string]BucketMetadata),
   617  	}
   618  }