| index 5e9880561..9e53db5b1 100644 |
| --- a/internal/activator/activator_test.go |
| +++ b/internal/activator/activator_test.go |
| @@ -6417,6 +6417,7 @@ func TestAStopKeepsTheLeaseUntilAnInFlightSidecarStartReturns(t *testing.T) { |
| host := "stale.example.com" |
| |
| arrangeAwake(t, a, testRoute(host)) |
| + h.reset() // the wake's own proof (finding 4) is not the stop's |
| first := b.lastSidecar() |
| first.exit() // the sweeper now wants a restart |
| |
| @@ -6436,8 +6437,8 @@ func TestAStopKeepsTheLeaseUntilAnInFlightSidecarStartReturns(t *testing.T) { |
| t.Error("the lease was released before the in-flight start's sidecar was stopped") |
| } |
| ev := b.events() |
| - stopped, proven := slices.Index(ev, "stop:sidecar:late"), slices.Index(ev, "emptyhost") |
| - if stopped < 0 || proven < stopped { |
| + stopped := slices.Index(ev, "stop:sidecar:late") |
| + if stopped < 0 || !slices.Contains(ev[stopped:], "emptyhost") { |
| t.Errorf("events %v: want the late sidecar stopped, then the host proven empty, before the release", ev) |
| } |
| }) |
| @@ -6519,6 +6520,100 @@ func (h *hostEmptyFake) count() int { |
| return h.calls |
| } |
| |
| +// reset forgets the proofs made so far: a stop test calls it after its wake, |
| +// whose own flag-off proof (finding 4) it does not count. |
| +func (h *hostEmptyFake) reset() { |
| + h.mu.Lock() |
| + h.calls, h.passes = 0, 0 |
| + h.mu.Unlock() |
| +} |
| + |
| +// Task 8 fix round 1, finding 4: after an agent restart the in-memory |
| +// pending stop and kill marks are gone, so a flag-off wake proves the host |
| +// cgroup empty itself, under its lease and before Restore. A proof that |
| +// fails refuses the wake before anything is restored or started, and keeps |
| +// the lease (a leftover may still write the database). One that holds lets |
| +// the wake go on. |
| +func TestAFlagOffWakeProvesTheHostCgroupBeforeItRestores(t *testing.T) { |
| + a, b, _, _ := testActivator(t, time.Minute) |
| + h := &hostEmptyFake{fakeBackend: b} |
| + a.backend = h |
| + h.set(errors.New("a leftover still populates the host cgroup")) |
| + rt := testRoute("leftover.example.com") |
| + |
| + if _, err := a.beginRequest(context.Background(), rt); !errors.Is(err, errHostUnproven) { |
| + t.Fatalf("wake = %v, want errHostUnproven", err) |
| + } |
| + if _, restores, starts := b.counts(); restores != 0 || starts != 0 { |
| + t.Fatalf("restores=%d starts=%d over a host cgroup not proven empty, want neither", restores, starts) |
| + } |
| + if got := b.releaseCount(); got != 0 { |
| + t.Fatalf("lease releases = %d, want 0: a leftover may still write the database", got) |
| + } |
| + if got := memberSafeWakeReason(fmt.Errorf("leftover.example.com: refusing to wake: %w", errHostUnproven)); got != memberSafeWakeReason(ErrFenced) { |
| + t.Fatalf("member reads %q, want the fence's existing text", got) |
| + } |
| + |
| + h.set(nil) |
| + if _, err := a.beginRequest(context.Background(), rt); err != nil { |
| + t.Fatalf("wake = %v once the proof holds", err) |
| + } |
| + ev := b.events() |
| + last := func(name string) int { |
| + for i := len(ev) - 1; i >= 0; i-- { |
| + if ev[i] == name { |
| + return i |
| + } |
| + } |
| + return -1 |
| + } |
| + proven, restored := last("emptyhost"), last("restore") |
| + if proven < 0 || restored < proven { |
| + t.Fatalf("events %v: want the host proven empty before the restore", ev) |
| + } |
| +} |
| + |
| +// No delegated cgroup: the wake goes on as today. A row whose child is not |
| +// in carlos-exec@<host> (nested, unit-backed) asks no proof, and neither |
| +// does a wake with generations on, whose proofs are its own. |
| +func TestAWakeAsksTheHostProofOnlyFlagOffForAnExecChild(t *testing.T) { |
| + t.Run("no delegation", func(t *testing.T) { |
| + a, b, _, _ := testActivator(t, time.Minute) |
| + h := &hostEmptyFake{fakeBackend: b} |
| + h.set(cgroup.ErrNoDelegation) |
| + a.backend = h |
| + if _, err := a.beginRequest(context.Background(), testRoute("undelegated-wake.example.com")); err != nil { |
| + t.Fatalf("wake = %v, want today's wake with no delegation", err) |
| + } |
| + if h.count() != 1 { |
| + t.Fatalf("proofs = %d, want 1", h.count()) |
| + } |
| + }) |
| + for _, tc := range []struct { |
| + name string |
| + setup func(*Service, *fakeBackend, *registry.Route) |
| + }{ |
| + {"nested", func(_ *Service, b *fakeBackend, _ *registry.Route) { |
| + b.layoutFn = func(registry.Route) (Layout, string) { return LayoutNested, "" } |
| + }}, |
| + {"unit-backed", func(_ *Service, _ *fakeBackend, rt *registry.Route) { rt.Backing = registry.BackingUnit }}, |
| + {"generations on", func(a *Service, _ *fakeBackend, _ *registry.Route) { a.gens = true }}, |
| + } { |
| + t.Run(tc.name, func(t *testing.T) { |
| + a, b, _, _ := testActivator(t, time.Minute) |
| + h := &hostEmptyFake{fakeBackend: b} |
| + h.set(errors.New("must not be asked")) |
| + a.backend = h |
| + rt := testRoute("no-host-proof.example.com") |
| + tc.setup(a, b, &rt) |
| + _, _ = a.beginRequest(context.Background(), rt) |
| + if n := h.count(); n != 0 { |
| + t.Fatalf("host proofs = %d, want none", n) |
| + } |
| + }) |
| + } |
| +} |
| + |
| // lateSidecarLabel labels every sidecar StartSidecar returns from now on. |
| func (b *fakeBackend) lateSidecarLabel(label string) { |
| b.mu.Lock() |
| @@ -6535,6 +6630,7 @@ func TestAFlagOffStopKeepsTheLeaseUntilTheHostCgroupIsProvenEmpty(t *testing.T) |
| h := &hostEmptyFake{fakeBackend: b} |
| a.backend = h |
| arrangeAwake(t, a, testRoute("f6.example.com")) |
| + h.reset() // the wake's own proof (finding 4) is not the stop's |
| h.set(errors.New("the host cgroup still holds a process")) |
| |
| clk.advance(2 * time.Minute) |
| @@ -6568,6 +6664,7 @@ func TestAFlagOffStopProvesTheHostCgroupBeforeTheLeaseGoes(t *testing.T) { |
| h := &hostEmptyFake{fakeBackend: b} |
| a.backend = h |
| arrangeAwake(t, a, testRoute("proven.example.com")) |
| + h.reset() // the wake's own proof (finding 4) is not the stop's |
| rep := b.lastRep() |
| var atClose atomic.Int32 |
| atClose.Store(-1) |
| @@ -6589,6 +6686,7 @@ func TestAFlagOffStopWithNoDelegationReleasesAtOnce(t *testing.T) { |
| h.set(cgroup.ErrNoDelegation) |
| a.backend = h |
| arrangeAwake(t, a, testRoute("undelegated.example.com")) |
| + h.reset() // the wake's own proof (finding 4) is not the stop's |
| |
| clk.advance(2 * time.Minute) |
| a.sweepOnce() |
| @@ -6663,6 +6761,7 @@ func TestAPendingFlagOffStopProvesTheHostCgroupBeforeTheLeaseGoes(t *testing.T) |
| a.backend = h |
| b.procStuck = true |
| arrangeAwake(t, a, testRoute("pending-f6.example.com")) |
| + h.reset() // the wake's own proof (finding 4) is not the stop's |
| h.set(errors.New("the host cgroup still holds a process")) |
| |
| clk.advance(2 * time.Minute) |