package edge import ( "net/http" "path/filepath" "sync/atomic" "testing" "github.com/carlosframework/platform/internal/registry" ) // genActivator is an Activator that is also a GenSource: an instant wake // that proxies straight through, over a hand-driven fakeGens. served counts // the requests that reached it, so a test can tell a cache hit (none) from // a proxied request. type genActivator struct { *fakeGens served atomic.Int64 } func (a *genActivator) ServeHTTP(w http.ResponseWriter, r *http.Request, _ registry.Route, proxy http.Handler) bool { a.served.Add(1) proxy.ServeHTTP(w, r) return true } func (a *genActivator) RetireExcept(map[string]bool) {} // genFixture is a registry with one activator-managed instance whose // CANONICAL socket is served by an app answering "canonical", and a // generation-1 app answering "A". Which one a request reaches says which // path the edge took. func genFixture(t *testing.T, on bool, hibernate bool) (*Edge, *genActivator, *genApp) { t.Helper() dir := genSockDir(t) startGenApp(t, filepath.Join(dir, "canon.sock"), body("canonical", "")) a := startGenApp(t, filepath.Join(dir, "a.sock"), body("A", "")) reg := openReg(t) if err := reg.Upsert(registry.Route{Host: "inst.example.com", Kind: "instance", Hibernate: hibernate, Socket: filepath.Join(dir, "canon.sock"), Status: "ready", Version: "v0"}); err != nil { t.Fatal(err) } act := &genActivator{fakeGens: newFakeGens(on)} act.switchTo("inst.example.com", GenInfo{Socket: a.sock, N: 1, Build: "v1"}) e, err := New(reg, act) if err != nil { t.Fatalf("New: %v", err) } return e, act, a } func TestManagedRouteDialsTheCurrentGeneration(t *testing.T) { e, act, _ := genFixture(t, true, true) rec := serve(t, e, "http://inst.example.com/") if rec.Body.String() != "A" { t.Fatalf("body %q, want A: the edge dialled the canonical socket, not the current generation", rec.Body.String()) } if got := rec.Header().Get("X-Carlos-Version"); got != "v1" { t.Fatalf("X-Carlos-Version = %q, want v1, the serving generation's build, not the row's v0", got) } if !e.current()["inst.example.com"].genManaged { t.Fatal("the managed route's upstream is not marked genManaged") } if len(act.retired) != 1 { t.Fatalf("New registered %d retire hooks, want 1 (the edge's pools)", len(act.retired)) } } // Flag off: byte for byte the old edge. unixProxy on the canonical socket, // the row's version, no retire hook, no pools. func TestGenerationsOffKeepsTheCanonicalProxyAndTheRowVersion(t *testing.T) { e, act, _ := genFixture(t, false, true) rec := serve(t, e, "http://inst.example.com/") if rec.Body.String() != "canonical" || rec.Header().Get("X-Carlos-Version") != "v0" { t.Fatalf("got %q version %q, want canonical v0", rec.Body.String(), rec.Header().Get("X-Carlos-Version")) } if e.gens != nil || e.genPools != nil || len(act.retired) != 0 { t.Fatal("generations were wired with the switch off") } up := e.current()["inst.example.com"] if up.genManaged { t.Fatal("upstream marked genManaged with the switch off") } } // An unflagged socket route is an externally supervised process: canonical, // whatever the switch says. func TestUnmanagedSocketRouteStaysCanonicalWithGenerationsOn(t *testing.T) { e, _, _ := genFixture(t, true, false) rec := serve(t, e, "http://inst.example.com/") if rec.Body.String() != "canonical" || rec.Header().Get("X-Carlos-Version") != "v0" { t.Fatalf("got %q version %q, want canonical v0", rec.Body.String(), rec.Header().Get("X-Carlos-Version")) } up := e.current()["inst.example.com"] if up.genManaged { t.Fatal("an unflagged route was marked genManaged") } }