1
16
17 package testing
18
19 import (
20 "context"
21 "fmt"
22 "net"
23 "os"
24 "time"
25
26 "github.com/spf13/pflag"
27
28 "k8s.io/apimachinery/pkg/util/wait"
29 "k8s.io/client-go/kubernetes"
30 restclient "k8s.io/client-go/rest"
31 logsapi "k8s.io/component-base/logs/api/v1"
32 "k8s.io/klog/v2"
33 "k8s.io/kubernetes/cmd/kube-controller-manager/app"
34 kubecontrollerconfig "k8s.io/kubernetes/cmd/kube-controller-manager/app/config"
35 "k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
36 )
37
38 func init() {
39
40
41
42 logsapi.ReapplyHandling = logsapi.ReapplyHandlingIgnoreUnchanged
43 }
44
45
46 type TearDownFunc func()
47
48
49 type TestServer struct {
50 LoopbackClientConfig *restclient.Config
51 Options *options.KubeControllerManagerOptions
52 Config *kubecontrollerconfig.Config
53 TearDownFn TearDownFunc
54 TmpDir string
55 }
56
57
58
59
60
61
62
63 func StartTestServer(ctx context.Context, customFlags []string) (result TestServer, err error) {
64 logger := klog.FromContext(ctx)
65 ctx, cancel := context.WithCancel(ctx)
66 var errCh chan error
67 tearDown := func() {
68 cancel()
69
70
71
72 if errCh != nil {
73 err, ok := <-errCh
74 if ok && err != nil {
75 logger.Error(err, "Failed to shutdown test server cleanly")
76 }
77 }
78 if len(result.TmpDir) != 0 {
79 os.RemoveAll(result.TmpDir)
80 }
81 }
82 defer func() {
83 if result.TearDownFn == nil {
84 tearDown()
85 }
86 }()
87
88 result.TmpDir, err = os.MkdirTemp("", "kube-controller-manager")
89 if err != nil {
90 return result, fmt.Errorf("failed to create temp dir: %v", err)
91 }
92
93 fs := pflag.NewFlagSet("test", pflag.PanicOnError)
94
95 s, err := options.NewKubeControllerManagerOptions()
96 if err != nil {
97 return TestServer{}, err
98 }
99 all, disabled, aliases := app.KnownControllers(), app.ControllersDisabledByDefault(), app.ControllerAliases()
100 namedFlagSets := s.Flags(all, disabled, aliases)
101 for _, f := range namedFlagSets.FlagSets {
102 fs.AddFlagSet(f)
103 }
104 fs.Parse(customFlags)
105
106 if s.SecureServing.BindPort != 0 {
107 s.SecureServing.Listener, s.SecureServing.BindPort, err = createListenerOnFreePort()
108 if err != nil {
109 return result, fmt.Errorf("failed to create listener: %v", err)
110 }
111 s.SecureServing.ServerCert.CertDirectory = result.TmpDir
112
113 logger.Info("kube-controller-manager will listen securely", "port", s.SecureServing.BindPort)
114 }
115
116 config, err := s.Config(all, disabled, aliases)
117 if err != nil {
118 return result, fmt.Errorf("failed to create config from options: %v", err)
119 }
120
121 errCh = make(chan error)
122 go func(ctx context.Context) {
123 defer close(errCh)
124
125 if err := app.Run(ctx, config.Complete()); err != nil {
126 errCh <- err
127 }
128 }(ctx)
129
130 logger.Info("Waiting for /healthz to be ok...")
131 client, err := kubernetes.NewForConfig(config.LoopbackClientConfig)
132 if err != nil {
133 return result, fmt.Errorf("failed to create a client: %v", err)
134 }
135 err = wait.PollWithContext(ctx, 100*time.Millisecond, 30*time.Second, func(ctx context.Context) (bool, error) {
136 select {
137 case <-ctx.Done():
138 return false, ctx.Err()
139 case err := <-errCh:
140 return false, err
141 default:
142 }
143
144 result := client.CoreV1().RESTClient().Get().AbsPath("/healthz").Do(context.TODO())
145 status := 0
146 result.StatusCode(&status)
147 if status == 200 {
148 return true, nil
149 }
150 return false, nil
151 })
152 if err != nil {
153 return result, fmt.Errorf("failed to wait for /healthz to return ok: %v", err)
154 }
155
156
157 result.LoopbackClientConfig = config.LoopbackClientConfig
158 result.Options = s
159 result.Config = config
160 result.TearDownFn = tearDown
161
162 return result, nil
163 }
164
165
166 func StartTestServerOrDie(ctx context.Context, flags []string) *TestServer {
167 result, err := StartTestServer(ctx, flags)
168 if err == nil {
169 return &result
170 }
171
172 panic(fmt.Errorf("failed to launch server: %v", err))
173 }
174
175 func createListenerOnFreePort() (net.Listener, int, error) {
176 ln, err := net.Listen("tcp", ":0")
177 if err != nil {
178 return nil, 0, err
179 }
180
181
182 tcpAddr, ok := ln.Addr().(*net.TCPAddr)
183 if !ok {
184 ln.Close()
185 return nil, 0, fmt.Errorf("invalid listen address: %q", ln.Addr().String())
186 }
187
188 return ln, tcpAddr.Port, nil
189 }
190
View as plain text