e2e/shutdown_test.go
83 lines · 3062 bytes
1package e2e
2
3import (
4 "fmt"
5 "net/http"
6 "os/exec"
7 "path/filepath"
8 "strings"
9 "syscall"
10 "testing"
11 "time"
12)
13
14// SIGTERM is how a deploy restarts the daemon. It used to be a plain
15// kill: no listener closed, no request or push allowed to finish. The
16// daemon now stops its listeners, drains, and exits 0 (#105).
17func TestServeStopsOnSIGTERM(t *testing.T) {
18 inst := startInstance(t)
19 if resp, err := http.Get(inst.base() + "/healthz"); err != nil || resp.StatusCode != 200 {
20 t.Fatalf("healthz before shutdown: %v", err)
21 }
22 if err := inst.proc.Process.Signal(syscall.SIGTERM); err != nil {
23 t.Fatal(err)
24 }
25 done := make(chan error, 1)
26 go func() { done <- inst.proc.Wait() }()
27 select {
28 case err := <-done:
29 if err != nil {
30 t.Fatalf("daemon did not exit cleanly on SIGTERM: %v", err)
31 }
32 case <-time.After(15 * time.Second):
33 t.Fatal("daemon still running 15s after SIGTERM")
34 }
35 if _, err := http.Get(inst.base() + "/healthz"); err == nil {
36 t.Fatal("http listener still answering after shutdown")
37 }
38}
39
40// A CLI keeps a shared connection open between commands. On shutdown that
41// idle connection is closed at once rather than holding the drain for its
42// full 30 s; only a session mid-command is waited for (#141).
43func TestShutdownClosesIdleConnections(t *testing.T) {
44 inst := startInstance(t)
45 aliceKey := inst.newKey(t, "alice")
46 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
47
48 // A control master with no session: -N, backgrounded, persistent.
49 // A unix socket path has a short limit; t.TempDir is far past it here.
50 sock := fmt.Sprintf("/tmp/gitbay-e2e-cm-%d", inst.port)
51 base := []string{"-p", fmt.Sprint(inst.port), "-i", aliceKey, "-o", "IdentitiesOnly=yes",
52 "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=" + filepath.Join(inst.sshDir, "known_hosts"),
53 "-o", "BatchMode=yes", "-o", "ControlPath=" + sock}
54 master := exec.Command("ssh", append(append([]string{}, base...), "-o", "ControlMaster=yes", "-o", "ControlPersist=yes", "-N", "-f", "git@127.0.0.1")...)
55 if out, err := master.CombinedOutput(); err != nil {
56 t.Fatalf("control master: %v\n%s", err, out)
57 }
58 t.Cleanup(func() {
59 exec.Command("ssh", append(append([]string{}, base...), "-O", "exit", "git@127.0.0.1")...).Run()
60 })
61 // The shared connection works.
62 if out, err := exec.Command("ssh", append(append([]string{}, base...), "git@127.0.0.1", "whoami")...).Output(); err != nil || strings.TrimSpace(string(out)) != "alice" {
63 t.Fatalf("multiplexed whoami: %v %q", err, out)
64 }
65
66 start := time.Now()
67 if err := inst.proc.Process.Signal(syscall.SIGTERM); err != nil {
68 t.Fatal(err)
69 }
70 done := make(chan error, 1)
71 go func() { done <- inst.proc.Wait() }()
72 select {
73 case err := <-done:
74 if err != nil {
75 t.Fatalf("daemon did not exit cleanly: %v", err)
76 }
77 case <-time.After(20 * time.Second):
78 t.Fatal("daemon still running 20s after SIGTERM with only an idle connection open")
79 }
80 if took := time.Since(start); took > 5*time.Second {
81 t.Fatalf("shutdown took %s with only an idle connection open", took)
82 }
83}