| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/lease" |
| 9 | "github.com/carlosframework/platform/internal/moves" |
| 10 | "github.com/carlosframework/platform/internal/provision" |
| 11 | ) |
| 12 | |
| 13 | // Final review finding 2, end to end: the target's fresh report priced |
| 14 | // T11 and checked T6 for the database it saw, 16 KiB here. The database |
| 15 | // then grows to 2.5 GiB before the target reports again, as the source's |
| 16 | // local file or as its replica's newest LTX. The source's next round must |
| 17 | // not promote on the old report, and the source's report must carry the |
| 18 | // size it saw. The target's next report prices the new size. |
| 19 | func TestPromotionWaitsForAReportCoveringTheGrownDatabase(t *testing.T) { |
| 20 | const grown = 2560 << 20 |
| 21 | for name, grow := range map[string]func(t *testing.T, h *phase1Harness){ |
| 22 | "local file": func(t *testing.T, h *phase1Harness) { |
| 23 | db, err := os.Create(provision.DefaultDBPath(h.cfg.DataDir, p1Host)) |
| 24 | if err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | if err := db.Truncate(grown); err != nil { // sparse |
| 28 | t.Fatal(err) |
| 29 | } |
| 30 | db.Close() |
| 31 | }, |
| 32 | "replica": func(t *testing.T, h *phase1Harness) { |
| 33 | newest := ltxForTest(t, 4096, grown/4096, 3, 3) |
| 34 | key := "instances/" + p1Host + "/" + h.inc + "/g-cur/0000/0000000000000003-0000000000000003.ltx" |
| 35 | h.f.store("data-ie", key, fakeMoveObject{lease.MirrorObject{ETag: "grown", Size: int64(len(newest))}, string(newest)}) |
| 36 | }, |
| 37 | } { |
| 38 | t.Run(name, func(t *testing.T) { |
| 39 | h := newPhase1Harness(t) |
| 40 | h.start(t, "manual") |
| 41 | h.pass(t) |
| 42 | h.targetPass(t, h.targetCfg()) |
| 43 | tr := h.targetReport(t) |
| 44 | if ok, why := tr.Passing(); !ok || tr.DBMiB*(1<<20) != 4*4096 { |
| 45 | t.Fatalf("the target's first report: %v %s, priced %v MiB", ok, why, tr.DBMiB) |
| 46 | } |
| 47 | grow(t, h) |
| 48 | h.pass(t) |
| 49 | if got := h.read(t, p1Host).Move.State; got != provision.MovePreparing { |
| 50 | t.Fatalf("promoted on a report that priced %v MiB after the database grew to 2.5 GiB: %s", tr.DBMiB, got) |
| 51 | } |
| 52 | sr := h.sourceReport(t) |
| 53 | if max(sr.DBBytes, sr.ReplicaBytes) != grown { |
| 54 | t.Fatalf("the source report carries DBBytes %d and ReplicaBytes %d, want %d", sr.DBBytes, sr.ReplicaBytes, int64(grown)) |
| 55 | } |
| 56 | _, why := moves.Ready(0, h.cfg.ReadyDelta, h.targetReport(t), sr, h.clock.Now()) |
| 57 | if !strings.HasPrefix(why, "T11: the database has grown to 2560 MiB") { |
| 58 | t.Fatalf("Ready's reason: %q", why) |
| 59 | } |
| 60 | h.targetPass(t, h.targetCfg()) |
| 61 | if r := h.targetReport(t); r.DBMiB != 2560 { |
| 62 | t.Fatalf("the target's next report priced %v MiB", r.DBMiB) |
| 63 | } |
| 64 | }) |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | // Without growth the same sequence promotes: the control for the test |
| 69 | // above, and the replica's size is what the source reports. |
| 70 | func TestSourceReportCarriesTheReplicaSize(t *testing.T) { |
| 71 | h := newPhase1Harness(t) |
| 72 | h.start(t, "manual") |
| 73 | h.pass(t) |
| 74 | if sr := h.sourceReport(t); sr.ReplicaBytes != 4*4096 { |
| 75 | t.Fatalf("ReplicaBytes %d, want the 16 KiB the newest header names", sr.ReplicaBytes) |
| 76 | } |
| 77 | h.targetPass(t, h.targetCfg()) |
| 78 | h.pass(t) |
| 79 | if got := h.read(t, p1Host).Move.State; got != provision.MoveReady { |
| 80 | t.Fatalf("state %s, want ready", got) |
| 81 | } |
| 82 | } |
| 83 | |