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  }