kubelet: convert node shutdown manager to contextual logging

This will make output checking easier (done in a separate commit). kubelet
itself still uses the global logger.
This commit is contained in:
Patrick Ohly 2022-06-10 14:22:40 +02:00
parent 0669ba386b
commit 65385fec20
11 changed files with 895 additions and 15 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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)
}
}

View File

@ -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,

305
vendor/k8s.io/klog/v2/internal/verbosity/verbosity.go generated vendored Normal file
View File

@ -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
}

35
vendor/k8s.io/klog/v2/ktesting/init/init.go generated vendored Normal file
View File

@ -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)
}

130
vendor/k8s.io/klog/v2/ktesting/options.go generated vendored Normal file
View File

@ -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")
}

48
vendor/k8s.io/klog/v2/ktesting/setup.go generated vendored Normal file
View File

@ -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
}

338
vendor/k8s.io/klog/v2/ktesting/testinglogger.go generated vendored Normal file
View File

@ -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{}

3
vendor/modules.txt vendored
View File

@ -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