diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index c8640789ba0..8482c60db13 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -334,6 +334,8 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, nodeStatusMaxImages int32, seccompDefault bool, ) (*Kubelet, error) { + logger := klog.TODO() + if rootDirectory == "" { return nil, fmt.Errorf("invalid root directory %q", rootDirectory) } @@ -819,6 +821,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, // setup node shutdown manager shutdownManager, shutdownAdmitHandler := nodeshutdown.NewManager(&nodeshutdown.Config{ + Logger: logger, ProbeManager: klet.probeManager, Recorder: kubeDeps.Recorder, NodeRef: nodeRef, diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index eb8269be2c2..82f0564da0d 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -44,6 +44,7 @@ import ( "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/flowcontrol" + "k8s.io/klog/v2/ktesting" cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" "k8s.io/kubernetes/pkg/kubelet/cm" "k8s.io/kubernetes/pkg/kubelet/config" @@ -149,6 +150,8 @@ func newTestKubeletWithImageList( imageList []kubecontainer.Image, controllerAttachDetachEnabled bool, initFakeVolumePlugin bool) *TestKubelet { + logger, _ := ktesting.NewTestContext(t) + fakeRuntime := &containertest.FakeRuntime{ ImageList: imageList, // Set ready conditions by default. @@ -321,6 +324,7 @@ func newTestKubeletWithImageList( // setup shutdown manager shutdownManager, shutdownAdmitHandler := nodeshutdown.NewManager(&nodeshutdown.Config{ + Logger: logger, ProbeManager: kubelet.probeManager, Recorder: fakeRecorder, NodeRef: nodeRef, diff --git a/pkg/kubelet/nodeshutdown/nodeshutdown_manager.go b/pkg/kubelet/nodeshutdown/nodeshutdown_manager.go index 00406299be1..aac590f967d 100644 --- a/pkg/kubelet/nodeshutdown/nodeshutdown_manager.go +++ b/pkg/kubelet/nodeshutdown/nodeshutdown_manager.go @@ -21,6 +21,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" "k8s.io/kubernetes/pkg/kubelet/eviction" "k8s.io/kubernetes/pkg/kubelet/lifecycle" @@ -37,6 +38,7 @@ type Manager interface { // Config represents Manager configuration type Config struct { + Logger klog.Logger ProbeManager prober.Manager Recorder record.EventRecorder NodeRef *v1.ObjectReference diff --git a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go index a9733b70210..fb34f1bfb10 100644 --- a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go +++ b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go @@ -67,6 +67,7 @@ type dbusInhibiter interface { // managerImpl has functions that can be used to interact with the Node Shutdown Manager. type managerImpl struct { + logger klog.Logger recorder record.EventRecorder nodeRef *v1.ObjectReference probeManager prober.Manager @@ -118,6 +119,7 @@ func NewManager(conf *Config) (Manager, lifecycle.PodAdmitHandler) { conf.Clock = clock.RealClock{} } manager := &managerImpl{ + logger: conf.Logger, probeManager: conf.ProbeManager, recorder: conf.Recorder, nodeRef: conf.NodeRef, @@ -131,7 +133,7 @@ func NewManager(conf *Config) (Manager, lifecycle.PodAdmitHandler) { Path: filepath.Join(conf.StateDirectory, localStorageStateFile), }, } - klog.InfoS("Creating node shutdown manager", + manager.logger.Info("Creating node shutdown manager", "shutdownGracePeriodRequested", conf.ShutdownGracePeriodRequested, "shutdownGracePeriodCriticalPods", conf.ShutdownGracePeriodCriticalPods, "shutdownGracePeriodByPodPriority", shutdownGracePeriodByPodPriority, @@ -159,7 +161,7 @@ func (m *managerImpl) setMetrics() { sta := state{} err := m.storage.Load(&sta) if err != nil { - klog.ErrorS(err, "Failed to load graceful shutdown state") + m.logger.Error(err, "Failed to load graceful shutdown state") } else { if !sta.StartTime.IsZero() { metrics.GracefulShutdownStartTime.Set(timestamp(sta.StartTime)) @@ -184,10 +186,10 @@ func (m *managerImpl) Start() error { } time.Sleep(dbusReconnectPeriod) - klog.V(1).InfoS("Restarting watch for node shutdown events") + m.logger.V(1).Info("Restarting watch for node shutdown events") stop, err = m.start() if err != nil { - klog.ErrorS(err, "Unable to watch the node for shutdown events") + m.logger.Error(err, "Unable to watch the node for shutdown events") } } }() @@ -255,11 +257,11 @@ func (m *managerImpl) start() (chan struct{}, error) { select { case isShuttingDown, ok := <-events: if !ok { - klog.ErrorS(err, "Ended to watching the node for shutdown events") + m.logger.Error(err, "Ended to watching the node for shutdown events") close(stop) return } - klog.V(1).InfoS("Shutdown manager detected new shutdown event, isNodeShuttingDownNow", "event", isShuttingDown) + m.logger.V(1).Info("Shutdown manager detected new shutdown event, isNodeShuttingDownNow", "event", isShuttingDown) var shutdownType string if isShuttingDown { @@ -267,7 +269,7 @@ func (m *managerImpl) start() (chan struct{}, error) { } else { shutdownType = "cancelled" } - klog.V(1).InfoS("Shutdown manager detected new shutdown event", "event", shutdownType) + m.logger.V(1).Info("Shutdown manager detected new shutdown event", "event", shutdownType) if isShuttingDown { m.recorder.Event(m.nodeRef, v1.EventTypeNormal, kubeletevents.NodeShutdown, "Shutdown manager detected shutdown event") } else { @@ -316,12 +318,12 @@ func (m *managerImpl) ShutdownStatus() error { } func (m *managerImpl) processShutdownEvent() error { - klog.V(1).InfoS("Shutdown manager processing shutdown event") + m.logger.V(1).Info("Shutdown manager processing shutdown event") activePods := m.getPods() defer func() { m.dbusCon.ReleaseInhibitLock(m.inhibitLock) - klog.V(1).InfoS("Shutdown manager completed processing shutdown event, node will shutdown shortly") + m.logger.V(1).Info("Shutdown manager completed processing shutdown event, node will shutdown shortly") }() if m.enableMetrics && m.storage != nil { @@ -330,7 +332,7 @@ func (m *managerImpl) processShutdownEvent() error { StartTime: startTime, }) if err != nil { - klog.ErrorS(err, "Failed to store graceful shutdown state") + m.logger.Error(err, "Failed to store graceful shutdown state") } metrics.GracefulShutdownStartTime.Set(timestamp(startTime)) metrics.GracefulShutdownEndTime.Set(0) @@ -342,7 +344,7 @@ func (m *managerImpl) processShutdownEvent() error { EndTime: endTime, }) if err != nil { - klog.ErrorS(err, "Failed to store graceful shutdown state") + m.logger.Error(err, "Failed to store graceful shutdown state") } metrics.GracefulShutdownStartTime.Set(timestamp(endTime)) }() @@ -369,7 +371,7 @@ func (m *managerImpl) processShutdownEvent() error { gracePeriodOverride = *pod.Spec.TerminationGracePeriodSeconds } - klog.V(1).InfoS("Shutdown manager killing pod with gracePeriod", "pod", klog.KObj(pod), "gracePeriod", gracePeriodOverride) + m.logger.V(1).Info("Shutdown manager killing pod with gracePeriod", "pod", klog.KObj(pod), "gracePeriod", gracePeriodOverride) if err := m.killPodFunc(pod, false, &gracePeriodOverride, func(status *v1.PodStatus) { // set the pod status to failed (unless it was already in a successful terminal phase) @@ -379,9 +381,9 @@ func (m *managerImpl) processShutdownEvent() error { status.Message = nodeShutdownMessage status.Reason = nodeShutdownReason }); err != nil { - klog.V(1).InfoS("Shutdown manager failed killing pod", "pod", klog.KObj(pod), "err", err) + m.logger.V(1).Info("Shutdown manager failed killing pod", "pod", klog.KObj(pod), "err", err) } else { - klog.V(1).InfoS("Shutdown manager finished killing pod", "pod", klog.KObj(pod)) + m.logger.V(1).Info("Shutdown manager finished killing pod", "pod", klog.KObj(pod)) } }(pod, group) } @@ -399,7 +401,7 @@ func (m *managerImpl) processShutdownEvent() error { case <-doneCh: timer.Stop() case <-timer.C(): - klog.V(1).InfoS("Shutdown manager pod killing time out", "gracePeriod", group.ShutdownGracePeriodSeconds, "priority", group.Priority) + m.logger.V(1).Info("Shutdown manager pod killing time out", "gracePeriod", group.ShutdownGracePeriodSeconds, "priority", group.Priority) } } diff --git a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux_test.go b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux_test.go index e682c0963f0..c8b12b64d4b 100644 --- a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux_test.go +++ b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux_test.go @@ -36,6 +36,8 @@ import ( "k8s.io/client-go/tools/record" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" + _ "k8s.io/klog/v2/ktesting/init" // activate ktesting command line flags "k8s.io/kubernetes/pkg/apis/scheduling" pkgfeatures "k8s.io/kubernetes/pkg/features" kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" @@ -211,6 +213,8 @@ func TestManager(t *testing.T) { for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + activePodsFunc := func() []*v1.Pod { return tc.activePods } @@ -243,6 +247,7 @@ func TestManager(t *testing.T) { fakeRecorder := &record.FakeRecorder{} nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} manager, _ := NewManager(&Config{ + Logger: logger, ProbeManager: proberManager, Recorder: fakeRecorder, NodeRef: nodeRef, @@ -326,6 +331,7 @@ func TestFeatureEnabled(t *testing.T) { } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) activePodsFunc := func() []*v1.Pod { return nil } @@ -339,6 +345,7 @@ func TestFeatureEnabled(t *testing.T) { nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} manager, _ := NewManager(&Config{ + Logger: logger, ProbeManager: proberManager, Recorder: fakeRecorder, NodeRef: nodeRef, @@ -355,6 +362,7 @@ func TestFeatureEnabled(t *testing.T) { } func TestRestart(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) systemDbusTmp := systemDbus defer func() { systemDbus = systemDbusTmp @@ -393,6 +401,7 @@ func TestRestart(t *testing.T) { fakeRecorder := &record.FakeRecorder{} nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} manager, _ := NewManager(&Config{ + Logger: logger, ProbeManager: proberManager, Recorder: fakeRecorder, NodeRef: nodeRef, @@ -706,6 +715,7 @@ func Test_managerImpl_processShutdownEvent(t *testing.T) { klog.LogToStderr(false) m := &managerImpl{ + logger: klog.TODO(), // This test will be updated in a separate commit. recorder: tt.fields.recorder, nodeRef: tt.fields.nodeRef, probeManager: tt.fields.probeManager, diff --git a/vendor/k8s.io/klog/v2/internal/verbosity/verbosity.go b/vendor/k8s.io/klog/v2/internal/verbosity/verbosity.go new file mode 100644 index 00000000000..309cdfaa9d3 --- /dev/null +++ b/vendor/k8s.io/klog/v2/internal/verbosity/verbosity.go @@ -0,0 +1,305 @@ +/* +Copyright 2013 Google Inc. All Rights Reserved. +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 verbosity + +import ( + "bytes" + "errors" + "flag" + "fmt" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" +) + +// New returns a struct that implements -v and -vmodule support. Changing and +// checking these settings is thread-safe, with all concurrency issues handled +// internally. +func New() *VState { + vs := new(VState) + + // The two fields must have a pointer to the overal struct for their + // implementation of Set. + vs.vmodule.vs = vs + vs.verbosity.vs = vs + + return vs +} + +// Value is an extension that makes it possible to use the values in pflag. +type Value interface { + flag.Value + Type() string +} + +func (vs *VState) V() Value { + return &vs.verbosity +} + +func (vs *VState) VModule() Value { + return &vs.vmodule +} + +// VState contains settings and state. Some of its fields can be accessed +// through atomic read/writes, in other cases a mutex must be held. +type VState struct { + mu sync.Mutex + + // These flags are modified only under lock, although verbosity may be fetched + // safely using atomic.LoadInt32. + vmodule moduleSpec // The state of the -vmodule flag. + verbosity levelSpec // V logging level, the value of the -v flag/ + + // pcs is used in V to avoid an allocation when computing the caller's PC. + pcs [1]uintptr + // vmap is a cache of the V Level for each V() call site, identified by PC. + // It is wiped whenever the vmodule flag changes state. + vmap map[uintptr]Level + // filterLength stores the length of the vmodule filter chain. If greater + // than zero, it means vmodule is enabled. It may be read safely + // using sync.LoadInt32, but is only modified under mu. + filterLength int32 +} + +// Level must be an int32 to support atomic read/writes. +type Level int32 + +type levelSpec struct { + vs *VState + l Level +} + +// get returns the value of the level. +func (l *levelSpec) get() Level { + return Level(atomic.LoadInt32((*int32)(&l.l))) +} + +// set sets the value of the level. +func (l *levelSpec) set(val Level) { + atomic.StoreInt32((*int32)(&l.l), int32(val)) +} + +// String is part of the flag.Value interface. +func (l *levelSpec) String() string { + return strconv.FormatInt(int64(l.l), 10) +} + +// Get is part of the flag.Getter interface. It returns the +// verbosity level as int32. +func (l *levelSpec) Get() interface{} { + return int32(l.l) +} + +// Type is part of pflag.Value. +func (l *levelSpec) Type() string { + return "Level" +} + +// Set is part of the flag.Value interface. +func (l *levelSpec) Set(value string) error { + v, err := strconv.ParseInt(value, 10, 32) + if err != nil { + return err + } + l.vs.mu.Lock() + defer l.vs.mu.Unlock() + l.vs.set(Level(v), l.vs.vmodule.filter, false) + return nil +} + +// moduleSpec represents the setting of the -vmodule flag. +type moduleSpec struct { + vs *VState + filter []modulePat +} + +// modulePat contains a filter for the -vmodule flag. +// It holds a verbosity level and a file pattern to match. +type modulePat struct { + pattern string + literal bool // The pattern is a literal string + level Level +} + +// match reports whether the file matches the pattern. It uses a string +// comparison if the pattern contains no metacharacters. +func (m *modulePat) match(file string) bool { + if m.literal { + return file == m.pattern + } + match, _ := filepath.Match(m.pattern, file) + return match +} + +func (m *moduleSpec) String() string { + // Lock because the type is not atomic. TODO: clean this up. + // Empty instances don't have and don't need a lock (can + // happen when flag uses introspection). + if m.vs != nil { + m.vs.mu.Lock() + defer m.vs.mu.Unlock() + } + var b bytes.Buffer + for i, f := range m.filter { + if i > 0 { + b.WriteRune(',') + } + fmt.Fprintf(&b, "%s=%d", f.pattern, f.level) + } + return b.String() +} + +// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the +// struct is not exported. +func (m *moduleSpec) Get() interface{} { + return nil +} + +// Type is part of pflag.Value +func (m *moduleSpec) Type() string { + return "pattern=N,..." +} + +var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N") + +// Set will sets module value +// Syntax: -vmodule=recordio=2,file=1,gfs*=3 +func (m *moduleSpec) Set(value string) error { + var filter []modulePat + 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 errVmoduleSyntax + } + pattern := patLev[0] + v, err := strconv.ParseInt(patLev[1], 10, 32) + if err != nil { + return errors.New("syntax error: expect comma-separated list of filename=N") + } + if v < 0 { + return errors.New("negative value for vmodule level") + } + if v == 0 { + continue // Ignore. It's harmless but no point in paying the overhead. + } + // TODO: check syntax of filter? + filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)}) + } + m.vs.mu.Lock() + defer m.vs.mu.Unlock() + m.vs.set(m.vs.verbosity.l, filter, true) + return nil +} + +// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters +// that require filepath.Match to be called to match the pattern. +func isLiteral(pattern string) bool { + return !strings.ContainsAny(pattern, `\*?[]`) +} + +// set sets a consistent state for V logging. +// The mutex must be held. +func (vs *VState) set(l Level, filter []modulePat, setFilter bool) { + // Turn verbosity off so V will not fire while we are in transition. + vs.verbosity.set(0) + // Ditto for filter length. + atomic.StoreInt32(&vs.filterLength, 0) + + // Set the new filters and wipe the pc->Level map if the filter has changed. + if setFilter { + vs.vmodule.filter = filter + vs.vmap = make(map[uintptr]Level) + } + + // Things are consistent now, so enable filtering and verbosity. + // They are enabled in order opposite to that in V. + atomic.StoreInt32(&vs.filterLength, int32(len(filter))) + vs.verbosity.set(l) +} + +// Enabled checks whether logging is enabled at the given level. This must be +// called with depth=0 when the caller of enabled will do the logging and +// higher values when more stack levels need to be skipped. +// +// The mutex will be locked only if needed. +func (vs *VState) Enabled(level Level, depth int) bool { + // This function tries hard to be cheap unless there's work to do. + // The fast path is two atomic loads and compares. + + // Here is a cheap but safe test to see if V logging is enabled globally. + if vs.verbosity.get() >= level { + return true + } + + // It's off globally but vmodule may still be set. + // Here is another cheap but safe test to see if vmodule is enabled. + if atomic.LoadInt32(&vs.filterLength) > 0 { + // Now we need a proper lock to use the logging structure. The pcs field + // is shared so we must lock before accessing it. This is fairly expensive, + // but if V logging is enabled we're slow anyway. + vs.mu.Lock() + defer vs.mu.Unlock() + if runtime.Callers(depth+2, vs.pcs[:]) == 0 { + return false + } + // runtime.Callers returns "return PCs", but we want + // to look up the symbolic information for the call, + // so subtract 1 from the PC. runtime.CallersFrames + // would be cleaner, but allocates. + pc := vs.pcs[0] - 1 + v, ok := vs.vmap[pc] + if !ok { + v = vs.setV(pc) + } + return v >= level + } + return false +} + +// setV computes and remembers the V level for a given PC +// when vmodule is enabled. +// File pattern matching takes the basename of the file, stripped +// of its .go suffix, and uses filepath.Match, which is a little more +// general than the *? matching used in C++. +// Mutex is held. +func (vs *VState) setV(pc uintptr) Level { + fn := runtime.FuncForPC(pc) + file, _ := fn.FileLine(pc) + // The file is something like /a/b/c/d.go. We want just the d. + if strings.HasSuffix(file, ".go") { + file = file[:len(file)-3] + } + if slash := strings.LastIndex(file, "/"); slash >= 0 { + file = file[slash+1:] + } + for _, filter := range vs.vmodule.filter { + if filter.match(file) { + vs.vmap[pc] = filter.level + return filter.level + } + } + vs.vmap[pc] = 0 + return 0 +} diff --git a/vendor/k8s.io/klog/v2/ktesting/init/init.go b/vendor/k8s.io/klog/v2/ktesting/init/init.go new file mode 100644 index 00000000000..c1fa145a8b8 --- /dev/null +++ b/vendor/k8s.io/klog/v2/ktesting/init/init.go @@ -0,0 +1,35 @@ +/* +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 init registers the command line flags for k8s.io/klogr/testing in +// the flag.CommandLine. This is done during initialization, so merely +// importing it is enough. +// +// Experimental +// +// Notice: This package is EXPERIMENTAL and may be changed or removed in a +// later release. +package init + +import ( + "flag" + + "k8s.io/klog/v2/ktesting" +) + +func init() { + ktesting.DefaultConfig.AddFlags(flag.CommandLine) +} diff --git a/vendor/k8s.io/klog/v2/ktesting/options.go b/vendor/k8s.io/klog/v2/ktesting/options.go new file mode 100644 index 00000000000..8cb13c406af --- /dev/null +++ b/vendor/k8s.io/klog/v2/ktesting/options.go @@ -0,0 +1,130 @@ +/* +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 ktesting + +import ( + "flag" + "strconv" + + "k8s.io/klog/v2/internal/verbosity" +) + +// Config influences logging in a test logger. To make this configurable via +// command line flags, instantiate this once per program and use AddFlags to +// bind command line flags to the instance before passing it to NewTestContext. +// +// Must be constructed with NewConfig. +// +// Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a +// later release. +type Config struct { + vstate *verbosity.VState + co configOptions +} + +// ConfigOption implements functional parameters for NewConfig. +// +// Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a +// later release. +type ConfigOption func(co *configOptions) + +type configOptions struct { + verbosityFlagName string + vmoduleFlagName string + verbosityDefault int +} + +// VerbosityFlagName overrides the default -testing.v for the verbosity level. +// +// Experimental +// +// Notice: This function is EXPERIMENTAL and may be changed or removed in a +// later release. +func VerbosityFlagName(name string) ConfigOption { + return func(co *configOptions) { + co.verbosityFlagName = name + } +} + +// VModulFlagName overrides the default -testing.vmodule for the per-module +// verbosity levels. +// +// Experimental +// +// Notice: This function is EXPERIMENTAL and may be changed or removed in a +// later release. +func VModuleFlagName(name string) ConfigOption { + return func(co *configOptions) { + co.vmoduleFlagName = name + } +} + +// Verbosity overrides the default verbosity level of 5. That default is higher +// than in klog itself because it enables logging entries for "the steps +// leading up to errors and warnings" and "troubleshooting" (see +// https://github.com/kubernetes/community/blob/9406b4352fe2d5810cb21cc3cb059ce5886de157/contributors/devel/sig-instrumentation/logging.md#logging-conventions), +// which is useful when debugging a failed test. `go test` only shows the log +// output for failed tests. To see all output, use `go test -v`. +// +// Experimental +// +// Notice: This function is EXPERIMENTAL and may be changed or removed in a +// later release. +func Verbosity(level int) ConfigOption { + return func(co *configOptions) { + co.verbosityDefault = level + } +} + +// NewConfig returns a configuration with recommended defaults and optional +// modifications. Command line flags are not bound to any FlagSet yet. +// +// Experimental +// +// Notice: This function is EXPERIMENTAL and may be changed or removed in a +// later release. +func NewConfig(opts ...ConfigOption) *Config { + c := &Config{ + co: configOptions{ + verbosityFlagName: "testing.v", + vmoduleFlagName: "testing.vmodule", + verbosityDefault: 5, + }, + } + for _, opt := range opts { + opt(&c.co) + } + + c.vstate = verbosity.New() + c.vstate.V().Set(strconv.FormatInt(int64(c.co.verbosityDefault), 10)) + return c +} + +// AddFlags registers the command line flags that control the configuration. +// +// Experimental +// +// Notice: This function is EXPERIMENTAL and may be changed or removed in a +// later release. +func (c *Config) AddFlags(fs *flag.FlagSet) { + fs.Var(c.vstate.V(), c.co.verbosityFlagName, "number for the log level verbosity of the testing logger") + fs.Var(c.vstate.VModule(), c.co.vmoduleFlagName, "comma-separated list of pattern=N log level settings for files matching the patterns") +} diff --git a/vendor/k8s.io/klog/v2/ktesting/setup.go b/vendor/k8s.io/klog/v2/ktesting/setup.go new file mode 100644 index 00000000000..d3029ae96ec --- /dev/null +++ b/vendor/k8s.io/klog/v2/ktesting/setup.go @@ -0,0 +1,48 @@ +/* +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 ktesting + +import ( + "context" + + "github.com/go-logr/logr" +) + +// DefaultConfig is the global default logging configuration for a unit +// test. It is used by NewTestContext and k8s.io/klogr/testing/init. +// +// Experimental +// +// Notice: This variable is EXPERIMENTAL and may be changed or removed in a +// later release. +var DefaultConfig = NewConfig() + +// NewTestContext returns a logger and context for use in a unit test case or +// benchmark. The tl parameter can be a testing.T or testing.B pointer that +// will receive all log output. Importing k8s.io/klogr/testing/init will add +// command line flags that modify the configuration of that log output. +// +// Experimental +// +// Notice: This function is EXPERIMENTAL and may be changed or removed in a +// later release. +func NewTestContext(tl TL) (logr.Logger, context.Context) { + logger := NewLogger(tl, DefaultConfig) + ctx := logr.NewContext(context.Background(), logger) + return logger, ctx + +} diff --git a/vendor/k8s.io/klog/v2/ktesting/testinglogger.go b/vendor/k8s.io/klog/v2/ktesting/testinglogger.go new file mode 100644 index 00000000000..905c1d634ab --- /dev/null +++ b/vendor/k8s.io/klog/v2/ktesting/testinglogger.go @@ -0,0 +1,338 @@ +/* +Copyright 2019 The Kubernetes Authors. +Copyright 2020 Intel Coporation. + +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 testinglogger contains an implementation of the logr interface +// which is logging through a function like testing.TB.Log function. +// Therefore it can be used in standard Go tests and Gingko test suites +// to ensure that output is associated with the currently running test. +// +// In addition, the log data is captured in a buffer and can be used by the +// test to verify that the code under test is logging as expected. To get +// access to that data, cast the LogSink into the Underlier type and retrieve +// it: +// +// logger := ktesting.NewLogger(...) +// if testingLogger, ok := logger.GetSink().(ktesting.Underlier); ok { +// t := testingLogger.GetUnderlying() +// buffer := testingLogger.GetBuffer() +// text := buffer.String() +// log := buffer.Data() +// +// Serialization of the structured log parameters is done in the same way +// as for klog.InfoS. +// +// Experimental +// +// Notice: This package is EXPERIMENTAL and may be changed or removed in a +// later release. +package ktesting + +import ( + "bytes" + "strings" + "sync" + "time" + + "github.com/go-logr/logr" + + "k8s.io/klog/v2/internal/serialize" + "k8s.io/klog/v2/internal/verbosity" +) + +// TL is the relevant subset of testing.TB. +// +// Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a +// later release. +type TL interface { + Helper() + Log(args ...interface{}) +} + +// NopTL implements TL with empty stubs. It can be used when only capturing +// output in memory is relevant. +// +// Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a +// later release. +type NopTL struct{} + +func (n NopTL) Helper() {} +func (n NopTL) Log(args ...interface{}) {} + +var _TL = NopTL{} + +// NewLogger constructs a new logger for the given test interface. +// +// Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a +// later release. +func NewLogger(t TL, c *Config) logr.Logger { + return logr.New(&tlogger{ + t: t, + prefix: "", + values: nil, + config: c, + buffer: new(buffer), + }) +} + +// Buffer stores log entries as formatted text and structured data. +// It is safe to use this concurrently. +// +// Experimental +// +// Notice: This interface is EXPERIMENTAL and may be changed or removed in a +// later release. +type Buffer interface { + // String returns the log entries in a format that is similar to the + // klog text output. + String() string + + // Data returns the log entries as structs. + Data() Log +} + +// Log contains log entries in the order in which they were generated. +// +// Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a +// later release. +type Log []LogEntry + +// DeepCopy returns a copy of the log. The error instance and key/value +// pairs remain shared. +// +// Experimental +// +// Notice: This function is EXPERIMENTAL and may be changed or removed in a +// later release. +func (l Log) DeepCopy() Log { + log := make(Log, 0, len(l)) + log = append(log, l...) + return log +} + +// LogEntry represents all information captured for a log entry. +// +// Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a +// later release. +type LogEntry struct { + // Timestamp stores the time when the log entry was created. + Timestamp time.Time + + // Type is either LogInfo or LogError. + Type LogType + + // Prefix contains the WithName strings concatenated with a slash. + Prefix string + + // Message is the fixed log message string. + Message string + + // Verbosity is always 0 for LogError. + Verbosity int + + // Err is always nil for LogInfo. It may or may not be + // nil for LogError. + Err error + + // WithKVList are the concatenated key/value pairs from WithValues + // calls. It's guaranteed to have an even number of entries because + // the logger ensures that when WithValues is called. + WithKVList []interface{} + + // ParameterKVList are the key/value pairs passed into the call, + // without any validation. + ParameterKVList []interface{} +} + +// LogType determines whether a log entry was created with an Error or Info +// call. +// +// Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a +// later release. +type LogType string + +const ( + // LogError is the special value used for Error log entries. + // + // Experimental + // + // Notice: This value is EXPERIMENTAL and may be changed or removed in + // a later release. + LogError = LogType("ERROR") + + // LogInfo is the special value used for Info log entries. + // + // Experimental + // + // Notice: This value is EXPERIMENTAL and may be changed or removed in + // a later release. + LogInfo = LogType("INFO") +) + +// Underlier is implemented by the LogSink of this logger. It provides access +// to additional APIs that are normally hidden behind the Logger API. +// +// Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a +// later release. +type Underlier interface { + // GetUnderlying returns the testing instance that logging goes to. + GetUnderlying() TL + + // GetBuffer grants access to the in-memory copy of the log entries. + GetBuffer() Buffer +} + +type buffer struct { + mutex sync.Mutex + text strings.Builder + log Log +} + +func (b *buffer) String() string { + b.mutex.Lock() + defer b.mutex.Unlock() + return b.text.String() +} + +func (b *buffer) Data() Log { + b.mutex.Lock() + defer b.mutex.Unlock() + return b.log.DeepCopy() +} + +type tlogger struct { + t TL + prefix string + values []interface{} + config *Config + buffer *buffer +} + +func (l *tlogger) Init(info logr.RuntimeInfo) { +} + +func (l *tlogger) GetCallStackHelper() func() { + return l.t.Helper +} + +func (l *tlogger) Info(level int, msg string, kvList ...interface{}) { + l.t.Helper() + buffer := &bytes.Buffer{} + merged := serialize.MergeKVs(l.values, kvList) + serialize.KVListFormat(buffer, merged...) + l.log(LogInfo, msg, level, buffer, nil, kvList) +} + +func (l *tlogger) Enabled(level int) bool { + return l.config.vstate.Enabled(verbosity.Level(level), 1) +} + +func (l *tlogger) Error(err error, msg string, kvList ...interface{}) { + l.t.Helper() + buffer := &bytes.Buffer{} + if err != nil { + serialize.KVListFormat(buffer, "err", err) + } + merged := serialize.MergeKVs(l.values, kvList) + serialize.KVListFormat(buffer, merged...) + l.log(LogError, msg, 0, buffer, err, kvList) +} + +func (l *tlogger) log(what LogType, msg string, level int, buffer *bytes.Buffer, err error, kvList []interface{}) { + l.t.Helper() + args := []interface{}{what} + if l.prefix != "" { + args = append(args, l.prefix+":") + } + args = append(args, msg) + if buffer.Len() > 0 { + // Skip leading space inserted by serialize.KVListFormat. + args = append(args, string(buffer.Bytes()[1:])) + } + l.t.Log(args...) + + l.buffer.mutex.Lock() + defer l.buffer.mutex.Unlock() + + // Store as text. + l.buffer.text.WriteString(string(what)) + for i := 1; i < len(args); i++ { + l.buffer.text.WriteByte(' ') + l.buffer.text.WriteString(args[i].(string)) + } + lastArg := args[len(args)-1].(string) + if lastArg[len(lastArg)-1] != '\n' { + l.buffer.text.WriteByte('\n') + } + + // Store as raw data. + l.buffer.log = append(l.buffer.log, + LogEntry{ + Timestamp: time.Now(), + Type: what, + Prefix: l.prefix, + Message: msg, + Verbosity: level, + Err: err, + WithKVList: l.values, + ParameterKVList: kvList, + }, + ) +} + +// WithName returns a new logr.Logger with the specified name appended. klogr +// uses '/' characters to separate name elements. Callers should not pass '/' +// in the provided name string, but this library does not actually enforce that. +func (l *tlogger) WithName(name string) logr.LogSink { + new := *l + if len(l.prefix) > 0 { + new.prefix = l.prefix + "/" + } + new.prefix += name + return &new +} + +func (l *tlogger) WithValues(kvList ...interface{}) logr.LogSink { + new := *l + new.values = serialize.WithValues(l.values, kvList) + return &new +} + +func (l *tlogger) GetUnderlying() TL { + return l.t +} + +func (l *tlogger) GetBuffer() Buffer { + return l.buffer +} + +var _ logr.LogSink = &tlogger{} +var _ logr.CallStackHelperLogSink = &tlogger{} +var _ Underlier = &tlogger{} diff --git a/vendor/modules.txt b/vendor/modules.txt index 071903f13ab..3361160133d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2143,6 +2143,9 @@ k8s.io/klog/v2/internal/buffer k8s.io/klog/v2/internal/clock k8s.io/klog/v2/internal/serialize k8s.io/klog/v2/internal/severity +k8s.io/klog/v2/internal/verbosity +k8s.io/klog/v2/ktesting +k8s.io/klog/v2/ktesting/init k8s.io/klog/v2/test # k8s.io/kube-aggregator v0.0.0 => ./staging/src/k8s.io/kube-aggregator ## explicit; go 1.18