kube-proxy: fix combination of --config and logging command line flags
When parsing a config file, all settings derived from command line flags are
discarded because only the config settings are used. That has been the
traditional behavior for non-logging flags.
But `--config ... -v=4` used to work until
71ef0dafa7 added logging to the configuration.
To restore the original behavior, kube-proxy now:
- parses flags
- reads the config file
- applies logging settings from the flags to the config loaded from file
- uses that merged config
			
			
This commit is contained in:
		@@ -235,7 +235,7 @@ func NewOptions() *Options {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Complete completes all the required options.
 | 
					// Complete completes all the required options.
 | 
				
			||||||
func (o *Options) Complete() error {
 | 
					func (o *Options) Complete(fs *pflag.FlagSet) error {
 | 
				
			||||||
	if len(o.ConfigFile) == 0 && len(o.WriteConfigTo) == 0 {
 | 
						if len(o.ConfigFile) == 0 && len(o.WriteConfigTo) == 0 {
 | 
				
			||||||
		o.config.HealthzBindAddress = addressFromDeprecatedFlags(o.config.HealthzBindAddress, o.healthzPort)
 | 
							o.config.HealthzBindAddress = addressFromDeprecatedFlags(o.config.HealthzBindAddress, o.healthzPort)
 | 
				
			||||||
		o.config.MetricsBindAddress = addressFromDeprecatedFlags(o.config.MetricsBindAddress, o.metricsPort)
 | 
							o.config.MetricsBindAddress = addressFromDeprecatedFlags(o.config.MetricsBindAddress, o.metricsPort)
 | 
				
			||||||
@@ -247,6 +247,14 @@ func (o *Options) Complete() error {
 | 
				
			|||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Before we overwrite the config which holds the parsed
 | 
				
			||||||
 | 
							// command line parameters, we need to copy all modified
 | 
				
			||||||
 | 
							// logging settings over to the loaded config (i.e.  logging
 | 
				
			||||||
 | 
							// command line flags have priority). Otherwise `--config
 | 
				
			||||||
 | 
							// ... -v=5` doesn't work (config resets verbosity even
 | 
				
			||||||
 | 
							// when it contains no logging settings).
 | 
				
			||||||
 | 
							copyLogsFromFlags(fs, &c.Logging)
 | 
				
			||||||
		o.config = c
 | 
							o.config = c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := o.initWatcher(); err != nil {
 | 
							if err := o.initWatcher(); err != nil {
 | 
				
			||||||
@@ -263,6 +271,39 @@ func (o *Options) Complete() error {
 | 
				
			|||||||
	return utilfeature.DefaultMutableFeatureGate.SetFromMap(o.config.FeatureGates)
 | 
						return utilfeature.DefaultMutableFeatureGate.SetFromMap(o.config.FeatureGates)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// copyLogsFromFlags applies the logging flags from the given flag set to the given
 | 
				
			||||||
 | 
					// configuration. Fields for which the corresponding flag was not used are left
 | 
				
			||||||
 | 
					// unmodified. For fields that have multiple values (like vmodule), the values from
 | 
				
			||||||
 | 
					// the flags get joined so that the command line flags have priority.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// TODO (pohly): move this to logsapi
 | 
				
			||||||
 | 
					func copyLogsFromFlags(from *pflag.FlagSet, to *logsapi.LoggingConfiguration) error {
 | 
				
			||||||
 | 
						var cloneFS pflag.FlagSet
 | 
				
			||||||
 | 
						logsapi.AddFlags(to, &cloneFS)
 | 
				
			||||||
 | 
						vmodule := to.VModule
 | 
				
			||||||
 | 
						to.VModule = nil
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						cloneFS.VisitAll(func(f *pflag.Flag) {
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							fsFlag := from.Lookup(f.Name)
 | 
				
			||||||
 | 
							if fsFlag == nil {
 | 
				
			||||||
 | 
								err = fmt.Errorf("logging flag %s not found in flag set", f.Name)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !fsFlag.Changed {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if setErr := f.Value.Set(fsFlag.Value.String()); setErr != nil {
 | 
				
			||||||
 | 
								err = fmt.Errorf("copying flag %s value: %v", f.Name, setErr)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						to.VModule = append(to.VModule, vmodule...)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Creates a new filesystem watcher and adds watches for the config file.
 | 
					// Creates a new filesystem watcher and adds watches for the config file.
 | 
				
			||||||
func (o *Options) initWatcher() error {
 | 
					func (o *Options) initWatcher() error {
 | 
				
			||||||
	fswatcher := filesystem.NewFsnotifyWatcher()
 | 
						fswatcher := filesystem.NewFsnotifyWatcher()
 | 
				
			||||||
@@ -476,7 +517,7 @@ with the apiserver API to configure the proxy.`,
 | 
				
			|||||||
				return fmt.Errorf("failed os init: %w", err)
 | 
									return fmt.Errorf("failed os init: %w", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := opts.Complete(); err != nil {
 | 
								if err := opts.Complete(cmd.Flags()); err != nil {
 | 
				
			||||||
				return fmt.Errorf("failed complete: %w", err)
 | 
									return fmt.Errorf("failed complete: %w", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ import (
 | 
				
			|||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/spf13/pflag"
 | 
				
			||||||
	v1 "k8s.io/api/core/v1"
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
@@ -563,7 +564,7 @@ detectLocalMode: "BridgeInterface"`)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		opt := NewOptions()
 | 
							opt := NewOptions()
 | 
				
			||||||
		opt.ConfigFile = file.Name()
 | 
							opt.ConfigFile = file.Name()
 | 
				
			||||||
		err = opt.Complete()
 | 
							err = opt.Complete(new(pflag.FlagSet))
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			t.Fatal(err)
 | 
								t.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,13 +19,18 @@ package app
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/google/go-cmp/cmp"
 | 
						"github.com/google/go-cmp/cmp"
 | 
				
			||||||
 | 
						"github.com/spf13/pflag"
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	v1 "k8s.io/api/core/v1"
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/api/resource"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	clientsetfake "k8s.io/client-go/kubernetes/fake"
 | 
						clientsetfake "k8s.io/client-go/kubernetes/fake"
 | 
				
			||||||
	componentbaseconfig "k8s.io/component-base/config"
 | 
						componentbaseconfig "k8s.io/component-base/config"
 | 
				
			||||||
@@ -364,6 +369,125 @@ func TestProcessHostnameOverrideFlag(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestOptionsComplete checks that command line flags are combined with a
 | 
				
			||||||
 | 
					// config properly.
 | 
				
			||||||
 | 
					func TestOptionsComplete(t *testing.T) {
 | 
				
			||||||
 | 
						header := `apiVersion: kubeproxy.config.k8s.io/v1alpha1
 | 
				
			||||||
 | 
					kind: KubeProxyConfiguration
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Determine default config (depends on platform defaults).
 | 
				
			||||||
 | 
						o := NewOptions()
 | 
				
			||||||
 | 
						require.NoError(t, o.Complete(new(pflag.FlagSet)))
 | 
				
			||||||
 | 
						expected := o.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := header + `logging:
 | 
				
			||||||
 | 
					  format: json
 | 
				
			||||||
 | 
					  flushFrequency: 1s
 | 
				
			||||||
 | 
					  verbosity: 10
 | 
				
			||||||
 | 
					  vmodule:
 | 
				
			||||||
 | 
					  - filePattern: foo.go
 | 
				
			||||||
 | 
					    verbosity: 6
 | 
				
			||||||
 | 
					  - filePattern: bar.go
 | 
				
			||||||
 | 
					    verbosity: 8
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
						expectedLoggingConfig := logsapi.LoggingConfiguration{
 | 
				
			||||||
 | 
							Format:         "json",
 | 
				
			||||||
 | 
							FlushFrequency: logsapi.TimeOrMetaDuration{Duration: metav1.Duration{Duration: time.Second}, SerializeAsString: true},
 | 
				
			||||||
 | 
							Verbosity:      10,
 | 
				
			||||||
 | 
							VModule: []logsapi.VModuleItem{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									FilePattern: "foo.go",
 | 
				
			||||||
 | 
									Verbosity:   6,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									FilePattern: "bar.go",
 | 
				
			||||||
 | 
									Verbosity:   8,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Options: logsapi.FormatOptions{
 | 
				
			||||||
 | 
								JSON: logsapi.JSONOptions{
 | 
				
			||||||
 | 
									InfoBufferSize: resource.QuantityValue{Quantity: resource.MustParse("0")},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for name, tc := range map[string]struct {
 | 
				
			||||||
 | 
							config   string
 | 
				
			||||||
 | 
							flags    []string
 | 
				
			||||||
 | 
							expected *kubeproxyconfig.KubeProxyConfiguration
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							"empty": {
 | 
				
			||||||
 | 
								expected: expected,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"empty-config": {
 | 
				
			||||||
 | 
								config:   header,
 | 
				
			||||||
 | 
								expected: expected,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"logging-config": {
 | 
				
			||||||
 | 
								config: config,
 | 
				
			||||||
 | 
								expected: func() *kubeproxyconfig.KubeProxyConfiguration {
 | 
				
			||||||
 | 
									c := expected.DeepCopy()
 | 
				
			||||||
 | 
									c.Logging = *expectedLoggingConfig.DeepCopy()
 | 
				
			||||||
 | 
									return c
 | 
				
			||||||
 | 
								}(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"flags": {
 | 
				
			||||||
 | 
								flags: []string{
 | 
				
			||||||
 | 
									"-v=7",
 | 
				
			||||||
 | 
									"--vmodule", "goo.go=8",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: func() *kubeproxyconfig.KubeProxyConfiguration {
 | 
				
			||||||
 | 
									c := expected.DeepCopy()
 | 
				
			||||||
 | 
									c.Logging.Verbosity = 7
 | 
				
			||||||
 | 
									c.Logging.VModule = append(c.Logging.VModule, logsapi.VModuleItem{
 | 
				
			||||||
 | 
										FilePattern: "goo.go",
 | 
				
			||||||
 | 
										Verbosity:   8,
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									return c
 | 
				
			||||||
 | 
								}(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"both": {
 | 
				
			||||||
 | 
								config: config,
 | 
				
			||||||
 | 
								flags: []string{
 | 
				
			||||||
 | 
									"-v=7",
 | 
				
			||||||
 | 
									"--vmodule", "goo.go=8",
 | 
				
			||||||
 | 
									"--ipvs-scheduler", "some-scheduler", // Overwritten by config.
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: func() *kubeproxyconfig.KubeProxyConfiguration {
 | 
				
			||||||
 | 
									c := expected.DeepCopy()
 | 
				
			||||||
 | 
									c.Logging = *expectedLoggingConfig.DeepCopy()
 | 
				
			||||||
 | 
									// Flag wins.
 | 
				
			||||||
 | 
									c.Logging.Verbosity = 7
 | 
				
			||||||
 | 
									// Flag and config get merged with command line flags first.
 | 
				
			||||||
 | 
									c.Logging.VModule = append([]logsapi.VModuleItem{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											FilePattern: "goo.go",
 | 
				
			||||||
 | 
											Verbosity:   8,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									}, c.Logging.VModule...)
 | 
				
			||||||
 | 
									return c
 | 
				
			||||||
 | 
								}(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						} {
 | 
				
			||||||
 | 
							t.Run(name, func(t *testing.T) {
 | 
				
			||||||
 | 
								options := NewOptions()
 | 
				
			||||||
 | 
								fs := new(pflag.FlagSet)
 | 
				
			||||||
 | 
								options.AddFlags(fs)
 | 
				
			||||||
 | 
								flags := tc.flags
 | 
				
			||||||
 | 
								if len(tc.config) > 0 {
 | 
				
			||||||
 | 
									tmp := t.TempDir()
 | 
				
			||||||
 | 
									configFile := path.Join(tmp, "kube-proxy.conf")
 | 
				
			||||||
 | 
									require.NoError(t, ioutil.WriteFile(configFile, []byte(tc.config), 0666))
 | 
				
			||||||
 | 
									flags = append(flags, "--config", configFile)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								require.NoError(t, fs.Parse(flags))
 | 
				
			||||||
 | 
								require.NoError(t, options.Complete(fs))
 | 
				
			||||||
 | 
								assert.Equal(t, tc.expected, options.config)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type fakeProxyServerLongRun struct{}
 | 
					type fakeProxyServerLongRun struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Run runs the specified ProxyServer.
 | 
					// Run runs the specified ProxyServer.
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user