/* Copyright 2022 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package discovery import ( "testing" "github.com/stretchr/testify/assert" apidiscovery "k8s.io/api/apidiscovery/v2" apidiscoveryv2beta1 "k8s.io/api/apidiscovery/v2beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) func TestSplitGroupsAndResources(t *testing.T) { tests := []struct { name string agg apidiscovery.APIGroupDiscoveryList expectedGroups metav1.APIGroupList expectedGVResources map[schema.GroupVersion]*metav1.APIResourceList expectedFailedGVs map[schema.GroupVersion]error }{ { name: "Aggregated discovery: core/v1 group and pod resource", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "", }, Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "pods", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Pod", }, Scope: apidiscovery.ScopeNamespace, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "v1", Version: "v1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "", Version: "v1"}: { GroupVersion: "v1", APIResources: []metav1.APIResource{ { Name: "pods", Namespaced: true, Group: "", Version: "v1", Kind: "Pod", }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources at /apis", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v2", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v2", Kind: "Deployment", }, Scope: apidiscovery.ScopeNamespace, }, }, }, { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscovery.ScopeNamespace, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "apps", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "apps/v2", Version: "v2", }, { GroupVersion: "apps/v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "apps/v2", Version: "v2", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "apps", Version: "v1"}: { GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ { Name: "deployments", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", }, }, }, {Group: "apps", Version: "v2"}: { GroupVersion: "apps/v2", APIResources: []metav1.APIResource{ { Name: "deployments", Namespaced: true, Group: "apps", Version: "v2", Kind: "Deployment", }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery: 1 group/2 resources at /api, 1 group/2 resources at /apis", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "pods", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Pod", }, Scope: apidiscovery.ScopeNamespace, }, { Resource: "services", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Service", }, Scope: apidiscovery.ScopeNamespace, }, }, }, }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscovery.ScopeNamespace, }, { Resource: "statefulsets", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "StatefulSet", }, Scope: apidiscovery.ScopeNamespace, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "v1", Version: "v1", }, }, { Name: "apps", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "apps/v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "apps/v1", Version: "v1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "", Version: "v1"}: { GroupVersion: "v1", APIResources: []metav1.APIResource{ { Name: "pods", Namespaced: true, Group: "", Version: "v1", Kind: "Pod", }, { Name: "services", Namespaced: true, Group: "", Version: "v1", Kind: "Service", }, }, }, {Group: "apps", Version: "v1"}: { GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ { Name: "deployments", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", }, { Name: "statefulsets", Namespaced: true, Group: "apps", Version: "v1", Kind: "StatefulSet", }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery: multiple groups with cluster-scoped resources", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "pods", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Pod", }, Scope: apidiscovery.ScopeNamespace, }, { Resource: "namespaces", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Namespace", }, Scope: apidiscovery.ScopeCluster, }, }, }, }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "rbac.authorization.k8s.io", }, Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "roles", ResponseKind: &metav1.GroupVersionKind{ Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "Role", }, Scope: apidiscovery.ScopeCluster, }, { Resource: "clusterroles", ResponseKind: &metav1.GroupVersionKind{ Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole", }, Scope: apidiscovery.ScopeCluster, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "v1", Version: "v1", }, }, { Name: "rbac.authorization.k8s.io", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "rbac.authorization.k8s.io/v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "rbac.authorization.k8s.io/v1", Version: "v1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "", Version: "v1"}: { GroupVersion: "v1", APIResources: []metav1.APIResource{ { Name: "pods", Namespaced: true, Group: "", Version: "v1", Kind: "Pod", }, { Name: "namespaces", Namespaced: false, Group: "", Version: "v1", Kind: "Namespace", }, }, }, {Group: "rbac.authorization.k8s.io", Version: "v1"}: { GroupVersion: "rbac.authorization.k8s.io/v1", APIResources: []metav1.APIResource{ { Name: "roles", Namespaced: false, Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "Role", }, { Name: "clusterroles", Namespaced: false, Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole", }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery with single subresource", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscovery.ScopeNamespace, SingularResource: "deployment", ShortNames: []string{"deploy"}, Verbs: []string{"parentverb1", "parentverb2", "parentverb3", "parentverb4"}, Categories: []string{"all", "testcategory"}, Subresources: []apidiscovery.APISubresourceDiscovery{ { Subresource: "scale", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Verbs: []string{"get", "patch", "update"}, }, }, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "apps", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "apps/v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "apps/v1", Version: "v1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "apps", Version: "v1"}: { GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ { Name: "deployments", SingularName: "deployment", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", Verbs: []string{"parentverb1", "parentverb2", "parentverb3", "parentverb4"}, ShortNames: []string{"deploy"}, Categories: []string{"all", "testcategory"}, }, { Name: "deployments/scale", SingularName: "deployment", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", Verbs: []string{"get", "patch", "update"}, }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery with single subresource and parent missing GVK", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "external.metrics.k8s.io", }, Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1beta1", Resources: []apidiscovery.APIResourceDiscovery{ { // resilient to nil GVK for parent Resource: "*", Scope: apidiscovery.ScopeNamespace, SingularResource: "", Subresources: []apidiscovery.APISubresourceDiscovery{ { Subresource: "other-external-metric", ResponseKind: &metav1.GroupVersionKind{ Kind: "MetricValueList", }, Verbs: []string{"get"}, }, }, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "external.metrics.k8s.io", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "external.metrics.k8s.io/v1beta1", Version: "v1beta1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "external.metrics.k8s.io/v1beta1", Version: "v1beta1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "external.metrics.k8s.io", Version: "v1beta1"}: { GroupVersion: "external.metrics.k8s.io/v1beta1", APIResources: []metav1.APIResource{ // Since parent GVK was nil, it is NOT returned--only the subresource. { Name: "*/other-external-metric", SingularName: "", Namespaced: true, Group: "", Version: "", Kind: "MetricValueList", Verbs: []string{"get"}, }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery with single subresource and parent empty GVK", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "external.metrics.k8s.io", }, Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1beta1", Resources: []apidiscovery.APIResourceDiscovery{ { // resilient to empty GVK for parent Resource: "*", Scope: apidiscovery.ScopeNamespace, SingularResource: "", ResponseKind: &metav1.GroupVersionKind{}, Subresources: []apidiscovery.APISubresourceDiscovery{ { Subresource: "other-external-metric", ResponseKind: &metav1.GroupVersionKind{ Kind: "MetricValueList", }, Verbs: []string{"get"}, }, }, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "external.metrics.k8s.io", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "external.metrics.k8s.io/v1beta1", Version: "v1beta1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "external.metrics.k8s.io/v1beta1", Version: "v1beta1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "external.metrics.k8s.io", Version: "v1beta1"}: { GroupVersion: "external.metrics.k8s.io/v1beta1", APIResources: []metav1.APIResource{ // Since parent GVK was nil, it is NOT returned--only the subresource. { Name: "*/other-external-metric", SingularName: "", Namespaced: true, Group: "", Version: "", Kind: "MetricValueList", Verbs: []string{"get"}, }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery with multiple subresources", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscovery.ScopeNamespace, SingularResource: "deployment", Subresources: []apidiscovery.APISubresourceDiscovery{ { Subresource: "scale", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Verbs: []string{"get", "patch", "update"}, }, { Subresource: "status", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Verbs: []string{"get", "patch", "update"}, }, }, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "apps", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "apps/v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "apps/v1", Version: "v1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "apps", Version: "v1"}: { GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ { Name: "deployments", SingularName: "deployment", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", }, { Name: "deployments/scale", SingularName: "deployment", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", Verbs: []string{"get", "patch", "update"}, }, { Name: "deployments/status", SingularName: "deployment", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", Verbs: []string{"get", "patch", "update"}, }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery: single failed GV at /api", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "pods", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Pod", }, Scope: apidiscovery.ScopeNamespace, }, { Resource: "services", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Service", }, Scope: apidiscovery.ScopeNamespace, }, }, Freshness: apidiscovery.DiscoveryFreshnessStale, }, }, }, }, }, // Single core Group/Version is stale, so no Version within Group. expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{{Name: ""}}, }, // Single core Group/Version is stale, so there are no expected resources. expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{}, expectedFailedGVs: map[schema.GroupVersion]error{ {Group: "", Version: "v1"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "", Version: "v1"}}, }, }, { name: "Aggregated discovery: single failed GV at /apis", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscovery.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscovery.ScopeNamespace, }, { Resource: "statefulsets", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "StatefulSets", }, Scope: apidiscovery.ScopeNamespace, }, }, Freshness: apidiscovery.DiscoveryFreshnessStale, }, }, }, }, }, // Single apps/v1 Group/Version is stale, so no Version within Group. expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{{Name: "apps"}}, }, // Single apps/v1 Group/Version is stale, so there are no expected resources. expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{}, expectedFailedGVs: map[schema.GroupVersion]error{ {Group: "apps", Version: "v1"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "apps", Version: "v1"}}, }, }, { name: "Aggregated discovery: 1 group/2 versions/1 failed GV at /apis", agg: apidiscovery.APIGroupDiscoveryList{ Items: []apidiscovery.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscovery.APIVersionDiscovery{ // Stale v2 should report failed GV. { Version: "v2", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "daemonsets", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v2", Kind: "DaemonSets", }, Scope: apidiscovery.ScopeNamespace, }, }, Freshness: apidiscovery.DiscoveryFreshnessStale, }, { Version: "v1", Resources: []apidiscovery.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscovery.ScopeNamespace, }, }, }, }, }, }, }, // Only apps/v1 is non-stale expected Group/Version expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "apps", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "apps/v1", Version: "v1", }, }, // PreferredVersion must be apps/v1 PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "apps/v1", Version: "v1", }, }, }, }, // Only apps/v1 resources expected. expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "apps", Version: "v1"}: { GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ { Name: "deployments", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{ {Group: "apps", Version: "v2"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "apps", Version: "v2"}}, }, }, } for _, test := range tests { apiGroups, resourcesByGV, failedGVs := SplitGroupsAndResources(test.agg) assert.Equal(t, test.expectedFailedGVs, failedGVs) assert.Equal(t, test.expectedGroups, *apiGroups) assert.Equal(t, test.expectedGVResources, resourcesByGV) } } // Duplicated from test above. Remove after 1.33 func TestSplitGroupsAndResourcesV2Beta1(t *testing.T) { tests := []struct { name string agg apidiscoveryv2beta1.APIGroupDiscoveryList expectedGroups metav1.APIGroupList expectedGVResources map[schema.GroupVersion]*metav1.APIResourceList expectedFailedGVs map[schema.GroupVersion]error }{ { name: "Aggregated discovery: core/v1 group and pod resource", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "", }, Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "pods", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Pod", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "v1", Version: "v1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "", Version: "v1"}: { GroupVersion: "v1", APIResources: []metav1.APIResource{ { Name: "pods", Namespaced: true, Group: "", Version: "v1", Kind: "Pod", }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources at /apis", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v2", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v2", Kind: "Deployment", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, }, }, { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "apps", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "apps/v2", Version: "v2", }, { GroupVersion: "apps/v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "apps/v2", Version: "v2", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "apps", Version: "v1"}: { GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ { Name: "deployments", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", }, }, }, {Group: "apps", Version: "v2"}: { GroupVersion: "apps/v2", APIResources: []metav1.APIResource{ { Name: "deployments", Namespaced: true, Group: "apps", Version: "v2", Kind: "Deployment", }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery: 1 group/2 resources at /api, 1 group/2 resources at /apis", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "pods", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Pod", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, { Resource: "services", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Service", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, }, }, }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, { Resource: "statefulsets", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "StatefulSet", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "v1", Version: "v1", }, }, { Name: "apps", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "apps/v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "apps/v1", Version: "v1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "", Version: "v1"}: { GroupVersion: "v1", APIResources: []metav1.APIResource{ { Name: "pods", Namespaced: true, Group: "", Version: "v1", Kind: "Pod", }, { Name: "services", Namespaced: true, Group: "", Version: "v1", Kind: "Service", }, }, }, {Group: "apps", Version: "v1"}: { GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ { Name: "deployments", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", }, { Name: "statefulsets", Namespaced: true, Group: "apps", Version: "v1", Kind: "StatefulSet", }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery: multiple groups with cluster-scoped resources", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "pods", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Pod", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, { Resource: "namespaces", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Namespace", }, Scope: apidiscoveryv2beta1.ScopeCluster, }, }, }, }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "rbac.authorization.k8s.io", }, Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "roles", ResponseKind: &metav1.GroupVersionKind{ Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "Role", }, Scope: apidiscoveryv2beta1.ScopeCluster, }, { Resource: "clusterroles", ResponseKind: &metav1.GroupVersionKind{ Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole", }, Scope: apidiscoveryv2beta1.ScopeCluster, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "v1", Version: "v1", }, }, { Name: "rbac.authorization.k8s.io", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "rbac.authorization.k8s.io/v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "rbac.authorization.k8s.io/v1", Version: "v1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "", Version: "v1"}: { GroupVersion: "v1", APIResources: []metav1.APIResource{ { Name: "pods", Namespaced: true, Group: "", Version: "v1", Kind: "Pod", }, { Name: "namespaces", Namespaced: false, Group: "", Version: "v1", Kind: "Namespace", }, }, }, {Group: "rbac.authorization.k8s.io", Version: "v1"}: { GroupVersion: "rbac.authorization.k8s.io/v1", APIResources: []metav1.APIResource{ { Name: "roles", Namespaced: false, Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "Role", }, { Name: "clusterroles", Namespaced: false, Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole", }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery with single subresource", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscoveryv2beta1.ScopeNamespace, SingularResource: "deployment", ShortNames: []string{"deploy"}, Verbs: []string{"parentverb1", "parentverb2", "parentverb3", "parentverb4"}, Categories: []string{"all", "testcategory"}, Subresources: []apidiscoveryv2beta1.APISubresourceDiscovery{ { Subresource: "scale", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Verbs: []string{"get", "patch", "update"}, }, }, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "apps", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "apps/v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "apps/v1", Version: "v1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "apps", Version: "v1"}: { GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ { Name: "deployments", SingularName: "deployment", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", Verbs: []string{"parentverb1", "parentverb2", "parentverb3", "parentverb4"}, ShortNames: []string{"deploy"}, Categories: []string{"all", "testcategory"}, }, { Name: "deployments/scale", SingularName: "deployment", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", Verbs: []string{"get", "patch", "update"}, }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery with single subresource and parent missing GVK", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "external.metrics.k8s.io", }, Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1beta1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { // resilient to nil GVK for parent Resource: "*", Scope: apidiscoveryv2beta1.ScopeNamespace, SingularResource: "", Subresources: []apidiscoveryv2beta1.APISubresourceDiscovery{ { Subresource: "other-external-metric", ResponseKind: &metav1.GroupVersionKind{ Kind: "MetricValueList", }, Verbs: []string{"get"}, }, }, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "external.metrics.k8s.io", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "external.metrics.k8s.io/v1beta1", Version: "v1beta1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "external.metrics.k8s.io/v1beta1", Version: "v1beta1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "external.metrics.k8s.io", Version: "v1beta1"}: { GroupVersion: "external.metrics.k8s.io/v1beta1", APIResources: []metav1.APIResource{ // Since parent GVK was nil, it is NOT returned--only the subresource. { Name: "*/other-external-metric", SingularName: "", Namespaced: true, Group: "", Version: "", Kind: "MetricValueList", Verbs: []string{"get"}, }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery with single subresource and parent empty GVK", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "external.metrics.k8s.io", }, Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1beta1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { // resilient to empty GVK for parent Resource: "*", Scope: apidiscoveryv2beta1.ScopeNamespace, SingularResource: "", ResponseKind: &metav1.GroupVersionKind{}, Subresources: []apidiscoveryv2beta1.APISubresourceDiscovery{ { Subresource: "other-external-metric", ResponseKind: &metav1.GroupVersionKind{ Kind: "MetricValueList", }, Verbs: []string{"get"}, }, }, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "external.metrics.k8s.io", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "external.metrics.k8s.io/v1beta1", Version: "v1beta1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "external.metrics.k8s.io/v1beta1", Version: "v1beta1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "external.metrics.k8s.io", Version: "v1beta1"}: { GroupVersion: "external.metrics.k8s.io/v1beta1", APIResources: []metav1.APIResource{ // Since parent GVK was nil, it is NOT returned--only the subresource. { Name: "*/other-external-metric", SingularName: "", Namespaced: true, Group: "", Version: "", Kind: "MetricValueList", Verbs: []string{"get"}, }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery with multiple subresources", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscoveryv2beta1.ScopeNamespace, SingularResource: "deployment", Subresources: []apidiscoveryv2beta1.APISubresourceDiscovery{ { Subresource: "scale", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Verbs: []string{"get", "patch", "update"}, }, { Subresource: "status", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Verbs: []string{"get", "patch", "update"}, }, }, }, }, }, }, }, }, }, expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "apps", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "apps/v1", Version: "v1", }, }, PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "apps/v1", Version: "v1", }, }, }, }, expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "apps", Version: "v1"}: { GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ { Name: "deployments", SingularName: "deployment", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", }, { Name: "deployments/scale", SingularName: "deployment", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", Verbs: []string{"get", "patch", "update"}, }, { Name: "deployments/status", SingularName: "deployment", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", Verbs: []string{"get", "patch", "update"}, }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{}, }, { name: "Aggregated discovery: single failed GV at /api", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "pods", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Pod", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, { Resource: "services", ResponseKind: &metav1.GroupVersionKind{ Group: "", Version: "v1", Kind: "Service", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, }, Freshness: apidiscoveryv2beta1.DiscoveryFreshnessStale, }, }, }, }, }, // Single core Group/Version is stale, so no Version within Group. expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{{Name: ""}}, }, // Single core Group/Version is stale, so there are no expected resources. expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{}, expectedFailedGVs: map[schema.GroupVersion]error{ {Group: "", Version: "v1"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "", Version: "v1"}}, }, }, { name: "Aggregated discovery: single failed GV at /apis", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, { Resource: "statefulsets", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "StatefulSets", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, }, Freshness: apidiscoveryv2beta1.DiscoveryFreshnessStale, }, }, }, }, }, // Single apps/v1 Group/Version is stale, so no Version within Group. expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{{Name: "apps"}}, }, // Single apps/v1 Group/Version is stale, so there are no expected resources. expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{}, expectedFailedGVs: map[schema.GroupVersion]error{ {Group: "apps", Version: "v1"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "apps", Version: "v1"}}, }, }, { name: "Aggregated discovery: 1 group/2 versions/1 failed GV at /apis", agg: apidiscoveryv2beta1.APIGroupDiscoveryList{ Items: []apidiscoveryv2beta1.APIGroupDiscovery{ { ObjectMeta: metav1.ObjectMeta{ Name: "apps", }, Versions: []apidiscoveryv2beta1.APIVersionDiscovery{ // Stale v2 should report failed GV. { Version: "v2", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "daemonsets", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v2", Kind: "DaemonSets", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, }, Freshness: apidiscoveryv2beta1.DiscoveryFreshnessStale, }, { Version: "v1", Resources: []apidiscoveryv2beta1.APIResourceDiscovery{ { Resource: "deployments", ResponseKind: &metav1.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }, Scope: apidiscoveryv2beta1.ScopeNamespace, }, }, }, }, }, }, }, // Only apps/v1 is non-stale expected Group/Version expectedGroups: metav1.APIGroupList{ Groups: []metav1.APIGroup{ { Name: "apps", Versions: []metav1.GroupVersionForDiscovery{ { GroupVersion: "apps/v1", Version: "v1", }, }, // PreferredVersion must be apps/v1 PreferredVersion: metav1.GroupVersionForDiscovery{ GroupVersion: "apps/v1", Version: "v1", }, }, }, }, // Only apps/v1 resources expected. expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{ {Group: "apps", Version: "v1"}: { GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ { Name: "deployments", Namespaced: true, Group: "apps", Version: "v1", Kind: "Deployment", }, }, }, }, expectedFailedGVs: map[schema.GroupVersion]error{ {Group: "apps", Version: "v2"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "apps", Version: "v2"}}, }, }, } for _, test := range tests { apiGroups, resourcesByGV, failedGVs := SplitGroupsAndResourcesV2Beta1(test.agg) assert.Equal(t, test.expectedFailedGVs, failedGVs) assert.Equal(t, test.expectedGroups, *apiGroups) assert.Equal(t, test.expectedGVResources, resourcesByGV) } }