Skip to content

Commit 66db254

Browse files
Merge pull request #95981 from caesarxuchao/http2-healthcheck
Enables HTTP/2 health check Kubernetes-commit: afeac926fa79179bfd0c5e718459086845a2a80c
2 parents 7c9ea22 + 8dde295 commit 66db254

File tree

4 files changed

+168
-4
lines changed

4 files changed

+168
-4
lines changed

Diff for: Godeps/Godeps.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ require (
2727
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
2828
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
2929
k8s.io/api v0.0.0-20201114085527-4a626d306b98
30-
k8s.io/apimachinery v0.0.0-20201114085355-859536f6dc9b
30+
k8s.io/apimachinery v0.0.0-20201118005411-2456ebdaba22
3131
k8s.io/klog/v2 v2.4.0
3232
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
3333
sigs.k8s.io/yaml v1.2.0
3434
)
3535

3636
replace (
3737
k8s.io/api => k8s.io/api v0.0.0-20201114085527-4a626d306b98
38-
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20201114085355-859536f6dc9b
38+
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20201118005411-2456ebdaba22
3939
)

Diff for: go.sum

+1-1
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
434434
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
435435
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
436436
k8s.io/api v0.0.0-20201114085527-4a626d306b98/go.mod h1:Vaqh9qFKpET0Mx+jNQHyAcNFyvwkGvuIKOt2htB36BQ=
437-
k8s.io/apimachinery v0.0.0-20201114085355-859536f6dc9b/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
437+
k8s.io/apimachinery v0.0.0-20201118005411-2456ebdaba22/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
438438
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
439439
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
440440
k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=

Diff for: rest/connection_test.go

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rest
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"io"
23+
"net"
24+
"net/http"
25+
"net/http/httptest"
26+
"net/url"
27+
"os"
28+
"strconv"
29+
"sync/atomic"
30+
"testing"
31+
"time"
32+
33+
"k8s.io/apimachinery/pkg/runtime/schema"
34+
"k8s.io/apimachinery/pkg/runtime/serializer"
35+
utilnet "k8s.io/apimachinery/pkg/util/net"
36+
)
37+
38+
type tcpLB struct {
39+
t *testing.T
40+
ln net.Listener
41+
serverURL string
42+
dials int32
43+
}
44+
45+
func (lb *tcpLB) handleConnection(in net.Conn, stopCh chan struct{}) {
46+
out, err := net.Dial("tcp", lb.serverURL)
47+
if err != nil {
48+
lb.t.Log(err)
49+
return
50+
}
51+
go io.Copy(out, in)
52+
go io.Copy(in, out)
53+
<-stopCh
54+
if err := out.Close(); err != nil {
55+
lb.t.Fatalf("failed to close connection: %v", err)
56+
}
57+
}
58+
59+
func (lb *tcpLB) serve(stopCh chan struct{}) {
60+
conn, err := lb.ln.Accept()
61+
if err != nil {
62+
lb.t.Fatalf("failed to accept: %v", err)
63+
}
64+
atomic.AddInt32(&lb.dials, 1)
65+
go lb.handleConnection(conn, stopCh)
66+
}
67+
68+
func newLB(t *testing.T, serverURL string) *tcpLB {
69+
ln, err := net.Listen("tcp", "127.0.0.1:0")
70+
if err != nil {
71+
t.Fatalf("failed to bind: %v", err)
72+
}
73+
lb := tcpLB{
74+
serverURL: serverURL,
75+
ln: ln,
76+
t: t,
77+
}
78+
return &lb
79+
}
80+
81+
func setEnv(key, value string) func() {
82+
originalValue := os.Getenv(key)
83+
os.Setenv(key, value)
84+
return func() {
85+
os.Setenv(key, originalValue)
86+
}
87+
}
88+
89+
const (
90+
readIdleTimeout int = 1
91+
pingTimeout int = 1
92+
)
93+
94+
func TestReconnectBrokenTCP(t *testing.T) {
95+
defer setEnv("HTTP2_READ_IDLE_TIMEOUT_SECONDS", strconv.Itoa(readIdleTimeout))()
96+
defer setEnv("HTTP2_PING_TIMEOUT_SECONDS", strconv.Itoa(pingTimeout))()
97+
defer setEnv("DISABLE_HTTP2", "")()
98+
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
99+
fmt.Fprintf(w, "Hello, %s", r.Proto)
100+
}))
101+
ts.EnableHTTP2 = true
102+
ts.StartTLS()
103+
defer ts.Close()
104+
105+
u, err := url.Parse(ts.URL)
106+
if err != nil {
107+
t.Fatalf("failed to parse URL from %q: %v", ts.URL, err)
108+
}
109+
lb := newLB(t, u.Host)
110+
defer lb.ln.Close()
111+
stopCh := make(chan struct{})
112+
go lb.serve(stopCh)
113+
transport, ok := ts.Client().Transport.(*http.Transport)
114+
if !ok {
115+
t.Fatalf("failed to assert *http.Transport")
116+
}
117+
config := &Config{
118+
Host: "https://" + lb.ln.Addr().String(),
119+
Transport: utilnet.SetTransportDefaults(transport),
120+
Timeout: 1 * time.Second,
121+
// These fields are required to create a REST client.
122+
ContentConfig: ContentConfig{
123+
GroupVersion: &schema.GroupVersion{},
124+
NegotiatedSerializer: &serializer.CodecFactory{},
125+
},
126+
}
127+
client, err := RESTClientFor(config)
128+
if err != nil {
129+
t.Fatalf("failed to create REST client: %v", err)
130+
}
131+
data, err := client.Get().AbsPath("/").DoRaw(context.TODO())
132+
if err != nil {
133+
t.Fatalf("unexpected err: %s: %v", data, err)
134+
}
135+
if string(data) != "Hello, HTTP/2.0" {
136+
t.Fatalf("unexpected response: %s", data)
137+
}
138+
139+
// Deliberately let the LB stop proxying traffic for the current
140+
// connection. This mimics a broken TCP connection that's not properly
141+
// closed.
142+
close(stopCh)
143+
144+
stopCh = make(chan struct{})
145+
go lb.serve(stopCh)
146+
// Sleep enough time for the HTTP/2 health check to detect and close
147+
// the broken TCP connection.
148+
time.Sleep(time.Duration(1+readIdleTimeout+pingTimeout) * time.Second)
149+
// If the HTTP/2 health check were disabled, the broken connection
150+
// would still be in the connection pool, the following request would
151+
// then reuse the broken connection instead of creating a new one, and
152+
// thus would fail.
153+
data, err = client.Get().AbsPath("/").DoRaw(context.TODO())
154+
if err != nil {
155+
t.Fatalf("unexpected err: %v", err)
156+
}
157+
if string(data) != "Hello, HTTP/2.0" {
158+
t.Fatalf("unexpected response: %s", data)
159+
}
160+
dials := atomic.LoadInt32(&lb.dials)
161+
if dials != 2 {
162+
t.Fatalf("expected %d dials, got %d", 2, dials)
163+
}
164+
}

0 commit comments

Comments
 (0)