File: testbin_test.go

package info (click to toggle)
golang-github-facebookgo-grace 0.0~git20170218.0.4afe952-7
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, sid
  • size: 164 kB
  • sloc: makefile: 6
file content (252 lines) | stat: -rw-r--r-- 8,414 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
package gracehttp_test

import (
	"crypto/tls"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/facebookgo/grace/gracehttp"
)

const preStartProcessEnv = "GRACEHTTP_PRE_START_PROCESS"

func TestMain(m *testing.M) {
	const (
		testbinKey   = "GRACEHTTP_TEST_BIN"
		testbinValue = "1"
	)
	if os.Getenv(testbinKey) == testbinValue {
		testbinMain()
		return
	}
	if err := os.Setenv(testbinKey, testbinValue); err != nil {
		panic(err)
	}
	os.Exit(m.Run())
}

type response struct {
	Sleep time.Duration
	Pid   int
	Error string `json:",omitempty"`
}

// Wait for 10 consecutive responses from our own pid.
//
// This prevents flaky tests that arise from the fact that we have the
// perfectly acceptable (read: not a bug) condition where both the new and the
// old servers are accepting requests. In fact the amount of time both are
// accepting at the same time and the number of requests that flip flop between
// them is unbounded and in the hands of the various kernels our code tends to
// run on.
//
// In order to combat this, we wait for 10 successful responses from our own
// pid. This is a somewhat reliable way to ensure the old server isn't
// serving anymore.
func wait(wg *sync.WaitGroup, url string) {
	var success int
	defer wg.Done()
	for {
		res, err := http.Get(url)
		if err == nil {
			// ensure it isn't a response from a previous instance
			defer res.Body.Close()
			var r response
			if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
				log.Fatalf("Error decoding json: %s", err)
			}
			if r.Pid == os.Getpid() {
				success++
				if success == 10 {
					return
				}
				continue
			}
		} else {
			success = 0
			// we expect connection refused
			if !strings.HasSuffix(err.Error(), "connection refused") {
				e2 := json.NewEncoder(os.Stderr).Encode(&response{
					Error: err.Error(),
					Pid:   os.Getpid(),
				})
				if e2 != nil {
					log.Fatalf("Error writing error json: %s", e2)
				}
			}
		}
	}
}

func httpsServer(addr string) *http.Server {
	cert, err := tls.X509KeyPair(localhostCert, localhostKey)
	if err != nil {
		log.Fatalf("error loading cert: %v", err)
	}
	return &http.Server{
		Addr:    addr,
		Handler: newHandler(),
		TLSConfig: &tls.Config{
			NextProtos:   []string{"http/1.1"},
			Certificates: []tls.Certificate{cert},
		},
	}
}

func testbinMain() {
	var httpAddr, httpsAddr string
	var testOption int
	flag.StringVar(&httpAddr, "http", ":48560", "http address to bind to")
	flag.StringVar(&httpsAddr, "https", ":48561", "https address to bind to")
	flag.IntVar(&testOption, "testOption", -1, "which option to test on ServeWithOptions")
	flag.Parse()

	// we have self signed certs
	http.DefaultTransport = &http.Transport{
		DisableKeepAlives: true,
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
	}

	// print json to stderr once we can successfully connect to all three
	// addresses. the ensures we only print the line once we're ready to serve.
	go func() {
		var wg sync.WaitGroup
		wg.Add(2)
		go wait(&wg, fmt.Sprintf("http://%s/sleep/?duration=1ms", httpAddr))
		go wait(&wg, fmt.Sprintf("https://%s/sleep/?duration=1ms", httpsAddr))
		wg.Wait()

		err := json.NewEncoder(os.Stderr).Encode(&response{Pid: os.Getpid()})
		if err != nil {
			log.Fatalf("Error writing startup json: %s", err)
		}
	}()

	servers := []*http.Server{
		&http.Server{Addr: httpAddr, Handler: newHandler()},
		httpsServer(httpsAddr),
	}

	if testOption == -1 {
		err := gracehttp.Serve(servers...)
		if err != nil {
			log.Fatalf("Error in gracehttp.Serve: %s", err)
		}
	} else {
		if testOption == testPreStartProcess {
			switch os.Getenv(preStartProcessEnv) {
			case "":
				err := os.Setenv(preStartProcessEnv, "READY")
				if err != nil {
					log.Fatalf("testbin (first incarnation) could not set %v to 'ready': %v", preStartProcessEnv, err)
				}
			case "FIRED":
				// all good, reset for next round
				err := os.Setenv(preStartProcessEnv, "READY")
				if err != nil {
					log.Fatalf("testbin (second incarnation) could not reset %v to 'ready': %v", preStartProcessEnv, err)
				}
			case "READY":
				log.Fatalf("failure to update startup hook before new process started")
			default:
				log.Fatalf("something strange happened with %v: it ended up as %v, which is not '', 'FIRED', or 'READY'", preStartProcessEnv, os.Getenv(preStartProcessEnv))
			}

			err := gracehttp.ServeWithOptions(
				servers,
				gracehttp.PreStartProcess(func() error {
					err := os.Setenv(preStartProcessEnv, "FIRED")
					if err != nil {
						log.Fatalf("startup hook could not set %v to 'fired': %v", preStartProcessEnv, err)
					}
					return nil
				}),
			)
			if err != nil {
				log.Fatalf("Error in gracehttp.Serve: %s", err)
			}
		}
	}
}

func newHandler() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("/sleep/", func(w http.ResponseWriter, r *http.Request) {
		duration, err := time.ParseDuration(r.FormValue("duration"))
		if err != nil {
			http.Error(w, err.Error(), 400)
		}
		time.Sleep(duration)
		err = json.NewEncoder(w).Encode(&response{
			Sleep: duration,
			Pid:   os.Getpid(),
		})
		if err != nil {
			log.Fatalf("Error encoding json: %s", err)
		}
	})
	return mux
}

// localhostCert is a PEM-encoded TLS cert with SAN IPs
// "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
// of ASN.1 time).
// generated from src/pkg/crypto/tls:
// go run generate_cert.go  --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
MIIC5DCCAcygAwIBAgIQXO4QJIKF7QigtZiEtBiwJjANBgkqhkiG9w0BAQsFADAA
MB4XDTE2MDEyMzE3MTg0NVoXDTI2MDEyMDE3MTg0NVowADCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBANPdwufJajG29dPoETUKmyRzZiiezBXBEuG1eoCR
GASEldPsfwSAFOtNXv5m5/bgd4mm01+u4eXvlecDPuqZWyc52U/BkiBnLuQoBo5a
VxjGv1A6QFgElJ8SRZXd+9yxZqlyHVYH4efOoU0u62LdgQiJ99Rkry2aMny2PLKP
oSwTcHOYxSaJhFBNEqSZlOfhOsBR22y4w6VCNb3+jFWhNzaYee3oegQLhArjm3+m
HtXJHIkkknC8lTi74jupv6RmEGadnmDZEhsKPA+kqjYzfIWjES7M04AHg6G8LpkZ
yQxfOtALpkdF+GHblzBCa1zN5S8JRADVMmRRbGH/wTEz6WUCAwEAAaNaMFgwDgYD
VR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
Af8CAQEwHQYDVR0OBBYEFPq1RmsNXdqrPcJ9uI4rPxHJWjv1MA0GCSqGSIb3DQEB
CwUAA4IBAQBv8pqjyBLdwdow0aG67rg6Rmz6pIN0QYzMqPc19K8WTXme+H7o4BNT
I13K8S4qT1t+uTcBwh2dUACUnq6oFbBNDH6k68ziEy5MtcbJM6CqkWlyqdLIbjwa
mrhP0kUUk5H23xSKEOwSrDJRukBngSzmadCYRYTz7ni6/AriiP0U9CLNOQfV8SbF
vrY07rj6MTQc6GsYKrM9/hPQaYc++xp9A33OKTzDjr4cIxIzo9nlLUEz+bRceQ+V
wLj1tZJxGgA1Ewyc+Z5D8j+YOKSoNSAeFEjTD4zhRCJyjEtQoyPbk4ADE802o1bd
HnH/LoLCHEWcDxthmefQCPgUII4rif/L
-----END CERTIFICATE-----`)

// localhostKey is the private key for localhostCert.
var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA093C58lqMbb10+gRNQqbJHNmKJ7MFcES4bV6gJEYBISV0+x/
BIAU601e/mbn9uB3iabTX67h5e+V5wM+6plbJznZT8GSIGcu5CgGjlpXGMa/UDpA
WASUnxJFld373LFmqXIdVgfh586hTS7rYt2BCIn31GSvLZoyfLY8so+hLBNwc5jF
JomEUE0SpJmU5+E6wFHbbLjDpUI1vf6MVaE3Nph57eh6BAuECuObf6Ye1ckciSSS
cLyVOLviO6m/pGYQZp2eYNkSGwo8D6SqNjN8haMRLszTgAeDobwumRnJDF860Aum
R0X4YduXMEJrXM3lLwlEANUyZFFsYf/BMTPpZQIDAQABAoIBAQC6cz/SkhvFspj2
qxVxk2rjEjeGafF695YxUm+Dc60qVLAyd790a299gHKn+lILnpE0b783RoWAwG8w
hVe6R8nDZJKNMPHzWDsZCOx0HKbnpAi7hvgXPbi5oO/iKyA6oViSqF2O15MEWID1
luQJ9ptWs2yJ2y2bOUdTH2GdVu9lA/9e0wV55fx9uFj4pHODaJm1qKOl1Jk0i7zy
wUGzmJgXvzYnq9h7XErfPcOsh6Rpkc9a0VpKVgmZtBzzikqcM62OgO9BxqCqFN7U
iBiR9Xq5FyfaeXnPYW/qV1i3dgdd4wRmO3ksQNSPf7cLE122WbVuYFw5dIiyw7lK
dsyXDW7FAoGBAPurwwBk0n3p0BlZDXx56JO/IOjfP+p4skoG9opxcOVznA0zA9p0
pSezNecimyh6AiPFApruLvIRk+crMZQRVADEQ/MhzvXedEvevtI8SlEKrV9UgRcn
bp62EZqagf1AJEDWC6G+DxEv1s3ZF4Y7bdHWLDFAv4Ciw7bjQZVDdDq/AoGBANeC
uAYUtQDxLMuOAWidW6SX4wMUg05AiVPAKka7NIq8H1ylFNpnEjYi9Af4BZeA1/IO
eQWap101qbqW/tIlgnwR6NSLfLSvFCSon0ysQ9lHNJhmC7YOoDQRQHQssIrbJaN3
Q6JxVZ10Un8IowqxDZIVfXCFuw5fNorOv9hAbFjbAoGAJzBGzB/m+v5WjivkwrZE
9gSz/i8NR9iFgqt05nflqYUIDrIb7n9tXDI2uYgU+weMn79EuZVPMBh2nG+IZ9MO
7pOhNRHVpUl/eHT158zFkbsE5ixFcbKNMh+NvDJE/YdoXcQ2yXfL5tQ5MZKVbCyC
3ELqXL0FVOWDbk4S30hCqAcCgYEAo2+R6aKohjdgllpyPQkhJ9i8I2jaD20n+CjC
pvNv7EqwqgzTnLIQAJhPYv+4FeZzXjGVnCdmB20b89JxG6OwqjDW1uGVyF0CNK7g
aEA4ED5M58pz1TSQUAxJShFeLV/20lovI7E5kXhW29oL857EQOYlREFW05Zngas7
mF97C4MCgYEAj3bNc1qsHiHXlpzhEA3zWSOVMI4gBin9NZihrr8FO5W621BD8uu7
oWAkjT0AziDvmjdnODdUcAGOd3KX5bYpKuiYCNN3E6MvOhreaurJH23xJM+nsJcR
zoVMfHWf6lDLzBU96kHtQzaokNoq3TNr8N8prqu2h9poyu5ZNhcrN0Q=
-----END RSA PRIVATE KEY-----`)