...

Source file src/k8s.io/kubernetes/test/integration/apiserver/openapi/openapiv3_test.go

Documentation: k8s.io/kubernetes/test/integration/apiserver/openapi

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package openapi
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"io"
    23  	"net/http"
    24  	"reflect"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	openapi_v3 "github.com/google/gnostic-models/openapiv3"
    30  	"google.golang.org/protobuf/proto"
    31  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    32  	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    33  	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
    34  	"k8s.io/client-go/dynamic"
    35  	kubernetes "k8s.io/client-go/kubernetes"
    36  	restclient "k8s.io/client-go/rest"
    37  	"k8s.io/kube-openapi/pkg/handler3"
    38  	"k8s.io/kube-openapi/pkg/spec3"
    39  	apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    40  	"k8s.io/kubernetes/test/integration/framework"
    41  	"k8s.io/kubernetes/test/utils/ktesting"
    42  	"sigs.k8s.io/yaml"
    43  )
    44  
    45  func TestOpenAPIV3SpecRoundTrip(t *testing.T) {
    46  	tCtx := ktesting.Init(t)
    47  	_, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{})
    48  	defer tearDownFn()
    49  
    50  	paths := []string{
    51  		"/apis/apps/v1",
    52  		"/apis/authentication.k8s.io/v1",
    53  		"/apis/policy/v1",
    54  		"/apis/batch/v1",
    55  		"/version",
    56  	}
    57  	for _, path := range paths {
    58  		t.Run(path, func(t *testing.T) {
    59  			rt, err := restclient.TransportFor(kubeConfig)
    60  			if err != nil {
    61  				t.Fatal(err)
    62  			}
    63  			// attempt to fetch and unmarshal
    64  			url := kubeConfig.Host + "/openapi/v3" + path
    65  			req, err := http.NewRequest("GET", url, nil)
    66  			if err != nil {
    67  				t.Fatal(err)
    68  			}
    69  			resp, err := rt.RoundTrip(req)
    70  			if err != nil {
    71  				t.Fatal(err)
    72  			}
    73  			defer resp.Body.Close()
    74  			bs, err := io.ReadAll(resp.Body)
    75  			if err != nil {
    76  				t.Fatal(err)
    77  			}
    78  			var firstSpec spec3.OpenAPI
    79  			err = json.Unmarshal(bs, &firstSpec)
    80  			if err != nil {
    81  				t.Fatal(err)
    82  			}
    83  			specBytes, err := json.Marshal(&firstSpec)
    84  			if err != nil {
    85  				t.Fatal(err)
    86  			}
    87  			var secondSpec spec3.OpenAPI
    88  			err = json.Unmarshal(specBytes, &secondSpec)
    89  			if err != nil {
    90  				t.Fatal(err)
    91  			}
    92  			if !reflect.DeepEqual(firstSpec, secondSpec) {
    93  				t.Fatal("spec mismatch")
    94  			}
    95  		})
    96  	}
    97  }
    98  
    99  func TestAddRemoveGroupVersion(t *testing.T) {
   100  	server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
   101  	if err != nil {
   102  		t.Fatal(err)
   103  	}
   104  	defer server.TearDownFn()
   105  	config := server.ClientConfig
   106  
   107  	apiExtensionClient, err := clientset.NewForConfig(config)
   108  	if err != nil {
   109  		t.Fatal(err)
   110  	}
   111  	dynamicClient, err := dynamic.NewForConfig(config)
   112  	if err != nil {
   113  		t.Fatal(err)
   114  	}
   115  	clientset, err := kubernetes.NewForConfig(config)
   116  	if err != nil {
   117  		t.Fatal(err)
   118  	}
   119  
   120  	// Create a new CRD with group mygroup.example.com
   121  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
   122  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   123  	if err != nil {
   124  		t.Fatal(err)
   125  	}
   126  
   127  	// It takes a second for CRD specs to propagate to the aggregator
   128  	time.Sleep(4 * time.Second)
   129  
   130  	// Verify that the new group version is populated in the discovery for OpenaPI v3
   131  	jsonData, err := clientset.RESTClient().Get().AbsPath("/openapi/v3").Do(context.TODO()).Raw()
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  	openAPIv3GV := handler3.OpenAPIV3Discovery{}
   136  	err = json.Unmarshal(jsonData, &openAPIv3GV)
   137  	if err != nil {
   138  		t.Fatal(err)
   139  	}
   140  	foundPath := false
   141  	for path := range openAPIv3GV.Paths {
   142  		if strings.Contains(path, "mygroup.example.com/v1beta1") {
   143  			foundPath = true
   144  		}
   145  	}
   146  	if foundPath == false {
   147  		t.Fatal("Expected group version mygroup.example.com to be present after CRD applied")
   148  	}
   149  
   150  	// Check the spec for the newly published group version
   151  	jsonData, err = clientset.RESTClient().Get().AbsPath("/openapi/v3/apis/mygroup.example.com/v1beta1").Do(context.TODO()).Raw()
   152  	if err != nil {
   153  		t.Fatal(err)
   154  	}
   155  	var firstSpec spec3.OpenAPI
   156  	err = json.Unmarshal(jsonData, &firstSpec)
   157  	if err != nil {
   158  		t.Fatal(err)
   159  	}
   160  
   161  	// Delete the CRD and ensure that the group/version is also deleted in discovery
   162  	if err := fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient); err != nil {
   163  		t.Fatal(err)
   164  	}
   165  	time.Sleep(4 * time.Second)
   166  
   167  	jsonData, err = clientset.RESTClient().Get().AbsPath("/openapi/v3").Do(context.TODO()).Raw()
   168  	if err != nil {
   169  		t.Fatal(err)
   170  	}
   171  	openAPIv3GV = handler3.OpenAPIV3Discovery{}
   172  	err = json.Unmarshal(jsonData, &openAPIv3GV)
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  
   177  	for path := range openAPIv3GV.Paths {
   178  		if strings.Contains(path, "mygroup.example.com") {
   179  			t.Fatal("Unexpected group version mygroup.example.com in OpenAPI v3 discovery")
   180  		}
   181  	}
   182  }
   183  
   184  func TestOpenAPIV3ProtoRoundtrip(t *testing.T) {
   185  	// The OpenAPI V3 proto library strips fields that are sibling elements to $ref
   186  	// See https://github.com/kubernetes/kubernetes/issues/106387 for more details
   187  	t.Skip("Skipping OpenAPI V3 Proto roundtrip test")
   188  
   189  	tCtx := ktesting.Init(t)
   190  	_, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{})
   191  	defer tearDownFn()
   192  
   193  	rt, err := restclient.TransportFor(kubeConfig)
   194  	if err != nil {
   195  		t.Fatal(err)
   196  	}
   197  	// attempt to fetch and unmarshal
   198  	req, err := http.NewRequest("GET", kubeConfig.Host+"/openapi/v3/apis/apps/v1", nil)
   199  	if err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	resp, err := rt.RoundTrip(req)
   203  	if err != nil {
   204  		t.Fatal(err)
   205  	}
   206  	defer resp.Body.Close()
   207  	bs, err := io.ReadAll(resp.Body)
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  	var firstSpec spec3.OpenAPI
   212  	err = json.Unmarshal(bs, &firstSpec)
   213  	if err != nil {
   214  		t.Fatal(err)
   215  	}
   216  
   217  	protoReq, err := http.NewRequest("GET", kubeConfig.Host+"/openapi/v3/apis/apps/v1", nil)
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  	protoReq.Header.Set("Accept", "application/com.github.proto-openapi.spec.v3@v1.0+protobuf")
   222  	protoResp, err := rt.RoundTrip(protoReq)
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	defer protoResp.Body.Close()
   227  	bs, err = io.ReadAll(protoResp.Body)
   228  	if err != nil {
   229  		t.Fatal(err)
   230  	}
   231  	var protoDoc openapi_v3.Document
   232  	err = proto.Unmarshal(bs, &protoDoc)
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  
   237  	yamlBytes, err := protoDoc.YAMLValue("")
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	jsonBytes, err := yaml.YAMLToJSON(yamlBytes)
   242  	if err != nil {
   243  		t.Fatal(err)
   244  	}
   245  	var specFromProto spec3.OpenAPI
   246  	err = json.Unmarshal(jsonBytes, &specFromProto)
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  
   251  	if !reflect.DeepEqual(specFromProto, firstSpec) {
   252  		t.Fatalf("spec mismatch - specFromProto: %s\n", jsonBytes)
   253  	}
   254  }
   255  

View as plain text