diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 15ed83553e7..c070dc22374 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -53901,12 +53901,19 @@ func schema_k8sio_kube_proxy_config_v1alpha1_KubeProxyConfiguration(ref common.R Ref: ref("k8s.io/kube-proxy/config/v1alpha1.DetectLocalConfiguration"), }, }, + "logging": { + SchemaProps: spec.SchemaProps{ + Description: "logging specifies the options of logging. Refer to [Logs Options](https://github.com/kubernetes/component-base/blob/master/logs/options.go) for more information.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/component-base/logs/api/v1.LoggingConfiguration"), + }, + }, }, Required: []string{"bindAddress", "healthzBindAddress", "metricsBindAddress", "bindAddressHardFail", "enableProfiling", "clusterCIDR", "hostnameOverride", "clientConnection", "iptables", "ipvs", "oomScoreAdj", "mode", "portRange", "conntrack", "configSyncPeriod", "nodePortAddresses", "winkernel", "showHiddenMetricsForVersion", "detectLocalMode", "detectLocal"}, }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "k8s.io/component-base/config/v1alpha1.ClientConnectionConfiguration", "k8s.io/kube-proxy/config/v1alpha1.DetectLocalConfiguration", "k8s.io/kube-proxy/config/v1alpha1.KubeProxyConntrackConfiguration", "k8s.io/kube-proxy/config/v1alpha1.KubeProxyIPTablesConfiguration", "k8s.io/kube-proxy/config/v1alpha1.KubeProxyIPVSConfiguration", "k8s.io/kube-proxy/config/v1alpha1.KubeProxyWinkernelConfiguration"}, + "k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "k8s.io/component-base/config/v1alpha1.ClientConnectionConfiguration", "k8s.io/component-base/logs/api/v1.LoggingConfiguration", "k8s.io/kube-proxy/config/v1alpha1.DetectLocalConfiguration", "k8s.io/kube-proxy/config/v1alpha1.KubeProxyConntrackConfiguration", "k8s.io/kube-proxy/config/v1alpha1.KubeProxyIPTablesConfiguration", "k8s.io/kube-proxy/config/v1alpha1.KubeProxyIPVSConfiguration", "k8s.io/kube-proxy/config/v1alpha1.KubeProxyWinkernelConfiguration"}, } } diff --git a/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/after/v1beta1.yaml b/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/after/v1beta1.yaml index 16406184bd6..ab43f865f3a 100644 --- a/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/after/v1beta1.yaml +++ b/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/after/v1beta1.yaml @@ -51,7 +51,7 @@ kubeAPIBurst: 100 kubeAPIQPS: 50 localStorageCapacityIsolation: true logging: - flushFrequency: 5000000000 + flushFrequency: 5s format: text options: json: diff --git a/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/roundtrip/default/v1beta1.yaml b/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/roundtrip/default/v1beta1.yaml index 12aea87941e..8b877c3ad4e 100644 --- a/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/roundtrip/default/v1beta1.yaml +++ b/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/roundtrip/default/v1beta1.yaml @@ -51,7 +51,7 @@ kubeAPIBurst: 100 kubeAPIQPS: 50 localStorageCapacityIsolation: true logging: - flushFrequency: 5000000000 + flushFrequency: 5s format: text options: json: diff --git a/pkg/kubelet/apis/config/v1beta1/defaults_test.go b/pkg/kubelet/apis/config/v1beta1/defaults_test.go index 9bd3b7a97a6..52b4bc38ff1 100644 --- a/pkg/kubelet/apis/config/v1beta1/defaults_test.go +++ b/pkg/kubelet/apis/config/v1beta1/defaults_test.go @@ -115,7 +115,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { VolumePluginDir: DefaultVolumePluginDir, Logging: logsapi.LoggingConfiguration{ Format: "text", - FlushFrequency: 5 * time.Second, + FlushFrequency: logsapi.TimeOrMetaDuration{Duration: 5 * time.Second}, }, EnableSystemLogHandler: utilpointer.Bool(true), EnableProfilingHandler: utilpointer.Bool(true), @@ -239,7 +239,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { KernelMemcgNotification: false, Logging: logsapi.LoggingConfiguration{ Format: "", - FlushFrequency: 5 * time.Second, + FlushFrequency: logsapi.TimeOrMetaDuration{Duration: 5 * time.Second}, }, EnableSystemLogHandler: utilpointer.Bool(false), ShutdownGracePeriod: zeroDuration, @@ -339,7 +339,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { VolumePluginDir: DefaultVolumePluginDir, Logging: logsapi.LoggingConfiguration{ Format: "text", - FlushFrequency: 5 * time.Second, + FlushFrequency: logsapi.TimeOrMetaDuration{Duration: 5 * time.Second}, }, EnableSystemLogHandler: utilpointer.Bool(false), ReservedMemory: []v1beta1.MemoryReservation{}, @@ -481,7 +481,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { KernelMemcgNotification: true, Logging: logsapi.LoggingConfiguration{ Format: "json", - FlushFrequency: 5 * time.Second, + FlushFrequency: logsapi.TimeOrMetaDuration{Duration: 5 * time.Second}, }, EnableSystemLogHandler: utilpointer.Bool(true), ShutdownGracePeriod: metav1.Duration{Duration: 60 * time.Second}, @@ -627,7 +627,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { KernelMemcgNotification: true, Logging: logsapi.LoggingConfiguration{ Format: "json", - FlushFrequency: 5 * time.Second, + FlushFrequency: logsapi.TimeOrMetaDuration{Duration: 5 * time.Second}, }, EnableSystemLogHandler: utilpointer.Bool(true), ShutdownGracePeriod: metav1.Duration{Duration: 60 * time.Second}, @@ -724,7 +724,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { VolumePluginDir: DefaultVolumePluginDir, Logging: logsapi.LoggingConfiguration{ Format: "text", - FlushFrequency: 5 * time.Second, + FlushFrequency: logsapi.TimeOrMetaDuration{Duration: 5 * time.Second}, }, EnableSystemLogHandler: utilpointer.Bool(true), EnableProfilingHandler: utilpointer.Bool(true), @@ -813,7 +813,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { VolumePluginDir: DefaultVolumePluginDir, Logging: logsapi.LoggingConfiguration{ Format: "text", - FlushFrequency: 5 * time.Second, + FlushFrequency: logsapi.TimeOrMetaDuration{Duration: 5 * time.Second}, }, EnableSystemLogHandler: utilpointer.Bool(true), EnableProfilingHandler: utilpointer.Bool(true), @@ -902,7 +902,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { VolumePluginDir: DefaultVolumePluginDir, Logging: logsapi.LoggingConfiguration{ Format: "text", - FlushFrequency: 5 * time.Second, + FlushFrequency: logsapi.TimeOrMetaDuration{Duration: 5 * time.Second}, }, EnableSystemLogHandler: utilpointer.Bool(true), EnableProfilingHandler: utilpointer.Bool(true), diff --git a/staging/src/k8s.io/component-base/logs/api/v1/options.go b/staging/src/k8s.io/component-base/logs/api/v1/options.go index 0279cf883d5..44e036ac7e5 100644 --- a/staging/src/k8s.io/component-base/logs/api/v1/options.go +++ b/staging/src/k8s.io/component-base/logs/api/v1/options.go @@ -262,7 +262,7 @@ func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate feature if err := loggingFlags.Lookup("vmodule").Value.Set(VModuleConfigurationPflag(&c.VModule).String()); err != nil { return fmt.Errorf("internal error while setting klog vmodule: %v", err) } - klog.StartFlushDaemon(c.FlushFrequency) + klog.StartFlushDaemon(c.FlushFrequency.Duration) klog.EnableContextualLogging(p.ContextualLoggingEnabled) return nil } @@ -342,7 +342,7 @@ func addFlags(c *LoggingConfiguration, fs flagSet) { // No new log formats should be added after generation is of flag options logRegistry.freeze() - fs.DurationVar(&c.FlushFrequency, LogFlushFreqFlagName, c.FlushFrequency, "Maximum number of seconds between log flushes") + fs.DurationVar(&c.FlushFrequency.Duration, LogFlushFreqFlagName, c.FlushFrequency.Duration, "Maximum number of seconds between log flushes") fs.VarP(VerbosityLevelPflag(&c.Verbosity), "v", "v", "number for the log level verbosity") fs.Var(VModuleConfigurationPflag(&c.VModule), "vmodule", "comma-separated list of pattern=N settings for file-filtered logging (only works for text log format)") @@ -364,8 +364,8 @@ func SetRecommendedLoggingConfiguration(c *LoggingConfiguration) { if c.Format == "" { c.Format = "text" } - if c.FlushFrequency == 0 { - c.FlushFrequency = LogFlushFreqDefault + if c.FlushFrequency.Duration == 0 { + c.FlushFrequency.Duration = LogFlushFreqDefault } var empty resource.QuantityValue if c.Options.JSON.InfoBufferSize == empty { diff --git a/staging/src/k8s.io/component-base/logs/api/v1/types.go b/staging/src/k8s.io/component-base/logs/api/v1/types.go index d1bf313643b..98a64bc1fd4 100644 --- a/staging/src/k8s.io/component-base/logs/api/v1/types.go +++ b/staging/src/k8s.io/component-base/logs/api/v1/types.go @@ -17,9 +17,11 @@ limitations under the License. package v1 import ( - "time" + "encoding/json" + "fmt" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Supported output formats. @@ -39,10 +41,11 @@ type LoggingConfiguration struct { // Format Flag specifies the structure of log messages. // default value of format is `text` Format string `json:"format,omitempty"` - // Maximum number of nanoseconds (i.e. 1s = 1000000000) between log - // flushes. Ignored if the selected logging backend writes log - // messages without buffering. - FlushFrequency time.Duration `json:"flushFrequency"` + // Maximum time between log flushes. + // If a string, parsed as a duration (i.e. "1s") + // If an int, the maximum number of nanoseconds (i.e. 1s = 1000000000). + // Ignored if the selected logging backend writes log messages without buffering. + FlushFrequency TimeOrMetaDuration `json:"flushFrequency"` // Verbosity is the threshold that determines which log messages are // logged. Default is zero which logs only the most important // messages. Higher values enable additional messages. Error messages @@ -58,6 +61,25 @@ type LoggingConfiguration struct { Options FormatOptions `json:"options,omitempty"` } +// TimeOrMetaDuration is present only for backwards compatibility for the +// flushFrequency field, and new fields should use metav1.Duration. +type TimeOrMetaDuration metav1.Duration + +func (t TimeOrMetaDuration) MarshalJSON() ([]byte, error) { + return (metav1.Duration(t)).MarshalJSON() +} + +func (t *TimeOrMetaDuration) UnmarshalJSON(b []byte) error { + if len(b) > 0 && b[0] == '"' { + // string values unmarshal as metav1.Duration + return json.Unmarshal(b, (*metav1.Duration)(t)) + } + if err := json.Unmarshal(b, &t.Duration); err != nil { + return fmt.Errorf("invalid duration %q: %w", string(b), err) + } + return nil +} + // FormatOptions contains options for the different logging formats. type FormatOptions struct { // [Alpha] JSON contains options for logging format "json". diff --git a/staging/src/k8s.io/component-base/logs/api/v1/types_test.go b/staging/src/k8s.io/component-base/logs/api/v1/types_test.go index 175a5697641..7b847fc3d0d 100644 --- a/staging/src/k8s.io/component-base/logs/api/v1/types_test.go +++ b/staging/src/k8s.io/component-base/logs/api/v1/types_test.go @@ -17,6 +17,7 @@ limitations under the License. package v1 import ( + enjson "encoding/json" "fmt" "math" "reflect" @@ -168,7 +169,7 @@ func TestCompatibility(t *testing.T) { expectAllFields: true, expectConfig: LoggingConfiguration{ Format: JSONLogFormat, - FlushFrequency: time.Nanosecond, + FlushFrequency: TimeOrMetaDuration{Duration: time.Nanosecond}, Verbosity: VerbosityLevel(5), VModule: VModuleConfiguration{ { @@ -264,3 +265,48 @@ func notZeroRecursive(t *testing.T, i interface{}, path string) bool { return valid } + +func TestTimeOrMetaDuration_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + tomd *TimeOrMetaDuration + arg any + wanted string + }{ + { + name: "string values unmarshal as metav1.Duration", + tomd: &TimeOrMetaDuration{}, + arg: "1s", + wanted: "1s", + }, { + name: "int values unmarshal as metav1.Duration", + tomd: &TimeOrMetaDuration{}, + arg: 1000000000, + wanted: "1s", + }, { + name: "invalid value return error", + tomd: &TimeOrMetaDuration{}, + arg: "invalid", + wanted: "time: invalid duration \"invalid\"", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, err := enjson.Marshal(tt.arg) + if err != nil { + t.Errorf("unexpect error: %v", err) + } + + if err := tt.tomd.UnmarshalJSON(b); err == nil { + if tt.wanted != tt.tomd.String() { + t.Errorf("unexpected wanted for %s, wanted: %v, got: %v", tt.name, tt.wanted, tt.tomd.String()) + } + } else { + if err.Error() != tt.wanted { + t.Errorf("UnmarshalJSON() error = %v", err) + } + } + }) + } + +} diff --git a/staging/src/k8s.io/component-base/logs/api/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/component-base/logs/api/v1/zz_generated.deepcopy.go index 87ca10da1a3..9e7fdcd4693 100644 --- a/staging/src/k8s.io/component-base/logs/api/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/component-base/logs/api/v1/zz_generated.deepcopy.go @@ -58,6 +58,7 @@ func (in *JSONOptions) DeepCopy() *JSONOptions { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) { *out = *in + out.FlushFrequency = in.FlushFrequency if in.VModule != nil { in, out := &in.VModule, &out.VModule *out = make(VModuleConfiguration, len(*in)) @@ -77,6 +78,22 @@ func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TimeOrMetaDuration) DeepCopyInto(out *TimeOrMetaDuration) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TimeOrMetaDuration. +func (in *TimeOrMetaDuration) DeepCopy() *TimeOrMetaDuration { + if in == nil { + return nil + } + out := new(TimeOrMetaDuration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in VModuleConfiguration) DeepCopyInto(out *VModuleConfiguration) { {