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