Skip to content

Commit ae654cc

Browse files
Merge pull request #31080 from dgoodwin/admin-kubeconfig-retry
OCPBUGS-84504: retry transient kubeconfig read failures in GetClientConfig
2 parents b6f09c2 + 94caafd commit ae654cc

2 files changed

Lines changed: 140 additions & 11 deletions

File tree

test/extended/util/client.go

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,19 +1272,36 @@ func WaitForAccess(c kubernetes.Interface, allowed bool, review *kubeauthorizati
12721272
}
12731273

12741274
func GetClientConfig(kubeConfigFile string) (*rest.Config, error) {
1275-
kubeConfigBytes, err := ioutil.ReadFile(kubeConfigFile)
1276-
if err != nil {
1277-
return nil, err
1278-
}
1279-
kubeConfig, err := clientcmd.NewClientConfigFromBytes(kubeConfigBytes)
1280-
if err != nil {
1281-
return nil, err
1282-
}
1283-
clientConfig, err := kubeConfig.ClientConfig()
1275+
var clientConfig *rest.Config
1276+
var lastErr error
1277+
err := wait.PollImmediate(200*time.Millisecond, 10*time.Second, func() (bool, error) {
1278+
kubeConfigBytes, err := os.ReadFile(kubeConfigFile)
1279+
if err != nil {
1280+
lastErr = err
1281+
if os.IsNotExist(err) {
1282+
return false, nil
1283+
}
1284+
return false, err
1285+
}
1286+
if len(kubeConfigBytes) == 0 {
1287+
lastErr = fmt.Errorf("kubeconfig file %q is empty", kubeConfigFile)
1288+
return false, nil
1289+
}
1290+
kubeConfig, err := clientcmd.NewClientConfigFromBytes(kubeConfigBytes)
1291+
if err != nil {
1292+
lastErr = err
1293+
return false, nil
1294+
}
1295+
clientConfig, err = kubeConfig.ClientConfig()
1296+
if err != nil {
1297+
lastErr = err
1298+
return false, nil
1299+
}
1300+
return true, nil
1301+
})
12841302
if err != nil {
1285-
return nil, err
1303+
return nil, fmt.Errorf("failed to read kubeconfig %q after retries: %w", kubeConfigFile, lastErr)
12861304
}
1287-
12881305
return clientConfig, nil
12891306
}
12901307

test/extended/util/client_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package util
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
"time"
8+
)
9+
10+
const validKubeconfig = `apiVersion: v1
11+
kind: Config
12+
clusters:
13+
- cluster:
14+
server: https://localhost:6443
15+
name: test-cluster
16+
contexts:
17+
- context:
18+
cluster: test-cluster
19+
user: test-user
20+
name: test-context
21+
current-context: test-context
22+
users:
23+
- name: test-user
24+
user:
25+
token: test-token
26+
`
27+
28+
func TestGetClientConfigRetryOnMissingFile(t *testing.T) {
29+
dir := t.TempDir()
30+
path := filepath.Join(dir, "kubeconfig")
31+
32+
if err := os.WriteFile(path, []byte(validKubeconfig), 0644); err != nil {
33+
t.Fatal(err)
34+
}
35+
36+
go func() {
37+
time.Sleep(200 * time.Millisecond)
38+
os.Remove(path)
39+
time.Sleep(800 * time.Millisecond)
40+
os.WriteFile(path, []byte(validKubeconfig), 0644)
41+
}()
42+
43+
time.Sleep(300 * time.Millisecond)
44+
45+
cfg, err := GetClientConfig(path)
46+
if err != nil {
47+
t.Fatalf("expected retry to succeed, got error: %v", err)
48+
}
49+
if cfg.Host != "https://localhost:6443" {
50+
t.Fatalf("unexpected host: %s", cfg.Host)
51+
}
52+
}
53+
54+
func TestGetClientConfigRetryOnEmptyFile(t *testing.T) {
55+
dir := t.TempDir()
56+
path := filepath.Join(dir, "kubeconfig")
57+
58+
if err := os.WriteFile(path, []byte{}, 0644); err != nil {
59+
t.Fatal(err)
60+
}
61+
62+
go func() {
63+
time.Sleep(500 * time.Millisecond)
64+
os.WriteFile(path, []byte(validKubeconfig), 0644)
65+
}()
66+
67+
cfg, err := GetClientConfig(path)
68+
if err != nil {
69+
t.Fatalf("expected retry to succeed, got error: %v", err)
70+
}
71+
if cfg.Host != "https://localhost:6443" {
72+
t.Fatalf("unexpected host: %s", cfg.Host)
73+
}
74+
}
75+
76+
func TestGetClientConfigFailsOnPermanentMissing(t *testing.T) {
77+
path := filepath.Join(t.TempDir(), "nonexistent", "kubeconfig")
78+
79+
start := time.Now()
80+
_, err := GetClientConfig(path)
81+
elapsed := time.Since(start)
82+
83+
if err == nil {
84+
t.Fatal("expected error for permanently missing file")
85+
}
86+
if elapsed > 15*time.Second {
87+
t.Fatalf("took too long: %v", elapsed)
88+
}
89+
}
90+
91+
func TestGetClientConfigSucceedsImmediately(t *testing.T) {
92+
dir := t.TempDir()
93+
path := filepath.Join(dir, "kubeconfig")
94+
95+
if err := os.WriteFile(path, []byte(validKubeconfig), 0644); err != nil {
96+
t.Fatal(err)
97+
}
98+
99+
start := time.Now()
100+
cfg, err := GetClientConfig(path)
101+
elapsed := time.Since(start)
102+
103+
if err != nil {
104+
t.Fatalf("unexpected error: %v", err)
105+
}
106+
if cfg.Host != "https://localhost:6443" {
107+
t.Fatalf("unexpected host: %s", cfg.Host)
108+
}
109+
if elapsed > 1*time.Second {
110+
t.Fatalf("should have succeeded immediately, took %v", elapsed)
111+
}
112+
}

0 commit comments

Comments
 (0)