github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/bucket-targets.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 "errors" 23 "fmt" 24 "net/url" 25 "sync" 26 "time" 27 28 jsoniter "github.com/json-iterator/go" 29 "github.com/minio/madmin-go/v3" 30 "github.com/minio/minio-go/v7" 31 "github.com/minio/minio-go/v7/pkg/credentials" 32 "github.com/minio/minio/internal/bucket/replication" 33 "github.com/minio/minio/internal/crypto" 34 "github.com/minio/minio/internal/kms" 35 "github.com/minio/minio/internal/logger" 36 ) 37 38 const ( 39 defaultHealthCheckDuration = 5 * time.Second 40 // default interval for reload of all remote target endpoints 41 defaultHealthCheckReloadDuration = 30 * time.Minute 42 ) 43 44 type arnTarget struct { 45 Client *TargetClient 46 lastRefresh time.Time 47 } 48 49 // arnErrs represents number of errors seen for a ARN and if update is in progress 50 // to refresh remote targets from bucket metadata. 51 type arnErrs struct { 52 count int64 53 updateInProgress bool 54 bucket string 55 } 56 57 // BucketTargetSys represents bucket targets subsystem 58 type BucketTargetSys struct { 59 sync.RWMutex 60 arnRemotesMap map[string]arnTarget 61 targetsMap map[string][]madmin.BucketTarget 62 hMutex sync.RWMutex 63 hc map[string]epHealth 64 hcClient *madmin.AnonymousClient 65 aMutex sync.RWMutex 66 arnErrsMap map[string]arnErrs // map of ARN to error count of failures to get target 67 } 68 69 type latencyStat struct { 70 lastmin lastMinuteLatency 71 curr time.Duration 72 avg time.Duration 73 peak time.Duration 74 N int64 75 } 76 77 func (l *latencyStat) update(d time.Duration) { 78 l.lastmin.add(d) 79 l.N++ 80 if d > l.peak { 81 l.peak = d 82 } 83 l.curr = l.lastmin.getTotal().avg() 84 l.avg = time.Duration((int64(l.avg)*(l.N-1) + int64(l.curr)) / l.N) 85 } 86 87 // epHealth struct represents health of a replication target endpoint. 88 type epHealth struct { 89 Endpoint string 90 Scheme string 91 Online bool 92 lastOnline time.Time 93 lastHCAt time.Time 94 offlineDuration time.Duration 95 latency latencyStat 96 } 97 98 // isOffline returns current liveness result of remote target. Add endpoint to 99 // healthCheck map if missing and default to online status 100 func (sys *BucketTargetSys) isOffline(ep *url.URL) bool { 101 sys.hMutex.RLock() 102 defer sys.hMutex.RUnlock() 103 if h, ok := sys.hc[ep.Host]; ok { 104 return !h.Online 105 } 106 go sys.initHC(ep) 107 return false 108 } 109 110 // markOffline sets endpoint to offline if network i/o timeout seen. 111 func (sys *BucketTargetSys) markOffline(ep *url.URL) { 112 sys.hMutex.Lock() 113 defer sys.hMutex.Unlock() 114 if h, ok := sys.hc[ep.Host]; ok { 115 h.Online = false 116 sys.hc[ep.Host] = h 117 } 118 } 119 120 func (sys *BucketTargetSys) initHC(ep *url.URL) { 121 sys.hMutex.Lock() 122 sys.hc[ep.Host] = epHealth{ 123 Endpoint: ep.Host, 124 Scheme: ep.Scheme, 125 Online: true, 126 } 127 sys.hMutex.Unlock() 128 } 129 130 // newHCClient initializes an anonymous client for performing health check on the remote endpoints 131 func newHCClient() *madmin.AnonymousClient { 132 clnt, e := madmin.NewAnonymousClientNoEndpoint() 133 if e != nil { 134 logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to initialize health check client"), string(replicationSubsystem)) 135 return nil 136 } 137 clnt.SetCustomTransport(globalRemoteTargetTransport) 138 return clnt 139 } 140 141 // heartBeat performs liveness check on remote endpoints. 142 func (sys *BucketTargetSys) heartBeat(ctx context.Context) { 143 hcTimer := time.NewTimer(defaultHealthCheckDuration) 144 defer hcTimer.Stop() 145 for { 146 select { 147 case <-hcTimer.C: 148 sys.hMutex.RLock() 149 eps := make([]madmin.ServerProperties, 0, len(sys.hc)) 150 for _, ep := range sys.hc { 151 eps = append(eps, madmin.ServerProperties{Endpoint: ep.Endpoint, Scheme: ep.Scheme}) 152 } 153 sys.hMutex.RUnlock() 154 155 if len(eps) > 0 { 156 cctx, cancel := context.WithTimeout(ctx, 30*time.Second) 157 m := make(map[string]epHealth, len(eps)) 158 start := time.Now() 159 160 for result := range sys.hcClient.Alive(cctx, madmin.AliveOpts{}, eps...) { 161 var lastOnline time.Time 162 var offline time.Duration 163 // var deploymentID string 164 sys.hMutex.RLock() 165 prev, ok := sys.hc[result.Endpoint.Host] 166 sys.hMutex.RUnlock() 167 if ok { 168 if prev.Online != result.Online || !result.Online { 169 if !prev.lastHCAt.IsZero() { 170 offline = time.Since(prev.lastHCAt) + prev.offlineDuration 171 } else { 172 offline = prev.offlineDuration 173 } 174 } else if result.Online { 175 offline = prev.offlineDuration 176 } 177 } 178 lastOnline = prev.lastOnline 179 if result.Online { 180 lastOnline = time.Now() 181 } 182 l := prev.latency 183 l.update(time.Since(start)) 184 m[result.Endpoint.Host] = epHealth{ 185 Endpoint: result.Endpoint.Host, 186 Scheme: result.Endpoint.Scheme, 187 Online: result.Online, 188 lastOnline: lastOnline, 189 offlineDuration: offline, 190 lastHCAt: time.Now(), 191 latency: l, 192 } 193 } 194 cancel() 195 sys.hMutex.Lock() 196 sys.hc = m 197 sys.hMutex.Unlock() 198 } 199 hcTimer.Reset(defaultHealthCheckDuration) 200 case <-ctx.Done(): 201 return 202 } 203 } 204 } 205 206 // periodically rebuild the healthCheck map from list of targets to clear 207 // out stale endpoints 208 func (sys *BucketTargetSys) reloadHealthCheckers(ctx context.Context) { 209 m := make(map[string]epHealth) 210 tgts := sys.ListTargets(ctx, "", "") 211 sys.hMutex.Lock() 212 for _, t := range tgts { 213 if _, ok := m[t.Endpoint]; !ok { 214 scheme := "http" 215 if t.Secure { 216 scheme = "https" 217 } 218 epHealth := epHealth{ 219 Online: true, 220 Endpoint: t.Endpoint, 221 Scheme: scheme, 222 } 223 if prev, ok := sys.hc[t.Endpoint]; ok { 224 epHealth.lastOnline = prev.lastOnline 225 epHealth.offlineDuration = prev.offlineDuration 226 epHealth.lastHCAt = prev.lastHCAt 227 epHealth.latency = prev.latency 228 } 229 m[t.Endpoint] = epHealth 230 } 231 } 232 // swap out the map 233 sys.hc = m 234 sys.hMutex.Unlock() 235 } 236 237 func (sys *BucketTargetSys) healthStats() map[string]epHealth { 238 sys.hMutex.RLock() 239 defer sys.hMutex.RUnlock() 240 m := make(map[string]epHealth, len(sys.hc)) 241 for k, v := range sys.hc { 242 m[k] = v 243 } 244 return m 245 } 246 247 // ListTargets lists bucket targets across tenant or for individual bucket, and returns 248 // results filtered by arnType 249 func (sys *BucketTargetSys) ListTargets(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget) { 250 h := sys.healthStats() 251 252 if bucket != "" { 253 if ts, err := sys.ListBucketTargets(ctx, bucket); err == nil { 254 for _, t := range ts.Targets { 255 if string(t.Type) == arnType || arnType == "" { 256 if hs, ok := h[t.URL().Host]; ok { 257 t.TotalDowntime = hs.offlineDuration 258 t.Online = hs.Online 259 t.LastOnline = hs.lastOnline 260 t.Latency = madmin.LatencyStat{ 261 Curr: hs.latency.curr, 262 Avg: hs.latency.avg, 263 Max: hs.latency.peak, 264 } 265 } 266 targets = append(targets, t.Clone()) 267 } 268 } 269 } 270 return targets 271 } 272 sys.RLock() 273 defer sys.RUnlock() 274 for _, tgts := range sys.targetsMap { 275 for _, t := range tgts { 276 if string(t.Type) == arnType || arnType == "" { 277 if hs, ok := h[t.URL().Host]; ok { 278 t.TotalDowntime = hs.offlineDuration 279 t.Online = hs.Online 280 t.LastOnline = hs.lastOnline 281 t.Latency = madmin.LatencyStat{ 282 Curr: hs.latency.curr, 283 Avg: hs.latency.avg, 284 Max: hs.latency.peak, 285 } 286 } 287 targets = append(targets, t.Clone()) 288 } 289 } 290 } 291 return 292 } 293 294 // ListBucketTargets - gets list of bucket targets for this bucket. 295 func (sys *BucketTargetSys) ListBucketTargets(ctx context.Context, bucket string) (*madmin.BucketTargets, error) { 296 sys.RLock() 297 defer sys.RUnlock() 298 299 tgts, ok := sys.targetsMap[bucket] 300 if ok { 301 return &madmin.BucketTargets{Targets: tgts}, nil 302 } 303 return nil, BucketRemoteTargetNotFound{Bucket: bucket} 304 } 305 306 // Delete clears targets present for a bucket 307 func (sys *BucketTargetSys) Delete(bucket string) { 308 sys.Lock() 309 defer sys.Unlock() 310 tgts, ok := sys.targetsMap[bucket] 311 if !ok { 312 return 313 } 314 for _, t := range tgts { 315 delete(sys.arnRemotesMap, t.Arn) 316 } 317 delete(sys.targetsMap, bucket) 318 } 319 320 // SetTarget - sets a new minio-go client target for this bucket. 321 func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketTarget, update bool) error { 322 if !tgt.Type.IsValid() && !update { 323 return BucketRemoteArnTypeInvalid{Bucket: bucket} 324 } 325 clnt, err := sys.getRemoteTargetClient(tgt) 326 if err != nil { 327 return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket, Err: err} 328 } 329 // validate if target credentials are ok 330 exists, err := clnt.BucketExists(ctx, tgt.TargetBucket) 331 if err != nil { 332 switch minio.ToErrorResponse(err).Code { 333 case "NoSuchBucket": 334 return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket, Err: err} 335 case "AccessDenied": 336 return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, AccessKey: tgt.Credentials.AccessKey, Err: err} 337 } 338 return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, AccessKey: tgt.Credentials.AccessKey, Err: err} 339 } 340 if !exists { 341 return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket} 342 } 343 if tgt.Type == madmin.ReplicationService { 344 if !globalBucketVersioningSys.Enabled(bucket) { 345 return BucketReplicationSourceNotVersioned{Bucket: bucket} 346 } 347 vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket) 348 if err != nil { 349 return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err, AccessKey: tgt.Credentials.AccessKey} 350 } 351 if !vcfg.Enabled() { 352 return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket} 353 } 354 } 355 356 // Check if target is a MinIO server and alive 357 hcCtx, cancel := context.WithTimeout(ctx, 3*time.Second) 358 scheme := "http" 359 if tgt.Secure { 360 scheme = "https" 361 } 362 result := <-sys.hcClient.Alive(hcCtx, madmin.AliveOpts{}, madmin.ServerProperties{ 363 Endpoint: tgt.Endpoint, 364 Scheme: scheme, 365 }) 366 367 cancel() 368 if result.Error != nil { 369 return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: result.Error, AccessKey: tgt.Credentials.AccessKey} 370 } 371 if !result.Online { 372 err := errors.New("Health check timed out after 3 seconds") 373 return RemoteTargetConnectionErr{Err: err} 374 } 375 376 sys.Lock() 377 defer sys.Unlock() 378 379 tgts := sys.targetsMap[bucket] 380 newtgts := make([]madmin.BucketTarget, len(tgts)) 381 found := false 382 for idx, t := range tgts { 383 if t.Type == tgt.Type { 384 if t.Arn == tgt.Arn { 385 if !update { 386 return BucketRemoteAlreadyExists{Bucket: t.TargetBucket} 387 } 388 newtgts[idx] = *tgt 389 found = true 390 continue 391 } 392 // fail if endpoint is already present in list of targets and not a matching ARN 393 if t.Endpoint == tgt.Endpoint { 394 return BucketRemoteAlreadyExists{Bucket: t.TargetBucket} 395 } 396 } 397 newtgts[idx] = t 398 } 399 if !found && !update { 400 newtgts = append(newtgts, *tgt) 401 } 402 403 sys.targetsMap[bucket] = newtgts 404 sys.arnRemotesMap[tgt.Arn] = arnTarget{Client: clnt} 405 sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit) 406 return nil 407 } 408 409 func (sys *BucketTargetSys) updateBandwidthLimit(bucket, arn string, limit int64) { 410 if limit == 0 { 411 globalBucketMonitor.DeleteBucketThrottle(bucket, arn) 412 return 413 } 414 // Setup bandwidth throttling 415 416 globalBucketMonitor.SetBandwidthLimit(bucket, arn, limit) 417 } 418 419 // RemoveTarget - removes a remote bucket target for this source bucket. 420 func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr string) error { 421 if arnStr == "" { 422 return BucketRemoteArnInvalid{Bucket: bucket} 423 } 424 425 arn, err := madmin.ParseARN(arnStr) 426 if err != nil { 427 return BucketRemoteArnInvalid{Bucket: bucket} 428 } 429 430 if arn.Type == madmin.ReplicationService { 431 // reject removal of remote target if replication configuration is present 432 rcfg, err := getReplicationConfig(ctx, bucket) 433 if err == nil { 434 for _, tgtArn := range rcfg.FilterTargetArns(replication.ObjectOpts{OpType: replication.AllReplicationType}) { 435 if err == nil && (tgtArn == arnStr || rcfg.RoleArn == arnStr) { 436 sys.RLock() 437 _, ok := sys.arnRemotesMap[arnStr] 438 sys.RUnlock() 439 if ok { 440 return BucketRemoteRemoveDisallowed{Bucket: bucket} 441 } 442 } 443 } 444 } 445 } 446 447 // delete ARN type from list of matching targets 448 sys.Lock() 449 defer sys.Unlock() 450 found := false 451 tgts, ok := sys.targetsMap[bucket] 452 if !ok { 453 return BucketRemoteTargetNotFound{Bucket: bucket} 454 } 455 targets := make([]madmin.BucketTarget, 0, len(tgts)) 456 for _, tgt := range tgts { 457 if tgt.Arn != arnStr { 458 targets = append(targets, tgt) 459 continue 460 } 461 found = true 462 } 463 if !found { 464 return BucketRemoteTargetNotFound{Bucket: bucket} 465 } 466 sys.targetsMap[bucket] = targets 467 delete(sys.arnRemotesMap, arnStr) 468 sys.updateBandwidthLimit(bucket, arnStr, 0) 469 return nil 470 } 471 472 func (sys *BucketTargetSys) markRefreshInProgress(bucket, arn string) { 473 sys.aMutex.Lock() 474 defer sys.aMutex.Unlock() 475 if v, ok := sys.arnErrsMap[arn]; !ok { 476 sys.arnErrsMap[arn] = arnErrs{ 477 updateInProgress: true, 478 count: v.count + 1, 479 bucket: bucket, 480 } 481 } 482 } 483 484 func (sys *BucketTargetSys) markRefreshDone(bucket, arn string) { 485 sys.aMutex.Lock() 486 defer sys.aMutex.Unlock() 487 if v, ok := sys.arnErrsMap[arn]; ok { 488 sys.arnErrsMap[arn] = arnErrs{ 489 updateInProgress: false, 490 count: v.count, 491 bucket: bucket, 492 } 493 } 494 } 495 496 func (sys *BucketTargetSys) isReloadingTarget(bucket, arn string) bool { 497 sys.aMutex.RLock() 498 defer sys.aMutex.RUnlock() 499 if v, ok := sys.arnErrsMap[arn]; ok { 500 return v.updateInProgress 501 } 502 return false 503 } 504 505 func (sys *BucketTargetSys) incTargetErr(bucket, arn string) { 506 sys.aMutex.Lock() 507 defer sys.aMutex.Unlock() 508 if v, ok := sys.arnErrsMap[arn]; ok { 509 sys.arnErrsMap[arn] = arnErrs{ 510 updateInProgress: v.updateInProgress, 511 count: v.count + 1, 512 } 513 } 514 } 515 516 // GetRemoteTargetClient returns minio-go client for replication target instance 517 func (sys *BucketTargetSys) GetRemoteTargetClient(bucket, arn string) *TargetClient { 518 sys.RLock() 519 tgt := sys.arnRemotesMap[arn] 520 sys.RUnlock() 521 522 if tgt.Client != nil { 523 return tgt.Client 524 } 525 defer func() { // lazy refresh remote targets 526 if tgt.Client == nil && !sys.isReloadingTarget(bucket, arn) && (tgt.lastRefresh.Equal(timeSentinel) || tgt.lastRefresh.Before(UTCNow().Add(-5*time.Minute))) { 527 tgts, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket) 528 if err == nil { 529 sys.markRefreshInProgress(bucket, arn) 530 sys.UpdateAllTargets(bucket, tgts) 531 sys.markRefreshDone(bucket, arn) 532 } 533 } 534 sys.incTargetErr(bucket, arn) 535 }() 536 return nil 537 } 538 539 // GetRemoteBucketTargetByArn returns BucketTarget for a ARN 540 func (sys *BucketTargetSys) GetRemoteBucketTargetByArn(ctx context.Context, bucket, arn string) madmin.BucketTarget { 541 sys.RLock() 542 defer sys.RUnlock() 543 var tgt madmin.BucketTarget 544 for _, t := range sys.targetsMap[bucket] { 545 if t.Arn == arn { 546 tgt = t.Clone() 547 tgt.Credentials = t.Credentials 548 return tgt 549 } 550 } 551 return tgt 552 } 553 554 // NewBucketTargetSys - creates new replication system. 555 func NewBucketTargetSys(ctx context.Context) *BucketTargetSys { 556 sys := &BucketTargetSys{ 557 arnRemotesMap: make(map[string]arnTarget), 558 targetsMap: make(map[string][]madmin.BucketTarget), 559 arnErrsMap: make(map[string]arnErrs), 560 hc: make(map[string]epHealth), 561 hcClient: newHCClient(), 562 } 563 // reload healthCheck endpoints map periodically to remove stale endpoints from the map. 564 go func() { 565 rTimer := time.NewTimer(defaultHealthCheckReloadDuration) 566 defer rTimer.Stop() 567 for { 568 select { 569 case <-rTimer.C: 570 sys.reloadHealthCheckers(ctx) 571 rTimer.Reset(defaultHealthCheckReloadDuration) 572 case <-ctx.Done(): 573 return 574 } 575 } 576 }() 577 go sys.heartBeat(ctx) 578 return sys 579 } 580 581 // UpdateAllTargets updates target to reflect metadata updates 582 func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketTargets) { 583 if sys == nil { 584 return 585 } 586 sys.Lock() 587 defer sys.Unlock() 588 589 // Remove existingtarget and arn association 590 if stgts, ok := sys.targetsMap[bucket]; ok { 591 for _, t := range stgts { 592 delete(sys.arnRemotesMap, t.Arn) 593 } 594 delete(sys.targetsMap, bucket) 595 } 596 597 if tgts != nil { 598 for _, tgt := range tgts.Targets { 599 tgtClient, err := sys.getRemoteTargetClient(&tgt) 600 if err != nil { 601 continue 602 } 603 sys.arnRemotesMap[tgt.Arn] = arnTarget{ 604 Client: tgtClient, 605 lastRefresh: UTCNow(), 606 } 607 sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit) 608 } 609 610 if !tgts.Empty() { 611 sys.targetsMap[bucket] = tgts.Targets 612 } 613 } 614 } 615 616 // create minio-go clients for buckets having remote targets 617 func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) { 618 cfg := meta.bucketTargetConfig 619 if cfg == nil || cfg.Empty() { 620 return 621 } 622 sys.Lock() 623 defer sys.Unlock() 624 for _, tgt := range cfg.Targets { 625 tgtClient, err := sys.getRemoteTargetClient(&tgt) 626 if err != nil { 627 logger.LogIf(GlobalContext, err) 628 continue 629 } 630 sys.arnRemotesMap[tgt.Arn] = arnTarget{Client: tgtClient} 631 sys.updateBandwidthLimit(bucket.Name, tgt.Arn, tgt.BandwidthLimit) 632 } 633 sys.targetsMap[bucket.Name] = cfg.Targets 634 } 635 636 // Returns a minio-go Client configured to access remote host described in replication target config. 637 func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*TargetClient, error) { 638 config := tcfg.Credentials 639 creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "") 640 641 api, err := minio.New(tcfg.Endpoint, &minio.Options{ 642 Creds: creds, 643 Secure: tcfg.Secure, 644 Region: tcfg.Region, 645 Transport: globalRemoteTargetTransport, 646 }) 647 if err != nil { 648 return nil, err 649 } 650 api.SetAppInfo("minio-replication-target", ReleaseTag+" "+tcfg.Arn) 651 652 hcDuration := defaultHealthCheckDuration 653 if tcfg.HealthCheckDuration >= 1 { // require minimum health check duration of 1 sec. 654 hcDuration = tcfg.HealthCheckDuration 655 } 656 tc := &TargetClient{ 657 Client: api, 658 healthCheckDuration: hcDuration, 659 replicateSync: tcfg.ReplicationSync, 660 Bucket: tcfg.TargetBucket, 661 StorageClass: tcfg.StorageClass, 662 disableProxy: tcfg.DisableProxy, 663 ARN: tcfg.Arn, 664 ResetID: tcfg.ResetID, 665 Endpoint: tcfg.Endpoint, 666 Secure: tcfg.Secure, 667 } 668 return tc, nil 669 } 670 671 // getRemoteARN gets existing ARN for an endpoint or generates a new one. 672 func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTarget, deplID string) (arn string, exists bool) { 673 if target == nil { 674 return 675 } 676 sys.RLock() 677 defer sys.RUnlock() 678 tgts := sys.targetsMap[bucket] 679 for _, tgt := range tgts { 680 if tgt.Type == target.Type && 681 tgt.TargetBucket == target.TargetBucket && 682 target.URL().String() == tgt.URL().String() && 683 tgt.Credentials.AccessKey == target.Credentials.AccessKey { 684 return tgt.Arn, true 685 } 686 } 687 if !target.Type.IsValid() { 688 return 689 } 690 return generateARN(target, deplID), false 691 } 692 693 // getRemoteARNForPeer returns the remote target for a peer site in site replication 694 func (sys *BucketTargetSys) getRemoteARNForPeer(bucket string, peer madmin.PeerInfo) string { 695 sys.RLock() 696 defer sys.RUnlock() 697 tgts := sys.targetsMap[bucket] 698 for _, target := range tgts { 699 ep, _ := url.Parse(peer.Endpoint) 700 if target.SourceBucket == bucket && 701 target.TargetBucket == bucket && 702 target.Endpoint == ep.Host && 703 target.Secure == (ep.Scheme == "https") && 704 target.Type == madmin.ReplicationService { 705 return target.Arn 706 } 707 } 708 return "" 709 } 710 711 // generate ARN that is unique to this target type 712 func generateARN(t *madmin.BucketTarget, deplID string) string { 713 uuid := deplID 714 if uuid == "" { 715 uuid = mustGetUUID() 716 } 717 arn := madmin.ARN{ 718 Type: t.Type, 719 ID: uuid, 720 Region: t.Region, 721 Bucket: t.TargetBucket, 722 } 723 return arn.String() 724 } 725 726 // Returns parsed target config. If KMS is configured, remote target is decrypted 727 func parseBucketTargetConfig(bucket string, cdata, cmetadata []byte) (*madmin.BucketTargets, error) { 728 var ( 729 data []byte 730 err error 731 t madmin.BucketTargets 732 meta map[string]string 733 ) 734 if len(cdata) == 0 { 735 return nil, nil 736 } 737 data = cdata 738 json := jsoniter.ConfigCompatibleWithStandardLibrary 739 if len(cmetadata) != 0 { 740 if err := json.Unmarshal(cmetadata, &meta); err != nil { 741 return nil, err 742 } 743 if crypto.S3.IsEncrypted(meta) { 744 if data, err = decryptBucketMetadata(cdata, bucket, meta, kms.Context{ 745 bucket: bucket, 746 bucketTargetsFile: bucketTargetsFile, 747 }); err != nil { 748 return nil, err 749 } 750 } 751 } 752 753 if err = json.Unmarshal(data, &t); err != nil { 754 return nil, err 755 } 756 return &t, nil 757 } 758 759 // TargetClient is the struct for remote target client. 760 type TargetClient struct { 761 *minio.Client 762 healthCheckDuration time.Duration 763 Bucket string // remote bucket target 764 replicateSync bool 765 StorageClass string // storage class on remote 766 disableProxy bool 767 ARN string // ARN to uniquely identify remote target 768 ResetID string 769 Endpoint string 770 Secure bool 771 }