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 }