k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/apimachinery/aggregated_discovery.go (about) 1 /* 2 Copyright 2024 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 apimachinery 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "time" 24 25 apidiscoveryv2 "k8s.io/api/apidiscovery/v2" 26 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 27 apiextensionclientset "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/runtime/schema" 31 "k8s.io/apimachinery/pkg/util/wait" 32 "k8s.io/apiserver/pkg/storage/names" 33 clientdiscovery "k8s.io/client-go/discovery" 34 "k8s.io/client-go/dynamic" 35 "k8s.io/kubernetes/test/e2e/framework" 36 admissionapi "k8s.io/pod-security-admission/api" 37 ) 38 39 var _ = SIGDescribe("AggregatedDiscovery", func() { 40 f := framework.NewDefaultFramework("aggregateddiscovery") 41 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 42 43 // Ensure that resources in both the legacy api/v1 group-version and resources in /apis/* exist 44 expectedLegacyGVR := []schema.GroupVersionResource{ 45 { 46 Group: "", 47 Version: "v1", 48 Resource: "namespaces", 49 }, 50 } 51 expectedGVR := []schema.GroupVersionResource{ 52 { 53 Group: "admissionregistration.k8s.io", 54 Version: "v1", 55 Resource: "validatingwebhookconfigurations", 56 }, 57 { 58 Group: "apiextensions.k8s.io", 59 Version: "v1", 60 Resource: "customresourcedefinitions", 61 }, 62 { 63 Group: "apiregistration.k8s.io", 64 Version: "v1", 65 Resource: "apiservices", 66 }, 67 { 68 Group: "apps", 69 Version: "v1", 70 Resource: "deployments", 71 }, 72 { 73 Group: "authentication.k8s.io", 74 Version: "v1", 75 Resource: "tokenreviews", 76 }, 77 { 78 Group: "authorization.k8s.io", 79 Version: "v1", 80 Resource: "selfsubjectaccessreviews", 81 }, 82 { 83 Group: "autoscaling", 84 Version: "v1", 85 Resource: "horizontalpodautoscalers", 86 }, 87 { 88 Group: "autoscaling", 89 Version: "v2", 90 Resource: "horizontalpodautoscalers", 91 }, 92 { 93 Group: "batch", 94 Version: "v1", 95 Resource: "jobs", 96 }, 97 { 98 Group: "certificates.k8s.io", 99 Version: "v1", 100 Resource: "certificatesigningrequests", 101 }, 102 { 103 Group: "coordination.k8s.io", 104 Version: "v1", 105 Resource: "leases", 106 }, 107 { 108 Group: "discovery.k8s.io", 109 Version: "v1", 110 Resource: "endpointslices", 111 }, 112 { 113 Group: "events.k8s.io", 114 Version: "v1", 115 Resource: "events", 116 }, 117 { 118 Group: "networking.k8s.io", 119 Version: "v1", 120 Resource: "ingresses", 121 }, 122 { 123 Group: "node.k8s.io", 124 Version: "v1", 125 Resource: "runtimeclasses", 126 }, 127 { 128 Group: "policy", 129 Version: "v1", 130 Resource: "poddisruptionbudgets", 131 }, 132 { 133 Group: "scheduling.k8s.io", 134 Version: "v1", 135 Resource: "priorityclasses", 136 }, 137 { 138 Group: "storage.k8s.io", 139 Version: "v1", 140 Resource: "csinodes", 141 }, 142 } 143 144 const aggregatedAccept = "application/json;g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList" 145 146 /* 147 Release : v1.30 148 Testname: Aggregated Discovery Endpoint Accept Headers 149 Description: An apiserver MUST support the Aggregated Discovery endpoint Accept headers. Built-in resources MUST all be present. 150 */ 151 framework.ConformanceIt("should support raw aggregated discovery endpoint Accept headers", func(ctx context.Context) { 152 d, err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/apis").SetHeader("Accept", aggregatedAccept).Do(ctx).Raw() 153 if err != nil { 154 framework.Failf("Failed to get raw aggregated discovery document") 155 } 156 157 groupList := apidiscoveryv2.APIGroupDiscoveryList{} 158 err = json.Unmarshal(d, &groupList) 159 if err != nil { 160 framework.Failf("Failed to parse discovery: %v", err) 161 } 162 163 for _, gvr := range expectedGVR { 164 if !isGVRPresentAPIDiscovery(groupList, gvr) { 165 framework.Failf("Expected gvr %s %s %s to exist in discovery", gvr.Group, gvr.Version, gvr.Resource) 166 167 } 168 } 169 170 d2, err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/api").SetHeader("Accept", aggregatedAccept).Do(ctx).Raw() 171 if err != nil { 172 framework.Failf("Failed to get raw aggregated discovery document") 173 } 174 175 groupListLegacy := apidiscoveryv2.APIGroupDiscoveryList{} 176 err = json.Unmarshal(d2, &groupListLegacy) 177 if err != nil { 178 framework.Failf("Failed to parse discovery: %v", err) 179 } 180 181 for _, gvr := range expectedLegacyGVR { 182 if !isGVRPresentAPIDiscovery(groupListLegacy, gvr) { 183 framework.Failf("Expected legacy gvr api %s %s to exist in discovery", gvr.Version, gvr.Resource) 184 } 185 } 186 }) 187 188 /* 189 Release : v1.30 190 Testname: Aggregated Discovery Endpoint Accept Headers CRDs 191 Description: An apiserver MUST support the Aggregated Discovery endpoint Accept headers. 192 Add a CRD to the apiserver. The CRD MUST appear in the discovery document. 193 */ 194 framework.ConformanceIt("should support raw aggregated discovery request for CRDs", func(ctx context.Context) { 195 config, err := framework.LoadConfig() 196 framework.ExpectNoError(err) 197 apiExtensionClient, err := apiextensionclientset.NewForConfig(config) 198 framework.ExpectNoError(err) 199 dynamicClient, err := dynamic.NewForConfig(config) 200 framework.ExpectNoError(err) 201 resourceName := "testcrd" 202 // Generate a CRD with random group name to avoid group conflict with other tests that run in parallel. 203 groupName := fmt.Sprintf("%s.example.com", names.SimpleNameGenerator.GenerateName("group")) 204 crd := &apiextensionsv1.CustomResourceDefinition{ 205 ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%ss.%s", resourceName, groupName)}, 206 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 207 Group: groupName, 208 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 209 { 210 Name: "v1", 211 Served: true, 212 Storage: true, 213 Schema: fixtures.AllowAllSchema(), 214 }, 215 }, 216 Names: apiextensionsv1.CustomResourceDefinitionNames{ 217 Plural: resourceName + "s", 218 Singular: resourceName, 219 Kind: resourceName, 220 ListKind: resourceName + "List", 221 }, 222 Scope: apiextensionsv1.NamespaceScoped, 223 }, 224 } 225 gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name, Resource: resourceName + "s"} 226 _, err = fixtures.CreateNewV1CustomResourceDefinition(crd, apiExtensionClient, dynamicClient) 227 framework.ExpectNoError(err) 228 defer func() { 229 _ = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient) 230 }() 231 232 err = wait.PollUntilContextTimeout(context.Background(), time.Second*1, wait.ForeverTestTimeout, true, func(context.Context) (bool, error) { 233 234 d, err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/apis").SetHeader("Accept", aggregatedAccept).Do(ctx).Raw() 235 if err != nil { 236 framework.Failf("Failed to get raw aggregated discovery document") 237 } 238 239 groupList := apidiscoveryv2.APIGroupDiscoveryList{} 240 err = json.Unmarshal(d, &groupList) 241 if err != nil { 242 framework.Failf("Failed to parse discovery: %v", err) 243 } 244 if isGVRPresentAPIDiscovery(groupList, gvr) { 245 return true, nil 246 } 247 return false, nil 248 249 }) 250 framework.ExpectNoError(err, "timed out waiting for CustomResourceDefinition GVR to appear in Discovery") 251 252 }) 253 254 /* 255 Release : v1.30 256 Testname: Aggregated Discovery Interface 257 Description: An apiserver MUST support the Aggregated Discovery client interface. Built-in resources MUST all be present. 258 */ 259 framework.ConformanceIt("should support aggregated discovery interface", func(ctx context.Context) { 260 d := f.ClientSet.Discovery() 261 262 ad, ok := d.(clientdiscovery.AggregatedDiscoveryInterface) 263 if !ok { 264 framework.Failf("Expected client to support aggregated discovery") 265 } 266 serverGroups, resourcesByGV, _, err := ad.GroupsAndMaybeResources() 267 if err != nil { 268 framework.Failf("Failed to get api groups and resources: %v", err) 269 } 270 271 expectedCombinedGVR := append(expectedGVR, expectedLegacyGVR...) 272 expectedGVs := []schema.GroupVersion{} 273 for _, gvr := range expectedCombinedGVR { 274 expectedGVs = append(expectedGVs, schema.GroupVersion{ 275 Group: gvr.Group, 276 Version: gvr.Version, 277 }) 278 } 279 280 for _, gvr := range expectedCombinedGVR { 281 if !isGVRPresent(resourcesByGV, gvr) { 282 framework.Failf("Expected %v to be present", gvr) 283 } 284 } 285 286 if serverGroups == nil { 287 framework.Failf("Expected serverGroups to be non-nil") 288 } 289 290 for _, gv := range expectedGVs { 291 if !isGVPresent(serverGroups, gv) { 292 framework.Failf("Expected %v to be present", gv) 293 } 294 } 295 }) 296 297 /* 298 Release : v1.30 299 Testname: Aggregated Discovery Interface CRDs 300 Description: An apiserver MUST support the Aggregated Discovery client interface. 301 Add a CRD to the apiserver. The CRD resource MUST be present in the discovery document. 302 */ 303 framework.ConformanceIt("should support aggregated discovery interface for CRDs", func(ctx context.Context) { 304 config, err := framework.LoadConfig() 305 framework.ExpectNoError(err) 306 apiExtensionClient, err := apiextensionclientset.NewForConfig(config) 307 framework.ExpectNoError(err) 308 dynamicClient, err := dynamic.NewForConfig(config) 309 framework.ExpectNoError(err) 310 resourceName := "testcrd" 311 // Generate a CRD with random group name to avoid group conflict with other tests that run in parallel. 312 groupName := fmt.Sprintf("%s.example.com", names.SimpleNameGenerator.GenerateName("group")) 313 crd := &apiextensionsv1.CustomResourceDefinition{ 314 ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%ss.%s", resourceName, groupName)}, 315 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 316 Group: groupName, 317 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 318 { 319 Name: "v1", 320 Served: true, 321 Storage: true, 322 Schema: fixtures.AllowAllSchema(), 323 }, 324 }, 325 Names: apiextensionsv1.CustomResourceDefinitionNames{ 326 Plural: resourceName + "s", 327 Singular: resourceName, 328 Kind: resourceName, 329 ListKind: resourceName + "List", 330 }, 331 Scope: apiextensionsv1.NamespaceScoped, 332 }, 333 } 334 gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name, Resource: resourceName + "s"} 335 _, err = fixtures.CreateNewV1CustomResourceDefinition(crd, apiExtensionClient, dynamicClient) 336 framework.ExpectNoError(err) 337 defer func() { 338 _ = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient) 339 }() 340 341 d := f.ClientSet.Discovery() 342 343 ad, ok := d.(clientdiscovery.AggregatedDiscoveryInterface) 344 if !ok { 345 framework.Failf("Expected client to support aggregated discovery") 346 } 347 err = wait.PollUntilContextTimeout(context.Background(), time.Second*1, wait.ForeverTestTimeout, true, func(context.Context) (bool, error) { 348 _, resourcesByGV, _, err := ad.GroupsAndMaybeResources() 349 if err != nil { 350 framework.Failf("Failed to get api groups and resources: %v", err) 351 } 352 if isGVRPresent(resourcesByGV, gvr) { 353 return true, nil 354 } 355 return false, nil 356 357 }) 358 framework.ExpectNoError(err, "timed out waiting for CustomResourceDefinition GVR to appear in Discovery") 359 }) 360 }) 361 362 func isGVRPresent(resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList, gvr schema.GroupVersionResource) bool { 363 resourceList, ok := resourcesByGV[gvr.GroupVersion()] 364 if !ok { 365 return false 366 } 367 368 if resourceList == nil { 369 return false 370 } 371 for _, resource := range resourceList.APIResources { 372 if resource.Name == gvr.Resource { 373 return true 374 } 375 } 376 return false 377 } 378 379 func isGVPresent(gvs *metav1.APIGroupList, gv schema.GroupVersion) bool { 380 for _, group := range gvs.Groups { 381 if group.Name != gv.Group { 382 continue 383 } 384 for _, version := range group.Versions { 385 if version.Version == gv.Version { 386 return true 387 } 388 } 389 } 390 return false 391 } 392 393 func isGVRPresentAPIDiscovery(apidiscovery apidiscoveryv2.APIGroupDiscoveryList, gvr schema.GroupVersionResource) bool { 394 for _, group := range apidiscovery.Items { 395 if gvr.Group == group.Name { 396 for _, version := range group.Versions { 397 if version.Version == gvr.Version { 398 for _, resource := range version.Resources { 399 if resource.Resource == gvr.Resource { 400 return true 401 } 402 } 403 } 404 } 405 } 406 } 407 return false 408 }