| 1 | //go:build linux && rootrunner |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "crypto/ed25519" |
| 8 | "encoding/base64" |
| 9 | "os" |
| 10 | "path/filepath" |
| 11 | "testing" |
| 12 | |
| 13 | "github.com/carlosframework/platform/internal/release" |
| 14 | ) |
| 15 | |
| 16 | func TestPlan14RootFloorHealthyAdoptRootRunner(t *testing.T) { |
| 17 | scratch := generationRootRunnerDir(t) |
| 18 | t.Setenv("TMPDIR", scratch) |
| 19 | for _, healthy := range []bool{false, true} { |
| 20 | t.Run(map[bool]string{false: "failed health", true: "healthy"}[healthy], func(t *testing.T) { |
| 21 | f, _ := applyHappyVersion(t, "v2.0.0") |
| 22 | f.answer["units:"+filepath.Join(f.a.stagingDir(), "carlos")] += " data-instances.mount" |
| 23 | for path := f.a.optDir; path != scratch; path = filepath.Dir(path) { |
| 24 | if err := os.Chmod(path, 0755); err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | } |
| 28 | t.Setenv("CARLOS_RELEASE_PUBKEY", base64.StdEncoding.EncodeToString(f.pub)) |
| 29 | id := rootFloorIdentity{"deploy", "box", "en"} |
| 30 | f.a.rootMoves = func() (rootFloorIdentity, bool, error) { return id, true, nil } |
| 31 | raw, etag, err := f.store.Get(t.Context(), release.SystemPointerKey("router", "stable")) |
| 32 | if err != nil { |
| 33 | t.Fatal(err) |
| 34 | } |
| 35 | var p release.SystemPointer |
| 36 | if err := release.VerifyRootEnvelope(f.pub, raw, &p); err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | p.Current.ReleaseTag = "v2.0.0" |
| 40 | p.Current.MovesProtocol = 1 |
| 41 | wire, err := release.SignRootEnvelope(release.RootSigner(f.priv), p) |
| 42 | if err != nil { |
| 43 | t.Fatal(err) |
| 44 | } |
| 45 | if _, err := f.store.Put(t.Context(), release.SystemPointerKey("router", "stable"), wire, etag); err != nil { |
| 46 | t.Fatal(err) |
| 47 | } |
| 48 | rootFloorSignRunnerBytes(t, f.a.optDir, systemBinaryPath(f.a.optDir), "v1.0.0", []byte("#!/bin/sh\necho carlos v1.0.0\n"), f.priv) |
| 49 | f.answer["version:"+systemPrevPath(f.a.optDir)] = "carlos v1.0.0\n" |
| 50 | if !healthy { |
| 51 | f.probeErr = os.ErrInvalid |
| 52 | } |
| 53 | err = f.a.Run(context.Background(), false) |
| 54 | floors, e := openRootMovesFloors(f.a.optDir, id, false) |
| 55 | if e != nil { |
| 56 | t.Fatal(e) |
| 57 | } |
| 58 | defer floors.close() |
| 59 | _, accepted := floors.record(rootFloorAccepted) |
| 60 | if healthy { |
| 61 | if err != nil || accepted != nil { |
| 62 | t.Fatal("healthy apply did not accept", err, accepted) |
| 63 | } |
| 64 | h, e := floors.healthy() |
| 65 | if e != nil || h.Version != "v2.0.0" { |
| 66 | t.Fatal("wrong accepted build", h, e) |
| 67 | } |
| 68 | } else if accepted == nil { |
| 69 | t.Fatal("failed health accepted a floor") |
| 70 | } |
| 71 | }) |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | func TestPlan14RootFloorUnknownCrashRecoveryRootRunner(t *testing.T) { |
| 76 | scratch := generationRootRunnerDir(t) |
| 77 | t.Setenv("TMPDIR", scratch) |
| 78 | f, _ := applyHappyVersion(t, "v2.0.0") |
| 79 | for path := f.a.optDir; path != scratch; path = filepath.Dir(path) { |
| 80 | if err := os.Chmod(path, 0755); err != nil { |
| 81 | t.Fatal(err) |
| 82 | } |
| 83 | } |
| 84 | f.brokenBox("v2.0.0", "v1.0.0", "v1.0.0") |
| 85 | t.Setenv("CARLOS_RELEASE_PUBKEY", base64.StdEncoding.EncodeToString(f.pub)) |
| 86 | id := rootFloorIdentity{"deploy", "box", "en"} |
| 87 | f.a.rootMoves = func() (rootFloorIdentity, bool, error) { return id, true, nil } |
| 88 | rootFloorSignRunnerBytes(t, f.a.optDir, systemBinaryPath(f.a.optDir), "v2.0.0", []byte("#!/bin/sh\nexit 99\n"), f.priv) |
| 89 | rootFloorSignRunnerBytes(t, f.a.optDir, systemPrevPath(f.a.optDir), "v1.0.0", []byte("#!/bin/sh\necho carlos v1.0.0\n"), f.priv) |
| 90 | delete(f.answer, "version:"+systemBinaryPath(f.a.optDir)) |
| 91 | f.a.openStore = func(context.Context) (release.Store, ed25519.PublicKey, error) { |
| 92 | t.Fatal("offline recovery opened provider") |
| 93 | return nil, nil, os.ErrPermission |
| 94 | } |
| 95 | restored, err := f.a.recoverIfNeeded(t.Context()) |
| 96 | if err != nil || !restored { |
| 97 | t.Fatal("unknown-history restore failed", restored, err) |
| 98 | } |
| 99 | b, err := readRootFloorBinary(f.a.optDir, systemBinaryPath(f.a.optDir)) |
| 100 | if err != nil || b.Version != "v1.0.0" { |
| 101 | t.Fatal("prev not restored", b, err) |
| 102 | } |
| 103 | if f.result() == nil || f.result().Outcome != systemOutcomeRolledBack { |
| 104 | t.Fatal("recovery did not finish", f.result()) |
| 105 | } |
| 106 | if !f.called("systemctl restart --no-block " + systemEdgeUnit) { |
| 107 | t.Fatal("recovery did not schedule edge", f.calls) |
| 108 | } |
| 109 | } |
| 110 | |