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