e2e/shutdown_test.go

419f6dfdc5489a0c6374e36dd1ebbfca68040056
gitbay/e2e/shutdown_test.go history · blame · raw

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}