Skip to content

Commit 4bb327e

Browse files
Merge pull request #63936 from awly/extract-connwatch
Automatic merge from submit-queue (batch tested with PRs 63865, 57849, 63932, 63930, 63936). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Extract connection rotating dialer into a package **What this PR does / why we need it**: This will be re-used for exec auth plugin to rotate connections on credential change. **Special notes for your reviewer**: this was split from kubernetes/kubernetes#61803 to simplify review **Release note**: ```release-note NONE ``` Kubernetes-commit: da8e25c63dbc48f35065f5790f2f522bbe0c3641
2 parents ea16f61 + 6c082e8 commit 4bb327e

File tree

2 files changed

+166
-0
lines changed

2 files changed

+166
-0
lines changed

util/connrotation/connrotation.go

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Copyright 2018 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 connrotation implements a connection dialer that tracks and can close
18+
// all created connections.
19+
//
20+
// This is used for credential rotation of long-lived connections, when there's
21+
// no way to re-authenticate on a live connection.
22+
package connrotation
23+
24+
import (
25+
"context"
26+
"net"
27+
"sync"
28+
)
29+
30+
// DialFunc is a shorthand for signature of net.DialContext.
31+
type DialFunc func(ctx context.Context, network, address string) (net.Conn, error)
32+
33+
// Dialer opens connections through Dial and tracks them.
34+
type Dialer struct {
35+
dial DialFunc
36+
37+
mu sync.Mutex
38+
conns map[*closableConn]struct{}
39+
}
40+
41+
// NewDialer creates a new Dialer instance.
42+
//
43+
// If dial is not nil, it will be used to create new underlying connections.
44+
// Otherwise net.DialContext is used.
45+
func NewDialer(dial DialFunc) *Dialer {
46+
return &Dialer{
47+
dial: dial,
48+
conns: make(map[*closableConn]struct{}),
49+
}
50+
}
51+
52+
// CloseAll forcibly closes all tracked connections.
53+
//
54+
// Note: new connections may get created before CloseAll returns.
55+
func (d *Dialer) CloseAll() {
56+
d.mu.Lock()
57+
conns := d.conns
58+
d.conns = make(map[*closableConn]struct{})
59+
d.mu.Unlock()
60+
61+
for conn := range conns {
62+
conn.Close()
63+
}
64+
}
65+
66+
// Dial creates a new tracked connection.
67+
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
68+
return d.DialContext(context.Background(), network, address)
69+
}
70+
71+
// DialContext creates a new tracked connection.
72+
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
73+
conn, err := d.dial(ctx, network, address)
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
closable := &closableConn{Conn: conn}
79+
80+
// Start tracking the connection
81+
d.mu.Lock()
82+
d.conns[closable] = struct{}{}
83+
d.mu.Unlock()
84+
85+
// When the connection is closed, remove it from the map. This will
86+
// be no-op if the connection isn't in the map, e.g. if CloseAll()
87+
// is called.
88+
closable.onClose = func() {
89+
d.mu.Lock()
90+
delete(d.conns, closable)
91+
d.mu.Unlock()
92+
}
93+
94+
return closable, nil
95+
}
96+
97+
type closableConn struct {
98+
onClose func()
99+
net.Conn
100+
}
101+
102+
func (c *closableConn) Close() error {
103+
go c.onClose()
104+
return c.Conn.Close()
105+
}
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
Copyright 2018 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 connrotation
18+
19+
import (
20+
"context"
21+
"net"
22+
"testing"
23+
"time"
24+
)
25+
26+
func TestCloseAll(t *testing.T) {
27+
closed := make(chan struct{})
28+
dialFn := func(ctx context.Context, network, address string) (net.Conn, error) {
29+
return closeOnlyConn{onClose: func() { closed <- struct{}{} }}, nil
30+
}
31+
dialer := NewDialer(dialFn)
32+
33+
const numConns = 10
34+
35+
// Outer loop to ensure Dialer is re-usable after CloseAll.
36+
for i := 0; i < 5; i++ {
37+
for j := 0; j < numConns; j++ {
38+
if _, err := dialer.Dial("", ""); err != nil {
39+
t.Fatal(err)
40+
}
41+
}
42+
dialer.CloseAll()
43+
for j := 0; j < numConns; j++ {
44+
select {
45+
case <-closed:
46+
case <-time.After(time.Second):
47+
t.Fatalf("iteration %d: 1s after CloseAll only %d/%d connections closed", i, j, numConns)
48+
}
49+
}
50+
}
51+
}
52+
53+
type closeOnlyConn struct {
54+
net.Conn
55+
onClose func()
56+
}
57+
58+
func (c closeOnlyConn) Close() error {
59+
go c.onClose()
60+
return nil
61+
}

0 commit comments

Comments
 (0)