| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "crypto/ed25519" |
| 6 | "crypto/rand" |
| 7 | "encoding/json" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | "time" |
| 11 | |
| 12 | "github.com/carlosframework/platform/internal/hostactors" |
| 13 | "github.com/carlosframework/platform/internal/hostclaim" |
| 14 | "github.com/carlosframework/platform/internal/hostops" |
| 15 | "github.com/carlosframework/platform/internal/release" |
| 16 | ) |
| 17 | |
| 18 | func TestClaimHoldRecoveryInvalidatesUnusedStartAndPreservesActiveHold(t *testing.T) { |
| 19 | for _, active := range []bool{false, true} { |
| 20 | t.Run(map[bool]string{false: "unused", true: "active"}[active], func(t *testing.T) { |
| 21 | ctx := context.Background() |
| 22 | store := release.NewDirStore(t.TempDir()) |
| 23 | pub, key, err := ed25519.GenerateKey(rand.Reader) |
| 24 | if err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | signer := release.RootSigner(key) |
| 28 | enrollment := strings.Repeat("a", 32) |
| 29 | before, err := hostclaim.Read(ctx, store, "app.example") |
| 30 | if err != nil { |
| 31 | t.Fatal(err) |
| 32 | } |
| 33 | roster := hostactors.Roster{V: 1, Revision: 1, DeploymentBucket: "bucket", Boxes: map[string]hostactors.Box{"box": {Enrollment: enrollment, Namespace: "platform", BoxID: "box", Label: "box", Region: "ie", Pool: "default", Cloud: hostactors.Cloud{Provider: "aws", Account: "123", Region: "eu-west-1", InstanceID: "i-box"}, EvidenceSHA256: strings.Repeat("b", 64), State: "active"}}, Consoles: map[string]hostactors.Console{}, Holds: map[string]hostactors.Reservation{"hold": {ID: "hold", Host: before.Host, OpID: "op", ExpectedClaimETag: before.ETag, FloorSHA256: strings.Repeat("f", 64), Actors: map[string]string{"box:box": enrollment}, Releases: map[string]hostactors.Report{"box:box": {Enrollment: enrollment}}}}, Activations: map[string]hostactors.Reservation{}} |
| 34 | wire, err := release.SignRootEnvelope(signer, roster) |
| 35 | if err != nil { |
| 36 | t.Fatal(err) |
| 37 | } |
| 38 | if _, err = store.Put(ctx, hostactors.Key, wire, ""); err != nil { |
| 39 | t.Fatal(err) |
| 40 | } |
| 41 | if active { |
| 42 | if _, err = hostclaim.Update(ctx, store, before, func(c *hostclaim.Claim) error { |
| 43 | now := time.Now().UTC() |
| 44 | n := hostclaim.NewHolder{Family: "instance", Key: "apps/bab/app/instances/app.example.json", Account: "bab", App: "app", Host: before.Host, Region: "ie", CreatedAt: now} |
| 45 | body, _ := json.Marshal(map[string]any{"host": before.Host, "account": "bab", "app": "app", "created_at": now}) |
| 46 | if err := hostclaim.BeginCreate(n, body, "op", "test", now)(c); err != nil { |
| 47 | return err |
| 48 | } |
| 49 | c.Op.Intent["actors_hold"] = "hold" |
| 50 | return nil |
| 51 | }); err != nil { |
| 52 | t.Fatal(err) |
| 53 | } |
| 54 | } |
| 55 | directory := hostactors.Directory{Store: store, PublicKey: pub, Bucket: "bucket"} |
| 56 | s := &Server{cfg: Config{HostClaim: true, ClaimAuthority: &hostops.ProductionAuthority{Store: store, Directory: directory, Signer: signer}}} |
| 57 | if err = s.SweepClaimHolds(ctx); err != nil { |
| 58 | t.Fatal(err) |
| 59 | } |
| 60 | after, err := directory.Read(ctx) |
| 61 | if err != nil { |
| 62 | t.Fatal(err) |
| 63 | } |
| 64 | _, exists := after.Roster.Holds["hold"] |
| 65 | if exists != active { |
| 66 | t.Fatalf("hold exists=%v active=%v", exists, active) |
| 67 | } |
| 68 | if !active { |
| 69 | if _, err = hostclaim.Update(ctx, store, before, func(*hostclaim.Claim) error { return nil }); err == nil { |
| 70 | t.Fatal("unused start observation remained usable") |
| 71 | } |
| 72 | } |
| 73 | }) |
| 74 | } |
| 75 | } |
| 76 | |