| 1 | package activator |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "sync/atomic" |
| 7 | "testing" |
| 8 | "time" |
| 9 | |
| 10 | "github.com/carlosframework/platform/internal/registry" |
| 11 | ) |
| 12 | |
| 13 | // Final review round 1, finding 6 (Astra): a failed wake's cleanup decides |
| 14 | // whether to prove the host cgroup from the layout its launch used, not |
| 15 | // from the layout the row reads at cleanup. A flat launch whose layout reads |
| 16 | // fenced or conflicting by the time the cleanup runs is still proven, and so |
| 17 | // is a nested launch whose layout no longer reads nested (unknown, fenced, |
| 18 | // conflicting or flat): only a launch that was nested, and still reads |
| 19 | // nested, keeps its own proof (ruling c). Here the host cgroup stays |
| 20 | // populated, so a proven wake is retained and its lease kept. |
| 21 | func TestAFailedWakeIsProvenByTheLayoutItsLaunchUsed(t *testing.T) { |
| 22 | t.Parallel() |
| 23 | for _, row := range []struct { |
| 24 | name string |
| 25 | launch, cleanup Layout |
| 26 | retained, killed bool |
| 27 | }{ |
| 28 | {"flat, fenced at cleanup", LayoutFlat, LayoutFenced, true, true}, |
| 29 | {"flat, conflicting at cleanup", LayoutFlat, LayoutConflict, true, true}, |
| 30 | {"nested, fenced at cleanup", LayoutNested, LayoutFenced, true, true}, |
| 31 | {"nested, flat at cleanup", LayoutNested, LayoutFlat, true, true}, |
| 32 | {"nested, still nested", LayoutNested, LayoutNested, false, false}, |
| 33 | } { |
| 34 | t.Run(row.name, func(t *testing.T) { |
| 35 | a, b, _, _ := testActivator(t, time.Hour) |
| 36 | a.gens = false |
| 37 | var launched atomic.Bool |
| 38 | b.layoutFn = func(registry.Route) (Layout, string) { |
| 39 | if launched.Load() { |
| 40 | return row.cleanup, "changed after the launch" |
| 41 | } |
| 42 | return row.launch, "" |
| 43 | } |
| 44 | leader := &sigtermIgnorer{} |
| 45 | leader.exited.Store(true) |
| 46 | kb := &killGenBackend{fakeBackend: b, err: errNoGenContainment} |
| 47 | ub := &unprovenStartBackend{killGenBackend: kb, fail: func(uint64) (Proc, error) { |
| 48 | launched.Store(true) // the layout changes once the launch has run |
| 49 | return leader, errors.New("health: no answer within 30s") |
| 50 | }} |
| 51 | hb := &uncontainedStartBackend{unprovenStartBackend: ub, populated: true} |
| 52 | a.backend = hb |
| 53 | rt := testRoute("f6-layout.example.com") |
| 54 | if _, err := a.beginRequest(context.Background(), rt); err == nil { |
| 55 | t.Fatal("the wake should have failed") |
| 56 | } |
| 57 | a.mu.Lock() |
| 58 | _, retained := a.unprovenWakes[rt.Host] |
| 59 | a.mu.Unlock() |
| 60 | if retained != row.retained { |
| 61 | t.Fatalf("retained = %v, want %v", retained, row.retained) |
| 62 | } |
| 63 | if asked := hb.hostKillCount() > 0; asked != row.killed { |
| 64 | t.Fatalf("KillHost asked = %v, want %v", asked, row.killed) |
| 65 | } |
| 66 | }) |
| 67 | } |
| 68 | } |
| 69 | |