k8s.io/kubernetes@v1.29.3/test/integration/apiserver/openapi/openapi_merge_test.go (about)

     1  /*
     2  Copyright 2022 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  	"testing"
    23  	"time"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    27  	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    28  	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/client-go/dynamic"
    31  	kubernetes "k8s.io/client-go/kubernetes"
    32  	"k8s.io/kube-openapi/pkg/spec3"
    33  	apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    34  	"k8s.io/kubernetes/test/integration/framework"
    35  )
    36  
    37  const scaleSchemaName = "io.k8s.api.autoscaling.v1.Scale"
    38  const statusSchemaName = "io.k8s.apimachinery.pkg.apis.meta.v1.Status"
    39  const objectMetaSchemaName = "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
    40  
    41  func TestOpenAPIV3MultipleCRDsSameGV(t *testing.T) {
    42  	server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  	defer server.TearDownFn()
    47  	config := server.ClientConfig
    48  
    49  	apiExtensionClient, err := clientset.NewForConfig(config)
    50  	if err != nil {
    51  		t.Fatal(err)
    52  	}
    53  	dynamicClient, err := dynamic.NewForConfig(config)
    54  	if err != nil {
    55  		t.Fatal(err)
    56  	}
    57  	clientset, err := kubernetes.NewForConfig(config)
    58  	if err != nil {
    59  		t.Fatal(err)
    60  	}
    61  
    62  	fooWithSubresourceCRD := &apiextensionsv1.CustomResourceDefinition{
    63  		ObjectMeta: metav1.ObjectMeta{
    64  			Name: "foosubs.cr.bar.com",
    65  		},
    66  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
    67  			Group: "cr.bar.com",
    68  			Scope: apiextensionsv1.NamespaceScoped,
    69  			Names: apiextensionsv1.CustomResourceDefinitionNames{
    70  				Plural: "foosubs",
    71  				Kind:   "FooSub",
    72  			},
    73  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
    74  				{
    75  					Name:    "v1",
    76  					Served:  true,
    77  					Storage: true,
    78  					Schema: &apiextensionsv1.CustomResourceValidation{
    79  						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
    80  							Type: "object",
    81  							Properties: map[string]apiextensionsv1.JSONSchemaProps{
    82  								"spec": {
    83  									Type: "object",
    84  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
    85  										"replicas": {
    86  											Type: "integer",
    87  										},
    88  									},
    89  								},
    90  								"status": {
    91  									Type: "object",
    92  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
    93  										"replicas": {
    94  											Type: "integer",
    95  										},
    96  									},
    97  								},
    98  							},
    99  						},
   100  					},
   101  					Subresources: &apiextensionsv1.CustomResourceSubresources{
   102  						Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
   103  						Scale: &apiextensionsv1.CustomResourceSubresourceScale{
   104  							SpecReplicasPath:   ".spec.replicas",
   105  							StatusReplicasPath: ".status.replicas",
   106  						},
   107  					},
   108  				},
   109  			},
   110  		},
   111  	}
   112  
   113  	bazWithSubresourceCRD := &apiextensionsv1.CustomResourceDefinition{
   114  		ObjectMeta: metav1.ObjectMeta{
   115  			Name: "bazsubs.cr.bar.com",
   116  		},
   117  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   118  			Group: "cr.bar.com",
   119  			Scope: apiextensionsv1.NamespaceScoped,
   120  			Names: apiextensionsv1.CustomResourceDefinitionNames{
   121  				Plural: "bazsubs",
   122  				Kind:   "BazSub",
   123  			},
   124  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   125  				{
   126  					Name:    "v1",
   127  					Served:  true,
   128  					Storage: true,
   129  					Schema: &apiextensionsv1.CustomResourceValidation{
   130  						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   131  							Type: "object",
   132  							Properties: map[string]apiextensionsv1.JSONSchemaProps{
   133  								"spec": {
   134  									Type: "object",
   135  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
   136  										"replicas": {
   137  											Type: "integer",
   138  										},
   139  									},
   140  								},
   141  								"status": {
   142  									Type: "object",
   143  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
   144  										"replicas": {
   145  											Type: "integer",
   146  										},
   147  									},
   148  								},
   149  							},
   150  						},
   151  					},
   152  					Subresources: &apiextensionsv1.CustomResourceSubresources{
   153  						Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
   154  						Scale: &apiextensionsv1.CustomResourceSubresourceScale{
   155  							SpecReplicasPath:   ".spec.replicas",
   156  							StatusReplicasPath: ".status.replicas",
   157  						},
   158  					},
   159  				},
   160  			},
   161  		},
   162  	}
   163  
   164  	_, err = fixtures.CreateNewV1CustomResourceDefinition(fooWithSubresourceCRD, apiExtensionClient, dynamicClient)
   165  	if err != nil {
   166  		t.Fatal(err)
   167  	}
   168  
   169  	_, err = fixtures.CreateNewV1CustomResourceDefinition(bazWithSubresourceCRD, apiExtensionClient, dynamicClient)
   170  	if err != nil {
   171  		t.Fatal(err)
   172  	}
   173  
   174  	// It takes a second for CRD specs to propagate to the aggregator
   175  	time.Sleep(4 * time.Second)
   176  
   177  	jsonData, err := clientset.RESTClient().Get().AbsPath("/openapi/v3/apis/cr.bar.com/v1").Do(context.TODO()).Raw()
   178  	if err != nil {
   179  		t.Fatal(err)
   180  	}
   181  
   182  	openAPISpec := spec3.OpenAPI{}
   183  	err = json.Unmarshal(jsonData, &openAPISpec)
   184  	if err != nil {
   185  		t.Fatal(err)
   186  	}
   187  
   188  	// Ensure both CRD schemas are present in the OpenAPI
   189  	// Scale, status, and metadata dependencies must also be present
   190  	assert.Contains(t, openAPISpec.Components.Schemas, scaleSchemaName)
   191  	assert.Contains(t, openAPISpec.Components.Schemas, statusSchemaName)
   192  	assert.Contains(t, openAPISpec.Components.Schemas, objectMetaSchemaName)
   193  	assert.Contains(t, openAPISpec.Components.Schemas, "com.bar.cr.v1.BazSub")
   194  	assert.Contains(t, openAPISpec.Components.Schemas, "com.bar.cr.v1.FooSub")
   195  }