1
16
17 package testing
18
19 import (
20 "context"
21 "fmt"
22 "net"
23 "os"
24 "path/filepath"
25 "runtime"
26 "time"
27
28 "github.com/spf13/pflag"
29
30 extensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver"
31 "k8s.io/apiextensions-apiserver/pkg/cmd/server/options"
32 generatedopenapi "k8s.io/apiextensions-apiserver/pkg/generated/openapi"
33 "k8s.io/apimachinery/pkg/util/wait"
34 openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
35 genericapiserver "k8s.io/apiserver/pkg/server"
36 "k8s.io/apiserver/pkg/storage/storagebackend"
37 "k8s.io/apiserver/pkg/util/openapi"
38 "k8s.io/client-go/kubernetes"
39 restclient "k8s.io/client-go/rest"
40 logsapi "k8s.io/component-base/logs/api/v1"
41 "k8s.io/klog/v2"
42 )
43
44 func init() {
45
46
47
48 logsapi.ReapplyHandling = logsapi.ReapplyHandlingIgnoreUnchanged
49 }
50
51
52 type TearDownFunc func()
53
54
55 type TestServerInstanceOptions struct {
56 }
57
58
59 type TestServer struct {
60 ClientConfig *restclient.Config
61 ServerOpts *options.CustomResourceDefinitionsServerOptions
62 TearDownFn TearDownFunc
63 TmpDir string
64 CompletedConfig extensionsapiserver.CompletedConfig
65 }
66
67
68 type Logger interface {
69 Errorf(format string, args ...interface{})
70 Fatalf(format string, args ...interface{})
71 Logf(format string, args ...interface{})
72 }
73
74
75 func NewDefaultTestServerOptions() *TestServerInstanceOptions {
76 return &TestServerInstanceOptions{}
77 }
78
79
80
81
82
83
84
85 func StartTestServer(t Logger, _ *TestServerInstanceOptions, customFlags []string, storageConfig *storagebackend.Config) (result TestServer, err error) {
86 stopCh := make(chan struct{})
87 var errCh chan error
88 tearDown := func() {
89
90
91
92 close(stopCh)
93
94
95
96 if errCh != nil {
97 err, ok := <-errCh
98 if ok && err != nil {
99 klog.Errorf("Failed to shutdown test server clearly: %v", err)
100 }
101 }
102
103 if len(result.TmpDir) != 0 {
104 os.RemoveAll(result.TmpDir)
105 }
106 }
107 defer func() {
108 if result.TearDownFn == nil {
109 tearDown()
110 }
111 }()
112
113 result.TmpDir, err = os.MkdirTemp("", "apiextensions-apiserver")
114 if err != nil {
115 return result, fmt.Errorf("failed to create temp dir: %v", err)
116 }
117
118 fs := pflag.NewFlagSet("test", pflag.PanicOnError)
119
120 s := options.NewCustomResourceDefinitionsServerOptions(os.Stdout, os.Stderr)
121 s.AddFlags(fs)
122
123 s.RecommendedOptions.SecureServing.Listener, s.RecommendedOptions.SecureServing.BindPort, err = createLocalhostListenerOnFreePort()
124 if err != nil {
125 return result, fmt.Errorf("failed to create listener: %v", err)
126 }
127 s.RecommendedOptions.SecureServing.ServerCert.CertDirectory = result.TmpDir
128 s.RecommendedOptions.SecureServing.ExternalAddress = s.RecommendedOptions.SecureServing.Listener.Addr().(*net.TCPAddr).IP
129
130 pkgPath, err := pkgPath(t)
131 if err != nil {
132 return result, err
133 }
134 s.RecommendedOptions.SecureServing.ServerCert.FixtureDirectory = filepath.Join(pkgPath, "testdata")
135
136 if storageConfig != nil {
137 s.RecommendedOptions.Etcd.StorageConfig = *storageConfig
138 }
139 s.APIEnablement.RuntimeConfig.Set("api/all=true")
140
141 fs.Parse(customFlags)
142
143 if err := s.Complete(); err != nil {
144 return result, fmt.Errorf("failed to set default options: %v", err)
145 }
146 if err := s.Validate(); err != nil {
147 return result, fmt.Errorf("failed to validate options: %v", err)
148 }
149
150 t.Logf("runtime-config=%v", s.APIEnablement.RuntimeConfig)
151 t.Logf("Starting apiextensions-apiserver on port %d...", s.RecommendedOptions.SecureServing.BindPort)
152
153 config, err := s.Config()
154 if err != nil {
155 return result, fmt.Errorf("failed to create config from options: %v", err)
156 }
157
158 getOpenAPIDefinitions := openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(generatedopenapi.GetOpenAPIDefinitions)
159 namer := openapinamer.NewDefinitionNamer(extensionsapiserver.Scheme)
160 config.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(getOpenAPIDefinitions, namer)
161
162 completedConfig := config.Complete()
163 server, err := completedConfig.New(genericapiserver.NewEmptyDelegate())
164 if err != nil {
165 return result, fmt.Errorf("failed to create server: %v", err)
166 }
167
168 errCh = make(chan error)
169 go func(stopCh <-chan struct{}) {
170 defer close(errCh)
171
172 if err := server.GenericAPIServer.PrepareRun().Run(stopCh); err != nil {
173 errCh <- err
174 }
175 }(stopCh)
176
177 t.Logf("Waiting for /healthz to be ok...")
178
179 client, err := kubernetes.NewForConfig(server.GenericAPIServer.LoopbackClientConfig)
180 if err != nil {
181 return result, fmt.Errorf("failed to create a client: %v", err)
182 }
183 err = wait.Poll(100*time.Millisecond, time.Minute, func() (bool, error) {
184 select {
185 case err := <-errCh:
186 return false, err
187 default:
188 }
189
190 result := client.CoreV1().RESTClient().Get().AbsPath("/healthz").Do(context.TODO())
191 status := 0
192 result.StatusCode(&status)
193 if status == 200 {
194 return true, nil
195 }
196 return false, nil
197 })
198 if err != nil {
199 return result, fmt.Errorf("failed to wait for /healthz to return ok: %v", err)
200 }
201
202
203 result.ClientConfig = server.GenericAPIServer.LoopbackClientConfig
204 result.ServerOpts = s
205 result.TearDownFn = tearDown
206 result.CompletedConfig = completedConfig
207
208 return result, nil
209 }
210
211
212 func StartTestServerOrDie(t Logger, instanceOptions *TestServerInstanceOptions, flags []string, storageConfig *storagebackend.Config) *TestServer {
213 result, err := StartTestServer(t, instanceOptions, flags, storageConfig)
214 if err == nil {
215 return &result
216 }
217
218 t.Fatalf("failed to launch server: %v", err)
219 return nil
220 }
221
222 func createLocalhostListenerOnFreePort() (net.Listener, int, error) {
223 ln, err := net.Listen("tcp", "127.0.0.1:0")
224 if err != nil {
225 return nil, 0, err
226 }
227
228
229 tcpAddr, ok := ln.Addr().(*net.TCPAddr)
230 if !ok {
231 ln.Close()
232 return nil, 0, fmt.Errorf("invalid listen address: %q", ln.Addr().String())
233 }
234
235 return ln, tcpAddr.Port, nil
236 }
237
238
239
240
241
242
243
244
245 func pkgPath(t Logger) (string, error) {
246 _, thisFile, _, ok := runtime.Caller(0)
247 if !ok {
248 return "", fmt.Errorf("failed to get current file")
249 }
250
251 pkgPath := filepath.Dir(thisFile)
252
253
254
255 if testSrcdir, testWorkspace := os.Getenv("TEST_SRCDIR"), os.Getenv("TEST_WORKSPACE"); testSrcdir != "" && testWorkspace != "" {
256 t.Logf("Detected bazel env varaiables: TEST_SRCDIR=%q TEST_WORKSPACE=%q", testSrcdir, testWorkspace)
257 pkgPath = filepath.Join(testSrcdir, testWorkspace, pkgPath)
258 }
259
260
261
262 if !filepath.IsAbs(pkgPath) {
263 return "", fmt.Errorf("can't construct an absolute path from %q", pkgPath)
264 }
265
266 t.Logf("Resolved testserver package path to: %q", pkgPath)
267
268 return pkgPath, nil
269 }
270
View as plain text