...

Source file src/k8s.io/kubernetes/pkg/volume/downwardapi/downwardapi_test.go

Documentation: k8s.io/kubernetes/pkg/volume/downwardapi

     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  }
   406  

View as plain text