k8s.io/kubernetes@v1.29.3/pkg/kubelet/kubeletconfig/configfiles/configfiles_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 configfiles 18 19 import ( 20 "errors" 21 "fmt" 22 "path/filepath" 23 goruntime "runtime" 24 "testing" 25 26 apiequality "k8s.io/apimachinery/pkg/api/equality" 27 "k8s.io/apimachinery/pkg/runtime" 28 kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" 29 kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" 30 kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/config/scheme" 31 utilfiles "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files" 32 utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test" 33 utilfs "k8s.io/kubernetes/pkg/util/filesystem" 34 ) 35 36 const configDir = "/test-config-dir" 37 const relativePath = "relative/path/test" 38 const kubeletFile = "kubelet" 39 40 func TestLoad(t *testing.T) { 41 cases := []struct { 42 desc string 43 file *string 44 expect *kubeletconfig.KubeletConfiguration 45 err string 46 strictErr bool 47 skipOnWindows bool 48 }{ 49 // missing file 50 { 51 desc: "missing file", 52 err: "failed to read", 53 }, 54 // empty file 55 { 56 desc: "empty file", 57 file: newString(``), 58 err: "was empty", 59 }, 60 // invalid format 61 { 62 desc: "invalid yaml", 63 file: newString(`*`), 64 err: "failed to decode", 65 }, 66 { 67 desc: "invalid json", 68 file: newString(`{*`), 69 err: "failed to decode", 70 }, 71 // invalid object 72 { 73 desc: "missing kind", 74 file: newString(`{"apiVersion":"kubelet.config.k8s.io/v1beta1"}`), 75 err: "failed to decode", 76 }, 77 { 78 desc: "missing version", 79 file: newString(`{"kind":"KubeletConfiguration"}`), 80 err: "failed to decode", 81 }, 82 { 83 desc: "unregistered kind", 84 file: newString(`{"kind":"BogusKind","apiVersion":"kubelet.config.k8s.io/v1beta1"}`), 85 err: "failed to decode", 86 }, 87 { 88 desc: "unregistered version", 89 file: newString(`{"kind":"KubeletConfiguration","apiVersion":"bogusversion"}`), 90 err: "failed to decode", 91 }, 92 93 // empty object with correct kind and version should result in the defaults for that kind and version 94 { 95 desc: "default from yaml", 96 file: newString(`kind: KubeletConfiguration 97 apiVersion: kubelet.config.k8s.io/v1beta1`), 98 expect: newConfig(t), 99 skipOnWindows: true, 100 }, 101 { 102 desc: "default from json", 103 file: newString(`{"kind":"KubeletConfiguration","apiVersion":"kubelet.config.k8s.io/v1beta1"}`), 104 expect: newConfig(t), 105 skipOnWindows: true, 106 }, 107 108 // relative path 109 { 110 desc: "yaml, relative path is resolved", 111 file: newString(fmt.Sprintf(`kind: KubeletConfiguration 112 apiVersion: kubelet.config.k8s.io/v1beta1 113 staticPodPath: %s`, relativePath)), 114 expect: func() *kubeletconfig.KubeletConfiguration { 115 kc := newConfig(t) 116 kc.StaticPodPath = filepath.Join(configDir, relativePath) 117 return kc 118 }(), 119 skipOnWindows: true, 120 }, 121 { 122 desc: "json, relative path is resolved", 123 file: newString(fmt.Sprintf(`{"kind":"KubeletConfiguration","apiVersion":"kubelet.config.k8s.io/v1beta1","staticPodPath":"%s"}`, relativePath)), 124 expect: func() *kubeletconfig.KubeletConfiguration { 125 kc := newConfig(t) 126 kc.StaticPodPath = filepath.Join(configDir, relativePath) 127 return kc 128 }(), 129 skipOnWindows: true, 130 }, 131 { 132 // This should fail from v1beta2+ 133 desc: "duplicate field", 134 file: newString(fmt.Sprintf(`kind: KubeletConfiguration 135 apiVersion: kubelet.config.k8s.io/v1beta1 136 staticPodPath: %s 137 staticPodPath: %s/foo`, relativePath, relativePath)), 138 // err: `key "staticPodPath" already set`, 139 // strictErr: true, 140 expect: func() *kubeletconfig.KubeletConfiguration { 141 kc := newConfig(t) 142 kc.StaticPodPath = filepath.Join(configDir, relativePath, "foo") 143 return kc 144 }(), 145 skipOnWindows: true, 146 }, 147 { 148 // This should fail from v1beta2+ 149 desc: "unknown field", 150 file: newString(`kind: KubeletConfiguration 151 apiVersion: kubelet.config.k8s.io/v1beta1 152 foo: bar`), 153 // err: "found unknown field: foo", 154 // strictErr: true, 155 expect: newConfig(t), 156 skipOnWindows: true, 157 }, 158 } 159 160 for _, c := range cases { 161 t.Run(c.desc, func(t *testing.T) { 162 // Skip tests that fail on Windows, as discussed during the SIG Testing meeting from January 10, 2023 163 if c.skipOnWindows && goruntime.GOOS == "windows" { 164 t.Skip("Skipping test that fails on Windows") 165 } 166 167 fs := utilfs.NewTempFs() 168 fs.MkdirAll(configDir, 0777) 169 path := filepath.Join(configDir, kubeletFile) 170 if c.file != nil { 171 if err := addFile(fs, path, *c.file); err != nil { 172 t.Fatalf("unexpected error: %v", err) 173 } 174 } 175 loader, err := NewFsLoader(fs, path) 176 if err != nil { 177 t.Fatalf("unexpected error: %v", err) 178 } 179 kc, err := loader.Load() 180 181 if c.strictErr && !runtime.IsStrictDecodingError(errors.Unwrap(err)) { 182 t.Fatalf("got error: %v, want strict decoding error", err) 183 } 184 if utiltest.SkipRest(t, c.desc, err, c.err) { 185 return 186 } 187 if !apiequality.Semantic.DeepEqual(c.expect, kc) { 188 t.Fatalf("expect %#v but got %#v", *c.expect, *kc) 189 } 190 }) 191 } 192 } 193 194 func TestResolveRelativePaths(t *testing.T) { 195 absolutePath := filepath.Join(configDir, "absolute") 196 cases := []struct { 197 desc string 198 path string 199 expect string 200 skipOnWindows bool 201 }{ 202 {"empty path", "", "", false}, 203 {"absolute path", absolutePath, absolutePath, true}, 204 {"relative path", relativePath, filepath.Join(configDir, relativePath), false}, 205 } 206 207 paths := kubeletconfig.KubeletConfigurationPathRefs(newConfig(t)) 208 if len(paths) == 0 { 209 t.Fatalf("requires at least one path field to exist in the KubeletConfiguration type") 210 } 211 for _, c := range cases { 212 t.Run(c.desc, func(t *testing.T) { 213 // Skip tests that fail on Windows, as discussed during the SIG Testing meeting from January 10, 2023 214 if c.skipOnWindows && goruntime.GOOS == "windows" { 215 t.Skip("Skipping test that fails on Windows") 216 } 217 218 // set the path, resolve it, and check if it resolved as we would expect 219 *(paths[0]) = c.path 220 resolveRelativePaths(paths, configDir) 221 if *(paths[0]) != c.expect { 222 t.Fatalf("expect %s but got %s", c.expect, *(paths[0])) 223 } 224 }) 225 } 226 } 227 228 func newString(s string) *string { 229 return &s 230 } 231 232 func addFile(fs utilfs.Filesystem, path string, file string) error { 233 if err := utilfiles.EnsureDir(fs, filepath.Dir(path)); err != nil { 234 return err 235 } 236 return utilfiles.ReplaceFile(fs, path, []byte(file)) 237 } 238 239 func newConfig(t *testing.T) *kubeletconfig.KubeletConfiguration { 240 kubeletScheme, _, err := kubeletscheme.NewSchemeAndCodecs() 241 if err != nil { 242 t.Fatalf("unexpected error: %v", err) 243 } 244 // get the built-in default configuration 245 external := &kubeletconfigv1beta1.KubeletConfiguration{} 246 kubeletScheme.Default(external) 247 kc := &kubeletconfig.KubeletConfiguration{} 248 err = kubeletScheme.Convert(external, kc, nil) 249 if err != nil { 250 t.Fatalf("unexpected error: %v", err) 251 } 252 return kc 253 }