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 }