build: move e2e dependencies into e2e/go.mod

Several packages are only used while running the e2e suite. These
packages are less important to update, as the they can not influence the
final executable that is part of the Ceph-CSI container-image.

By moving these dependencies out of the main Ceph-CSI go.mod, it is
easier to identify if a reported CVE affects Ceph-CSI, or only the
testing (like most of the Kubernetes CVEs).

Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
Niels de Vos
2025-03-04 08:57:28 +01:00
committed by mergify[bot]
parent 15da101b1b
commit bec6090996
8047 changed files with 1407827 additions and 3453 deletions

View File

@ -1,13 +0,0 @@
# See the OWNERS docs at https://go.k8s.io/owners
# Disable inheritance as this is an api owners file
options:
no_parent_owners: true
approvers:
- api-approvers
reviewers:
- api-reviewers
labels:
- kind/api-change
- sig/api-machinery
- sig/scheduling

View File

@ -1,19 +0,0 @@
/*
Copyright 2018 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.
*/
// +k8s:deepcopy-gen=package
package config // import "k8s.io/component-base/config"

View File

@ -1,52 +0,0 @@
/*
Copyright 2017 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 options
import (
"github.com/spf13/pflag"
"k8s.io/component-base/config"
)
// BindLeaderElectionFlags binds the LeaderElectionConfiguration struct fields to a flagset
func BindLeaderElectionFlags(l *config.LeaderElectionConfiguration, fs *pflag.FlagSet) {
fs.BoolVar(&l.LeaderElect, "leader-elect", l.LeaderElect, ""+
"Start a leader election client and gain leadership before "+
"executing the main loop. Enable this when running replicated "+
"components for high availability.")
fs.DurationVar(&l.LeaseDuration.Duration, "leader-elect-lease-duration", l.LeaseDuration.Duration, ""+
"The duration that non-leader candidates will wait after observing a leadership "+
"renewal until attempting to acquire leadership of a led but unrenewed leader "+
"slot. This is effectively the maximum duration that a leader can be stopped "+
"before it is replaced by another candidate. This is only applicable if leader "+
"election is enabled.")
fs.DurationVar(&l.RenewDeadline.Duration, "leader-elect-renew-deadline", l.RenewDeadline.Duration, ""+
"The interval between attempts by the acting master to renew a leadership slot "+
"before it stops leading. This must be less than the lease duration. "+
"This is only applicable if leader election is enabled.")
fs.DurationVar(&l.RetryPeriod.Duration, "leader-elect-retry-period", l.RetryPeriod.Duration, ""+
"The duration the clients should wait between attempting acquisition and renewal "+
"of a leadership. This is only applicable if leader election is enabled.")
fs.StringVar(&l.ResourceLock, "leader-elect-resource-lock", l.ResourceLock, ""+
"The type of resource object that is used for locking during "+
"leader election. Supported options are 'leases'.")
fs.StringVar(&l.ResourceName, "leader-elect-resource-name", l.ResourceName, ""+
"The name of resource object that is used for locking during "+
"leader election.")
fs.StringVar(&l.ResourceNamespace, "leader-elect-resource-namespace", l.ResourceNamespace, ""+
"The namespace of resource object that is used for locking during "+
"leader election.")
}

View File

@ -1,80 +0,0 @@
/*
Copyright 2018 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 config
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ClientConnectionConfiguration contains details for constructing a client.
type ClientConnectionConfiguration struct {
// kubeconfig is the path to a KubeConfig file.
Kubeconfig string
// acceptContentTypes defines the Accept header sent by clients when connecting to a server, overriding the
// default value of 'application/json'. This field will control all connections to the server used by a particular
// client.
AcceptContentTypes string
// contentType is the content type used when sending data to the server from this client.
ContentType string
// qps controls the number of queries per second allowed for this connection.
QPS float32
// burst allows extra queries to accumulate when a client is exceeding its rate.
Burst int32
}
// LeaderElectionConfiguration defines the configuration of leader election
// clients for components that can run with leader election enabled.
type LeaderElectionConfiguration struct {
// leaderElect enables a leader election client to gain leadership
// before executing the main loop. Enable this when running replicated
// components for high availability.
LeaderElect bool
// leaseDuration is the duration that non-leader candidates will wait
// after observing a leadership renewal until attempting to acquire
// leadership of a led but unrenewed leader slot. This is effectively the
// maximum duration that a leader can be stopped before it is replaced
// by another candidate. This is only applicable if leader election is
// enabled.
LeaseDuration metav1.Duration
// renewDeadline is the interval between attempts by the acting master to
// renew a leadership slot before it stops leading. This must be less
// than or equal to the lease duration. This is only applicable if leader
// election is enabled.
RenewDeadline metav1.Duration
// retryPeriod is the duration the clients should wait between attempting
// acquisition and renewal of a leadership. This is only applicable if
// leader election is enabled.
RetryPeriod metav1.Duration
// resourceLock indicates the resource object type that will be used to lock
// during leader election cycles.
ResourceLock string
// resourceName indicates the name of resource object that will be used to lock
// during leader election cycles.
ResourceName string
// resourceNamespace indicates the namespace of resource object that will be used to lock
// during leader election cycles.
ResourceNamespace string
}
// DebuggingConfiguration holds configuration for Debugging related features.
type DebuggingConfiguration struct {
// enableProfiling enables profiling via web interface host:port/debug/pprof/
EnableProfiling bool
// enableContentionProfiling enables block profiling, if
// enableProfiling is true.
EnableContentionProfiling bool
}

View File

@ -1,53 +0,0 @@
/*
Copyright 2018 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 v1alpha1
import (
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/component-base/config"
)
// Important! The public back-and-forth conversion functions for the types in this generic
// package with ComponentConfig types need to be manually exposed like this in order for
// other packages that reference this package to be able to call these conversion functions
// in an autogenerated manner.
// TODO: Fix the bug in conversion-gen so it automatically discovers these Convert_* functions
// in autogenerated code as well.
func Convert_v1alpha1_ClientConnectionConfiguration_To_config_ClientConnectionConfiguration(in *ClientConnectionConfiguration, out *config.ClientConnectionConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_ClientConnectionConfiguration_To_config_ClientConnectionConfiguration(in, out, s)
}
func Convert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(in *config.ClientConnectionConfiguration, out *ClientConnectionConfiguration, s conversion.Scope) error {
return autoConvert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(in, out, s)
}
func Convert_v1alpha1_DebuggingConfiguration_To_config_DebuggingConfiguration(in *DebuggingConfiguration, out *config.DebuggingConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_DebuggingConfiguration_To_config_DebuggingConfiguration(in, out, s)
}
func Convert_config_DebuggingConfiguration_To_v1alpha1_DebuggingConfiguration(in *config.DebuggingConfiguration, out *DebuggingConfiguration, s conversion.Scope) error {
return autoConvert_config_DebuggingConfiguration_To_v1alpha1_DebuggingConfiguration(in, out, s)
}
func Convert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfiguration(in *LeaderElectionConfiguration, out *config.LeaderElectionConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfiguration(in, out, s)
}
func Convert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(in *config.LeaderElectionConfiguration, out *LeaderElectionConfiguration, s conversion.Scope) error {
return autoConvert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(in, out, s)
}

View File

@ -1,98 +0,0 @@
/*
Copyright 2018 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 v1alpha1
import (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilpointer "k8s.io/utils/pointer"
)
// RecommendedDefaultLeaderElectionConfiguration defaults a pointer to a
// LeaderElectionConfiguration struct. This will set the recommended default
// values, but they may be subject to change between API versions. This function
// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo`
// function to allow consumers of this type to set whatever defaults for their
// embedded configs. Forcing consumers to use these defaults would be problematic
// as defaulting in the scheme is done as part of the conversion, and there would
// be no easy way to opt-out. Instead, if you want to use this defaulting method
// run it in your wrapper struct of this type in its `SetDefaults_` method.
func RecommendedDefaultLeaderElectionConfiguration(obj *LeaderElectionConfiguration) {
zero := metav1.Duration{}
if obj.LeaseDuration == zero {
obj.LeaseDuration = metav1.Duration{Duration: 15 * time.Second}
}
if obj.RenewDeadline == zero {
obj.RenewDeadline = metav1.Duration{Duration: 10 * time.Second}
}
if obj.RetryPeriod == zero {
obj.RetryPeriod = metav1.Duration{Duration: 2 * time.Second}
}
if obj.ResourceLock == "" {
// TODO(#80289): Figure out how to migrate to LeaseLock at this point.
// This will most probably require going through EndpointsLease first.
obj.ResourceLock = EndpointsResourceLock
}
if obj.LeaderElect == nil {
obj.LeaderElect = utilpointer.BoolPtr(true)
}
}
// RecommendedDefaultClientConnectionConfiguration defaults a pointer to a
// ClientConnectionConfiguration struct. This will set the recommended default
// values, but they may be subject to change between API versions. This function
// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo`
// function to allow consumers of this type to set whatever defaults for their
// embedded configs. Forcing consumers to use these defaults would be problematic
// as defaulting in the scheme is done as part of the conversion, and there would
// be no easy way to opt-out. Instead, if you want to use this defaulting method
// run it in your wrapper struct of this type in its `SetDefaults_` method.
func RecommendedDefaultClientConnectionConfiguration(obj *ClientConnectionConfiguration) {
if len(obj.ContentType) == 0 {
obj.ContentType = "application/vnd.kubernetes.protobuf"
}
if obj.QPS == 0.0 {
obj.QPS = 50.0
}
if obj.Burst == 0 {
obj.Burst = 100
}
}
// RecommendedDebuggingConfiguration defaults profiling and debugging configuration.
// This will set the recommended default
// values, but they may be subject to change between API versions. This function
// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo`
// function to allow consumers of this type to set whatever defaults for their
// embedded configs. Forcing consumers to use these defaults would be problematic
// as defaulting in the scheme is done as part of the conversion, and there would
// be no easy way to opt-out. Instead, if you want to use this defaulting method
// run it in your wrapper struct of this type in its `SetDefaults_` method.
func RecommendedDebuggingConfiguration(obj *DebuggingConfiguration) {
if obj.EnableProfiling == nil {
obj.EnableProfiling = utilpointer.BoolPtr(true) // profile debugging is cheap to have exposed and standard on kube binaries
}
}
// NewRecommendedDebuggingConfiguration returns the current recommended DebuggingConfiguration.
// This may change between releases as recommendations shift.
func NewRecommendedDebuggingConfiguration() *DebuggingConfiguration {
ret := &DebuggingConfiguration{}
RecommendedDebuggingConfiguration(ret)
return ret
}

View File

@ -1,20 +0,0 @@
/*
Copyright 2018 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.
*/
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/component-base/config
package v1alpha1 // import "k8s.io/component-base/config/v1alpha1"

View File

@ -1,31 +0,0 @@
/*
Copyright 2018 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 v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
)
var (
// SchemeBuilder is the scheme builder with scheme init functions to run for this API package
SchemeBuilder runtime.SchemeBuilder
// localSchemeBuilder extends the SchemeBuilder instance with the external types. In this package,
// defaulting and conversion init funcs are registered as well.
localSchemeBuilder = &SchemeBuilder
// AddToScheme is a global function that registers this API group & version to a scheme
AddToScheme = localSchemeBuilder.AddToScheme
)

View File

@ -1,82 +0,0 @@
/*
Copyright 2018 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 v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const EndpointsResourceLock = "endpoints"
// LeaderElectionConfiguration defines the configuration of leader election
// clients for components that can run with leader election enabled.
type LeaderElectionConfiguration struct {
// leaderElect enables a leader election client to gain leadership
// before executing the main loop. Enable this when running replicated
// components for high availability.
LeaderElect *bool `json:"leaderElect"`
// leaseDuration is the duration that non-leader candidates will wait
// after observing a leadership renewal until attempting to acquire
// leadership of a led but unrenewed leader slot. This is effectively the
// maximum duration that a leader can be stopped before it is replaced
// by another candidate. This is only applicable if leader election is
// enabled.
LeaseDuration metav1.Duration `json:"leaseDuration"`
// renewDeadline is the interval between attempts by the acting master to
// renew a leadership slot before it stops leading. This must be less
// than or equal to the lease duration. This is only applicable if leader
// election is enabled.
RenewDeadline metav1.Duration `json:"renewDeadline"`
// retryPeriod is the duration the clients should wait between attempting
// acquisition and renewal of a leadership. This is only applicable if
// leader election is enabled.
RetryPeriod metav1.Duration `json:"retryPeriod"`
// resourceLock indicates the resource object type that will be used to lock
// during leader election cycles.
ResourceLock string `json:"resourceLock"`
// resourceName indicates the name of resource object that will be used to lock
// during leader election cycles.
ResourceName string `json:"resourceName"`
// resourceName indicates the namespace of resource object that will be used to lock
// during leader election cycles.
ResourceNamespace string `json:"resourceNamespace"`
}
// DebuggingConfiguration holds configuration for Debugging related features.
type DebuggingConfiguration struct {
// enableProfiling enables profiling via web interface host:port/debug/pprof/
EnableProfiling *bool `json:"enableProfiling,omitempty"`
// enableContentionProfiling enables block profiling, if
// enableProfiling is true.
EnableContentionProfiling *bool `json:"enableContentionProfiling,omitempty"`
}
// ClientConnectionConfiguration contains details for constructing a client.
type ClientConnectionConfiguration struct {
// kubeconfig is the path to a KubeConfig file.
Kubeconfig string `json:"kubeconfig"`
// acceptContentTypes defines the Accept header sent by clients when connecting to a server, overriding the
// default value of 'application/json'. This field will control all connections to the server used by a particular
// client.
AcceptContentTypes string `json:"acceptContentTypes"`
// contentType is the content type used when sending data to the server from this client.
ContentType string `json:"contentType"`
// qps controls the number of queries per second allowed for this connection.
QPS float32 `json:"qps"`
// burst allows extra queries to accumulate when a client is exceeding its rate.
Burst int32 `json:"burst"`
}

View File

@ -1,133 +0,0 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
config "k8s.io/component-base/config"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddConversionFunc((*config.ClientConnectionConfiguration)(nil), (*ClientConnectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(a.(*config.ClientConnectionConfiguration), b.(*ClientConnectionConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*config.DebuggingConfiguration)(nil), (*DebuggingConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_DebuggingConfiguration_To_v1alpha1_DebuggingConfiguration(a.(*config.DebuggingConfiguration), b.(*DebuggingConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*config.LeaderElectionConfiguration)(nil), (*LeaderElectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(a.(*config.LeaderElectionConfiguration), b.(*LeaderElectionConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ClientConnectionConfiguration)(nil), (*config.ClientConnectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_ClientConnectionConfiguration_To_config_ClientConnectionConfiguration(a.(*ClientConnectionConfiguration), b.(*config.ClientConnectionConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*DebuggingConfiguration)(nil), (*config.DebuggingConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_DebuggingConfiguration_To_config_DebuggingConfiguration(a.(*DebuggingConfiguration), b.(*config.DebuggingConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*LeaderElectionConfiguration)(nil), (*config.LeaderElectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfiguration(a.(*LeaderElectionConfiguration), b.(*config.LeaderElectionConfiguration), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha1_ClientConnectionConfiguration_To_config_ClientConnectionConfiguration(in *ClientConnectionConfiguration, out *config.ClientConnectionConfiguration, s conversion.Scope) error {
out.Kubeconfig = in.Kubeconfig
out.AcceptContentTypes = in.AcceptContentTypes
out.ContentType = in.ContentType
out.QPS = in.QPS
out.Burst = in.Burst
return nil
}
func autoConvert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(in *config.ClientConnectionConfiguration, out *ClientConnectionConfiguration, s conversion.Scope) error {
out.Kubeconfig = in.Kubeconfig
out.AcceptContentTypes = in.AcceptContentTypes
out.ContentType = in.ContentType
out.QPS = in.QPS
out.Burst = in.Burst
return nil
}
func autoConvert_v1alpha1_DebuggingConfiguration_To_config_DebuggingConfiguration(in *DebuggingConfiguration, out *config.DebuggingConfiguration, s conversion.Scope) error {
if err := v1.Convert_Pointer_bool_To_bool(&in.EnableProfiling, &out.EnableProfiling, s); err != nil {
return err
}
if err := v1.Convert_Pointer_bool_To_bool(&in.EnableContentionProfiling, &out.EnableContentionProfiling, s); err != nil {
return err
}
return nil
}
func autoConvert_config_DebuggingConfiguration_To_v1alpha1_DebuggingConfiguration(in *config.DebuggingConfiguration, out *DebuggingConfiguration, s conversion.Scope) error {
if err := v1.Convert_bool_To_Pointer_bool(&in.EnableProfiling, &out.EnableProfiling, s); err != nil {
return err
}
if err := v1.Convert_bool_To_Pointer_bool(&in.EnableContentionProfiling, &out.EnableContentionProfiling, s); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfiguration(in *LeaderElectionConfiguration, out *config.LeaderElectionConfiguration, s conversion.Scope) error {
if err := v1.Convert_Pointer_bool_To_bool(&in.LeaderElect, &out.LeaderElect, s); err != nil {
return err
}
out.LeaseDuration = in.LeaseDuration
out.RenewDeadline = in.RenewDeadline
out.RetryPeriod = in.RetryPeriod
out.ResourceLock = in.ResourceLock
out.ResourceName = in.ResourceName
out.ResourceNamespace = in.ResourceNamespace
return nil
}
func autoConvert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(in *config.LeaderElectionConfiguration, out *LeaderElectionConfiguration, s conversion.Scope) error {
if err := v1.Convert_bool_To_Pointer_bool(&in.LeaderElect, &out.LeaderElect, s); err != nil {
return err
}
out.LeaseDuration = in.LeaseDuration
out.RenewDeadline = in.RenewDeadline
out.RetryPeriod = in.RetryPeriod
out.ResourceLock = in.ResourceLock
out.ResourceName = in.ResourceName
out.ResourceNamespace = in.ResourceNamespace
return nil
}

View File

@ -1,88 +0,0 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClientConnectionConfiguration) DeepCopyInto(out *ClientConnectionConfiguration) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientConnectionConfiguration.
func (in *ClientConnectionConfiguration) DeepCopy() *ClientConnectionConfiguration {
if in == nil {
return nil
}
out := new(ClientConnectionConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DebuggingConfiguration) DeepCopyInto(out *DebuggingConfiguration) {
*out = *in
if in.EnableProfiling != nil {
in, out := &in.EnableProfiling, &out.EnableProfiling
*out = new(bool)
**out = **in
}
if in.EnableContentionProfiling != nil {
in, out := &in.EnableContentionProfiling, &out.EnableContentionProfiling
*out = new(bool)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DebuggingConfiguration.
func (in *DebuggingConfiguration) DeepCopy() *DebuggingConfiguration {
if in == nil {
return nil
}
out := new(DebuggingConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaderElectionConfiguration) DeepCopyInto(out *LeaderElectionConfiguration) {
*out = *in
if in.LeaderElect != nil {
in, out := &in.LeaderElect, &out.LeaderElect
*out = new(bool)
**out = **in
}
out.LeaseDuration = in.LeaseDuration
out.RenewDeadline = in.RenewDeadline
out.RetryPeriod = in.RetryPeriod
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderElectionConfiguration.
func (in *LeaderElectionConfiguration) DeepCopy() *LeaderElectionConfiguration {
if in == nil {
return nil
}
out := new(LeaderElectionConfiguration)
in.DeepCopyInto(out)
return out
}

View File

@ -1,61 +0,0 @@
/*
Copyright 2018 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 validation
import (
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/component-base/config"
)
// ValidateClientConnectionConfiguration ensures validation of the ClientConnectionConfiguration struct
func ValidateClientConnectionConfiguration(cc *config.ClientConnectionConfiguration, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if cc.Burst < 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("burst"), cc.Burst, "must be non-negative"))
}
return allErrs
}
// ValidateLeaderElectionConfiguration ensures validation of the LeaderElectionConfiguration struct
func ValidateLeaderElectionConfiguration(cc *config.LeaderElectionConfiguration, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if !cc.LeaderElect {
return allErrs
}
if cc.LeaseDuration.Duration <= 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("leaseDuration"), cc.LeaseDuration, "must be greater than zero"))
}
if cc.RenewDeadline.Duration <= 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("renewDeadline"), cc.RenewDeadline, "must be greater than zero"))
}
if cc.RetryPeriod.Duration <= 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("retryPeriod"), cc.RetryPeriod, "must be greater than zero"))
}
if cc.LeaseDuration.Duration <= cc.RenewDeadline.Duration {
allErrs = append(allErrs, field.Invalid(fldPath.Child("leaseDuration"), cc.RenewDeadline, "LeaseDuration must be greater than RenewDeadline"))
}
if len(cc.ResourceLock) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceLock"), cc.ResourceLock, "resourceLock is required"))
}
if len(cc.ResourceNamespace) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceNamespace"), cc.ResourceNamespace, "resourceNamespace is required"))
}
if len(cc.ResourceName) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceName"), cc.ResourceName, "resourceName is required"))
}
return allErrs
}

View File

@ -1,73 +0,0 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package config
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClientConnectionConfiguration) DeepCopyInto(out *ClientConnectionConfiguration) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientConnectionConfiguration.
func (in *ClientConnectionConfiguration) DeepCopy() *ClientConnectionConfiguration {
if in == nil {
return nil
}
out := new(ClientConnectionConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DebuggingConfiguration) DeepCopyInto(out *DebuggingConfiguration) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DebuggingConfiguration.
func (in *DebuggingConfiguration) DeepCopy() *DebuggingConfiguration {
if in == nil {
return nil
}
out := new(DebuggingConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaderElectionConfiguration) DeepCopyInto(out *LeaderElectionConfiguration) {
*out = *in
out.LeaseDuration = in.LeaseDuration
out.RenewDeadline = in.RenewDeadline
out.RetryPeriod = in.RetryPeriod
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderElectionConfiguration.
func (in *LeaderElectionConfiguration) DeepCopy() *LeaderElectionConfiguration {
if in == nil {
return nil
}
out := new(LeaderElectionConfiguration)
in.DeepCopyInto(out)
return out
}

View File

@ -1,12 +0,0 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- sig-instrumentation-approvers
- serathius
- pohly
reviewers:
- sig-instrumentation-reviewers
- serathius
labels:
- sig/instrumentation
- wg/structured-logging

View File

@ -1,32 +0,0 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:deepcopy-gen=package
// Package v1 contains the configuration API for logging.
//
// The intention is to only have a single version of this API, potentially with
// new fields added over time in a backwards-compatible manner. Fields for
// alpha or beta features are allowed as long as they are defined so that not
// changing the defaults leaves those features disabled.
//
// The "v1" package name is just a reminder that API compatibility rules apply,
// not an indication of the stability of all features covered by it.
// The LoggingAlphaOptions and LoggingBetaOptions feature gates control whether
// these unstable features can get enabled. This can be used to ensure that
// command invocations do not accidentally rely on unstable features.
package v1 // import "k8s.io/component-base/logs/api/v1"

View File

@ -1,72 +0,0 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"k8s.io/component-base/featuregate"
)
const (
// owner: @pohly
// kep: https://kep.k8s.io/3077
// alpha: v1.24
// beta: v1.30
//
// Enables looking up a logger from a context.Context instead of using
// the global fallback logger and manipulating the logger that is
// used by a call chain.
ContextualLogging featuregate.Feature = "ContextualLogging"
// contextualLoggingDefault is now true because the feature reached beta
// and performance comparisons showed no relevant degradation when
// enabling it.
contextualLoggingDefault = true
// Allow fine-tuning of experimental, alpha-quality logging options.
//
// Per https://groups.google.com/g/kubernetes-sig-architecture/c/Nxsc7pfe5rw/m/vF2djJh0BAAJ
// we want to avoid a proliferation of feature gates. This feature gate:
// - will guard *a group* of logging options whose quality level is alpha.
// - will never graduate to beta or stable.
LoggingAlphaOptions featuregate.Feature = "LoggingAlphaOptions"
// Allow fine-tuning of experimental, beta-quality logging options.
//
// Per https://groups.google.com/g/kubernetes-sig-architecture/c/Nxsc7pfe5rw/m/vF2djJh0BAAJ
// we want to avoid a proliferation of feature gates. This feature gate:
// - will guard *a group* of logging options whose quality level is beta.
// - is thus *introduced* as beta
// - will never graduate to stable.
LoggingBetaOptions featuregate.Feature = "LoggingBetaOptions"
// Stable logging options. Always enabled.
LoggingStableOptions featuregate.Feature = "LoggingStableOptions"
)
func featureGates() map[featuregate.Feature]featuregate.FeatureSpec {
return map[featuregate.Feature]featuregate.FeatureSpec{
ContextualLogging: {Default: contextualLoggingDefault, PreRelease: featuregate.Beta},
LoggingAlphaOptions: {Default: false, PreRelease: featuregate.Alpha},
LoggingBetaOptions: {Default: true, PreRelease: featuregate.Beta},
}
}
// AddFeatureGates adds all feature gates used by this package.
func AddFeatureGates(mutableFeatureGate featuregate.MutableFeatureGate) error {
return mutableFeatureGate.Add(featureGates())
}

View File

@ -1,449 +0,0 @@
/*
Copyright 2021 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 v1
import (
"errors"
"flag"
"fmt"
"io"
"math"
"os"
"strings"
"sync/atomic"
"time"
"github.com/google/go-cmp/cmp"
"github.com/spf13/pflag"
"k8s.io/klog/v2"
"k8s.io/klog/v2/textlogger"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/validation/field"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/featuregate"
"k8s.io/component-base/logs/internal/setverbositylevel"
"k8s.io/component-base/logs/klogflags"
)
const (
// LogFlushFreqDefault is the default for the corresponding command line
// parameter.
LogFlushFreqDefault = 5 * time.Second
)
const (
// LogFlushFreqFlagName is the name of the command line parameter.
// Depending on how flags get added, it is either a stand-alone
// value (logs.AddFlags) or part of LoggingConfiguration.
LogFlushFreqFlagName = "log-flush-frequency"
)
// NewLoggingConfiguration returns a struct holding the default logging configuration.
func NewLoggingConfiguration() *LoggingConfiguration {
c := LoggingConfiguration{}
SetRecommendedLoggingConfiguration(&c)
return &c
}
// Applying configurations multiple times is not safe unless it's guaranteed that there
// are no goroutines which might call logging functions. The default for ValidateAndApply
// and ValidateAndApplyWithOptions is to return an error when called more than once.
// Binaries and unit tests can override that behavior.
var ReapplyHandling = ReapplyHandlingError
type ReapplyHandlingType int
const (
// ReapplyHandlingError is the default: calling ValidateAndApply or
// ValidateAndApplyWithOptions again returns an error.
ReapplyHandlingError ReapplyHandlingType = iota
// ReapplyHandlingIgnoreUnchanged silently ignores any additional calls of
// ValidateAndApply or ValidateAndApplyWithOptions if the configuration
// is unchanged, otherwise they return an error.
ReapplyHandlingIgnoreUnchanged
)
// ValidateAndApply combines validation and application of the logging configuration.
// This should be invoked as early as possible because then the rest of the program
// startup (including validation of other options) will already run with the final
// logging configuration.
//
// The optional FeatureGate controls logging features. If nil, the default for
// these features is used.
//
// Logging options must be applied as early as possible during the program
// startup. Some changes are global and cannot be done safely when there are
// already goroutines running.
func ValidateAndApply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error {
return validateAndApply(c, nil, featureGate, nil)
}
// ValidateAndApplyWithOptions is a variant of ValidateAndApply which accepts
// additional options beyond those that can be configured through the API. This
// is meant for testing.
//
// Logging options must be applied as early as possible during the program
// startup. Some changes are global and cannot be done safely when there are
// already goroutines running.
func ValidateAndApplyWithOptions(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate) error {
return validateAndApply(c, options, featureGate, nil)
}
// +k8s:deepcopy-gen=false
// LoggingOptions can be used with ValidateAndApplyWithOptions to override
// certain global defaults.
type LoggingOptions struct {
// ErrorStream can be used to override the os.Stderr default.
ErrorStream io.Writer
// InfoStream can be used to override the os.Stdout default.
InfoStream io.Writer
}
// ValidateAndApplyAsField is a variant of ValidateAndApply that should be used
// when the LoggingConfiguration is embedded in some larger configuration
// structure.
func ValidateAndApplyAsField(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) error {
return validateAndApply(c, nil, featureGate, fldPath)
}
func validateAndApply(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate, fldPath *field.Path) error {
errs := Validate(c, featureGate, fldPath)
if len(errs) > 0 {
return errs.ToAggregate()
}
return apply(c, options, featureGate)
}
// Validate can be used to check for invalid settings without applying them.
// Most binaries should validate and apply the logging configuration as soon
// as possible via ValidateAndApply. The field path is optional: nil
// can be passed when the struct is not embedded in some larger struct.
func Validate(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) field.ErrorList {
errs := field.ErrorList{}
if c.Format != DefaultLogFormat {
// WordSepNormalizeFunc is just a guess. Commands should use it,
// but we cannot know for sure.
allFlags := unsupportedLoggingFlags(cliflag.WordSepNormalizeFunc)
for _, f := range allFlags {
if f.DefValue != f.Value.String() {
errs = append(errs, field.Invalid(fldPath.Child("format"), c.Format, fmt.Sprintf("Non-default format doesn't honor flag: %s", f.Name)))
}
}
}
format, err := logRegistry.get(c.Format)
if err != nil {
errs = append(errs, field.Invalid(fldPath.Child("format"), c.Format, "Unsupported log format"))
} else if format != nil {
if format.feature != LoggingStableOptions {
enabled := featureGates()[format.feature].Default
if featureGate != nil {
enabled = featureGate.Enabled(format.feature)
}
if !enabled {
errs = append(errs, field.Forbidden(fldPath.Child("format"), fmt.Sprintf("Log format %s is disabled, see %s feature", c.Format, format.feature)))
}
}
}
// The type in our struct is uint32, but klog only accepts positive int32.
if c.Verbosity > math.MaxInt32 {
errs = append(errs, field.Invalid(fldPath.Child("verbosity"), c.Verbosity, fmt.Sprintf("Must be <= %d", math.MaxInt32)))
}
vmoduleFldPath := fldPath.Child("vmodule")
if len(c.VModule) > 0 && c.Format != "" && c.Format != "text" {
errs = append(errs, field.Forbidden(vmoduleFldPath, "Only supported for text log format"))
}
for i, item := range c.VModule {
if item.FilePattern == "" {
errs = append(errs, field.Required(vmoduleFldPath.Index(i), "File pattern must not be empty"))
}
if strings.ContainsAny(item.FilePattern, "=,") {
errs = append(errs, field.Invalid(vmoduleFldPath.Index(i), item.FilePattern, "File pattern must not contain equal sign or comma"))
}
if item.Verbosity > math.MaxInt32 {
errs = append(errs, field.Invalid(vmoduleFldPath.Index(i), item.Verbosity, fmt.Sprintf("Must be <= %d", math.MaxInt32)))
}
}
errs = append(errs, validateFormatOptions(c, featureGate, fldPath.Child("options"))...)
return errs
}
func validateFormatOptions(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validateTextOptions(c, featureGate, fldPath.Child("text"))...)
errs = append(errs, validateJSONOptions(c, featureGate, fldPath.Child("json"))...)
return errs
}
func validateTextOptions(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) field.ErrorList {
errs := field.ErrorList{}
if gate := LoggingAlphaOptions; c.Options.Text.SplitStream && !featureEnabled(featureGate, gate) {
errs = append(errs, field.Forbidden(fldPath.Child("splitStream"), fmt.Sprintf("Feature %s is disabled", gate)))
}
if gate := LoggingAlphaOptions; c.Options.Text.InfoBufferSize.Value() != 0 && !featureEnabled(featureGate, gate) {
errs = append(errs, field.Forbidden(fldPath.Child("infoBufferSize"), fmt.Sprintf("Feature %s is disabled", gate)))
}
return errs
}
func validateJSONOptions(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) field.ErrorList {
errs := field.ErrorList{}
if gate := LoggingAlphaOptions; c.Options.JSON.SplitStream && !featureEnabled(featureGate, gate) {
errs = append(errs, field.Forbidden(fldPath.Child("splitStream"), fmt.Sprintf("Feature %s is disabled", gate)))
}
if gate := LoggingAlphaOptions; c.Options.JSON.InfoBufferSize.Value() != 0 && !featureEnabled(featureGate, gate) {
errs = append(errs, field.Forbidden(fldPath.Child("infoBufferSize"), fmt.Sprintf("Feature %s is disabled", gate)))
}
return errs
}
func featureEnabled(featureGate featuregate.FeatureGate, feature featuregate.Feature) bool {
enabled := false
if featureGate != nil {
enabled = featureGate.Enabled(feature)
}
return enabled
}
func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate) error {
p := &parameters{
C: c,
Options: options,
ContextualLoggingEnabled: contextualLoggingDefault,
}
if featureGate != nil {
p.ContextualLoggingEnabled = featureGate.Enabled(ContextualLogging)
}
oldP := applyParameters.Load()
if oldP != nil {
switch ReapplyHandling {
case ReapplyHandlingError:
return errors.New("logging configuration was already applied earlier, changing it is not allowed")
case ReapplyHandlingIgnoreUnchanged:
if diff := cmp.Diff(oldP, p); diff != "" {
return fmt.Errorf("the logging configuration should not be changed after setting it once (- old setting, + new setting):\n%s", diff)
}
return nil
default:
return fmt.Errorf("invalid value %d for ReapplyHandling", ReapplyHandling)
}
}
applyParameters.Store(p)
// if log format not exists, use nil loggr
format, _ := logRegistry.get(c.Format)
if format.factory == nil {
klog.ClearLogger()
} else {
if options == nil {
options = &LoggingOptions{
ErrorStream: os.Stderr,
InfoStream: os.Stdout,
}
}
log, control := format.factory.Create(*c, *options)
if control.SetVerbosityLevel != nil {
setverbositylevel.Mutex.Lock()
defer setverbositylevel.Mutex.Unlock()
setverbositylevel.Callbacks = append(setverbositylevel.Callbacks, control.SetVerbosityLevel)
}
opts := []klog.LoggerOption{
klog.ContextualLogger(p.ContextualLoggingEnabled),
klog.FlushLogger(control.Flush),
}
if writer, ok := log.GetSink().(textlogger.KlogBufferWriter); ok {
opts = append(opts, klog.WriteKlogBuffer(writer.WriteKlogBuffer))
}
klog.SetLoggerWithOptions(log, opts...)
}
if err := loggingFlags.Lookup("v").Value.Set(VerbosityLevelPflag(&c.Verbosity).String()); err != nil {
return fmt.Errorf("internal error while setting klog verbosity: %v", err)
}
if err := loggingFlags.Lookup("vmodule").Value.Set(VModuleConfigurationPflag(&c.VModule).String()); err != nil {
return fmt.Errorf("internal error while setting klog vmodule: %v", err)
}
setSlogDefaultLogger()
klog.StartFlushDaemon(c.FlushFrequency.Duration.Duration)
klog.EnableContextualLogging(p.ContextualLoggingEnabled)
return nil
}
type parameters struct {
C *LoggingConfiguration
Options *LoggingOptions
ContextualLoggingEnabled bool
}
var applyParameters atomic.Pointer[parameters]
// ResetForTest restores the default settings. This is not thread-safe and should only
// be used when there are no goroutines running. The intended users are unit
// tests in other packages.
func ResetForTest(featureGate featuregate.FeatureGate) error {
oldP := applyParameters.Load()
if oldP == nil {
// Nothing to do.
return nil
}
// This makes it possible to call apply again without triggering errors.
applyParameters.Store(nil)
// Restore defaults. Shouldn't fail, but check anyway.
config := NewLoggingConfiguration()
if err := ValidateAndApply(config, featureGate); err != nil {
return fmt.Errorf("apply default configuration: %v", err)
}
// And again...
applyParameters.Store(nil)
return nil
}
// AddFlags adds command line flags for the configuration.
func AddFlags(c *LoggingConfiguration, fs *pflag.FlagSet) {
addFlags(c, fs)
}
// AddGoFlags is a variant of AddFlags for a standard FlagSet.
func AddGoFlags(c *LoggingConfiguration, fs *flag.FlagSet) {
addFlags(c, goFlagSet{FlagSet: fs})
}
// flagSet is the interface implemented by pflag.FlagSet, with
// just those methods defined which are needed by addFlags.
type flagSet interface {
BoolVar(p *bool, name string, value bool, usage string)
DurationVar(p *time.Duration, name string, value time.Duration, usage string)
StringVar(p *string, name string, value string, usage string)
Var(value pflag.Value, name string, usage string)
VarP(value pflag.Value, name, shorthand, usage string)
}
// goFlagSet implements flagSet for a stdlib flag.FlagSet.
type goFlagSet struct {
*flag.FlagSet
}
func (fs goFlagSet) Var(value pflag.Value, name string, usage string) {
fs.FlagSet.Var(value, name, usage)
}
func (fs goFlagSet) VarP(value pflag.Value, name, shorthand, usage string) {
// Ignore shorthand, it's not needed and not supported.
fs.FlagSet.Var(value, name, usage)
}
// addFlags can be used with both flag.FlagSet and pflag.FlagSet. The internal
// interface definition avoids duplicating this code.
func addFlags(c *LoggingConfiguration, fs flagSet) {
formats := logRegistry.list()
fs.StringVar(&c.Format, "logging-format", c.Format, fmt.Sprintf("Sets the log format. Permitted formats: %s.", formats))
// No new log formats should be added after generation is of flag options
logRegistry.freeze()
fs.DurationVar(&c.FlushFrequency.Duration.Duration, LogFlushFreqFlagName, c.FlushFrequency.Duration.Duration, "Maximum number of seconds between log flushes")
fs.VarP(VerbosityLevelPflag(&c.Verbosity), "v", "v", "number for the log level verbosity")
fs.Var(VModuleConfigurationPflag(&c.VModule), "vmodule", "comma-separated list of pattern=N settings for file-filtered logging (only works for text log format)")
fs.BoolVar(&c.Options.Text.SplitStream, "log-text-split-stream", false, "[Alpha] In text format, write error messages to stderr and info messages to stdout. The default is to write a single stream to stdout. Enable the LoggingAlphaOptions feature gate to use this.")
fs.Var(&c.Options.Text.InfoBufferSize, "log-text-info-buffer-size", "[Alpha] In text format with split output streams, the info messages can be buffered for a while to increase performance. The default value of zero bytes disables buffering. The size can be specified as number of bytes (512), multiples of 1000 (1K), multiples of 1024 (2Ki), or powers of those (3M, 4G, 5Mi, 6Gi). Enable the LoggingAlphaOptions feature gate to use this.")
// JSON options. We only register them if "json" is a valid format. The
// config file API however always has them.
if _, err := logRegistry.get("json"); err == nil {
fs.BoolVar(&c.Options.JSON.SplitStream, "log-json-split-stream", false, "[Alpha] In JSON format, write error messages to stderr and info messages to stdout. The default is to write a single stream to stdout. Enable the LoggingAlphaOptions feature gate to use this.")
fs.Var(&c.Options.JSON.InfoBufferSize, "log-json-info-buffer-size", "[Alpha] In JSON format with split output streams, the info messages can be buffered for a while to increase performance. The default value of zero bytes disables buffering. The size can be specified as number of bytes (512), multiples of 1000 (1K), multiples of 1024 (2Ki), or powers of those (3M, 4G, 5Mi, 6Gi). Enable the LoggingAlphaOptions feature gate to use this.")
}
}
// SetRecommendedLoggingConfiguration sets the default logging configuration
// for fields that are unset.
//
// Consumers who embed LoggingConfiguration in their own configuration structs
// may set custom defaults and then should call this function to add the
// global defaults.
func SetRecommendedLoggingConfiguration(c *LoggingConfiguration) {
if c.Format == "" {
c.Format = "text"
}
if c.FlushFrequency.Duration.Duration == 0 {
c.FlushFrequency.Duration.Duration = LogFlushFreqDefault
c.FlushFrequency.SerializeAsString = true
}
setRecommendedOutputRouting(&c.Options.Text.OutputRoutingOptions)
setRecommendedOutputRouting(&c.Options.JSON.OutputRoutingOptions)
}
func setRecommendedOutputRouting(o *OutputRoutingOptions) {
var empty resource.QuantityValue
if o.InfoBufferSize == empty {
o.InfoBufferSize = resource.QuantityValue{
// This is similar, but not quite the same as a default
// constructed instance.
Quantity: *resource.NewQuantity(0, resource.DecimalSI),
}
// This sets the unexported Quantity.s which will be compared
// by reflect.DeepEqual in some tests.
_ = o.InfoBufferSize.String()
}
}
// loggingFlags captures the state of the logging flags, in particular their default value
// before flag parsing. It is used by unsupportedLoggingFlags.
var loggingFlags pflag.FlagSet
func init() {
var fs flag.FlagSet
klogflags.Init(&fs)
loggingFlags.AddGoFlagSet(&fs)
}
// List of logs (k8s.io/klog + k8s.io/component-base/logs) flags supported by all logging formats
var supportedLogsFlags = map[string]struct{}{
"v": {},
}
// unsupportedLoggingFlags lists unsupported logging flags. The normalize
// function is optional.
func unsupportedLoggingFlags(normalizeFunc func(f *pflag.FlagSet, name string) pflag.NormalizedName) []*pflag.Flag {
// k8s.io/component-base/logs and klog flags
pfs := &pflag.FlagSet{}
loggingFlags.VisitAll(func(flag *pflag.Flag) {
if _, found := supportedLogsFlags[flag.Name]; !found {
// Normalization changes flag.Name, so make a copy.
clone := *flag
pfs.AddFlag(&clone)
}
})
// Apply normalization.
pfs.SetNormalizeFunc(normalizeFunc)
var allFlags []*pflag.Flag
pfs.VisitAll(func(flag *pflag.Flag) {
allFlags = append(allFlags, flag)
})
return allFlags
}

View File

@ -1,24 +0,0 @@
//go:build !go1.21
// +build !go1.21
/*
Copyright 2023 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 v1
func setSlogDefaultLogger() {
// Do nothing when build with Go < 1.21.
}

View File

@ -1,37 +0,0 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2023 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 v1
import (
"log/slog"
"github.com/go-logr/logr"
"k8s.io/klog/v2"
)
// setSlogDefaultLogger sets the global slog default logger to the same default
// that klog currently uses.
func setSlogDefaultLogger() {
// klog.Background() always returns a valid logr.Logger, regardless of
// how logging was configured. We just need to turn it into a
// slog.Handler. SetDefault then needs a slog.Logger.
handler := logr.ToSlogHandler(klog.Background())
slog.SetDefault(slog.New(handler))
}

View File

@ -1,113 +0,0 @@
/*
Copyright 2021 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 v1
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/pflag"
)
// VModuleConfigurationPflag implements the pflag.Value interface for a
// VModuleConfiguration. The value pointer must not be nil.
func VModuleConfigurationPflag(value *VModuleConfiguration) pflag.Value {
return vmoduleConfigurationPFlag{value}
}
type vmoduleConfigurationPFlag struct {
value *VModuleConfiguration
}
// String returns the -vmodule parameter (comma-separated list of pattern=N).
func (wrapper vmoduleConfigurationPFlag) String() string {
if wrapper.value == nil {
return ""
}
var patterns []string
for _, item := range *wrapper.value {
patterns = append(patterns, fmt.Sprintf("%s=%d", item.FilePattern, item.Verbosity))
}
return strings.Join(patterns, ",")
}
// Set parses the -vmodule parameter (comma-separated list of pattern=N).
func (wrapper vmoduleConfigurationPFlag) Set(value string) error {
// This code mirrors https://github.com/kubernetes/klog/blob/9ad246211af1ed84621ee94a26fcce0038b69cd1/klog.go#L287-L313
for _, pat := range strings.Split(value, ",") {
if len(pat) == 0 {
// Empty strings such as from a trailing comma can be ignored.
continue
}
patLev := strings.Split(pat, "=")
if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 {
return fmt.Errorf("%q does not have the pattern=N format", pat)
}
pattern := patLev[0]
// 31 instead of 32 to ensure that it also fits into int32.
v, err := strconv.ParseUint(patLev[1], 10, 31)
if err != nil {
return fmt.Errorf("parsing verbosity in %q: %v", pat, err)
}
*wrapper.value = append(*wrapper.value, VModuleItem{FilePattern: pattern, Verbosity: VerbosityLevel(v)})
}
return nil
}
func (wrapper vmoduleConfigurationPFlag) Type() string {
return "pattern=N,..."
}
// VerbosityLevelPflag implements the pflag.Value interface for a verbosity
// level value.
func VerbosityLevelPflag(value *VerbosityLevel) pflag.Value {
return verbosityLevelPflag{value}
}
type verbosityLevelPflag struct {
value *VerbosityLevel
}
func (wrapper verbosityLevelPflag) String() string {
if wrapper.value == nil {
return "0"
}
return strconv.FormatInt(int64(*wrapper.value), 10)
}
func (wrapper verbosityLevelPflag) Get() interface{} {
if wrapper.value == nil {
return VerbosityLevel(0)
}
return *wrapper.value
}
func (wrapper verbosityLevelPflag) Set(value string) error {
// Limited to int32 for compatibility with klog.
v, err := strconv.ParseUint(value, 10, 31)
if err != nil {
return err
}
*wrapper.value = VerbosityLevel(v)
return nil
}
func (wrapper verbosityLevelPflag) Type() string {
return "Level"
}

View File

@ -1,135 +0,0 @@
/*
Copyright 2020 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 v1
import (
"fmt"
"sort"
"strings"
"sync"
"github.com/go-logr/logr"
"k8s.io/component-base/featuregate"
)
var logRegistry = newLogFormatRegistry()
// logFormatRegistry stores factories for all supported logging formats.
type logFormatRegistry struct {
mutex sync.Mutex
registry map[string]logFormat
frozen bool
}
type logFormat struct {
factory LogFormatFactory
feature featuregate.Feature
}
// +k8s:deepcopy-gen=false
// RuntimeControl provides operations that aren't available through the normal
// Logger or LogSink API.
type RuntimeControl struct {
// Flush ensures that all in-memory data is written.
// May be nil.
Flush func()
// SetVerbosityLevel changes the level for all Logger instances
// derived from the initial one. May be nil.
//
// The parameter is intentionally a plain uint32 instead of
// VerbosityLevel to enable implementations that don't need to import
// the API (helps avoid circular dependencies).
SetVerbosityLevel func(v uint32) error
}
// LogFormatFactory provides support for a certain additional,
// non-default log format.
type LogFormatFactory interface {
// Create returns a logger with the requested configuration.
Create(c LoggingConfiguration, o LoggingOptions) (logr.Logger, RuntimeControl)
}
// RegisterLogFormat registers support for a new logging format. This must be called
// before using any of the methods in LoggingConfiguration. The feature must
// be one of those defined in this package (typically LoggingAlphaOptions,
// LoggingBetaOptions or LoggingStableOptions).
func RegisterLogFormat(name string, factory LogFormatFactory, feature featuregate.Feature) error {
return logRegistry.register(name, logFormat{factory, feature})
}
func newLogFormatRegistry() *logFormatRegistry {
registry := &logFormatRegistry{
registry: make(map[string]logFormat),
frozen: false,
}
_ = registry.register(DefaultLogFormat, logFormat{factory: textFactory{}, feature: LoggingStableOptions})
return registry
}
// register adds a new log format. It's an error to modify an existing one.
func (lfr *logFormatRegistry) register(name string, format logFormat) error {
lfr.mutex.Lock()
defer lfr.mutex.Unlock()
if lfr.frozen {
return fmt.Errorf("log format registry is frozen, unable to register log format %s", name)
}
if _, ok := lfr.registry[name]; ok {
return fmt.Errorf("log format: %s already exists", name)
}
if _, ok := featureGates()[format.feature]; !ok && format.feature != LoggingStableOptions {
return fmt.Errorf("log format %s: unsupported feature gate %s", name, format.feature)
}
lfr.registry[name] = format
return nil
}
// get specified log format factory
func (lfr *logFormatRegistry) get(name string) (*logFormat, error) {
lfr.mutex.Lock()
defer lfr.mutex.Unlock()
format, ok := lfr.registry[name]
if !ok {
return nil, fmt.Errorf("log format: %s does not exists", name)
}
return &format, nil
}
// list names of registered log formats, including feature gates (sorted)
func (lfr *logFormatRegistry) list() string {
lfr.mutex.Lock()
defer lfr.mutex.Unlock()
formats := make([]string, 0, len(lfr.registry))
for name, format := range lfr.registry {
item := fmt.Sprintf(`"%s"`, name)
if format.feature != LoggingStableOptions {
item += fmt.Sprintf(" (gated by %s)", format.feature)
}
formats = append(formats, item)
}
sort.Strings(formats)
return strings.Join(formats, ", ")
}
// freeze prevents further modifications of the registered log formats.
func (lfr *logFormatRegistry) freeze() {
lfr.mutex.Lock()
defer lfr.mutex.Unlock()
lfr.frozen = true
}

View File

@ -1,142 +0,0 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"bufio"
"fmt"
"io"
"sync"
"github.com/go-logr/logr"
"k8s.io/component-base/featuregate"
"k8s.io/klog/v2/textlogger"
)
// textFactory produces klog text logger instances.
type textFactory struct{}
var _ LogFormatFactory = textFactory{}
func (f textFactory) Feature() featuregate.Feature {
return LoggingStableOptions
}
func (f textFactory) Create(c LoggingConfiguration, o LoggingOptions) (logr.Logger, RuntimeControl) {
output := o.ErrorStream
var flush func()
if c.Options.Text.SplitStream {
r := &klogMsgRouter{
info: o.InfoStream,
error: o.ErrorStream,
}
size := c.Options.Text.InfoBufferSize.Value()
if size > 0 {
// Prevent integer overflow.
if size > 2*1024*1024*1024 {
size = 2 * 1024 * 1024 * 1024
}
info := newBufferedWriter(r.info, int(size))
flush = info.Flush
r.info = info
}
output = r
}
options := []textlogger.ConfigOption{
textlogger.Verbosity(int(c.Verbosity)),
textlogger.Output(output),
}
loggerConfig := textlogger.NewConfig(options...)
// This should never fail, we produce a valid string here.
_ = loggerConfig.VModule().Set(VModuleConfigurationPflag(&c.VModule).String())
return textlogger.NewLogger(loggerConfig),
RuntimeControl{
SetVerbosityLevel: func(v uint32) error {
return loggerConfig.Verbosity().Set(fmt.Sprintf("%d", v))
},
Flush: flush,
}
}
type klogMsgRouter struct {
info, error io.Writer
}
var _ io.Writer = &klogMsgRouter{}
// Write redirects the message into either the info or error
// stream, depending on its type as indicated in text format
// by the first byte.
func (r *klogMsgRouter) Write(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
if p[0] == 'I' {
return r.info.Write(p)
}
return r.error.Write(p)
}
// bufferedWriter is an io.Writer that buffers writes in-memory before
// flushing them to a wrapped io.Writer after reaching some limit
// or getting flushed.
type bufferedWriter struct {
mu sync.Mutex
writer *bufio.Writer
out io.Writer
}
func newBufferedWriter(out io.Writer, size int) *bufferedWriter {
return &bufferedWriter{
writer: bufio.NewWriterSize(out, size),
out: out,
}
}
func (b *bufferedWriter) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
// To avoid partial writes into the underlying writer, we ensure that
// the entire new data fits into the buffer or flush first.
if len(p) > b.writer.Available() && b.writer.Buffered() > 0 {
if err := b.writer.Flush(); err != nil {
return 0, err
}
}
// If it still doesn't fit, then we bypass the now empty buffer
// and write directly.
if len(p) > b.writer.Available() {
return b.out.Write(p)
}
// This goes into the buffer.
return b.writer.Write(p)
}
func (b *bufferedWriter) Flush() {
b.mu.Lock()
defer b.mu.Unlock()
_ = b.writer.Flush()
}

View File

@ -1,146 +0,0 @@
/*
Copyright 2021 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 v1
import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Supported output formats.
const (
// DefaultLogFormat is the traditional klog output format.
DefaultLogFormat = "text"
// JSONLogFormat emits each log message as a JSON struct.
JSONLogFormat = "json"
)
// The alpha or beta level of structs is the highest stability level of any field
// inside it. Feature gates will get checked during LoggingConfiguration.ValidateAndApply.
// LoggingConfiguration contains logging options.
type LoggingConfiguration struct {
// Format Flag specifies the structure of log messages.
// default value of format is `text`
Format string `json:"format,omitempty"`
// Maximum time between log flushes.
// If a string, parsed as a duration (i.e. "1s")
// If an int, the maximum number of nanoseconds (i.e. 1s = 1000000000).
// Ignored if the selected logging backend writes log messages without buffering.
FlushFrequency TimeOrMetaDuration `json:"flushFrequency"`
// Verbosity is the threshold that determines which log messages are
// logged. Default is zero which logs only the most important
// messages. Higher values enable additional messages. Error messages
// are always logged.
Verbosity VerbosityLevel `json:"verbosity"`
// VModule overrides the verbosity threshold for individual files.
// Only supported for "text" log format.
VModule VModuleConfiguration `json:"vmodule,omitempty"`
// [Alpha] Options holds additional parameters that are specific
// to the different logging formats. Only the options for the selected
// format get used, but all of them get validated.
// Only available when the LoggingAlphaOptions feature gate is enabled.
Options FormatOptions `json:"options,omitempty"`
}
// TimeOrMetaDuration is present only for backwards compatibility for the
// flushFrequency field, and new fields should use metav1.Duration.
type TimeOrMetaDuration struct {
// Duration holds the duration
Duration metav1.Duration
// SerializeAsString controls whether the value is serialized as a string or an integer
SerializeAsString bool `json:"-"`
}
func (t TimeOrMetaDuration) MarshalJSON() ([]byte, error) {
if t.SerializeAsString {
return t.Duration.MarshalJSON()
} else {
// Marshal as integer for backwards compatibility
return json.Marshal(t.Duration.Duration)
}
}
func (t *TimeOrMetaDuration) UnmarshalJSON(b []byte) error {
if len(b) > 0 && b[0] == '"' {
// string values unmarshal as metav1.Duration
t.SerializeAsString = true
return json.Unmarshal(b, &t.Duration)
}
t.SerializeAsString = false
if err := json.Unmarshal(b, &t.Duration.Duration); err != nil {
return fmt.Errorf("invalid duration %q: %w", string(b), err)
}
return nil
}
// FormatOptions contains options for the different logging formats.
type FormatOptions struct {
// [Alpha] Text contains options for logging format "text".
// Only available when the LoggingAlphaOptions feature gate is enabled.
Text TextOptions `json:"text,omitempty"`
// [Alpha] JSON contains options for logging format "json".
// Only available when the LoggingAlphaOptions feature gate is enabled.
JSON JSONOptions `json:"json,omitempty"`
}
// TextOptions contains options for logging format "text".
type TextOptions struct {
OutputRoutingOptions `json:",inline"`
}
// JSONOptions contains options for logging format "json".
type JSONOptions struct {
OutputRoutingOptions `json:",inline"`
}
// OutputRoutingOptions contains options that are supported by both "text" and "json".
type OutputRoutingOptions struct {
// [Alpha] SplitStream redirects error messages to stderr while
// info messages go to stdout, with buffering. The default is to write
// both to stdout, without buffering. Only available when
// the LoggingAlphaOptions feature gate is enabled.
SplitStream bool `json:"splitStream,omitempty"`
// [Alpha] InfoBufferSize sets the size of the info stream when
// using split streams. The default is zero, which disables buffering.
// Only available when the LoggingAlphaOptions feature gate is enabled.
InfoBufferSize resource.QuantityValue `json:"infoBufferSize,omitempty"`
}
// VModuleConfiguration is a collection of individual file names or patterns
// and the corresponding verbosity threshold.
type VModuleConfiguration []VModuleItem
// VModuleItem defines verbosity for one or more files which match a certain
// glob pattern.
type VModuleItem struct {
// FilePattern is a base file name (i.e. minus the ".go" suffix and
// directory) or a "glob" pattern for such a name. It must not contain
// comma and equal signs because those are separators for the
// corresponding klog command line argument.
FilePattern string `json:"filePattern"`
// Verbosity is the threshold for log messages emitted inside files
// that match the pattern.
Verbosity VerbosityLevel `json:"verbosity"`
}
// VerbosityLevel represents a klog or logr verbosity threshold.
type VerbosityLevel uint32

View File

@ -1,167 +0,0 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FormatOptions) DeepCopyInto(out *FormatOptions) {
*out = *in
in.Text.DeepCopyInto(&out.Text)
in.JSON.DeepCopyInto(&out.JSON)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FormatOptions.
func (in *FormatOptions) DeepCopy() *FormatOptions {
if in == nil {
return nil
}
out := new(FormatOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONOptions) DeepCopyInto(out *JSONOptions) {
*out = *in
in.OutputRoutingOptions.DeepCopyInto(&out.OutputRoutingOptions)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONOptions.
func (in *JSONOptions) DeepCopy() *JSONOptions {
if in == nil {
return nil
}
out := new(JSONOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) {
*out = *in
out.FlushFrequency = in.FlushFrequency
if in.VModule != nil {
in, out := &in.VModule, &out.VModule
*out = make(VModuleConfiguration, len(*in))
copy(*out, *in)
}
in.Options.DeepCopyInto(&out.Options)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingConfiguration.
func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration {
if in == nil {
return nil
}
out := new(LoggingConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OutputRoutingOptions) DeepCopyInto(out *OutputRoutingOptions) {
*out = *in
in.InfoBufferSize.DeepCopyInto(&out.InfoBufferSize)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OutputRoutingOptions.
func (in *OutputRoutingOptions) DeepCopy() *OutputRoutingOptions {
if in == nil {
return nil
}
out := new(OutputRoutingOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TextOptions) DeepCopyInto(out *TextOptions) {
*out = *in
in.OutputRoutingOptions.DeepCopyInto(&out.OutputRoutingOptions)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TextOptions.
func (in *TextOptions) DeepCopy() *TextOptions {
if in == nil {
return nil
}
out := new(TextOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TimeOrMetaDuration) DeepCopyInto(out *TimeOrMetaDuration) {
*out = *in
out.Duration = in.Duration
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TimeOrMetaDuration.
func (in *TimeOrMetaDuration) DeepCopy() *TimeOrMetaDuration {
if in == nil {
return nil
}
out := new(TimeOrMetaDuration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in VModuleConfiguration) DeepCopyInto(out *VModuleConfiguration) {
{
in := &in
*out = make(VModuleConfiguration, len(*in))
copy(*out, *in)
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VModuleConfiguration.
func (in VModuleConfiguration) DeepCopy() VModuleConfiguration {
if in == nil {
return nil
}
out := new(VModuleConfiguration)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VModuleItem) DeepCopyInto(out *VModuleItem) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VModuleItem.
func (in *VModuleItem) DeepCopy() *VModuleItem {
if in == nil {
return nil
}
out := new(VModuleItem)
in.DeepCopyInto(out)
return out
}

View File

@ -1,34 +0,0 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package setverbositylevel stores callbacks that will be invoked by logs.GlogLevel.
//
// This is a separate package to avoid a dependency from
// k8s.io/component-base/logs (uses the callbacks) to
// k8s.io/component-base/logs/api/v1 (adds them). Not all users of the logs
// package also use the API.
package setverbositylevel
import (
"sync"
)
var (
// Mutex controls access to the callbacks.
Mutex sync.Mutex
Callbacks []func(v uint32) error
)

View File

@ -1,41 +0,0 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package klogflags
import (
"flag"
"k8s.io/klog/v2"
)
// Init is a replacement for klog.InitFlags which only adds those flags
// that are still supported for Kubernetes components (i.e. -v and -vmodule).
// See
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components.
func Init(fs *flag.FlagSet) {
var allFlags flag.FlagSet
klog.InitFlags(&allFlags)
if fs == nil {
fs = flag.CommandLine
}
allFlags.VisitAll(func(f *flag.Flag) {
switch f.Name {
case "v", "vmodule":
fs.Var(f.Value, f.Name, f.Usage)
}
})
}

View File

@ -1,78 +0,0 @@
/*
Copyright 2018 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 logreduction
import (
"sync"
"time"
)
var nowfunc = func() time.Time { return time.Now() }
// LogReduction provides a filter for consecutive identical log messages;
// a message will be printed no more than once per interval.
// If a string of messages is interrupted by a different message,
// the interval timer will be reset.
type LogReduction struct {
lastError map[string]string
errorPrinted map[string]time.Time
errorMapLock sync.Mutex
identicalErrorDelay time.Duration
}
// NewLogReduction returns an initialized LogReduction
func NewLogReduction(identicalErrorDelay time.Duration) *LogReduction {
l := new(LogReduction)
l.lastError = make(map[string]string)
l.errorPrinted = make(map[string]time.Time)
l.identicalErrorDelay = identicalErrorDelay
return l
}
func (l *LogReduction) cleanupErrorTimeouts() {
for name, timeout := range l.errorPrinted {
if nowfunc().Sub(timeout) >= l.identicalErrorDelay {
delete(l.errorPrinted, name)
delete(l.lastError, name)
}
}
}
// ShouldMessageBePrinted determines whether a message should be printed based
// on how long ago this particular message was last printed
func (l *LogReduction) ShouldMessageBePrinted(message string, parentID string) bool {
l.errorMapLock.Lock()
defer l.errorMapLock.Unlock()
l.cleanupErrorTimeouts()
lastMsg, ok := l.lastError[parentID]
lastPrinted, ok1 := l.errorPrinted[parentID]
if !ok || !ok1 || message != lastMsg || nowfunc().Sub(lastPrinted) >= l.identicalErrorDelay {
l.errorPrinted[parentID] = nowfunc()
l.lastError[parentID] = message
return true
}
return false
}
// ClearID clears out log reduction records pertaining to a particular parent
// (e. g. container ID)
func (l *LogReduction) ClearID(parentID string) {
l.errorMapLock.Lock()
defer l.errorMapLock.Unlock()
delete(l.lastError, parentID)
delete(l.errorPrinted, parentID)
}

View File

@ -1,209 +0,0 @@
/*
Copyright 2014 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 logs contains support for logging options, flags and setup.
// Commands must explicitly enable command line flags. They no longer
// get added automatically when importing this package.
package logs
import (
"flag"
"fmt"
"log"
"strconv"
"time"
"github.com/spf13/pflag"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/component-base/logs/internal/setverbositylevel"
"k8s.io/component-base/logs/klogflags"
"k8s.io/klog/v2"
)
const vmoduleUsage = " (only works for the default text log format)"
var (
packageFlags = flag.NewFlagSet("logging", flag.ContinueOnError)
// Periodic flushing gets configured either via the global flag
// in this file or via LoggingConfiguration.
logFlushFreq time.Duration
)
func init() {
klogflags.Init(packageFlags)
packageFlags.DurationVar(&logFlushFreq, logsapi.LogFlushFreqFlagName, logsapi.LogFlushFreqDefault, "Maximum number of seconds between log flushes")
}
type addFlagsOptions struct {
skipLoggingConfigurationFlags bool
}
type Option func(*addFlagsOptions)
// SkipLoggingConfigurationFlags must be used as option for AddFlags when
// the program also uses a LoggingConfiguration struct for configuring
// logging. Then only flags not covered by that get added.
func SkipLoggingConfigurationFlags() Option {
return func(o *addFlagsOptions) {
o.skipLoggingConfigurationFlags = true
}
}
// Options is an alias for LoggingConfiguration to comply with component-base
// conventions.
type Options = logsapi.LoggingConfiguration
// NewOptions is an alias for NewLoggingConfiguration.
var NewOptions = logsapi.NewLoggingConfiguration
// AddFlags registers this package's flags on arbitrary FlagSets. This includes
// the klog flags, with the original underscore as separator between. If
// commands want hyphens as separators, they can set
// k8s.io/component-base/cli/flag/WordSepNormalizeFunc as normalization
// function on the flag set before calling AddFlags.
//
// May be called more than once.
func AddFlags(fs *pflag.FlagSet, opts ...Option) {
o := addFlagsOptions{}
for _, opt := range opts {
opt(&o)
}
// Add all supported flags.
packageFlags.VisitAll(func(f *flag.Flag) {
pf := pflag.PFlagFromGoFlag(f)
switch f.Name {
case "v", logsapi.LogFlushFreqFlagName:
// unchanged, potentially skip it
if o.skipLoggingConfigurationFlags {
return
}
case "vmodule":
if o.skipLoggingConfigurationFlags {
return
}
pf.Usage += vmoduleUsage
}
if fs.Lookup(pf.Name) == nil {
fs.AddFlag(pf)
}
})
}
// AddGoFlags is a variant of AddFlags for traditional Go flag.FlagSet.
// Commands should use pflag whenever possible for the sake of consistency.
// Cases where this function is needed include tests (they have to set up flags
// in flag.CommandLine) and commands that for historic reasons use Go
// flag.Parse and cannot change to pflag because it would break their command
// line interface.
func AddGoFlags(fs *flag.FlagSet, opts ...Option) {
o := addFlagsOptions{}
for _, opt := range opts {
opt(&o)
}
// Add flags with deprecation remark added to the usage text of
// some klog flags.
packageFlags.VisitAll(func(f *flag.Flag) {
usage := f.Usage
switch f.Name {
case "v", logsapi.LogFlushFreqFlagName:
// unchanged
if o.skipLoggingConfigurationFlags {
return
}
case "vmodule":
if o.skipLoggingConfigurationFlags {
return
}
usage += vmoduleUsage
}
fs.Var(f.Value, f.Name, usage)
})
}
// KlogWriter serves as a bridge between the standard log package and the glog package.
type KlogWriter struct{}
// Write implements the io.Writer interface.
func (writer KlogWriter) Write(data []byte) (n int, err error) {
klog.InfoDepth(1, string(data))
return len(data), nil
}
// InitLogs initializes logs the way we want for Kubernetes.
// It should be called after parsing flags. If called before that,
// it will use the default log settings.
//
// InitLogs disables support for contextual logging in klog while
// that Kubernetes feature is not considered stable yet. Commands
// which want to support contextual logging can:
// - call klog.EnableContextualLogging after calling InitLogs,
// with a fixed `true` or depending on some command line flag or
// a feature gate check
// - set up a FeatureGate instance, the advanced logging configuration
// with Options and call Options.ValidateAndApply with the FeatureGate;
// k8s.io/component-base/logs/example/cmd demonstrates how to do that
func InitLogs() {
log.SetOutput(KlogWriter{})
log.SetFlags(0)
// Start flushing now. If LoggingConfiguration.ApplyAndValidate is
// used, it will restart the daemon with the log flush interval defined
// there.
klog.StartFlushDaemon(logFlushFreq)
// This is the default in Kubernetes. Options.ValidateAndApply
// will override this with the result of a feature gate check.
klog.EnableContextualLogging(false)
}
// FlushLogs flushes logs immediately. This should be called at the end of
// the main function via defer to ensure that all pending log messages
// are printed before exiting the program.
func FlushLogs() {
klog.Flush()
}
// NewLogger creates a new log.Logger which sends logs to klog.Info.
func NewLogger(prefix string) *log.Logger {
return log.New(KlogWriter{}, prefix, 0)
}
// GlogSetter modifies the verbosity threshold for the entire program.
// Some components have HTTP-based APIs for invoking this at runtime.
func GlogSetter(val string) (string, error) {
v, err := strconv.ParseUint(val, 10, 32)
if err != nil {
return "", err
}
var level klog.Level
if err := level.Set(val); err != nil {
return "", fmt.Errorf("failed set klog.logging.verbosity %s: %v", val, err)
}
setverbositylevel.Mutex.Lock()
defer setverbositylevel.Mutex.Unlock()
for _, cb := range setverbositylevel.Callbacks {
if err := cb(uint32(v)); err != nil {
return "", err
}
}
return fmt.Sprintf("successfully set klog.logging.verbosity to %s", val), nil
}

View File

@ -1,32 +0,0 @@
/*
Copyright 2021 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 testinit adds logging flags to a Ginkgo or Go test program during
// initialization, something that the logs package itself no longer does.
//
// Normal commands should not use this and instead manage logging flags with
// logs.Options and/or cli.Run.
package testinit
import (
"flag"
"k8s.io/component-base/logs"
)
func init() {
logs.AddGoFlags(flag.CommandLine)
}

View File

@ -1,45 +0,0 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package features
import (
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/component-base/featuregate"
)
const (
// owner: @logicalhan
// kep: https://kep.k8s.io/3466
ComponentSLIs featuregate.Feature = "ComponentSLIs"
)
func featureGates() map[featuregate.Feature]featuregate.VersionedSpecs {
return map[featuregate.Feature]featuregate.VersionedSpecs{
ComponentSLIs: {
{Version: version.MustParse("1.26"), Default: false, PreRelease: featuregate.Alpha},
{Version: version.MustParse("1.27"), Default: true, PreRelease: featuregate.Beta},
// ComponentSLIs officially graduated to GA in v1.29 but the gate was not updated until v1.32.
// To support emulated versions, keep the gate until v1.35.
{Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
},
}
}
// AddFeatureGates adds all feature gates used by this package.
func AddFeatureGates(mutableFeatureGate featuregate.MutableVersionedFeatureGate) error {
return mutableFeatureGate.AddVersioned(featureGates())
}

View File

@ -1,75 +0,0 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package slis
import (
"context"
k8smetrics "k8s.io/component-base/metrics"
)
type HealthcheckStatus string
const (
Success HealthcheckStatus = "success"
Error HealthcheckStatus = "error"
)
var (
// healthcheck is a Prometheus Gauge metrics used for recording the results of a k8s healthcheck.
healthcheck = k8smetrics.NewGaugeVec(
&k8smetrics.GaugeOpts{
Namespace: "kubernetes",
Name: "healthcheck",
Help: "This metric records the result of a single healthcheck.",
StabilityLevel: k8smetrics.STABLE,
},
[]string{"name", "type"},
)
// healthchecksTotal is a Prometheus Counter metrics used for counting the results of a k8s healthcheck.
healthchecksTotal = k8smetrics.NewCounterVec(
&k8smetrics.CounterOpts{
Namespace: "kubernetes",
Name: "healthchecks_total",
Help: "This metric records the results of all healthcheck.",
StabilityLevel: k8smetrics.STABLE,
},
[]string{"name", "type", "status"},
)
)
func Register(registry k8smetrics.KubeRegistry) {
registry.Register(healthcheck)
registry.Register(healthchecksTotal)
_ = k8smetrics.RegisterProcessStartTime(registry.Register)
}
func ResetHealthMetrics() {
healthcheck.Reset()
healthchecksTotal.Reset()
}
func ObserveHealthcheck(ctx context.Context, name string, healthcheckType string, status HealthcheckStatus) error {
if status == Success {
healthcheck.WithContext(ctx).WithLabelValues(name, healthcheckType).Set(1)
} else {
healthcheck.WithContext(ctx).WithLabelValues(name, healthcheckType).Set(0)
}
healthchecksTotal.WithContext(ctx).WithLabelValues(name, healthcheckType, string(status)).Inc()
return nil
}

View File

@ -1,27 +0,0 @@
/*
Copyright 2020 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 slis
import (
"k8s.io/component-base/metrics"
)
var (
// Registry exposes the SLI registry so that additional SLIs can be
// added on a per-component basis.
Registry = metrics.NewKubeRegistry()
)

View File

@ -1,53 +0,0 @@
/*
Copyright 2020 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 slis
import (
"net/http"
"sync"
"k8s.io/component-base/metrics"
)
var (
installOnce = sync.Once{}
installWithResetOnce = sync.Once{}
)
type mux interface {
Handle(path string, handler http.Handler)
}
type SLIMetrics struct{}
// Install adds the DefaultMetrics handler
func (s SLIMetrics) Install(m mux) {
installOnce.Do(func() {
Register(Registry)
})
m.Handle("/metrics/slis", metrics.HandlerFor(Registry, metrics.HandlerOpts{}))
}
type SLIMetricsWithReset struct{}
// Install adds the DefaultMetrics handler
func (s SLIMetricsWithReset) Install(m mux) {
installWithResetOnce.Do(func() {
Register(Registry)
})
m.Handle("/metrics/slis", metrics.HandlerWithReset(Registry, metrics.HandlerOpts{}))
}

View File

@ -1,137 +0,0 @@
/*
Copyright 2019 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 workqueue
import (
"k8s.io/client-go/util/workqueue"
k8smetrics "k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
)
// Package prometheus sets the workqueue DefaultMetricsFactory to produce
// prometheus metrics. To use this package, you just have to import it.
// Metrics subsystem and keys used by the workqueue.
const (
WorkQueueSubsystem = "workqueue"
DepthKey = "depth"
AddsKey = "adds_total"
QueueLatencyKey = "queue_duration_seconds"
WorkDurationKey = "work_duration_seconds"
UnfinishedWorkKey = "unfinished_work_seconds"
LongestRunningProcessorKey = "longest_running_processor_seconds"
RetriesKey = "retries_total"
)
var (
depth = k8smetrics.NewGaugeVec(&k8smetrics.GaugeOpts{
Subsystem: WorkQueueSubsystem,
Name: DepthKey,
StabilityLevel: k8smetrics.ALPHA,
Help: "Current depth of workqueue",
}, []string{"name"})
adds = k8smetrics.NewCounterVec(&k8smetrics.CounterOpts{
Subsystem: WorkQueueSubsystem,
Name: AddsKey,
StabilityLevel: k8smetrics.ALPHA,
Help: "Total number of adds handled by workqueue",
}, []string{"name"})
latency = k8smetrics.NewHistogramVec(&k8smetrics.HistogramOpts{
Subsystem: WorkQueueSubsystem,
Name: QueueLatencyKey,
StabilityLevel: k8smetrics.ALPHA,
Help: "How long in seconds an item stays in workqueue before being requested.",
Buckets: k8smetrics.ExponentialBuckets(10e-9, 10, 10),
}, []string{"name"})
workDuration = k8smetrics.NewHistogramVec(&k8smetrics.HistogramOpts{
Subsystem: WorkQueueSubsystem,
Name: WorkDurationKey,
StabilityLevel: k8smetrics.ALPHA,
Help: "How long in seconds processing an item from workqueue takes.",
Buckets: k8smetrics.ExponentialBuckets(10e-9, 10, 10),
}, []string{"name"})
unfinished = k8smetrics.NewGaugeVec(&k8smetrics.GaugeOpts{
Subsystem: WorkQueueSubsystem,
Name: UnfinishedWorkKey,
StabilityLevel: k8smetrics.ALPHA,
Help: "How many seconds of work has done that " +
"is in progress and hasn't been observed by work_duration. Large " +
"values indicate stuck threads. One can deduce the number of stuck " +
"threads by observing the rate at which this increases.",
}, []string{"name"})
longestRunningProcessor = k8smetrics.NewGaugeVec(&k8smetrics.GaugeOpts{
Subsystem: WorkQueueSubsystem,
Name: LongestRunningProcessorKey,
StabilityLevel: k8smetrics.ALPHA,
Help: "How many seconds has the longest running " +
"processor for workqueue been running.",
}, []string{"name"})
retries = k8smetrics.NewCounterVec(&k8smetrics.CounterOpts{
Subsystem: WorkQueueSubsystem,
Name: RetriesKey,
StabilityLevel: k8smetrics.ALPHA,
Help: "Total number of retries handled by workqueue",
}, []string{"name"})
metrics = []k8smetrics.Registerable{
depth, adds, latency, workDuration, unfinished, longestRunningProcessor, retries,
}
)
type prometheusMetricsProvider struct {
}
func init() {
for _, m := range metrics {
legacyregistry.MustRegister(m)
}
workqueue.SetProvider(prometheusMetricsProvider{})
}
func (prometheusMetricsProvider) NewDepthMetric(name string) workqueue.GaugeMetric {
return depth.WithLabelValues(name)
}
func (prometheusMetricsProvider) NewAddsMetric(name string) workqueue.CounterMetric {
return adds.WithLabelValues(name)
}
func (prometheusMetricsProvider) NewLatencyMetric(name string) workqueue.HistogramMetric {
return latency.WithLabelValues(name)
}
func (prometheusMetricsProvider) NewWorkDurationMetric(name string) workqueue.HistogramMetric {
return workDuration.WithLabelValues(name)
}
func (prometheusMetricsProvider) NewUnfinishedWorkSecondsMetric(name string) workqueue.SettableGaugeMetric {
return unfinished.WithLabelValues(name)
}
func (prometheusMetricsProvider) NewLongestRunningProcessorSecondsMetric(name string) workqueue.SettableGaugeMetric {
return longestRunningProcessor.WithLabelValues(name)
}
func (prometheusMetricsProvider) NewRetriesMetric(name string) workqueue.CounterMetric {
return retries.WithLabelValues(name)
}

View File

@ -1,475 +0,0 @@
/*
Copyright 2019 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 testutil
import (
"fmt"
"io"
"math"
"reflect"
"sort"
"strings"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
"github.com/prometheus/common/model"
"k8s.io/component-base/metrics"
)
var (
// MetricNameLabel is label under which model.Sample stores metric name
MetricNameLabel model.LabelName = model.MetricNameLabel
// QuantileLabel is label under which model.Sample stores latency quantile value
QuantileLabel model.LabelName = model.QuantileLabel
)
// Metrics is generic metrics for other specific metrics
type Metrics map[string]model.Samples
// Equal returns true if all metrics are the same as the arguments.
func (m *Metrics) Equal(o Metrics) bool {
var leftKeySet []string
var rightKeySet []string
for k := range *m {
leftKeySet = append(leftKeySet, k)
}
for k := range o {
rightKeySet = append(rightKeySet, k)
}
if !reflect.DeepEqual(leftKeySet, rightKeySet) {
return false
}
for _, k := range leftKeySet {
if !(*m)[k].Equal(o[k]) {
return false
}
}
return true
}
// NewMetrics returns new metrics which are initialized.
func NewMetrics() Metrics {
result := make(Metrics)
return result
}
// ParseMetrics parses Metrics from data returned from prometheus endpoint
func ParseMetrics(data string, output *Metrics) error {
dec := expfmt.NewDecoder(strings.NewReader(data), expfmt.NewFormat(expfmt.TypeTextPlain))
decoder := expfmt.SampleDecoder{
Dec: dec,
Opts: &expfmt.DecodeOptions{},
}
for {
var v model.Vector
if err := decoder.Decode(&v); err != nil {
if err == io.EOF {
// Expected loop termination condition.
return nil
}
continue
}
for _, metric := range v {
name := string(metric.Metric[MetricNameLabel])
(*output)[name] = append((*output)[name], metric)
}
}
}
// TextToMetricFamilies reads 'in' as the simple and flat text-based exchange
// format and creates MetricFamily proto messages. It returns the MetricFamily
// proto messages in a map where the metric names are the keys, along with any
// error encountered.
func TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricFamily, error) {
var textParser expfmt.TextParser
return textParser.TextToMetricFamilies(in)
}
// PrintSample returns formatted representation of metric Sample
func PrintSample(sample *model.Sample) string {
buf := make([]string, 0)
// Id is a VERY special label. For 'normal' container it's useless, but it's necessary
// for 'system' containers (e.g. /docker-daemon, /kubelet, etc.). We know if that's the
// case by checking if there's a label "kubernetes_container_name" present. It's hacky
// but it works...
_, normalContainer := sample.Metric["kubernetes_container_name"]
for k, v := range sample.Metric {
if strings.HasPrefix(string(k), "__") {
continue
}
if string(k) == "id" && normalContainer {
continue
}
buf = append(buf, fmt.Sprintf("%v=%v", string(k), v))
}
return fmt.Sprintf("[%v] = %v", strings.Join(buf, ","), sample.Value)
}
// ComputeHistogramDelta computes the change in histogram metric for a selected label.
// Results are stored in after samples
func ComputeHistogramDelta(before, after model.Samples, label model.LabelName) {
beforeSamplesMap := make(map[string]*model.Sample)
for _, bSample := range before {
beforeSamplesMap[makeKey(bSample.Metric[label], bSample.Metric["le"])] = bSample
}
for _, aSample := range after {
if bSample, found := beforeSamplesMap[makeKey(aSample.Metric[label], aSample.Metric["le"])]; found {
aSample.Value = aSample.Value - bSample.Value
}
}
}
func makeKey(a, b model.LabelValue) string {
return string(a) + "___" + string(b)
}
// GetMetricValuesForLabel returns value of metric for a given dimension
func GetMetricValuesForLabel(ms Metrics, metricName, label string) map[string]int64 {
samples, found := ms[metricName]
result := make(map[string]int64, len(samples))
if !found {
return result
}
for _, sample := range samples {
count := int64(sample.Value)
dimensionName := string(sample.Metric[model.LabelName(label)])
result[dimensionName] = count
}
return result
}
// ValidateMetrics verifies if every sample of metric has all expected labels
func ValidateMetrics(metrics Metrics, metricName string, expectedLabels ...string) error {
samples, ok := metrics[metricName]
if !ok {
return fmt.Errorf("metric %q was not found in metrics", metricName)
}
for _, sample := range samples {
for _, l := range expectedLabels {
if _, ok := sample.Metric[model.LabelName(l)]; !ok {
return fmt.Errorf("metric %q is missing label %q, sample: %q", metricName, l, sample.String())
}
}
}
return nil
}
// Histogram wraps prometheus histogram DTO (data transfer object)
type Histogram struct {
*dto.Histogram
}
// HistogramVec wraps a slice of Histogram.
// Note that each Histogram must have the same number of buckets.
type HistogramVec []*Histogram
// GetAggregatedSampleCount aggregates the sample count of each inner Histogram.
func (vec HistogramVec) GetAggregatedSampleCount() uint64 {
var count uint64
for _, hist := range vec {
count += hist.GetSampleCount()
}
return count
}
// GetAggregatedSampleSum aggregates the sample sum of each inner Histogram.
func (vec HistogramVec) GetAggregatedSampleSum() float64 {
var sum float64
for _, hist := range vec {
sum += hist.GetSampleSum()
}
return sum
}
// Quantile first aggregates inner buckets of each Histogram, and then
// computes q-th quantile of a cumulative histogram.
func (vec HistogramVec) Quantile(q float64) float64 {
var buckets []bucket
for i, hist := range vec {
for j, bckt := range hist.Bucket {
if i == 0 {
buckets = append(buckets, bucket{
count: float64(bckt.GetCumulativeCount()),
upperBound: bckt.GetUpperBound(),
})
} else {
buckets[j].count += float64(bckt.GetCumulativeCount())
}
}
}
if len(buckets) == 0 || buckets[len(buckets)-1].upperBound != math.Inf(+1) {
// The list of buckets in dto.Histogram doesn't include the final +Inf bucket, so we
// add it here for the rest of the samples.
buckets = append(buckets, bucket{
count: float64(vec.GetAggregatedSampleCount()),
upperBound: math.Inf(+1),
})
}
return bucketQuantile(q, buckets)
}
// Average computes wrapped histograms' average value.
func (vec HistogramVec) Average() float64 {
return vec.GetAggregatedSampleSum() / float64(vec.GetAggregatedSampleCount())
}
// Validate makes sure the wrapped histograms have all necessary fields set and with valid values.
func (vec HistogramVec) Validate() error {
bucketSize := 0
for i, hist := range vec {
if err := hist.Validate(); err != nil {
return err
}
if i == 0 {
bucketSize = len(hist.GetBucket())
} else if bucketSize != len(hist.GetBucket()) {
return fmt.Errorf("found different bucket size: expect %v, but got %v at index %v", bucketSize, len(hist.GetBucket()), i)
}
}
return nil
}
// GetHistogramVecFromGatherer collects a metric, that matches the input labelValue map,
// from a gatherer implementing k8s.io/component-base/metrics.Gatherer interface.
// Used only for testing purposes where we need to gather metrics directly from a running binary (without metrics endpoint).
func GetHistogramVecFromGatherer(gatherer metrics.Gatherer, metricName string, lvMap map[string]string) (HistogramVec, error) {
var metricFamily *dto.MetricFamily
m, err := gatherer.Gather()
if err != nil {
return nil, err
}
metricFamily = findMetricFamily(m, metricName)
if metricFamily == nil {
return nil, fmt.Errorf("metric %q not found", metricName)
}
if len(metricFamily.GetMetric()) == 0 {
return nil, fmt.Errorf("metric %q is empty", metricName)
}
vec := make(HistogramVec, 0)
for _, metric := range metricFamily.GetMetric() {
if LabelsMatch(metric, lvMap) {
if hist := metric.GetHistogram(); hist != nil {
vec = append(vec, &Histogram{hist})
}
}
}
return vec, nil
}
func uint64Ptr(u uint64) *uint64 {
return &u
}
// Bucket of a histogram
type bucket struct {
upperBound float64
count float64
}
func bucketQuantile(q float64, buckets []bucket) float64 {
if q < 0 {
return math.Inf(-1)
}
if q > 1 {
return math.Inf(+1)
}
if len(buckets) < 2 {
return math.NaN()
}
rank := q * buckets[len(buckets)-1].count
b := sort.Search(len(buckets)-1, func(i int) bool { return buckets[i].count >= rank })
if b == 0 {
return buckets[0].upperBound * (rank / buckets[0].count)
}
if b == len(buckets)-1 && math.IsInf(buckets[b].upperBound, 1) {
return buckets[len(buckets)-2].upperBound
}
// linear approximation of b-th bucket
brank := rank - buckets[b-1].count
bSize := buckets[b].upperBound - buckets[b-1].upperBound
bCount := buckets[b].count - buckets[b-1].count
return buckets[b-1].upperBound + bSize*(brank/bCount)
}
// Quantile computes q-th quantile of a cumulative histogram.
// It's expected the histogram is valid (by calling Validate)
func (hist *Histogram) Quantile(q float64) float64 {
var buckets []bucket
for _, bckt := range hist.Bucket {
buckets = append(buckets, bucket{
count: float64(bckt.GetCumulativeCount()),
upperBound: bckt.GetUpperBound(),
})
}
if len(buckets) == 0 || buckets[len(buckets)-1].upperBound != math.Inf(+1) {
// The list of buckets in dto.Histogram doesn't include the final +Inf bucket, so we
// add it here for the rest of the samples.
buckets = append(buckets, bucket{
count: float64(hist.GetSampleCount()),
upperBound: math.Inf(+1),
})
}
return bucketQuantile(q, buckets)
}
// Average computes histogram's average value
func (hist *Histogram) Average() float64 {
return hist.GetSampleSum() / float64(hist.GetSampleCount())
}
// Validate makes sure the wrapped histogram has all necessary fields set and with valid values.
func (hist *Histogram) Validate() error {
if hist.SampleCount == nil || hist.GetSampleCount() == 0 {
return fmt.Errorf("nil or empty histogram SampleCount")
}
if hist.SampleSum == nil || hist.GetSampleSum() == 0 {
return fmt.Errorf("nil or empty histogram SampleSum")
}
for _, bckt := range hist.Bucket {
if bckt == nil {
return fmt.Errorf("empty histogram bucket")
}
if bckt.UpperBound == nil || bckt.GetUpperBound() < 0 {
return fmt.Errorf("nil or negative histogram bucket UpperBound")
}
}
return nil
}
// GetGaugeMetricValue extracts metric value from GaugeMetric
func GetGaugeMetricValue(m metrics.GaugeMetric) (float64, error) {
metricProto := &dto.Metric{}
if err := m.Write(metricProto); err != nil {
return 0, fmt.Errorf("error writing m: %v", err)
}
return metricProto.Gauge.GetValue(), nil
}
// GetCounterMetricValue extracts metric value from CounterMetric
func GetCounterMetricValue(m metrics.CounterMetric) (float64, error) {
metricProto := &dto.Metric{}
if err := m.(metrics.Metric).Write(metricProto); err != nil {
return 0, fmt.Errorf("error writing m: %v", err)
}
return metricProto.Counter.GetValue(), nil
}
// GetHistogramMetricValue extracts sum of all samples from ObserverMetric
func GetHistogramMetricValue(m metrics.ObserverMetric) (float64, error) {
metricProto := &dto.Metric{}
if err := m.(metrics.Metric).Write(metricProto); err != nil {
return 0, fmt.Errorf("error writing m: %v", err)
}
return metricProto.Histogram.GetSampleSum(), nil
}
// GetHistogramMetricCount extracts count of all samples from ObserverMetric
func GetHistogramMetricCount(m metrics.ObserverMetric) (uint64, error) {
metricProto := &dto.Metric{}
if err := m.(metrics.Metric).Write(metricProto); err != nil {
return 0, fmt.Errorf("error writing m: %v", err)
}
return metricProto.Histogram.GetSampleCount(), nil
}
// LabelsMatch returns true if metric has all expected labels otherwise false
func LabelsMatch(metric *dto.Metric, labelFilter map[string]string) bool {
metricLabels := map[string]string{}
for _, labelPair := range metric.Label {
metricLabels[labelPair.GetName()] = labelPair.GetValue()
}
// length comparison then match key to values in the maps
if len(labelFilter) > len(metricLabels) {
return false
}
for labelName, labelValue := range labelFilter {
if value, ok := metricLabels[labelName]; !ok || value != labelValue {
return false
}
}
return true
}
// GetCounterVecFromGatherer collects a counter that matches the given name
// from a gatherer implementing k8s.io/component-base/metrics.Gatherer interface.
// It returns all counter values that had a label with a certain name in a map
// that uses the label value as keys.
//
// Used only for testing purposes where we need to gather metrics directly from a running binary (without metrics endpoint).
func GetCounterValuesFromGatherer(gatherer metrics.Gatherer, metricName string, lvMap map[string]string, labelName string) (map[string]float64, error) {
m, err := gatherer.Gather()
if err != nil {
return nil, err
}
metricFamily := findMetricFamily(m, metricName)
if metricFamily == nil {
return nil, fmt.Errorf("metric %q not found", metricName)
}
if len(metricFamily.GetMetric()) == 0 {
return nil, fmt.Errorf("metric %q is empty", metricName)
}
values := make(map[string]float64)
for _, metric := range metricFamily.GetMetric() {
if LabelsMatch(metric, lvMap) {
if counter := metric.GetCounter(); counter != nil {
for _, labelPair := range metric.Label {
if labelPair.GetName() == labelName {
values[labelPair.GetValue()] = counter.GetValue()
}
}
}
}
}
return values, nil
}
func findMetricFamily(metricFamilies []*dto.MetricFamily, metricName string) *dto.MetricFamily {
for _, mFamily := range metricFamilies {
if mFamily.GetName() == metricName {
return mFamily
}
}
return nil
}

View File

@ -1,151 +0,0 @@
/*
Copyright 2020 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 testutil
import (
"fmt"
"io"
"strings"
"github.com/prometheus/client_golang/prometheus/testutil/promlint"
)
// exceptionMetrics is an exception list of metrics which violates promlint rules.
//
// The original entries come from the existing metrics when we introduce promlint.
// We setup this list for allow and not fail on the current violations.
// Generally speaking, you need to fix the problem for a new metric rather than add it into the list.
var exceptionMetrics = []string{
// k8s.io/apiserver/pkg/server/egressselector
"apiserver_egress_dialer_dial_failure_count", // counter metrics should have "_total" suffix
// k8s.io/apiserver/pkg/server/healthz
"apiserver_request_total", // label names should be written in 'snake_case' not 'camelCase'
// k8s.io/apiserver/pkg/endpoints/filters
"authenticated_user_requests", // counter metrics should have "_total" suffix
"authentication_attempts", // counter metrics should have "_total" suffix
// kube-apiserver
"aggregator_openapi_v2_regeneration_count",
"apiserver_admission_step_admission_duration_seconds_summary",
"apiserver_current_inflight_requests",
"apiserver_longrunning_gauge",
"get_token_count",
"get_token_fail_count",
"ssh_tunnel_open_count",
"ssh_tunnel_open_fail_count",
// kube-controller-manager
"attachdetach_controller_forced_detaches",
"authenticated_user_requests",
"authentication_attempts",
"get_token_count",
"get_token_fail_count",
"node_collector_evictions_number",
}
// A Problem is an issue detected by a Linter.
type Problem promlint.Problem
func (p *Problem) String() string {
return fmt.Sprintf("%s:%s", p.Metric, p.Text)
}
// A Linter is a Prometheus metrics linter. It identifies issues with metric
// names, types, and metadata, and reports them to the caller.
type Linter struct {
promLinter *promlint.Linter
}
// Lint performs a linting pass, returning a slice of Problems indicating any
// issues found in the metrics stream. The slice is sorted by metric name
// and issue description.
func (l *Linter) Lint() ([]Problem, error) {
promProblems, err := l.promLinter.Lint()
if err != nil {
return nil, err
}
// Ignore problems those in exception list
problems := make([]Problem, 0, len(promProblems))
for i := range promProblems {
if !l.shouldIgnore(promProblems[i].Metric) {
problems = append(problems, Problem(promProblems[i]))
}
}
return problems, nil
}
// shouldIgnore returns true if metric in the exception list, otherwise returns false.
func (l *Linter) shouldIgnore(metricName string) bool {
for i := range exceptionMetrics {
if metricName == exceptionMetrics[i] {
return true
}
}
return false
}
// NewPromLinter creates a new Linter that reads an input stream of Prometheus metrics.
// Only the text exposition format is supported.
func NewPromLinter(r io.Reader) *Linter {
return &Linter{
promLinter: promlint.New(r),
}
}
func mergeProblems(problems []Problem) string {
var problemsMsg []string
for index := range problems {
problemsMsg = append(problemsMsg, problems[index].String())
}
return strings.Join(problemsMsg, ",")
}
// shouldIgnore returns true if metric in the exception list, otherwise returns false.
func shouldIgnore(metricName string) bool {
for i := range exceptionMetrics {
if metricName == exceptionMetrics[i] {
return true
}
}
return false
}
// getLintError will ignore the metrics in exception list and converts lint problem to error.
func getLintError(problems []promlint.Problem) error {
var filteredProblems []Problem
for _, problem := range problems {
if shouldIgnore(problem.Metric) {
continue
}
filteredProblems = append(filteredProblems, Problem(problem))
}
if len(filteredProblems) == 0 {
return nil
}
return fmt.Errorf("lint error: %s", mergeProblems(filteredProblems))
}

View File

@ -1,159 +0,0 @@
/*
Copyright 2019 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 testutil
import (
"fmt"
"io"
"github.com/prometheus/client_golang/prometheus/testutil"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
)
type TB interface {
Logf(format string, args ...any)
Errorf(format string, args ...any)
Fatalf(format string, args ...any)
}
// CollectAndCompare registers the provided Collector with a newly created
// pedantic Registry. It then does the same as GatherAndCompare, gathering the
// metrics from the pedantic Registry.
func CollectAndCompare(c metrics.Collector, expected io.Reader, metricNames ...string) error {
lintProblems, err := testutil.CollectAndLint(c, metricNames...)
if err != nil {
return err
}
if err := getLintError(lintProblems); err != nil {
return err
}
return testutil.CollectAndCompare(c, expected, metricNames...)
}
// GatherAndCompare gathers all metrics from the provided Gatherer and compares
// it to an expected output read from the provided Reader in the Prometheus text
// exposition format. If any metricNames are provided, only metrics with those
// names are compared.
func GatherAndCompare(g metrics.Gatherer, expected io.Reader, metricNames ...string) error {
lintProblems, err := testutil.GatherAndLint(g, metricNames...)
if err != nil {
return err
}
if err := getLintError(lintProblems); err != nil {
return err
}
return testutil.GatherAndCompare(g, expected, metricNames...)
}
// CustomCollectAndCompare registers the provided StableCollector with a newly created
// registry. It then does the same as GatherAndCompare, gathering the
// metrics from the pedantic Registry.
func CustomCollectAndCompare(c metrics.StableCollector, expected io.Reader, metricNames ...string) error {
registry := metrics.NewKubeRegistry()
registry.CustomMustRegister(c)
return GatherAndCompare(registry, expected, metricNames...)
}
// ScrapeAndCompare calls a remote exporter's endpoint which is expected to return some metrics in
// plain text format. Then it compares it with the results that the `expected` would return.
// If the `metricNames` is not empty it would filter the comparison only to the given metric names.
func ScrapeAndCompare(url string, expected io.Reader, metricNames ...string) error {
return testutil.ScrapeAndCompare(url, expected, metricNames...)
}
// NewFakeKubeRegistry creates a fake `KubeRegistry` that takes the input version as `build in version`.
// It should only be used in testing scenario especially for the deprecated metrics.
// The input version format should be `major.minor.patch`, e.g. '1.18.0'.
func NewFakeKubeRegistry(ver string) metrics.KubeRegistry {
backup := metrics.BuildVersion
defer func() {
metrics.BuildVersion = backup
}()
metrics.BuildVersion = func() apimachineryversion.Info {
return apimachineryversion.Info{
GitVersion: fmt.Sprintf("v%s-alpha+1.12345", ver),
}
}
return metrics.NewKubeRegistry()
}
func AssertVectorCount(t TB, name string, labelFilter map[string]string, wantCount int) {
metrics, err := legacyregistry.DefaultGatherer.Gather()
if err != nil {
t.Fatalf("Failed to gather metrics: %s", err)
}
counterSum := 0
for _, mf := range metrics {
if mf.GetName() != name {
continue // Ignore other metrics.
}
for _, metric := range mf.GetMetric() {
if !LabelsMatch(metric, labelFilter) {
continue
}
counterSum += int(metric.GetCounter().GetValue())
}
}
if wantCount != counterSum {
t.Errorf("Wanted count %d, got %d for metric %s with labels %#+v", wantCount, counterSum, name, labelFilter)
for _, mf := range metrics {
if mf.GetName() == name {
for _, metric := range mf.GetMetric() {
t.Logf("\tnear match: %s", metric.String())
}
}
}
}
}
func AssertHistogramTotalCount(t TB, name string, labelFilter map[string]string, wantCount int) {
metrics, err := legacyregistry.DefaultGatherer.Gather()
if err != nil {
t.Fatalf("Failed to gather metrics: %s", err)
}
counterSum := 0
for _, mf := range metrics {
if mf.GetName() != name {
continue // Ignore other metrics.
}
for _, metric := range mf.GetMetric() {
if !LabelsMatch(metric, labelFilter) {
continue
}
counterSum += int(metric.GetHistogram().GetSampleCount())
}
}
if wantCount != counterSum {
t.Errorf("Wanted count %d, got %d for metric %s with labels %#+v", wantCount, counterSum, name, labelFilter)
for _, mf := range metrics {
if mf.GetName() == name {
for _, metric := range mf.GetMetric() {
t.Logf("\tnear match: %s\n", metric.String())
}
}
}
}
}

View File

@ -1,8 +0,0 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- sig-instrumentation-approvers
reviewers:
- sig-instrumentation-reviewers
labels:
- sig/instrumentation

View File

@ -1,88 +0,0 @@
/*
Copyright 2021 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 v1
import (
"fmt"
"net/url"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/component-base/featuregate"
)
var (
maxSamplingRatePerMillion = int32(1000000)
)
// ValidateTracingConfiguration validates the tracing configuration
func ValidateTracingConfiguration(traceConfig *TracingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if traceConfig == nil {
return allErrs
}
if traceConfig.SamplingRatePerMillion != nil {
allErrs = append(allErrs, validateSamplingRate(*traceConfig.SamplingRatePerMillion, fldPath.Child("samplingRatePerMillion"))...)
}
if traceConfig.Endpoint != nil {
allErrs = append(allErrs, validateEndpoint(*traceConfig.Endpoint, fldPath.Child("endpoint"))...)
}
return allErrs
}
func validateSamplingRate(rate int32, fldPath *field.Path) field.ErrorList {
errs := field.ErrorList{}
if rate < 0 {
errs = append(errs, field.Invalid(
fldPath, rate,
"sampling rate must be positive",
))
}
if rate > maxSamplingRatePerMillion {
errs = append(errs, field.Invalid(
fldPath, rate,
"sampling rate per million must be less than or equal to one million",
))
}
return errs
}
func validateEndpoint(endpoint string, fldPath *field.Path) field.ErrorList {
errs := field.ErrorList{}
if !strings.Contains(endpoint, "//") {
endpoint = "dns://" + endpoint
}
url, err := url.Parse(endpoint)
if err != nil {
errs = append(errs, field.Invalid(
fldPath, endpoint,
err.Error(),
))
return errs
}
switch url.Scheme {
case "dns":
case "unix":
case "unix-abstract":
default:
errs = append(errs, field.Invalid(
fldPath, endpoint,
fmt.Sprintf("unsupported scheme: %v. Options are none, dns, unix, or unix-abstract. See https://github.com/grpc/grpc/blob/master/doc/naming.md", url.Scheme),
))
}
return errs
}

View File

@ -1,29 +0,0 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:deepcopy-gen=package
// Package v1 contains the configuration API for tracing.
//
// The intention is to only have a single version of this API, potentially with
// new fields added over time in a backwards-compatible manner. Fields for
// alpha or beta features are allowed as long as they are defined so that not
// changing the defaults leaves those features disabled.
//
// The "v1" package name is just a reminder that API compatibility rules apply,
// not an indication of the stability of all features covered by it.
package v1 // import "k8s.io/component-base/tracing/api/v1"

View File

@ -1,32 +0,0 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
// TracingConfiguration provides versioned configuration for OpenTelemetry tracing clients.
type TracingConfiguration struct {
// Endpoint of the collector this component will report traces to.
// The connection is insecure, and does not currently support TLS.
// Recommended is unset, and endpoint is the otlp grpc default, localhost:4317.
// +optional
Endpoint *string `json:"endpoint,omitempty"`
// SamplingRatePerMillion is the number of samples to collect per million spans.
// Recommended is unset. If unset, sampler respects its parent span's sampling
// rate, but otherwise never samples.
// +optional
SamplingRatePerMillion *int32 `json:"samplingRatePerMillion,omitempty"`
}

View File

@ -1,48 +0,0 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TracingConfiguration) DeepCopyInto(out *TracingConfiguration) {
*out = *in
if in.Endpoint != nil {
in, out := &in.Endpoint, &out.Endpoint
*out = new(string)
**out = **in
}
if in.SamplingRatePerMillion != nil {
in, out := &in.SamplingRatePerMillion, &out.SamplingRatePerMillion
*out = new(int32)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TracingConfiguration.
func (in *TracingConfiguration) DeepCopy() *TracingConfiguration {
if in == nil {
return nil
}
out := new(TracingConfiguration)
in.DeepCopyInto(out)
return out
}

View File

@ -1,98 +0,0 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tracing
import (
"context"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
utiltrace "k8s.io/utils/trace"
)
const instrumentationScope = "k8s.io/component-base/tracing"
// Start creates spans using both OpenTelemetry, and the k8s.io/utils/trace package.
// It only creates an OpenTelemetry span if the incoming context already includes a span.
func Start(ctx context.Context, name string, attributes ...attribute.KeyValue) (context.Context, *Span) {
// If the incoming context already includes an OpenTelemetry span, create a child span with the provided name and attributes.
// If the caller is not using OpenTelemetry, or has tracing disabled (e.g. with a component-specific feature flag), this is a noop.
ctx, otelSpan := trace.SpanFromContext(ctx).TracerProvider().Tracer(instrumentationScope).Start(ctx, name, trace.WithAttributes(attributes...))
// If there is already a utiltrace span in the context, use that as our parent span.
utilSpan := utiltrace.FromContext(ctx).Nest(name, attributesToFields(attributes)...)
// Set the trace as active in the context so that subsequent Start calls create nested spans.
return utiltrace.ContextWithTrace(ctx, utilSpan), &Span{
otelSpan: otelSpan,
utilSpan: utilSpan,
}
}
// Span is a component part of a trace. It represents a single named
// and timed operation of a workflow being observed.
// This Span is a combination of an OpenTelemetry and k8s.io/utils/trace span
// to facilitate the migration to OpenTelemetry.
type Span struct {
otelSpan trace.Span
utilSpan *utiltrace.Trace
}
// AddEvent adds a point-in-time event with a name and attributes.
func (s *Span) AddEvent(name string, attributes ...attribute.KeyValue) {
s.otelSpan.AddEvent(name, trace.WithAttributes(attributes...))
if s.utilSpan != nil {
s.utilSpan.Step(name, attributesToFields(attributes)...)
}
}
// End ends the span, and logs if the span duration is greater than the logThreshold.
func (s *Span) End(logThreshold time.Duration) {
s.otelSpan.End()
if s.utilSpan != nil {
s.utilSpan.LogIfLong(logThreshold)
}
}
// RecordError will record err as an exception span event for this span.
// If this span is not being recorded or err is nil then this method does nothing.
func (s *Span) RecordError(err error, attributes ...attribute.KeyValue) {
s.otelSpan.RecordError(err, trace.WithAttributes(attributes...))
}
func attributesToFields(attributes []attribute.KeyValue) []utiltrace.Field {
fields := make([]utiltrace.Field, len(attributes))
for i := range attributes {
attr := attributes[i]
fields[i] = utiltrace.Field{Key: string(attr.Key), Value: attr.Value.AsInterface()}
}
return fields
}
// SpanFromContext returns the *Span from the current context. It is composed of the active
// OpenTelemetry and k8s.io/utils/trace spans.
func SpanFromContext(ctx context.Context) *Span {
return &Span{
otelSpan: trace.SpanFromContext(ctx),
utilSpan: utiltrace.FromContext(ctx),
}
}
// ContextWithSpan returns a context with the Span included in the context.
func ContextWithSpan(ctx context.Context, s *Span) context.Context {
return trace.ContextWithSpan(utiltrace.ContextWithTrace(ctx, s.utilSpan), s.otelSpan)
}

View File

@ -1,134 +0,0 @@
/*
Copyright 2021 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 tracing
import (
"context"
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
oteltrace "go.opentelemetry.io/otel/trace"
noopoteltrace "go.opentelemetry.io/otel/trace/noop"
"k8s.io/client-go/transport"
"k8s.io/component-base/tracing/api/v1"
)
// TracerProvider is an OpenTelemetry TracerProvider which can be shut down
type TracerProvider interface {
oteltrace.TracerProvider
Shutdown(context.Context) error
}
type noopTracerProvider struct {
oteltrace.TracerProvider
}
func (n *noopTracerProvider) Shutdown(context.Context) error {
return nil
}
func NewNoopTracerProvider() TracerProvider {
return &noopTracerProvider{TracerProvider: noopoteltrace.NewTracerProvider()}
}
// NewProvider creates a TracerProvider in a component, and enforces recommended tracing behavior
func NewProvider(ctx context.Context,
tracingConfig *v1.TracingConfiguration,
addedOpts []otlptracegrpc.Option,
resourceOpts []resource.Option,
) (TracerProvider, error) {
if tracingConfig == nil {
return NewNoopTracerProvider(), nil
}
opts := append([]otlptracegrpc.Option{}, addedOpts...)
if tracingConfig.Endpoint != nil {
opts = append(opts, otlptracegrpc.WithEndpoint(*tracingConfig.Endpoint))
}
opts = append(opts, otlptracegrpc.WithInsecure())
exporter, err := otlptracegrpc.New(ctx, opts...)
if err != nil {
return nil, err
}
res, err := resource.New(ctx, resourceOpts...)
if err != nil {
return nil, err
}
// sampler respects parent span's sampling rate or
// otherwise never samples.
sampler := sdktrace.NeverSample()
// Or, emit spans for a fraction of transactions
if tracingConfig.SamplingRatePerMillion != nil && *tracingConfig.SamplingRatePerMillion > 0 {
sampler = sdktrace.TraceIDRatioBased(float64(*tracingConfig.SamplingRatePerMillion) / float64(1000000))
}
// batch span processor to aggregate spans before export.
bsp := sdktrace.NewBatchSpanProcessor(exporter)
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.ParentBased(sampler)),
sdktrace.WithSpanProcessor(bsp),
sdktrace.WithResource(res),
)
return tp, nil
}
// WithTracing adds tracing to requests if the incoming request is sampled
func WithTracing(handler http.Handler, tp oteltrace.TracerProvider, spanName string) http.Handler {
opts := []otelhttp.Option{
otelhttp.WithPropagators(Propagators()),
otelhttp.WithTracerProvider(tp),
}
wrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Add the http.target attribute to the otelhttp span
// Workaround for https://github.com/open-telemetry/opentelemetry-go-contrib/issues/3743
if r.URL != nil {
oteltrace.SpanFromContext(r.Context()).SetAttributes(semconv.HTTPTarget(r.URL.RequestURI()))
}
handler.ServeHTTP(w, r)
})
// With Noop TracerProvider, the otelhttp still handles context propagation.
// See https://github.com/open-telemetry/opentelemetry-go/tree/main/example/passthrough
return otelhttp.NewHandler(wrappedHandler, spanName, opts...)
}
// WrapperFor can be used to add tracing to a *rest.Config.
// Example usage:
// tp := NewProvider(...)
// config, _ := rest.InClusterConfig()
// config.Wrap(WrapperFor(tp))
// kubeclient, _ := clientset.NewForConfig(config)
func WrapperFor(tp oteltrace.TracerProvider) transport.WrapperFunc {
return func(rt http.RoundTripper) http.RoundTripper {
opts := []otelhttp.Option{
otelhttp.WithPropagators(Propagators()),
otelhttp.WithTracerProvider(tp),
}
// With Noop TracerProvider, the otelhttp still handles context propagation.
// See https://github.com/open-telemetry/opentelemetry-go/tree/main/example/passthrough
return otelhttp.NewTransport(rt, opts...)
}
}
// Propagators returns the recommended set of propagators.
func Propagators() propagation.TextMapPropagator {
return propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})
}

View File

@ -1,52 +0,0 @@
/*
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 flagz
import (
"github.com/spf13/pflag"
cliflag "k8s.io/component-base/cli/flag"
)
type Reader interface {
GetFlagz() map[string]string
}
// NamedFlagSetsGetter implements Reader for cliflag.NamedFlagSets
type NamedFlagSetsReader struct {
FlagSets cliflag.NamedFlagSets
}
func (n NamedFlagSetsReader) GetFlagz() map[string]string {
return convertNamedFlagSetToFlags(&n.FlagSets)
}
func convertNamedFlagSetToFlags(flagSets *cliflag.NamedFlagSets) map[string]string {
flags := make(map[string]string)
for _, fs := range flagSets.FlagSets {
fs.VisitAll(func(flag *pflag.Flag) {
if flag.Value != nil {
value := flag.Value.String()
if set, ok := flag.Annotations["classified"]; ok && len(set) > 0 {
value = "CLASSIFIED"
}
flags[flag.Name] = value
}
})
}
return flags
}

View File

@ -1,126 +0,0 @@
/*
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 flagz
import (
"bytes"
"fmt"
"io"
"math/rand"
"net/http"
"sort"
"strings"
"sync"
"github.com/munnerz/goautoneg"
"k8s.io/klog/v2"
)
const (
flagzHeaderFmt = `
%s flags
Warning: This endpoint is not meant to be machine parseable, has no formatting compatibility guarantees and is for debugging purposes only.
`
)
var (
flagzSeparators = []string{":", ": ", "=", " "}
errUnsupportedMediaType = fmt.Errorf("media type not acceptable, must be: text/plain")
)
type registry struct {
response bytes.Buffer
once sync.Once
}
type mux interface {
Handle(path string, handler http.Handler)
}
func Install(m mux, componentName string, flagReader Reader) {
var reg registry
reg.installHandler(m, componentName, flagReader)
}
func (reg *registry) installHandler(m mux, componentName string, flagReader Reader) {
m.Handle("/flagz", reg.handleFlags(componentName, flagReader))
}
func (reg *registry) handleFlags(componentName string, flagReader Reader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !acceptableMediaType(r) {
http.Error(w, errUnsupportedMediaType.Error(), http.StatusNotAcceptable)
return
}
reg.once.Do(func() {
fmt.Fprintf(&reg.response, flagzHeaderFmt, componentName)
if flagReader == nil {
klog.Error("received nil flagReader")
return
}
randomIndex := rand.Intn(len(flagzSeparators))
separator := flagzSeparators[randomIndex]
// Randomize the delimiter for printing to prevent scraping of the response.
printSortedFlags(&reg.response, flagReader.GetFlagz(), separator)
})
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, err := w.Write(reg.response.Bytes())
if err != nil {
klog.Errorf("error writing response: %v", err)
http.Error(w, "error writing response", http.StatusInternalServerError)
}
}
}
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 printSortedFlags(w io.Writer, flags map[string]string, separator string) {
var sortedKeys []string
for key := range flags {
sortedKeys = append(sortedKeys, key)
}
sort.Strings(sortedKeys)
for _, key := range sortedKeys {
fmt.Fprintf(w, "%s%s%s\n", key, separator, flags[key])
}
}