gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

replication.go (22635B)


      1 /*
      2  * MinIO Client (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 	"bytes"
     21 	"encoding/xml"
     22 	"fmt"
     23 	"strconv"
     24 	"strings"
     25 	"time"
     26 	"unicode/utf8"
     27 
     28 	"github.com/rs/xid"
     29 )
     30 
     31 var errInvalidFilter = fmt.Errorf("invalid filter")
     32 
     33 // OptionType specifies operation to be performed on config
     34 type OptionType string
     35 
     36 const (
     37 	// AddOption specifies addition of rule to config
     38 	AddOption OptionType = "Add"
     39 	// SetOption specifies modification of existing rule to config
     40 	SetOption OptionType = "Set"
     41 
     42 	// RemoveOption specifies rule options are for removing a rule
     43 	RemoveOption OptionType = "Remove"
     44 	// ImportOption is for getting current config
     45 	ImportOption OptionType = "Import"
     46 )
     47 
     48 // Options represents options to set a replication configuration rule
     49 type Options struct {
     50 	Op                      OptionType
     51 	RoleArn                 string
     52 	ID                      string
     53 	Prefix                  string
     54 	RuleStatus              string
     55 	Priority                string
     56 	TagString               string
     57 	StorageClass            string
     58 	DestBucket              string
     59 	IsTagSet                bool
     60 	IsSCSet                 bool
     61 	ReplicateDeletes        string // replicate versioned deletes
     62 	ReplicateDeleteMarkers  string // replicate soft deletes
     63 	ReplicaSync             string // replicate replica metadata modifications
     64 	ExistingObjectReplicate string
     65 }
     66 
     67 // Tags returns a slice of tags for a rule
     68 func (opts Options) Tags() ([]Tag, error) {
     69 	var tagList []Tag
     70 	tagTokens := strings.Split(opts.TagString, "&")
     71 	for _, tok := range tagTokens {
     72 		if tok == "" {
     73 			break
     74 		}
     75 		kv := strings.SplitN(tok, "=", 2)
     76 		if len(kv) != 2 {
     77 			return []Tag{}, fmt.Errorf("tags should be entered as comma separated k=v pairs")
     78 		}
     79 		tagList = append(tagList, Tag{
     80 			Key:   kv[0],
     81 			Value: kv[1],
     82 		})
     83 	}
     84 	return tagList, nil
     85 }
     86 
     87 // Config - replication configuration specified in
     88 // https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html
     89 type Config struct {
     90 	XMLName xml.Name `xml:"ReplicationConfiguration" json:"-"`
     91 	Rules   []Rule   `xml:"Rule" json:"Rules"`
     92 	Role    string   `xml:"Role" json:"Role"`
     93 }
     94 
     95 // Empty returns true if config is not set
     96 func (c *Config) Empty() bool {
     97 	return len(c.Rules) == 0
     98 }
     99 
    100 // AddRule adds a new rule to existing replication config. If a rule exists with the
    101 // same ID, then the rule is replaced.
    102 func (c *Config) AddRule(opts Options) error {
    103 	priority, err := strconv.Atoi(opts.Priority)
    104 	if err != nil {
    105 		return err
    106 	}
    107 	var compatSw bool // true if RoleArn is used with new mc client and older minio version prior to multisite
    108 	if opts.RoleArn != "" {
    109 		tokens := strings.Split(opts.RoleArn, ":")
    110 		if len(tokens) != 6 {
    111 			return fmt.Errorf("invalid format for replication Role Arn: %v", opts.RoleArn)
    112 		}
    113 		switch {
    114 		case strings.HasPrefix(opts.RoleArn, "arn:minio:replication") && len(c.Rules) == 0:
    115 			c.Role = opts.RoleArn
    116 			compatSw = true
    117 		case strings.HasPrefix(opts.RoleArn, "arn:aws:iam"):
    118 			c.Role = opts.RoleArn
    119 		default:
    120 			return fmt.Errorf("RoleArn invalid for AWS replication configuration: %v", opts.RoleArn)
    121 		}
    122 	}
    123 
    124 	var status Status
    125 	// toggle rule status for edit option
    126 	switch opts.RuleStatus {
    127 	case "enable":
    128 		status = Enabled
    129 	case "disable":
    130 		status = Disabled
    131 	default:
    132 		return fmt.Errorf("rule state should be either [enable|disable]")
    133 	}
    134 
    135 	tags, err := opts.Tags()
    136 	if err != nil {
    137 		return err
    138 	}
    139 	andVal := And{
    140 		Tags: tags,
    141 	}
    142 	filter := Filter{Prefix: opts.Prefix}
    143 	// only a single tag is set.
    144 	if opts.Prefix == "" && len(tags) == 1 {
    145 		filter.Tag = tags[0]
    146 	}
    147 	// both prefix and tag are present
    148 	if len(andVal.Tags) > 1 || opts.Prefix != "" {
    149 		filter.And = andVal
    150 		filter.And.Prefix = opts.Prefix
    151 		filter.Prefix = ""
    152 		filter.Tag = Tag{}
    153 	}
    154 	if opts.ID == "" {
    155 		opts.ID = xid.New().String()
    156 	}
    157 
    158 	destBucket := opts.DestBucket
    159 	// ref https://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html
    160 	if btokens := strings.Split(destBucket, ":"); len(btokens) != 6 {
    161 		if len(btokens) == 1 && compatSw {
    162 			destBucket = fmt.Sprintf("arn:aws:s3:::%s", destBucket)
    163 		} else {
    164 			return fmt.Errorf("destination bucket needs to be in Arn format")
    165 		}
    166 	}
    167 	dmStatus := Disabled
    168 	if opts.ReplicateDeleteMarkers != "" {
    169 		switch opts.ReplicateDeleteMarkers {
    170 		case "enable":
    171 			dmStatus = Enabled
    172 		case "disable":
    173 			dmStatus = Disabled
    174 		default:
    175 			return fmt.Errorf("ReplicateDeleteMarkers should be either enable|disable")
    176 		}
    177 	}
    178 
    179 	vDeleteStatus := Disabled
    180 	if opts.ReplicateDeletes != "" {
    181 		switch opts.ReplicateDeletes {
    182 		case "enable":
    183 			vDeleteStatus = Enabled
    184 		case "disable":
    185 			vDeleteStatus = Disabled
    186 		default:
    187 			return fmt.Errorf("ReplicateDeletes should be either enable|disable")
    188 		}
    189 	}
    190 	var replicaSync Status
    191 	// replica sync is by default Enabled, unless specified.
    192 	switch opts.ReplicaSync {
    193 	case "enable", "":
    194 		replicaSync = Enabled
    195 	case "disable":
    196 		replicaSync = Disabled
    197 	default:
    198 		return fmt.Errorf("replica metadata sync should be either [enable|disable]")
    199 	}
    200 
    201 	var existingStatus Status
    202 	if opts.ExistingObjectReplicate != "" {
    203 		switch opts.ExistingObjectReplicate {
    204 		case "enable":
    205 			existingStatus = Enabled
    206 		case "disable", "":
    207 			existingStatus = Disabled
    208 		default:
    209 			return fmt.Errorf("existingObjectReplicate should be either enable|disable")
    210 		}
    211 	}
    212 	newRule := Rule{
    213 		ID:       opts.ID,
    214 		Priority: priority,
    215 		Status:   status,
    216 		Filter:   filter,
    217 		Destination: Destination{
    218 			Bucket:       destBucket,
    219 			StorageClass: opts.StorageClass,
    220 		},
    221 		DeleteMarkerReplication: DeleteMarkerReplication{Status: dmStatus},
    222 		DeleteReplication:       DeleteReplication{Status: vDeleteStatus},
    223 		// MinIO enables replica metadata syncing by default in the case of bi-directional replication to allow
    224 		// automatic failover as the expectation in this case is that replica and source should be identical.
    225 		// However AWS leaves this configurable https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-for-metadata-changes.html
    226 		SourceSelectionCriteria: SourceSelectionCriteria{
    227 			ReplicaModifications: ReplicaModifications{
    228 				Status: replicaSync,
    229 			},
    230 		},
    231 		// By default disable existing object replication unless selected
    232 		ExistingObjectReplication: ExistingObjectReplication{
    233 			Status: existingStatus,
    234 		},
    235 	}
    236 
    237 	// validate rule after overlaying priority for pre-existing rule being disabled.
    238 	if err := newRule.Validate(); err != nil {
    239 		return err
    240 	}
    241 	// if replication config uses RoleArn, migrate this to the destination element as target ARN for remote bucket for MinIO configuration
    242 	if c.Role != "" && !strings.HasPrefix(c.Role, "arn:aws:iam") && !compatSw {
    243 		for i := range c.Rules {
    244 			c.Rules[i].Destination.Bucket = c.Role
    245 		}
    246 		c.Role = ""
    247 	}
    248 
    249 	for _, rule := range c.Rules {
    250 		if rule.Priority == newRule.Priority {
    251 			return fmt.Errorf("priority must be unique. Replication configuration already has a rule with this priority")
    252 		}
    253 		if rule.ID == newRule.ID {
    254 			return fmt.Errorf("a rule exists with this ID")
    255 		}
    256 	}
    257 
    258 	c.Rules = append(c.Rules, newRule)
    259 	return nil
    260 }
    261 
    262 // EditRule modifies an existing rule in replication config
    263 func (c *Config) EditRule(opts Options) error {
    264 	if opts.ID == "" {
    265 		return fmt.Errorf("rule ID missing")
    266 	}
    267 	// if replication config uses RoleArn, migrate this to the destination element as target ARN for remote bucket for non AWS.
    268 	if c.Role != "" && !strings.HasPrefix(c.Role, "arn:aws:iam") && len(c.Rules) > 1 {
    269 		for i := range c.Rules {
    270 			c.Rules[i].Destination.Bucket = c.Role
    271 		}
    272 		c.Role = ""
    273 	}
    274 
    275 	rIdx := -1
    276 	var newRule Rule
    277 	for i, rule := range c.Rules {
    278 		if rule.ID == opts.ID {
    279 			rIdx = i
    280 			newRule = rule
    281 			break
    282 		}
    283 	}
    284 	if rIdx < 0 {
    285 		return fmt.Errorf("rule with ID %s not found in replication configuration", opts.ID)
    286 	}
    287 	prefixChg := opts.Prefix != newRule.Prefix()
    288 	if opts.IsTagSet || prefixChg {
    289 		prefix := newRule.Prefix()
    290 		if prefix != opts.Prefix {
    291 			prefix = opts.Prefix
    292 		}
    293 		tags := []Tag{newRule.Filter.Tag}
    294 		if len(newRule.Filter.And.Tags) != 0 {
    295 			tags = newRule.Filter.And.Tags
    296 		}
    297 		var err error
    298 		if opts.IsTagSet {
    299 			tags, err = opts.Tags()
    300 			if err != nil {
    301 				return err
    302 			}
    303 		}
    304 		andVal := And{
    305 			Tags: tags,
    306 		}
    307 
    308 		filter := Filter{Prefix: prefix}
    309 		// only a single tag is set.
    310 		if prefix == "" && len(tags) == 1 {
    311 			filter.Tag = tags[0]
    312 		}
    313 		// both prefix and tag are present
    314 		if len(andVal.Tags) > 1 || prefix != "" {
    315 			filter.And = andVal
    316 			filter.And.Prefix = prefix
    317 			filter.Prefix = ""
    318 			filter.Tag = Tag{}
    319 		}
    320 		newRule.Filter = filter
    321 	}
    322 
    323 	// toggle rule status for edit option
    324 	if opts.RuleStatus != "" {
    325 		switch opts.RuleStatus {
    326 		case "enable":
    327 			newRule.Status = Enabled
    328 		case "disable":
    329 			newRule.Status = Disabled
    330 		default:
    331 			return fmt.Errorf("rule state should be either [enable|disable]")
    332 		}
    333 	}
    334 	// set DeleteMarkerReplication rule status for edit option
    335 	if opts.ReplicateDeleteMarkers != "" {
    336 		switch opts.ReplicateDeleteMarkers {
    337 		case "enable":
    338 			newRule.DeleteMarkerReplication.Status = Enabled
    339 		case "disable":
    340 			newRule.DeleteMarkerReplication.Status = Disabled
    341 		default:
    342 			return fmt.Errorf("ReplicateDeleteMarkers state should be either [enable|disable]")
    343 		}
    344 	}
    345 
    346 	// set DeleteReplication rule status for edit option. This is a MinIO specific
    347 	// option to replicate versioned deletes
    348 	if opts.ReplicateDeletes != "" {
    349 		switch opts.ReplicateDeletes {
    350 		case "enable":
    351 			newRule.DeleteReplication.Status = Enabled
    352 		case "disable":
    353 			newRule.DeleteReplication.Status = Disabled
    354 		default:
    355 			return fmt.Errorf("ReplicateDeletes state should be either [enable|disable]")
    356 		}
    357 	}
    358 
    359 	if opts.ReplicaSync != "" {
    360 		switch opts.ReplicaSync {
    361 		case "enable", "":
    362 			newRule.SourceSelectionCriteria.ReplicaModifications.Status = Enabled
    363 		case "disable":
    364 			newRule.SourceSelectionCriteria.ReplicaModifications.Status = Disabled
    365 		default:
    366 			return fmt.Errorf("replica metadata sync should be either [enable|disable]")
    367 		}
    368 	}
    369 
    370 	if opts.ExistingObjectReplicate != "" {
    371 		switch opts.ExistingObjectReplicate {
    372 		case "enable":
    373 			newRule.ExistingObjectReplication.Status = Enabled
    374 		case "disable":
    375 			newRule.ExistingObjectReplication.Status = Disabled
    376 		default:
    377 			return fmt.Errorf("existingObjectsReplication state should be either [enable|disable]")
    378 		}
    379 	}
    380 	if opts.IsSCSet {
    381 		newRule.Destination.StorageClass = opts.StorageClass
    382 	}
    383 	if opts.Priority != "" {
    384 		priority, err := strconv.Atoi(opts.Priority)
    385 		if err != nil {
    386 			return err
    387 		}
    388 		newRule.Priority = priority
    389 	}
    390 	if opts.DestBucket != "" {
    391 		destBucket := opts.DestBucket
    392 		// ref https://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html
    393 		if btokens := strings.Split(opts.DestBucket, ":"); len(btokens) != 6 {
    394 			return fmt.Errorf("destination bucket needs to be in Arn format")
    395 		}
    396 		newRule.Destination.Bucket = destBucket
    397 	}
    398 	// validate rule
    399 	if err := newRule.Validate(); err != nil {
    400 		return err
    401 	}
    402 	// ensure priority and destination bucket restrictions are not violated
    403 	for idx, rule := range c.Rules {
    404 		if rule.Priority == newRule.Priority && rIdx != idx {
    405 			return fmt.Errorf("priority must be unique. Replication configuration already has a rule with this priority")
    406 		}
    407 		if rule.Destination.Bucket != newRule.Destination.Bucket && rule.ID == newRule.ID {
    408 			return fmt.Errorf("invalid destination bucket for this rule")
    409 		}
    410 	}
    411 
    412 	c.Rules[rIdx] = newRule
    413 	return nil
    414 }
    415 
    416 // RemoveRule removes a rule from replication config.
    417 func (c *Config) RemoveRule(opts Options) error {
    418 	var newRules []Rule
    419 	ruleFound := false
    420 	for _, rule := range c.Rules {
    421 		if rule.ID != opts.ID {
    422 			newRules = append(newRules, rule)
    423 			continue
    424 		}
    425 		ruleFound = true
    426 	}
    427 	if !ruleFound {
    428 		return fmt.Errorf("Rule with ID %s not found", opts.ID)
    429 	}
    430 	if len(newRules) == 0 {
    431 		return fmt.Errorf("replication configuration should have at least one rule")
    432 	}
    433 	c.Rules = newRules
    434 	return nil
    435 }
    436 
    437 // Rule - a rule for replication configuration.
    438 type Rule struct {
    439 	XMLName                   xml.Name                  `xml:"Rule" json:"-"`
    440 	ID                        string                    `xml:"ID,omitempty"`
    441 	Status                    Status                    `xml:"Status"`
    442 	Priority                  int                       `xml:"Priority"`
    443 	DeleteMarkerReplication   DeleteMarkerReplication   `xml:"DeleteMarkerReplication"`
    444 	DeleteReplication         DeleteReplication         `xml:"DeleteReplication"`
    445 	Destination               Destination               `xml:"Destination"`
    446 	Filter                    Filter                    `xml:"Filter" json:"Filter"`
    447 	SourceSelectionCriteria   SourceSelectionCriteria   `xml:"SourceSelectionCriteria" json:"SourceSelectionCriteria"`
    448 	ExistingObjectReplication ExistingObjectReplication `xml:"ExistingObjectReplication,omitempty" json:"ExistingObjectReplication,omitempty"`
    449 }
    450 
    451 // Validate validates the rule for correctness
    452 func (r Rule) Validate() error {
    453 	if err := r.validateID(); err != nil {
    454 		return err
    455 	}
    456 	if err := r.validateStatus(); err != nil {
    457 		return err
    458 	}
    459 	if err := r.validateFilter(); err != nil {
    460 		return err
    461 	}
    462 
    463 	if r.Priority < 0 && r.Status == Enabled {
    464 		return fmt.Errorf("priority must be set for the rule")
    465 	}
    466 
    467 	if err := r.validateStatus(); err != nil {
    468 		return err
    469 	}
    470 	return r.ExistingObjectReplication.Validate()
    471 }
    472 
    473 // validateID - checks if ID is valid or not.
    474 func (r Rule) validateID() error {
    475 	// cannot be longer than 255 characters
    476 	if len(r.ID) > 255 {
    477 		return fmt.Errorf("ID must be less than 255 characters")
    478 	}
    479 	return nil
    480 }
    481 
    482 // validateStatus - checks if status is valid or not.
    483 func (r Rule) validateStatus() error {
    484 	// Status can't be empty
    485 	if len(r.Status) == 0 {
    486 		return fmt.Errorf("status cannot be empty")
    487 	}
    488 
    489 	// Status must be one of Enabled or Disabled
    490 	if r.Status != Enabled && r.Status != Disabled {
    491 		return fmt.Errorf("status must be set to either Enabled or Disabled")
    492 	}
    493 	return nil
    494 }
    495 
    496 func (r Rule) validateFilter() error {
    497 	return r.Filter.Validate()
    498 }
    499 
    500 // Prefix - a rule can either have prefix under <filter></filter> or under
    501 // <filter><and></and></filter>. This method returns the prefix from the
    502 // location where it is available
    503 func (r Rule) Prefix() string {
    504 	if r.Filter.Prefix != "" {
    505 		return r.Filter.Prefix
    506 	}
    507 	return r.Filter.And.Prefix
    508 }
    509 
    510 // Tags - a rule can either have tag under <filter></filter> or under
    511 // <filter><and></and></filter>. This method returns all the tags from the
    512 // rule in the format tag1=value1&tag2=value2
    513 func (r Rule) Tags() string {
    514 	ts := []Tag{r.Filter.Tag}
    515 	if len(r.Filter.And.Tags) != 0 {
    516 		ts = r.Filter.And.Tags
    517 	}
    518 
    519 	var buf bytes.Buffer
    520 	for _, t := range ts {
    521 		if buf.Len() > 0 {
    522 			buf.WriteString("&")
    523 		}
    524 		buf.WriteString(t.String())
    525 	}
    526 	return buf.String()
    527 }
    528 
    529 // Filter - a filter for a replication configuration Rule.
    530 type Filter struct {
    531 	XMLName xml.Name `xml:"Filter" json:"-"`
    532 	Prefix  string   `json:"Prefix,omitempty"`
    533 	And     And      `xml:"And,omitempty" json:"And,omitempty"`
    534 	Tag     Tag      `xml:"Tag,omitempty" json:"Tag,omitempty"`
    535 }
    536 
    537 // Validate - validates the filter element
    538 func (f Filter) Validate() error {
    539 	// A Filter must have exactly one of Prefix, Tag, or And specified.
    540 	if !f.And.isEmpty() {
    541 		if f.Prefix != "" {
    542 			return errInvalidFilter
    543 		}
    544 		if !f.Tag.IsEmpty() {
    545 			return errInvalidFilter
    546 		}
    547 	}
    548 	if f.Prefix != "" {
    549 		if !f.Tag.IsEmpty() {
    550 			return errInvalidFilter
    551 		}
    552 	}
    553 	if !f.Tag.IsEmpty() {
    554 		if err := f.Tag.Validate(); err != nil {
    555 			return err
    556 		}
    557 	}
    558 	return nil
    559 }
    560 
    561 // Tag - a tag for a replication configuration Rule filter.
    562 type Tag struct {
    563 	XMLName xml.Name `json:"-"`
    564 	Key     string   `xml:"Key,omitempty" json:"Key,omitempty"`
    565 	Value   string   `xml:"Value,omitempty" json:"Value,omitempty"`
    566 }
    567 
    568 func (tag Tag) String() string {
    569 	if tag.IsEmpty() {
    570 		return ""
    571 	}
    572 	return tag.Key + "=" + tag.Value
    573 }
    574 
    575 // IsEmpty returns whether this tag is empty or not.
    576 func (tag Tag) IsEmpty() bool {
    577 	return tag.Key == ""
    578 }
    579 
    580 // Validate checks this tag.
    581 func (tag Tag) Validate() error {
    582 	if len(tag.Key) == 0 || utf8.RuneCountInString(tag.Key) > 128 {
    583 		return fmt.Errorf("invalid Tag Key")
    584 	}
    585 
    586 	if utf8.RuneCountInString(tag.Value) > 256 {
    587 		return fmt.Errorf("invalid Tag Value")
    588 	}
    589 	return nil
    590 }
    591 
    592 // Destination - destination in ReplicationConfiguration.
    593 type Destination struct {
    594 	XMLName      xml.Name `xml:"Destination" json:"-"`
    595 	Bucket       string   `xml:"Bucket" json:"Bucket"`
    596 	StorageClass string   `xml:"StorageClass,omitempty" json:"StorageClass,omitempty"`
    597 }
    598 
    599 // And - a tag to combine a prefix and multiple tags for replication configuration rule.
    600 type And struct {
    601 	XMLName xml.Name `xml:"And,omitempty" json:"-"`
    602 	Prefix  string   `xml:"Prefix,omitempty" json:"Prefix,omitempty"`
    603 	Tags    []Tag    `xml:"Tag,omitempty" json:"Tag,omitempty"`
    604 }
    605 
    606 // isEmpty returns true if Tags field is null
    607 func (a And) isEmpty() bool {
    608 	return len(a.Tags) == 0 && a.Prefix == ""
    609 }
    610 
    611 // Status represents Enabled/Disabled status
    612 type Status string
    613 
    614 // Supported status types
    615 const (
    616 	Enabled  Status = "Enabled"
    617 	Disabled Status = "Disabled"
    618 )
    619 
    620 // DeleteMarkerReplication - whether delete markers are replicated - https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html
    621 type DeleteMarkerReplication struct {
    622 	Status Status `xml:"Status" json:"Status"` // should be set to "Disabled" by default
    623 }
    624 
    625 // IsEmpty returns true if DeleteMarkerReplication is not set
    626 func (d DeleteMarkerReplication) IsEmpty() bool {
    627 	return len(d.Status) == 0
    628 }
    629 
    630 // DeleteReplication - whether versioned deletes are replicated - this
    631 // is a MinIO specific extension
    632 type DeleteReplication struct {
    633 	Status Status `xml:"Status" json:"Status"` // should be set to "Disabled" by default
    634 }
    635 
    636 // IsEmpty returns true if DeleteReplication is not set
    637 func (d DeleteReplication) IsEmpty() bool {
    638 	return len(d.Status) == 0
    639 }
    640 
    641 // ReplicaModifications specifies if replica modification sync is enabled
    642 type ReplicaModifications struct {
    643 	Status Status `xml:"Status" json:"Status"` // should be set to "Enabled" by default
    644 }
    645 
    646 // SourceSelectionCriteria - specifies additional source selection criteria in ReplicationConfiguration.
    647 type SourceSelectionCriteria struct {
    648 	ReplicaModifications ReplicaModifications `xml:"ReplicaModifications" json:"ReplicaModifications"`
    649 }
    650 
    651 // IsValid - checks whether SourceSelectionCriteria is valid or not.
    652 func (s SourceSelectionCriteria) IsValid() bool {
    653 	return s.ReplicaModifications.Status == Enabled || s.ReplicaModifications.Status == Disabled
    654 }
    655 
    656 // Validate source selection criteria
    657 func (s SourceSelectionCriteria) Validate() error {
    658 	if (s == SourceSelectionCriteria{}) {
    659 		return nil
    660 	}
    661 	if !s.IsValid() {
    662 		return fmt.Errorf("invalid ReplicaModification status")
    663 	}
    664 	return nil
    665 }
    666 
    667 // ExistingObjectReplication - whether existing object replication is enabled
    668 type ExistingObjectReplication struct {
    669 	Status Status `xml:"Status"` // should be set to "Disabled" by default
    670 }
    671 
    672 // IsEmpty returns true if DeleteMarkerReplication is not set
    673 func (e ExistingObjectReplication) IsEmpty() bool {
    674 	return len(e.Status) == 0
    675 }
    676 
    677 // Validate validates whether the status is disabled.
    678 func (e ExistingObjectReplication) Validate() error {
    679 	if e.IsEmpty() {
    680 		return nil
    681 	}
    682 	if e.Status != Disabled && e.Status != Enabled {
    683 		return fmt.Errorf("invalid ExistingObjectReplication status")
    684 	}
    685 	return nil
    686 }
    687 
    688 // TargetMetrics represents inline replication metrics
    689 // such as pending, failed and completed bytes in total for a bucket remote target
    690 type TargetMetrics struct {
    691 	// Pending size in bytes
    692 	PendingSize uint64 `json:"pendingReplicationSize"`
    693 	// Completed size in bytes
    694 	ReplicatedSize uint64 `json:"completedReplicationSize"`
    695 	// Total Replica size in bytes
    696 	ReplicaSize uint64 `json:"replicaSize"`
    697 	// Failed size in bytes
    698 	FailedSize uint64 `json:"failedReplicationSize"`
    699 	// Total number of pending operations including metadata updates
    700 	PendingCount uint64 `json:"pendingReplicationCount"`
    701 	// Total number of failed operations including metadata updates
    702 	FailedCount uint64 `json:"failedReplicationCount"`
    703 	// Bandwidth limit in bytes/sec for this target
    704 	BandWidthLimitInBytesPerSecond int64 `json:"limitInBits"`
    705 	// Current bandwidth used in bytes/sec for this target
    706 	CurrentBandwidthInBytesPerSecond float64 `json:"currentBandwidth"`
    707 }
    708 
    709 // Metrics represents inline replication metrics for a bucket.
    710 type Metrics struct {
    711 	Stats map[string]TargetMetrics
    712 	// Total Pending size in bytes across targets
    713 	PendingSize uint64 `json:"pendingReplicationSize"`
    714 	// Completed size in bytes  across targets
    715 	ReplicatedSize uint64 `json:"completedReplicationSize"`
    716 	// Total Replica size in bytes  across targets
    717 	ReplicaSize uint64 `json:"replicaSize"`
    718 	// Failed size in bytes  across targets
    719 	FailedSize uint64 `json:"failedReplicationSize"`
    720 	// Total number of pending operations including metadata updates across targets
    721 	PendingCount uint64 `json:"pendingReplicationCount"`
    722 	// Total number of failed operations including metadata updates across targets
    723 	FailedCount uint64 `json:"failedReplicationCount"`
    724 }
    725 
    726 // ResyncTargetsInfo provides replication target information to resync replicated data.
    727 type ResyncTargetsInfo struct {
    728 	Targets []ResyncTarget `json:"target,omitempty"`
    729 }
    730 
    731 // ResyncTarget provides the replica resources and resetID to initiate resync replication.
    732 type ResyncTarget struct {
    733 	Arn       string    `json:"arn"`
    734 	ResetID   string    `json:"resetid"`
    735 	StartTime time.Time `json:"startTime,omitempty"`
    736 	EndTime   time.Time `json:"endTime,omitempty"`
    737 	// Status of resync operation
    738 	ResyncStatus string `json:"resyncStatus,omitempty"`
    739 	// Completed size in bytes
    740 	ReplicatedSize int64 `json:"completedReplicationSize,omitempty"`
    741 	// Failed size in bytes
    742 	FailedSize int64 `json:"failedReplicationSize,omitempty"`
    743 	// Total number of failed operations
    744 	FailedCount int64 `json:"failedReplicationCount,omitempty"`
    745 	// Total number of failed operations
    746 	ReplicatedCount int64 `json:"replicationCount,omitempty"`
    747 	// Last bucket/object replicated.
    748 	Bucket string `json:"bucket,omitempty"`
    749 	Object string `json:"object,omitempty"`
    750 }