mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 02:08:13 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			268 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2021 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package apiserver
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	openapi_v3 "github.com/googleapis/gnostic/openapiv3"
 | |
| 	"google.golang.org/protobuf/proto"
 | |
| 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 | |
| 	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 | |
| 	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
 | |
| 	genericfeatures "k8s.io/apiserver/pkg/features"
 | |
| 	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | |
| 	"k8s.io/client-go/dynamic"
 | |
| 	kubernetes "k8s.io/client-go/kubernetes"
 | |
| 	restclient "k8s.io/client-go/rest"
 | |
| 	featuregatetesting "k8s.io/component-base/featuregate/testing"
 | |
| 	"k8s.io/kube-openapi/pkg/spec3"
 | |
| 	apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
 | |
| 	"k8s.io/kubernetes/test/integration/framework"
 | |
| 	"sigs.k8s.io/yaml"
 | |
| )
 | |
| 
 | |
| func TestOpenAPIV3SpecRoundTrip(t *testing.T) {
 | |
| 	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.OpenAPIV3, true)()
 | |
| 	controlPlaneConfig := framework.NewIntegrationTestControlPlaneConfigWithOptions(&framework.ControlPlaneConfigOptions{})
 | |
| 	controlPlaneConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
 | |
| 	instanceConfig, _, closeFn := framework.RunAnAPIServer(controlPlaneConfig)
 | |
| 	defer closeFn()
 | |
| 	paths := []string{
 | |
| 		"/apis/apps/v1",
 | |
| 		"/apis/authentication.k8s.io/v1",
 | |
| 		"/apis/policy/v1",
 | |
| 		"/apis/batch/v1",
 | |
| 		"/version",
 | |
| 	}
 | |
| 	for _, path := range paths {
 | |
| 		t.Run(path, func(t *testing.T) {
 | |
| 			rt, err := restclient.TransportFor(instanceConfig.GenericAPIServer.LoopbackClientConfig)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			// attempt to fetch and unmarshal
 | |
| 			url := instanceConfig.GenericAPIServer.LoopbackClientConfig.Host + "/openapi/v3" + path
 | |
| 			req, err := http.NewRequest("GET", url, nil)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			resp, err := rt.RoundTrip(req)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			defer resp.Body.Close()
 | |
| 			bs, err := io.ReadAll(resp.Body)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			var firstSpec spec3.OpenAPI
 | |
| 			err = json.Unmarshal(bs, &firstSpec)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			specBytes, err := json.Marshal(&firstSpec)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			var secondSpec spec3.OpenAPI
 | |
| 			err = json.Unmarshal(specBytes, &secondSpec)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(firstSpec, secondSpec) {
 | |
| 				t.Fatal("spec mismatch")
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestAddRemoveGroupVersion(t *testing.T) {
 | |
| 	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.OpenAPIV3, true)()
 | |
| 	server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	defer server.TearDownFn()
 | |
| 	config := server.ClientConfig
 | |
| 
 | |
| 	apiExtensionClient, err := clientset.NewForConfig(config)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	dynamicClient, err := dynamic.NewForConfig(config)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	clientset, err := kubernetes.NewForConfig(config)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	// Create a new CRD with group mygroup.example.com
 | |
| 	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
 | |
| 	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	// It takes a second for CRD specs to propagate to the aggregator
 | |
| 	time.Sleep(4 * time.Second)
 | |
| 
 | |
| 	// Verify that the new group version is populated in the discovery for OpenaPI v3
 | |
| 	jsonData, err := clientset.RESTClient().Get().AbsPath("/openapi/v3").Do(context.TODO()).Raw()
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	openAPIv3GV := make(map[string][]string)
 | |
| 	err = json.Unmarshal(jsonData, &openAPIv3GV)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	paths, ok := openAPIv3GV["Paths"]
 | |
| 	if !ok {
 | |
| 		t.Fatal("OpenAPI v3 format error")
 | |
| 	}
 | |
| 
 | |
| 	foundPath := false
 | |
| 	for _, path := range paths {
 | |
| 		if strings.Contains(path, "mygroup.example.com/v1beta1") {
 | |
| 			foundPath = true
 | |
| 		}
 | |
| 	}
 | |
| 	if foundPath == false {
 | |
| 		t.Fatal("Expected group version mygroup.example.com to be present after CRD applied")
 | |
| 	}
 | |
| 
 | |
| 	// Check the spec for the newly published group version
 | |
| 	jsonData, err = clientset.RESTClient().Get().AbsPath("/openapi/v3/apis/mygroup.example.com/v1beta1").Do(context.TODO()).Raw()
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	var firstSpec spec3.OpenAPI
 | |
| 	err = json.Unmarshal(jsonData, &firstSpec)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	// Delete the CRD and ensure that the group/version is also deleted in discovery
 | |
| 	if err := fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient); err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	time.Sleep(4 * time.Second)
 | |
| 
 | |
| 	jsonData, err = clientset.RESTClient().Get().AbsPath("/openapi/v3").Do(context.TODO()).Raw()
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	err = json.Unmarshal(jsonData, &openAPIv3GV)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	paths, ok = openAPIv3GV["Paths"]
 | |
| 	if !ok {
 | |
| 		t.Fatal("OpenAPI v3 format error")
 | |
| 	}
 | |
| 
 | |
| 	for _, path := range paths {
 | |
| 		if strings.Contains(path, "mygroup.example.com") {
 | |
| 			t.Fatal("Unexpected group version mygroup.example.com in OpenAPI v3 discovery")
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestOpenAPIV3ProtoRoundtrip(t *testing.T) {
 | |
| 	// The OpenAPI V3 proto library strips fields that are sibling elements to $ref
 | |
| 	// See https://github.com/kubernetes/kubernetes/issues/106387 for more details
 | |
| 	t.Skip("Skipping OpenAPI V3 Proto roundtrip test")
 | |
| 	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.OpenAPIV3, true)()
 | |
| 	controlPlaneConfig := framework.NewIntegrationTestControlPlaneConfigWithOptions(&framework.ControlPlaneConfigOptions{})
 | |
| 	controlPlaneConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
 | |
| 	instanceConfig, _, closeFn := framework.RunAnAPIServer(controlPlaneConfig)
 | |
| 	defer closeFn()
 | |
| 	rt, err := restclient.TransportFor(instanceConfig.GenericAPIServer.LoopbackClientConfig)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	// attempt to fetch and unmarshal
 | |
| 	req, err := http.NewRequest("GET", instanceConfig.GenericAPIServer.LoopbackClientConfig.Host+"/openapi/v3/apis/apps/v1", nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	resp, err := rt.RoundTrip(req)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	defer resp.Body.Close()
 | |
| 	bs, err := io.ReadAll(resp.Body)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	var firstSpec spec3.OpenAPI
 | |
| 	err = json.Unmarshal(bs, &firstSpec)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	protoReq, err := http.NewRequest("GET", instanceConfig.GenericAPIServer.LoopbackClientConfig.Host+"/openapi/v3/apis/apps/v1", nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	protoReq.Header.Set("Accept", "application/com.github.proto-openapi.spec.v3@v1.0+protobuf")
 | |
| 	protoResp, err := rt.RoundTrip(protoReq)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	defer protoResp.Body.Close()
 | |
| 	bs, err = io.ReadAll(protoResp.Body)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	var protoDoc openapi_v3.Document
 | |
| 	err = proto.Unmarshal(bs, &protoDoc)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	yamlBytes, err := protoDoc.YAMLValue("")
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	jsonBytes, err := yaml.YAMLToJSON(yamlBytes)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	var specFromProto spec3.OpenAPI
 | |
| 	err = json.Unmarshal(jsonBytes, &specFromProto)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	if !reflect.DeepEqual(specFromProto, firstSpec) {
 | |
| 		t.Fatalf("spec mismatch - specFromProto: %s\n", jsonBytes)
 | |
| 	}
 | |
| }
 | 
