package host import ( "context" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" "github.com/carlosframework/platform/internal/activator" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rollout" ) // liveIdentityBackend is liveLaunchBackend with the exec backend's identity // capability exposed, as production's ExecBackend and SplitBackend expose // it: the activator's cold wake then captures live/ once at step 2b // (LiveIdentity) and runs that file by path (StartGenExe, SeedExe), never // the backend's live-link Start. before runs inside LiveIdentity, before // the capture: root's repoint after the plan. type liveIdentityBackend struct { liveLaunchBackend } func (b *liveIdentityBackend) LiveIdentity(ctx context.Context, rt registry.Route) (release.StagedIdentity, error) { if b.before != nil { b.before() } return b.eb.LiveIdentity(ctx, rt) } func (b *liveIdentityBackend) StartGenExe(ctx context.Context, rt registry.Route, n uint64, perGen bool, id release.StagedIdentity) (activator.Proc, activator.Gen, error) { return b.eb.StartGenExe(ctx, rt, n, perGen, id) } func (b *liveIdentityBackend) SeedExe(ctx context.Context, rt registry.Route, id release.StagedIdentity) error { return b.eb.SeedExe(ctx, rt, id) } // TestDeclinedBoxRefusesAHeldBuildAtLaunch on the path production takes on // this branch (merge of zdd-flag-readiness): the wake plans with the link on // v1, root repoints it at held v2 before the capture, and the capture is // asked of the declined box's gate before it is recorded or run. Nothing of // v2 is exec'd and no build state names it. func TestDeclinedBoxRefusesAHeldCapturedIdentityAtLaunch(t *testing.T) { prev := activator.GenerationsEnabled() activator.SetGenerations(false) t.Cleanup(func() { activator.SetGenerations(prev) }) resetRollout(t) env := newFastFixture(t) t.Setenv("CARLOS_OPT_DIR", env.optDir) host := "warm.test" run := t.TempDir() rt := registry.Route{Host: host, Kind: "instance", App: "fastapp", Account: "bab", Channel: "edge", Status: "ready", Hibernate: true, Version: "v1", GoodVersion: "v1", GoodDigest: "sha256:v1", Socket: filepath.Join(run, host+".sock"), DBPath: filepath.Join(env.dataDir, host+".db")} if err := env.reg.Upsert(rt); err != nil { t.Fatal(err) } env.stageLive(t, host, "v1") mark := filepath.Join(t.TempDir(), "v2.ran") if err := release.StageArtifact(env.optDir, "bab", "v2", "fastapp", []byte("#!/bin/sh\necho ran > '"+mark+"'\nexec sleep 30\n")); err != nil { t.Fatal(err) } writeFailureBook(t, env.dataDir, []rollout.FailureRecord{{Account: release.RouteAccount(rt), App: "fastapp", Build: "v2", Host: "sibling.test"}}, nil) eb := &activator.ExecBackend{Cfg: activator.Config{Bucket: "test-bucket", Idle: time.Hour}, OptDir: env.optDir, DataDir: t.TempDir(), RunDirOverride: run} backend := &liveIdentityBackend{liveLaunchBackend{eb: eb}} svc := activator.New(activator.Config{Bucket: "test-bucket", Idle: time.Hour}, backend, env.reg) svc.Routes = env.reg var recorded []string svc.BeforeExec = func(_ context.Context, _ string, build string) error { recorded = append(recorded, build) return nil } t.Cleanup(func() { sctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() svc.Shutdown(sctx) }) declineWithBook(t, svc, env.reg, env.dataDir) if eb.LiveExecGate == nil { t.Fatal("no LiveExecGate: the box did not decline with its book") } if p := svc.Planner(context.Background(), rt); !isTodaysWake(p) { t.Fatalf("plan with the link on v1 = %+v, want today's wake", p) } backend.before = func() { if err := release.PointLive(env.optDir, host, "bab", "v2", "fastapp"); err != nil { t.Error(err) } } rec := httptest.NewRecorder() proxy := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) }) if !svc.ServeHTTP(rec, httptest.NewRequest("GET", "http://"+host+"/", nil), rt, proxy) { t.Fatal("the activator did not take the request") } if rec.Code == http.StatusNoContent { t.Fatal("the wake onto held v2 was served") } time.Sleep(200 * time.Millisecond) if _, err := os.Stat(mark); err == nil { t.Fatal("held v2 was exec'd by path: the declined gate never saw the captured identity") } if len(recorded) != 0 { t.Fatalf("build states recorded %v: a build the box will not start was named as about to run", recorded) } }