k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubelet/app/options/options_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package options
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/spf13/pflag"
    26  
    27  	cliflag "k8s.io/component-base/cli/flag"
    28  	"k8s.io/kubernetes/pkg/kubelet/config"
    29  )
    30  
    31  func newKubeletServerOrDie() *KubeletServer {
    32  	s, err := NewKubeletServer()
    33  	if err != nil {
    34  		panic(err)
    35  	}
    36  	return s
    37  }
    38  
    39  // TestRoundTrip ensures that flag values from the Kubelet can be serialized
    40  // to arguments and then read back and have the same value. Also catches cases
    41  // where the default value reported by the flag is not actually allowed to be
    42  // specified.
    43  func TestRoundTrip(t *testing.T) {
    44  	testCases := []struct {
    45  		name          string
    46  		inputFlags    func() *KubeletServer
    47  		outputFlags   func() *KubeletServer
    48  		flagDefaulter func(*pflag.FlagSet)
    49  		skipDefault   bool
    50  		err           bool
    51  		expectArgs    bool
    52  	}{
    53  		{
    54  			name:          "default flags are eliminated",
    55  			inputFlags:    newKubeletServerOrDie,
    56  			outputFlags:   newKubeletServerOrDie,
    57  			flagDefaulter: newKubeletServerOrDie().AddFlags,
    58  			err:           false,
    59  			expectArgs:    false,
    60  		},
    61  		{
    62  			name:          "default flag values round trip",
    63  			inputFlags:    newKubeletServerOrDie,
    64  			outputFlags:   newKubeletServerOrDie,
    65  			flagDefaulter: func(*pflag.FlagSet) {},
    66  			err:           false,
    67  			expectArgs:    true,
    68  		},
    69  		{
    70  			name: "nil address does not fail for optional argument",
    71  			inputFlags: func() *KubeletServer {
    72  				s := newKubeletServerOrDie()
    73  				s.HealthzBindAddress = ""
    74  				return s
    75  			},
    76  			outputFlags: func() *KubeletServer {
    77  				s := newKubeletServerOrDie()
    78  				s.HealthzBindAddress = ""
    79  				return s
    80  			},
    81  			flagDefaulter: func(*pflag.FlagSet) {},
    82  			err:           false,
    83  			expectArgs:    true,
    84  		},
    85  	}
    86  	for _, testCase := range testCases {
    87  		modifiedFlags := testCase.inputFlags()
    88  		args := asArgs(modifiedFlags.AddFlags, testCase.flagDefaulter)
    89  		if testCase.expectArgs != (len(args) > 0) {
    90  			t.Errorf("%s: unexpected args: %v", testCase.name, args)
    91  			continue
    92  		}
    93  		t.Logf("%s: args: %v", testCase.name, args)
    94  		flagSet := pflag.NewFlagSet("output", pflag.ContinueOnError)
    95  		outputFlags := testCase.outputFlags()
    96  		outputFlags.AddFlags(flagSet)
    97  		if err := flagSet.Parse(args); err != nil {
    98  			if !testCase.err {
    99  				t.Errorf("%s: unexpected flag error: %v", testCase.name, err)
   100  			}
   101  			continue
   102  		}
   103  		if !reflect.DeepEqual(modifiedFlags, outputFlags) {
   104  			t.Errorf("%s: flags did not round trip: %s", testCase.name, cmp.Diff(modifiedFlags, outputFlags))
   105  			continue
   106  		}
   107  	}
   108  }
   109  
   110  func asArgs(fn, defaultFn func(*pflag.FlagSet)) []string {
   111  	fs := pflag.NewFlagSet("extended", pflag.ContinueOnError)
   112  	fn(fs)
   113  	defaults := pflag.NewFlagSet("defaults", pflag.ContinueOnError)
   114  	defaultFn(defaults)
   115  	var args []string
   116  	fs.VisitAll(func(flag *pflag.Flag) {
   117  		// if the flag implements cliflag.OmitEmpty and the value is Empty, then just omit it from the command line
   118  		if omit, ok := flag.Value.(cliflag.OmitEmpty); ok && omit.Empty() {
   119  			return
   120  		}
   121  		s := flag.Value.String()
   122  		// if the flag has the same value as the default, we can omit it without changing the meaning of the command line
   123  		var defaultValue string
   124  		if defaultFlag := defaults.Lookup(flag.Name); defaultFlag != nil {
   125  			defaultValue = defaultFlag.Value.String()
   126  			if s == defaultValue {
   127  				return
   128  			}
   129  		}
   130  		// if the flag is a string slice, each element is specified with an independent flag invocation
   131  		if values, err := fs.GetStringSlice(flag.Name); err == nil {
   132  			for _, s := range values {
   133  				args = append(args, fmt.Sprintf("--%s=%s", flag.Name, s))
   134  			}
   135  		} else {
   136  			if len(s) == 0 {
   137  				s = defaultValue
   138  			}
   139  			args = append(args, fmt.Sprintf("--%s=%s", flag.Name, s))
   140  		}
   141  	})
   142  	return args
   143  }
   144  
   145  func TestValidateKubeletFlags(t *testing.T) {
   146  	tests := []struct {
   147  		name   string
   148  		error  bool
   149  		labels map[string]string
   150  	}{
   151  		{
   152  			name:  "Invalid kubernetes.io label",
   153  			error: true,
   154  			labels: map[string]string{
   155  				"beta.kubernetes.io/metadata-proxy-ready": "true",
   156  			},
   157  		},
   158  		{
   159  			name:  "Valid label outside of kubernetes.io and k8s.io",
   160  			error: false,
   161  			labels: map[string]string{
   162  				"cloud.google.com/metadata-proxy-ready": "true",
   163  			},
   164  		},
   165  		{
   166  			name:   "Empty label list",
   167  			error:  false,
   168  			labels: map[string]string{},
   169  		},
   170  		{
   171  			name:  "Invalid label",
   172  			error: true,
   173  			labels: map[string]string{
   174  				"cloud.google.com/repository": "kubernetes/kubernetes",
   175  			},
   176  		},
   177  	}
   178  
   179  	for _, tt := range tests {
   180  		t.Run(tt.name, func(t *testing.T) {
   181  			err := ValidateKubeletFlags(&KubeletFlags{
   182  				ContainerRuntimeOptions: config.ContainerRuntimeOptions{},
   183  				NodeLabels:              tt.labels,
   184  			})
   185  
   186  			if tt.error && err == nil {
   187  				t.Errorf("ValidateKubeletFlags should have failed with labels: %+v", tt.labels)
   188  			}
   189  
   190  			if !tt.error && err != nil {
   191  				t.Errorf("ValidateKubeletFlags should not have failed with labels: %+v", tt.labels)
   192  			}
   193  		})
   194  	}
   195  
   196  }