diff --git a/cmd/bucket-replication.go b/cmd/bucket-replication.go
index e5ea6463f..37010eae1 100644
--- a/cmd/bucket-replication.go
+++ b/cmd/bucket-replication.go
@@ -95,28 +95,28 @@ func mustReplicateWeb(ctx context.Context, r *http.Request, bucket, object strin
if permErr != ErrNone {
return
}
- return mustReplicater(ctx, bucket, object, meta, replStatus)
+ return mustReplicater(ctx, bucket, object, meta, replStatus, false)
}
// mustReplicate returns 2 booleans - true if object meets replication criteria and true if replication is to be done in
// a synchronous manner.
-func mustReplicate(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string) (replicate bool, sync bool) {
+func mustReplicate(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string, metadataOnly bool) (replicate bool, sync bool) {
if s3Err := isPutActionAllowed(ctx, getRequestAuthType(r), bucket, "", r, iampolicy.GetReplicationConfigurationAction); s3Err != ErrNone {
return
}
- return mustReplicater(ctx, bucket, object, meta, replStatus)
+ return mustReplicater(ctx, bucket, object, meta, replStatus, metadataOnly)
}
// mustReplicater returns 2 booleans - true if object meets replication criteria and true if replication is to be done in
// a synchronous manner.
-func mustReplicater(ctx context.Context, bucket, object string, meta map[string]string, replStatus string) (replicate bool, sync bool) {
+func mustReplicater(ctx context.Context, bucket, object string, meta map[string]string, replStatus string, metadataOnly bool) (replicate bool, sync bool) {
if globalIsGateway {
return replicate, sync
}
if rs, ok := meta[xhttp.AmzBucketReplicationStatus]; ok {
replStatus = rs
}
- if replication.StatusType(replStatus) == replication.Replica {
+ if replication.StatusType(replStatus) == replication.Replica && !metadataOnly {
return replicate, sync
}
cfg, err := getReplicationConfig(ctx, bucket)
@@ -124,8 +124,9 @@ func mustReplicater(ctx context.Context, bucket, object string, meta map[string]
return replicate, sync
}
opts := replication.ObjectOpts{
- Name: object,
- SSEC: crypto.SSEC.IsEncrypted(meta),
+ Name: object,
+ SSEC: crypto.SSEC.IsEncrypted(meta),
+ Replica: replication.StatusType(replStatus) == replication.Replica,
}
tagStr, ok := meta[xhttp.AmzObjectTagging]
if ok {
diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go
index 2c7354342..c4739ac03 100644
--- a/cmd/object-handlers.go
+++ b/cmd/object-handlers.go
@@ -1285,7 +1285,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
if rs := r.Header.Get(xhttp.AmzBucketReplicationStatus); rs != "" {
srcInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = rs
}
- if ok, _ := mustReplicate(ctx, r, dstBucket, dstObject, srcInfo.UserDefined, srcInfo.ReplicationStatus.String()); ok {
+ if ok, _ := mustReplicate(ctx, r, dstBucket, dstObject, srcInfo.UserDefined, srcInfo.ReplicationStatus.String(), srcInfo.metadataOnly); ok {
srcInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
}
// Store the preserved compression metadata.
@@ -1387,7 +1387,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
objInfo.ETag = getDecryptedETag(r.Header, objInfo, false)
response := generateCopyObjectResponse(objInfo.ETag, objInfo.ModTime)
encodedSuccessResponse := encodeResponse(response)
- if replicate, sync := mustReplicate(ctx, r, dstBucket, dstObject, objInfo.UserDefined, objInfo.ReplicationStatus.String()); replicate {
+ if replicate, sync := mustReplicate(ctx, r, dstBucket, dstObject, objInfo.UserDefined, objInfo.ReplicationStatus.String(), objInfo.metadataOnly); replicate {
scheduleReplication(ctx, objInfo.Clone(), objectAPI, sync, replication.ObjectReplicationType)
}
@@ -1634,7 +1634,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
return
}
- if ok, _ := mustReplicate(ctx, r, bucket, object, metadata, ""); ok {
+ if ok, _ := mustReplicate(ctx, r, bucket, object, metadata, "", false); ok {
metadata[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
}
if r.Header.Get(xhttp.AmzBucketReplicationStatus) == replication.Replica.String() {
@@ -1721,7 +1721,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
objInfo.ETag = objInfo.ETag + "-1"
}
}
- if replicate, sync := mustReplicate(ctx, r, bucket, object, metadata, ""); replicate {
+ if replicate, sync := mustReplicate(ctx, r, bucket, object, metadata, "", false); replicate {
scheduleReplication(ctx, objInfo.Clone(), objectAPI, sync, replication.ObjectReplicationType)
}
@@ -1960,7 +1960,7 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
return
}
- if ok, _ := mustReplicate(ctx, r, bucket, object, metadata, ""); ok {
+ if ok, _ := mustReplicate(ctx, r, bucket, object, metadata, "", false); ok {
metadata[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
}
@@ -2016,8 +2016,9 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
return
}
- if replicate, sync := mustReplicate(ctx, r, bucket, object, metadata, ""); replicate {
+ if replicate, sync := mustReplicate(ctx, r, bucket, object, metadata, "", false); replicate {
scheduleReplication(ctx, objInfo.Clone(), objectAPI, sync, replication.ObjectReplicationType)
+
}
}
@@ -2130,7 +2131,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
return
}
- if ok, _ := mustReplicate(ctx, r, bucket, object, metadata, ""); ok {
+ if ok, _ := mustReplicate(ctx, r, bucket, object, metadata, "", false); ok {
metadata[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
}
// We need to preserve the encryption headers set in EncryptRequest,
@@ -3120,7 +3121,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
}
setPutObjHeaders(w, objInfo, false)
- if replicate, sync := mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, objInfo.ReplicationStatus.String()); replicate {
+ if replicate, sync := mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, objInfo.ReplicationStatus.String(), false); replicate {
scheduleReplication(ctx, objInfo.Clone(), objectAPI, sync, replication.ObjectReplicationType)
}
@@ -3381,7 +3382,7 @@ func (api objectAPIHandlers) PutObjectLegalHoldHandler(w http.ResponseWriter, r
return
}
objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = strings.ToUpper(string(legalHold.Status))
- replicate, sync := mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, "")
+ replicate, sync := mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, objInfo.ReplicationStatus.String(), true)
if replicate {
objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
}
@@ -3560,7 +3561,7 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r
objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = ""
objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = ""
}
- replicate, sync := mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, "")
+ replicate, sync := mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, objInfo.ReplicationStatus.String(), true)
if replicate {
objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
}
@@ -3752,7 +3753,12 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h
return
}
- replicate, sync := mustReplicate(ctx, r, bucket, object, map[string]string{xhttp.AmzObjectTagging: tags.String()}, "")
+ objInfo, err := objAPI.GetObjectInfo(ctx, bucket, object, opts)
+ if err != nil {
+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
+ return
+ }
+ replicate, sync := mustReplicate(ctx, r, bucket, object, map[string]string{xhttp.AmzObjectTagging: tags.String()}, objInfo.ReplicationStatus.String(), true)
if replicate {
opts.UserDefined = make(map[string]string)
opts.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
@@ -3761,7 +3767,7 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h
tagsStr := tags.String()
// Put object tags
- objInfo, err := objAPI.PutObjectTags(ctx, bucket, object, tagsStr, opts)
+ objInfo, err = objAPI.PutObjectTags(ctx, bucket, object, tagsStr, opts)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -3829,7 +3835,7 @@ func (api objectAPIHandlers) DeleteObjectTaggingHandler(w http.ResponseWriter, r
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
- replicate, sync := mustReplicate(ctx, r, bucket, object, map[string]string{xhttp.AmzObjectTagging: oi.UserTags}, "")
+ replicate, sync := mustReplicate(ctx, r, bucket, object, map[string]string{xhttp.AmzObjectTagging: oi.UserTags}, oi.ReplicationStatus.String(), true)
if replicate {
opts.UserDefined = make(map[string]string)
opts.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
diff --git a/docs/bucket/replication/README.md b/docs/bucket/replication/README.md
index dc28dda70..a0f47f0c0 100644
--- a/docs/bucket/replication/README.md
+++ b/docs/bucket/replication/README.md
@@ -132,6 +132,11 @@ The replication configuration can now be added to the source bucket by applying
"Destination": {
"Bucket": "arn:aws:s3:::destbucket",
"StorageClass": "STANDARD"
+ },
+ "SourceSelectionCriteria": {
+ "ReplicaModifications": {
+ "Status": "Enabled"
+ }
}
}
]
@@ -150,6 +155,11 @@ To perform bi-directional replication, repeat the above process on the target si

+## Replica Modification sync
+If bi-directional replication is set up between two clusters, any metadata update on the REPLICA object is by default reflected back in the source object when `ReplicaModifications` status in the `SourceSelectionCriteria` is `Enabled`. In MinIO, this is enabled by default. If a metadata update is performed on the "REPLICA" object, its `X-Amz-Replication-Status` will change from `PENDING` to `COMPLETE` or `FAILED`, and the source object version will show `X-Amz-Replication-Status` of `REPLICA` once the replication operation is complete.
+
+The replication configuration in use on a bucket can be viewed using the `mc replicate export alias/bucket` command.
+
## MinIO Extension
### Replicating Deletes
diff --git a/pkg/bucket/replication/replication.go b/pkg/bucket/replication/replication.go
index db0e4db19..1f1187221 100644
--- a/pkg/bucket/replication/replication.go
+++ b/pkg/bucket/replication/replication.go
@@ -58,6 +58,7 @@ var (
errReplicationUniquePriority = Errorf("Replication configuration has duplicate priority")
errReplicationDestinationMismatch = Errorf("The destination bucket must be same for all rules")
errRoleArnMissing = Errorf("Missing required parameter `Role` in ReplicationConfiguration")
+ errInvalidSourceSelectionCriteria = Errorf("Invalid ReplicaModification status")
)
// Config - replication configuration specified in
@@ -78,6 +79,16 @@ func ParseConfig(reader io.Reader) (*Config, error) {
if err := xml.NewDecoder(io.LimitReader(reader, maxReplicationConfigSize)).Decode(&config); err != nil {
return nil, err
}
+ // By default, set replica modification to enabled if unset.
+ for i := range config.Rules {
+ if len(config.Rules[i].SourceSelectionCriteria.ReplicaModifications.Status) == 0 {
+ config.Rules[i].SourceSelectionCriteria = SourceSelectionCriteria{
+ ReplicaModifications: ReplicaModifications{
+ Status: Enabled,
+ },
+ }
+ }
+ }
return &config, nil
}
@@ -136,6 +147,7 @@ type ObjectOpts struct {
DeleteMarker bool
SSEC bool
OpType Type
+ Replica bool
}
// FilterActionableRules returns the rules actions that need to be executed
@@ -187,9 +199,8 @@ func (c Config) Replicate(obj ObjectOpts) bool {
default:
return rule.DeleteMarkerReplication.Status == Enabled
}
- } else { // regular object/metadata replication
- return true
- }
+ } // regular object/metadata replication
+ return rule.MetadataReplicate(obj)
}
return false
}
diff --git a/pkg/bucket/replication/replication_test.go b/pkg/bucket/replication/replication_test.go
index 0593dd70c..13a79e747 100644
--- a/pkg/bucket/replication/replication_test.go
+++ b/pkg/bucket/replication/replication_test.go
@@ -140,7 +140,7 @@ func TestParseAndValidateReplicationConfig(t *testing.T) {
}
func TestReplicate(t *testing.T) {
cfgs := []Config{
- { //Config0 - Replication config has no filters, all replication enabled
+ { // Config0 - Replication config has no filters, all replication enabled
Rules: []Rule{
{
Status: Enabled,
@@ -151,7 +151,7 @@ func TestReplicate(t *testing.T) {
},
},
},
- { //Config1 - Replication config has no filters, delete,delete-marker replication disabled
+ { // Config1 - Replication config has no filters, delete,delete-marker replication disabled
Rules: []Rule{
{
Status: Enabled,
@@ -162,7 +162,7 @@ func TestReplicate(t *testing.T) {
},
},
},
- { //Config2 - Replication config has filters and more than 1 matching rule, delete,delete-marker replication disabled
+ { // Config2 - Replication config has filters and more than 1 matching rule, delete,delete-marker replication disabled
Rules: []Rule{
{
Status: Enabled,
@@ -180,7 +180,7 @@ func TestReplicate(t *testing.T) {
},
},
},
- { //Config3 - Replication config has filters and no overlapping rules
+ { // Config3 - Replication config has filters and no overlapping rules
Rules: []Rule{
{
Status: Enabled,
@@ -198,6 +198,17 @@ func TestReplicate(t *testing.T) {
},
},
},
+ { // Config4 - Replication config has filters and SourceSelectionCriteria Disabled
+ Rules: []Rule{
+ {
+ Status: Enabled,
+ Priority: 2,
+ DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled},
+ DeleteReplication: DeleteReplication{Status: Enabled},
+ SourceSelectionCriteria: SourceSelectionCriteria{ReplicaModifications: ReplicaModifications{Status: Disabled}},
+ },
+ },
+ },
}
testCases := []struct {
opts ObjectOpts
@@ -249,7 +260,9 @@ func TestReplicate(t *testing.T) {
{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
-
+ // using config 4 - with replica modification sync disabled.
+ {ObjectOpts{Name: "xy/c5test", UserTags: "k1=v1", Replica: true}, cfgs[4], false}, //39. replica syncing disabled, this object is a replica
+ {ObjectOpts{Name: "xa/c5test", UserTags: "k1=v1", Replica: false}, cfgs[4], true}, //40. replica syncing disabled, this object is NOT a replica
}
for i, testCase := range testCases {
diff --git a/pkg/bucket/replication/rule.go b/pkg/bucket/replication/rule.go
index 97432dd3b..b272fdd48 100644
--- a/pkg/bucket/replication/rule.go
+++ b/pkg/bucket/replication/rule.go
@@ -98,9 +98,10 @@ type Rule struct {
Priority int `xml:"Priority" json:"Priority"`
DeleteMarkerReplication DeleteMarkerReplication `xml:"DeleteMarkerReplication" json:"DeleteMarkerReplication"`
// MinIO extension to replicate versioned deletes
- DeleteReplication DeleteReplication `xml:"DeleteReplication" json:"DeleteReplication"`
- Destination Destination `xml:"Destination" json:"Destination"`
- Filter Filter `xml:"Filter" json:"Filter"`
+ DeleteReplication DeleteReplication `xml:"DeleteReplication" json:"DeleteReplication"`
+ Destination Destination `xml:"Destination" json:"Destination"`
+ SourceSelectionCriteria SourceSelectionCriteria `xml:"SourceSelectionCriteria" json:"SourceSelectionCriteria"`
+ Filter Filter `xml:"Filter" json:"Filter"`
}
var (
@@ -192,6 +193,10 @@ func (r Rule) Validate(bucket string, sameTarget bool) error {
if err := r.DeleteReplication.Validate(); err != nil {
return err
}
+ if err := r.SourceSelectionCriteria.Validate(); err != nil {
+ return err
+ }
+
if r.Priority < 0 {
return errPriorityMissing
}
@@ -200,3 +205,12 @@ func (r Rule) Validate(bucket string, sameTarget bool) error {
}
return nil
}
+
+// MetadataReplicate returns true if object is not a replica or in the case of replicas,
+// replica modification sync is enabled.
+func (r Rule) MetadataReplicate(obj ObjectOpts) bool {
+ if !obj.Replica {
+ return true
+ }
+ return obj.Replica && r.SourceSelectionCriteria.ReplicaModifications.Status == Enabled
+}
diff --git a/pkg/bucket/replication/rule_test.go b/pkg/bucket/replication/rule_test.go
new file mode 100644
index 000000000..c390e1ed6
--- /dev/null
+++ b/pkg/bucket/replication/rule_test.go
@@ -0,0 +1,68 @@
+// Copyright (c) 2015-2021 MinIO, Inc.
+//
+// This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package replication
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+)
+
+func TestMetadataReplicate(t *testing.T) {
+ testCases := []struct {
+ inputConfig string
+ opts ObjectOpts
+ expectedResult bool
+ }{
+ // case 1 - rule with replica modification enabled; not a replica
+ {inputConfig: `arn:aws:iam::AcctID:role/role-nameEnabledDisabledDisabledkey-prefixarn:aws:s3:::destinationbucketEnabled`,
+ opts: ObjectOpts{Name: "c1test", DeleteMarker: false, OpType: ObjectReplicationType, Replica: false}, //1. Replica mod sync enabled; not a replica
+ expectedResult: true,
+ },
+ // case 2 - rule with replica modification disabled; a replica
+ {inputConfig: `arn:aws:iam::AcctID:role/role-nameEnabledDisabledDisabledkey-prefixarn:aws:s3:::destinationbucketDisabled`,
+ opts: ObjectOpts{Name: "c2test", DeleteMarker: false, OpType: ObjectReplicationType, Replica: true}, //1. Replica mod sync enabled; a replica
+ expectedResult: false,
+ },
+ // case 3 - rule with replica modification disabled; not a replica
+ {inputConfig: `arn:aws:iam::AcctID:role/role-nameEnabledDisabledDisabledkey-prefixarn:aws:s3:::destinationbucketDisabled`,
+ opts: ObjectOpts{Name: "c2test", DeleteMarker: false, OpType: ObjectReplicationType, Replica: false}, //1. Replica mod sync disabled; not a replica
+ expectedResult: true,
+ },
+
+ // case 4 - rule with replica modification enabled; a replica
+ {inputConfig: `arn:aws:iam::AcctID:role/role-nameEnabledDisabledDisabledkey-prefixarn:aws:s3:::destinationbucketEnabled`,
+ opts: ObjectOpts{Name: "c2test", DeleteMarker: false, OpType: MetadataReplicationType, Replica: true}, //1. Replica mod sync enabled; a replica
+ expectedResult: 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.Rules[0].MetadataReplicate(tc.opts); got != tc.expectedResult {
+ t.Fatalf("Expected result with recursive set to false: `%v`, got: `%v`", tc.expectedResult, got)
+ }
+ })
+
+ }
+}
diff --git a/pkg/bucket/replication/sourceselectioncriteria.go b/pkg/bucket/replication/sourceselectioncriteria.go
new file mode 100644
index 000000000..19768f52b
--- /dev/null
+++ b/pkg/bucket/replication/sourceselectioncriteria.go
@@ -0,0 +1,76 @@
+// Copyright (c) 2015-2021 MinIO, Inc.
+//
+// This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package replication
+
+import (
+ "encoding/xml"
+)
+
+// ReplicaModifications specifies if replica modification sync is enabled
+type ReplicaModifications struct {
+ Status Status `xml:"Status" json:"Status"`
+}
+
+// SourceSelectionCriteria - specifies additional source selection criteria in ReplicationConfiguration.
+type SourceSelectionCriteria struct {
+ ReplicaModifications ReplicaModifications `xml:"ReplicaModifications" json:"ReplicaModifications"`
+}
+
+// IsValid - checks whether SourceSelectionCriteria is valid or not.
+func (s SourceSelectionCriteria) IsValid() bool {
+ return s.ReplicaModifications.Status == Enabled || s.ReplicaModifications.Status == Disabled
+}
+
+// Validate source selection criteria
+func (s SourceSelectionCriteria) Validate() error {
+ if (s == SourceSelectionCriteria{}) {
+ return nil
+ }
+ if !s.IsValid() {
+ return errInvalidSourceSelectionCriteria
+ }
+ return nil
+}
+
+// UnmarshalXML - decodes XML data.
+func (s *SourceSelectionCriteria) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) (err error) {
+ // Make subtype to avoid recursive UnmarshalXML().
+ type sourceSelectionCriteria SourceSelectionCriteria
+ ssc := sourceSelectionCriteria{}
+ if err := dec.DecodeElement(&ssc, &start); err != nil {
+ return err
+ }
+ if len(ssc.ReplicaModifications.Status) == 0 {
+ ssc.ReplicaModifications.Status = Enabled
+ }
+ *s = SourceSelectionCriteria(ssc)
+ return nil
+}
+
+// MarshalXML - encodes to XML data.
+func (s SourceSelectionCriteria) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ if s.IsValid() {
+ if err := e.EncodeElement(s.ReplicaModifications, xml.StartElement{Name: xml.Name{Local: "ReplicaModifications"}}); err != nil {
+ return err
+ }
+ }
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}