1
16
17 package controlplane
18
19 import (
20 "context"
21 "crypto/tls"
22 "encoding/json"
23 "io"
24 "net"
25 "net/http"
26 "net/http/httptest"
27 "reflect"
28 "strings"
29 "testing"
30
31 autoscalingapiv2beta1 "k8s.io/api/autoscaling/v2beta1"
32 autoscalingapiv2beta2 "k8s.io/api/autoscaling/v2beta2"
33 batchapiv1beta1 "k8s.io/api/batch/v1beta1"
34 certificatesapiv1beta1 "k8s.io/api/certificates/v1beta1"
35 discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
36 eventsv1beta1 "k8s.io/api/events/v1beta1"
37 nodev1beta1 "k8s.io/api/node/v1beta1"
38 policyapiv1beta1 "k8s.io/api/policy/v1beta1"
39 storageapiv1beta1 "k8s.io/api/storage/v1beta1"
40 extensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver"
41 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
42 "k8s.io/apimachinery/pkg/runtime/schema"
43 utilnet "k8s.io/apimachinery/pkg/util/net"
44 "k8s.io/apimachinery/pkg/util/sets"
45 "k8s.io/apimachinery/pkg/version"
46 "k8s.io/apiserver/pkg/authorization/authorizerfactory"
47 openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
48 genericapiserver "k8s.io/apiserver/pkg/server"
49 "k8s.io/apiserver/pkg/server/options"
50 "k8s.io/apiserver/pkg/server/resourceconfig"
51 serverstorage "k8s.io/apiserver/pkg/server/storage"
52 etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
53 "k8s.io/apiserver/pkg/util/openapi"
54 "k8s.io/client-go/discovery"
55 "k8s.io/client-go/informers"
56 "k8s.io/client-go/kubernetes"
57 restclient "k8s.io/client-go/rest"
58 kubeversion "k8s.io/component-base/version"
59 aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
60 "k8s.io/kubernetes/pkg/api/legacyscheme"
61 flowcontrolv1bet3 "k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta3"
62 "k8s.io/kubernetes/pkg/controlplane/reconcilers"
63 "k8s.io/kubernetes/pkg/controlplane/storageversionhashdata"
64 generatedopenapi "k8s.io/kubernetes/pkg/generated/openapi"
65 "k8s.io/kubernetes/pkg/kubeapiserver"
66 kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
67 certificatesrest "k8s.io/kubernetes/pkg/registry/certificates/rest"
68 corerest "k8s.io/kubernetes/pkg/registry/core/rest"
69 "k8s.io/kubernetes/pkg/registry/registrytest"
70 netutils "k8s.io/utils/net"
71
72 "github.com/stretchr/testify/assert"
73 )
74
75
76 func setUp(t *testing.T) (*etcd3testing.EtcdTestServer, Config, *assert.Assertions) {
77 server, storageConfig := etcd3testing.NewUnsecuredEtcd3TestClientServer(t)
78
79 config := &Config{
80 GenericConfig: genericapiserver.NewConfig(legacyscheme.Codecs),
81 ExtraConfig: ExtraConfig{
82 APIResourceConfigSource: DefaultAPIResourceConfigSource(),
83 APIServerServicePort: 443,
84 MasterCount: 1,
85 EndpointReconcilerType: reconcilers.MasterCountReconcilerType,
86 ServiceIPRange: net.IPNet{IP: netutils.ParseIPSloppy("10.0.0.0"), Mask: net.CIDRMask(24, 32)},
87 },
88 }
89
90 storageFactoryConfig := kubeapiserver.NewStorageFactoryConfig()
91 storageConfig.StorageObjectCountTracker = config.GenericConfig.StorageObjectCountTracker
92 resourceEncoding := resourceconfig.MergeResourceEncodingConfigs(storageFactoryConfig.DefaultResourceEncoding, storageFactoryConfig.ResourceEncodingOverrides)
93 storageFactory := serverstorage.NewDefaultStorageFactory(*storageConfig, "application/vnd.kubernetes.protobuf", storageFactoryConfig.Serializer, resourceEncoding, DefaultAPIResourceConfigSource(), nil)
94 etcdOptions := options.NewEtcdOptions(storageConfig)
95
96 etcdOptions.EnableWatchCache = false
97 err := etcdOptions.ApplyWithStorageFactoryTo(storageFactory, config.GenericConfig)
98 if err != nil {
99 t.Fatal(err)
100 }
101
102 kubeVersion := kubeversion.Get()
103 config.GenericConfig.Authorization.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer()
104 config.GenericConfig.Version = &kubeVersion
105 config.ExtraConfig.StorageFactory = storageFactory
106 config.GenericConfig.LoopbackClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs}}
107 config.GenericConfig.PublicAddress = netutils.ParseIPSloppy("192.168.10.4")
108 config.GenericConfig.LegacyAPIGroupPrefixes = sets.NewString("/api")
109 config.ExtraConfig.KubeletClientConfig = kubeletclient.KubeletClientConfig{Port: 10250}
110 config.ExtraConfig.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{
111 DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return nil, nil },
112 TLSClientConfig: &tls.Config{},
113 })
114
115
116 config.GenericConfig.SecureServing = &genericapiserver.SecureServingInfo{Listener: fakeLocalhost443Listener{}}
117
118 getOpenAPIDefinitions := openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(generatedopenapi.GetOpenAPIDefinitions)
119 namer := openapinamer.NewDefinitionNamer(legacyscheme.Scheme, extensionsapiserver.Scheme, aggregatorscheme.Scheme)
120 config.GenericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(getOpenAPIDefinitions, namer)
121
122 clientset, err := kubernetes.NewForConfig(config.GenericConfig.LoopbackClientConfig)
123 if err != nil {
124 t.Fatalf("unable to create client set due to %v", err)
125 }
126 config.ExtraConfig.VersionedInformers = informers.NewSharedInformerFactory(clientset, config.GenericConfig.LoopbackClientConfig.Timeout)
127
128 return server, *config, assert.New(t)
129 }
130
131 type fakeLocalhost443Listener struct{}
132
133 func (fakeLocalhost443Listener) Accept() (net.Conn, error) {
134 return nil, nil
135 }
136
137 func (fakeLocalhost443Listener) Close() error {
138 return nil
139 }
140
141 func (fakeLocalhost443Listener) Addr() net.Addr {
142 return &net.TCPAddr{
143 IP: net.IPv4(127, 0, 0, 1),
144 Port: 443,
145 }
146 }
147
148
149
150
151 func TestLegacyRestStorageStrategies(t *testing.T) {
152 _, etcdserver, apiserverCfg, _ := newInstance(t)
153 defer etcdserver.Terminate(t)
154
155 storageProvider, err := corerest.New(corerest.Config{
156 GenericConfig: corerest.GenericConfig{
157 StorageFactory: apiserverCfg.ExtraConfig.StorageFactory,
158 EventTTL: apiserverCfg.ExtraConfig.EventTTL,
159 LoopbackClientConfig: apiserverCfg.GenericConfig.LoopbackClientConfig,
160 Informers: apiserverCfg.ExtraConfig.VersionedInformers,
161 },
162 Proxy: corerest.ProxyConfig{
163 Transport: apiserverCfg.ExtraConfig.ProxyTransport,
164 KubeletClientConfig: apiserverCfg.ExtraConfig.KubeletClientConfig,
165 },
166 Services: corerest.ServicesConfig{
167 ClusterIPRange: apiserverCfg.ExtraConfig.ServiceIPRange,
168 NodePortRange: apiserverCfg.ExtraConfig.ServiceNodePortRange,
169 },
170 })
171 if err != nil {
172 t.Fatalf("unexpected error from REST storage: %v", err)
173 }
174
175 apiGroupInfo, err := storageProvider.NewRESTStorage(serverstorage.NewResourceConfig(), apiserverCfg.GenericConfig.RESTOptionsGetter)
176 if err != nil {
177 t.Errorf("failed to create legacy REST storage: %v", err)
178 }
179
180 strategyErrors := registrytest.ValidateStorageStrategies(apiGroupInfo.VersionedResourcesStorageMap["v1"])
181 for _, err := range strategyErrors {
182 t.Error(err)
183 }
184 }
185
186 func TestCertificatesRestStorageStrategies(t *testing.T) {
187 _, etcdserver, apiserverCfg, _ := newInstance(t)
188 defer etcdserver.Terminate(t)
189
190 certStorageProvider := certificatesrest.RESTStorageProvider{}
191 apiGroupInfo, err := certStorageProvider.NewRESTStorage(apiserverCfg.ExtraConfig.APIResourceConfigSource, apiserverCfg.GenericConfig.RESTOptionsGetter)
192 if err != nil {
193 t.Fatalf("unexpected error from REST storage: %v", err)
194 }
195
196 strategyErrors := registrytest.ValidateStorageStrategies(
197 apiGroupInfo.VersionedResourcesStorageMap[certificatesapiv1beta1.SchemeGroupVersion.Version])
198 for _, err := range strategyErrors {
199 t.Error(err)
200 }
201 }
202
203 func newInstance(t *testing.T) (*Instance, *etcd3testing.EtcdTestServer, Config, *assert.Assertions) {
204 etcdserver, config, assert := setUp(t)
205
206 apiserver, err := config.Complete().New(genericapiserver.NewEmptyDelegate())
207 if err != nil {
208 t.Fatalf("Error in bringing up the master: %v", err)
209 }
210
211 return apiserver, etcdserver, config, assert
212 }
213
214
215 func TestVersion(t *testing.T) {
216 s, etcdserver, _, _ := newInstance(t)
217 defer etcdserver.Terminate(t)
218
219 req, _ := http.NewRequest("GET", "/version", nil)
220 resp := httptest.NewRecorder()
221 s.GenericAPIServer.Handler.ServeHTTP(resp, req)
222 if resp.Code != 200 {
223 t.Fatalf("expected http 200, got: %d", resp.Code)
224 }
225
226 var info version.Info
227 err := json.NewDecoder(resp.Body).Decode(&info)
228 if err != nil {
229 t.Errorf("unexpected error: %v", err)
230 }
231
232 if !reflect.DeepEqual(kubeversion.Get(), info) {
233 t.Errorf("Expected %#v, Got %#v", kubeversion.Get(), info)
234 }
235 }
236
237 func decodeResponse(resp *http.Response, obj interface{}) error {
238 defer resp.Body.Close()
239
240 data, err := io.ReadAll(resp.Body)
241 if err != nil {
242 return err
243 }
244 if err := json.Unmarshal(data, obj); err != nil {
245 return err
246 }
247 return nil
248 }
249
250
251
252 func TestAPIVersionOfDiscoveryEndpoints(t *testing.T) {
253 apiserver, etcdserver, _, assert := newInstance(t)
254 defer etcdserver.Terminate(t)
255
256 server := httptest.NewServer(apiserver.GenericAPIServer.Handler.GoRestfulContainer.ServeMux)
257
258
259 resp, err := http.Get(server.URL + "/api")
260 if err != nil {
261 t.Errorf("unexpected error: %v", err)
262 }
263 apiVersions := metav1.APIVersions{}
264 assert.NoError(decodeResponse(resp, &apiVersions))
265 assert.Equal(apiVersions.APIVersion, "")
266
267
268 resp, err = http.Get(server.URL + "/api/v1")
269 if err != nil {
270 t.Errorf("unexpected error: %v", err)
271 }
272 resourceList := metav1.APIResourceList{}
273 assert.NoError(decodeResponse(resp, &resourceList))
274 assert.Equal(resourceList.APIVersion, "")
275
276
277 resp, err = http.Get(server.URL + "/apis")
278 if err != nil {
279 t.Errorf("unexpected error: %v", err)
280 }
281 groupList := metav1.APIGroupList{}
282 assert.NoError(decodeResponse(resp, &groupList))
283 assert.Equal(groupList.APIVersion, "")
284
285
286
287 resp, err = http.Get(server.URL + "/apis/autoscaling")
288 if err != nil {
289 t.Errorf("unexpected error: %v", err)
290 }
291 group := metav1.APIGroup{}
292 assert.NoError(decodeResponse(resp, &group))
293 assert.Equal(group.APIVersion, "v1")
294
295
296
297
298 resp, err = http.Get(server.URL + "/apis/autoscaling/v1")
299 if err != nil {
300 t.Errorf("unexpected error: %v", err)
301 }
302 resourceList = metav1.APIResourceList{}
303 assert.NoError(decodeResponse(resp, &resourceList))
304 assert.Equal(resourceList.APIVersion, "v1")
305
306 }
307
308
309 func TestStorageVersionHashes(t *testing.T) {
310 apiserver, etcdserver, _, _ := newInstance(t)
311 defer etcdserver.Terminate(t)
312
313 server := httptest.NewServer(apiserver.GenericAPIServer.Handler.GoRestfulContainer.ServeMux)
314
315 c := &restclient.Config{
316 Host: server.URL,
317 APIPath: "/api",
318 ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs},
319 }
320 discover := discovery.NewDiscoveryClientForConfigOrDie(c).WithLegacy()
321 _, all, err := discover.ServerGroupsAndResources()
322 if err != nil {
323 t.Error(err)
324 }
325 var count int
326 apiResources := sets.NewString()
327 for _, g := range all {
328 for _, r := range g.APIResources {
329 apiResources.Insert(g.GroupVersion + "/" + r.Name)
330 if strings.Contains(r.Name, "/") ||
331 storageversionhashdata.NoStorageVersionHash.Has(g.GroupVersion+"/"+r.Name) {
332 if r.StorageVersionHash != "" {
333 t.Errorf("expect resource %s/%s to have empty storageVersionHash, got hash %q", g.GroupVersion, r.Name, r.StorageVersionHash)
334 }
335 continue
336 }
337 if r.StorageVersionHash == "" {
338 t.Errorf("expect the storageVersionHash of %s/%s to exist", g.GroupVersion, r.Name)
339 continue
340 }
341
342
343 expected := storageversionhashdata.GVRToStorageVersionHash[g.GroupVersion+"/"+r.Name]
344 if r.StorageVersionHash != expected {
345 t.Errorf("expect the storageVersionHash of %s/%s to be %q, got %q", g.GroupVersion, r.Name, expected, r.StorageVersionHash)
346 }
347 count++
348 }
349 }
350 if count != len(storageversionhashdata.GVRToStorageVersionHash) {
351 knownResources := sets.StringKeySet(storageversionhashdata.GVRToStorageVersionHash)
352 t.Errorf("please remove the redundant entries from GVRToStorageVersionHash: %v", knownResources.Difference(apiResources).List())
353 }
354 }
355
356 func TestNoAlphaVersionsEnabledByDefault(t *testing.T) {
357 config := DefaultAPIResourceConfigSource()
358 for gv, enable := range config.GroupVersionConfigs {
359 if enable && strings.Contains(gv.Version, "alpha") {
360 t.Errorf("Alpha API version %s enabled by default", gv.String())
361 }
362 }
363
364 for gvr, enabled := range config.ResourceConfigs {
365 if !strings.Contains(gvr.Version, "alpha") || !enabled {
366 continue
367 }
368
369
370
371
372 gr := gvr.GroupVersion()
373 if enabled, found := config.GroupVersionConfigs[gr]; !found || enabled {
374 t.Errorf("Alpha API version %q should be disabled by default", gr.String())
375 }
376 }
377 }
378
379 func TestNoBetaVersionsEnabledByDefault(t *testing.T) {
380 config := DefaultAPIResourceConfigSource()
381 for gv, enable := range config.GroupVersionConfigs {
382 if enable && strings.Contains(gv.Version, "beta") {
383 t.Errorf("Beta API version %s enabled by default", gv.String())
384 }
385 }
386
387 for gvr, enabled := range config.ResourceConfigs {
388 if !strings.Contains(gvr.Version, "beta") || !enabled {
389 continue
390 }
391
392
393
394
395 gr := gvr.GroupVersion()
396 if enabled, found := config.GroupVersionConfigs[gr]; !found || enabled {
397 t.Errorf("Beta API version %q should be disabled by default", gr.String())
398 }
399 }
400 }
401
402 func TestDefaultVars(t *testing.T) {
403
404 for i := range stableAPIGroupVersionsEnabledByDefault {
405 gv := stableAPIGroupVersionsEnabledByDefault[i]
406 if strings.Contains(gv.Version, "beta") || strings.Contains(gv.Version, "alpha") {
407 t.Errorf("stableAPIGroupVersionsEnabledByDefault should contain stable version, but found: %q", gv.String())
408 }
409 }
410
411
412 for i := range legacyBetaEnabledByDefaultResources {
413 gv := legacyBetaEnabledByDefaultResources[i]
414 if !strings.Contains(gv.Version, "beta") {
415 t.Errorf("legacyBetaEnabledByDefaultResources should contain beta version, but found: %q", gv.String())
416 }
417 }
418
419
420 for i := range betaAPIGroupVersionsDisabledByDefault {
421 gv := betaAPIGroupVersionsDisabledByDefault[i]
422 if !strings.Contains(gv.Version, "beta") {
423 t.Errorf("betaAPIGroupVersionsDisabledByDefault should contain beta version, but found: %q", gv.String())
424 }
425 }
426
427
428 for i := range alphaAPIGroupVersionsDisabledByDefault {
429 gv := alphaAPIGroupVersionsDisabledByDefault[i]
430 if !strings.Contains(gv.Version, "alpha") {
431 t.Errorf("alphaAPIGroupVersionsDisabledByDefault should contain alpha version, but found: %q", gv.String())
432 }
433 }
434 }
435
436 func TestNewBetaResourcesEnabledByDefault(t *testing.T) {
437
438
439 legacyEnabledBetaResources := map[schema.GroupVersionResource]bool{
440 autoscalingapiv2beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"): true,
441 autoscalingapiv2beta2.SchemeGroupVersion.WithResource("horizontalpodautoscalers"): true,
442 batchapiv1beta1.SchemeGroupVersion.WithResource("cronjobs"): true,
443 discoveryv1beta1.SchemeGroupVersion.WithResource("endpointslices"): true,
444 eventsv1beta1.SchemeGroupVersion.WithResource("events"): true,
445 nodev1beta1.SchemeGroupVersion.WithResource("runtimeclasses"): true,
446 policyapiv1beta1.SchemeGroupVersion.WithResource("poddisruptionbudgets"): true,
447 policyapiv1beta1.SchemeGroupVersion.WithResource("podsecuritypolicies"): true,
448 storageapiv1beta1.SchemeGroupVersion.WithResource("csinodes"): true,
449 }
450
451
452
453
454
455 legacyBetaResourcesWithoutStableEquivalents := map[schema.GroupResource]bool{
456 flowcontrolv1bet3.SchemeGroupVersion.WithResource("flowschemas").GroupResource(): true,
457 flowcontrolv1bet3.SchemeGroupVersion.WithResource("prioritylevelconfigurations").GroupResource(): true,
458 }
459
460 config := DefaultAPIResourceConfigSource()
461 for gvr, enable := range config.ResourceConfigs {
462 if !strings.Contains(gvr.Version, "beta") {
463 continue
464 }
465 if !enable {
466 continue
467 }
468 if legacyEnabledBetaResources[gvr] {
469 continue
470 }
471 if legacyBetaResourcesWithoutStableEquivalents[gvr.GroupResource()] {
472 continue
473 }
474 t.Errorf("no new beta resources can be enabled by default, see https://github.com/kubernetes/enhancements/blob/0ad0fc8269165ca300d05ca51c7ce190a79976a5/keps/sig-architecture/3136-beta-apis-off-by-default/README.md: %v", gvr)
475 }
476 }
477
View as plain text