mirror of
https://github.com/kubernetes/client-go.git
synced 2026-06-10 11:16:28 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
854fa8a99f | ||
|
|
6f060a2946 | ||
|
|
eb7665e713 | ||
|
|
4528de7a82 | ||
|
|
a3f42c98ed | ||
|
|
4fcdf7e289 | ||
|
|
377de3d217 | ||
|
|
a7c6cd2afe | ||
|
|
478748cad1 | ||
|
|
eedba609c5 | ||
|
|
79fff9604c | ||
|
|
e65aa52ba1 | ||
|
|
b32de8e148 | ||
|
|
fa0b9c68e8 | ||
|
|
400bca4e0c |
8
Godeps/Godeps.json
generated
8
Godeps/Godeps.json
generated
@@ -360,7 +360,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net",
|
||||
"Rev": "ab3426394381"
|
||||
"Rev": "69a78807bb2b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2",
|
||||
@@ -372,7 +372,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/sys",
|
||||
"Rev": "ed371f2e16b4"
|
||||
"Rev": "5cba982894dd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/text",
|
||||
@@ -440,11 +440,11 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api",
|
||||
"Rev": "v0.19.2"
|
||||
"Rev": "v0.19.5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery",
|
||||
"Rev": "v0.19.2"
|
||||
"Rev": "v0.19.5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/gengo",
|
||||
|
||||
10
go.mod
10
go.mod
@@ -23,17 +23,17 @@ require (
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||
k8s.io/api v0.19.2
|
||||
k8s.io/apimachinery v0.19.2
|
||||
k8s.io/api v0.19.5
|
||||
k8s.io/apimachinery v0.19.5
|
||||
k8s.io/klog/v2 v2.2.0
|
||||
k8s.io/utils v0.0.0-20200729134348-d5654de09c73
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.19.2
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.19.2
|
||||
k8s.io/api => k8s.io/api v0.19.5
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.19.5
|
||||
)
|
||||
|
||||
13
go.sum
13
go.sum
@@ -214,8 +214,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -240,8 +240,9 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
@@ -333,8 +334,8 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI=
|
||||
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
|
||||
k8s.io/api v0.19.5/go.mod h1:yGZReuNa0vj56op6eT+NLrXJne0R0u9ktexZ8jdJzpc=
|
||||
k8s.io/apimachinery v0.19.5/go.mod h1:6sRbGRAVY5DOCuZwB5XkqguBqpqLU6q/kOaOdk29z6Q=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=
|
||||
|
||||
164
rest/connection_test.go
Normal file
164
rest/connection_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
)
|
||||
|
||||
type tcpLB struct {
|
||||
t *testing.T
|
||||
ln net.Listener
|
||||
serverURL string
|
||||
dials int32
|
||||
}
|
||||
|
||||
func (lb *tcpLB) handleConnection(in net.Conn, stopCh chan struct{}) {
|
||||
out, err := net.Dial("tcp", lb.serverURL)
|
||||
if err != nil {
|
||||
lb.t.Log(err)
|
||||
return
|
||||
}
|
||||
go io.Copy(out, in)
|
||||
go io.Copy(in, out)
|
||||
<-stopCh
|
||||
if err := out.Close(); err != nil {
|
||||
lb.t.Fatalf("failed to close connection: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (lb *tcpLB) serve(stopCh chan struct{}) {
|
||||
conn, err := lb.ln.Accept()
|
||||
if err != nil {
|
||||
lb.t.Fatalf("failed to accept: %v", err)
|
||||
}
|
||||
atomic.AddInt32(&lb.dials, 1)
|
||||
go lb.handleConnection(conn, stopCh)
|
||||
}
|
||||
|
||||
func newLB(t *testing.T, serverURL string) *tcpLB {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to bind: %v", err)
|
||||
}
|
||||
lb := tcpLB{
|
||||
serverURL: serverURL,
|
||||
ln: ln,
|
||||
t: t,
|
||||
}
|
||||
return &lb
|
||||
}
|
||||
|
||||
func setEnv(key, value string) func() {
|
||||
originalValue := os.Getenv(key)
|
||||
os.Setenv(key, value)
|
||||
return func() {
|
||||
os.Setenv(key, originalValue)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
readIdleTimeout int = 1
|
||||
pingTimeout int = 1
|
||||
)
|
||||
|
||||
func TestReconnectBrokenTCP(t *testing.T) {
|
||||
defer setEnv("HTTP2_READ_IDLE_TIMEOUT_SECONDS", strconv.Itoa(readIdleTimeout))()
|
||||
defer setEnv("HTTP2_PING_TIMEOUT_SECONDS", strconv.Itoa(pingTimeout))()
|
||||
defer setEnv("DISABLE_HTTP2", "")()
|
||||
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, %s", r.Proto)
|
||||
}))
|
||||
ts.EnableHTTP2 = true
|
||||
ts.StartTLS()
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse(ts.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse URL from %q: %v", ts.URL, err)
|
||||
}
|
||||
lb := newLB(t, u.Host)
|
||||
defer lb.ln.Close()
|
||||
stopCh := make(chan struct{})
|
||||
go lb.serve(stopCh)
|
||||
transport, ok := ts.Client().Transport.(*http.Transport)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert *http.Transport")
|
||||
}
|
||||
config := &Config{
|
||||
Host: "https://" + lb.ln.Addr().String(),
|
||||
Transport: utilnet.SetTransportDefaults(transport),
|
||||
Timeout: 1 * time.Second,
|
||||
// These fields are required to create a REST client.
|
||||
ContentConfig: ContentConfig{
|
||||
GroupVersion: &schema.GroupVersion{},
|
||||
NegotiatedSerializer: &serializer.CodecFactory{},
|
||||
},
|
||||
}
|
||||
client, err := RESTClientFor(config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create REST client: %v", err)
|
||||
}
|
||||
data, err := client.Get().AbsPath("/").DoRaw(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %s: %v", data, err)
|
||||
}
|
||||
if string(data) != "Hello, HTTP/2.0" {
|
||||
t.Fatalf("unexpected response: %s", data)
|
||||
}
|
||||
|
||||
// Deliberately let the LB stop proxying traffic for the current
|
||||
// connection. This mimics a broken TCP connection that's not properly
|
||||
// closed.
|
||||
close(stopCh)
|
||||
|
||||
stopCh = make(chan struct{})
|
||||
go lb.serve(stopCh)
|
||||
// Sleep enough time for the HTTP/2 health check to detect and close
|
||||
// the broken TCP connection.
|
||||
time.Sleep(time.Duration(1+readIdleTimeout+pingTimeout) * time.Second)
|
||||
// If the HTTP/2 health check were disabled, the broken connection
|
||||
// would still be in the connection pool, the following request would
|
||||
// then reuse the broken connection instead of creating a new one, and
|
||||
// thus would fail.
|
||||
data, err = client.Get().AbsPath("/").DoRaw(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if string(data) != "Hello, HTTP/2.0" {
|
||||
t.Fatalf("unexpected response: %s", data)
|
||||
}
|
||||
dials := atomic.LoadInt32(&lb.dials)
|
||||
if dials != 2 {
|
||||
t.Fatalf("expected %d dials, got %d", 2, dials)
|
||||
}
|
||||
}
|
||||
@@ -290,8 +290,12 @@ func (le *LeaderElector) release() bool {
|
||||
if !le.IsLeader() {
|
||||
return true
|
||||
}
|
||||
now := metav1.Now()
|
||||
leaderElectionRecord := rl.LeaderElectionRecord{
|
||||
LeaderTransitions: le.observedRecord.LeaderTransitions,
|
||||
LeaderTransitions: le.observedRecord.LeaderTransitions,
|
||||
LeaseDurationSeconds: 1,
|
||||
RenewTime: now,
|
||||
AcquireTime: now,
|
||||
}
|
||||
if err := le.config.Lock.Update(context.TODO(), leaderElectionRecord); err != nil {
|
||||
klog.Errorf("Failed to release lock: %v", err)
|
||||
|
||||
@@ -917,3 +917,284 @@ func TestTryAcquireOrRenewEndpointsLeases(t *testing.T) {
|
||||
func TestTryAcquireOrRenewConfigMapsLeases(t *testing.T) {
|
||||
testTryAcquireOrRenewMultiLock(t, "configmapsleases")
|
||||
}
|
||||
|
||||
func testReleaseLease(t *testing.T, objectType string) {
|
||||
tests := []struct {
|
||||
name string
|
||||
observedRecord rl.LeaderElectionRecord
|
||||
observedTime time.Time
|
||||
reactors []Reactor
|
||||
|
||||
expectSuccess bool
|
||||
transitionLeader bool
|
||||
outHolder string
|
||||
}{
|
||||
{
|
||||
name: "release acquired lock from no object",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: objectType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "create",
|
||||
objectType: objectType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.CreateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: objectType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
expectSuccess: true,
|
||||
outHolder: "",
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
test := &tests[i]
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// OnNewLeader is called async so we have to wait for it.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
var reportedLeader string
|
||||
var lock rl.Interface
|
||||
|
||||
objectMeta := metav1.ObjectMeta{Namespace: "foo", Name: "bar"}
|
||||
resourceLockConfig := rl.ResourceLockConfig{
|
||||
Identity: "baz",
|
||||
EventRecorder: &record.FakeRecorder{},
|
||||
}
|
||||
c := &fake.Clientset{}
|
||||
for _, reactor := range test.reactors {
|
||||
c.AddReactor(reactor.verb, objectType, reactor.reaction)
|
||||
}
|
||||
c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) {
|
||||
t.Errorf("unreachable action. testclient called too many times: %+v", action)
|
||||
return true, nil, fmt.Errorf("unreachable action")
|
||||
})
|
||||
|
||||
switch objectType {
|
||||
case "endpoints":
|
||||
lock = &rl.EndpointsLock{
|
||||
EndpointsMeta: objectMeta,
|
||||
LockConfig: resourceLockConfig,
|
||||
Client: c.CoreV1(),
|
||||
}
|
||||
case "configmaps":
|
||||
lock = &rl.ConfigMapLock{
|
||||
ConfigMapMeta: objectMeta,
|
||||
LockConfig: resourceLockConfig,
|
||||
Client: c.CoreV1(),
|
||||
}
|
||||
case "leases":
|
||||
lock = &rl.LeaseLock{
|
||||
LeaseMeta: objectMeta,
|
||||
LockConfig: resourceLockConfig,
|
||||
Client: c.CoordinationV1(),
|
||||
}
|
||||
}
|
||||
|
||||
lec := LeaderElectionConfig{
|
||||
Lock: lock,
|
||||
LeaseDuration: 10 * time.Second,
|
||||
Callbacks: LeaderCallbacks{
|
||||
OnNewLeader: func(l string) {
|
||||
defer wg.Done()
|
||||
reportedLeader = l
|
||||
},
|
||||
},
|
||||
}
|
||||
observedRawRecord := GetRawRecordOrDie(t, objectType, test.observedRecord)
|
||||
le := &LeaderElector{
|
||||
config: lec,
|
||||
observedRecord: test.observedRecord,
|
||||
observedRawRecord: observedRawRecord,
|
||||
observedTime: test.observedTime,
|
||||
clock: clock.RealClock{},
|
||||
}
|
||||
if !le.tryAcquireOrRenew(context.Background()) {
|
||||
t.Errorf("unexpected result of tryAcquireOrRenew: [succeeded=%v]", true)
|
||||
}
|
||||
|
||||
le.maybeReportTransition()
|
||||
|
||||
// Wait for a response to the leader transition, and add 1 so that we can track the final transition.
|
||||
wg.Wait()
|
||||
wg.Add(1)
|
||||
|
||||
if test.expectSuccess != le.release() {
|
||||
t.Errorf("unexpected result of release: [succeeded=%v]", !test.expectSuccess)
|
||||
}
|
||||
|
||||
le.observedRecord.AcquireTime = metav1.Time{}
|
||||
le.observedRecord.RenewTime = metav1.Time{}
|
||||
if le.observedRecord.HolderIdentity != test.outHolder {
|
||||
t.Errorf("expected holder:\n\t%+v\ngot:\n\t%+v", test.outHolder, le.observedRecord.HolderIdentity)
|
||||
}
|
||||
if len(test.reactors) != len(c.Actions()) {
|
||||
t.Errorf("wrong number of api interactions")
|
||||
}
|
||||
if test.transitionLeader && le.observedRecord.LeaderTransitions != 1 {
|
||||
t.Errorf("leader should have transitioned but did not")
|
||||
}
|
||||
if !test.transitionLeader && le.observedRecord.LeaderTransitions != 0 {
|
||||
t.Errorf("leader should not have transitioned but did")
|
||||
}
|
||||
le.maybeReportTransition()
|
||||
wg.Wait()
|
||||
if reportedLeader != test.outHolder {
|
||||
t.Errorf("reported leader was not the new leader. expected %q, got %q", test.outHolder, reportedLeader)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Will test leader election using endpoints as the resource
|
||||
func TestReleaseLeaseEndpoints(t *testing.T) {
|
||||
testReleaseLease(t, "endpoints")
|
||||
}
|
||||
|
||||
// Will test leader election using endpoints as the resource
|
||||
func TestReleaseLeaseConfigMaps(t *testing.T) {
|
||||
testReleaseLease(t, "configmaps")
|
||||
}
|
||||
|
||||
// Will test leader election using endpoints as the resource
|
||||
func TestReleaseLeaseLeases(t *testing.T) {
|
||||
testReleaseLease(t, "leases")
|
||||
}
|
||||
|
||||
func TestReleaseOnCancellation_Endpoints(t *testing.T) {
|
||||
testReleaseOnCancellation(t, "endpoints")
|
||||
}
|
||||
|
||||
func TestReleaseOnCancellation_ConfigMaps(t *testing.T) {
|
||||
testReleaseOnCancellation(t, "configmaps")
|
||||
}
|
||||
|
||||
func TestReleaseOnCancellation_Leases(t *testing.T) {
|
||||
testReleaseOnCancellation(t, "leases")
|
||||
}
|
||||
|
||||
func testReleaseOnCancellation(t *testing.T, objectType string) {
|
||||
var (
|
||||
onNewLeader = make(chan struct{})
|
||||
onRenewCalled = make(chan struct{})
|
||||
onRenewResume = make(chan struct{})
|
||||
onRelease = make(chan struct{})
|
||||
|
||||
lockObj runtime.Object
|
||||
updates int
|
||||
)
|
||||
|
||||
resourceLockConfig := rl.ResourceLockConfig{
|
||||
Identity: "baz",
|
||||
EventRecorder: &record.FakeRecorder{},
|
||||
}
|
||||
c := &fake.Clientset{}
|
||||
|
||||
c.AddReactor("get", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
if lockObj != nil {
|
||||
return true, lockObj, nil
|
||||
}
|
||||
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
|
||||
})
|
||||
|
||||
// create lock
|
||||
c.AddReactor("create", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
lockObj = action.(fakeclient.CreateAction).GetObject()
|
||||
return true, lockObj, nil
|
||||
})
|
||||
|
||||
c.AddReactor("update", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
updates++
|
||||
|
||||
// Second update (first renew) should return our canceled error
|
||||
// FakeClient doesn't do anything with the context so we're doing this ourselves
|
||||
if updates == 2 {
|
||||
close(onRenewCalled)
|
||||
<-onRenewResume
|
||||
return true, nil, context.Canceled
|
||||
} else if updates == 3 {
|
||||
close(onRelease)
|
||||
}
|
||||
|
||||
lockObj = action.(fakeclient.UpdateAction).GetObject()
|
||||
return true, lockObj, nil
|
||||
|
||||
})
|
||||
|
||||
c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) {
|
||||
t.Errorf("unreachable action. testclient called too many times: %+v", action)
|
||||
return true, nil, fmt.Errorf("unreachable action")
|
||||
})
|
||||
|
||||
lock, err := rl.New(objectType, "foo", "bar", c.CoreV1(), c.CoordinationV1(), resourceLockConfig)
|
||||
if err != nil {
|
||||
t.Fatal("resourcelock.New() = ", err)
|
||||
}
|
||||
|
||||
lec := LeaderElectionConfig{
|
||||
Lock: lock,
|
||||
LeaseDuration: 15 * time.Second,
|
||||
RenewDeadline: 2 * time.Second,
|
||||
RetryPeriod: 1 * time.Second,
|
||||
|
||||
// This is what we're testing
|
||||
ReleaseOnCancel: true,
|
||||
|
||||
Callbacks: LeaderCallbacks{
|
||||
OnNewLeader: func(identity string) {},
|
||||
OnStoppedLeading: func() {},
|
||||
OnStartedLeading: func(context.Context) {
|
||||
close(onNewLeader)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
elector, err := NewLeaderElector(lec)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create leader elector: ", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go elector.Run(ctx)
|
||||
|
||||
// Wait for us to become the leader
|
||||
select {
|
||||
case <-onNewLeader:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("failed to become the leader")
|
||||
}
|
||||
|
||||
// Wait for renew (update) to be invoked
|
||||
select {
|
||||
case <-onRenewCalled:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("the elector failed to renew the lock")
|
||||
}
|
||||
|
||||
// Cancel the context - stopping the elector while
|
||||
// it's running
|
||||
cancel()
|
||||
|
||||
// Resume the update call to return the cancellation
|
||||
// which should trigger the release flow
|
||||
close(onRenewResume)
|
||||
|
||||
select {
|
||||
case <-onRelease:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("the lock was not released")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,8 +92,12 @@ func (cml *ConfigMapLock) Update(ctx context.Context, ler LeaderElectionRecord)
|
||||
cml.cm.Annotations = make(map[string]string)
|
||||
}
|
||||
cml.cm.Annotations[LeaderElectionRecordAnnotationKey] = string(recordBytes)
|
||||
cml.cm, err = cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Update(ctx, cml.cm, metav1.UpdateOptions{})
|
||||
return err
|
||||
cm, err := cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Update(ctx, cml.cm, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cml.cm = cm
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordEvent in leader election while adding meta-data
|
||||
|
||||
@@ -87,8 +87,12 @@ func (el *EndpointsLock) Update(ctx context.Context, ler LeaderElectionRecord) e
|
||||
el.e.Annotations = make(map[string]string)
|
||||
}
|
||||
el.e.Annotations[LeaderElectionRecordAnnotationKey] = string(recordBytes)
|
||||
el.e, err = el.Client.Endpoints(el.EndpointsMeta.Namespace).Update(ctx, el.e, metav1.UpdateOptions{})
|
||||
return err
|
||||
e, err := el.Client.Endpoints(el.EndpointsMeta.Namespace).Update(ctx, el.e, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
el.e = e
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordEvent in leader election while adding meta-data
|
||||
|
||||
@@ -71,9 +71,14 @@ func (ll *LeaseLock) Update(ctx context.Context, ler LeaderElectionRecord) error
|
||||
return errors.New("lease not initialized, call get or create first")
|
||||
}
|
||||
ll.lease.Spec = LeaderElectionRecordToLeaseSpec(&ler)
|
||||
var err error
|
||||
ll.lease, err = ll.Client.Leases(ll.LeaseMeta.Namespace).Update(ctx, ll.lease, metav1.UpdateOptions{})
|
||||
return err
|
||||
|
||||
lease, err := ll.Client.Leases(ll.LeaseMeta.Namespace).Update(ctx, ll.lease, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ll.lease = lease
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordEvent in leader election while adding meta-data
|
||||
|
||||
@@ -47,12 +47,9 @@ type tlsCacheKey struct {
|
||||
keyData string
|
||||
certFile string
|
||||
keyFile string
|
||||
getCert string
|
||||
serverName string
|
||||
nextProtos string
|
||||
dial string
|
||||
disableCompression bool
|
||||
proxy string
|
||||
}
|
||||
|
||||
func (t tlsCacheKey) String() string {
|
||||
@@ -60,22 +57,24 @@ func (t tlsCacheKey) String() string {
|
||||
if len(t.keyData) > 0 {
|
||||
keyText = "<redacted>"
|
||||
}
|
||||
return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, getCert: %s, serverName:%s, dial:%s disableCompression:%t, proxy: %s", t.insecure, t.caData, t.certData, keyText, t.getCert, t.serverName, t.dial, t.disableCompression, t.proxy)
|
||||
return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, serverName:%s, disableCompression:%t", t.insecure, t.caData, t.certData, keyText, t.serverName, t.disableCompression)
|
||||
}
|
||||
|
||||
func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
|
||||
key, err := tlsConfigKey(config)
|
||||
key, canCache, err := tlsConfigKey(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure we only create a single transport for the given TLS options
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if canCache {
|
||||
// Ensure we only create a single transport for the given TLS options
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// See if we already have a custom transport for this config
|
||||
if t, ok := c.transports[key]; ok {
|
||||
return t, nil
|
||||
// See if we already have a custom transport for this config
|
||||
if t, ok := c.transports[key]; ok {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get the TLS options for this client config
|
||||
@@ -110,8 +109,7 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
|
||||
proxy = config.Proxy
|
||||
}
|
||||
|
||||
// Cache a single transport for these options
|
||||
c.transports[key] = utilnet.SetTransportDefaults(&http.Transport{
|
||||
transport := utilnet.SetTransportDefaults(&http.Transport{
|
||||
Proxy: proxy,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
@@ -119,24 +117,33 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
|
||||
DialContext: dial,
|
||||
DisableCompression: config.DisableCompression,
|
||||
})
|
||||
return c.transports[key], nil
|
||||
|
||||
if canCache {
|
||||
// Cache a single transport for these options
|
||||
c.transports[key] = transport
|
||||
}
|
||||
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
// tlsConfigKey returns a unique key for tls.Config objects returned from TLSConfigFor
|
||||
func tlsConfigKey(c *Config) (tlsCacheKey, error) {
|
||||
func tlsConfigKey(c *Config) (tlsCacheKey, bool, error) {
|
||||
// Make sure ca/key/cert content is loaded
|
||||
if err := loadTLSFiles(c); err != nil {
|
||||
return tlsCacheKey{}, err
|
||||
return tlsCacheKey{}, false, err
|
||||
}
|
||||
|
||||
if c.TLS.GetCert != nil || c.Dial != nil || c.Proxy != nil {
|
||||
// cannot determine equality for functions
|
||||
return tlsCacheKey{}, false, nil
|
||||
}
|
||||
|
||||
k := tlsCacheKey{
|
||||
insecure: c.TLS.Insecure,
|
||||
caData: string(c.TLS.CAData),
|
||||
getCert: fmt.Sprintf("%p", c.TLS.GetCert),
|
||||
serverName: c.TLS.ServerName,
|
||||
nextProtos: strings.Join(c.TLS.NextProtos, ","),
|
||||
dial: fmt.Sprintf("%p", c.Dial),
|
||||
disableCompression: c.DisableCompression,
|
||||
proxy: fmt.Sprintf("%p", c.Proxy),
|
||||
}
|
||||
|
||||
if c.TLS.ReloadTLSFiles {
|
||||
@@ -147,5 +154,5 @@ func tlsConfigKey(c *Config) (tlsCacheKey, error) {
|
||||
k.keyData = string(c.TLS.KeyData)
|
||||
}
|
||||
|
||||
return k, nil
|
||||
return k, true, nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -37,16 +36,24 @@ func TestTLSConfigKey(t *testing.T) {
|
||||
}
|
||||
for nameA, valueA := range identicalConfigurations {
|
||||
for nameB, valueB := range identicalConfigurations {
|
||||
keyA, err := tlsConfigKey(valueA)
|
||||
keyA, canCache, err := tlsConfigKey(valueA)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for %q: %v", nameA, err)
|
||||
continue
|
||||
}
|
||||
keyB, err := tlsConfigKey(valueB)
|
||||
if !canCache {
|
||||
t.Errorf("Unexpected canCache=false")
|
||||
continue
|
||||
}
|
||||
keyB, canCache, err := tlsConfigKey(valueB)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for %q: %v", nameB, err)
|
||||
continue
|
||||
}
|
||||
if !canCache {
|
||||
t.Errorf("Unexpected canCache=false")
|
||||
continue
|
||||
}
|
||||
if keyA != keyB {
|
||||
t.Errorf("Expected identical cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
|
||||
continue
|
||||
@@ -132,12 +139,12 @@ func TestTLSConfigKey(t *testing.T) {
|
||||
}
|
||||
for nameA, valueA := range uniqueConfigurations {
|
||||
for nameB, valueB := range uniqueConfigurations {
|
||||
keyA, err := tlsConfigKey(valueA)
|
||||
keyA, canCacheA, err := tlsConfigKey(valueA)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for %q: %v", nameA, err)
|
||||
continue
|
||||
}
|
||||
keyB, err := tlsConfigKey(valueB)
|
||||
keyB, canCacheB, err := tlsConfigKey(valueB)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for %q: %v", nameB, err)
|
||||
continue
|
||||
@@ -148,33 +155,18 @@ func TestTLSConfigKey(t *testing.T) {
|
||||
if keyA != keyB {
|
||||
t.Errorf("Expected identical cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
|
||||
}
|
||||
if canCacheA != canCacheB {
|
||||
t.Errorf("Expected identical canCache %q and %q, got:\n\t%v\n\t%v", nameA, nameB, canCacheA, canCacheB)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if keyA == keyB {
|
||||
t.Errorf("Expected unique cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
|
||||
continue
|
||||
if canCacheA && canCacheB {
|
||||
if keyA == keyB {
|
||||
t.Errorf("Expected unique cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSConfigKeyFuncPtr(t *testing.T) {
|
||||
keys := make(map[tlsCacheKey]struct{})
|
||||
makeKey := func(p func(*http.Request) (*url.URL, error)) tlsCacheKey {
|
||||
key, err := tlsConfigKey(&Config{Proxy: p})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error creating cache key: %v", err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
keys[makeKey(http.ProxyFromEnvironment)] = struct{}{}
|
||||
keys[makeKey(http.ProxyFromEnvironment)] = struct{}{}
|
||||
keys[makeKey(http.ProxyURL(nil))] = struct{}{}
|
||||
keys[makeKey(nil)] = struct{}{}
|
||||
|
||||
if got, want := len(keys), 3; got != want {
|
||||
t.Fatalf("Unexpected number of keys: got=%d want=%d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,13 +103,23 @@ func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) {
|
||||
if j.beginRange > 0 {
|
||||
j.beginRange--
|
||||
j.inRange++
|
||||
for _, value := range results {
|
||||
if len(results) > 0 {
|
||||
for _, value := range results {
|
||||
j.parser.Root.Nodes = nodes[i+1:]
|
||||
nextResults, err := j.FindResults(value.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fullResult = append(fullResult, nextResults...)
|
||||
}
|
||||
} else {
|
||||
// If the range has no results, we still need to process the nodes within the range
|
||||
// so the position will advance to the end node
|
||||
j.parser.Root.Nodes = nodes[i+1:]
|
||||
nextResults, err := j.FindResults(value.Interface())
|
||||
_, err := j.FindResults(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fullResult = append(fullResult, nextResults...)
|
||||
}
|
||||
j.inRange--
|
||||
|
||||
|
||||
@@ -393,6 +393,21 @@ func TestKubernetes(t *testing.T) {
|
||||
testJSONPathSortOutput(randomPrintOrderTests, t)
|
||||
}
|
||||
|
||||
func TestEmptyRange(t *testing.T) {
|
||||
var input = []byte(`{"items":[]}`)
|
||||
var emptyList interface{}
|
||||
err := json.Unmarshal(input, &emptyList)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
tests := []jsonpathTest{
|
||||
{"empty range", `{range .items[*]}{.metadata.name}{end}`, &emptyList, "", false},
|
||||
{"empty nested range", `{range .items[*]}{.metadata.name}{":"}{range @.spec.containers[*]}{.name}{","}{end}{"+"}{end}`, &emptyList, "", false},
|
||||
}
|
||||
testJSONPath(tests, true, t)
|
||||
}
|
||||
|
||||
func TestNestedRanges(t *testing.T) {
|
||||
var input = []byte(`{
|
||||
"items": [
|
||||
|
||||
Reference in New Issue
Block a user