| 1 | package edge |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | |
| 7 | "github.com/carlosframework/platform/internal/registry" |
| 8 | ) |
| 9 | |
| 10 | // A hibernate-flagged instance route on a box with no activator has nothing |
| 11 | // on that box that would ever spawn its process, and before this it said so |
| 12 | // nowhere: the row is real and the socket path is derived, so a request |
| 13 | // reaching it here dials a socket nothing will bind and comes back as a |
| 14 | // bodiless 502 that reads as a broken backend. Reload must name the host, |
| 15 | // the socket, and the fix. |
| 16 | // |
| 17 | // The message is deliberately careful about what it claims — it reports what |
| 18 | // this box knows, not that every request 502s, since it cannot see whether |
| 19 | // something outside the platform is already bound to that socket. |
| 20 | func TestReloadNamesAHibernatingRouteWithNoActivator(t *testing.T) { |
| 21 | buf := captureLog(t) |
| 22 | |
| 23 | reg := openReg(t) |
| 24 | // Socket-backed and hibernate-flagged: exactly what provision.Reconcile |
| 25 | // mints for a plain instance record, which is the shape that stranded. |
| 26 | if err := reg.Upsert(registry.Route{ |
| 27 | Host: "region-probe.example.com", Kind: "instance", |
| 28 | Socket: "/run/carlos/region-probe.example.com.sock", |
| 29 | Status: "ready", Hibernate: true, |
| 30 | }); err != nil { |
| 31 | t.Fatalf("upsert: %v", err) |
| 32 | } |
| 33 | // A nil activator is the whole point: this is a box whose host.env never |
| 34 | // named a CARLOS_HIBERNATE_BUCKET, so internal/host armed nothing. |
| 35 | if _, err := New(reg, nil); err != nil { |
| 36 | t.Fatalf("New: %v", err) |
| 37 | } |
| 38 | |
| 39 | got := buf.String() |
| 40 | for _, want := range []string{ |
| 41 | "region-probe.example.com", |
| 42 | "/run/carlos/region-probe.example.com.sock", |
| 43 | "CARLOS_HIBERNATE_BUCKET", |
| 44 | "carlos edge", |
| 45 | } { |
| 46 | if !strings.Contains(got, want) { |
| 47 | t.Errorf("reload log does not mention %q — an operator reading the journal still cannot tell this from a broken app.\ngot: %s", want, got) |
| 48 | } |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | // The complaint is about the ACTIVATOR, not about instances in general: a box |
| 53 | // with an activator armed is working exactly as designed and must say |
| 54 | // nothing, or the line becomes noise on every healthy box in the fleet. |
| 55 | func TestReloadIsSilentWhenTheActivatorIsArmed(t *testing.T) { |
| 56 | buf := captureLog(t) |
| 57 | |
| 58 | reg := openReg(t) |
| 59 | if err := reg.Upsert(registry.Route{ |
| 60 | Host: "healthy.example.com", Kind: "instance", |
| 61 | Socket: "/run/carlos/healthy.example.com.sock", |
| 62 | Status: "ready", Hibernate: true, |
| 63 | }); err != nil { |
| 64 | t.Fatalf("upsert: %v", err) |
| 65 | } |
| 66 | if _, err := New(reg, &fakeActivator{}); err != nil { |
| 67 | t.Fatalf("New: %v", err) |
| 68 | } |
| 69 | |
| 70 | if got := buf.String(); strings.Contains(got, "no activator") { |
| 71 | t.Errorf("complained about an armed box: %s", got) |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | // An UNFLAGGED instance is proxied straight by design — that is the |
| 76 | // deploy-safety property that lets the hibernate env ship fleet-wide as a |
| 77 | // no-op before any host is flagged — so a box with no activator must not |
| 78 | // complain about one either. |
| 79 | func TestReloadIsSilentForAnUnflaggedInstance(t *testing.T) { |
| 80 | buf := captureLog(t) |
| 81 | |
| 82 | reg := openReg(t) |
| 83 | if err := reg.Upsert(registry.Route{ |
| 84 | Host: "unflagged.example.com", Kind: "instance", |
| 85 | Socket: "/run/carlos/unflagged.example.com.sock", |
| 86 | Status: "ready", |
| 87 | }); err != nil { |
| 88 | t.Fatalf("upsert: %v", err) |
| 89 | } |
| 90 | if _, err := New(reg, nil); err != nil { |
| 91 | t.Fatalf("New: %v", err) |
| 92 | } |
| 93 | |
| 94 | if got := buf.String(); strings.Contains(got, "no activator") { |
| 95 | t.Errorf("complained about an unflagged instance: %s", got) |
| 96 | } |
| 97 | } |
| 98 | |