mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-08 19:47:56 +00:00
Merge pull request #117108 from pohly/test-integration-race-detection-component-base-logs
component-base/logs: improve handling of re-applying a configuration
This commit is contained in:
commit
e5efa0a5ee
@ -42,6 +42,7 @@ import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/util/cert"
|
||||
logsapi "k8s.io/component-base/logs/api/v1"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kube-aggregator/pkg/apiserver"
|
||||
|
||||
@ -51,6 +52,13 @@ import (
|
||||
testutil "k8s.io/kubernetes/test/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// If instantiated more than once or together with other servers, the
|
||||
// servers would try to modify the global logging state. This must get
|
||||
// ignored during testing.
|
||||
logsapi.ReapplyHandling = logsapi.ReapplyHandlingIgnoreUnchanged
|
||||
}
|
||||
|
||||
// This key is for testing purposes only and is not considered secure.
|
||||
const ecdsaPrivateKey = `-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIEZmTmUhuanLjPA2CLquXivuwBDHTt5XYwgIr/kA1LtRoAoGCCqGSM49
|
||||
|
@ -28,14 +28,21 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
logsapi "k8s.io/component-base/logs/api/v1"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kube-controller-manager/app"
|
||||
kubecontrollerconfig "k8s.io/kubernetes/cmd/kube-controller-manager/app/config"
|
||||
"k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
|
||||
"k8s.io/kubernetes/cmd/kube-controller-manager/names"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// If instantiated more than once or together with other servers, the
|
||||
// servers would try to modify the global logging state. This must get
|
||||
// ignored during testing.
|
||||
logsapi.ReapplyHandling = logsapi.ReapplyHandlingIgnoreUnchanged
|
||||
}
|
||||
|
||||
// TearDownFunc is to be called to tear down a test server.
|
||||
type TearDownFunc func()
|
||||
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/component-base/configz"
|
||||
logsapi "k8s.io/component-base/logs/api/v1"
|
||||
"k8s.io/kubernetes/cmd/kube-scheduler/app"
|
||||
kubeschedulerconfig "k8s.io/kubernetes/cmd/kube-scheduler/app/config"
|
||||
"k8s.io/kubernetes/cmd/kube-scheduler/app/options"
|
||||
@ -36,6 +37,13 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// If instantiated more than once or together with other servers, the
|
||||
// servers would try to modify the global logging state. This must get
|
||||
// ignored during testing.
|
||||
logsapi.ReapplyHandling = logsapi.ReapplyHandlingIgnoreUnchanged
|
||||
}
|
||||
|
||||
// TearDownFunc is to be called to tear down a test server.
|
||||
type TearDownFunc func()
|
||||
|
||||
|
@ -34,9 +34,17 @@ import (
|
||||
"k8s.io/apiserver/pkg/storage/storagebackend"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
logsapi "k8s.io/component-base/logs/api/v1"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// If instantiated more than once or together with other servers, the
|
||||
// servers would try to modify the global logging state. This must get
|
||||
// ignored during testing.
|
||||
logsapi.ReapplyHandling = logsapi.ReapplyHandlingIgnoreUnchanged
|
||||
}
|
||||
|
||||
// TearDownFunc is to be called to tear down a test server.
|
||||
type TearDownFunc func()
|
||||
|
||||
|
@ -34,9 +34,26 @@ import (
|
||||
"k8s.io/cloud-provider/names"
|
||||
"k8s.io/cloud-provider/options"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
logsapi "k8s.io/component-base/logs/api/v1"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// If instantiated more than once or together with other servers, the
|
||||
// servers would try to modify the global logging state. This must get
|
||||
// ignored during testing.
|
||||
logsapi.ReapplyHandling = logsapi.ReapplyHandlingIgnoreUnchanged
|
||||
|
||||
// Because the test server gets started after other goroutines are
|
||||
// running already, we also have to initialize logging here when
|
||||
// those goroutines are not running yet. This works because the
|
||||
// test server uses the default config.
|
||||
config := logsapi.NewLoggingConfiguration()
|
||||
if err := logsapi.ValidateAndApply(config, nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TearDownFunc is to be called to tear down a test server.
|
||||
type TearDownFunc func()
|
||||
|
||||
|
@ -17,14 +17,17 @@ 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"
|
||||
@ -57,6 +60,24 @@ func NewLoggingConfiguration() *LoggingConfiguration {
|
||||
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
|
||||
@ -64,6 +85,10 @@ func NewLoggingConfiguration() *LoggingConfiguration {
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
@ -71,6 +96,10 @@ func ValidateAndApply(c *LoggingConfiguration, featureGate featuregate.FeatureGa
|
||||
// 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)
|
||||
}
|
||||
@ -183,10 +212,30 @@ func featureEnabled(featureGate featuregate.FeatureGate, feature featuregate.Fea
|
||||
}
|
||||
|
||||
func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate) error {
|
||||
contextualLoggingEnabled := contextualLoggingDefault
|
||||
if featureGate != nil {
|
||||
contextualLoggingEnabled = featureGate.Enabled(ContextualLogging)
|
||||
p := ¶meters{
|
||||
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)
|
||||
@ -205,7 +254,7 @@ func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate feature
|
||||
defer setverbositylevel.Mutex.Unlock()
|
||||
setverbositylevel.Callbacks = append(setverbositylevel.Callbacks, control.SetVerbosityLevel)
|
||||
}
|
||||
klog.SetLoggerWithOptions(log, klog.ContextualLogger(contextualLoggingEnabled), klog.FlushLogger(control.Flush))
|
||||
klog.SetLoggerWithOptions(log, klog.ContextualLogger(p.ContextualLoggingEnabled), klog.FlushLogger(control.Flush))
|
||||
}
|
||||
if err := loggingFlags.Lookup("v").Value.Set(VerbosityLevelPflag(&c.Verbosity).String()); err != nil {
|
||||
return fmt.Errorf("internal error while setting klog verbosity: %v", err)
|
||||
@ -214,7 +263,40 @@ func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate feature
|
||||
return fmt.Errorf("internal error while setting klog vmodule: %v", err)
|
||||
}
|
||||
klog.StartFlushDaemon(c.FlushFrequency)
|
||||
klog.EnableContextualLogging(contextualLoggingEnabled)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,34 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func TestReapply(t *testing.T) {
|
||||
oldReapplyHandling := ReapplyHandling
|
||||
defer func() {
|
||||
ReapplyHandling = oldReapplyHandling
|
||||
if err := ResetForTest(nil /* feature gates */); err != nil {
|
||||
t.Errorf("Unexpected error resetting the logging configuration: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
newOptions := NewLoggingConfiguration()
|
||||
if err := ValidateAndApply(newOptions, nil); err != nil {
|
||||
t.Errorf("unexpected error for first ValidateAndApply: %v", err)
|
||||
}
|
||||
ReapplyHandling = ReapplyHandlingError
|
||||
if err := ValidateAndApply(newOptions, nil); err == nil {
|
||||
t.Error("did not get expected error for second ValidateAndApply")
|
||||
}
|
||||
ReapplyHandling = ReapplyHandlingIgnoreUnchanged
|
||||
if err := ValidateAndApply(newOptions, nil); err != nil {
|
||||
t.Errorf("unexpected error for third ValidateAndApply: %v", err)
|
||||
}
|
||||
modifiedOptions := newOptions.DeepCopy()
|
||||
modifiedOptions.Verbosity = 100
|
||||
if err := ValidateAndApply(modifiedOptions, nil); err == nil {
|
||||
t.Errorf("unexpected success for forth ValidateAndApply, should have complained about modified config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
newOptions := NewLoggingConfiguration()
|
||||
testcases := []struct {
|
||||
@ -75,6 +103,11 @@ func TestOptions(t *testing.T) {
|
||||
if !assert.Equal(t, tc.want, c) {
|
||||
t.Errorf("Wrong Validate() result for %q. expect %v, got %v", tc.name, tc.want, c)
|
||||
}
|
||||
defer func() {
|
||||
if err := ResetForTest(nil /* feature gates */); err != nil {
|
||||
t.Errorf("Unexpected error resetting the logging configuration: %v", err)
|
||||
}
|
||||
}()
|
||||
errs := ValidateAndApply(c, nil /* We don't care about feature gates here. */)
|
||||
defer klog.StopFlushDaemon()
|
||||
if !assert.ElementsMatch(t, tc.errs, errs) {
|
||||
@ -182,6 +215,11 @@ func testContextualLogging(t *testing.T, enabled bool) {
|
||||
AddFeatureGates(featureGate)
|
||||
err = featureGate.SetFromMap(map[string]bool{string(ContextualLogging): enabled})
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := ResetForTest(nil /* feature gates */); err != nil {
|
||||
t.Errorf("Unexpected error resetting the logging configuration: %v", err)
|
||||
}
|
||||
}()
|
||||
err = ValidateAndApply(c, featureGate)
|
||||
require.NoError(t, err)
|
||||
defer klog.StopFlushDaemon()
|
||||
|
@ -155,8 +155,12 @@ func TestJSONFormatRegister(t *testing.T) {
|
||||
err := mutable.SetFromMap(map[string]bool{string(logsapi.ContextualLogging): tc.contextualLogging})
|
||||
require.NoError(t, err)
|
||||
featureGate = mutable
|
||||
defer func() {
|
||||
if err := logsapi.ResetForTest(featureGate); err != nil {
|
||||
t.Errorf("Unexpected error while resetting the logging configuration: %v", err)
|
||||
}
|
||||
}()
|
||||
errs := logsapi.ValidateAndApply(c, featureGate)
|
||||
defer klog.ClearLogger()
|
||||
if !assert.ElementsMatch(t, tc.errs, errs) {
|
||||
t.Errorf("Wrong Validate() result for %q.\n expect:\t%+v\n got:\t%+v", tc.name, tc.errs, errs)
|
||||
|
||||
|
@ -81,6 +81,11 @@ func TestGlogSetter(t *testing.T) {
|
||||
}
|
||||
os.Stderr = newStderr
|
||||
|
||||
defer func() {
|
||||
if err := logsapi.ResetForTest(nil /* feature gates */); err != nil {
|
||||
t.Errorf("Unexpected error resetting the logging configuration: %v", err)
|
||||
}
|
||||
}()
|
||||
tc.init(t)
|
||||
|
||||
// First write with default verbosity level of 0 -> no output.
|
||||
|
@ -194,6 +194,11 @@ func TestData(t *testing.T) {
|
||||
InfoStream: &buffer,
|
||||
}
|
||||
klog.SetOutput(&buffer)
|
||||
defer func() {
|
||||
if err := logsapi.ResetForTest(nil); err != nil {
|
||||
t.Errorf("Unexpected error resetting the logging configuration: %v", err)
|
||||
}
|
||||
}()
|
||||
if err := logsapi.ValidateAndApplyWithOptions(c, &o, nil); err != nil {
|
||||
t.Fatalf("Unexpected error configuring logging: %v", err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user