1
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 , 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
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
108
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 , 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
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
308
309
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
388 if err = test.mounter.SetUp(volume.MounterArgs{}); err != nil {
389 test.t.Errorf("Failed to re-setup volume: %v", err)
390 }
391
392
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 }
406
View as plain text