package host import ( "context" "errors" "github.com/carlosframework/platform/internal/provision" "os" "path/filepath" "testing" "time" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootstate" ) type activationPreflightOwner struct{ calls int } func (p *activationPreflightOwner) OpenClaimOwners(context.Context, release.Store, ClaimRuntimeResources) (ClaimOwnerBindings, error) { p.calls++ return ClaimOwnerBindings{}, os.ErrNotExist } func TestPlan14ActivationNamedUnitPreflight(t *testing.T) { oldOpen, oldShapes := openClaimActivationState, productionClaimUnitShapes t.Cleanup(func() { openClaimActivationState, productionClaimUnitShapes = oldOpen, oldShapes }) openClaimActivationState = func(string, bool, bool) (rootstate.Store, error) { return nil, os.ErrNotExist } productionClaimUnitShapes = func() (map[string]claimUnitShape, error) { return map[string]claimUnitShape{}, nil } for _, mode := range []string{"agent", "adopt", "instance-control"} { for _, kind := range []string{"named", "backing", "ordinary", "empty"} { t.Run(mode+"/"+kind, func(t *testing.T) { dir := t.TempDir() reg, err := registry.Open(filepath.Join(dir, "registry.db")) if err != nil { t.Fatal(err) } defer reg.Close() row := registry.Route{Host: "console.example", Account: "bab", App: "console", Socket: "/run/console.sock"} if kind == "named" { row.Unit = "console.service" } if kind == "backing" { row.Backing = "unit" } if kind != "empty" { if err := reg.Upsert(row); err != nil { t.Fatal(err) } } p := &activationPreflightOwner{} r := ClaimRuntimeResources{Mode: mode, Registry: reg, DataDir: filepath.Join(dir, "data"), OptDir: filepath.Join(dir, "opt"), UnitsDir: filepath.Join(dir, "units")} _, err = OpenProductionClaimAssembly(t.Context(), release.NewDirStore(filepath.Join(dir, "bucket")), r, p) blocked := !claimNamedUnit4aComplete && (kind == "named" || kind == "backing") if blocked { if !errors.Is(err, hostwrite.ErrUnavailable) || p.calls != 0 { t.Fatalf("preflight=%v provider calls=%d", err, p.calls) } } else if !errors.Is(err, os.ErrNotExist) || p.calls != 1 { t.Fatalf("ordinary construction=%v calls=%d", err, p.calls) } if mode == "instance-control" { p.calls = 0 err = migrateClaimJournalsWith(t.Context(), release.NewDirStore(filepath.Join(dir, "bucket")), r, p, true) if blocked && (!errors.Is(err, hostwrite.ErrUnavailable) || p.calls != 0) { t.Fatalf("migration preflight: %v calls=%d", err, p.calls) } if !blocked && (!errors.Is(err, os.ErrNotExist) || p.calls != 1) { t.Fatalf("migration ordinary: %v calls=%d", err, p.calls) } } for _, path := range []string{r.DataDir, r.OptDir, r.UnitsDir} { if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) { t.Fatalf("claim state created: %s %v", path, err) } } }) } } } func TestPlan14ActivationRejectsHiddenNamedRecords(t *testing.T) { for _, attack := range []string{"omitted", "cleared"} { t.Run(attack, func(t *testing.T) { t.Setenv("CARLOS_REGION", "") store := release.NewDirStore(t.TempDir()) plan14PutRecord(t, store, provision.Record{Schema: 1, Host: "console.example", Account: "bab", App: "console", Channel: "stable", Unit: "console.service", CreatedAt: time.Now()}) reg := restartTestRegistry(t) if attack == "cleared" { if err := reg.Upsert(registry.Route{Host: "console.example", Account: "bab", App: "console", Socket: "/run/console.sock"}); err != nil { t.Fatal(err) } } for _, mode := range []string{"agent", "instance-control"} { p := &activationPreflightOwner{} dir := t.TempDir() r := ClaimRuntimeResources{Mode: mode, Registry: reg, DataDir: dir, OptDir: dir, UnitsDir: dir} if _, err := OpenProductionClaimAssembly(t.Context(), store, r, p); !errors.Is(err, hostwrite.ErrUnavailable) || p.calls != 0 { t.Fatal("hidden named route entered construction", err, p.calls) } r.Mode = "instance-control" if err := migrateClaimJournalsWith(t.Context(), store, r, p, true); !errors.Is(err, hostwrite.ErrUnavailable) || p.calls != 0 { t.Fatal("hidden named route entered migration", err, p.calls) } } }) } } func TestPlan14ActivationRejectsHiddenLocalUnits(t *testing.T) { for _, unit := range []string{"carlos-app@shop.example.service", "console.service", "woodstar.service.d/override.conf"} { for _, attack := range []string{"omitted", "cleared"} { t.Run(unit+"/"+attack, func(t *testing.T) { reg := restartTestRegistry(t) if attack == "cleared" { if err := reg.Upsert(registry.Route{Host: "shop.example", Account: "bab", App: "shop", Socket: "/run/shop.sock"}); err != nil { t.Fatal(err) } } dir := t.TempDir() units := filepath.Join(dir, "units") path := filepath.Join(units, unit) if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte("[Service]\nExecStart=/opt/carlos/live/shop.example serve\n"), 0644); err != nil { t.Fatal(err) } store := release.NewDirStore(filepath.Join(dir, "bucket")) for _, mode := range []string{"agent", "instance-control"} { p := &activationPreflightOwner{} r := ClaimRuntimeResources{Mode: mode, Registry: reg, DataDir: filepath.Join(dir, "data"), OptDir: filepath.Join(dir, "opt"), UnitsDir: units} if _, err := OpenProductionClaimAssembly(t.Context(), store, r, p); !errors.Is(err, hostwrite.ErrUnavailable) || p.calls != 0 { t.Fatal(err, p.calls) } r.Mode = "instance-control" if err := migrateClaimJournalsWith(t.Context(), store, r, p, true); !errors.Is(err, hostwrite.ErrUnavailable) || p.calls != 0 { t.Fatal(err, p.calls) } for _, path := range []string{r.DataDir, r.OptDir} { if _, err := os.Stat(path); !os.IsNotExist(err) { t.Fatal("created state", path, err) } } } }) } } }