storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/bucket/replication/replication.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package replication
    18  
    19  import (
    20  	"encoding/xml"
    21  	"io"
    22  	"sort"
    23  	"strconv"
    24  	"strings"
    25  )
    26  
    27  // StatusType of Replication for x-amz-replication-status header
    28  type StatusType string
    29  
    30  const (
    31  	// Pending - replication is pending.
    32  	Pending StatusType = "PENDING"
    33  
    34  	// Completed - replication completed ok.
    35  	Completed StatusType = "COMPLETED"
    36  
    37  	// Failed - replication failed.
    38  	Failed StatusType = "FAILED"
    39  
    40  	// Replica - this is a replica.
    41  	Replica StatusType = "REPLICA"
    42  )
    43  
    44  // String returns string representation of status
    45  func (s StatusType) String() string {
    46  	return string(s)
    47  }
    48  
    49  // Empty returns true if this status is not set
    50  func (s StatusType) Empty() bool {
    51  	return string(s) == ""
    52  }
    53  
    54  var (
    55  	errReplicationTooManyRules        = Errorf("Replication configuration allows a maximum of 1000 rules")
    56  	errReplicationNoRule              = Errorf("Replication configuration should have at least one rule")
    57  	errReplicationUniquePriority      = Errorf("Replication configuration has duplicate priority")
    58  	errReplicationDestinationMismatch = Errorf("The destination bucket must be same for all rules")
    59  	errRoleArnMissing                 = Errorf("Missing required parameter `Role` in ReplicationConfiguration")
    60  )
    61  
    62  // Config - replication configuration specified in
    63  // https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html
    64  type Config struct {
    65  	XMLName xml.Name `xml:"ReplicationConfiguration" json:"-"`
    66  	Rules   []Rule   `xml:"Rule" json:"Rules"`
    67  	// RoleArn is being reused for MinIO replication ARN
    68  	RoleArn string `xml:"Role" json:"Role"`
    69  }
    70  
    71  // Maximum 2MiB size per replication config.
    72  const maxReplicationConfigSize = 2 << 20
    73  
    74  // ParseConfig parses ReplicationConfiguration from xml
    75  func ParseConfig(reader io.Reader) (*Config, error) {
    76  	config := Config{}
    77  	if err := xml.NewDecoder(io.LimitReader(reader, maxReplicationConfigSize)).Decode(&config); err != nil {
    78  		return nil, err
    79  	}
    80  	return &config, nil
    81  }
    82  
    83  // Validate - validates the replication configuration
    84  func (c Config) Validate(bucket string, sameTarget bool) error {
    85  	// replication config can't have more than 1000 rules
    86  	if len(c.Rules) > 1000 {
    87  		return errReplicationTooManyRules
    88  	}
    89  	// replication config should have at least one rule
    90  	if len(c.Rules) == 0 {
    91  		return errReplicationNoRule
    92  	}
    93  	if c.RoleArn == "" {
    94  		return errRoleArnMissing
    95  	}
    96  	// Validate all the rules in the replication config
    97  	targetMap := make(map[string]struct{})
    98  	priorityMap := make(map[string]struct{})
    99  	for _, r := range c.Rules {
   100  		if len(targetMap) == 0 {
   101  			targetMap[r.Destination.Bucket] = struct{}{}
   102  		}
   103  		if _, ok := targetMap[r.Destination.Bucket]; !ok {
   104  			return errReplicationDestinationMismatch
   105  		}
   106  		if err := r.Validate(bucket, sameTarget); err != nil {
   107  			return err
   108  		}
   109  		if _, ok := priorityMap[strconv.Itoa(r.Priority)]; ok {
   110  			return errReplicationUniquePriority
   111  		}
   112  		priorityMap[strconv.Itoa(r.Priority)] = struct{}{}
   113  	}
   114  	return nil
   115  }
   116  
   117  // Type - replication type enum
   118  type Type int
   119  
   120  // Types of replication
   121  const (
   122  	ObjectReplicationType Type = 1 + iota
   123  	DeleteReplicationType
   124  	MetadataReplicationType
   125  	HealReplicationType
   126  )
   127  
   128  // ObjectOpts provides information to deduce whether replication
   129  // can be triggered on the resultant object.
   130  type ObjectOpts struct {
   131  	Name         string
   132  	UserTags     string
   133  	VersionID    string
   134  	IsLatest     bool
   135  	DeleteMarker bool
   136  	SSEC         bool
   137  	OpType       Type
   138  }
   139  
   140  // FilterActionableRules returns the rules actions that need to be executed
   141  // after evaluating prefix/tag filtering
   142  func (c Config) FilterActionableRules(obj ObjectOpts) []Rule {
   143  	if obj.Name == "" {
   144  		return nil
   145  	}
   146  	var rules []Rule
   147  	for _, rule := range c.Rules {
   148  		if rule.Status == Disabled {
   149  			continue
   150  		}
   151  		if !strings.HasPrefix(obj.Name, rule.Prefix()) {
   152  			continue
   153  		}
   154  		if rule.Filter.TestTags(strings.Split(obj.UserTags, "&")) {
   155  			rules = append(rules, rule)
   156  		}
   157  	}
   158  	sort.Slice(rules[:], func(i, j int) bool {
   159  		return rules[i].Priority > rules[j].Priority
   160  	})
   161  	return rules
   162  }
   163  
   164  // GetDestination returns destination bucket and storage class.
   165  func (c Config) GetDestination() Destination {
   166  	if len(c.Rules) > 0 {
   167  		return c.Rules[0].Destination
   168  	}
   169  	return Destination{}
   170  }
   171  
   172  // Replicate returns true if the object should be replicated.
   173  func (c Config) Replicate(obj ObjectOpts) bool {
   174  	if obj.SSEC {
   175  		return false
   176  	}
   177  	for _, rule := range c.FilterActionableRules(obj) {
   178  		if rule.Status == Disabled {
   179  			continue
   180  		}
   181  		if obj.OpType == DeleteReplicationType {
   182  			switch {
   183  			case obj.VersionID != "":
   184  				// // check MinIO extension for versioned deletes
   185  				return rule.DeleteReplication.Status == Enabled
   186  			default:
   187  				return rule.DeleteMarkerReplication.Status == Enabled
   188  			}
   189  		} else { // regular object/metadata replication
   190  			return true
   191  		}
   192  	}
   193  	return false
   194  }
   195  
   196  // HasActiveRules - returns whether replication policy has active rules
   197  // Optionally a prefix can be supplied.
   198  // If recursive is specified the function will also return true if any level below the
   199  // prefix has active rules. If no prefix is specified recursive is effectively true.
   200  func (c Config) HasActiveRules(prefix string, recursive bool) bool {
   201  	if len(c.Rules) == 0 {
   202  		return false
   203  	}
   204  	for _, rule := range c.Rules {
   205  		if rule.Status == Disabled {
   206  			continue
   207  		}
   208  		if len(prefix) > 0 && len(rule.Filter.Prefix) > 0 {
   209  			// incoming prefix must be in rule prefix
   210  			if !recursive && !strings.HasPrefix(prefix, rule.Filter.Prefix) {
   211  				continue
   212  			}
   213  			// If recursive, we can skip this rule if it doesn't match the tested prefix or level below prefix
   214  			// does not match
   215  			if recursive && !strings.HasPrefix(rule.Prefix(), prefix) && !strings.HasPrefix(prefix, rule.Prefix()) {
   216  				continue
   217  			}
   218  		}
   219  		return true
   220  	}
   221  	return false
   222  }