k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/apiserver/openapi/openapi_v2_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 "strings" 23 "testing" 24 "time" 25 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/apimachinery/pkg/util/wait" 31 "k8s.io/client-go/dynamic" 32 kubernetes "k8s.io/client-go/kubernetes" 33 "k8s.io/kube-openapi/pkg/validation/spec" 34 apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 35 "k8s.io/kubernetes/test/integration/framework" 36 ) 37 38 func TestOpenAPIV2CRDMergeNoDuplicateTypes(t *testing.T) { 39 server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd()) 40 if err != nil { 41 t.Fatal(err) 42 } 43 defer server.TearDownFn() 44 config := server.ClientConfig 45 46 apiExtensionClient, err := clientset.NewForConfig(config) 47 if err != nil { 48 t.Fatal(err) 49 } 50 dynamicClient, err := dynamic.NewForConfig(config) 51 if err != nil { 52 t.Fatal(err) 53 } 54 clientset, err := kubernetes.NewForConfig(config) 55 if err != nil { 56 t.Fatal(err) 57 } 58 59 foo := &apiextensionsv1.CustomResourceDefinition{ 60 ObjectMeta: metav1.ObjectMeta{ 61 Name: "foosubs.cr.bar.com", 62 }, 63 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 64 Group: "cr.bar.com", 65 Scope: apiextensionsv1.NamespaceScoped, 66 Names: apiextensionsv1.CustomResourceDefinitionNames{ 67 Plural: "foosubs", 68 Kind: "FooSub", 69 }, 70 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 71 { 72 Name: "v1", 73 Served: true, 74 Storage: true, 75 Schema: &apiextensionsv1.CustomResourceValidation{ 76 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 77 Type: "object", 78 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 79 "spec": { 80 Type: "object", 81 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 82 "replicas": { 83 Type: "integer", 84 }, 85 }, 86 }, 87 "status": { 88 Type: "object", 89 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 90 "replicas": { 91 Type: "integer", 92 }, 93 }, 94 }, 95 }, 96 }, 97 }, 98 Subresources: &apiextensionsv1.CustomResourceSubresources{ 99 Status: &apiextensionsv1.CustomResourceSubresourceStatus{}, 100 Scale: &apiextensionsv1.CustomResourceSubresourceScale{ 101 SpecReplicasPath: ".spec.replicas", 102 StatusReplicasPath: ".status.replicas", 103 }, 104 }, 105 }, 106 }, 107 }, 108 } 109 110 baz := &apiextensionsv1.CustomResourceDefinition{ 111 ObjectMeta: metav1.ObjectMeta{ 112 Name: "bazsubs.cr.bar.com", 113 }, 114 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 115 Group: "cr.bar.com", 116 Scope: apiextensionsv1.NamespaceScoped, 117 Names: apiextensionsv1.CustomResourceDefinitionNames{ 118 Plural: "bazsubs", 119 Kind: "BazSub", 120 }, 121 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 122 { 123 Name: "v1", 124 Served: true, 125 Storage: true, 126 Schema: &apiextensionsv1.CustomResourceValidation{ 127 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 128 Type: "object", 129 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 130 "spec": { 131 Type: "object", 132 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 133 "replicas": { 134 Type: "integer", 135 }, 136 }, 137 }, 138 "status": { 139 Type: "object", 140 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 141 "replicas": { 142 Type: "integer", 143 }, 144 }, 145 }, 146 }, 147 }, 148 }, 149 Subresources: &apiextensionsv1.CustomResourceSubresources{ 150 Status: &apiextensionsv1.CustomResourceSubresourceStatus{}, 151 Scale: &apiextensionsv1.CustomResourceSubresourceScale{ 152 SpecReplicasPath: ".spec.replicas", 153 StatusReplicasPath: ".status.replicas", 154 }, 155 }, 156 }, 157 }, 158 }, 159 } 160 161 _, err = fixtures.CreateNewV1CustomResourceDefinition(foo, apiExtensionClient, dynamicClient) 162 if err != nil { 163 t.Fatal(err) 164 } 165 166 _, err = fixtures.CreateNewV1CustomResourceDefinition(baz, apiExtensionClient, dynamicClient) 167 if err != nil { 168 t.Fatal(err) 169 } 170 171 var openAPISpec spec.Swagger 172 // Poll for the OpenAPI to be updated with the new CRD 173 wait.Poll(time.Second*1, wait.ForeverTestTimeout, func() (bool, error) { 174 jsonData, err := clientset.RESTClient().Get().AbsPath("/openapi/v2").Do(context.TODO()).Raw() 175 if err != nil { 176 t.Fatal(err) 177 } 178 openAPISpec = spec.Swagger{} 179 err = json.Unmarshal(jsonData, &openAPISpec) 180 if err != nil { 181 t.Fatal(err) 182 } 183 for schemaName := range openAPISpec.Definitions { 184 if strings.HasPrefix(schemaName, "com.bar.cr.v1.BazSub") { 185 return true, nil 186 } 187 } 188 return false, nil 189 }) 190 191 for schemaName := range openAPISpec.Definitions { 192 if strings.HasSuffix(schemaName, "_v2") { 193 t.Errorf("Error: Expected no _v2 types, got %s", schemaName) 194 } 195 } 196 }