mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 18:00:08 +00:00
add statusz implementation and enablement in apiserver
This commit is contained in:
parent
847be85000
commit
8bf6eecedf
@ -36,6 +36,8 @@ import (
|
|||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
clientgoinformers "k8s.io/client-go/informers"
|
clientgoinformers "k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
|
zpagesfeatures "k8s.io/component-base/zpages/features"
|
||||||
|
"k8s.io/component-base/zpages/statusz"
|
||||||
"k8s.io/component-helpers/apimachinery/lease"
|
"k8s.io/component-helpers/apimachinery/lease"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
"k8s.io/utils/clock"
|
"k8s.io/utils/clock"
|
||||||
@ -151,6 +153,10 @@ func (c completedConfig) New(name string, delegationTarget genericapiserver.Dele
|
|||||||
return nil, fmt.Errorf("failed to get listener address: %w", err)
|
return nil, fmt.Errorf("failed to get listener address: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentStatusz) {
|
||||||
|
statusz.Install(s.GenericAPIServer.Handler.NonGoRestfulMux, name, statusz.NewRegistry())
|
||||||
|
}
|
||||||
|
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(apiserverfeatures.CoordinatedLeaderElection) {
|
if utilfeature.DefaultFeatureGate.Enabled(apiserverfeatures.CoordinatedLeaderElection) {
|
||||||
leaseInformer := s.VersionedInformers.Coordination().V1().Leases()
|
leaseInformer := s.VersionedInformers.Coordination().V1().Leases()
|
||||||
lcInformer := s.VersionedInformers.Coordination().V1alpha1().LeaseCandidates()
|
lcInformer := s.VersionedInformers.Coordination().V1alpha1().LeaseCandidates()
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
clientfeatures "k8s.io/client-go/features"
|
clientfeatures "k8s.io/client-go/features"
|
||||||
"k8s.io/component-base/featuregate"
|
"k8s.io/component-base/featuregate"
|
||||||
|
zpagesfeatures "k8s.io/component-base/zpages/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -840,6 +841,7 @@ const (
|
|||||||
func init() {
|
func init() {
|
||||||
runtime.Must(utilfeature.DefaultMutableFeatureGate.Add(defaultKubernetesFeatureGates))
|
runtime.Must(utilfeature.DefaultMutableFeatureGate.Add(defaultKubernetesFeatureGates))
|
||||||
runtime.Must(utilfeature.DefaultMutableFeatureGate.AddVersioned(defaultVersionedKubernetesFeatureGates))
|
runtime.Must(utilfeature.DefaultMutableFeatureGate.AddVersioned(defaultVersionedKubernetesFeatureGates))
|
||||||
|
runtime.Must(zpagesfeatures.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
|
||||||
|
|
||||||
// Register all client-go features with kube's feature gate instance and make all client-go
|
// Register all client-go features with kube's feature gate instance and make all client-go
|
||||||
// feature checks use kube's instance. The effect is that for kube binaries, client-go
|
// feature checks use kube's instance. The effect is that for kube binaries, client-go
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/version"
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
"k8s.io/component-base/featuregate"
|
"k8s.io/component-base/featuregate"
|
||||||
|
zpagesfeatures "k8s.io/component-base/zpages/features"
|
||||||
kcmfeatures "k8s.io/controller-manager/pkg/features"
|
kcmfeatures "k8s.io/controller-manager/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -800,4 +801,8 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
|
|||||||
WindowsHostNetwork: {
|
WindowsHostNetwork: {
|
||||||
{Version: version.MustParse("1.26"), Default: true, PreRelease: featuregate.Alpha},
|
{Version: version.MustParse("1.26"), Default: true, PreRelease: featuregate.Alpha},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
zpagesfeatures.ComponentStatusz: {
|
||||||
|
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/authentication/serviceaccount"
|
"k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
zpagesfeatures "k8s.io/component-base/zpages/features"
|
||||||
|
|
||||||
rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
|
rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
@ -194,6 +195,18 @@ func NodeRules() []rbacv1.PolicyRule {
|
|||||||
|
|
||||||
// ClusterRoles returns the cluster roles to bootstrap an API server with
|
// ClusterRoles returns the cluster roles to bootstrap an API server with
|
||||||
func ClusterRoles() []rbacv1.ClusterRole {
|
func ClusterRoles() []rbacv1.ClusterRole {
|
||||||
|
monitoringRules := []rbacv1.PolicyRule{
|
||||||
|
rbacv1helpers.NewRule("get").URLs(
|
||||||
|
"/metrics", "/metrics/slis",
|
||||||
|
"/livez", "/readyz", "/healthz",
|
||||||
|
"/livez/*", "/readyz/*", "/healthz/*",
|
||||||
|
).RuleOrDie(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentStatusz) {
|
||||||
|
monitoringRules = append(monitoringRules, rbacv1helpers.NewRule("get").URLs("/statusz").RuleOrDie())
|
||||||
|
}
|
||||||
|
|
||||||
roles := []rbacv1.ClusterRole{
|
roles := []rbacv1.ClusterRole{
|
||||||
{
|
{
|
||||||
// a "root" role which can do absolutely anything
|
// a "root" role which can do absolutely anything
|
||||||
@ -223,13 +236,7 @@ func ClusterRoles() []rbacv1.ClusterRole {
|
|||||||
// The splatted health check endpoints allow read access to individual health check
|
// The splatted health check endpoints allow read access to individual health check
|
||||||
// endpoints which may contain more sensitive cluster information information
|
// endpoints which may contain more sensitive cluster information information
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "system:monitoring"},
|
ObjectMeta: metav1.ObjectMeta{Name: "system:monitoring"},
|
||||||
Rules: []rbacv1.PolicyRule{
|
Rules: monitoringRules,
|
||||||
rbacv1helpers.NewRule("get").URLs(
|
|
||||||
"/metrics", "/metrics/slis",
|
|
||||||
"/livez", "/readyz", "/healthz",
|
|
||||||
"/livez/*", "/readyz/*", "/healthz/*",
|
|
||||||
).RuleOrDie(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -979,7 +979,7 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
|
|||||||
|
|
||||||
s.listedPathProvider = routes.ListedPathProviders{s.listedPathProvider, delegationTarget}
|
s.listedPathProvider = routes.ListedPathProviders{s.listedPathProvider, delegationTarget}
|
||||||
|
|
||||||
installAPI(s, c.Config)
|
installAPI(name, s, c.Config)
|
||||||
|
|
||||||
// use the UnprotectedHandler from the delegation target to ensure that we don't attempt to double authenticator, authorize,
|
// use the UnprotectedHandler from the delegation target to ensure that we don't attempt to double authenticator, authorize,
|
||||||
// or some other part of the filter chain in delegation cases.
|
// or some other part of the filter chain in delegation cases.
|
||||||
@ -1076,7 +1076,7 @@ func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
|
|||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func installAPI(s *GenericAPIServer, c *Config) {
|
func installAPI(name string, s *GenericAPIServer, c *Config) {
|
||||||
if c.EnableIndex {
|
if c.EnableIndex {
|
||||||
routes.Index{}.Install(s.listedPathProvider, s.Handler.NonGoRestfulMux)
|
routes.Index{}.Install(s.listedPathProvider, s.Handler.NonGoRestfulMux)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ require (
|
|||||||
github.com/go-logr/zapr v1.3.0
|
github.com/go-logr/zapr v1.3.0
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/moby/term v0.5.0
|
github.com/moby/term v0.5.0
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
|
||||||
github.com/prometheus/client_golang v1.19.1
|
github.com/prometheus/client_golang v1.19.1
|
||||||
github.com/prometheus/client_model v0.6.1
|
github.com/prometheus/client_model v0.6.1
|
||||||
github.com/prometheus/common v0.55.0
|
github.com/prometheus/common v0.55.0
|
||||||
@ -59,7 +60,6 @@ require (
|
|||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
||||||
|
@ -35,7 +35,7 @@ var processStartTime = NewGaugeVec(
|
|||||||
// a prometheus registry. This metric needs to be included to ensure counter
|
// a prometheus registry. This metric needs to be included to ensure counter
|
||||||
// data fidelity.
|
// data fidelity.
|
||||||
func RegisterProcessStartTime(registrationFunc func(Registerable) error) error {
|
func RegisterProcessStartTime(registrationFunc func(Registerable) error) error {
|
||||||
start, err := getProcessStart()
|
start, err := GetProcessStart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Could not get process start time, %v", err)
|
klog.Errorf("Could not get process start time, %v", err)
|
||||||
start = float64(time.Now().Unix())
|
start = float64(time.Now().Unix())
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/prometheus/procfs"
|
"github.com/prometheus/procfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getProcessStart() (float64, error) {
|
func GetProcessStart() (float64, error) {
|
||||||
pid := os.Getpid()
|
pid := os.Getpid()
|
||||||
p, err := procfs.NewProc(pid)
|
p, err := procfs.NewProc(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getProcessStart() (float64, error) {
|
func GetProcessStart() (float64, error) {
|
||||||
processHandle := windows.CurrentProcess()
|
processHandle := windows.CurrentProcess()
|
||||||
|
|
||||||
var creationTime, exitTime, kernelTime, userTime windows.Filetime
|
var creationTime, exitTime, kernelTime, userTime windows.Filetime
|
||||||
|
22
staging/src/k8s.io/component-base/zpages/features/doc.go
Normal file
22
staging/src/k8s.io/component-base/zpages/features/doc.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 features contains a separate feature set specifically designed for
|
||||||
|
// managing zpages related features. These feature gates control the
|
||||||
|
// availability and behavior of various zpages within Kubernetes components.
|
||||||
|
// New zpages added to Kubernetes components should utilize this feature set
|
||||||
|
// to ensure proper management of their availability.
|
||||||
|
package features
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// owner: @richabanker
|
||||||
|
// kep: https://kep.k8s.io/4827
|
||||||
|
// alpha: v1.32
|
||||||
|
//
|
||||||
|
// Enables /statusz endpoint for a component making it accessible to
|
||||||
|
// users with the system:monitoring cluster role.
|
||||||
|
ComponentStatusz featuregate.Feature = "ComponentStatusz"
|
||||||
|
)
|
||||||
|
|
||||||
|
func featureGates() map[featuregate.Feature]featuregate.VersionedSpecs {
|
||||||
|
return map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
ComponentStatusz: {
|
||||||
|
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFeatureGates adds all feature gates used by this package.
|
||||||
|
func AddFeatureGates(mutableFeatureGate featuregate.MutableVersionedFeatureGate) error {
|
||||||
|
return mutableFeatureGate.AddVersioned(featureGates())
|
||||||
|
}
|
68
staging/src/k8s.io/component-base/zpages/statusz/registry.go
Normal file
68
staging/src/k8s.io/component-base/zpages/statusz/registry.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 statusz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
compbasemetrics "k8s.io/component-base/metrics"
|
||||||
|
utilversion "k8s.io/component-base/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
type statuszRegistry interface {
|
||||||
|
processStartTime() time.Time
|
||||||
|
goVersion() string
|
||||||
|
binaryVersion() *version.Version
|
||||||
|
emulationVersion() *version.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
type registry struct{}
|
||||||
|
|
||||||
|
func (registry) processStartTime() time.Time {
|
||||||
|
start, err := compbasemetrics.GetProcessStart()
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("Could not get process start time, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Unix(int64(start), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (registry) goVersion() string {
|
||||||
|
return utilversion.Get().GoVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (registry) binaryVersion() *version.Version {
|
||||||
|
effectiveVer := featuregate.DefaultComponentGlobalsRegistry.EffectiveVersionFor(featuregate.DefaultKubeComponent)
|
||||||
|
if effectiveVer != nil {
|
||||||
|
return effectiveVer.BinaryVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilversion.DefaultKubeEffectiveVersion().BinaryVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (registry) emulationVersion() *version.Version {
|
||||||
|
effectiveVer := featuregate.DefaultComponentGlobalsRegistry.EffectiveVersionFor(featuregate.DefaultKubeComponent)
|
||||||
|
if effectiveVer != nil {
|
||||||
|
return effectiveVer.EmulationVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 statusz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
utilversion "k8s.io/component-base/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBinaryVersion(t *testing.T) {
|
||||||
|
componentGlobalsRegistry := featuregate.DefaultComponentGlobalsRegistry
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setFakeEffectiveVersion bool
|
||||||
|
fakeVersion string
|
||||||
|
wantBinaryVersion *version.Version
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "binaryVersion with effective version",
|
||||||
|
wantBinaryVersion: version.MustParseSemantic("v1.2.3"),
|
||||||
|
setFakeEffectiveVersion: true,
|
||||||
|
fakeVersion: "1.2.3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "binaryVersion without effective version",
|
||||||
|
wantBinaryVersion: utilversion.DefaultKubeEffectiveVersion().BinaryVersion(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
componentGlobalsRegistry.Reset()
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.setFakeEffectiveVersion {
|
||||||
|
verKube := utilversion.NewEffectiveVersion(tt.fakeVersion)
|
||||||
|
fg := featuregate.NewVersionedFeatureGate(version.MustParse(tt.fakeVersion))
|
||||||
|
utilruntime.Must(componentGlobalsRegistry.Register(featuregate.DefaultKubeComponent, verKube, fg))
|
||||||
|
}
|
||||||
|
|
||||||
|
registry := ®istry{}
|
||||||
|
got := registry.binaryVersion()
|
||||||
|
assert.Equal(t, tt.wantBinaryVersion, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmulationVersion(t *testing.T) {
|
||||||
|
componentGlobalsRegistry := featuregate.DefaultComponentGlobalsRegistry
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setFakeEffectiveVersion bool
|
||||||
|
fakeEmulVer string
|
||||||
|
wantEmul *version.Version
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "emulationVersion with effective version",
|
||||||
|
fakeEmulVer: "2.3.4",
|
||||||
|
setFakeEffectiveVersion: true,
|
||||||
|
wantEmul: version.MustParseSemantic("2.3.4"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emulationVersion without effective version",
|
||||||
|
wantEmul: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
componentGlobalsRegistry.Reset()
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.setFakeEffectiveVersion {
|
||||||
|
verKube := utilversion.NewEffectiveVersion("0.0.0")
|
||||||
|
verKube.SetEmulationVersion(version.MustParse(tt.fakeEmulVer))
|
||||||
|
fg := featuregate.NewVersionedFeatureGate(version.MustParse(tt.fakeEmulVer))
|
||||||
|
utilruntime.Must(componentGlobalsRegistry.Register(featuregate.DefaultKubeComponent, verKube, fg))
|
||||||
|
}
|
||||||
|
|
||||||
|
registry := ®istry{}
|
||||||
|
got := registry.emulationVersion()
|
||||||
|
if tt.wantEmul != nil && got != nil {
|
||||||
|
assert.Equal(t, tt.wantEmul.Major(), got.Major())
|
||||||
|
assert.Equal(t, tt.wantEmul.Minor(), got.Minor())
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, tt.wantEmul, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
164
staging/src/k8s.io/component-base/zpages/statusz/statusz.go
Normal file
164
staging/src/k8s.io/component-base/zpages/statusz/statusz.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 statusz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/munnerz/goautoneg"
|
||||||
|
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
delimiters = []string{":", ": ", "=", " "}
|
||||||
|
errUnsupportedMediaType = fmt.Errorf("media type not acceptable, must be: text/plain")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
headerFmt = `
|
||||||
|
%s statusz
|
||||||
|
Warning: This endpoint is not meant to be machine parseable, has no formatting compatibility guarantees and is for debugging purposes only.
|
||||||
|
`
|
||||||
|
|
||||||
|
dataTemplate = `
|
||||||
|
Started{{.Delim}} {{.StartTime}}
|
||||||
|
Up{{.Delim}} {{.Uptime}}
|
||||||
|
Go version{{.Delim}} {{.GoVersion}}
|
||||||
|
Binary version{{.Delim}} {{.BinaryVersion}}
|
||||||
|
{{if .EmulationVersion}}Emulation version{{.Delim}} {{.EmulationVersion}}{{end}}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
type contentFields struct {
|
||||||
|
Delim string
|
||||||
|
StartTime string
|
||||||
|
Uptime string
|
||||||
|
GoVersion string
|
||||||
|
BinaryVersion string
|
||||||
|
EmulationVersion string
|
||||||
|
}
|
||||||
|
|
||||||
|
type mux interface {
|
||||||
|
Handle(path string, handler http.Handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() statuszRegistry {
|
||||||
|
return registry{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Install(m mux, componentName string, reg statuszRegistry) {
|
||||||
|
dataTmpl, err := initializeTemplates()
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("error while parsing gotemplates: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Handle("/statusz", handleStatusz(componentName, dataTmpl, reg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializeTemplates() (*template.Template, error) {
|
||||||
|
d := template.New("data")
|
||||||
|
dataTmpl, err := d.Parse(dataTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataTmpl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleStatusz(componentName string, dataTmpl *template.Template, reg statuszRegistry) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !acceptableMediaType(r) {
|
||||||
|
http.Error(w, errUnsupportedMediaType.Error(), http.StatusNotAcceptable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, headerFmt, componentName)
|
||||||
|
data, err := populateStatuszData(dataTmpl, reg)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("error while populating statusz data: %v", err)
|
||||||
|
http.Error(w, "error while populating statusz data", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
fmt.Fprint(w, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(richabanker) : Move this to a common place to be reused for all zpages.
|
||||||
|
func acceptableMediaType(r *http.Request) bool {
|
||||||
|
accepts := goautoneg.ParseAccept(r.Header.Get("Accept"))
|
||||||
|
for _, accept := range accepts {
|
||||||
|
if !mediaTypeMatches(accept) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(accept.Params) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(accept.Params) == 1 {
|
||||||
|
if charset, ok := accept.Params["charset"]; ok && strings.EqualFold(charset, "utf-8") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func mediaTypeMatches(a goautoneg.Accept) bool {
|
||||||
|
return (a.Type == "text" || a.Type == "*") &&
|
||||||
|
(a.SubType == "plain" || a.SubType == "*")
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateStatuszData(tmpl *template.Template, reg statuszRegistry) (string, error) {
|
||||||
|
if tmpl == nil {
|
||||||
|
return "", fmt.Errorf("received nil template")
|
||||||
|
}
|
||||||
|
|
||||||
|
randomIndex := rand.Intn(len(delimiters))
|
||||||
|
data := contentFields{
|
||||||
|
Delim: delimiters[randomIndex],
|
||||||
|
StartTime: reg.processStartTime().Format(time.UnixDate),
|
||||||
|
Uptime: uptime(reg.processStartTime()),
|
||||||
|
GoVersion: reg.goVersion(),
|
||||||
|
BinaryVersion: reg.binaryVersion().String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if reg.emulationVersion() != nil {
|
||||||
|
data.EmulationVersion = reg.emulationVersion().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
var tpl bytes.Buffer
|
||||||
|
err := tmpl.Execute(&tpl, data)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error executing statusz template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tpl.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func uptime(t time.Time) string {
|
||||||
|
upSince := int64(time.Since(t).Seconds())
|
||||||
|
return fmt.Sprintf("%d hr %02d min %02d sec",
|
||||||
|
upSince/3600, (upSince/60)%60, upSince%60)
|
||||||
|
}
|
237
staging/src/k8s.io/component-base/zpages/statusz/statusz_test.go
Normal file
237
staging/src/k8s.io/component-base/zpages/statusz/statusz_test.go
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 statusz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const wantTmpl = `
|
||||||
|
%s statusz
|
||||||
|
Warning: This endpoint is not meant to be machine parseable, has no formatting compatibility guarantees and is for debugging purposes only.
|
||||||
|
|
||||||
|
Started: %v
|
||||||
|
Up: %s
|
||||||
|
Go version: %s
|
||||||
|
Binary version: %v
|
||||||
|
Emulation version: %v
|
||||||
|
`
|
||||||
|
|
||||||
|
const wantTmplWithoutEmulation = `
|
||||||
|
%s statusz
|
||||||
|
Warning: This endpoint is not meant to be machine parseable, has no formatting compatibility guarantees and is for debugging purposes only.
|
||||||
|
|
||||||
|
Started: %v
|
||||||
|
Up: %s
|
||||||
|
Go version: %s
|
||||||
|
Binary version: %v
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestStatusz(t *testing.T) {
|
||||||
|
delimiters = []string{":"}
|
||||||
|
fakeStartTime := time.Now()
|
||||||
|
fakeUptime := uptime(fakeStartTime)
|
||||||
|
fakeGoVersion := "1.21"
|
||||||
|
fakeBvStr := "1.31"
|
||||||
|
fakeEvStr := "1.30"
|
||||||
|
fakeBinaryVersion := parseVersion(t, fakeBvStr)
|
||||||
|
fakeEmulationVersion := parseVersion(t, fakeEvStr)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
componentName string
|
||||||
|
reqHeader string
|
||||||
|
registry fakeRegistry
|
||||||
|
wantStatusCode int
|
||||||
|
wantBody string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "invalid header",
|
||||||
|
reqHeader: "some header",
|
||||||
|
wantStatusCode: http.StatusNotAcceptable,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid request",
|
||||||
|
componentName: "test-server",
|
||||||
|
reqHeader: "text/plain; charset=utf-8",
|
||||||
|
registry: fakeRegistry{
|
||||||
|
startTime: fakeStartTime,
|
||||||
|
goVer: fakeGoVersion,
|
||||||
|
binaryVer: fakeBinaryVersion,
|
||||||
|
emulationVer: fakeEmulationVersion,
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantBody: fmt.Sprintf(
|
||||||
|
wantTmpl,
|
||||||
|
"test-server",
|
||||||
|
fakeStartTime.Format(time.UnixDate),
|
||||||
|
fakeUptime,
|
||||||
|
fakeGoVersion,
|
||||||
|
fakeBinaryVersion,
|
||||||
|
fakeEmulationVersion,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing emulation version",
|
||||||
|
componentName: "test-server",
|
||||||
|
reqHeader: "text/plain; charset=utf-8",
|
||||||
|
registry: fakeRegistry{
|
||||||
|
startTime: fakeStartTime,
|
||||||
|
goVer: fakeGoVersion,
|
||||||
|
binaryVer: fakeBinaryVersion,
|
||||||
|
emulationVer: nil,
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantBody: fmt.Sprintf(
|
||||||
|
wantTmplWithoutEmulation,
|
||||||
|
"test-server",
|
||||||
|
fakeStartTime.Format(time.UnixDate),
|
||||||
|
fakeUptime,
|
||||||
|
fakeGoVersion,
|
||||||
|
fakeBinaryVersion,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
Install(mux, tt.componentName, tt.registry)
|
||||||
|
|
||||||
|
path := "/statusz"
|
||||||
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com%s", path), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while creating request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Accept", "text/plain; charset=utf-8")
|
||||||
|
if tt.reqHeader != "" {
|
||||||
|
req.Header.Set("Accept", tt.reqHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != tt.wantStatusCode {
|
||||||
|
t.Fatalf("want status code: %v, got: %v", tt.wantStatusCode, w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantStatusCode == http.StatusOK {
|
||||||
|
c := w.Header().Get("Content-Type")
|
||||||
|
if c != "text/plain; charset=utf-8" {
|
||||||
|
t.Fatalf("want header: %v, got: %v", "text/plain", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(tt.wantBody, string(w.Body.String())); diff != "" {
|
||||||
|
t.Errorf("Unexpected diff on response (-want,+got):\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAcceptableMediaTypes(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
reqHeader string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid text/plain header",
|
||||||
|
reqHeader: "text/plain",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid text/* header",
|
||||||
|
reqHeader: "text/*",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid */plain header",
|
||||||
|
reqHeader: "*/plain",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid accept args",
|
||||||
|
reqHeader: "text/plain; charset=utf-8",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid text/foo header",
|
||||||
|
reqHeader: "text/foo",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid text/plain params",
|
||||||
|
reqHeader: "text/plain; foo=bar",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "http://example.com/statusz", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error while creating request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Accept", tt.reqHeader)
|
||||||
|
got := acceptableMediaType(req)
|
||||||
|
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Unexpected response from acceptableMediaType(), want %v, got = %v", tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVersion(t *testing.T, v string) *version.Version {
|
||||||
|
parsed, err := version.ParseMajorMinor(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error parsing binary version: %s", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeRegistry struct {
|
||||||
|
startTime time.Time
|
||||||
|
goVer string
|
||||||
|
binaryVer *version.Version
|
||||||
|
emulationVer *version.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeRegistry) processStartTime() time.Time {
|
||||||
|
return f.startTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeRegistry) goVersion() string {
|
||||||
|
return f.goVer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeRegistry) binaryVersion() *version.Version {
|
||||||
|
return f.binaryVer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeRegistry) emulationVersion() *version.Version {
|
||||||
|
return f.emulationVer
|
||||||
|
}
|
@ -208,6 +208,12 @@
|
|||||||
lockToDefault: true
|
lockToDefault: true
|
||||||
preRelease: GA
|
preRelease: GA
|
||||||
version: "1.32"
|
version: "1.32"
|
||||||
|
- name: ComponentStatusz
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: "1.32"
|
||||||
- name: ConcurrentWatchObjectDecode
|
- name: ConcurrentWatchObjectDecode
|
||||||
versionedSpecs:
|
versionedSpecs:
|
||||||
- default: false
|
- default: false
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
gopath "path"
|
gopath "path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -43,10 +44,13 @@ import (
|
|||||||
unionauthz "k8s.io/apiserver/pkg/authorization/union"
|
unionauthz "k8s.io/apiserver/pkg/authorization/union"
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
watchtools "k8s.io/client-go/tools/watch"
|
watchtools "k8s.io/client-go/tools/watch"
|
||||||
"k8s.io/client-go/transport"
|
"k8s.io/client-go/transport"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
zpagesfeatures "k8s.io/component-base/zpages/features"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
||||||
rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1"
|
rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1"
|
||||||
@ -1037,3 +1041,111 @@ func TestRBACContextContamination(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMonitoringURLs(t *testing.T) {
|
||||||
|
type request struct {
|
||||||
|
path string
|
||||||
|
wantBodyRegex string
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
requests []request
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "monitoring endpoints",
|
||||||
|
requests: []request{
|
||||||
|
{
|
||||||
|
path: "/metrics",
|
||||||
|
wantBodyRegex: `# HELP \w+`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/metrics/slis",
|
||||||
|
wantBodyRegex: `kubernetes_healthcheck\{\w+`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/livez",
|
||||||
|
wantBodyRegex: `^ok$`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/readyz",
|
||||||
|
wantBodyRegex: `^ok$`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/healthz",
|
||||||
|
wantBodyRegex: `^ok$`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/statusz",
|
||||||
|
wantBodyRegex: `kube-apiserver statusz`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, zpagesfeatures.ComponentStatusz, true)
|
||||||
|
tCtx := ktesting.Init(t)
|
||||||
|
|
||||||
|
// Create a user with the system:monitoring role
|
||||||
|
monitoringUser := "monitoring-user"
|
||||||
|
authenticator := group.NewAuthenticatedGroupAdder(bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
|
||||||
|
monitoringUser: {Name: monitoringUser, Groups: []string{"system:monitoring"}},
|
||||||
|
})))
|
||||||
|
|
||||||
|
_, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
|
||||||
|
ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
|
||||||
|
opts.Authorization.Modes = []string{"RBAC"}
|
||||||
|
},
|
||||||
|
ModifyServerConfig: func(config *controlplane.Config) {
|
||||||
|
config.ControlPlane.Generic.Authentication.Authenticator = authenticator
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer tearDownFn()
|
||||||
|
|
||||||
|
transport, err := restclient.TransportFor(kubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range tc.requests {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, kubeConfig.Host+r.path, nil)
|
||||||
|
if r.path == "/statusz" {
|
||||||
|
req.Header.Set("Accept", "text/plain")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := clientForToken(monitoringUser, transport).Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to make request to %s: %v", r, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("request to %s: expected %q got %q", r, statusCode(http.StatusOK), statusCode(resp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedBytes, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read response body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedStr := string(parsedBytes)
|
||||||
|
matched, err := regexp.MatchString(r.wantBodyRegex, parsedStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid regex: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matched {
|
||||||
|
t.Errorf("request to %s: response body does not match expected pattern", r.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user