activator: the adoption verb answers 500 for a launch state whose record the pinner could not read, the 500 guards are tested, the swapped sync is restored by t.Cleanup, and no test message prints a record (end sweep, task 5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 files changed,
+46
−3
internal/activator/keyadoption.go+11 −1internal/activator/keyadoption_linux_test.go+6 −1internal/activator/keyadoption_test.go+29 −1
diff --git a/internal/activator/keyadoption.go b/internal/activator/keyadoption.go| index ce63f7c24..2eb6d4b4e 100644 |
| --- a/internal/activator/keyadoption.go |
| +++ b/internal/activator/keyadoption.go |
| @@ -67,7 +67,9 @@ type AdoptionLaunch struct { |
| // some other key), or for a replica row (ruling 26); nothing recorded; |
| // - 404 with no launch state, 400 for a malformed body (a session or |
| // generation in it included), 401 without the bearer, 500 when the |
| -// durable write fails (nothing published). |
| +// durable write fails, or when the launch state is not a complete |
| +// writer binding for this host with a publisher (O6's programming |
| +// error); nothing written or published either way. |
| // |
| // Bodies are http.StatusText only: the helper treats every failure as |
| // non-fatal, and the status is all it reads. |
| @@ -112,6 +114,14 @@ func (b *ExecBackend) MountInstanceKeyAdoption(mux *http.ServeMux, rt registry.R |
| return |
| } |
| record := instancekey.AdoptionRecord(l.Writer, l.Source, l.KeySHA256) |
| + // The record must be one the pinner can read back: a launch state |
| + // with an incomplete writer (a zero session, say) is O6's |
| + // programming error, answered as the other guard is, not a 204 |
| + // for a record nobody will ever accept. |
| + if _, err := instancekey.ParseAdoption(record); err != nil { |
| + fail(w, http.StatusInternalServerError) |
| + return |
| + } |
| if err := writeAdoptionRecord(l, rt.Host, record); err != nil { |
| fail(w, http.StatusInternalServerError) |
| return |
diff --git a/internal/activator/keyadoption_linux_test.go b/internal/activator/keyadoption_linux_test.go| index a891e0913..e5b1b6114 100644 |
| --- a/internal/activator/keyadoption_linux_test.go |
| +++ b/internal/activator/keyadoption_linux_test.go |
| @@ -25,6 +25,11 @@ func TestInstanceKeyAdoptionIsDurableBeforeItAnswers(t *testing.T) { |
| old := adoptionRecordSync |
| adoptionRecordSync = func(string) error { return errors.New("injected: EIO") } |
| adminTokenMu.Unlock() |
| + t.Cleanup(func() { // whatever fails below, the next test gets the real sync |
| + adminTokenMu.Lock() |
| + adoptionRecordSync = old |
| + adminTokenMu.Unlock() |
| + }) |
| if got := f.put(tok, adoptionBody(key, "platform")); got != http.StatusInternalServerError { |
| t.Fatalf("a failing directory sync: %d, want 500", got) |
| } |
| @@ -39,6 +44,6 @@ func TestInstanceKeyAdoptionIsDurableBeforeItAnswers(t *testing.T) { |
| } |
| rec, err := os.ReadFile(filepath.Join(f.launch.Dir, rt.Host+instancekey.AdoptionSuffix)) |
| if err != nil || len(f.published) != 1 || string(rec) != string(f.published[0]) { |
| - t.Fatalf("after the retry: record %q (%v), %d published", rec, err, len(f.published)) |
| + t.Fatalf("after the retry: record %d bytes (%v), %d published", len(rec), err, len(f.published)) |
| } |
| } |
diff --git a/internal/activator/keyadoption_test.go b/internal/activator/keyadoption_test.go| index 7013744b0..4d775129e 100644 |
| --- a/internal/activator/keyadoption_test.go |
| +++ b/internal/activator/keyadoption_test.go |
| @@ -84,7 +84,7 @@ func TestInstanceKeyAdoptionRecordsTheLaunchesWriter(t *testing.T) { |
| } |
| rec, err := os.ReadFile(recPath) |
| if err != nil || string(rec) != string(want) { |
| - t.Fatalf("report %d: record = %q, %v", i, rec, err) |
| + t.Fatalf("report %d: record is %d bytes, want %d, %v", i, len(rec), len(want), err) |
| } |
| if st, _ := os.Stat(recPath); st.Mode().Perm() != 0o600 { |
| t.Fatalf("record mode = %o, want 600", st.Mode().Perm()) |
| @@ -120,3 +120,31 @@ func TestInstanceKeyAdoptionRecordsTheLaunchesWriter(t *testing.T) { |
| t.Fatal("a record was published without a writer") |
| } |
| } |
| + |
| +// A launch state O6 built wrong (another host's writer, no publisher, or a |
| +// writer binding the pinner would refuse) is a 500, with nothing written |
| +// and nothing published: never a 204 for a record nobody can read. |
| +func TestInstanceKeyAdoptionRefusesAnIncompleteLaunchState(t *testing.T) { |
| + tok := strings.Repeat("f", 64) |
| + key := strings.Repeat("K", 43) + "=" |
| + rt := registry.Route{Host: "shop.example.com", Kind: "instance"} |
| + for _, tc := range []struct { |
| + name string |
| + wreck func(*AdoptionLaunch) |
| + }{ |
| + {"another host's writer", func(l *AdoptionLaunch) { l.Writer.Host = "other.example.com" }}, |
| + {"no publisher", func(l *AdoptionLaunch) { l.Publish = nil }}, |
| + {"a zero session", func(l *AdoptionLaunch) { l.Writer.Session = 0 }}, |
| + {"no incarnation", func(l *AdoptionLaunch) { l.Writer.Incarnation = "" }}, |
| + {"no generation", func(l *AdoptionLaunch) { l.Writer.Generation = "" }}, |
| + } { |
| + f := newAdoptionFixture(t, rt, tok, key, instancekey.SourcePlatform) |
| + tc.wreck(&f.launch) |
| + if got := f.put(tok, adoptionBody(key, "platform")); got != http.StatusInternalServerError { |
| + t.Fatalf("%s: %d, want 500", tc.name, got) |
| + } |
| + if _, err := os.Stat(filepath.Join(f.launch.Dir, rt.Host+instancekey.AdoptionSuffix)); !os.IsNotExist(err) || len(f.published) != 0 { |
| + t.Fatalf("%s: a record was written or published", tc.name) |
| + } |
| + } |
| +} |