| 1 | package provision |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "testing" |
| 6 | "time" |
| 7 | ) |
| 8 | |
| 9 | func TestMoveRollbackLossRebind(t *testing.T) { |
| 10 | g := MoveGuard{ID: "mv_abcdefghijklmnopqrstuvwxyz", Incarnation: "inc"} |
| 11 | now := time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC) |
| 12 | lost := func(phase, ref string, dirty bool) *Record { |
| 13 | at := now.Add(-time.Minute) |
| 14 | return &Record{Move: &MoveState{ID: g.ID, Incarnation: g.Incarnation, State: MoveRollingBack, |
| 15 | Rollback: &MoveRollback{ID: "rb_bbbbbbbbbbbbbbbbbbbbbbbbbb", TargetPhase: phase, Dirty: dirty, LossRef: ref, LostAt: &at, SourcePhase: RbSourceWaiting}}} |
| 16 | } |
| 17 | rec := lost(RbTargetLost, "old", true) |
| 18 | if err := MoveRollbackLossRebind(g, "old", "new", now)(rec); err != nil { |
| 19 | t.Fatal(err) |
| 20 | } |
| 21 | rb := rec.Move.Rollback |
| 22 | if rb.LossRef != "new" || !rb.LostAt.Equal(now) || rb.TargetPhase != RbTargetLost || !rb.Dirty || rb.ID != "rb_bbbbbbbbbbbbbbbbbbbbbbbbbb" { |
| 23 | t.Fatalf("rollback = %+v", rb) |
| 24 | } |
| 25 | // A replay changes nothing. |
| 26 | if err := MoveRollbackLossRebind(g, "old", "new", now.Add(time.Hour))(rec); err != nil || !rb.LostAt.Equal(now) { |
| 27 | t.Fatalf("replay = %v %v", err, rb.LostAt) |
| 28 | } |
| 29 | for name, tc := range map[string]struct { |
| 30 | rec *Record |
| 31 | g MoveGuard |
| 32 | oldRef, newRef string |
| 33 | }{ |
| 34 | "other old ref": {lost(RbTargetLost, "other", false), g, "old", "new"}, |
| 35 | "not lost": {lost(RbTargetReleasing, "", false), g, "old", "new"}, |
| 36 | "released": {lost(RbTargetReleased, "old", false), g, "old", "new"}, |
| 37 | "same ref": {lost(RbTargetLost, "old", false), g, "old", "old"}, |
| 38 | "empty old": {lost(RbTargetLost, "", false), g, "", "new"}, |
| 39 | "other move": {lost(RbTargetLost, "old", false), MoveGuard{ID: "mv_cccccccccccccccccccccccccc", Incarnation: "inc"}, "old", "new"}, |
| 40 | "other incarnate": {lost(RbTargetLost, "old", false), MoveGuard{ID: g.ID, Incarnation: "other"}, "old", "new"}, |
| 41 | } { |
| 42 | before := *tc.rec.Move.Rollback |
| 43 | err := MoveRollbackLossRebind(tc.g, tc.oldRef, tc.newRef, now)(tc.rec) |
| 44 | if err == nil { |
| 45 | t.Errorf("%s accepted", name) |
| 46 | } |
| 47 | if name != "same ref" && name != "empty old" && !errors.Is(err, ErrMoveConflict) { |
| 48 | t.Errorf("%s = %v", name, err) |
| 49 | } |
| 50 | if after := *tc.rec.Move.Rollback; after.LossRef != before.LossRef || after.TargetPhase != before.TargetPhase { |
| 51 | t.Errorf("%s changed the rollback", name) |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | |