1
0
mirror of https://github.com/kubernetes/kubernetes.git synced 2025-02-06 10:03:10 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Jeffrey Ying
25d80ea41d
Merge 131b03b42b4b62cd70f621a4ac8076fdddd069d0 into 9a03243789677637762eb0f907e1b4e45a0136c1 2025-02-05 21:30:08 -08:00
Kubernetes Prow Robot
9a03243789
Merge pull request #129929 from serathius/deprecate-separate-rpc
Flip SeparateCacheWatchRPC feature gate to false and deprecate it
2025-02-05 17:18:16 -08:00
Kubernetes Prow Robot
22f25efc2c
Merge pull request #128991 from Henrywu573/cm-statuz
Add statusz endpoint for kube-controller-manager
2025-02-05 15:54:15 -08:00
Henry(Qishan) Wu
8bd4e1bab2 Update test/integration/serving/serving_test.go
Co-authored-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2025-02-05 09:48:08 -08:00
Marek Siarkowicz
4a5bbc4c15 Flip SeparateCacheWatchRPC feature gate to false and deprecate it.
Watch requests to etcd are mapped to a single stream that has a limited throughput.
By opening a lot of concurrent watch requests to single resource, users
could starve other watches from getting any events.

Separating the RPC was meant to protect the watch opened by cache.
However, as we are no longer planning to allow users to open watch directly to etcd,
the flag is not needed.
2025-01-31 14:08:15 +01:00
Jefftree
131b03b42b remove v2beta1 aggregated discovery in client-go 2025-01-28 19:50:21 +00:00
Henry Wu
5a8d77a2ae Add statusz endpoint for kube-controller-manager 2024-12-05 23:48:51 +00:00
10 changed files with 170 additions and 1575 deletions

View File

@ -64,6 +64,8 @@ import (
"k8s.io/component-base/term"
utilversion "k8s.io/component-base/version"
"k8s.io/component-base/version/verflag"
zpagesfeatures "k8s.io/component-base/zpages/features"
"k8s.io/component-base/zpages/statusz"
genericcontrollermanager "k8s.io/controller-manager/app"
"k8s.io/controller-manager/controller"
"k8s.io/controller-manager/pkg/clientbuilder"
@ -91,6 +93,8 @@ const (
ControllerStartJitter = 1.0
// ConfigzName is the name used for register kube-controller manager /configz, same with GroupName.
ConfigzName = "kubecontrollermanager.config.k8s.io"
// kubeControllerManager defines variable used internally when referring to cloud-controller-manager component
kubeControllerManager = "kube-controller-manager"
)
// NewControllerManagerCommand creates a *cobra.Command object with default parameters
@ -105,7 +109,7 @@ func NewControllerManagerCommand() *cobra.Command {
}
cmd := &cobra.Command{
Use: "kube-controller-manager",
Use: kubeControllerManager,
Long: `The Kubernetes controller manager is a daemon that embeds
the core control loops shipped with Kubernetes. In applications of robotics and
automation, a control loop is a non-terminating loop that regulates the state of
@ -213,6 +217,10 @@ func Run(ctx context.Context, c *config.CompletedConfig) error {
unsecuredMux = genericcontrollermanager.NewBaseHandler(&c.ComponentConfig.Generic.Debugging, healthzHandler)
slis.SLIMetricsWithReset{}.Install(unsecuredMux)
if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentStatusz) {
statusz.Install(unsecuredMux, kubeControllerManager, statusz.NewRegistry())
}
handler := genericcontrollermanager.BuildHandlerChain(unsecuredMux, &c.Authorization, &c.Authentication)
// TODO: handle stoppedCh and listenerStoppedCh returned by c.SecureServing.Serve
if _, _, err := c.SecureServing.Serve(handler, 0, stopCh); err != nil {
@ -267,7 +275,7 @@ func Run(ctx context.Context, c *config.CompletedConfig) error {
logger.Info("starting leader migration")
leaderMigrator = leadermigration.NewLeaderMigrator(&c.ComponentConfig.Generic.LeaderMigration,
"kube-controller-manager")
kubeControllerManager)
// startSATokenControllerInit is the original InitFunc.
startSATokenControllerInit := saTokenControllerDescriptor.GetInitFunc()
@ -295,7 +303,7 @@ func Run(ctx context.Context, c *config.CompletedConfig) error {
c.Client,
"kube-system",
id,
"kube-controller-manager",
kubeControllerManager,
binaryVersion.FinalizeVersion(),
emulationVersion.FinalizeVersion(),
coordinationv1.OldestEmulationVersion,
@ -634,7 +642,7 @@ func CreateControllerContext(ctx context.Context, s *config.CompletedConfig, roo
RESTMapper: restMapper,
InformersStarted: make(chan struct{}),
ResyncPeriod: ResyncPeriod(s),
ControllerManagerMetrics: controllersmetrics.NewControllerManagerMetrics("kube-controller-manager"),
ControllerManagerMetrics: controllersmetrics.NewControllerManagerMetrics(kubeControllerManager),
}
if controllerContext.ComponentConfig.GarbageCollectorController.EnableGarbageCollector &&

View File

@ -325,6 +325,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
genericfeatures.SeparateCacheWatchRPC: {
{Version: version.MustParse("1.28"), Default: true, PreRelease: featuregate.Beta},
{Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Deprecated},
},
genericfeatures.StorageVersionAPI: {

View File

@ -364,6 +364,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
SeparateCacheWatchRPC: {
{Version: version.MustParse("1.28"), Default: true, PreRelease: featuregate.Beta},
{Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Deprecated},
},
StorageVersionAPI: {

View File

@ -20,7 +20,6 @@ import (
"fmt"
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"
)
@ -155,124 +154,3 @@ func convertAPISubresource(parent metav1.APIResource, in apidiscovery.APISubreso
result.Verbs = in.Verbs
return result, nil
}
// Please note the functions below will be removed in v1.33. They facilitate conversion
// between the deprecated type apidiscoveryv2beta1.APIGroupDiscoveryList.
// SplitGroupsAndResourcesV2Beta1 transforms "aggregated" discovery top-level structure into
// the previous "unaggregated" discovery groups and resources.
// Deprecated: Please use SplitGroupsAndResources
func SplitGroupsAndResourcesV2Beta1(aggregatedGroups apidiscoveryv2beta1.APIGroupDiscoveryList) (
*metav1.APIGroupList,
map[schema.GroupVersion]*metav1.APIResourceList,
map[schema.GroupVersion]error) {
// Aggregated group list will contain the entirety of discovery, including
// groups, versions, and resources. GroupVersions marked "stale" are failed.
groups := []*metav1.APIGroup{}
failedGVs := map[schema.GroupVersion]error{}
resourcesByGV := map[schema.GroupVersion]*metav1.APIResourceList{}
for _, aggGroup := range aggregatedGroups.Items {
group, resources, failed := convertAPIGroupv2beta1(aggGroup)
groups = append(groups, group)
for gv, resourceList := range resources {
resourcesByGV[gv] = resourceList
}
for gv, err := range failed {
failedGVs[gv] = err
}
}
// Transform slice of groups to group list before returning.
groupList := &metav1.APIGroupList{}
groupList.Groups = make([]metav1.APIGroup, 0, len(groups))
for _, group := range groups {
groupList.Groups = append(groupList.Groups, *group)
}
return groupList, resourcesByGV, failedGVs
}
// convertAPIGroupv2beta1 tranforms an "aggregated" APIGroupDiscovery to an "legacy" APIGroup,
// also returning the map of APIResourceList for resources within GroupVersions.
func convertAPIGroupv2beta1(g apidiscoveryv2beta1.APIGroupDiscovery) (
*metav1.APIGroup,
map[schema.GroupVersion]*metav1.APIResourceList,
map[schema.GroupVersion]error) {
// Iterate through versions to convert to group and resources.
group := &metav1.APIGroup{}
gvResources := map[schema.GroupVersion]*metav1.APIResourceList{}
failedGVs := map[schema.GroupVersion]error{}
group.Name = g.ObjectMeta.Name
for _, v := range g.Versions {
gv := schema.GroupVersion{Group: g.Name, Version: v.Version}
if v.Freshness == apidiscoveryv2beta1.DiscoveryFreshnessStale {
failedGVs[gv] = StaleGroupVersionError{gv: gv}
continue
}
version := metav1.GroupVersionForDiscovery{}
version.GroupVersion = gv.String()
version.Version = v.Version
group.Versions = append(group.Versions, version)
// PreferredVersion is first non-stale Version
if group.PreferredVersion == (metav1.GroupVersionForDiscovery{}) {
group.PreferredVersion = version
}
resourceList := &metav1.APIResourceList{}
resourceList.GroupVersion = gv.String()
for _, r := range v.Resources {
resource, err := convertAPIResourcev2beta1(r)
if err == nil {
resourceList.APIResources = append(resourceList.APIResources, resource)
}
// Subresources field in new format get transformed into full APIResources.
// It is possible a partial result with an error was returned to be used
// as the parent resource for the subresource.
for _, subresource := range r.Subresources {
sr, err := convertAPISubresourcev2beta1(resource, subresource)
if err == nil {
resourceList.APIResources = append(resourceList.APIResources, sr)
}
}
}
gvResources[gv] = resourceList
}
return group, gvResources, failedGVs
}
// convertAPIResource tranforms a APIResourceDiscovery to an APIResource. We are
// resilient to missing GVK, since this resource might be the parent resource
// for a subresource. If the parent is missing a GVK, it is not returned in
// discovery, and the subresource MUST have the GVK.
func convertAPIResourcev2beta1(in apidiscoveryv2beta1.APIResourceDiscovery) (metav1.APIResource, error) {
result := metav1.APIResource{
Name: in.Resource,
SingularName: in.SingularResource,
Namespaced: in.Scope == apidiscoveryv2beta1.ScopeNamespace,
Verbs: in.Verbs,
ShortNames: in.ShortNames,
Categories: in.Categories,
}
// Can return partial result with error, which can be the parent for a
// subresource. Do not add this result to the returned discovery resources.
if in.ResponseKind == nil || (*in.ResponseKind) == emptyKind {
return result, fmt.Errorf("discovery resource %s missing GVK", in.Resource)
}
result.Group = in.ResponseKind.Group
result.Version = in.ResponseKind.Version
result.Kind = in.ResponseKind.Kind
return result, nil
}
// convertAPISubresource tranforms a APISubresourceDiscovery to an APIResource.
func convertAPISubresourcev2beta1(parent metav1.APIResource, in apidiscoveryv2beta1.APISubresourceDiscovery) (metav1.APIResource, error) {
result := metav1.APIResource{}
if in.ResponseKind == nil || (*in.ResponseKind) == emptyKind {
return result, fmt.Errorf("subresource %s/%s missing GVK", parent.Name, in.Subresource)
}
result.Name = fmt.Sprintf("%s/%s", parent.Name, in.Subresource)
result.SingularName = parent.SingularName
result.Namespaced = parent.Namespaced
result.Group = in.ResponseKind.Group
result.Version = in.ResponseKind.Version
result.Kind = in.ResponseKind.Kind
result.Verbs = in.Verbs
return result, nil
}

View File

@ -21,7 +21,6 @@ import (
"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"
)
@ -962,941 +961,3 @@ func TestSplitGroupsAndResources(t *testing.T) {
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)
}
}

View File

@ -1416,7 +1416,7 @@ func TestMemCacheAggregatedServerGroups(t *testing.T) {
output, err := json.Marshal(agg)
require.NoError(t, err)
// Content-type is "aggregated" discovery format.
w.Header().Set("Content-Type", discovery.AcceptV2Beta1)
w.Header().Set("Content-Type", discovery.AcceptV2)
w.WriteHeader(http.StatusOK)
w.Write(output)
}))

View File

@ -33,7 +33,6 @@ import (
"google.golang.org/protobuf/proto"
apidiscoveryv2 "k8s.io/api/apidiscovery/v2"
apidiscoveryv2beta1 "k8s.io/api/apidiscovery/v2beta1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -63,14 +62,12 @@ const (
// Aggregated discovery content-type (v2beta1). NOTE: content-type parameters
// MUST be ordered (g, v, as) for server in "Accept" header (BUT we are resilient
// to ordering when comparing returned values in "Content-Type" header).
AcceptV2Beta1 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList"
AcceptV2 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList"
AcceptV2 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList"
// Prioritize aggregated discovery by placing first in the order of discovery accept types.
acceptDiscoveryFormats = AcceptV2 + "," + AcceptV2Beta1 + "," + AcceptV1
acceptDiscoveryFormats = AcceptV2 + "," + AcceptV1
)
// Aggregated discovery content-type GVK.
var v2Beta1GVK = schema.GroupVersionKind{Group: "apidiscovery.k8s.io", Version: "v2beta1", Kind: "APIGroupDiscoveryList"}
var v2GVK = schema.GroupVersionKind{Group: "apidiscovery.k8s.io", Version: "v2", Kind: "APIGroupDiscoveryList"}
// DiscoveryInterface holds the methods that discover server-supported API groups,
@ -274,13 +271,6 @@ func (d *DiscoveryClient) downloadLegacy() (
return nil, nil, nil, err
}
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
} else if isGVK, _ := ContentTypeIsGVK(responseContentType, v2Beta1GVK); isGVK {
var aggregatedDiscovery apidiscoveryv2beta1.APIGroupDiscoveryList
err = json.Unmarshal(body, &aggregatedDiscovery)
if err != nil {
return nil, nil, nil, err
}
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResourcesV2Beta1(aggregatedDiscovery)
} else {
// Default is unaggregated discovery v1.
var v metav1.APIVersions
@ -333,13 +323,6 @@ func (d *DiscoveryClient) downloadAPIs() (
return nil, nil, nil, err
}
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
} else if isGVK, _ := ContentTypeIsGVK(responseContentType, v2Beta1GVK); isGVK {
var aggregatedDiscovery apidiscoveryv2beta1.APIGroupDiscoveryList
err = json.Unmarshal(body, &aggregatedDiscovery)
if err != nil {
return nil, nil, nil, err
}
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResourcesV2Beta1(aggregatedDiscovery)
} else {
// Default is unaggregated discovery v1.
err = json.Unmarshal(body, apiGroupList)

View File

@ -33,7 +33,6 @@ import (
"github.com/stretchr/testify/require"
golangproto "google.golang.org/protobuf/proto"
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"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -1347,9 +1346,7 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
tests := []struct {
name string
corev1 *apidiscovery.APIGroupDiscoveryList
corev1DiscoveryBeta *apidiscoveryv2beta1.APIGroupDiscoveryList
apis *apidiscovery.APIGroupDiscoveryList
apisDiscoveryBeta *apidiscoveryv2beta1.APIGroupDiscoveryList
expectedGroupNames []string
expectedGroupVersions []string
expectedGVKs []string
@ -1379,28 +1376,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
corev1DiscoveryBeta: &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,
},
},
},
},
},
},
},
apis: &apidiscovery.APIGroupDiscoveryList{
Items: []apidiscovery.APIGroupDiscovery{
{
@ -1426,31 +1401,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
apisDiscoveryBeta: &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,
},
},
},
},
},
},
},
expectedGroupNames: []string{"", "apps"},
expectedGroupVersions: []string{"v1", "apps/v1"},
expectedGVKs: []string{
@ -1482,28 +1432,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
corev1DiscoveryBeta: &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,
},
},
},
},
},
},
},
apis: &apidiscovery.APIGroupDiscoveryList{
Items: []apidiscovery.APIGroupDiscovery{
{
@ -1543,45 +1471,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
apisDiscoveryBeta: &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,
},
},
},
{
Version: "v2",
Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
{
Resource: "deployments",
ResponseKind: &metav1.GroupVersionKind{
Group: "apps",
Version: "v2",
Kind: "Deployment",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
},
},
},
},
},
},
expectedGroupNames: []string{"", "apps"},
expectedGroupVersions: []string{"v1", "apps/v1", "apps/v2"},
expectedGVKs: []string{
@ -1614,28 +1503,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
corev1DiscoveryBeta: &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,
},
},
},
},
},
},
},
apis: &apidiscovery.APIGroupDiscoveryList{
Items: []apidiscovery.APIGroupDiscovery{
{
@ -1676,46 +1543,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
apisDiscoveryBeta: &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,
},
},
},
{
Version: "v2",
Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
{
Resource: "deployments",
ResponseKind: &metav1.GroupVersionKind{
Group: "apps",
Version: "v2",
Kind: "Deployment",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
},
Freshness: apidiscoveryv2beta1.DiscoveryFreshnessStale,
},
},
},
},
},
expectedGroupNames: []string{"", "apps"},
expectedGroupVersions: []string{"v1", "apps/v1"},
expectedGVKs: []string{
@ -1757,37 +1584,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
corev1DiscoveryBeta: &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,
},
},
},
},
},
},
},
apis: &apidiscovery.APIGroupDiscoveryList{
Items: []apidiscovery.APIGroupDiscovery{
{
@ -1847,65 +1643,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
apisDiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{
Items: []apidiscoveryv2beta1.APIGroupDiscovery{
{
ObjectMeta: metav1.ObjectMeta{
Name: "apps",
},
Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
// Stale "v2" version not included.
{
Version: "v2",
Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
{
Resource: "deployments",
ResponseKind: &metav1.GroupVersionKind{
Group: "apps",
Version: "v2",
Kind: "Deployment",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
{
Resource: "statefulsets",
ResponseKind: &metav1.GroupVersionKind{
Group: "apps",
Version: "v2",
Kind: "StatefulSet",
},
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,
},
{
Resource: "statefulsets",
ResponseKind: &metav1.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "StatefulSet",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
},
},
},
},
},
},
expectedGroupNames: []string{"", "apps"},
expectedGroupVersions: []string{"v1", "apps/v1"},
expectedGVKs: []string{
@ -1949,37 +1686,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
corev1DiscoveryBeta: &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,
},
},
},
},
},
},
},
apis: &apidiscovery.APIGroupDiscoveryList{
Items: []apidiscovery.APIGroupDiscovery{
{
@ -2069,95 +1775,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
apisDiscoveryBeta: &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: "StatefulSet",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "batch",
},
Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
// Stale Group/Version is not included
{
Version: "v1",
Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
{
Resource: "jobs",
ResponseKind: &metav1.GroupVersionKind{
Group: "batch",
Version: "v1",
Kind: "Job",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
{
Resource: "cronjobs",
ResponseKind: &metav1.GroupVersionKind{
Group: "batch",
Version: "v1",
Kind: "CronJob",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
},
Freshness: apidiscoveryv2beta1.DiscoveryFreshnessStale,
},
{
Version: "v1beta1",
Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
{
Resource: "jobs",
ResponseKind: &metav1.GroupVersionKind{
Group: "batch",
Version: "v1beta1",
Kind: "Job",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
{
Resource: "cronjobs",
ResponseKind: &metav1.GroupVersionKind{
Group: "batch",
Version: "v1beta1",
Kind: "CronJob",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
},
},
},
},
},
},
expectedGroupNames: []string{"", "apps", "batch"},
expectedGroupVersions: []string{"v1", "apps/v1", "batch/v1beta1"},
expectedGVKs: []string{
@ -2171,9 +1788,8 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
expectedFailedGVs: []string{"batch/v1"},
},
{
name: "Aggregated discovery: /api returns nothing, 2 groups/2 resources at /apis",
corev1: &apidiscovery.APIGroupDiscoveryList{},
corev1DiscoveryBeta: &apidiscoveryv2beta1.APIGroupDiscoveryList{},
name: "Aggregated discovery: /api returns nothing, 2 groups/2 resources at /apis",
corev1: &apidiscovery.APIGroupDiscoveryList{},
apis: &apidiscovery.APIGroupDiscoveryList{
Items: []apidiscovery.APIGroupDiscovery{
{
@ -2263,95 +1879,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
},
},
apisDiscoveryBeta: &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: "StatefulSet",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "batch",
},
Versions: []apidiscoveryv2beta1.APIVersionDiscovery{
{
Version: "v1",
Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
{
Resource: "jobs",
ResponseKind: &metav1.GroupVersionKind{
Group: "batch",
Version: "v1",
Kind: "Job",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
{
Resource: "cronjobs",
ResponseKind: &metav1.GroupVersionKind{
Group: "batch",
Version: "v1",
Kind: "CronJob",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
},
},
{
// Stale "v1beta1" not included.
Version: "v1beta1",
Resources: []apidiscoveryv2beta1.APIResourceDiscovery{
{
Resource: "jobs",
ResponseKind: &metav1.GroupVersionKind{
Group: "batch",
Version: "v1beta1",
Kind: "Job",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
{
Resource: "cronjobs",
ResponseKind: &metav1.GroupVersionKind{
Group: "batch",
Version: "v1beta1",
Kind: "CronJob",
},
Scope: apidiscoveryv2beta1.ScopeNamespace,
},
},
Freshness: apidiscoveryv2beta1.DiscoveryFreshnessStale,
},
},
},
},
},
expectedGroupNames: []string{"apps", "batch"},
expectedGroupVersions: []string{"apps/v1", "batch/v1"},
expectedGVKs: []string{
@ -2364,8 +1891,7 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
},
}
// Ensure that client can parse both V2Beta1 and V2 types from server
serverAccepts := []string{AcceptV2Beta1, AcceptV2}
serverAccepts := []string{AcceptV2}
for _, test := range tests {
for _, accept := range serverAccepts {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
@ -2384,19 +1910,6 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
}
output, err = json.Marshal(agg)
require.NoError(t, err)
} else {
var agg *apidiscoveryv2beta1.APIGroupDiscoveryList
switch req.URL.Path {
case "/api":
agg = test.corev1DiscoveryBeta
case "/apis":
agg = test.apisDiscoveryBeta
default:
w.WriteHeader(http.StatusNotFound)
return
}
output, err = json.Marshal(&agg)
require.NoError(t, err)
}
// Content-Type is "aggregated" discovery format. Add extra parameter
// to ensure we are resilient to these extra parameters.

View File

@ -1150,6 +1150,10 @@
lockToDefault: false
preRelease: Beta
version: "1.28"
- default: false
lockToDefault: false
preRelease: Deprecated
version: "1.33"
- name: SeparateTaintEvictionController
versionedSpecs:
- default: true

View File

@ -22,6 +22,7 @@ import (
"crypto/x509"
"fmt"
"io"
"net"
"net/http"
"os"
"path"
@ -30,9 +31,12 @@ import (
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options"
utilfeature "k8s.io/apiserver/pkg/util/feature"
cloudprovider "k8s.io/cloud-provider"
cloudctrlmgrtesting "k8s.io/cloud-provider/app/testing"
"k8s.io/cloud-provider/fake"
featuregatetesting "k8s.io/component-base/featuregate/testing"
zpagesfeatures "k8s.io/component-base/zpages/features"
"k8s.io/klog/v2/ktesting"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
kubectrlmgrtesting "k8s.io/kubernetes/cmd/kube-controller-manager/app/testing"
@ -289,3 +293,145 @@ func fakeCloudProviderFactory(io.Reader) (cloudprovider.Interface, error) {
DisableRoutes: true, // disable routes for server tests, otherwise --cluster-cidr is required
}, nil
}
func TestKubeControllerManagerServingStatusz(t *testing.T) {
// authenticate to apiserver via bearer token
token := "flwqkenfjasasdfmwerasd" // Fake token for testing.
tokenFile, err := os.CreateTemp("", "kubeconfig")
if err != nil {
t.Fatal(err)
}
if _, err = tokenFile.WriteString(fmt.Sprintf(`
%s,system:kube-controller-manager,system:kube-controller-manager,""
`, token)); err != nil {
t.Fatal(err)
}
if err = tokenFile.Close(); err != nil {
t.Fatal(err)
}
// start apiserver
server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{
"--token-auth-file", tokenFile.Name(),
"--authorization-mode", "RBAC",
}, framework.SharedEtcd())
defer server.TearDownFn()
// create kubeconfig for the apiserver
apiserverConfig, err := os.CreateTemp("", "kubeconfig")
if err != nil {
t.Fatal(err)
}
if _, err = apiserverConfig.WriteString(fmt.Sprintf(`
apiVersion: v1
kind: Config
clusters:
- cluster:
server: %s
certificate-authority: %s
name: integration
contexts:
- context:
cluster: integration
user: controller-manager
name: default-context
current-context: default-context
users:
- name: controller-manager
user:
token: %s
`, server.ClientConfig.Host, server.ServerOpts.SecureServing.ServerCert.CertKey.CertFile, token)); err != nil {
t.Fatal(err)
}
if err = apiserverConfig.Close(); err != nil {
t.Fatal(err)
}
tests := []struct {
name string
flags []string
path string
anonymous bool // to use the token or not
wantErr bool
wantSecureCode *int
}{
{"serving /statusz", []string{
"--authentication-skip-lookup", // to survive inaccessible extensions-apiserver-authentication configmap
"--authentication-kubeconfig", apiserverConfig.Name(),
"--authorization-kubeconfig", apiserverConfig.Name(),
"--authorization-always-allow-paths", "/statusz",
"--kubeconfig", apiserverConfig.Name(),
"--leader-elect=false",
}, "/statusz", false, false, intPtr(http.StatusOK)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, zpagesfeatures.ComponentStatusz, true)
_, ctx := ktesting.NewTestContext(t)
secureOptions, secureInfo, tearDownFn, err := kubeControllerManagerTester{}.StartTestServer(ctx, append(append([]string{}, tt.flags...), []string{}...))
if tearDownFn != nil {
defer tearDownFn()
}
if (err != nil) != tt.wantErr {
t.Fatalf("StartTestServer() error = %v, wantErr %v", err, tt.wantErr)
}
if err != nil {
return
}
if want, got := tt.wantSecureCode != nil, secureInfo != nil; want != got {
t.Errorf("SecureServing enabled: expected=%v got=%v", want, got)
} else if want {
// only interested on the port, because we are using always localhost
_, port, err := net.SplitHostPort(secureInfo.Listener.Addr().String())
if err != nil {
t.Fatalf("could not get host and port from %s : %v", secureInfo.Listener.Addr().String(), err)
}
// use IPv4 because the self-signed cert does not support [::]
url := fmt.Sprintf("https://127.0.0.1:%s%s", port, tt.path)
// read self-signed server cert disk
pool := x509.NewCertPool()
serverCertPath := path.Join(secureOptions.ServerCert.CertDirectory, secureOptions.ServerCert.PairName+".crt")
serverCert, err := os.ReadFile(serverCertPath)
if err != nil {
t.Fatalf("Failed to read component server cert %q: %v", serverCertPath, err)
}
pool.AppendCertsFromPEM(serverCert)
tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
},
}
client := &http.Client{Transport: tr}
req, err := http.NewRequest(http.MethodGet, url, nil)
req.Header.Set("Accept", "text/plain")
if err != nil {
t.Fatal(err)
}
if !tt.anonymous {
req.Header.Add("Authorization", fmt.Sprintf("Token %s", token))
}
r, err := client.Do(req)
if err != nil {
t.Fatalf("failed to GET %s from component: %v", tt.path, err)
}
if _, err = io.ReadAll(r.Body); err != nil {
t.Fatalf("failed to read response body: %v", err)
}
defer func() {
if err := r.Body.Close(); err != nil {
t.Fatalf("Error closing response body: %v", err)
}
}()
if got, expected := r.StatusCode, *tt.wantSecureCode; got != expected {
t.Fatalf("expected http %d at %s of component, got: %d", expected, tt.path, got)
}
}
})
}
}