k8s.io/kubernetes@v1.29.3/test/e2e/apimachinery/discovery.go (about)

     1  /*
     2  Copyright 2019 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  	"fmt"
    22  	"path"
    23  	"strings"
    24  
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime/schema"
    27  	utilversion "k8s.io/apimachinery/pkg/util/version"
    28  	"k8s.io/apiserver/pkg/endpoints/discovery"
    29  	clientdiscovery "k8s.io/client-go/discovery"
    30  	"k8s.io/kubernetes/test/e2e/framework"
    31  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    32  	"k8s.io/kubernetes/test/utils/crd"
    33  	"k8s.io/kubernetes/test/utils/format"
    34  	admissionapi "k8s.io/pod-security-admission/api"
    35  
    36  	"github.com/onsi/ginkgo/v2"
    37  	"github.com/onsi/gomega"
    38  )
    39  
    40  var storageVersionServerVersion = utilversion.MustParseSemantic("v1.13.99")
    41  var _ = SIGDescribe("Discovery", func() {
    42  	f := framework.NewDefaultFramework("discovery")
    43  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    44  
    45  	var namespaceName string
    46  
    47  	ginkgo.BeforeEach(func() {
    48  		namespaceName = f.Namespace.Name
    49  
    50  		e2eskipper.SkipUnlessServerVersionGTE(storageVersionServerVersion, f.ClientSet.Discovery())
    51  
    52  		ginkgo.By("Setting up server cert")
    53  		setupServerCert(namespaceName, serviceName)
    54  	})
    55  
    56  	ginkgo.It("should accurately determine present and missing resources", func(ctx context.Context) {
    57  		// checks that legacy api group resources function
    58  		ok, err := clientdiscovery.IsResourceEnabled(f.ClientSet.Discovery(), schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"})
    59  		framework.ExpectNoError(err)
    60  		if !ok {
    61  			framework.Failf("namespace.v1 should always be present")
    62  		}
    63  		// checks that non-legacy api group resources function
    64  		ok, err = clientdiscovery.IsResourceEnabled(f.ClientSet.Discovery(), schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"})
    65  		framework.ExpectNoError(err)
    66  		if !ok {
    67  			framework.Failf("deployments.v1.apps should always be present")
    68  		}
    69  		// checks that nonsense resources in existing api groups function
    70  		ok, err = clientdiscovery.IsResourceEnabled(f.ClientSet.Discovery(), schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "please-dont-ever-create-this"})
    71  		framework.ExpectNoError(err)
    72  		if ok {
    73  			framework.Failf("please-dont-ever-create-this.v1.apps should never be present")
    74  		}
    75  		// checks that resources resources in nonsense api groups function
    76  		ok, err = clientdiscovery.IsResourceEnabled(f.ClientSet.Discovery(), schema.GroupVersionResource{Group: "not-these-apps", Version: "v1", Resource: "deployments"})
    77  		framework.ExpectNoError(err)
    78  		if ok {
    79  			framework.Failf("deployments.v1.not-these-apps should never be present")
    80  		}
    81  	})
    82  
    83  	ginkgo.It("Custom resource should have storage version hash", func(ctx context.Context) {
    84  		testcrd, err := crd.CreateTestCRD(f)
    85  		if err != nil {
    86  			return
    87  		}
    88  		ginkgo.DeferCleanup(testcrd.CleanUp)
    89  		spec := testcrd.Crd.Spec
    90  		resources, err := testcrd.APIExtensionClient.Discovery().ServerResourcesForGroupVersion(spec.Group + "/" + spec.Versions[0].Name)
    91  		if err != nil {
    92  			framework.Failf("failed to find the discovery doc for %v: %v", resources, err)
    93  		}
    94  		found := false
    95  		var storageVersion string
    96  		for _, v := range spec.Versions {
    97  			if v.Storage {
    98  				storageVersion = v.Name
    99  			}
   100  		}
   101  		// DISCLAIMER: the algorithm of deriving the storageVersionHash
   102  		// is an implementation detail, which shouldn't be relied on by
   103  		// the clients. The following calculation is for test purpose
   104  		// only.
   105  		expected := discovery.StorageVersionHash(spec.Group, storageVersion, spec.Names.Kind)
   106  
   107  		for _, r := range resources.APIResources {
   108  			if r.Name == spec.Names.Plural {
   109  				found = true
   110  				if r.StorageVersionHash != expected {
   111  					framework.Failf("expected storageVersionHash of %s/%s/%s to be %s, got %s", r.Group, r.Version, r.Name, expected, r.StorageVersionHash)
   112  				}
   113  			}
   114  		}
   115  		if !found {
   116  			framework.Failf("didn't find resource %s in the discovery doc", spec.Names.Plural)
   117  		}
   118  	})
   119  
   120  	/*
   121  	   Release : v1.19
   122  	   Testname: Discovery, confirm the PreferredVersion for each api group
   123  	   Description: Ensure that a list of apis is retrieved.
   124  	   Each api group found MUST return a valid PreferredVersion unless the group suffix is example.com.
   125  	*/
   126  	framework.ConformanceIt("should validate PreferredVersion for each APIGroup", func(ctx context.Context) {
   127  
   128  		// get list of APIGroup endpoints
   129  		list := &metav1.APIGroupList{}
   130  		err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/apis/").Do(ctx).Into(list)
   131  		framework.ExpectNoError(err, "Failed to find /apis/")
   132  		framework.ExpectNotEqual(len(list.Groups), 0, "Missing APIGroups")
   133  
   134  		for _, group := range list.Groups {
   135  			if strings.HasSuffix(group.Name, ".example.com") {
   136  				// ignore known example dynamic API groups that are added/removed during the e2e test run
   137  				continue
   138  			}
   139  			framework.Logf("Checking APIGroup: %v", group.Name)
   140  
   141  			// locate APIGroup endpoint
   142  			checkGroup := &metav1.APIGroup{}
   143  			apiPath := "/apis/" + group.Name + "/"
   144  			err = f.ClientSet.Discovery().RESTClient().Get().AbsPath(apiPath).Do(ctx).Into(checkGroup)
   145  			framework.ExpectNoError(err, "Fail to access: %s", apiPath)
   146  			framework.ExpectNotEqual(len(checkGroup.Versions), 0, "No version found for %v", group.Name)
   147  			framework.Logf("PreferredVersion.GroupVersion: %s", checkGroup.PreferredVersion.GroupVersion)
   148  			framework.Logf("Versions found %v", checkGroup.Versions)
   149  
   150  			// confirm that the PreferredVersion is a valid version
   151  			match := false
   152  			for _, version := range checkGroup.Versions {
   153  				if version.GroupVersion == checkGroup.PreferredVersion.GroupVersion {
   154  					framework.Logf("%s matches %s", version.GroupVersion, checkGroup.PreferredVersion.GroupVersion)
   155  					match = true
   156  					break
   157  				}
   158  			}
   159  			if !match {
   160  				framework.Failf("Failed to find a valid version for PreferredVersion %s in versions:\n%s", checkGroup.PreferredVersion.GroupVersion, format.Object(checkGroup.Versions, 1))
   161  			}
   162  		}
   163  	})
   164  
   165  	/*
   166  		Release: v1.28
   167  		Testname: Discovery, confirm the groupVerion and a resourcefrom each apiGroup
   168  		Description: A resourceList MUST be found for each apiGroup that is retrieved.
   169  		For each apiGroup the groupVersion MUST equal the groupVersion as reported by
   170  		the schema. From each resourceList a valid resource MUST be found.
   171  	*/
   172  	framework.ConformanceIt("should locate the groupVersion and a resource within each APIGroup", func(ctx context.Context) {
   173  
   174  		tests := []struct {
   175  			apiBasePath   string
   176  			apiGroup      string
   177  			apiVersion    string
   178  			validResource string
   179  		}{
   180  			{
   181  				apiBasePath:   "/api",
   182  				apiGroup:      "",
   183  				apiVersion:    "v1",
   184  				validResource: "namespaces",
   185  			},
   186  			{
   187  				apiBasePath:   "/apis",
   188  				apiGroup:      "admissionregistration.k8s.io",
   189  				apiVersion:    "v1",
   190  				validResource: "validatingwebhookconfigurations",
   191  			},
   192  			{
   193  				apiBasePath:   "/apis",
   194  				apiGroup:      "apiextensions.k8s.io",
   195  				apiVersion:    "v1",
   196  				validResource: "customresourcedefinitions",
   197  			},
   198  			{
   199  				apiBasePath:   "/apis",
   200  				apiGroup:      "apiregistration.k8s.io",
   201  				apiVersion:    "v1",
   202  				validResource: "apiservices",
   203  			},
   204  			{
   205  				apiBasePath:   "/apis",
   206  				apiGroup:      "apps",
   207  				apiVersion:    "v1",
   208  				validResource: "deployments",
   209  			},
   210  			{
   211  				apiBasePath:   "/apis",
   212  				apiGroup:      "authentication.k8s.io",
   213  				apiVersion:    "v1",
   214  				validResource: "tokenreviews",
   215  			},
   216  			{
   217  				apiBasePath:   "/apis",
   218  				apiGroup:      "authorization.k8s.io",
   219  				apiVersion:    "v1",
   220  				validResource: "selfsubjectaccessreviews",
   221  			},
   222  			{
   223  				apiBasePath:   "/apis",
   224  				apiGroup:      "autoscaling",
   225  				apiVersion:    "v1",
   226  				validResource: "horizontalpodautoscalers",
   227  			},
   228  			{
   229  				apiBasePath:   "/apis",
   230  				apiGroup:      "autoscaling",
   231  				apiVersion:    "v2",
   232  				validResource: "horizontalpodautoscalers",
   233  			},
   234  			{
   235  				apiBasePath:   "/apis",
   236  				apiGroup:      "batch",
   237  				apiVersion:    "v1",
   238  				validResource: "jobs",
   239  			},
   240  			{
   241  				apiBasePath:   "/apis",
   242  				apiGroup:      "certificates.k8s.io",
   243  				apiVersion:    "v1",
   244  				validResource: "certificatesigningrequests",
   245  			},
   246  			{
   247  				apiBasePath:   "/apis",
   248  				apiGroup:      "coordination.k8s.io",
   249  				apiVersion:    "v1",
   250  				validResource: "leases",
   251  			},
   252  			{
   253  				apiBasePath:   "/apis",
   254  				apiGroup:      "discovery.k8s.io",
   255  				apiVersion:    "v1",
   256  				validResource: "endpointslices",
   257  			},
   258  			{
   259  				apiBasePath:   "/apis",
   260  				apiGroup:      "events.k8s.io",
   261  				apiVersion:    "v1",
   262  				validResource: "events",
   263  			},
   264  			{
   265  				apiBasePath:   "/apis",
   266  				apiGroup:      "networking.k8s.io",
   267  				apiVersion:    "v1",
   268  				validResource: "ingresses",
   269  			},
   270  			{
   271  				apiBasePath:   "/apis",
   272  				apiGroup:      "node.k8s.io",
   273  				apiVersion:    "v1",
   274  				validResource: "runtimeclasses",
   275  			},
   276  			{
   277  				apiBasePath:   "/apis",
   278  				apiGroup:      "policy",
   279  				apiVersion:    "v1",
   280  				validResource: "poddisruptionbudgets",
   281  			},
   282  			{
   283  				apiBasePath:   "/apis",
   284  				apiGroup:      "scheduling.k8s.io",
   285  				apiVersion:    "v1",
   286  				validResource: "priorityclasses",
   287  			},
   288  			{
   289  				apiBasePath:   "/apis",
   290  				apiGroup:      "storage.k8s.io",
   291  				apiVersion:    "v1",
   292  				validResource: "csinodes",
   293  			},
   294  		}
   295  
   296  		for _, t := range tests {
   297  			resourceList := &metav1.APIResourceList{}
   298  			apiPath := path.Join(t.apiBasePath, t.apiGroup, t.apiVersion)
   299  			ginkgo.By(fmt.Sprintf("Requesting APIResourceList from %q", apiPath))
   300  			err := f.ClientSet.Discovery().RESTClient().Get().AbsPath(apiPath).Do(ctx).Into(resourceList)
   301  			framework.ExpectNoError(err, "Fail to access: %s", apiPath)
   302  			gomega.Expect(resourceList.GroupVersion).To(gomega.Equal((schema.GroupVersion{Group: t.apiGroup, Version: t.apiVersion}).String()))
   303  
   304  			foundResource := false
   305  			for _, r := range resourceList.APIResources {
   306  				if t.validResource == r.Name {
   307  					foundResource = true
   308  					break
   309  				}
   310  			}
   311  			gomega.Expect(foundResource).To(gomega.BeTrue(), "Resource %q was not found inside of resourceList\n%#v", t.validResource, resourceList.APIResources)
   312  		}
   313  	})
   314  })