| 1 | package registry |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "path/filepath" |
| 6 | "testing" |
| 7 | ) |
| 8 | |
| 9 | func TestPlan12RebindClaimRows(t *testing.T) { |
| 10 | for _, mode := range []string{"ok", "changed-route", "changed-owner", "changed-payload", "extra-timer"} { |
| 11 | t.Run(mode, func(t *testing.T) { |
| 12 | r, e := Open(filepath.Join(t.TempDir(), "registry.db")) |
| 13 | if e != nil { |
| 14 | t.Fatal(e) |
| 15 | } |
| 16 | defer r.Close() |
| 17 | row := Route{Host: "app.example", Kind: "instance", ClaimOwnership: ClaimOwnership{"inc", 1, "old", "old-row"}} |
| 18 | if e = r.Upsert(row); e != nil { |
| 19 | t.Fatal(e) |
| 20 | } |
| 21 | saved, e := r.Get(row.Host) |
| 22 | if e != nil { |
| 23 | t.Fatal(e) |
| 24 | } |
| 25 | old := ColdRows{Route: *saved} |
| 26 | next := old |
| 27 | next.Route.ClaimOwnership = ClaimOwnership{"inc", 2, "new", "new-row"} |
| 28 | switch mode { |
| 29 | case "changed-route": |
| 30 | r.sql.Exec("UPDATE routes SET version='other' WHERE host=?", row.Host) |
| 31 | case "changed-owner": |
| 32 | r.sql.Exec("UPDATE routes SET claim_owner='other' WHERE host=?", row.Host) |
| 33 | case "changed-payload": |
| 34 | next.Route.Version = "other" |
| 35 | case "extra-timer": |
| 36 | if e = r.UpsertTimer(Timer{Host: row.Host, Name: "extra", Kind: "cron", Spec: "* * * * *"}); e != nil { |
| 37 | t.Fatal(e) |
| 38 | } |
| 39 | } |
| 40 | e = r.RebindClaimRows(t.Context(), old, next) |
| 41 | if mode != "ok" { |
| 42 | if !errors.Is(e, ErrClaimOwnership) { |
| 43 | t.Fatal("accepted changed installation", e) |
| 44 | } |
| 45 | return |
| 46 | } |
| 47 | if e != nil { |
| 48 | t.Fatal(e) |
| 49 | } |
| 50 | if e = r.RebindClaimRows(t.Context(), old, next); e != nil { |
| 51 | t.Fatal("replay", e) |
| 52 | } |
| 53 | got, e := r.Get(row.Host) |
| 54 | if e != nil || *got != next.Route { |
| 55 | t.Fatal(got, e) |
| 56 | } |
| 57 | }) |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | func TestPlan12RebindRefusesForeignTimerOwnership(t *testing.T) { |
| 62 | for _, mode := range []string{"foreign-owner", "stale-incarnation", "stale-stamp"} { |
| 63 | t.Run(mode, func(t *testing.T) { |
| 64 | r, err := Open(filepath.Join(t.TempDir(), "registry.db")) |
| 65 | if err != nil { |
| 66 | t.Fatal(err) |
| 67 | } |
| 68 | defer r.Close() |
| 69 | row := Route{Host: "app.example", Kind: "instance", ClaimOwnership: ClaimOwnership{"inc", 1, "old", "row"}} |
| 70 | if err = r.Upsert(row); err != nil { |
| 71 | t.Fatal(err) |
| 72 | } |
| 73 | saved, err := r.Get(row.Host) |
| 74 | if err != nil { |
| 75 | t.Fatal(err) |
| 76 | } |
| 77 | timer := Timer{Host: row.Host, Name: "extra", Kind: "cron", Spec: "* * * * *", ClaimOwnership: ClaimOwnership{"inc", 1, "old", "timer"}} |
| 78 | switch mode { |
| 79 | case "foreign-owner": |
| 80 | timer.ClaimOwner = "foreign" |
| 81 | case "stale-incarnation": |
| 82 | timer.ClaimIncarnation = "stale" |
| 83 | case "stale-stamp": |
| 84 | timer.ClaimStamp = 9 |
| 85 | } |
| 86 | if err = r.UpsertTimer(timer); err != nil { |
| 87 | t.Fatal(err) |
| 88 | } |
| 89 | if _, err = r.sql.Exec("UPDATE timers SET claim_incarnation=?,claim_stamp=?,claim_owner=?,claim_row_id=? WHERE host=?", timer.ClaimIncarnation, timer.ClaimStamp, timer.ClaimOwner, timer.ClaimRowID, row.Host); err != nil { |
| 90 | t.Fatal(err) |
| 91 | } |
| 92 | timers, err := r.TimersFor(row.Host) |
| 93 | if err != nil { |
| 94 | t.Fatal(err) |
| 95 | } |
| 96 | old := ColdRows{Route: *saved, Timers: timers} |
| 97 | next := ColdRows{Route: *saved, Timers: append([]Timer(nil), timers...)} |
| 98 | next.Route.ClaimOwnership = ClaimOwnership{"inc", 2, "new", "new-row"} |
| 99 | next.Timers[0].ClaimOwnership = ClaimOwnership{"inc", 2, "new", "new-timer"} |
| 100 | if err = r.RebindClaimRows(t.Context(), old, next); !errors.Is(err, ErrClaimOwnership) { |
| 101 | t.Fatalf("stole timer: %v", err) |
| 102 | } |
| 103 | got, err := r.Get(row.Host) |
| 104 | if err != nil || *got != *saved { |
| 105 | t.Fatal("refusal changed route", err) |
| 106 | } |
| 107 | }) |
| 108 | } |
| 109 | } |
| 110 | |