github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/bucket-replication-utils.go (about) 1 // Copyright (c) 2015-2023 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "bytes" 22 "context" 23 "fmt" 24 "net/http" 25 "net/url" 26 "regexp" 27 "strconv" 28 "strings" 29 "sync" 30 "time" 31 32 "github.com/minio/madmin-go/v3" 33 "github.com/minio/minio/internal/bucket/replication" 34 "github.com/minio/minio/internal/crypto" 35 xhttp "github.com/minio/minio/internal/http" 36 ) 37 38 //go:generate msgp -file=$GOFILE 39 40 // replicatedTargetInfo struct represents replication info on a target 41 type replicatedTargetInfo struct { 42 Arn string 43 Size int64 44 Duration time.Duration 45 ReplicationAction replicationAction // full or metadata only 46 OpType replication.Type // whether incoming replication, existing object, healing etc.. 47 ReplicationStatus replication.StatusType 48 PrevReplicationStatus replication.StatusType 49 VersionPurgeStatus VersionPurgeStatusType 50 ResyncTimestamp string 51 ReplicationResynced bool // true only if resync attempted for this target 52 endpoint string 53 secure bool 54 Err error // replication error if any 55 } 56 57 // Empty returns true for a target if arn is empty 58 func (rt replicatedTargetInfo) Empty() bool { 59 return rt.Arn == "" 60 } 61 62 type replicatedInfos struct { 63 ReplicationTimeStamp time.Time 64 Targets []replicatedTargetInfo 65 } 66 67 func (ri replicatedInfos) CompletedSize() (sz int64) { 68 for _, t := range ri.Targets { 69 if t.Empty() { 70 continue 71 } 72 if t.ReplicationStatus == replication.Completed && t.PrevReplicationStatus != replication.Completed { 73 sz += t.Size 74 } 75 } 76 return sz 77 } 78 79 // ReplicationAttempted returns true if replication was attempted on any of the targets for the object version 80 // queued 81 func (ri replicatedInfos) ReplicationResynced() bool { 82 for _, t := range ri.Targets { 83 if t.Empty() || !t.ReplicationResynced { 84 continue 85 } 86 return true 87 } 88 return false 89 } 90 91 func (ri replicatedInfos) ReplicationStatusInternal() string { 92 b := new(bytes.Buffer) 93 for _, t := range ri.Targets { 94 if t.Empty() { 95 continue 96 } 97 fmt.Fprintf(b, "%s=%s;", t.Arn, t.ReplicationStatus.String()) 98 } 99 return b.String() 100 } 101 102 func (ri replicatedInfos) ReplicationStatus() replication.StatusType { 103 if len(ri.Targets) == 0 { 104 return replication.StatusType("") 105 } 106 completed := 0 107 for _, v := range ri.Targets { 108 switch v.ReplicationStatus { 109 case replication.Failed: 110 return replication.Failed 111 case replication.Completed: 112 completed++ 113 } 114 } 115 if completed == len(ri.Targets) { 116 return replication.Completed 117 } 118 return replication.Pending 119 } 120 121 func (ri replicatedInfos) VersionPurgeStatus() VersionPurgeStatusType { 122 if len(ri.Targets) == 0 { 123 return VersionPurgeStatusType("") 124 } 125 completed := 0 126 for _, v := range ri.Targets { 127 switch v.VersionPurgeStatus { 128 case Failed: 129 return Failed 130 case Complete: 131 completed++ 132 } 133 } 134 if completed == len(ri.Targets) { 135 return Complete 136 } 137 return Pending 138 } 139 140 func (ri replicatedInfos) VersionPurgeStatusInternal() string { 141 b := new(bytes.Buffer) 142 for _, t := range ri.Targets { 143 if t.Empty() { 144 continue 145 } 146 if t.VersionPurgeStatus.Empty() { 147 continue 148 } 149 fmt.Fprintf(b, "%s=%s;", t.Arn, t.VersionPurgeStatus) 150 } 151 return b.String() 152 } 153 154 func (ri replicatedInfos) Action() replicationAction { 155 for _, t := range ri.Targets { 156 if t.Empty() { 157 continue 158 } 159 // rely on replication action from target that actually performed replication now. 160 if t.PrevReplicationStatus != replication.Completed { 161 return t.ReplicationAction 162 } 163 } 164 return replicateNone 165 } 166 167 var replStatusRegex = regexp.MustCompile(`([^=].*?)=([^,].*?);`) 168 169 // TargetReplicationStatus - returns replication status of a target 170 func (ri ReplicateObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) { 171 repStatMatches := replStatusRegex.FindAllStringSubmatch(ri.ReplicationStatusInternal, -1) 172 for _, repStatMatch := range repStatMatches { 173 if len(repStatMatch) != 3 { 174 return 175 } 176 if repStatMatch[1] == arn { 177 return replication.StatusType(repStatMatch[2]) 178 } 179 } 180 return 181 } 182 183 // TargetReplicationStatus - returns replication status of a target 184 func (o ObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) { 185 repStatMatches := replStatusRegex.FindAllStringSubmatch(o.ReplicationStatusInternal, -1) 186 for _, repStatMatch := range repStatMatches { 187 if len(repStatMatch) != 3 { 188 return 189 } 190 if repStatMatch[1] == arn { 191 return replication.StatusType(repStatMatch[2]) 192 } 193 } 194 return 195 } 196 197 type replicateTargetDecision struct { 198 Replicate bool // Replicate to this target 199 Synchronous bool // Synchronous replication configured. 200 Arn string // ARN of replication target 201 ID string 202 } 203 204 func (t *replicateTargetDecision) String() string { 205 return fmt.Sprintf("%t;%t;%s;%s", t.Replicate, t.Synchronous, t.Arn, t.ID) 206 } 207 208 func newReplicateTargetDecision(arn string, replicate bool, sync bool) replicateTargetDecision { 209 d := replicateTargetDecision{ 210 Replicate: replicate, 211 Synchronous: sync, 212 Arn: arn, 213 } 214 return d 215 } 216 217 // ReplicateDecision represents replication decision for each target 218 type ReplicateDecision struct { 219 targetsMap map[string]replicateTargetDecision 220 } 221 222 // ReplicateAny returns true if at least one target qualifies for replication 223 func (d ReplicateDecision) ReplicateAny() bool { 224 for _, t := range d.targetsMap { 225 if t.Replicate { 226 return true 227 } 228 } 229 return false 230 } 231 232 // Synchronous returns true if at least one target qualifies for synchronous replication 233 func (d ReplicateDecision) Synchronous() bool { 234 for _, t := range d.targetsMap { 235 if t.Synchronous { 236 return true 237 } 238 } 239 return false 240 } 241 242 func (d ReplicateDecision) String() string { 243 b := new(bytes.Buffer) 244 for key, value := range d.targetsMap { 245 fmt.Fprintf(b, "%s=%s,", key, value.String()) 246 } 247 return strings.TrimSuffix(b.String(), ",") 248 } 249 250 // Set updates ReplicateDecision with target's replication decision 251 func (d *ReplicateDecision) Set(t replicateTargetDecision) { 252 if d.targetsMap == nil { 253 d.targetsMap = make(map[string]replicateTargetDecision) 254 } 255 d.targetsMap[t.Arn] = t 256 } 257 258 // PendingStatus returns a stringified representation of internal replication status with all targets marked as `PENDING` 259 func (d ReplicateDecision) PendingStatus() string { 260 b := new(bytes.Buffer) 261 for _, k := range d.targetsMap { 262 if k.Replicate { 263 fmt.Fprintf(b, "%s=%s;", k.Arn, replication.Pending.String()) 264 } 265 } 266 return b.String() 267 } 268 269 // ResyncDecision is a struct representing a map with target's individual resync decisions 270 type ResyncDecision struct { 271 targets map[string]ResyncTargetDecision 272 } 273 274 // Empty returns true if no targets with resync decision present 275 func (r ResyncDecision) Empty() bool { 276 return r.targets == nil 277 } 278 279 func (r ResyncDecision) mustResync() bool { 280 for _, v := range r.targets { 281 if v.Replicate { 282 return true 283 } 284 } 285 return false 286 } 287 288 func (r ResyncDecision) mustResyncTarget(tgtArn string) bool { 289 if r.targets == nil { 290 return false 291 } 292 v, ok := r.targets[tgtArn] 293 return ok && v.Replicate 294 } 295 296 // ResyncTargetDecision is struct that represents resync decision for this target 297 type ResyncTargetDecision struct { 298 Replicate bool 299 ResetID string 300 ResetBeforeDate time.Time 301 } 302 303 var errInvalidReplicateDecisionFormat = fmt.Errorf("ReplicateDecision has invalid format") 304 305 // parse k-v pairs of target ARN to stringified ReplicateTargetDecision delimited by ',' into a 306 // ReplicateDecision struct 307 func parseReplicateDecision(ctx context.Context, bucket, s string) (r ReplicateDecision, err error) { 308 r = ReplicateDecision{ 309 targetsMap: make(map[string]replicateTargetDecision), 310 } 311 if len(s) == 0 { 312 return 313 } 314 for _, p := range strings.Split(s, ",") { 315 if p == "" { 316 continue 317 } 318 slc := strings.Split(p, "=") 319 if len(slc) != 2 { 320 return r, errInvalidReplicateDecisionFormat 321 } 322 tgtStr := strings.TrimSuffix(strings.TrimPrefix(slc[1], `"`), `"`) 323 tgt := strings.Split(tgtStr, ";") 324 if len(tgt) != 4 { 325 return r, errInvalidReplicateDecisionFormat 326 } 327 r.targetsMap[slc[0]] = replicateTargetDecision{Replicate: tgt[0] == "true", Synchronous: tgt[1] == "true", Arn: tgt[2], ID: tgt[3]} 328 } 329 return 330 } 331 332 // ReplicationState represents internal replication state 333 type ReplicationState struct { 334 ReplicaTimeStamp time.Time // timestamp when last replica update was received 335 ReplicaStatus replication.StatusType // replica statusstringis 336 DeleteMarker bool // represents DeleteMarker replication state 337 ReplicationTimeStamp time.Time // timestamp when last replication activity happened 338 ReplicationStatusInternal string // stringified representation of all replication activity 339 // VersionPurgeStatusInternal is internally in the format "arn1=PENDING;arn2=COMPLETED;" 340 VersionPurgeStatusInternal string // stringified representation of all version purge statuses 341 ReplicateDecisionStr string // stringified representation of replication decision for each target 342 Targets map[string]replication.StatusType // map of ARN->replication status for ongoing replication activity 343 PurgeTargets map[string]VersionPurgeStatusType // map of ARN->VersionPurgeStatus for all the targets 344 ResetStatusesMap map[string]string // map of ARN-> stringified reset id and timestamp for all the targets 345 } 346 347 // Equal returns true if replication state is identical for version purge statuses and (replica)tion statuses. 348 func (rs *ReplicationState) Equal(o ReplicationState) bool { 349 return rs.ReplicaStatus == o.ReplicaStatus && 350 rs.ReplicationStatusInternal == o.ReplicationStatusInternal && 351 rs.VersionPurgeStatusInternal == o.VersionPurgeStatusInternal 352 } 353 354 // CompositeReplicationStatus returns overall replication status for the object version being replicated. 355 func (rs *ReplicationState) CompositeReplicationStatus() (st replication.StatusType) { 356 switch { 357 case rs.ReplicationStatusInternal != "": 358 switch replication.StatusType(rs.ReplicationStatusInternal) { 359 case replication.Pending, replication.Completed, replication.Failed, replication.Replica: // for backward compatibility 360 return replication.StatusType(rs.ReplicationStatusInternal) 361 default: 362 replStatus := getCompositeReplicationStatus(rs.Targets) 363 // return REPLICA status if replica received timestamp is later than replication timestamp 364 // provided object replication completed for all targets. 365 if rs.ReplicaTimeStamp.Equal(timeSentinel) || rs.ReplicaTimeStamp.IsZero() { 366 return replStatus 367 } 368 if replStatus == replication.Completed && rs.ReplicaTimeStamp.After(rs.ReplicationTimeStamp) { 369 return rs.ReplicaStatus 370 } 371 return replStatus 372 } 373 case !rs.ReplicaStatus.Empty(): 374 return rs.ReplicaStatus 375 default: 376 return 377 } 378 } 379 380 // CompositeVersionPurgeStatus returns overall replication purge status for the permanent delete being replicated. 381 func (rs *ReplicationState) CompositeVersionPurgeStatus() VersionPurgeStatusType { 382 switch VersionPurgeStatusType(rs.VersionPurgeStatusInternal) { 383 case Pending, Complete, Failed: // for backward compatibility 384 return VersionPurgeStatusType(rs.VersionPurgeStatusInternal) 385 default: 386 return getCompositeVersionPurgeStatus(rs.PurgeTargets) 387 } 388 } 389 390 // TargetState returns replicatedInfos struct initialized with the previous state of replication 391 func (rs *ReplicationState) targetState(arn string) (r replicatedTargetInfo) { 392 return replicatedTargetInfo{ 393 Arn: arn, 394 PrevReplicationStatus: rs.Targets[arn], 395 VersionPurgeStatus: rs.PurgeTargets[arn], 396 ResyncTimestamp: rs.ResetStatusesMap[arn], 397 } 398 } 399 400 // getReplicationState returns replication state using target replicated info for the targets 401 func getReplicationState(rinfos replicatedInfos, prevState ReplicationState, vID string) ReplicationState { 402 rs := ReplicationState{ 403 ReplicateDecisionStr: prevState.ReplicateDecisionStr, 404 ResetStatusesMap: prevState.ResetStatusesMap, 405 ReplicaTimeStamp: prevState.ReplicaTimeStamp, 406 ReplicaStatus: prevState.ReplicaStatus, 407 } 408 var replStatuses, vpurgeStatuses string 409 replStatuses = rinfos.ReplicationStatusInternal() 410 rs.Targets = replicationStatusesMap(replStatuses) 411 rs.ReplicationStatusInternal = replStatuses 412 rs.ReplicationTimeStamp = rinfos.ReplicationTimeStamp 413 414 vpurgeStatuses = rinfos.VersionPurgeStatusInternal() 415 rs.VersionPurgeStatusInternal = vpurgeStatuses 416 rs.PurgeTargets = versionPurgeStatusesMap(vpurgeStatuses) 417 418 for _, rinfo := range rinfos.Targets { 419 if rinfo.ResyncTimestamp != "" { 420 rs.ResetStatusesMap[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp 421 } 422 } 423 return rs 424 } 425 426 // constructs a replication status map from string representation 427 func replicationStatusesMap(s string) map[string]replication.StatusType { 428 targets := make(map[string]replication.StatusType) 429 repStatMatches := replStatusRegex.FindAllStringSubmatch(s, -1) 430 for _, repStatMatch := range repStatMatches { 431 if len(repStatMatch) != 3 { 432 continue 433 } 434 status := replication.StatusType(repStatMatch[2]) 435 targets[repStatMatch[1]] = status 436 } 437 return targets 438 } 439 440 // constructs a version purge status map from string representation 441 func versionPurgeStatusesMap(s string) map[string]VersionPurgeStatusType { 442 targets := make(map[string]VersionPurgeStatusType) 443 purgeStatusMatches := replStatusRegex.FindAllStringSubmatch(s, -1) 444 for _, purgeStatusMatch := range purgeStatusMatches { 445 if len(purgeStatusMatch) != 3 { 446 continue 447 } 448 targets[purgeStatusMatch[1]] = VersionPurgeStatusType(purgeStatusMatch[2]) 449 } 450 return targets 451 } 452 453 // return the overall replication status for all the targets 454 func getCompositeReplicationStatus(m map[string]replication.StatusType) replication.StatusType { 455 if len(m) == 0 { 456 return replication.StatusType("") 457 } 458 completed := 0 459 for _, v := range m { 460 switch v { 461 case replication.Failed: 462 return replication.Failed 463 case replication.Completed: 464 completed++ 465 } 466 } 467 if completed == len(m) { 468 return replication.Completed 469 } 470 return replication.Pending 471 } 472 473 // return the overall version purge status for all the targets 474 func getCompositeVersionPurgeStatus(m map[string]VersionPurgeStatusType) VersionPurgeStatusType { 475 if len(m) == 0 { 476 return VersionPurgeStatusType("") 477 } 478 completed := 0 479 for _, v := range m { 480 switch v { 481 case Failed: 482 return Failed 483 case Complete: 484 completed++ 485 } 486 } 487 if completed == len(m) { 488 return Complete 489 } 490 return Pending 491 } 492 493 // getHealReplicateObjectInfo returns info needed by heal replication in ReplicateObjectInfo 494 func getHealReplicateObjectInfo(oi ObjectInfo, rcfg replicationConfig) ReplicateObjectInfo { 495 userDefined := cloneMSS(oi.UserDefined) 496 if rcfg.Config != nil && rcfg.Config.RoleArn != "" { 497 // For backward compatibility of objects pending/failed replication. 498 // Save replication related statuses in the new internal representation for 499 // compatible behavior. 500 if !oi.ReplicationStatus.Empty() { 501 oi.ReplicationStatusInternal = fmt.Sprintf("%s=%s;", rcfg.Config.RoleArn, oi.ReplicationStatus) 502 } 503 if !oi.VersionPurgeStatus.Empty() { 504 oi.VersionPurgeStatusInternal = fmt.Sprintf("%s=%s;", rcfg.Config.RoleArn, oi.VersionPurgeStatus) 505 } 506 for k, v := range userDefined { 507 if strings.EqualFold(k, ReservedMetadataPrefixLower+ReplicationReset) { 508 delete(userDefined, k) 509 userDefined[targetResetHeader(rcfg.Config.RoleArn)] = v 510 } 511 } 512 } 513 514 var dsc ReplicateDecision 515 if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() { 516 dsc = checkReplicateDelete(GlobalContext, oi.Bucket, ObjectToDelete{ 517 ObjectV: ObjectV{ 518 ObjectName: oi.Name, 519 VersionID: oi.VersionID, 520 }, 521 }, oi, ObjectOptions{ 522 Versioned: globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name), 523 VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name), 524 }, nil) 525 } else { 526 dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(userDefined, oi.UserTags, "", replication.HealReplicationType, ObjectOptions{})) 527 } 528 529 tgtStatuses := replicationStatusesMap(oi.ReplicationStatusInternal) 530 purgeStatuses := versionPurgeStatusesMap(oi.VersionPurgeStatusInternal) 531 existingObjResync := rcfg.Resync(GlobalContext, oi, dsc, tgtStatuses) 532 tm, _ := time.Parse(time.RFC3339Nano, userDefined[ReservedMetadataPrefixLower+ReplicationTimestamp]) 533 rstate := oi.ReplicationState() 534 rstate.ReplicateDecisionStr = dsc.String() 535 asz, _ := oi.GetActualSize() 536 537 return ReplicateObjectInfo{ 538 Name: oi.Name, 539 Size: oi.Size, 540 ActualSize: asz, 541 Bucket: oi.Bucket, 542 VersionID: oi.VersionID, 543 ETag: oi.ETag, 544 ModTime: oi.ModTime, 545 ReplicationStatus: oi.ReplicationStatus, 546 ReplicationStatusInternal: oi.ReplicationStatusInternal, 547 DeleteMarker: oi.DeleteMarker, 548 VersionPurgeStatusInternal: oi.VersionPurgeStatusInternal, 549 VersionPurgeStatus: oi.VersionPurgeStatus, 550 551 ReplicationState: rstate, 552 OpType: replication.HealReplicationType, 553 Dsc: dsc, 554 ExistingObjResync: existingObjResync, 555 TargetStatuses: tgtStatuses, 556 TargetPurgeStatuses: purgeStatuses, 557 ReplicationTimestamp: tm, 558 SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined), 559 UserTags: oi.UserTags, 560 } 561 } 562 563 // ReplicationState - returns replication state using other internal replication metadata in ObjectInfo 564 func (o ObjectInfo) ReplicationState() ReplicationState { 565 rs := ReplicationState{ 566 ReplicationStatusInternal: o.ReplicationStatusInternal, 567 VersionPurgeStatusInternal: o.VersionPurgeStatusInternal, 568 ReplicateDecisionStr: o.replicationDecision, 569 Targets: make(map[string]replication.StatusType), 570 PurgeTargets: make(map[string]VersionPurgeStatusType), 571 ResetStatusesMap: make(map[string]string), 572 } 573 rs.Targets = replicationStatusesMap(o.ReplicationStatusInternal) 574 rs.PurgeTargets = versionPurgeStatusesMap(o.VersionPurgeStatusInternal) 575 for k, v := range o.UserDefined { 576 if strings.HasPrefix(k, ReservedMetadataPrefixLower+ReplicationReset) { 577 arn := strings.TrimPrefix(k, fmt.Sprintf("%s-", ReservedMetadataPrefixLower+ReplicationReset)) 578 rs.ResetStatusesMap[arn] = v 579 } 580 } 581 return rs 582 } 583 584 // ReplicationState returns replication state using other internal replication metadata in ObjectToDelete 585 func (o ObjectToDelete) ReplicationState() ReplicationState { 586 r := ReplicationState{ 587 ReplicationStatusInternal: o.DeleteMarkerReplicationStatus, 588 VersionPurgeStatusInternal: o.VersionPurgeStatuses, 589 ReplicateDecisionStr: o.ReplicateDecisionStr, 590 } 591 592 r.Targets = replicationStatusesMap(o.DeleteMarkerReplicationStatus) 593 r.PurgeTargets = versionPurgeStatusesMap(o.VersionPurgeStatuses) 594 return r 595 } 596 597 // VersionPurgeStatus returns a composite version purge status across targets 598 func (d *DeletedObject) VersionPurgeStatus() VersionPurgeStatusType { 599 return d.ReplicationState.CompositeVersionPurgeStatus() 600 } 601 602 // DeleteMarkerReplicationStatus return composite replication status of delete marker across targets 603 func (d *DeletedObject) DeleteMarkerReplicationStatus() replication.StatusType { 604 return d.ReplicationState.CompositeReplicationStatus() 605 } 606 607 // ResyncTargetsInfo holds a slice of targets with resync info per target 608 type ResyncTargetsInfo struct { 609 Targets []ResyncTarget `json:"target,omitempty"` 610 } 611 612 // ResyncTarget is a struct representing the Target reset ID where target is identified by its Arn 613 type ResyncTarget struct { 614 Arn string `json:"arn"` 615 ResetID string `json:"resetid"` 616 StartTime time.Time `json:"startTime"` 617 EndTime time.Time `json:"endTime"` 618 // Status of resync operation 619 ResyncStatus string `json:"resyncStatus,omitempty"` 620 // Completed size in bytes 621 ReplicatedSize int64 `json:"completedReplicationSize"` 622 // Failed size in bytes 623 FailedSize int64 `json:"failedReplicationSize"` 624 // Total number of failed operations 625 FailedCount int64 `json:"failedReplicationCount"` 626 // Total number of failed operations 627 ReplicatedCount int64 `json:"replicationCount"` 628 // Last bucket/object replicated. 629 Bucket string `json:"bucket,omitempty"` 630 Object string `json:"object,omitempty"` 631 } 632 633 // VersionPurgeStatusType represents status of a versioned delete or permanent delete w.r.t bucket replication 634 type VersionPurgeStatusType string 635 636 const ( 637 // Pending - versioned delete replication is pending. 638 Pending VersionPurgeStatusType = "PENDING" 639 640 // Complete - versioned delete replication is now complete, erase version on disk. 641 Complete VersionPurgeStatusType = "COMPLETE" 642 643 // Failed - versioned delete replication failed. 644 Failed VersionPurgeStatusType = "FAILED" 645 ) 646 647 // Empty returns true if purge status was not set. 648 func (v VersionPurgeStatusType) Empty() bool { 649 return string(v) == "" 650 } 651 652 // Pending returns true if the version is pending purge. 653 func (v VersionPurgeStatusType) Pending() bool { 654 return v == Pending || v == Failed 655 } 656 657 type replicationResyncer struct { 658 // map of bucket to their resync status 659 statusMap map[string]BucketReplicationResyncStatus 660 workerSize int 661 resyncCancelCh chan struct{} 662 workerCh chan struct{} 663 sync.RWMutex 664 } 665 666 const ( 667 replicationDir = ".replication" 668 resyncFileName = "resync.bin" 669 resyncMetaFormat = 1 670 resyncMetaVersionV1 = 1 671 resyncMetaVersion = resyncMetaVersionV1 672 ) 673 674 type resyncOpts struct { 675 bucket string 676 arn string 677 resyncID string 678 resyncBefore time.Time 679 } 680 681 // ResyncStatusType status of resync operation 682 type ResyncStatusType int 683 684 const ( 685 // NoResync - no resync in progress 686 NoResync ResyncStatusType = iota 687 // ResyncPending - resync pending 688 ResyncPending 689 // ResyncCanceled - resync canceled 690 ResyncCanceled 691 // ResyncStarted - resync in progress 692 ResyncStarted 693 // ResyncCompleted - resync finished 694 ResyncCompleted 695 // ResyncFailed - resync failed 696 ResyncFailed 697 ) 698 699 func (rt ResyncStatusType) isValid() bool { 700 return rt != NoResync 701 } 702 703 func (rt ResyncStatusType) String() string { 704 switch rt { 705 case ResyncStarted: 706 return "Ongoing" 707 case ResyncCompleted: 708 return "Completed" 709 case ResyncFailed: 710 return "Failed" 711 case ResyncPending: 712 return "Pending" 713 case ResyncCanceled: 714 return "Canceled" 715 default: 716 return "" 717 } 718 } 719 720 // TargetReplicationResyncStatus status of resync of bucket for a specific target 721 type TargetReplicationResyncStatus struct { 722 StartTime time.Time `json:"startTime" msg:"st"` 723 LastUpdate time.Time `json:"lastUpdated" msg:"lst"` 724 // Resync ID assigned to this reset 725 ResyncID string `json:"resyncID" msg:"id"` 726 // ResyncBeforeDate - resync all objects created prior to this date 727 ResyncBeforeDate time.Time `json:"resyncBeforeDate" msg:"rdt"` 728 // Status of resync operation 729 ResyncStatus ResyncStatusType `json:"resyncStatus" msg:"rst"` 730 // Failed size in bytes 731 FailedSize int64 `json:"failedReplicationSize" msg:"fs"` 732 // Total number of failed operations 733 FailedCount int64 `json:"failedReplicationCount" msg:"frc"` 734 // Completed size in bytes 735 ReplicatedSize int64 `json:"completedReplicationSize" msg:"rs"` 736 // Total number of failed operations 737 ReplicatedCount int64 `json:"replicationCount" msg:"rrc"` 738 // Last bucket/object replicated. 739 Bucket string `json:"-" msg:"bkt"` 740 Object string `json:"-" msg:"obj"` 741 Error error `json:"-" msg:"-"` 742 } 743 744 // BucketReplicationResyncStatus captures current replication resync status 745 type BucketReplicationResyncStatus struct { 746 Version int `json:"version" msg:"v"` 747 // map of remote arn to their resync status for a bucket 748 TargetsMap map[string]TargetReplicationResyncStatus `json:"resyncMap,omitempty" msg:"brs"` 749 ID int `json:"id" msg:"id"` 750 LastUpdate time.Time `json:"lastUpdate" msg:"lu"` 751 } 752 753 func (rs *BucketReplicationResyncStatus) cloneTgtStats() (m map[string]TargetReplicationResyncStatus) { 754 m = make(map[string]TargetReplicationResyncStatus) 755 for arn, st := range rs.TargetsMap { 756 m[arn] = st 757 } 758 return 759 } 760 761 func newBucketResyncStatus(bucket string) BucketReplicationResyncStatus { 762 return BucketReplicationResyncStatus{ 763 TargetsMap: make(map[string]TargetReplicationResyncStatus), 764 Version: resyncMetaVersion, 765 } 766 } 767 768 var contentRangeRegexp = regexp.MustCompile(`bytes ([0-9]+)-([0-9]+)/([0-9]+|\\*)`) 769 770 // parse size from content-range header 771 func parseSizeFromContentRange(h http.Header) (sz int64, err error) { 772 cr := h.Get(xhttp.ContentRange) 773 if cr == "" { 774 return sz, fmt.Errorf("Content-Range not set") 775 } 776 parts := contentRangeRegexp.FindStringSubmatch(cr) 777 if len(parts) != 4 { 778 return sz, fmt.Errorf("invalid Content-Range header %s", cr) 779 } 780 if parts[3] == "*" { 781 return -1, nil 782 } 783 var usz uint64 784 usz, err = strconv.ParseUint(parts[3], 10, 64) 785 if err != nil { 786 return sz, err 787 } 788 return int64(usz), nil 789 } 790 791 func extractReplicateDiffOpts(q url.Values) (opts madmin.ReplDiffOpts) { 792 opts.Verbose = q.Get("verbose") == "true" 793 opts.ARN = q.Get("arn") 794 opts.Prefix = q.Get("prefix") 795 return 796 } 797 798 const ( 799 replicationMRFDir = bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + "mrf" 800 mrfMetaFormat = 1 801 mrfMetaVersionV1 = 1 802 mrfMetaVersion = mrfMetaVersionV1 803 ) 804 805 // MRFReplicateEntry mrf entry to save to disk 806 type MRFReplicateEntry struct { 807 Bucket string `json:"bucket" msg:"b"` 808 Object string `json:"object" msg:"o"` 809 versionID string `json:"-"` 810 RetryCount int `json:"retryCount" msg:"rc"` 811 sz int64 `json:"-"` 812 } 813 814 // MRFReplicateEntries has the map of MRF entries to save to disk 815 type MRFReplicateEntries struct { 816 Entries map[string]MRFReplicateEntry `json:"entries" msg:"e"` 817 Version int `json:"version" msg:"v"` 818 } 819 820 // ToMRFEntry returns the relevant info needed by MRF 821 func (ri ReplicateObjectInfo) ToMRFEntry() MRFReplicateEntry { 822 return MRFReplicateEntry{ 823 Bucket: ri.Bucket, 824 Object: ri.Name, 825 versionID: ri.VersionID, 826 sz: ri.Size, 827 RetryCount: int(ri.RetryCount), 828 } 829 }