github.com/minio/madmin-go/v3@v3.0.51/replication-api.go (about)

     1  //
     2  // Copyright (c) 2015-2022 MinIO, Inc.
     3  //
     4  // This file is part of MinIO Object Storage stack
     5  //
     6  // This program is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU Affero General Public License as
     8  // published by the Free Software Foundation, either version 3 of the
     9  // License, or (at your option) any later version.
    10  //
    11  // This program is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU Affero General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU Affero General Public License
    17  // along with this program. If not, see <http://www.gnu.org/licenses/>.
    18  //
    19  
    20  package madmin
    21  
    22  import (
    23  	"context"
    24  	"encoding/json"
    25  	"net/http"
    26  	"net/url"
    27  	"time"
    28  )
    29  
    30  //go:generate msgp -file $GOFILE
    31  
    32  // ReplDiffOpts holds options for `mc replicate diff` command
    33  //
    34  //msgp:ignore ReplDiffOpts
    35  type ReplDiffOpts struct {
    36  	ARN     string
    37  	Verbose bool
    38  	Prefix  string
    39  }
    40  
    41  // TgtDiffInfo returns status of unreplicated objects
    42  // for the target ARN
    43  //msgp:ignore TgtDiffInfo
    44  
    45  type TgtDiffInfo struct {
    46  	ReplicationStatus       string `json:"rStatus,omitempty"`  // target replication status
    47  	DeleteReplicationStatus string `json:"drStatus,omitempty"` // target delete replication status
    48  }
    49  
    50  // DiffInfo represents relevant replication status and last attempt to replicate
    51  // for the replication targets configured for the bucket
    52  //msgp:ignore DiffInfo
    53  
    54  type DiffInfo struct {
    55  	Object                  string                 `json:"object"`
    56  	VersionID               string                 `json:"versionId"`
    57  	Targets                 map[string]TgtDiffInfo `json:"targets,omitempty"`
    58  	Err                     error                  `json:"error,omitempty"`
    59  	ReplicationStatus       string                 `json:"rStatus,omitempty"` // overall replication status
    60  	DeleteReplicationStatus string                 `json:"dStatus,omitempty"` // overall replication status of version delete
    61  	ReplicationTimestamp    time.Time              `json:"replTimestamp,omitempty"`
    62  	LastModified            time.Time              `json:"lastModified,omitempty"`
    63  	IsDeleteMarker          bool                   `json:"deletemarker"`
    64  }
    65  
    66  // BucketReplicationDiff - gets diff for non-replicated entries.
    67  func (adm *AdminClient) BucketReplicationDiff(ctx context.Context, bucketName string, opts ReplDiffOpts) <-chan DiffInfo {
    68  	diffCh := make(chan DiffInfo)
    69  
    70  	// start a routine to start reading line by line.
    71  	go func(diffCh chan<- DiffInfo) {
    72  		defer close(diffCh)
    73  		queryValues := url.Values{}
    74  		queryValues.Set("bucket", bucketName)
    75  
    76  		if opts.Verbose {
    77  			queryValues.Set("verbose", "true")
    78  		}
    79  		if opts.ARN != "" {
    80  			queryValues.Set("arn", opts.ARN)
    81  		}
    82  		if opts.Prefix != "" {
    83  			queryValues.Set("prefix", opts.Prefix)
    84  		}
    85  
    86  		reqData := requestData{
    87  			relPath:     adminAPIPrefix + "/replication/diff",
    88  			queryValues: queryValues,
    89  		}
    90  
    91  		// Execute PUT on /minio/admin/v3/diff to set quota for a bucket.
    92  		resp, err := adm.executeMethod(ctx, http.MethodPost, reqData)
    93  		if err != nil {
    94  			diffCh <- DiffInfo{Err: err}
    95  			return
    96  		}
    97  		defer closeResponse(resp)
    98  
    99  		if resp.StatusCode != http.StatusOK {
   100  			diffCh <- DiffInfo{Err: httpRespToErrorResponse(resp)}
   101  			return
   102  		}
   103  
   104  		dec := json.NewDecoder(resp.Body)
   105  		for {
   106  			var di DiffInfo
   107  			if err = dec.Decode(&di); err != nil {
   108  				break
   109  			}
   110  			select {
   111  			case <-ctx.Done():
   112  				return
   113  			case diffCh <- di:
   114  			}
   115  		}
   116  	}(diffCh)
   117  	// Returns the diff channel, for caller to start reading from.
   118  	return diffCh
   119  }
   120  
   121  // ReplicationMRF represents MRF backlog for a bucket
   122  type ReplicationMRF struct {
   123  	NodeName   string `json:"nodeName" msg:"n"`
   124  	Bucket     string `json:"bucket" msg:"b"`
   125  	Object     string `json:"object" msg:"o"`
   126  	VersionID  string `json:"versionId" msg:"v"`
   127  	RetryCount int    `json:"retryCount" msg:"rc"`
   128  	Err        string `json:"error,omitempty" msg:"err"`
   129  }
   130  
   131  // BucketReplicationMRF - gets MRF entries for bucket and node. Return MRF across buckets if bucket is empty, across nodes
   132  // if node is `all`
   133  func (adm *AdminClient) BucketReplicationMRF(ctx context.Context, bucketName string, node string) <-chan ReplicationMRF {
   134  	mrfCh := make(chan ReplicationMRF)
   135  
   136  	// start a routine to start reading line by line.
   137  	go func(mrfCh chan<- ReplicationMRF) {
   138  		defer close(mrfCh)
   139  		queryValues := url.Values{}
   140  		queryValues.Set("bucket", bucketName)
   141  		if node != "" {
   142  			queryValues.Set("node", node)
   143  		}
   144  		reqData := requestData{
   145  			relPath:     adminAPIPrefix + "/replication/mrf",
   146  			queryValues: queryValues,
   147  		}
   148  
   149  		// Execute GET on /minio/admin/v3/replication/mrf to get mrf backlog for a bucket.
   150  		resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   151  		if err != nil {
   152  			mrfCh <- ReplicationMRF{Err: err.Error()}
   153  			return
   154  		}
   155  		defer closeResponse(resp)
   156  
   157  		if resp.StatusCode != http.StatusOK {
   158  			mrfCh <- ReplicationMRF{Err: httpRespToErrorResponse(resp).Error()}
   159  			return
   160  		}
   161  		dec := json.NewDecoder(resp.Body)
   162  		for {
   163  			var bk ReplicationMRF
   164  			if err = dec.Decode(&bk); err != nil {
   165  				break
   166  			}
   167  			select {
   168  			case <-ctx.Done():
   169  				return
   170  			case mrfCh <- bk:
   171  			}
   172  		}
   173  	}(mrfCh)
   174  	// Returns the mrf backlog channel, for caller to start reading from.
   175  	return mrfCh
   176  }
   177  
   178  // LatencyStat represents replication link latency statistics
   179  type LatencyStat struct {
   180  	Curr time.Duration `json:"curr"`
   181  	Avg  time.Duration `json:"avg"`
   182  	Max  time.Duration `json:"max"`
   183  }
   184  
   185  // TimedErrStats has failed replication stats across time windows
   186  type TimedErrStats struct {
   187  	LastMinute RStat `json:"lastMinute"`
   188  	LastHour   RStat `json:"lastHour"`
   189  	Totals     RStat `json:"totals"`
   190  	// ErrCounts is a map of error codes to count of errors since server start - tracks
   191  	// only AccessDenied errors for now.
   192  	ErrCounts map[string]int `json:"errCounts,omitempty"`
   193  }
   194  
   195  // Add - adds two TimedErrStats
   196  func (te TimedErrStats) Add(o TimedErrStats) TimedErrStats {
   197  	m := make(map[string]int)
   198  	for k, v := range te.ErrCounts {
   199  		m[k] = v
   200  	}
   201  	for k, v := range o.ErrCounts {
   202  		m[k] += v
   203  	}
   204  	return TimedErrStats{
   205  		LastMinute: te.LastMinute.Add(o.LastMinute),
   206  		LastHour:   te.LastHour.Add(o.LastHour),
   207  		Totals:     te.Totals.Add(o.Totals),
   208  		ErrCounts:  m,
   209  	}
   210  }
   211  
   212  // RStat represents count and bytes replicated/failed
   213  type RStat struct {
   214  	Count float64 `json:"count"`
   215  	Bytes int64   `json:"bytes"`
   216  }
   217  
   218  // Add - adds two RStats
   219  func (r RStat) Add(r1 RStat) RStat {
   220  	return RStat{
   221  		Count: r.Count + r1.Count,
   222  		Bytes: r.Bytes + r1.Bytes,
   223  	}
   224  }