package hostclaim import ( "errors" "testing" ) // plan11Relocating is a relocating claim at phase whose target, uk-box-2, // executes from installing on. The source, uk-box, executes before pinning. func plan11Relocating(phase string) *Claim { exec := "uk-box-2" switch phase { case "starting", "releasing": exec = "uk-box" case "pinning": exec = "none" } return &Claim{V: schemaVersion, Host: "a.go.example.com", Gen: 20, Holder: &Holder{Family: "instance", Incarnation: "inc", Location: "instances/a.go.example.com", Generation: "g1", Stamp: 19, Session: 18, WriterBox: "uk-box", Admissions: []AdmissionRecord{{Session: 18, Box: "uk-box", Generation: "g1", Mode: "cold", Region: "uk", PredecessorRegion: "uk"}}}, Op: Op{Kind: KindRelocating, ID: "r1", Phase: phase, Exec: exec, Participants: []string{"uk-box", "uk-box-2"}, Intent: map[string]string{"region": "uk", "source_box": "uk-box", "target_box": "uk-box-2"}}} } func plan11Probe(c *Claim) Admission { return Admission{ObservedOpID: c.Op.ID, ObservedPhase: c.Op.Phase, ObservedSession: c.Holder.Session, ObservedGeneration: c.Holder.Generation, Box: c.Op.Exec, Mode: "cold", NewGeneration: "p1", Region: "uk", PredecessorRegion: "uk"} } // The relocating session row is probing then serving, adjacent and forward // only (claim ยง4 phase table: probing admits cold probes; serving is the // successful target serving on the admitted session). func TestPlan11RelocatingSessionStepsAreAdjacentAndForward(t *testing.T) { for _, tc := range []struct { from, to string want bool }{ {"probing", "probing", true}, {"probing", "serving", true}, {"serving", "serving", true}, {"serving", "probing", false}, {"probing", "probe-failed", false}, {"installed", "probing", false}, {"installing", "probing", false}, {"releasing", "pinning", false}, {"starting", "releasing", false}, } { if got := SessionContinues(KindRelocating, tc.from, tc.to); got != tc.want { t.Errorf("SessionContinues(relocating, %q, %q) = %v, want %v", tc.from, tc.to, got, tc.want) } } for phase, want := range map[string]bool{"probing": true, "serving": true, "starting": false, "releasing": false, "pinning": false, "installing": false, "installed": false, "probe-failed": false} { if got := SessionPhase(KindRelocating, phase); got != want { t.Errorf("SessionPhase(relocating, %q) = %v, want %v", phase, got, want) } } if !SessionObserves(KindRelocating, "probing", "serving") || SessionObserves(KindRelocating, "serving", "probing") { t.Fatal("observation follows the row forward only") } // The landing row is unchanged. if !SessionContinues(KindLanding, "probed", "committed") || SessionContinues(KindLanding, "probing", "committed") { t.Fatal("landing row changed") } } func TestPlan11KeepWriterSessionCarriesTheProbeIntoServing(t *testing.T) { toServing := func(c *Claim) error { c.Op.Phase = "serving"; return nil } c := plan11Relocating("probing") if err := KeepWriterSession("r1", toServing)(c); err != nil { t.Fatalf("probing to serving keeps the probe's session: %v", err) } c = plan11Relocating("probing") failed := func(c *Claim) error { c.Op.Phase = "probe-failed"; return nil } if err := KeepWriterSession("r1", failed)(c); !errors.Is(err, ErrWrongState) { t.Fatalf("probe-failed ends the session: %v", err) } c = plan11Relocating("probing") newSession := func(c *Claim) error { c.Op.Phase = "serving"; c.Holder.Session++; return nil } if err := KeepWriterSession("r1", newSession)(c); !errors.Is(err, ErrWrongState) { t.Fatalf("a step that moves the session is refused: %v", err) } } // AdmitWriter under relocating admits only op.exec's cold probe at probing. // The source's ordinary admission in starting or releasing stays refused // until its admission-fence authority exists (claim Plan 11, R1 as accepted). func TestPlan11AdmitWriterUnderRelocatingAdmitsOnlyTheExecutorsColdProbe(t *testing.T) { c := plan11Relocating("probing") in := plan11Probe(c) if err := AdmitWriter(in)(c); err != nil { t.Fatalf("op.exec's cold probe at probing: %v", err) } if c.Holder.WriterBox != "uk-box-2" || c.Holder.Generation != "p1" || c.Holder.Session != 21 { t.Fatalf("admission not recorded: %+v", c.Holder) } refused := map[string]func() (*Claim, Admission){ "not the executor": func() (*Claim, Admission) { c := plan11Relocating("probing") in := plan11Probe(c) c.Op.Exec, in.Box = "uk-box-2", "uk-box" return c, in }, "warm": func() (*Claim, Admission) { c := plan11Relocating("probing") in := plan11Probe(c) in.Mode, in.NewGeneration, in.PredecessorCertified = "warm", "", true return c, in }, "released coverage": func() (*Claim, Admission) { c := plan11Relocating("probing") in := plan11Probe(c) in.Coverage = "released" return c, in }, "stuck": func() (*Claim, Admission) { c := plan11Relocating("probing") c.Op.Journal = map[string]string{"stuck": "operator"} return c, plan11Probe(c) }, } for _, phase := range []string{"starting", "releasing", "pinning", "installing", "installed", "probe-failed", "serving"} { phase := phase refused["phase "+phase] = func() (*Claim, Admission) { c := plan11Relocating(phase) in := plan11Probe(c) if c.Op.Exec == "none" { c.Op.Exec = "uk-box-2" in.Box = "uk-box-2" } return c, in } } for name, build := range refused { c, in := build() before := *c.Holder if err := AdmitWriter(in)(c); !errors.Is(err, ErrWrongState) { t.Errorf("%s: %v", name, err) } if c.Holder.Session != before.Session || c.Holder.Generation != before.Generation { t.Errorf("%s: refused admission changed the holder", name) } } } func TestPlan11PublishGenerationUnderRelocatingNeedsAWriterPhase(t *testing.T) { for phase, want := range map[string]bool{"probing": true, "serving": true, "installing": false, "installed": false, "probe-failed": false, "releasing": false} { c := plan11Relocating(phase) err := PublishGeneration("r1", phase, c.Holder.Session, "g1", "uk", func() string { return "chore" })(c) if (err == nil) != want { t.Errorf("relocating/%s publish: %v, want allowed=%v", phase, err, want) } } }