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 }