Add integration tests for OpenAPI v3.

This commit is contained in:
Kermit Alexander 2021-11-11 19:27:11 +00:00 committed by Jefftree
parent ae1984a89d
commit 3180012c1b

View File

@ -0,0 +1,267 @@
/*
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/ioutil"
"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 := ioutil.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 := ioutil.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 = ioutil.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)
}
}