github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/bucket/replication/replication_test.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 replication 19 20 import ( 21 "bytes" 22 "fmt" 23 "testing" 24 ) 25 26 func TestParseAndValidateReplicationConfig(t *testing.T) { 27 testCases := []struct { 28 inputConfig string 29 expectedParsingErr error 30 expectedValidationErr error 31 destBucket string 32 sameTarget bool 33 }{ 34 { // 1 Invalid delete marker status in replication config 35 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>string</Status></DeleteMarkerReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 36 destBucket: "destinationbucket", 37 sameTarget: false, 38 expectedParsingErr: nil, 39 expectedValidationErr: errInvalidDeleteMarkerReplicationStatus, 40 }, 41 // 2 No delete replication status in replication config 42 { 43 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 44 destBucket: "destinationbucket", 45 sameTarget: false, 46 expectedParsingErr: nil, 47 expectedValidationErr: nil, 48 }, 49 // 3 valid replication config 50 { 51 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 52 destBucket: "destinationbucket", 53 sameTarget: false, 54 expectedParsingErr: nil, 55 expectedValidationErr: nil, 56 }, 57 // 4 missing role in config and destination ARN is in legacy format 58 { 59 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 60 // destination bucket in config different from bucket specified 61 destBucket: "destinationbucket", 62 sameTarget: false, 63 expectedParsingErr: nil, 64 expectedValidationErr: errDestinationArnMissing, 65 }, 66 // 5 replication destination in different rules not identical 67 { 68 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role></Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:minio:replication:::destinationbucket</Bucket></Destination></Rule><Rule><Status>Enabled</Status><Priority>3</Priority><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:minio:replication:::destinationbucket2</Bucket></Destination></Rule></ReplicationConfiguration>`, 69 destBucket: "destinationbucket", 70 sameTarget: false, 71 expectedParsingErr: nil, 72 expectedValidationErr: nil, 73 }, 74 // 6 missing rule status in replication config 75 { 76 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 77 destBucket: "destinationbucket", 78 sameTarget: false, 79 expectedParsingErr: nil, 80 expectedValidationErr: errEmptyRuleStatus, 81 }, 82 // 7 invalid rule status in replication config 83 { 84 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enssabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 85 destBucket: "destinationbucket", 86 sameTarget: false, 87 expectedParsingErr: nil, 88 expectedValidationErr: errInvalidRuleStatus, 89 }, 90 // 8 invalid rule id exceeds length allowed in replication config 91 { 92 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><ID>vsUVERgOc8zZYagLSzSa5lE8qeI6nh1lyLNS4R9W052yfecrhhepGboswSWMMNO8CPcXM4GM3nKyQ72EadlMzzZBFoYWKn7ju5GoE5w9c57a0piHR1vexpdd9FrMquiruvAJ0MTGVupm0EegMVxoIOdjx7VgZhGrmi2XDvpVEFT7WmYMA9fSK297XkTHWyECaNHBySJ1Qp4vwX8tPNauKpfHx4kzUpnKe1PZbptGMWbY5qTcwlNuMhVSmgFffShq</ID><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 93 destBucket: "destinationbucket", 94 sameTarget: false, 95 expectedParsingErr: nil, 96 expectedValidationErr: errInvalidRuleID, 97 }, 98 // 9 invalid priority status in replication config 99 { 100 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 101 destBucket: "destinationbucket", 102 sameTarget: false, 103 expectedParsingErr: nil, 104 expectedValidationErr: errReplicationUniquePriority, 105 }, 106 // 10 no rule in replication config 107 { 108 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role></ReplicationConfiguration>`, 109 destBucket: "destinationbucket", 110 sameTarget: false, 111 expectedParsingErr: nil, 112 expectedValidationErr: errReplicationNoRule, 113 }, 114 // 11 no destination in replication config 115 { 116 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination></Destination></Rule></ReplicationConfiguration>`, 117 destBucket: "destinationbucket", 118 sameTarget: false, 119 expectedParsingErr: Errorf("invalid destination '%v'", ""), 120 expectedValidationErr: nil, 121 }, 122 // 12 destination not matching ARN in replication config 123 { 124 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>destinationbucket2</Bucket></Destination></Rule></ReplicationConfiguration>`, 125 destBucket: "destinationbucket", 126 sameTarget: false, 127 expectedParsingErr: fmt.Errorf("invalid destination '%v'", "destinationbucket2"), 128 expectedValidationErr: nil, 129 }, 130 // 13 missing role in config and destination ARN has target ARN 131 { 132 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:minio:replication::8320b6d18f9032b4700f1f03b50d8d1853de8f22cab86931ee794e12f190852c:destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 133 // destination bucket in config different from bucket specified 134 destBucket: "destinationbucket", 135 sameTarget: false, 136 expectedParsingErr: nil, 137 expectedValidationErr: nil, 138 }, 139 // 14 role absent in config and destination ARN has target ARN in invalid format 140 { 141 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:xx:replication::8320b6d18f9032b4700f1f03b50d8d1853de8f22cab86931ee794e12f190852c:destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 142 // destination bucket in config different from bucket specified 143 destBucket: "destinationbucket", 144 sameTarget: false, 145 expectedParsingErr: fmt.Errorf("invalid destination '%v'", "arn:xx:replication::8320b6d18f9032b4700f1f03b50d8d1853de8f22cab86931ee794e12f190852c:destinationbucket"), 146 expectedValidationErr: nil, 147 }, 148 } 149 for i, tc := range testCases { 150 t.Run(fmt.Sprintf("Test %d", i+1), func(t *testing.T) { 151 cfg, err := ParseConfig(bytes.NewReader([]byte(tc.inputConfig))) 152 if err != nil && tc.expectedParsingErr != nil && err.Error() != tc.expectedParsingErr.Error() { 153 t.Fatalf("%d: Expected '%v' during parsing but got '%v'", i+1, tc.expectedParsingErr, err) 154 } 155 if err == nil && tc.expectedParsingErr != nil { 156 t.Fatalf("%d: Expected '%v' during parsing but got '%v'", i+1, tc.expectedParsingErr, err) 157 } 158 if tc.expectedParsingErr != nil { 159 // We already expect a parsing error, 160 // no need to continue this test. 161 return 162 } 163 err = cfg.Validate(tc.destBucket, tc.sameTarget) 164 if err != tc.expectedValidationErr { 165 t.Fatalf("%d: Expected %v during parsing but got %v", i+1, tc.expectedValidationErr, err) 166 } 167 }) 168 } 169 } 170 171 func TestReplicate(t *testing.T) { 172 cfgs := []Config{ 173 { // Config0 - Replication config has no filters, all replication enabled 174 Rules: []Rule{ 175 { 176 Status: Enabled, 177 Priority: 3, 178 DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, 179 DeleteReplication: DeleteReplication{Status: Enabled}, 180 Filter: Filter{}, 181 }, 182 }, 183 }, 184 { // Config1 - Replication config has no filters, delete,delete-marker replication disabled 185 Rules: []Rule{ 186 { 187 Status: Enabled, 188 Priority: 3, 189 DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled}, 190 DeleteReplication: DeleteReplication{Status: Disabled}, 191 Filter: Filter{}, 192 }, 193 }, 194 }, 195 { // Config2 - Replication config has filters and more than 1 matching rule, delete,delete-marker replication disabled 196 Rules: []Rule{ 197 { 198 Status: Enabled, 199 Priority: 2, 200 DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled}, 201 DeleteReplication: DeleteReplication{Status: Enabled}, 202 Filter: Filter{Prefix: "xy", And: And{}, Tag: Tag{Key: "k1", Value: "v1"}}, 203 }, 204 { 205 Status: Enabled, 206 Priority: 1, 207 DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, 208 DeleteReplication: DeleteReplication{Status: Disabled}, 209 Filter: Filter{Prefix: "xyz"}, 210 }, 211 }, 212 }, 213 { // Config3 - Replication config has filters and no overlapping rules 214 Rules: []Rule{ 215 { 216 Status: Enabled, 217 Priority: 2, 218 DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled}, 219 DeleteReplication: DeleteReplication{Status: Enabled}, 220 Filter: Filter{Prefix: "xy", And: And{}, Tag: Tag{Key: "k1", Value: "v1"}}, 221 }, 222 { 223 Status: Enabled, 224 Priority: 1, 225 DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, 226 DeleteReplication: DeleteReplication{Status: Disabled}, 227 Filter: Filter{Prefix: "abc"}, 228 }, 229 }, 230 }, 231 { // Config4 - Replication config has filters and SourceSelectionCriteria Disabled 232 Rules: []Rule{ 233 { 234 Status: Enabled, 235 Priority: 2, 236 DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, 237 DeleteReplication: DeleteReplication{Status: Enabled}, 238 SourceSelectionCriteria: SourceSelectionCriteria{ReplicaModifications: ReplicaModifications{Status: Disabled}}, 239 }, 240 }, 241 }, 242 } 243 testCases := []struct { 244 opts ObjectOpts 245 c Config 246 expectedResult bool 247 }{ 248 // using config 1 - no filters, all replication enabled 249 {ObjectOpts{}, cfgs[0], false}, // 1. invalid ObjectOpts missing object name 250 {ObjectOpts{Name: "c1test"}, cfgs[0], true}, // 2. valid ObjectOpts passing empty Filter 251 {ObjectOpts{Name: "c1test", VersionID: "vid"}, cfgs[0], true}, // 3. valid ObjectOpts passing empty Filter 252 253 {ObjectOpts{Name: "c1test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 4. DeleteMarker version replication valid case - matches DeleteMarkerReplication status 254 {ObjectOpts{Name: "c1test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[0], true}, // 5. permanent delete of version, matches DeleteReplication status - valid case 255 {ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 6. permanent delete of version, matches DeleteReplication status 256 {ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 7. permanent delete of version 257 {ObjectOpts{Name: "c1test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 8. setting DeleteMarker on SSE-C encrypted object 258 {ObjectOpts{Name: "c1test", SSEC: true}, cfgs[0], true}, // 9. replication of SSE-C encrypted object 259 260 // using config 2 - no filters, only replication of object, metadata enabled 261 {ObjectOpts{Name: "c2test"}, cfgs[1], true}, // 10. valid ObjectOpts passing empty Filter 262 {ObjectOpts{Name: "c2test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 11. DeleteMarker version replication not allowed due to DeleteMarkerReplication status 263 {ObjectOpts{Name: "c2test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[1], false}, // 12. permanent delete of version, disallowed by DeleteReplication status 264 {ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 13. permanent delete of DeleteMarker version, disallowed by DeleteReplication status 265 {ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 14. permanent delete of version, disqualified by SSE-C & DeleteReplication status 266 {ObjectOpts{Name: "c2test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 15. setting DeleteMarker on SSE-C encrypted object, disqualified by SSE-C & DeleteMarkerReplication status 267 {ObjectOpts{Name: "c2test", SSEC: true}, cfgs[1], true}, // 16. replication of SSE-C encrypted object 268 // using config 2 - has more than one rule with overlapping prefixes 269 {ObjectOpts{Name: "xy/c3test", UserTags: "k1=v1"}, cfgs[2], true}, // 17. matches rule 1 for replication of content/metadata 270 {ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1"}, cfgs[2], true}, // 18. matches rule 1 for replication of content/metadata 271 {ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[2], false}, // 19. matches rule 1 - DeleteMarker replication disallowed by rule 272 {ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1", DeleteMarker: true, VersionID: "vid", OpType: DeleteReplicationType}, cfgs[2], true}, // 20. matches rule 1 - DeleteReplication allowed by rule for permanent delete of DeleteMarker 273 {ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[2], true}, // 21. matches rule 1 - DeleteReplication allowed by rule for permanent delete of version 274 {ObjectOpts{Name: "xyz/c3test"}, cfgs[2], true}, // 22. matches rule 2 for replication of content/metadata 275 {ObjectOpts{Name: "xy/c3test", UserTags: "k1=v2"}, cfgs[2], false}, // 23. does not match rule1 because tag value does not pass filter 276 {ObjectOpts{Name: "xyz/c3test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[2], true}, // 24. matches rule 2 - DeleteMarker replication allowed by rule 277 {ObjectOpts{Name: "xyz/c3test", DeleteMarker: true, VersionID: "vid", OpType: DeleteReplicationType}, cfgs[2], false}, // 25. matches rule 2 - DeleteReplication disallowed by rule for permanent delete of DeleteMarker 278 {ObjectOpts{Name: "xyz/c3test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[2], false}, // 26. matches rule 1 - DeleteReplication disallowed by rule for permanent delete of version 279 {ObjectOpts{Name: "abc/c3test"}, cfgs[2], false}, // 27. matches no rule because object prefix does not match 280 281 // using config 3 - has no overlapping rules 282 {ObjectOpts{Name: "xy/c4test", UserTags: "k1=v1"}, cfgs[3], true}, // 28. matches rule 1 for replication of content/metadata 283 {ObjectOpts{Name: "xa/c4test", UserTags: "k1=v1"}, cfgs[3], false}, // 29. no rule match object prefix not in rules 284 {ObjectOpts{Name: "xyz/c4test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], false}, // 30. rule 1 not matched because of tags filter 285 {ObjectOpts{Name: "xyz/c4test", UserTags: "k1=v1", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], false}, // 31. matches rule 1 - DeleteMarker replication disallowed by rule 286 {ObjectOpts{Name: "xyz/c4test", UserTags: "k1=v1", DeleteMarker: true, VersionID: "vid", OpType: DeleteReplicationType}, cfgs[3], true}, // 32. matches rule 1 - DeleteReplication allowed by rule for permanent delete of DeleteMarker 287 {ObjectOpts{Name: "xyz/c4test", UserTags: "k1=v1", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[3], true}, // 33. matches rule 1 - DeleteReplication allowed by rule for permanent delete of version 288 {ObjectOpts{Name: "abc/c4test"}, cfgs[3], true}, // 34. matches rule 2 for replication of content/metadata 289 {ObjectOpts{Name: "abc/c4test", UserTags: "k1=v2"}, cfgs[3], true}, // 35. matches rule 2 for replication of content/metadata 290 {ObjectOpts{Name: "abc/c4test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], true}, // 36. matches rule 2 - DeleteMarker replication allowed by rule 291 {ObjectOpts{Name: "abc/c4test", DeleteMarker: true, VersionID: "vid", OpType: DeleteReplicationType}, cfgs[3], false}, // 37. matches rule 2 - DeleteReplication disallowed by rule for permanent delete of DeleteMarker 292 {ObjectOpts{Name: "abc/c4test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[3], false}, // 38. matches rule 2 - DeleteReplication disallowed by rule for permanent delete of version 293 // using config 4 - with replica modification sync disabled. 294 {ObjectOpts{Name: "xy/c5test", UserTags: "k1=v1", Replica: true}, cfgs[4], false}, // 39. replica syncing disabled, this object is a replica 295 {ObjectOpts{Name: "xa/c5test", UserTags: "k1=v1", Replica: false}, cfgs[4], true}, // 40. replica syncing disabled, this object is NOT a replica 296 } 297 298 for _, testCase := range testCases { 299 testCase := testCase 300 t.Run(testCase.opts.Name, func(t *testing.T) { 301 result := testCase.c.Replicate(testCase.opts) 302 if result != testCase.expectedResult { 303 t.Errorf("expected: %v, got: %v", testCase.expectedResult, result) 304 } 305 }) 306 } 307 } 308 309 func TestHasActiveRules(t *testing.T) { 310 testCases := []struct { 311 inputConfig string 312 prefix string 313 expectedNonRec bool 314 expectedRec bool 315 }{ 316 // case 1 - only one rule which is in Disabled status 317 { 318 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Disabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 319 prefix: "miss/prefix", 320 expectedNonRec: false, 321 expectedRec: false, 322 }, 323 // case 2 - only one rule which matches prefix filter 324 { 325 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Filter><Prefix>key/prefix</Prefix></Filter><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 326 prefix: "key/prefix1", 327 expectedNonRec: true, 328 expectedRec: true, 329 }, 330 // case 3 - empty prefix 331 { 332 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 333 prefix: "key-prefix", 334 expectedNonRec: true, 335 expectedRec: true, 336 }, 337 // case 4 - has Filter based on prefix 338 { 339 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Filter><Prefix>testdir/dir1/</Prefix></Filter><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 340 prefix: "testdir/", 341 expectedNonRec: false, 342 expectedRec: true, 343 }, 344 // case 5 - has filter with prefix and tags, here we are not matching on tags 345 { 346 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Filter> 347 <And><Prefix>key-prefix</Prefix><Tag><Key>key1</Key><Value>value1</Value></Tag><Tag><Key>key2</Key><Value>value2</Value></Tag></And></Filter><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 348 prefix: "testdir/", 349 expectedNonRec: true, 350 expectedRec: true, 351 }, 352 } 353 354 for i, tc := range testCases { 355 tc := tc 356 t.Run(fmt.Sprintf("Test_%d", i+1), func(t *testing.T) { 357 cfg, err := ParseConfig(bytes.NewReader([]byte(tc.inputConfig))) 358 if err != nil { 359 t.Fatalf("Got unexpected error: %v", err) 360 } 361 if got := cfg.HasActiveRules(tc.prefix, false); got != tc.expectedNonRec { 362 t.Fatalf("Expected result with recursive set to false: `%v`, got: `%v`", tc.expectedNonRec, got) 363 } 364 if got := cfg.HasActiveRules(tc.prefix, true); got != tc.expectedRec { 365 t.Fatalf("Expected result with recursive set to true: `%v`, got: `%v`", tc.expectedRec, got) 366 } 367 }) 368 369 } 370 } 371 372 func TestFilterActionableRules(t *testing.T) { 373 testCases := []struct { 374 inputConfig string 375 prefix string 376 ExpectedRules []Rule 377 }{ 378 // case 1 - only one rule 379 { 380 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>prefix</Prefix><Priority>1</Priority><Destination><Bucket>arn:minio:replication:xxx::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 381 prefix: "prefix", 382 ExpectedRules: []Rule{{Status: Enabled, Priority: 1, DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, DeleteReplication: DeleteReplication{Status: Disabled}, Destination: Destination{Bucket: "destinationbucket", ARN: "arn:minio:replication:xxx::destinationbucket"}}}, 383 }, 384 // case 2 - multiple rules for same target, overlapping rules with different priority 385 { 386 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>prefix</Prefix><Priority>3</Priority><Destination><Bucket>arn:minio:replication:xxx::destinationbucket</Bucket></Destination></Rule><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>prefix</Prefix><Priority>1</Priority><Destination><Bucket>arn:minio:replication:xxx::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 387 prefix: "prefix", 388 ExpectedRules: []Rule{ 389 {Status: Enabled, Priority: 3, DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, DeleteReplication: DeleteReplication{Status: Disabled}, Destination: Destination{Bucket: "destinationbucket", ARN: "arn:minio:replication:xxx::destinationbucket"}}, 390 {Status: Enabled, Priority: 1, DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, DeleteReplication: DeleteReplication{Status: Disabled}, Destination: Destination{Bucket: "destinationbucket", ARN: "arn:minio:replication:xxx::destinationbucket"}}, 391 }, 392 }, 393 // case 3 - multiple rules for different target, overlapping rules on a target 394 { 395 inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>prefix</Prefix><Priority>2</Priority><Destination><Bucket>arn:minio:replication:xxx::destinationbucket2</Bucket></Destination></Rule><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>prefix</Prefix><Priority>4</Priority><Destination><Bucket>arn:minio:replication:xxx::destinationbucket2</Bucket></Destination></Rule><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>prefix</Prefix><Priority>3</Priority><Destination><Bucket>arn:minio:replication:xxx::destinationbucket</Bucket></Destination></Rule><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>prefix</Prefix><Priority>1</Priority><Destination><Bucket>arn:minio:replication:xxx::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`, 396 prefix: "prefix", 397 ExpectedRules: []Rule{ 398 {Status: Enabled, Priority: 4, DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, DeleteReplication: DeleteReplication{Status: Disabled}, Destination: Destination{Bucket: "destinationbucket2", ARN: "arn:minio:replication:xxx::destinationbucket2"}}, 399 {Status: Enabled, Priority: 2, DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, DeleteReplication: DeleteReplication{Status: Disabled}, Destination: Destination{Bucket: "destinationbucket2", ARN: "arn:minio:replication:xxx::destinationbucket2"}}, 400 {Status: Enabled, Priority: 3, DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, DeleteReplication: DeleteReplication{Status: Disabled}, Destination: Destination{Bucket: "destinationbucket", ARN: "arn:minio:replication:xxx::destinationbucket"}}, 401 {Status: Enabled, Priority: 1, DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled}, DeleteReplication: DeleteReplication{Status: Disabled}, Destination: Destination{Bucket: "destinationbucket", ARN: "arn:minio:replication:xxx::destinationbucket"}}, 402 }, 403 }, 404 } 405 for _, tc := range testCases { 406 tc := tc 407 cfg, err := ParseConfig(bytes.NewReader([]byte(tc.inputConfig))) 408 if err != nil { 409 t.Fatalf("Got unexpected error: %v", err) 410 } 411 got := cfg.FilterActionableRules(ObjectOpts{Name: tc.prefix}) 412 if len(got) != len(tc.ExpectedRules) { 413 t.Fatalf("Expected matching number of actionable rules: `%v`, got: `%v`", tc.ExpectedRules, got) 414 } 415 for i := range got { 416 if got[i].Destination.ARN != tc.ExpectedRules[i].Destination.ARN || got[i].Priority != tc.ExpectedRules[i].Priority { 417 t.Fatalf("Expected order of filtered rules to be identical: `%v`, got: `%v`", tc.ExpectedRules, got) 418 } 419 } 420 } 421 }