rebase: update kubernetes to v1.20.0

updated kubernetes packages to latest
release.

Signed-off-by: Madhu Rajanna <madhupr007@gmail.com>
This commit is contained in:
Madhu Rajanna
2020-12-17 17:58:29 +05:30
committed by mergify[bot]
parent 4abe128bd8
commit 83559144b1
1624 changed files with 247222 additions and 160270 deletions

5
vendor/k8s.io/utils/exec/README.md generated vendored Normal file
View File

@ -0,0 +1,5 @@
# Exec
This package provides an interface for `os/exec`. It makes it easier to mock
and replace in tests, especially with the [FakeExec](testing/fake_exec.go)
struct.

4
vendor/k8s.io/utils/io/README.md generated vendored Normal file
View File

@ -0,0 +1,4 @@
# IO
This package provides interfaces for working with file IO. Currently it
provides functionality for consistently reading a file.

1
vendor/k8s.io/utils/mount/OWNERS generated vendored
View File

@ -8,6 +8,7 @@ reviewers:
- andyzhangx
- gnufied
approvers:
- andyzhangx
- jingxu97
- saad-ali
- jsafrane

View File

@ -21,7 +21,7 @@ import (
"path/filepath"
"sync"
"k8s.io/klog"
"k8s.io/klog/v2"
)
// FakeMounter implements mount.Interface for tests.

14
vendor/k8s.io/utils/mount/mount.go generated vendored
View File

@ -77,7 +77,7 @@ type Interface interface {
var _ Interface = &Mounter{}
// MountPoint represents a single line in /proc/mounts or /etc/fstab.
type MountPoint struct {
type MountPoint struct { // nolint: golint
Device string
Path string
Type string
@ -86,7 +86,7 @@ type MountPoint struct {
Pass int
}
type MountErrorType string
type MountErrorType string // nolint: golint
const (
FilesystemMismatch MountErrorType = "FilesystemMismatch"
@ -97,7 +97,7 @@ const (
UnknownMountError MountErrorType = "UnknownMountError"
)
type MountError struct {
type MountError struct { // nolint: golint
Type MountErrorType
Message string
}
@ -346,14 +346,14 @@ func StartsWithBackstep(rel string) bool {
return rel == ".." || strings.HasPrefix(filepath.ToSlash(rel), "../")
}
// sanitizedOptionsForLogging will return a comma seperated string containing
// sanitizedOptionsForLogging will return a comma separated string containing
// options and sensitiveOptions. Each entry in sensitiveOptions will be
// replaced with the string sensitiveOptionsRemoved
// e.g. o1,o2,<masked>,<masked>
func sanitizedOptionsForLogging(options []string, sensitiveOptions []string) string {
seperator := ""
separator := ""
if len(options) > 0 && len(sensitiveOptions) > 0 {
seperator = ","
separator = ","
}
sensitiveOptionsStart := ""
@ -364,7 +364,7 @@ func sanitizedOptionsForLogging(options []string, sensitiveOptions []string) str
}
return strings.Join(options, ",") +
seperator +
separator +
sensitiveOptionsStart +
sensitiveOptionsEnd
}

View File

@ -20,7 +20,7 @@ import (
"fmt"
"os"
"k8s.io/klog"
"k8s.io/klog/v2"
)
// CleanupMountPoint unmounts the given path and deletes the remaining directory

View File

@ -56,7 +56,7 @@ func IsCorruptedMnt(err error) bool {
}
// MountInfo represents a single line in /proc/<pid>/mountinfo.
type MountInfo struct {
type MountInfo struct { // nolint: golint
// Unique ID for the mount (maybe reused after umount).
ID int
// The ID of the parent mount (or of self for the root of this mount namespace's mount tree).

View File

@ -25,7 +25,7 @@ import (
"strings"
"syscall"
"k8s.io/klog"
"k8s.io/klog/v2"
)
// following failure codes are from https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--1300-1699-

View File

@ -27,7 +27,7 @@ import (
"strings"
"syscall"
"k8s.io/klog"
"k8s.io/klog/v2"
utilexec "k8s.io/utils/exec"
utilio "k8s.io/utils/io"
)
@ -103,7 +103,7 @@ func (mounter *Mounter) MountSensitive(source string, target string, fstype stri
}
// doMount runs the mount command. mounterPath is the path to mounter binary if containerized mounter is used.
// sensitiveOptions is an extention of options except they will not be logged (because they may contain sensitive material)
// sensitiveOptions is an extension of options except they will not be logged (because they may contain sensitive material)
func (mounter *Mounter) doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string, sensitiveOptions []string) error {
mountArgs, mountArgsLogStr := MakeMountArgsSensitive(source, target, fstype, options, sensitiveOptions)
if len(mounterPath) > 0 {
@ -187,7 +187,7 @@ func MakeMountArgs(source, target, fstype string, options []string) (mountArgs [
}
// MakeMountArgsSensitive makes the arguments to the mount(8) command.
// sensitiveOptions is an extention of options except they will not be logged (because they may contain sensitive material)
// sensitiveOptions is an extension of options except they will not be logged (because they may contain sensitive material)
func MakeMountArgsSensitive(source, target, fstype string, options []string, sensitiveOptions []string) (mountArgs []string, mountArgsLogStr string) {
// Build mount command as follows:
// mount [-t $fstype] [-o $options] [$source] $target

View File

@ -25,10 +25,12 @@ import (
"path/filepath"
"strings"
"k8s.io/klog"
utilexec "k8s.io/utils/exec"
"k8s.io/klog/v2"
"k8s.io/utils/keymutex"
utilpath "k8s.io/utils/path"
)
const (
accessDenied string = "access is denied"
)
// Mounter provides the default implementation of mount.Interface
@ -86,9 +88,8 @@ func (mounter *Mounter) MountSensitive(source string, target string, fstype stri
allOptions = append(allOptions, options...)
allOptions = append(allOptions, sensitiveOptions...)
if len(allOptions) < 2 {
klog.Warningf("mount options(%q) command number(%d) less than 2, source:%q, target:%q, skip mounting",
return fmt.Errorf("mount options(%q) should have at least 2 options, current number:%d, source:%q, target:%q",
sanitizedOptionsForLogging, len(allOptions), source, target)
return nil
}
// currently only cifs mount is supported
@ -100,25 +101,37 @@ func (mounter *Mounter) MountSensitive(source string, target string, fstype stri
getSMBMountMutex.LockKey(source)
defer getSMBMountMutex.UnlockKey(source)
if output, err := newSMBMapping(allOptions[0], allOptions[1], source); err != nil {
username := allOptions[0]
password := allOptions[1]
if output, err := newSMBMapping(username, password, source); err != nil {
klog.Warningf("SMB Mapping(%s) returned with error(%v), output(%s)", source, err, string(output))
if isSMBMappingExist(source) {
klog.V(2).Infof("SMB Mapping(%s) already exists, now begin to remove and remount", source)
if output, err := removeSMBMapping(source); err != nil {
return fmt.Errorf("Remove-SmbGlobalMapping failed: %v, output: %q", err, output)
}
if output, err := newSMBMapping(allOptions[0], allOptions[1], source); err != nil {
return fmt.Errorf("New-SmbGlobalMapping remount failed: %v, output: %q", err, output)
valid, err := isValidPath(source)
if !valid {
if err == nil || isAccessDeniedError(err) {
klog.V(2).Infof("SMB Mapping(%s) already exists while it's not valid, return error: %v, now begin to remove and remount", source, err)
if output, err = removeSMBMapping(source); err != nil {
return fmt.Errorf("Remove-SmbGlobalMapping failed: %v, output: %q", err, output)
}
if output, err := newSMBMapping(username, password, source); err != nil {
return fmt.Errorf("New-SmbGlobalMapping(%s) failed: %v, output: %q", source, err, output)
}
}
} else {
klog.V(2).Infof("SMB Mapping(%s) already exists and is still valid, skip error(%v)", source, err)
}
} else {
return fmt.Errorf("New-SmbGlobalMapping failed: %v, output: %q", err, output)
return fmt.Errorf("New-SmbGlobalMapping(%s) failed: %v, output: %q", source, err, output)
}
}
}
if output, err := exec.Command("cmd", "/c", "mklink", "/D", target, bindSource).CombinedOutput(); err != nil {
output, err := exec.Command("cmd", "/c", "mklink", "/D", target, bindSource).CombinedOutput()
if err != nil {
klog.Errorf("mklink failed: %v, source(%q) target(%q) output: %q", err, bindSource, target, string(output))
return err
}
klog.V(2).Infof("mklink source(%q) on target(%q) successfully, output: %q", bindSource, target, string(output))
return nil
}
@ -153,6 +166,23 @@ func isSMBMappingExist(remotepath string) bool {
return err == nil
}
// check whether remotepath is valid
// return (true, nil) if remotepath is valid
func isValidPath(remotepath string) (bool, error) {
cmd := exec.Command("powershell", "/c", `Test-Path $Env:remoteapth`)
cmd.Env = append(os.Environ(), fmt.Sprintf("remoteapth=%s", remotepath))
output, err := cmd.CombinedOutput()
if err != nil {
return false, fmt.Errorf("returned output: %s, error: %v", string(output), err)
}
return strings.HasPrefix(strings.ToLower(string(output)), "true"), nil
}
func isAccessDeniedError(err error) bool {
return err != nil && strings.Contains(strings.ToLower(err.Error()), accessDenied)
}
// remove SMB mapping
func removeSMBMapping(remotepath string) (string, error) {
cmd := exec.Command("powershell", "/c", `Remove-SmbGlobalMapping -RemotePath $Env:smbremotepath -Force`)
@ -183,19 +213,10 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
if err != nil {
return true, err
}
// If current file is a symlink, then it is a mountpoint.
if stat.Mode()&os.ModeSymlink != 0 {
target, err := os.Readlink(file)
if err != nil {
return true, fmt.Errorf("readlink error: %v", err)
}
exists, err := utilpath.Exists(utilpath.CheckFollowSymlink, target)
if err != nil {
return true, err
}
return !exists, nil
}
if stat.Mode()&os.ModeSymlink != 0 {
return false, err
}
return true, nil
}
@ -230,37 +251,38 @@ func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target
// format disk if it is unformatted(raw)
cmd := fmt.Sprintf("Get-Disk -Number %s | Where partitionstyle -eq 'raw' | Initialize-Disk -PartitionStyle MBR -PassThru"+
" | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -FileSystem %s -Confirm:$false", source, fstype)
" | New-Partition -UseMaximumSize | Format-Volume -FileSystem %s -Confirm:$false", source, fstype)
if output, err := mounter.Exec.Command("powershell", "/c", cmd).CombinedOutput(); err != nil {
return fmt.Errorf("diskMount: format disk failed, error: %v, output: %q", err, string(output))
}
klog.V(4).Infof("diskMount: Disk successfully formatted, disk: %q, fstype: %q", source, fstype)
driveLetter, err := getDriveLetterByDiskNumber(source, mounter.Exec)
volumeIds, err := listVolumesOnDisk(source)
if err != nil {
return err
}
driverPath := driveLetter + ":"
driverPath := volumeIds[0]
target = NormalizeWindowsPath(target)
klog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, driverPath, target)
if output, err := mounter.Exec.Command("cmd", "/c", "mklink", "/D", target, driverPath).CombinedOutput(); err != nil {
klog.Errorf("mklink failed: %v, output: %q", err, string(output))
output, err := mounter.Exec.Command("cmd", "/c", "mklink", "/D", target, driverPath).CombinedOutput()
if err != nil {
klog.Errorf("mklink(%s, %s) failed: %v, output: %q", target, driverPath, err, string(output))
return err
}
klog.V(2).Infof("formatAndMount disk(%s) fstype(%s) on(%s) with output(%s) successfully", driverPath, fstype, target, string(output))
return nil
}
// Get drive letter according to windows disk number
func getDriveLetterByDiskNumber(diskNum string, exec utilexec.Interface) (string, error) {
cmd := fmt.Sprintf("(Get-Partition -DiskNumber %s).DriveLetter", diskNum)
// ListVolumesOnDisk - returns back list of volumes(volumeIDs) in the disk (requested in diskID).
func listVolumesOnDisk(diskID string) (volumeIDs []string, err error) {
cmd := fmt.Sprintf("(Get-Disk -DeviceId %s | Get-Partition | Get-Volume).UniqueId", diskID)
output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
klog.V(4).Infof("listVolumesOnDisk id from %s: %s", diskID, string(output))
if err != nil {
return "", fmt.Errorf("azureMount: Get Drive Letter failed: %v, output: %q", err, string(output))
return []string{}, fmt.Errorf("error list volumes on disk. cmd: %s, output: %s, error: %v", cmd, string(output), err)
}
if len(string(output)) < 1 {
return "", fmt.Errorf("azureMount: Get Drive Letter failed, output is empty")
}
return string(output)[:1], nil
volumeIds := strings.Split(strings.TrimSpace(string(output)), "\r\n")
return volumeIds, nil
}
// getAllParentLinks walks all symbolic links and return all the parent targets recursively

24
vendor/k8s.io/utils/net/net.go generated vendored
View File

@ -137,6 +137,30 @@ func IsIPv6CIDR(cidr *net.IPNet) bool {
return IsIPv6(ip)
}
// IsIPv4 returns if netIP is IPv4.
func IsIPv4(netIP net.IP) bool {
return netIP != nil && netIP.To4() != nil
}
// IsIPv4String returns if ip is IPv4.
func IsIPv4String(ip string) bool {
netIP := net.ParseIP(ip)
return IsIPv4(netIP)
}
// IsIPv4CIDR returns if a cidr is ipv4
func IsIPv4CIDR(cidr *net.IPNet) bool {
ip := cidr.IP
return IsIPv4(ip)
}
// IsIPv4CIDRString returns if cidr is IPv4.
// This assumes cidr is a valid CIDR.
func IsIPv4CIDRString(cidr string) bool {
ip, _, _ := net.ParseCIDR(cidr)
return IsIPv4(ip)
}
// ParsePort parses a string representing an IP port. If the string is not a
// valid port number, this returns an error.
func ParsePort(port string, allowZero bool) (int, error) {

4
vendor/k8s.io/utils/nsenter/README.md generated vendored Normal file
View File

@ -0,0 +1,4 @@
# NSEnter
This package provides interfaces for executing and interacting with processes
running within a namespace.

View File

@ -26,7 +26,7 @@ import (
"path/filepath"
"strings"
"k8s.io/klog"
"k8s.io/klog/v2"
"k8s.io/utils/exec"
)

2
vendor/k8s.io/utils/path/file.go generated vendored
View File

@ -30,7 +30,7 @@ const (
// the symlink exists.
CheckFollowSymlink LinkTreatment = iota
// CheckSymlinkOnly does not follow the symlink and verfies only that they
// CheckSymlinkOnly does not follow the symlink and verifies only that they
// symlink itself exists.
CheckSymlinkOnly
)

3
vendor/k8s.io/utils/pointer/README.md generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Pointer
This package provides some functions for pointer-based operations.

View File

@ -51,14 +51,23 @@ func Int32Ptr(i int32) *int32 {
return &i
}
// Int32PtrDerefOr dereference the int32 ptr and returns it if not nil,
// else returns def.
func Int32PtrDerefOr(ptr *int32, def int32) int32 {
if ptr != nil {
return *ptr
}
return def
}
// Int64Ptr returns a pointer to an int64
func Int64Ptr(i int64) *int64 {
return &i
}
// Int32PtrDerefOr dereference the int32 ptr and returns it if not nil,
// Int64PtrDerefOr dereference the int64 ptr and returns it if not nil,
// else returns def.
func Int32PtrDerefOr(ptr *int32, def int32) int32 {
func Int64PtrDerefOr(ptr *int64, def int64) int64 {
if ptr != nil {
return *ptr
}
@ -70,17 +79,53 @@ func BoolPtr(b bool) *bool {
return &b
}
// BoolPtrDerefOr dereference the bool ptr and returns it if not nil,
// else returns def.
func BoolPtrDerefOr(ptr *bool, def bool) bool {
if ptr != nil {
return *ptr
}
return def
}
// StringPtr returns a pointer to the passed string.
func StringPtr(s string) *string {
return &s
}
// StringPtrDerefOr dereference the string ptr and returns it if not nil,
// else returns def.
func StringPtrDerefOr(ptr *string, def string) string {
if ptr != nil {
return *ptr
}
return def
}
// Float32Ptr returns a pointer to the passed float32.
func Float32Ptr(i float32) *float32 {
return &i
}
// Float32PtrDerefOr dereference the float32 ptr and returns it if not nil,
// else returns def.
func Float32PtrDerefOr(ptr *float32, def float32) float32 {
if ptr != nil {
return *ptr
}
return def
}
// Float64Ptr returns a pointer to the passed float64.
func Float64Ptr(i float64) *float64 {
return &i
}
// Float64PtrDerefOr dereference the float64 ptr and returns it if not nil,
// else returns def.
func Float64PtrDerefOr(ptr *float64, def float64) float64 {
if ptr != nil {
return *ptr
}
return def
}

View File

@ -1,36 +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 strings
import (
"strings"
)
// EscapeQualifiedName converts a plugin name, which might contain a / into a
// string that is safe to use on-disk. This assumes that the input has already
// been validates as a qualified name. we use "~" rather than ":" here in case
// we ever use a filesystem that doesn't allow ":".
func EscapeQualifiedName(in string) string {
return strings.Replace(in, "/", "~", -1)
}
// UnescapeQualifiedName converts an escaped plugin name (as per EscapeQualifiedName)
// back to its normal form. This assumes that the input has already been
// validates as a qualified name.
func UnescapeQualifiedName(in string) string {
return strings.Replace(in, "~", "/", -1)
}

View File

@ -1,64 +0,0 @@
/*
Copyright 2015 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 strings
import (
"bytes"
"io"
"strings"
)
// LineDelimiter is a filter that will split input on lines
// and bracket each line with the delimiter string.
type LineDelimiter struct {
output io.Writer
delimiter []byte
buf bytes.Buffer
}
// NewLineDelimiter allocates a new io.Writer that will split input on lines
// and bracket each line with the delimiter string. This can be useful in
// output tests where it is difficult to see and test trailing whitespace.
func NewLineDelimiter(output io.Writer, delimiter string) *LineDelimiter {
return &LineDelimiter{output: output, delimiter: []byte(delimiter)}
}
// Write writes buf to the LineDelimiter ld. The only errors returned are ones
// encountered while writing to the underlying output stream.
func (ld *LineDelimiter) Write(buf []byte) (n int, err error) {
return ld.buf.Write(buf)
}
// Flush all lines up until now. This will assume insert a linebreak at the current point of the stream.
func (ld *LineDelimiter) Flush() (err error) {
lines := strings.Split(ld.buf.String(), "\n")
for _, line := range lines {
if _, err = ld.output.Write(ld.delimiter); err != nil {
return
}
if _, err = ld.output.Write([]byte(line)); err != nil {
return
}
if _, err = ld.output.Write(ld.delimiter); err != nil {
return
}
if _, err = ld.output.Write([]byte("\n")); err != nil {
return
}
}
return
}

View File

@ -1,46 +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 strings
import (
"path"
"strings"
)
// SplitQualifiedName Splits a fully qualified name and returns its namespace and name.
// Assumes that the input 'str' has been validated.
func SplitQualifiedName(str string) (string, string) {
parts := strings.Split(str, "/")
if len(parts) < 2 {
return "", str
}
return parts[0], parts[1]
}
// JoinQualifiedName joins 'namespace' and 'name' and returns a fully qualified name
// Assumes that the input is valid.
func JoinQualifiedName(namespace, name string) string {
return path.Join(namespace, name)
}
// ShortenString returns the first N slice of a string.
func ShortenString(str string, n int) string {
if len(str) <= n {
return str
}
return str[:n]
}

67
vendor/k8s.io/utils/trace/README.md generated vendored Normal file
View File

@ -0,0 +1,67 @@
# Trace
This package provides an interface for recording the latency of operations and logging details
about all operations where the latency exceeds a limit.
## Usage
To create a trace:
```go
func doSomething() {
opTrace := trace.New("operation", Field{Key: "fieldKey1", Value: "fieldValue1"})
defer opTrace.LogIfLong(100 * time.Millisecond)
// do something
}
```
To split an trace into multiple steps:
```go
func doSomething() {
opTrace := trace.New("operation")
defer opTrace.LogIfLong(100 * time.Millisecond)
// do step 1
opTrace.Step("step1", Field{Key: "stepFieldKey1", Value: "stepFieldValue1"})
// do step 2
opTrace.Step("step2")
}
```
To nest traces:
```go
func doSomething() {
rootTrace := trace.New("rootOperation")
defer rootTrace.LogIfLong(100 * time.Millisecond)
func() {
nestedTrace := rootTrace.Nest("nested", Field{Key: "nestedFieldKey1", Value: "nestedFieldValue1"})
defer nestedTrace.LogIfLong(50 * time.Millisecond)
// do nested operation
}()
}
```
Traces can also be logged unconditionally or introspected:
```go
opTrace.TotalTime() // Duration since the Trace was created
opTrace.Log() // unconditionally log the trace
```
### Using context.Context to nest traces
`context.Context` can be used to manage nested traces. Create traces by calling `trace.GetTraceFromContext(ctx).Nest`.
This is safe even if there is no parent trace already in the context because `(*(Trace)nil).Nest()` returns
a top level trace.
```go
func doSomething(ctx context.Context) {
opTrace := trace.FromContext(ctx).Nest("operation") // create a trace, possibly nested
ctx = trace.ContextWithTrace(ctx, opTrace) // make this trace the parent trace of the context
defer opTrace.LogIfLong(50 * time.Millisecond)
doSomethingElse(ctx)
}
```

245
vendor/k8s.io/utils/trace/trace.go generated vendored
View File

@ -18,13 +18,18 @@ package trace
import (
"bytes"
"context"
"fmt"
"math/rand"
"time"
"k8s.io/klog"
"k8s.io/klog/v2"
)
var klogV = func(lvl klog.Level) bool {
return klog.V(lvl).Enabled()
}
// Field is a key value pair that provides additional details about the trace.
type Field struct {
Key string
@ -44,19 +49,83 @@ func writeFields(b *bytes.Buffer, l []Field) {
}
}
func writeTraceItemSummary(b *bytes.Buffer, msg string, totalTime time.Duration, startTime time.Time, fields []Field) {
b.WriteString(fmt.Sprintf("%q ", msg))
if len(fields) > 0 {
writeFields(b, fields)
b.WriteString(" ")
}
b.WriteString(fmt.Sprintf("%vms (%v)", durationToMilliseconds(totalTime), startTime.Format("15:04:00.000")))
}
func durationToMilliseconds(timeDuration time.Duration) int64 {
return timeDuration.Nanoseconds() / 1e6
}
type traceItem interface {
// time returns when the trace was recorded as completed.
time() time.Time
// writeItem outputs the traceItem to the buffer. If stepThreshold is non-nil, only output the
// traceItem if its the duration exceeds the stepThreshold.
// Each line of output is prefixed by formatter to visually indent nested items.
writeItem(b *bytes.Buffer, formatter string, startTime time.Time, stepThreshold *time.Duration)
}
type traceStep struct {
stepTime time.Time
msg string
fields []Field
}
func (s traceStep) time() time.Time {
return s.stepTime
}
func (s traceStep) writeItem(b *bytes.Buffer, formatter string, startTime time.Time, stepThreshold *time.Duration) {
stepDuration := s.stepTime.Sub(startTime)
if stepThreshold == nil || *stepThreshold == 0 || stepDuration >= *stepThreshold || klogV(4) {
b.WriteString(fmt.Sprintf("%s---", formatter))
writeTraceItemSummary(b, s.msg, stepDuration, s.stepTime, s.fields)
}
}
// Trace keeps track of a set of "steps" and allows us to log a specific
// step if it took longer than its share of the total allowed time
type Trace struct {
name string
fields []Field
startTime time.Time
steps []traceStep
name string
fields []Field
threshold *time.Duration
startTime time.Time
endTime *time.Time
traceItems []traceItem
parentTrace *Trace
}
func (t *Trace) time() time.Time {
if t.endTime != nil {
return *t.endTime
}
return t.startTime // if the trace is incomplete, don't assume an end time
}
func (t *Trace) writeItem(b *bytes.Buffer, formatter string, startTime time.Time, stepThreshold *time.Duration) {
if t.durationIsWithinThreshold() || klogV(4) {
b.WriteString(fmt.Sprintf("%v[", formatter))
writeTraceItemSummary(b, t.name, t.TotalTime(), t.startTime, t.fields)
if st := t.calculateStepThreshold(); st != nil {
stepThreshold = st
}
t.writeTraceSteps(b, formatter+" ", stepThreshold)
b.WriteString("]")
return
}
// If the trace should not be written, still check for nested traces that should be written
for _, s := range t.traceItems {
if nestedTrace, ok := s.(*Trace); ok {
nestedTrace.writeItem(b, formatter, startTime, stepThreshold)
}
}
}
// New creates a Trace with the specified name. The name identifies the operation to be traced. The
@ -69,63 +138,145 @@ func New(name string, fields ...Field) *Trace {
// how long it took. The Fields add key value pairs to provide additional details about the trace
// step.
func (t *Trace) Step(msg string, fields ...Field) {
if t.steps == nil {
if t.traceItems == nil {
// traces almost always have less than 6 steps, do this to avoid more than a single allocation
t.steps = make([]traceStep, 0, 6)
t.traceItems = make([]traceItem, 0, 6)
}
t.steps = append(t.steps, traceStep{stepTime: time.Now(), msg: msg, fields: fields})
t.traceItems = append(t.traceItems, traceStep{stepTime: time.Now(), msg: msg, fields: fields})
}
// Log is used to dump all the steps in the Trace
// Nest adds a nested trace with the given message and fields and returns it.
// As a convenience, if the receiver is nil, returns a top level trace. This allows
// one to call FromContext(ctx).Nest without having to check if the trace
// in the context is nil.
func (t *Trace) Nest(msg string, fields ...Field) *Trace {
newTrace := New(msg, fields...)
if t != nil {
newTrace.parentTrace = t
t.traceItems = append(t.traceItems, newTrace)
}
return newTrace
}
// Log is used to dump all the steps in the Trace. It also logs the nested trace messages using indentation.
// If the Trace is nested it is not immediately logged. Instead, it is logged when the trace it is nested within
// is logged.
func (t *Trace) Log() {
// an explicit logging request should dump all the steps out at the higher level
t.logWithStepThreshold(0)
}
func (t *Trace) logWithStepThreshold(stepThreshold time.Duration) {
var buffer bytes.Buffer
tracenum := rand.Int31()
endTime := time.Now()
totalTime := endTime.Sub(t.startTime)
buffer.WriteString(fmt.Sprintf("Trace[%d]: %q ", tracenum, t.name))
if len(t.fields) > 0 {
writeFields(&buffer, t.fields)
buffer.WriteString(" ")
t.endTime = &endTime
// an explicit logging request should dump all the steps out at the higher level
if t.parentTrace == nil { // We don't start logging until Log or LogIfLong is called on the root trace
t.logTrace()
}
buffer.WriteString(fmt.Sprintf("(started: %v) (total time: %v):\n", t.startTime, totalTime))
lastStepTime := t.startTime
for _, step := range t.steps {
stepDuration := step.stepTime.Sub(lastStepTime)
if stepThreshold == 0 || stepDuration > stepThreshold || klog.V(4) {
buffer.WriteString(fmt.Sprintf("Trace[%d]: [%v] [%v] ", tracenum, step.stepTime.Sub(t.startTime), stepDuration))
buffer.WriteString(step.msg)
if len(step.fields) > 0 {
buffer.WriteString(" ")
writeFields(&buffer, step.fields)
}
buffer.WriteString("\n")
}
lastStepTime = step.stepTime
}
stepDuration := endTime.Sub(lastStepTime)
if stepThreshold == 0 || stepDuration > stepThreshold || klog.V(4) {
buffer.WriteString(fmt.Sprintf("Trace[%d]: [%v] [%v] END\n", tracenum, endTime.Sub(t.startTime), stepDuration))
}
klog.Info(buffer.String())
}
// LogIfLong is used to dump steps that took longer than its share
// LogIfLong only logs the trace if the duration of the trace exceeds the threshold.
// Only steps that took longer than their share or the given threshold are logged.
// If klog is at verbosity level 4 or higher and the trace took longer than the threshold,
// all substeps and subtraces are logged. Otherwise, only those which took longer than
// their own threshold.
// If the Trace is nested it is not immediately logged. Instead, it is logged when the trace it
// is nested within is logged.
func (t *Trace) LogIfLong(threshold time.Duration) {
if time.Since(t.startTime) >= threshold {
t.threshold = &threshold
t.Log()
}
// logTopLevelTraces finds all traces in a hierarchy of nested traces that should be logged but do not have any
// parents that will be logged, due to threshold limits, and logs them as top level traces.
func (t *Trace) logTrace() {
if t.durationIsWithinThreshold() {
var buffer bytes.Buffer
traceNum := rand.Int31()
totalTime := t.endTime.Sub(t.startTime)
buffer.WriteString(fmt.Sprintf("Trace[%d]: %q ", traceNum, t.name))
if len(t.fields) > 0 {
writeFields(&buffer, t.fields)
buffer.WriteString(" ")
}
// if any step took more than it's share of the total allowed time, it deserves a higher log level
stepThreshold := threshold / time.Duration(len(t.steps)+1)
t.logWithStepThreshold(stepThreshold)
buffer.WriteString(fmt.Sprintf("(%v) (total time: %vms):", t.startTime.Format("02-Jan-2006 15:04:05.000"), totalTime.Milliseconds()))
stepThreshold := t.calculateStepThreshold()
t.writeTraceSteps(&buffer, fmt.Sprintf("\nTrace[%d]: ", traceNum), stepThreshold)
buffer.WriteString(fmt.Sprintf("\nTrace[%d]: [%v] [%v] END\n", traceNum, t.endTime.Sub(t.startTime), totalTime))
klog.Info(buffer.String())
return
}
// If the trace should not be logged, still check if nested traces should be logged
for _, s := range t.traceItems {
if nestedTrace, ok := s.(*Trace); ok {
nestedTrace.logTrace()
}
}
}
func (t *Trace) writeTraceSteps(b *bytes.Buffer, formatter string, stepThreshold *time.Duration) {
lastStepTime := t.startTime
for _, stepOrTrace := range t.traceItems {
stepOrTrace.writeItem(b, formatter, lastStepTime, stepThreshold)
lastStepTime = stepOrTrace.time()
}
}
func (t *Trace) durationIsWithinThreshold() bool {
if t.endTime == nil { // we don't assume incomplete traces meet the threshold
return false
}
return t.threshold == nil || *t.threshold == 0 || t.endTime.Sub(t.startTime) >= *t.threshold
}
// TotalTime can be used to figure out how long it took since the Trace was created
func (t *Trace) TotalTime() time.Duration {
return time.Since(t.startTime)
}
// calculateStepThreshold returns a threshold for the individual steps of a trace, or nil if there is no threshold and
// all steps should be written.
func (t *Trace) calculateStepThreshold() *time.Duration {
if t.threshold == nil {
return nil
}
lenTrace := len(t.traceItems) + 1
traceThreshold := *t.threshold
for _, s := range t.traceItems {
nestedTrace, ok := s.(*Trace)
if ok && nestedTrace.threshold != nil {
traceThreshold = traceThreshold - *nestedTrace.threshold
lenTrace--
}
}
// the limit threshold is used when the threshold(
//remaining after subtracting that of the child trace) is getting very close to zero to prevent unnecessary logging
limitThreshold := *t.threshold / 4
if traceThreshold < limitThreshold {
traceThreshold = limitThreshold
lenTrace = len(t.traceItems) + 1
}
stepThreshold := traceThreshold / time.Duration(lenTrace)
return &stepThreshold
}
// ContextTraceKey provides a common key for traces in context.Context values.
type ContextTraceKey struct{}
// FromContext returns the trace keyed by ContextTraceKey in the context values, if one
// is present, or nil If there is no trace in the Context.
// It is safe to call Nest() on the returned value even if it is nil because ((*Trace)nil).Nest returns a top level
// trace.
func FromContext(ctx context.Context) *Trace {
if v, ok := ctx.Value(ContextTraceKey{}).(*Trace); ok {
return v
}
return nil
}
// ContextWithTrace returns a context with trace included in the context values, keyed by ContextTraceKey.
func ContextWithTrace(ctx context.Context, trace *Trace) context.Context {
return context.WithValue(ctx, ContextTraceKey{}, trace)
}