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 }