| 1 | package release |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "testing" |
| 7 | ) |
| 8 | |
| 9 | // Issue #315: a pass that cannot restart a named unit (the agent, User=carlos) |
| 10 | // hands that route to root before it stages or repoints anything. The live |
| 11 | // link keeps naming the running build, the version is not poisoned for the |
| 12 | // other hosts in the pass, and root's pass then installs it normally. |
| 13 | func TestRootRestartsUnitsHandsNamedUnitsToRoot(t *testing.T) { |
| 14 | ctx := context.Background() |
| 15 | s := NewDirStore(t.TempDir()) |
| 16 | priv, pub := testKeys(t) |
| 17 | opt := t.TempDir() |
| 18 | reg := testRegistry(t) |
| 19 | t.Cleanup(func() { reg.Close() }) |
| 20 | const consoleHost, appHost = "console.example.com", "shop.example.com" |
| 21 | for _, ver := range []string{"v1", "v2"} { |
| 22 | shipRelease(t, s, "bab", "myapp", ver, adoptPlatform, []byte(ver)) |
| 23 | } |
| 24 | if err := Promote(ctx, s, priv, "bab", "myapp", "canary/x", "v1"); err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | addRoute(t, reg, consoleHost, "myapp", "canary/x", false) |
| 28 | addRoute(t, reg, appHost, "myapp", "canary/x", false) |
| 29 | if err := Pass(ctx, reg, s, pub, opt, nil); err != nil { |
| 30 | t.Fatal(err) |
| 31 | } |
| 32 | if err := reg.SetUnit(consoleHost, "console.service"); err != nil { |
| 33 | t.Fatal(err) |
| 34 | } |
| 35 | if err := Promote(ctx, s, priv, "bab", "myapp", "canary/x", "v2"); err != nil { |
| 36 | t.Fatal(err) |
| 37 | } |
| 38 | |
| 39 | var restarted []string |
| 40 | restart := func(host string) error { |
| 41 | restarted = append(restarted, host) |
| 42 | if host == consoleHost { |
| 43 | return errors.New("try-restart console.service: Interactive authentication required") |
| 44 | } |
| 45 | return nil |
| 46 | } |
| 47 | type req struct { |
| 48 | host, ver string |
| 49 | err error |
| 50 | } |
| 51 | var reqs []req |
| 52 | relay := func(host, ver string, err error) { reqs = append(reqs, req{host, ver, err}) } |
| 53 | |
| 54 | if err := Pass(ctx, reg, s, pub, opt, restart, OnStageBlocked(relay), RootRestartsUnits()); err != nil { |
| 55 | t.Fatal(err) |
| 56 | } |
| 57 | if len(reqs) != 1 || reqs[0].host != consoleHost || reqs[0].ver != "v2" || !errors.Is(reqs[0].err, ErrUnitRestartNeedsRoot) { |
| 58 | t.Fatalf("relays = %+v, want one for %s v2 with ErrUnitRestartNeedsRoot", reqs, consoleHost) |
| 59 | } |
| 60 | if len(restarted) != 1 || restarted[0] != appHost { |
| 61 | t.Fatalf("restarted = %v, want only %s", restarted, appHost) |
| 62 | } |
| 63 | if ver, err := LiveVersion(opt, consoleHost); err != nil || ver != "v1" { |
| 64 | t.Fatalf("%s live = %q, %v; want v1 (untouched)", consoleHost, ver, err) |
| 65 | } |
| 66 | if rt, err := reg.Get(consoleHost); err != nil || rt == nil || rt.Version != "v1" { |
| 67 | t.Fatalf("%s route = %+v, %v; want stamped v1", consoleHost, rt, err) |
| 68 | } |
| 69 | if ver, err := LiveVersion(opt, appHost); err != nil || ver != "v2" { |
| 70 | t.Fatalf("%s live = %q, %v; want v2 (not poisoned by the hand-off)", appHost, ver, err) |
| 71 | } |
| 72 | if r, err := ReadRefusal(opt, consoleHost); err != nil || r != nil { |
| 73 | t.Fatalf("refusal for %s = %+v, %v; want none", consoleHost, r, err) |
| 74 | } |
| 75 | |
| 76 | // Root's pass, without the option, installs it. |
| 77 | restarted = nil |
| 78 | rootRestart := func(host string) error { restarted = append(restarted, host); return nil } |
| 79 | if err := Pass(ctx, reg, s, pub, opt, rootRestart); err != nil { |
| 80 | t.Fatal(err) |
| 81 | } |
| 82 | if ver, err := LiveVersion(opt, consoleHost); err != nil || ver != "v2" || len(restarted) != 1 || restarted[0] != consoleHost { |
| 83 | t.Fatalf("root pass: %s live = %q, %v, restarted %v; want v2 and one restart", consoleHost, ver, err, restarted) |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | // The option hands nothing over for a route this pass would not restart, and |
| 88 | // leaves routes with no unit to the pass. |
| 89 | func TestRootRestartsUnitsNeedsANamedUnitAndARestart(t *testing.T) { |
| 90 | ctx := context.Background() |
| 91 | s := NewDirStore(t.TempDir()) |
| 92 | priv, pub := testKeys(t) |
| 93 | opt := t.TempDir() |
| 94 | reg := testRegistry(t) |
| 95 | t.Cleanup(func() { reg.Close() }) |
| 96 | const host = "console.example.com" |
| 97 | shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("v1")) |
| 98 | if err := Promote(ctx, s, priv, "bab", "myapp", "canary/x", "v1"); err != nil { |
| 99 | t.Fatal(err) |
| 100 | } |
| 101 | addRoute(t, reg, host, "myapp", "canary/x", false) |
| 102 | if err := reg.SetUnit(host, "console.service"); err != nil { |
| 103 | t.Fatal(err) |
| 104 | } |
| 105 | relays := 0 |
| 106 | relay := func(string, string, error) { relays++ } |
| 107 | // restart nil: a pure repoint, which needs no unit restart. |
| 108 | if err := Pass(ctx, reg, s, pub, opt, nil, OnStageBlocked(relay), RootRestartsUnits()); err != nil { |
| 109 | t.Fatal(err) |
| 110 | } |
| 111 | if ver, err := LiveVersion(opt, host); err != nil || ver != "v1" || relays != 0 { |
| 112 | t.Fatalf("live = %q, %v, relays %d; want v1 and no relay", ver, err, relays) |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | // Astra review of #315: a route with no unit of its own whose process is a |
| 117 | // named unit (same account, app, channel and upstream) is root's too. Stamped |
| 118 | // by the agent it would claim a build console.service does not run. |
| 119 | func TestRootRestartsUnitsHandsTheProcessGroupToRoot(t *testing.T) { |
| 120 | ctx, reg, s, pub, opt := startUnitFixture(t, "console.example.com") |
| 121 | if err := reg.SetUnit("console.example.com", ""); err != nil { |
| 122 | t.Fatal(err) |
| 123 | } |
| 124 | groupFixtureRoute(t, reg, "platform.example.com", "", "127.0.0.1:8090", "console.service") |
| 125 | if err := reg.SetUpstreams("console.example.com", "127.0.0.1:8090", "", ""); err != nil { |
| 126 | t.Fatal(err) |
| 127 | } |
| 128 | if err := PointLive(opt, "platform.example.com", "bab", "old", "console"); err != nil { |
| 129 | t.Fatal(err) |
| 130 | } |
| 131 | var relayed []string |
| 132 | relay := func(host, ver string, err error) { |
| 133 | if errors.Is(err, ErrUnitRestartNeedsRoot) { |
| 134 | relayed = append(relayed, host) |
| 135 | } |
| 136 | } |
| 137 | restart := func(host string) error { |
| 138 | t.Errorf("restart(%s): every host here is the unit's", host) |
| 139 | return nil |
| 140 | } |
| 141 | if err := Pass(ctx, reg, s, pub, opt, restart, OnStageBlocked(relay), RootRestartsUnits()); err != nil { |
| 142 | t.Fatal(err) |
| 143 | } |
| 144 | if len(relayed) != 2 { |
| 145 | t.Fatalf("relayed = %v, want both hosts", relayed) |
| 146 | } |
| 147 | for _, h := range []string{"console.example.com", "platform.example.com"} { |
| 148 | if ver, err := LiveVersion(opt, h); err != nil || ver != "old" { |
| 149 | t.Fatalf("%s live = %q, %v; want old", h, ver, err) |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | |