mirror of
https://github.com/minio/minio.git
synced 2025-01-26 22:23:15 -05:00
Replication: Enforce DeleteMarker disable setting (#11720)
This PR also enforces DeleteReplication disable setting
This commit is contained in:
parent
7fa3e4106b
commit
5e003549cc
@ -186,6 +186,7 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
|
|||||||
UserTags: oi.UserTags,
|
UserTags: oi.UserTags,
|
||||||
DeleteMarker: oi.DeleteMarker,
|
DeleteMarker: oi.DeleteMarker,
|
||||||
VersionID: dobj.VersionID,
|
VersionID: dobj.VersionID,
|
||||||
|
OpType: replication.DeleteReplicationType,
|
||||||
}
|
}
|
||||||
replicate = rcfg.Replicate(opts)
|
replicate = rcfg.Replicate(opts)
|
||||||
// when incoming delete is removal of a delete marker( a.k.a versioned delete),
|
// when incoming delete is removal of a delete marker( a.k.a versioned delete),
|
||||||
|
@ -84,7 +84,7 @@ func (d *Destination) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) (er
|
|||||||
switch dest.StorageClass {
|
switch dest.StorageClass {
|
||||||
case "STANDARD", "REDUCED_REDUNDANCY":
|
case "STANDARD", "REDUCED_REDUNDANCY":
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown storage class %v", dest.StorageClass)
|
return fmt.Errorf("unknown storage class %s", dest.StorageClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parsedDest.StorageClass = dest.StorageClass
|
parsedDest.StorageClass = dest.StorageClass
|
||||||
@ -107,7 +107,7 @@ func (d Destination) Validate(bucketName string) error {
|
|||||||
// parseDestination - parses string to Destination.
|
// parseDestination - parses string to Destination.
|
||||||
func parseDestination(s string) (Destination, error) {
|
func parseDestination(s string) (Destination, error) {
|
||||||
if !strings.HasPrefix(s, DestinationARNPrefix) {
|
if !strings.HasPrefix(s, DestinationARNPrefix) {
|
||||||
return Destination{}, Errorf("invalid destination '%v'", s)
|
return Destination{}, Errorf("invalid destination '%s'", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketName := strings.TrimPrefix(s, DestinationARNPrefix)
|
bucketName := strings.TrimPrefix(s, DestinationARNPrefix)
|
||||||
|
@ -114,6 +114,16 @@ func (c Config) Validate(bucket string, sameTarget bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type - replication type enum
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
// Types of replication
|
||||||
|
const (
|
||||||
|
ObjectReplicationType Type = 1 + iota
|
||||||
|
DeleteReplicationType
|
||||||
|
MetadataReplicationType
|
||||||
|
)
|
||||||
|
|
||||||
// ObjectOpts provides information to deduce whether replication
|
// ObjectOpts provides information to deduce whether replication
|
||||||
// can be triggered on the resultant object.
|
// can be triggered on the resultant object.
|
||||||
type ObjectOpts struct {
|
type ObjectOpts struct {
|
||||||
@ -123,6 +133,7 @@ type ObjectOpts struct {
|
|||||||
IsLatest bool
|
IsLatest bool
|
||||||
DeleteMarker bool
|
DeleteMarker bool
|
||||||
SSEC bool
|
SSEC bool
|
||||||
|
OpType Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterActionableRules returns the rules actions that need to be executed
|
// FilterActionableRules returns the rules actions that need to be executed
|
||||||
@ -159,24 +170,24 @@ func (c Config) GetDestination() Destination {
|
|||||||
|
|
||||||
// Replicate returns true if the object should be replicated.
|
// Replicate returns true if the object should be replicated.
|
||||||
func (c Config) Replicate(obj ObjectOpts) bool {
|
func (c Config) Replicate(obj ObjectOpts) bool {
|
||||||
|
if obj.SSEC {
|
||||||
|
return false
|
||||||
|
}
|
||||||
for _, rule := range c.FilterActionableRules(obj) {
|
for _, rule := range c.FilterActionableRules(obj) {
|
||||||
// check MinIO extension for versioned deletes
|
|
||||||
if !obj.DeleteMarker && obj.VersionID != "" && rule.DeleteReplication.Status == Disabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if obj.DeleteMarker && rule.DeleteMarkerReplication.Status == Disabled {
|
|
||||||
// Indicates whether MinIO will remove a delete marker. By default, delete markers
|
|
||||||
// are not replicated.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if obj.SSEC {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if rule.Status == Disabled {
|
if rule.Status == Disabled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return true
|
if obj.OpType == DeleteReplicationType {
|
||||||
|
switch {
|
||||||
|
case obj.VersionID != "":
|
||||||
|
// // check MinIO extension for versioned deletes
|
||||||
|
return rule.DeleteReplication.Status == Enabled
|
||||||
|
default:
|
||||||
|
return rule.DeleteMarkerReplication.Status == Enabled
|
||||||
|
}
|
||||||
|
} else { // regular object/metadata replication
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -198,8 +209,9 @@ func (c Config) HasActiveRules(prefix string, recursive bool) bool {
|
|||||||
if !recursive && !strings.HasPrefix(prefix, rule.Filter.Prefix) {
|
if !recursive && !strings.HasPrefix(prefix, rule.Filter.Prefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// If recursive, we can skip this rule if it doesn't match the tested prefix.
|
// If recursive, we can skip this rule if it doesn't match the tested prefix or level below prefix
|
||||||
if recursive && !strings.HasPrefix(rule.Filter.Prefix, prefix) {
|
// does not match
|
||||||
|
if recursive && !strings.HasPrefix(rule.Prefix(), prefix) && !strings.HasPrefix(prefix, rule.Prefix()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
305
pkg/bucket/replication/replication_test.go
Normal file
305
pkg/bucket/replication/replication_test.go
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
package replication
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseAndValidateReplicationConfig(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
inputConfig string
|
||||||
|
expectedParsingErr error
|
||||||
|
expectedValidationErr error
|
||||||
|
destBucket string
|
||||||
|
sameTarget bool
|
||||||
|
}{
|
||||||
|
{ //1 Invalid delete marker status in replication config
|
||||||
|
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>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: errInvalidDeleteMarkerReplicationStatus,
|
||||||
|
},
|
||||||
|
//2 Invalid delete replication status in replication config
|
||||||
|
{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>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: errDeleteReplicationMissing,
|
||||||
|
},
|
||||||
|
//3 valid replication config
|
||||||
|
{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>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: nil,
|
||||||
|
},
|
||||||
|
//4 missing role in config
|
||||||
|
{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>`,
|
||||||
|
// destination bucket in config different from bucket specified
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: errRoleArnMissing,
|
||||||
|
},
|
||||||
|
//5 replication destination in different rules not identical
|
||||||
|
{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><Priority>3</Priority><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket2</Bucket></Destination></Rule></ReplicationConfiguration>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: errReplicationDestinationMismatch,
|
||||||
|
},
|
||||||
|
//6 missing rule status in replication config
|
||||||
|
{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>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: errEmptyRuleStatus,
|
||||||
|
},
|
||||||
|
//7 invalid rule status in replication config
|
||||||
|
{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>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: errInvalidRuleStatus,
|
||||||
|
},
|
||||||
|
//8 invalid rule id exceeds length allowed in replication config
|
||||||
|
{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>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: errInvalidRuleID,
|
||||||
|
},
|
||||||
|
//9 invalid priority status in replication config
|
||||||
|
{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>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: errReplicationUniquePriority,
|
||||||
|
},
|
||||||
|
//10 no rule in replication config
|
||||||
|
{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role></ReplicationConfiguration>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: errReplicationNoRule,
|
||||||
|
},
|
||||||
|
//11 no destination in replication config
|
||||||
|
{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>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: Errorf("invalid destination '%v'", ""),
|
||||||
|
expectedValidationErr: nil,
|
||||||
|
},
|
||||||
|
//12 destination not matching ARN in replication config
|
||||||
|
{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>`,
|
||||||
|
destBucket: "destinationbucket",
|
||||||
|
sameTarget: false,
|
||||||
|
expectedParsingErr: fmt.Errorf("invalid destination '%v'", "destinationbucket2"),
|
||||||
|
expectedValidationErr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("Test %d", i+1), func(t *testing.T) {
|
||||||
|
cfg, err := ParseConfig(bytes.NewReader([]byte(tc.inputConfig)))
|
||||||
|
if err != nil && tc.expectedParsingErr != nil && err.Error() != tc.expectedParsingErr.Error() {
|
||||||
|
t.Fatalf("%d: Expected '%v' during parsing but got '%v'", i+1, tc.expectedParsingErr, err)
|
||||||
|
}
|
||||||
|
if err == nil && tc.expectedParsingErr != nil {
|
||||||
|
t.Fatalf("%d: Expected '%v' during parsing but got '%v'", i+1, tc.expectedParsingErr, err)
|
||||||
|
}
|
||||||
|
if tc.expectedParsingErr != nil {
|
||||||
|
// We already expect a parsing error,
|
||||||
|
// no need to continue this test.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = cfg.Validate(tc.destBucket, tc.sameTarget)
|
||||||
|
if err != tc.expectedValidationErr {
|
||||||
|
t.Fatalf("%d: Expected %v during parsing but got %v", i+1, tc.expectedValidationErr, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestReplicate(t *testing.T) {
|
||||||
|
cfgs := []Config{
|
||||||
|
{ //Config0 - Replication config has no filters, all replication enabled
|
||||||
|
Rules: []Rule{
|
||||||
|
{
|
||||||
|
Status: Enabled,
|
||||||
|
Priority: 3,
|
||||||
|
DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled},
|
||||||
|
DeleteReplication: DeleteReplication{Status: Enabled},
|
||||||
|
Filter: Filter{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //Config1 - Replication config has no filters, delete,delete-marker replication disabled
|
||||||
|
Rules: []Rule{
|
||||||
|
{
|
||||||
|
Status: Enabled,
|
||||||
|
Priority: 3,
|
||||||
|
DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled},
|
||||||
|
DeleteReplication: DeleteReplication{Status: Disabled},
|
||||||
|
Filter: Filter{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //Config2 - Replication config has filters and more than 1 matching rule, delete,delete-marker replication disabled
|
||||||
|
Rules: []Rule{
|
||||||
|
{
|
||||||
|
Status: Enabled,
|
||||||
|
Priority: 2,
|
||||||
|
DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled},
|
||||||
|
DeleteReplication: DeleteReplication{Status: Enabled},
|
||||||
|
Filter: Filter{Prefix: "xy", And: And{}, Tag: Tag{Key: "k1", Value: "v1"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Status: Enabled,
|
||||||
|
Priority: 1,
|
||||||
|
DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled},
|
||||||
|
DeleteReplication: DeleteReplication{Status: Disabled},
|
||||||
|
Filter: Filter{Prefix: "xyz"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //Config3 - Replication config has filters and no overlapping rules
|
||||||
|
Rules: []Rule{
|
||||||
|
{
|
||||||
|
Status: Enabled,
|
||||||
|
Priority: 2,
|
||||||
|
DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled},
|
||||||
|
DeleteReplication: DeleteReplication{Status: Enabled},
|
||||||
|
Filter: Filter{Prefix: "xy", And: And{}, Tag: Tag{Key: "k1", Value: "v1"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Status: Enabled,
|
||||||
|
Priority: 1,
|
||||||
|
DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled},
|
||||||
|
DeleteReplication: DeleteReplication{Status: Disabled},
|
||||||
|
Filter: Filter{Prefix: "abc"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
opts ObjectOpts
|
||||||
|
c Config
|
||||||
|
expectedResult bool
|
||||||
|
}{
|
||||||
|
// using config 1 - no filters, all replication enabled
|
||||||
|
{ObjectOpts{}, cfgs[0], false}, //1. invalid ObjectOpts missing object name
|
||||||
|
{ObjectOpts{Name: "c1test"}, cfgs[0], true}, //2. valid ObjectOpts passing empty Filter
|
||||||
|
{ObjectOpts{Name: "c1test", VersionID: "vid"}, cfgs[0], true}, //3. valid ObjectOpts passing empty Filter
|
||||||
|
|
||||||
|
{ObjectOpts{Name: "c1test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, //4. DeleteMarker version replication valid case - matches DeleteMarkerReplication status
|
||||||
|
{ObjectOpts{Name: "c1test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[0], true}, //5. permanent delete of version, matches DeleteReplication status - valid case
|
||||||
|
{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, //6. permanent delete of version, matches DeleteReplication status
|
||||||
|
{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], false}, //7. permanent delete of version, disqualified by SSE-C
|
||||||
|
{ObjectOpts{Name: "c1test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], false}, //8. setting DeleteMarker on SSE-C encrypted object, disqualified by SSE-C
|
||||||
|
{ObjectOpts{Name: "c1test", SSEC: true}, cfgs[0], false}, //9. replication of SSE-C encrypted object, disqualified
|
||||||
|
|
||||||
|
// using config 2 - no filters, only replication of object, metadata enabled
|
||||||
|
{ObjectOpts{Name: "c2test"}, cfgs[1], true}, //10. valid ObjectOpts passing empty Filter
|
||||||
|
{ObjectOpts{Name: "c2test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false}, //11. DeleteMarker version replication not allowed due to DeleteMarkerReplication status
|
||||||
|
{ObjectOpts{Name: "c2test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[1], false}, //12. permanent delete of version, disallowed by DeleteReplication status
|
||||||
|
{ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false}, //13. permanent delete of DeleteMarker version, disallowed by DeleteReplication status
|
||||||
|
{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
|
||||||
|
{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
|
||||||
|
{ObjectOpts{Name: "c2test", SSEC: true}, cfgs[1], false}, //16. replication of SSE-C encrypted object, disqualified by default
|
||||||
|
// using config 2 - has more than one rule with overlapping prefixes
|
||||||
|
{ObjectOpts{Name: "xy/c3test", UserTags: "k1=v1"}, cfgs[2], true}, //17. matches rule 1 for replication of content/metadata
|
||||||
|
{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1"}, cfgs[2], true}, //18. matches rule 1 for replication of content/metadata
|
||||||
|
{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[2], false}, //19. matches rule 1 - DeleteMarker replication disallowed by rule
|
||||||
|
{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
|
||||||
|
{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
|
||||||
|
{ObjectOpts{Name: "xyz/c3test"}, cfgs[2], true}, //22. matches rule 2 for replication of content/metadata
|
||||||
|
{ObjectOpts{Name: "xy/c3test", UserTags: "k1=v2"}, cfgs[2], false}, //23. does not match rule1 because tag value does not pass filter
|
||||||
|
{ObjectOpts{Name: "xyz/c3test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[2], true}, //24. matches rule 2 - DeleteMarker replication allowed by rule
|
||||||
|
{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
|
||||||
|
{ObjectOpts{Name: "xyz/c3test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[2], false}, //26. matches rule 1 - DeleteReplication disallowed by rule for permanent delete of version
|
||||||
|
{ObjectOpts{Name: "abc/c3test"}, cfgs[2], false}, //27. matches no rule because object prefix does not match
|
||||||
|
|
||||||
|
// using config 3 - has no overlapping rules
|
||||||
|
{ObjectOpts{Name: "xy/c4test", UserTags: "k1=v1"}, cfgs[3], true}, //28. matches rule 1 for replication of content/metadata
|
||||||
|
{ObjectOpts{Name: "xa/c4test", UserTags: "k1=v1"}, cfgs[3], false}, //29. no rule match object prefix not in rules
|
||||||
|
{ObjectOpts{Name: "xyz/c4test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], false}, //30. rule 1 not matched because of tags filter
|
||||||
|
{ObjectOpts{Name: "xyz/c4test", UserTags: "k1=v1", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], false}, //31. matches rule 1 - DeleteMarker replication disallowed by rule
|
||||||
|
{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
|
||||||
|
{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
|
||||||
|
{ObjectOpts{Name: "abc/c4test"}, cfgs[3], true}, //34. matches rule 2 for replication of content/metadata
|
||||||
|
{ObjectOpts{Name: "abc/c4test", UserTags: "k1=v2"}, cfgs[3], true}, //35. matches rule 2 for replication of content/metadata
|
||||||
|
{ObjectOpts{Name: "abc/c4test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], true}, //36. matches rule 2 - DeleteMarker replication allowed by rule
|
||||||
|
{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
|
||||||
|
{ObjectOpts{Name: "abc/c4test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[3], false}, //38. matches rule 2 - DeleteReplication disallowed by rule for permanent delete of version
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, testCase := range testCases {
|
||||||
|
result := testCase.c.Replicate(testCase.opts)
|
||||||
|
|
||||||
|
if result != testCase.expectedResult {
|
||||||
|
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasActiveRules(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
inputConfig string
|
||||||
|
prefix string
|
||||||
|
expectedNonRec bool
|
||||||
|
expectedRec bool
|
||||||
|
}{
|
||||||
|
|
||||||
|
// case 1 - only one rule which is in Disabled status
|
||||||
|
{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>`,
|
||||||
|
prefix: "miss/prefix",
|
||||||
|
expectedNonRec: false,
|
||||||
|
expectedRec: false,
|
||||||
|
},
|
||||||
|
// case 2 - only one rule which matches prefix filter
|
||||||
|
{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>`,
|
||||||
|
prefix: "key/prefix1",
|
||||||
|
expectedNonRec: true,
|
||||||
|
expectedRec: true,
|
||||||
|
},
|
||||||
|
// case 3 - empty prefix
|
||||||
|
{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>`,
|
||||||
|
prefix: "key-prefix",
|
||||||
|
expectedNonRec: true,
|
||||||
|
expectedRec: true,
|
||||||
|
},
|
||||||
|
// case 4 - has Filter based on prefix
|
||||||
|
{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>`,
|
||||||
|
prefix: "testdir/",
|
||||||
|
expectedNonRec: false,
|
||||||
|
expectedRec: true,
|
||||||
|
},
|
||||||
|
//case 5 - has filter with prefix and tags, here we are not matching on tags
|
||||||
|
{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>
|
||||||
|
<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>`,
|
||||||
|
prefix: "testdir/",
|
||||||
|
expectedNonRec: true,
|
||||||
|
expectedRec: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(fmt.Sprintf("Test_%d", i+1), func(t *testing.T) {
|
||||||
|
cfg, err := ParseConfig(bytes.NewReader([]byte(tc.inputConfig)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Got unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if got := cfg.HasActiveRules(tc.prefix, false); got != tc.expectedNonRec {
|
||||||
|
t.Fatalf("Expected result with recursive set to false: `%v`, got: `%v`", tc.expectedNonRec, got)
|
||||||
|
}
|
||||||
|
if got := cfg.HasActiveRules(tc.prefix, true); got != tc.expectedRec {
|
||||||
|
t.Fatalf("Expected result with recursive set to true: `%v`, got: `%v`", tc.expectedRec, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -108,7 +108,7 @@ var (
|
|||||||
errInvalidRuleStatus = Errorf("Status must be set to either Enabled or Disabled")
|
errInvalidRuleStatus = Errorf("Status must be set to either Enabled or Disabled")
|
||||||
errDeleteMarkerReplicationMissing = Errorf("DeleteMarkerReplication must be specified")
|
errDeleteMarkerReplicationMissing = Errorf("DeleteMarkerReplication must be specified")
|
||||||
errPriorityMissing = Errorf("Priority must be specified")
|
errPriorityMissing = Errorf("Priority must be specified")
|
||||||
errInvalidDeleteMarkerReplicationStatus = Errorf("Delete marker replication is currently not supported")
|
errInvalidDeleteMarkerReplicationStatus = Errorf("Delete marker replication status is invalid")
|
||||||
errDestinationSourceIdentical = Errorf("Destination bucket cannot be the same as the source bucket.")
|
errDestinationSourceIdentical = Errorf("Destination bucket cannot be the same as the source bucket.")
|
||||||
errDeleteReplicationMissing = Errorf("Delete replication must be specified")
|
errDeleteReplicationMissing = Errorf("Delete replication must be specified")
|
||||||
errInvalidDeleteReplicationStatus = Errorf("Delete replication is either enable|disable")
|
errInvalidDeleteReplicationStatus = Errorf("Delete replication is either enable|disable")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user