k8s.io/kubernetes@v1.29.3/pkg/volume/downwardapi/downwardapi_test.go (about) 1 /* 2 Copyright 2015 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 downwardapi 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "runtime" 25 "testing" 26 27 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/types" 30 clientset "k8s.io/client-go/kubernetes" 31 "k8s.io/client-go/kubernetes/fake" 32 utiltesting "k8s.io/client-go/util/testing" 33 "k8s.io/kubernetes/pkg/fieldpath" 34 "k8s.io/kubernetes/pkg/volume" 35 "k8s.io/kubernetes/pkg/volume/emptydir" 36 volumetest "k8s.io/kubernetes/pkg/volume/testing" 37 ) 38 39 const ( 40 downwardAPIDir = "..data" 41 testPodUID = types.UID("test_pod_uid") 42 testNamespace = "test_metadata_namespace" 43 testName = "test_metadata_name" 44 ) 45 46 func newTestHost(t *testing.T, clientset clientset.Interface) (string, volume.VolumeHost) { 47 tempDir, err := utiltesting.MkTmpdir("downwardApi_volume_test.") 48 if err != nil { 49 t.Fatalf("can't make a temp rootdir: %v", err) 50 } 51 return tempDir, volumetest.NewFakeVolumeHost(t, tempDir, clientset, emptydir.ProbeVolumePlugins()) 52 } 53 54 func TestCanSupport(t *testing.T) { 55 pluginMgr := volume.VolumePluginMgr{} 56 tmpDir, host := newTestHost(t, nil) 57 defer os.RemoveAll(tmpDir) 58 pluginMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 59 60 plugin, err := pluginMgr.FindPluginByName(downwardAPIPluginName) 61 if err != nil { 62 t.Fatal("Can't find the plugin by name") 63 } 64 if plugin.GetPluginName() != downwardAPIPluginName { 65 t.Errorf("Wrong name: %s", plugin.GetPluginName()) 66 } 67 if !plugin.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{DownwardAPI: &v1.DownwardAPIVolumeSource{}}}}) { 68 t.Errorf("Expected true") 69 } 70 if plugin.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{}}}) { 71 t.Errorf("Expected false") 72 } 73 } 74 75 func TestDownwardAPI(t *testing.T) { 76 // Skip tests that fail on Windows, as discussed during the SIG Testing meeting from January 10, 2023 77 if runtime.GOOS == "windows" { 78 t.Skip("Skipping test that fails on Windows") 79 } 80 81 labels1 := map[string]string{ 82 "key1": "value1", 83 "key2": "value2", 84 } 85 labels2 := map[string]string{ 86 "key1": "value1", 87 "key2": "value2", 88 "key3": "value3", 89 } 90 annotations := map[string]string{ 91 "a1": "value1", 92 "a2": "value2", 93 "multiline": "c\nb\na", 94 } 95 testCases := []struct { 96 name string 97 files map[string]string 98 modes map[string]int32 99 podLabels map[string]string 100 podAnnotations map[string]string 101 steps []testStep 102 }{ 103 { 104 name: "test_labels", 105 files: map[string]string{"labels": "metadata.labels"}, 106 podLabels: labels1, 107 steps: []testStep{ 108 // for steps that involve files, stepName is also 109 // used as the name of the file to verify 110 verifyMapInFile{stepName{"labels"}, labels1}, 111 }, 112 }, 113 { 114 name: "test_annotations", 115 files: map[string]string{"annotations": "metadata.annotations"}, 116 podAnnotations: annotations, 117 steps: []testStep{ 118 verifyMapInFile{stepName{"annotations"}, annotations}, 119 }, 120 }, 121 { 122 name: "test_name", 123 files: map[string]string{"name_file_name": "metadata.name"}, 124 steps: []testStep{ 125 verifyLinesInFile{stepName{"name_file_name"}, testName}, 126 }, 127 }, 128 { 129 name: "test_namespace", 130 files: map[string]string{"namespace_file_name": "metadata.namespace"}, 131 steps: []testStep{ 132 verifyLinesInFile{stepName{"namespace_file_name"}, testNamespace}, 133 }, 134 }, 135 { 136 name: "test_write_twice_no_update", 137 files: map[string]string{"labels": "metadata.labels"}, 138 podLabels: labels1, 139 steps: []testStep{ 140 reSetUp{stepName{"resetup"}, false, nil}, 141 verifyMapInFile{stepName{"labels"}, labels1}, 142 }, 143 }, 144 { 145 name: "test_write_twice_with_update", 146 files: map[string]string{"labels": "metadata.labels"}, 147 podLabels: labels1, 148 steps: []testStep{ 149 reSetUp{stepName{"resetup"}, true, labels2}, 150 verifyMapInFile{stepName{"labels"}, labels2}, 151 }, 152 }, 153 { 154 name: "test_write_with_unix_path", 155 files: map[string]string{ 156 "these/are/my/labels": "metadata.labels", 157 "these/are/your/annotations": "metadata.annotations", 158 }, 159 podLabels: labels1, 160 podAnnotations: annotations, 161 steps: []testStep{ 162 verifyMapInFile{stepName{"these/are/my/labels"}, labels1}, 163 verifyMapInFile{stepName{"these/are/your/annotations"}, annotations}, 164 }, 165 }, 166 { 167 name: "test_write_with_two_consecutive_slashes_in_the_path", 168 files: map[string]string{"this//labels": "metadata.labels"}, 169 podLabels: labels1, 170 steps: []testStep{ 171 verifyMapInFile{stepName{"this/labels"}, labels1}, 172 }, 173 }, 174 { 175 name: "test_default_mode", 176 files: map[string]string{"name_file_name": "metadata.name"}, 177 steps: []testStep{ 178 verifyMode{stepName{"name_file_name"}, 0644}, 179 }, 180 }, 181 { 182 name: "test_item_mode", 183 files: map[string]string{"name_file_name": "metadata.name"}, 184 modes: map[string]int32{"name_file_name": 0400}, 185 steps: []testStep{ 186 verifyMode{stepName{"name_file_name"}, 0400}, 187 }, 188 }, 189 } 190 for _, testCase := range testCases { 191 test := newDownwardAPITest(t, testCase.name, testCase.files, testCase.podLabels, testCase.podAnnotations, testCase.modes) 192 for _, step := range testCase.steps { 193 test.t.Logf("Test case: %q Step: %q", testCase.name, step.getName()) 194 step.run(test) 195 } 196 test.tearDown() 197 } 198 } 199 200 type downwardAPITest struct { 201 t *testing.T 202 name string 203 plugin volume.VolumePlugin 204 pod *v1.Pod 205 mounter volume.Mounter 206 volumePath string 207 rootDir string 208 } 209 210 func newDownwardAPITest(t *testing.T, name string, volumeFiles, podLabels, podAnnotations map[string]string, modes map[string]int32) *downwardAPITest { 211 defaultMode := int32(0644) 212 var files []v1.DownwardAPIVolumeFile 213 for path, fieldPath := range volumeFiles { 214 file := v1.DownwardAPIVolumeFile{ 215 Path: path, 216 FieldRef: &v1.ObjectFieldSelector{ 217 FieldPath: fieldPath, 218 }, 219 } 220 if mode, found := modes[path]; found { 221 file.Mode = &mode 222 } 223 files = append(files, file) 224 } 225 podMeta := metav1.ObjectMeta{ 226 Name: testName, 227 Namespace: testNamespace, 228 Labels: podLabels, 229 Annotations: podAnnotations, 230 } 231 clientset := fake.NewSimpleClientset(&v1.Pod{ObjectMeta: podMeta}) 232 233 pluginMgr := volume.VolumePluginMgr{} 234 rootDir, host := newTestHost(t, clientset) 235 pluginMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 236 plugin, err := pluginMgr.FindPluginByName(downwardAPIPluginName) 237 if err != nil { 238 t.Fatal("Can't find the plugin by name") 239 } 240 241 volumeSpec := &v1.Volume{ 242 Name: name, 243 VolumeSource: v1.VolumeSource{ 244 DownwardAPI: &v1.DownwardAPIVolumeSource{ 245 DefaultMode: &defaultMode, 246 Items: files, 247 }, 248 }, 249 } 250 podMeta.UID = testPodUID 251 pod := &v1.Pod{ObjectMeta: podMeta} 252 mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}) 253 if err != nil { 254 t.Errorf("Failed to make a new Mounter: %v", err) 255 } 256 if mounter == nil { 257 t.Fatalf("Got a nil Mounter") 258 } 259 260 volumePath := mounter.GetPath() 261 262 err = mounter.SetUp(volume.MounterArgs{}) 263 if err != nil { 264 t.Errorf("Failed to setup volume: %v", err) 265 } 266 267 // downwardAPI volume should create its own empty wrapper path 268 podWrapperMetadataDir := fmt.Sprintf("%v/pods/%v/plugins/kubernetes.io~empty-dir/wrapped_%v", rootDir, testPodUID, name) 269 270 if _, err := os.Stat(podWrapperMetadataDir); err != nil { 271 if os.IsNotExist(err) { 272 t.Errorf("SetUp() failed, empty-dir wrapper path was not created: %s", podWrapperMetadataDir) 273 } else { 274 t.Errorf("SetUp() failed: %v", err) 275 } 276 } 277 278 return &downwardAPITest{ 279 t: t, 280 plugin: plugin, 281 pod: pod, 282 mounter: mounter, 283 volumePath: volumePath, 284 rootDir: rootDir, 285 } 286 } 287 288 func (test *downwardAPITest) tearDown() { 289 unmounter, err := test.plugin.NewUnmounter(test.name, testPodUID) 290 if err != nil { 291 test.t.Errorf("Failed to make a new Unmounter: %v", err) 292 } 293 if unmounter == nil { 294 test.t.Fatalf("Got a nil Unmounter") 295 } 296 297 if err := unmounter.TearDown(); err != nil { 298 test.t.Errorf("Expected success, got: %v", err) 299 } 300 if _, err := os.Stat(test.volumePath); err == nil { 301 test.t.Errorf("TearDown() failed, volume path still exists: %s", test.volumePath) 302 } else if !os.IsNotExist(err) { 303 test.t.Errorf("TearDown() failed: %v", err) 304 } 305 os.RemoveAll(test.rootDir) 306 } 307 308 // testStep represents a named step of downwardAPITest. 309 // For steps that deal with files, step name also serves 310 // as the name of the file that's used by the step. 311 type testStep interface { 312 getName() string 313 run(*downwardAPITest) 314 } 315 316 type stepName struct { 317 name string 318 } 319 320 func (step stepName) getName() string { return step.name } 321 322 func doVerifyLinesInFile(t *testing.T, volumePath, filename string, expected string) { 323 data, err := ioutil.ReadFile(filepath.Join(volumePath, filename)) 324 if err != nil { 325 t.Errorf(err.Error()) 326 return 327 } 328 actualStr := string(data) 329 expectedStr := expected 330 if actualStr != expectedStr { 331 t.Errorf("Found `%s`, expected `%s`", actualStr, expectedStr) 332 } 333 } 334 335 type verifyLinesInFile struct { 336 stepName 337 expected string 338 } 339 340 func (step verifyLinesInFile) run(test *downwardAPITest) { 341 doVerifyLinesInFile(test.t, test.volumePath, step.name, step.expected) 342 } 343 344 type verifyMapInFile struct { 345 stepName 346 expected map[string]string 347 } 348 349 func (step verifyMapInFile) run(test *downwardAPITest) { 350 doVerifyLinesInFile(test.t, test.volumePath, step.name, fieldpath.FormatMap(step.expected)) 351 } 352 353 type verifyMode struct { 354 stepName 355 expectedMode int32 356 } 357 358 func (step verifyMode) run(test *downwardAPITest) { 359 fileInfo, err := os.Stat(filepath.Join(test.volumePath, step.name)) 360 if err != nil { 361 test.t.Errorf(err.Error()) 362 return 363 } 364 365 actualMode := fileInfo.Mode() 366 expectedMode := os.FileMode(step.expectedMode) 367 if actualMode != expectedMode { 368 test.t.Errorf("Found mode `%v` expected %v", actualMode, expectedMode) 369 } 370 } 371 372 type reSetUp struct { 373 stepName 374 linkShouldChange bool 375 newLabels map[string]string 376 } 377 378 func (step reSetUp) run(test *downwardAPITest) { 379 if step.newLabels != nil { 380 test.pod.ObjectMeta.Labels = step.newLabels 381 } 382 383 currentTarget, err := os.Readlink(filepath.Join(test.volumePath, downwardAPIDir)) 384 if err != nil { 385 test.t.Errorf("labels file should be a link... %s\n", err.Error()) 386 } 387 388 // now re-run Setup 389 if err = test.mounter.SetUp(volume.MounterArgs{}); err != nil { 390 test.t.Errorf("Failed to re-setup volume: %v", err) 391 } 392 393 // get the link of the link 394 currentTarget2, err := os.Readlink(filepath.Join(test.volumePath, downwardAPIDir)) 395 if err != nil { 396 test.t.Errorf(".current should be a link... %s\n", err.Error()) 397 } 398 399 switch { 400 case step.linkShouldChange && currentTarget2 == currentTarget: 401 test.t.Errorf("Got and update between the two Setup... Target link should NOT be the same\n") 402 case !step.linkShouldChange && currentTarget2 != currentTarget: 403 test.t.Errorf("No update between the two Setup... Target link should be the same %s %s\n", 404 currentTarget, currentTarget2) 405 } 406 }