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

     1  // Copyright (c) 2015-2023 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  	"bytes"
    22  	"context"
    23  	"fmt"
    24  	"net/http"
    25  	"net/url"
    26  	"regexp"
    27  	"strconv"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  
    32  	"github.com/minio/madmin-go/v3"
    33  	"github.com/minio/minio/internal/bucket/replication"
    34  	"github.com/minio/minio/internal/crypto"
    35  	xhttp "github.com/minio/minio/internal/http"
    36  )
    37  
    38  //go:generate msgp -file=$GOFILE
    39  
    40  // replicatedTargetInfo struct represents replication info on a target
    41  type replicatedTargetInfo struct {
    42  	Arn                   string
    43  	Size                  int64
    44  	Duration              time.Duration
    45  	ReplicationAction     replicationAction // full or metadata only
    46  	OpType                replication.Type  // whether incoming replication, existing object, healing etc..
    47  	ReplicationStatus     replication.StatusType
    48  	PrevReplicationStatus replication.StatusType
    49  	VersionPurgeStatus    VersionPurgeStatusType
    50  	ResyncTimestamp       string
    51  	ReplicationResynced   bool // true only if resync attempted for this target
    52  	endpoint              string
    53  	secure                bool
    54  	Err                   error // replication error if any
    55  }
    56  
    57  // Empty returns true for a target if arn is empty
    58  func (rt replicatedTargetInfo) Empty() bool {
    59  	return rt.Arn == ""
    60  }
    61  
    62  type replicatedInfos struct {
    63  	ReplicationTimeStamp time.Time
    64  	Targets              []replicatedTargetInfo
    65  }
    66  
    67  func (ri replicatedInfos) CompletedSize() (sz int64) {
    68  	for _, t := range ri.Targets {
    69  		if t.Empty() {
    70  			continue
    71  		}
    72  		if t.ReplicationStatus == replication.Completed && t.PrevReplicationStatus != replication.Completed {
    73  			sz += t.Size
    74  		}
    75  	}
    76  	return sz
    77  }
    78  
    79  // ReplicationAttempted returns true if replication was attempted on any of the targets for the object version
    80  // queued
    81  func (ri replicatedInfos) ReplicationResynced() bool {
    82  	for _, t := range ri.Targets {
    83  		if t.Empty() || !t.ReplicationResynced {
    84  			continue
    85  		}
    86  		return true
    87  	}
    88  	return false
    89  }
    90  
    91  func (ri replicatedInfos) ReplicationStatusInternal() string {
    92  	b := new(bytes.Buffer)
    93  	for _, t := range ri.Targets {
    94  		if t.Empty() {
    95  			continue
    96  		}
    97  		fmt.Fprintf(b, "%s=%s;", t.Arn, t.ReplicationStatus.String())
    98  	}
    99  	return b.String()
   100  }
   101  
   102  func (ri replicatedInfos) ReplicationStatus() replication.StatusType {
   103  	if len(ri.Targets) == 0 {
   104  		return replication.StatusType("")
   105  	}
   106  	completed := 0
   107  	for _, v := range ri.Targets {
   108  		switch v.ReplicationStatus {
   109  		case replication.Failed:
   110  			return replication.Failed
   111  		case replication.Completed:
   112  			completed++
   113  		}
   114  	}
   115  	if completed == len(ri.Targets) {
   116  		return replication.Completed
   117  	}
   118  	return replication.Pending
   119  }
   120  
   121  func (ri replicatedInfos) VersionPurgeStatus() VersionPurgeStatusType {
   122  	if len(ri.Targets) == 0 {
   123  		return VersionPurgeStatusType("")
   124  	}
   125  	completed := 0
   126  	for _, v := range ri.Targets {
   127  		switch v.VersionPurgeStatus {
   128  		case Failed:
   129  			return Failed
   130  		case Complete:
   131  			completed++
   132  		}
   133  	}
   134  	if completed == len(ri.Targets) {
   135  		return Complete
   136  	}
   137  	return Pending
   138  }
   139  
   140  func (ri replicatedInfos) VersionPurgeStatusInternal() string {
   141  	b := new(bytes.Buffer)
   142  	for _, t := range ri.Targets {
   143  		if t.Empty() {
   144  			continue
   145  		}
   146  		if t.VersionPurgeStatus.Empty() {
   147  			continue
   148  		}
   149  		fmt.Fprintf(b, "%s=%s;", t.Arn, t.VersionPurgeStatus)
   150  	}
   151  	return b.String()
   152  }
   153  
   154  func (ri replicatedInfos) Action() replicationAction {
   155  	for _, t := range ri.Targets {
   156  		if t.Empty() {
   157  			continue
   158  		}
   159  		// rely on replication action from target that actually performed replication now.
   160  		if t.PrevReplicationStatus != replication.Completed {
   161  			return t.ReplicationAction
   162  		}
   163  	}
   164  	return replicateNone
   165  }
   166  
   167  var replStatusRegex = regexp.MustCompile(`([^=].*?)=([^,].*?);`)
   168  
   169  // TargetReplicationStatus - returns replication status of a target
   170  func (ri ReplicateObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) {
   171  	repStatMatches := replStatusRegex.FindAllStringSubmatch(ri.ReplicationStatusInternal, -1)
   172  	for _, repStatMatch := range repStatMatches {
   173  		if len(repStatMatch) != 3 {
   174  			return
   175  		}
   176  		if repStatMatch[1] == arn {
   177  			return replication.StatusType(repStatMatch[2])
   178  		}
   179  	}
   180  	return
   181  }
   182  
   183  // TargetReplicationStatus - returns replication status of a target
   184  func (o ObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) {
   185  	repStatMatches := replStatusRegex.FindAllStringSubmatch(o.ReplicationStatusInternal, -1)
   186  	for _, repStatMatch := range repStatMatches {
   187  		if len(repStatMatch) != 3 {
   188  			return
   189  		}
   190  		if repStatMatch[1] == arn {
   191  			return replication.StatusType(repStatMatch[2])
   192  		}
   193  	}
   194  	return
   195  }
   196  
   197  type replicateTargetDecision struct {
   198  	Replicate   bool   // Replicate to this target
   199  	Synchronous bool   // Synchronous replication configured.
   200  	Arn         string // ARN of replication target
   201  	ID          string
   202  }
   203  
   204  func (t *replicateTargetDecision) String() string {
   205  	return fmt.Sprintf("%t;%t;%s;%s", t.Replicate, t.Synchronous, t.Arn, t.ID)
   206  }
   207  
   208  func newReplicateTargetDecision(arn string, replicate bool, sync bool) replicateTargetDecision {
   209  	d := replicateTargetDecision{
   210  		Replicate:   replicate,
   211  		Synchronous: sync,
   212  		Arn:         arn,
   213  	}
   214  	return d
   215  }
   216  
   217  // ReplicateDecision represents replication decision for each target
   218  type ReplicateDecision struct {
   219  	targetsMap map[string]replicateTargetDecision
   220  }
   221  
   222  // ReplicateAny returns true if at least one target qualifies for replication
   223  func (d ReplicateDecision) ReplicateAny() bool {
   224  	for _, t := range d.targetsMap {
   225  		if t.Replicate {
   226  			return true
   227  		}
   228  	}
   229  	return false
   230  }
   231  
   232  // Synchronous returns true if at least one target qualifies for synchronous replication
   233  func (d ReplicateDecision) Synchronous() bool {
   234  	for _, t := range d.targetsMap {
   235  		if t.Synchronous {
   236  			return true
   237  		}
   238  	}
   239  	return false
   240  }
   241  
   242  func (d ReplicateDecision) String() string {
   243  	b := new(bytes.Buffer)
   244  	for key, value := range d.targetsMap {
   245  		fmt.Fprintf(b, "%s=%s,", key, value.String())
   246  	}
   247  	return strings.TrimSuffix(b.String(), ",")
   248  }
   249  
   250  // Set updates ReplicateDecision with target's replication decision
   251  func (d *ReplicateDecision) Set(t replicateTargetDecision) {
   252  	if d.targetsMap == nil {
   253  		d.targetsMap = make(map[string]replicateTargetDecision)
   254  	}
   255  	d.targetsMap[t.Arn] = t
   256  }
   257  
   258  // PendingStatus returns a stringified representation of internal replication status with all targets marked as `PENDING`
   259  func (d ReplicateDecision) PendingStatus() string {
   260  	b := new(bytes.Buffer)
   261  	for _, k := range d.targetsMap {
   262  		if k.Replicate {
   263  			fmt.Fprintf(b, "%s=%s;", k.Arn, replication.Pending.String())
   264  		}
   265  	}
   266  	return b.String()
   267  }
   268  
   269  // ResyncDecision is a struct representing a map with target's individual resync decisions
   270  type ResyncDecision struct {
   271  	targets map[string]ResyncTargetDecision
   272  }
   273  
   274  // Empty returns true if no targets with resync decision present
   275  func (r ResyncDecision) Empty() bool {
   276  	return r.targets == nil
   277  }
   278  
   279  func (r ResyncDecision) mustResync() bool {
   280  	for _, v := range r.targets {
   281  		if v.Replicate {
   282  			return true
   283  		}
   284  	}
   285  	return false
   286  }
   287  
   288  func (r ResyncDecision) mustResyncTarget(tgtArn string) bool {
   289  	if r.targets == nil {
   290  		return false
   291  	}
   292  	v, ok := r.targets[tgtArn]
   293  	return ok && v.Replicate
   294  }
   295  
   296  // ResyncTargetDecision is struct that represents resync decision for this target
   297  type ResyncTargetDecision struct {
   298  	Replicate       bool
   299  	ResetID         string
   300  	ResetBeforeDate time.Time
   301  }
   302  
   303  var errInvalidReplicateDecisionFormat = fmt.Errorf("ReplicateDecision has invalid format")
   304  
   305  // parse k-v pairs of target ARN to stringified ReplicateTargetDecision delimited by ',' into a
   306  // ReplicateDecision struct
   307  func parseReplicateDecision(ctx context.Context, bucket, s string) (r ReplicateDecision, err error) {
   308  	r = ReplicateDecision{
   309  		targetsMap: make(map[string]replicateTargetDecision),
   310  	}
   311  	if len(s) == 0 {
   312  		return
   313  	}
   314  	for _, p := range strings.Split(s, ",") {
   315  		if p == "" {
   316  			continue
   317  		}
   318  		slc := strings.Split(p, "=")
   319  		if len(slc) != 2 {
   320  			return r, errInvalidReplicateDecisionFormat
   321  		}
   322  		tgtStr := strings.TrimSuffix(strings.TrimPrefix(slc[1], `"`), `"`)
   323  		tgt := strings.Split(tgtStr, ";")
   324  		if len(tgt) != 4 {
   325  			return r, errInvalidReplicateDecisionFormat
   326  		}
   327  		r.targetsMap[slc[0]] = replicateTargetDecision{Replicate: tgt[0] == "true", Synchronous: tgt[1] == "true", Arn: tgt[2], ID: tgt[3]}
   328  	}
   329  	return
   330  }
   331  
   332  // ReplicationState represents internal replication state
   333  type ReplicationState struct {
   334  	ReplicaTimeStamp          time.Time              // timestamp when last replica update was received
   335  	ReplicaStatus             replication.StatusType // replica statusstringis
   336  	DeleteMarker              bool                   // represents DeleteMarker replication state
   337  	ReplicationTimeStamp      time.Time              // timestamp when last replication activity happened
   338  	ReplicationStatusInternal string                 // stringified representation of all replication activity
   339  	// VersionPurgeStatusInternal is internally in the format "arn1=PENDING;arn2=COMPLETED;"
   340  	VersionPurgeStatusInternal string                            // stringified representation of all version purge statuses
   341  	ReplicateDecisionStr       string                            // stringified representation of replication decision for each target
   342  	Targets                    map[string]replication.StatusType // map of ARN->replication status for ongoing replication activity
   343  	PurgeTargets               map[string]VersionPurgeStatusType // map of ARN->VersionPurgeStatus for all the targets
   344  	ResetStatusesMap           map[string]string                 // map of ARN-> stringified reset id and timestamp for all the targets
   345  }
   346  
   347  // Equal returns true if replication state is identical for version purge statuses and (replica)tion statuses.
   348  func (rs *ReplicationState) Equal(o ReplicationState) bool {
   349  	return rs.ReplicaStatus == o.ReplicaStatus &&
   350  		rs.ReplicationStatusInternal == o.ReplicationStatusInternal &&
   351  		rs.VersionPurgeStatusInternal == o.VersionPurgeStatusInternal
   352  }
   353  
   354  // CompositeReplicationStatus returns overall replication status for the object version being replicated.
   355  func (rs *ReplicationState) CompositeReplicationStatus() (st replication.StatusType) {
   356  	switch {
   357  	case rs.ReplicationStatusInternal != "":
   358  		switch replication.StatusType(rs.ReplicationStatusInternal) {
   359  		case replication.Pending, replication.Completed, replication.Failed, replication.Replica: // for backward compatibility
   360  			return replication.StatusType(rs.ReplicationStatusInternal)
   361  		default:
   362  			replStatus := getCompositeReplicationStatus(rs.Targets)
   363  			// return REPLICA status if replica received timestamp is later than replication timestamp
   364  			// provided object replication completed for all targets.
   365  			if rs.ReplicaTimeStamp.Equal(timeSentinel) || rs.ReplicaTimeStamp.IsZero() {
   366  				return replStatus
   367  			}
   368  			if replStatus == replication.Completed && rs.ReplicaTimeStamp.After(rs.ReplicationTimeStamp) {
   369  				return rs.ReplicaStatus
   370  			}
   371  			return replStatus
   372  		}
   373  	case !rs.ReplicaStatus.Empty():
   374  		return rs.ReplicaStatus
   375  	default:
   376  		return
   377  	}
   378  }
   379  
   380  // CompositeVersionPurgeStatus returns overall replication purge status for the permanent delete being replicated.
   381  func (rs *ReplicationState) CompositeVersionPurgeStatus() VersionPurgeStatusType {
   382  	switch VersionPurgeStatusType(rs.VersionPurgeStatusInternal) {
   383  	case Pending, Complete, Failed: // for backward compatibility
   384  		return VersionPurgeStatusType(rs.VersionPurgeStatusInternal)
   385  	default:
   386  		return getCompositeVersionPurgeStatus(rs.PurgeTargets)
   387  	}
   388  }
   389  
   390  // TargetState returns replicatedInfos struct initialized with the previous state of replication
   391  func (rs *ReplicationState) targetState(arn string) (r replicatedTargetInfo) {
   392  	return replicatedTargetInfo{
   393  		Arn:                   arn,
   394  		PrevReplicationStatus: rs.Targets[arn],
   395  		VersionPurgeStatus:    rs.PurgeTargets[arn],
   396  		ResyncTimestamp:       rs.ResetStatusesMap[arn],
   397  	}
   398  }
   399  
   400  // getReplicationState returns replication state using target replicated info for the targets
   401  func getReplicationState(rinfos replicatedInfos, prevState ReplicationState, vID string) ReplicationState {
   402  	rs := ReplicationState{
   403  		ReplicateDecisionStr: prevState.ReplicateDecisionStr,
   404  		ResetStatusesMap:     prevState.ResetStatusesMap,
   405  		ReplicaTimeStamp:     prevState.ReplicaTimeStamp,
   406  		ReplicaStatus:        prevState.ReplicaStatus,
   407  	}
   408  	var replStatuses, vpurgeStatuses string
   409  	replStatuses = rinfos.ReplicationStatusInternal()
   410  	rs.Targets = replicationStatusesMap(replStatuses)
   411  	rs.ReplicationStatusInternal = replStatuses
   412  	rs.ReplicationTimeStamp = rinfos.ReplicationTimeStamp
   413  
   414  	vpurgeStatuses = rinfos.VersionPurgeStatusInternal()
   415  	rs.VersionPurgeStatusInternal = vpurgeStatuses
   416  	rs.PurgeTargets = versionPurgeStatusesMap(vpurgeStatuses)
   417  
   418  	for _, rinfo := range rinfos.Targets {
   419  		if rinfo.ResyncTimestamp != "" {
   420  			rs.ResetStatusesMap[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp
   421  		}
   422  	}
   423  	return rs
   424  }
   425  
   426  // constructs a replication status map from string representation
   427  func replicationStatusesMap(s string) map[string]replication.StatusType {
   428  	targets := make(map[string]replication.StatusType)
   429  	repStatMatches := replStatusRegex.FindAllStringSubmatch(s, -1)
   430  	for _, repStatMatch := range repStatMatches {
   431  		if len(repStatMatch) != 3 {
   432  			continue
   433  		}
   434  		status := replication.StatusType(repStatMatch[2])
   435  		targets[repStatMatch[1]] = status
   436  	}
   437  	return targets
   438  }
   439  
   440  // constructs a version purge status map from string representation
   441  func versionPurgeStatusesMap(s string) map[string]VersionPurgeStatusType {
   442  	targets := make(map[string]VersionPurgeStatusType)
   443  	purgeStatusMatches := replStatusRegex.FindAllStringSubmatch(s, -1)
   444  	for _, purgeStatusMatch := range purgeStatusMatches {
   445  		if len(purgeStatusMatch) != 3 {
   446  			continue
   447  		}
   448  		targets[purgeStatusMatch[1]] = VersionPurgeStatusType(purgeStatusMatch[2])
   449  	}
   450  	return targets
   451  }
   452  
   453  // return the overall replication status for all the targets
   454  func getCompositeReplicationStatus(m map[string]replication.StatusType) replication.StatusType {
   455  	if len(m) == 0 {
   456  		return replication.StatusType("")
   457  	}
   458  	completed := 0
   459  	for _, v := range m {
   460  		switch v {
   461  		case replication.Failed:
   462  			return replication.Failed
   463  		case replication.Completed:
   464  			completed++
   465  		}
   466  	}
   467  	if completed == len(m) {
   468  		return replication.Completed
   469  	}
   470  	return replication.Pending
   471  }
   472  
   473  // return the overall version purge status for all the targets
   474  func getCompositeVersionPurgeStatus(m map[string]VersionPurgeStatusType) VersionPurgeStatusType {
   475  	if len(m) == 0 {
   476  		return VersionPurgeStatusType("")
   477  	}
   478  	completed := 0
   479  	for _, v := range m {
   480  		switch v {
   481  		case Failed:
   482  			return Failed
   483  		case Complete:
   484  			completed++
   485  		}
   486  	}
   487  	if completed == len(m) {
   488  		return Complete
   489  	}
   490  	return Pending
   491  }
   492  
   493  // getHealReplicateObjectInfo returns info needed by heal replication in ReplicateObjectInfo
   494  func getHealReplicateObjectInfo(oi ObjectInfo, rcfg replicationConfig) ReplicateObjectInfo {
   495  	userDefined := cloneMSS(oi.UserDefined)
   496  	if rcfg.Config != nil && rcfg.Config.RoleArn != "" {
   497  		// For backward compatibility of objects pending/failed replication.
   498  		// Save replication related statuses in the new internal representation for
   499  		// compatible behavior.
   500  		if !oi.ReplicationStatus.Empty() {
   501  			oi.ReplicationStatusInternal = fmt.Sprintf("%s=%s;", rcfg.Config.RoleArn, oi.ReplicationStatus)
   502  		}
   503  		if !oi.VersionPurgeStatus.Empty() {
   504  			oi.VersionPurgeStatusInternal = fmt.Sprintf("%s=%s;", rcfg.Config.RoleArn, oi.VersionPurgeStatus)
   505  		}
   506  		for k, v := range userDefined {
   507  			if strings.EqualFold(k, ReservedMetadataPrefixLower+ReplicationReset) {
   508  				delete(userDefined, k)
   509  				userDefined[targetResetHeader(rcfg.Config.RoleArn)] = v
   510  			}
   511  		}
   512  	}
   513  
   514  	var dsc ReplicateDecision
   515  	if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
   516  		dsc = checkReplicateDelete(GlobalContext, oi.Bucket, ObjectToDelete{
   517  			ObjectV: ObjectV{
   518  				ObjectName: oi.Name,
   519  				VersionID:  oi.VersionID,
   520  			},
   521  		}, oi, ObjectOptions{
   522  			Versioned:        globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name),
   523  			VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
   524  		}, nil)
   525  	} else {
   526  		dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(userDefined, oi.UserTags, "", replication.HealReplicationType, ObjectOptions{}))
   527  	}
   528  
   529  	tgtStatuses := replicationStatusesMap(oi.ReplicationStatusInternal)
   530  	purgeStatuses := versionPurgeStatusesMap(oi.VersionPurgeStatusInternal)
   531  	existingObjResync := rcfg.Resync(GlobalContext, oi, dsc, tgtStatuses)
   532  	tm, _ := time.Parse(time.RFC3339Nano, userDefined[ReservedMetadataPrefixLower+ReplicationTimestamp])
   533  	rstate := oi.ReplicationState()
   534  	rstate.ReplicateDecisionStr = dsc.String()
   535  	asz, _ := oi.GetActualSize()
   536  
   537  	return ReplicateObjectInfo{
   538  		Name:                       oi.Name,
   539  		Size:                       oi.Size,
   540  		ActualSize:                 asz,
   541  		Bucket:                     oi.Bucket,
   542  		VersionID:                  oi.VersionID,
   543  		ETag:                       oi.ETag,
   544  		ModTime:                    oi.ModTime,
   545  		ReplicationStatus:          oi.ReplicationStatus,
   546  		ReplicationStatusInternal:  oi.ReplicationStatusInternal,
   547  		DeleteMarker:               oi.DeleteMarker,
   548  		VersionPurgeStatusInternal: oi.VersionPurgeStatusInternal,
   549  		VersionPurgeStatus:         oi.VersionPurgeStatus,
   550  
   551  		ReplicationState:     rstate,
   552  		OpType:               replication.HealReplicationType,
   553  		Dsc:                  dsc,
   554  		ExistingObjResync:    existingObjResync,
   555  		TargetStatuses:       tgtStatuses,
   556  		TargetPurgeStatuses:  purgeStatuses,
   557  		ReplicationTimestamp: tm,
   558  		SSEC:                 crypto.SSEC.IsEncrypted(oi.UserDefined),
   559  		UserTags:             oi.UserTags,
   560  	}
   561  }
   562  
   563  // ReplicationState - returns replication state using other internal replication metadata in ObjectInfo
   564  func (o ObjectInfo) ReplicationState() ReplicationState {
   565  	rs := ReplicationState{
   566  		ReplicationStatusInternal:  o.ReplicationStatusInternal,
   567  		VersionPurgeStatusInternal: o.VersionPurgeStatusInternal,
   568  		ReplicateDecisionStr:       o.replicationDecision,
   569  		Targets:                    make(map[string]replication.StatusType),
   570  		PurgeTargets:               make(map[string]VersionPurgeStatusType),
   571  		ResetStatusesMap:           make(map[string]string),
   572  	}
   573  	rs.Targets = replicationStatusesMap(o.ReplicationStatusInternal)
   574  	rs.PurgeTargets = versionPurgeStatusesMap(o.VersionPurgeStatusInternal)
   575  	for k, v := range o.UserDefined {
   576  		if strings.HasPrefix(k, ReservedMetadataPrefixLower+ReplicationReset) {
   577  			arn := strings.TrimPrefix(k, fmt.Sprintf("%s-", ReservedMetadataPrefixLower+ReplicationReset))
   578  			rs.ResetStatusesMap[arn] = v
   579  		}
   580  	}
   581  	return rs
   582  }
   583  
   584  // ReplicationState returns replication state using other internal replication metadata in ObjectToDelete
   585  func (o ObjectToDelete) ReplicationState() ReplicationState {
   586  	r := ReplicationState{
   587  		ReplicationStatusInternal:  o.DeleteMarkerReplicationStatus,
   588  		VersionPurgeStatusInternal: o.VersionPurgeStatuses,
   589  		ReplicateDecisionStr:       o.ReplicateDecisionStr,
   590  	}
   591  
   592  	r.Targets = replicationStatusesMap(o.DeleteMarkerReplicationStatus)
   593  	r.PurgeTargets = versionPurgeStatusesMap(o.VersionPurgeStatuses)
   594  	return r
   595  }
   596  
   597  // VersionPurgeStatus returns a composite version purge status across targets
   598  func (d *DeletedObject) VersionPurgeStatus() VersionPurgeStatusType {
   599  	return d.ReplicationState.CompositeVersionPurgeStatus()
   600  }
   601  
   602  // DeleteMarkerReplicationStatus return composite replication status of delete marker across targets
   603  func (d *DeletedObject) DeleteMarkerReplicationStatus() replication.StatusType {
   604  	return d.ReplicationState.CompositeReplicationStatus()
   605  }
   606  
   607  // ResyncTargetsInfo holds a slice of targets with resync info per target
   608  type ResyncTargetsInfo struct {
   609  	Targets []ResyncTarget `json:"target,omitempty"`
   610  }
   611  
   612  // ResyncTarget is a struct representing the Target reset ID where target is identified by its Arn
   613  type ResyncTarget struct {
   614  	Arn       string    `json:"arn"`
   615  	ResetID   string    `json:"resetid"`
   616  	StartTime time.Time `json:"startTime"`
   617  	EndTime   time.Time `json:"endTime"`
   618  	// Status of resync operation
   619  	ResyncStatus string `json:"resyncStatus,omitempty"`
   620  	// Completed size in bytes
   621  	ReplicatedSize int64 `json:"completedReplicationSize"`
   622  	// Failed size in bytes
   623  	FailedSize int64 `json:"failedReplicationSize"`
   624  	// Total number of failed operations
   625  	FailedCount int64 `json:"failedReplicationCount"`
   626  	// Total number of failed operations
   627  	ReplicatedCount int64 `json:"replicationCount"`
   628  	// Last bucket/object replicated.
   629  	Bucket string `json:"bucket,omitempty"`
   630  	Object string `json:"object,omitempty"`
   631  }
   632  
   633  // VersionPurgeStatusType represents status of a versioned delete or permanent delete w.r.t bucket replication
   634  type VersionPurgeStatusType string
   635  
   636  const (
   637  	// Pending - versioned delete replication is pending.
   638  	Pending VersionPurgeStatusType = "PENDING"
   639  
   640  	// Complete - versioned delete replication is now complete, erase version on disk.
   641  	Complete VersionPurgeStatusType = "COMPLETE"
   642  
   643  	// Failed - versioned delete replication failed.
   644  	Failed VersionPurgeStatusType = "FAILED"
   645  )
   646  
   647  // Empty returns true if purge status was not set.
   648  func (v VersionPurgeStatusType) Empty() bool {
   649  	return string(v) == ""
   650  }
   651  
   652  // Pending returns true if the version is pending purge.
   653  func (v VersionPurgeStatusType) Pending() bool {
   654  	return v == Pending || v == Failed
   655  }
   656  
   657  type replicationResyncer struct {
   658  	// map of bucket to their resync status
   659  	statusMap      map[string]BucketReplicationResyncStatus
   660  	workerSize     int
   661  	resyncCancelCh chan struct{}
   662  	workerCh       chan struct{}
   663  	sync.RWMutex
   664  }
   665  
   666  const (
   667  	replicationDir      = ".replication"
   668  	resyncFileName      = "resync.bin"
   669  	resyncMetaFormat    = 1
   670  	resyncMetaVersionV1 = 1
   671  	resyncMetaVersion   = resyncMetaVersionV1
   672  )
   673  
   674  type resyncOpts struct {
   675  	bucket       string
   676  	arn          string
   677  	resyncID     string
   678  	resyncBefore time.Time
   679  }
   680  
   681  // ResyncStatusType status of resync operation
   682  type ResyncStatusType int
   683  
   684  const (
   685  	// NoResync - no resync in progress
   686  	NoResync ResyncStatusType = iota
   687  	// ResyncPending - resync pending
   688  	ResyncPending
   689  	// ResyncCanceled - resync canceled
   690  	ResyncCanceled
   691  	// ResyncStarted -  resync in progress
   692  	ResyncStarted
   693  	// ResyncCompleted -  resync finished
   694  	ResyncCompleted
   695  	// ResyncFailed -  resync failed
   696  	ResyncFailed
   697  )
   698  
   699  func (rt ResyncStatusType) isValid() bool {
   700  	return rt != NoResync
   701  }
   702  
   703  func (rt ResyncStatusType) String() string {
   704  	switch rt {
   705  	case ResyncStarted:
   706  		return "Ongoing"
   707  	case ResyncCompleted:
   708  		return "Completed"
   709  	case ResyncFailed:
   710  		return "Failed"
   711  	case ResyncPending:
   712  		return "Pending"
   713  	case ResyncCanceled:
   714  		return "Canceled"
   715  	default:
   716  		return ""
   717  	}
   718  }
   719  
   720  // TargetReplicationResyncStatus status of resync of bucket for a specific target
   721  type TargetReplicationResyncStatus struct {
   722  	StartTime  time.Time `json:"startTime" msg:"st"`
   723  	LastUpdate time.Time `json:"lastUpdated" msg:"lst"`
   724  	// Resync ID assigned to this reset
   725  	ResyncID string `json:"resyncID" msg:"id"`
   726  	// ResyncBeforeDate - resync all objects created prior to this date
   727  	ResyncBeforeDate time.Time `json:"resyncBeforeDate" msg:"rdt"`
   728  	// Status of resync operation
   729  	ResyncStatus ResyncStatusType `json:"resyncStatus" msg:"rst"`
   730  	// Failed size in bytes
   731  	FailedSize int64 `json:"failedReplicationSize"  msg:"fs"`
   732  	// Total number of failed operations
   733  	FailedCount int64 `json:"failedReplicationCount"  msg:"frc"`
   734  	// Completed size in bytes
   735  	ReplicatedSize int64 `json:"completedReplicationSize"  msg:"rs"`
   736  	// Total number of failed operations
   737  	ReplicatedCount int64 `json:"replicationCount"  msg:"rrc"`
   738  	// Last bucket/object replicated.
   739  	Bucket string `json:"-" msg:"bkt"`
   740  	Object string `json:"-" msg:"obj"`
   741  	Error  error  `json:"-" msg:"-"`
   742  }
   743  
   744  // BucketReplicationResyncStatus captures current replication resync status
   745  type BucketReplicationResyncStatus struct {
   746  	Version int `json:"version" msg:"v"`
   747  	// map of remote arn to their resync status for a bucket
   748  	TargetsMap map[string]TargetReplicationResyncStatus `json:"resyncMap,omitempty" msg:"brs"`
   749  	ID         int                                      `json:"id" msg:"id"`
   750  	LastUpdate time.Time                                `json:"lastUpdate" msg:"lu"`
   751  }
   752  
   753  func (rs *BucketReplicationResyncStatus) cloneTgtStats() (m map[string]TargetReplicationResyncStatus) {
   754  	m = make(map[string]TargetReplicationResyncStatus)
   755  	for arn, st := range rs.TargetsMap {
   756  		m[arn] = st
   757  	}
   758  	return
   759  }
   760  
   761  func newBucketResyncStatus(bucket string) BucketReplicationResyncStatus {
   762  	return BucketReplicationResyncStatus{
   763  		TargetsMap: make(map[string]TargetReplicationResyncStatus),
   764  		Version:    resyncMetaVersion,
   765  	}
   766  }
   767  
   768  var contentRangeRegexp = regexp.MustCompile(`bytes ([0-9]+)-([0-9]+)/([0-9]+|\\*)`)
   769  
   770  // parse size from content-range header
   771  func parseSizeFromContentRange(h http.Header) (sz int64, err error) {
   772  	cr := h.Get(xhttp.ContentRange)
   773  	if cr == "" {
   774  		return sz, fmt.Errorf("Content-Range not set")
   775  	}
   776  	parts := contentRangeRegexp.FindStringSubmatch(cr)
   777  	if len(parts) != 4 {
   778  		return sz, fmt.Errorf("invalid Content-Range header %s", cr)
   779  	}
   780  	if parts[3] == "*" {
   781  		return -1, nil
   782  	}
   783  	var usz uint64
   784  	usz, err = strconv.ParseUint(parts[3], 10, 64)
   785  	if err != nil {
   786  		return sz, err
   787  	}
   788  	return int64(usz), nil
   789  }
   790  
   791  func extractReplicateDiffOpts(q url.Values) (opts madmin.ReplDiffOpts) {
   792  	opts.Verbose = q.Get("verbose") == "true"
   793  	opts.ARN = q.Get("arn")
   794  	opts.Prefix = q.Get("prefix")
   795  	return
   796  }
   797  
   798  const (
   799  	replicationMRFDir = bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + "mrf"
   800  	mrfMetaFormat     = 1
   801  	mrfMetaVersionV1  = 1
   802  	mrfMetaVersion    = mrfMetaVersionV1
   803  )
   804  
   805  // MRFReplicateEntry mrf entry to save to disk
   806  type MRFReplicateEntry struct {
   807  	Bucket     string `json:"bucket" msg:"b"`
   808  	Object     string `json:"object" msg:"o"`
   809  	versionID  string `json:"-"`
   810  	RetryCount int    `json:"retryCount" msg:"rc"`
   811  	sz         int64  `json:"-"`
   812  }
   813  
   814  // MRFReplicateEntries has the map of MRF entries to save to disk
   815  type MRFReplicateEntries struct {
   816  	Entries map[string]MRFReplicateEntry `json:"entries" msg:"e"`
   817  	Version int                          `json:"version" msg:"v"`
   818  }
   819  
   820  // ToMRFEntry returns the relevant info needed by MRF
   821  func (ri ReplicateObjectInfo) ToMRFEntry() MRFReplicateEntry {
   822  	return MRFReplicateEntry{
   823  		Bucket:     ri.Bucket,
   824  		Object:     ri.Name,
   825  		versionID:  ri.VersionID,
   826  		sz:         ri.Size,
   827  		RetryCount: int(ri.RetryCount),
   828  	}
   829  }