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  }