...

Source file src/k8s.io/kubernetes/pkg/volume/util/hostutil/hostutil_test.go

Documentation: k8s.io/kubernetes/pkg/volume/util/hostutil

     1  /*
     2  Copyright 2023 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 hostutil
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"net"
    23  	"os"
    24  	"path/filepath"
    25  	goruntime "runtime"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"k8s.io/mount-utils"
    31  	"k8s.io/utils/exec"
    32  )
    33  
    34  // fakeMounter implements mount.Interface for tests.
    35  type fakeMounter struct {
    36  	mount.FakeMounter
    37  	mountRefs  []string
    38  	raiseError bool
    39  }
    40  
    41  // GetMountRefs finds all mount references to the path, returns a
    42  // list of paths.
    43  func (f *fakeMounter) GetMountRefs(pathname string) ([]string, error) {
    44  	if f.raiseError {
    45  		return nil, errors.New("Expected error.")
    46  	}
    47  
    48  	return f.mountRefs, nil
    49  }
    50  
    51  func TestDeviceNameFromMount(t *testing.T) {
    52  	hu := NewHostUtil()
    53  	path := "/tmp/foo"
    54  	if goruntime.GOOS == "windows" {
    55  		path = "C:" + path
    56  	}
    57  
    58  	testCases := map[string]struct {
    59  		mountRefs     []string
    60  		expectedPath  string
    61  		raiseError    bool
    62  		expectedError string
    63  	}{
    64  		"GetMountRefs error": {
    65  			raiseError:    true,
    66  			expectedError: "Expected error.",
    67  		},
    68  		"No Refs error": {
    69  			expectedError: fmt.Sprintf("directory %s is not mounted", path),
    70  		},
    71  		"No Matching refs": {
    72  			mountRefs:    []string{filepath.Join("foo", "lish")},
    73  			expectedPath: filepath.Base(path),
    74  		},
    75  		"Matched ref": {
    76  			mountRefs:    []string{filepath.Join(path, "lish")},
    77  			expectedPath: "lish",
    78  		},
    79  	}
    80  
    81  	for name, tc := range testCases {
    82  		t.Run(name, func(t *testing.T) {
    83  			mounter := &fakeMounter{
    84  				mountRefs:  tc.mountRefs,
    85  				raiseError: tc.raiseError,
    86  			}
    87  
    88  			path, err := hu.GetDeviceNameFromMount(mounter, path, path)
    89  			if tc.expectedError != "" {
    90  				if err == nil || err.Error() != tc.expectedError {
    91  					t.Fatalf("expected error message `%s` but got `%v`", tc.expectedError, err)
    92  				}
    93  				return
    94  			}
    95  
    96  			expectedPath := filepath.FromSlash(tc.expectedPath)
    97  			assert.Equal(t, expectedPath, path)
    98  		})
    99  	}
   100  }
   101  
   102  func createSocketFile(socketDir string) (string, error) {
   103  	testSocketFile := filepath.Join(socketDir, "mt.sock")
   104  
   105  	// Switch to volume path and create the socket file
   106  	// socket file can not have length of more than 108 character
   107  	// and hence we must use relative path
   108  	oldDir, _ := os.Getwd()
   109  
   110  	err := os.Chdir(socketDir)
   111  	if err != nil {
   112  		return "", err
   113  	}
   114  	defer func() {
   115  		os.Chdir(oldDir)
   116  	}()
   117  	_, socketCreateError := net.Listen("unix", "mt.sock")
   118  	return testSocketFile, socketCreateError
   119  }
   120  
   121  func TestGetFileType(t *testing.T) {
   122  	hu := NewHostUtil()
   123  
   124  	testCase := []struct {
   125  		name         string
   126  		skipWindows  bool
   127  		expectedType FileType
   128  		setUp        func() (string, string, error)
   129  	}{
   130  		{
   131  			"Directory Test",
   132  			false,
   133  			FileTypeDirectory,
   134  			func() (string, string, error) {
   135  				tempDir, err := os.MkdirTemp("", "test-get-filetype-")
   136  				return tempDir, tempDir, err
   137  			},
   138  		},
   139  		{
   140  			"File Test",
   141  			false,
   142  			FileTypeFile,
   143  			func() (string, string, error) {
   144  				tempFile, err := os.CreateTemp("", "test-get-filetype")
   145  				if err != nil {
   146  					return "", "", err
   147  				}
   148  				tempFile.Close()
   149  				return tempFile.Name(), tempFile.Name(), nil
   150  			},
   151  		},
   152  		{
   153  			"Socket Test",
   154  			false,
   155  			FileTypeSocket,
   156  			func() (string, string, error) {
   157  				tempDir, err := os.MkdirTemp("", "test-get-filetype-")
   158  				if err != nil {
   159  					return "", "", err
   160  				}
   161  				tempSocketFile, err := createSocketFile(tempDir)
   162  				return tempSocketFile, tempDir, err
   163  			},
   164  		},
   165  		{
   166  			"Block Device Test",
   167  			true,
   168  			FileTypeBlockDev,
   169  			func() (string, string, error) {
   170  				tempDir, err := os.MkdirTemp("", "test-get-filetype-")
   171  				if err != nil {
   172  					return "", "", err
   173  				}
   174  
   175  				tempBlockFile := filepath.Join(tempDir, "test_blk_dev")
   176  				outputBytes, err := exec.New().Command("mknod", tempBlockFile, "b", "89", "1").CombinedOutput()
   177  				if err != nil {
   178  					err = fmt.Errorf("%v: %s ", err, outputBytes)
   179  				}
   180  				return tempBlockFile, tempDir, err
   181  			},
   182  		},
   183  		{
   184  			"Character Device Test",
   185  			true,
   186  			FileTypeCharDev,
   187  			func() (string, string, error) {
   188  				tempDir, err := os.MkdirTemp("", "test-get-filetype-")
   189  				if err != nil {
   190  					return "", "", err
   191  				}
   192  
   193  				tempCharFile := filepath.Join(tempDir, "test_char_dev")
   194  				outputBytes, err := exec.New().Command("mknod", tempCharFile, "c", "89", "1").CombinedOutput()
   195  				if err != nil {
   196  					err = fmt.Errorf("%v: %s ", err, outputBytes)
   197  				}
   198  				return tempCharFile, tempDir, err
   199  			},
   200  		},
   201  	}
   202  
   203  	for idx, tc := range testCase {
   204  		if goruntime.GOOS == "windows" && tc.skipWindows {
   205  			continue
   206  		}
   207  		path, cleanUpPath, err := tc.setUp()
   208  		defer os.RemoveAll(cleanUpPath) // RemoveAll can deal with a empty path ""
   209  		if err != nil {
   210  			// Locally passed, but upstream CI is not friendly to create such device files
   211  			// Leave "Operation not permitted" out, which can be covered in an e2e test
   212  			if isOperationNotPermittedError(err) {
   213  				continue
   214  			}
   215  			t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err)
   216  		}
   217  
   218  		fileType, err := hu.GetFileType(path)
   219  		if err != nil {
   220  			t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err)
   221  		}
   222  		if fileType != tc.expectedType {
   223  			t.Fatalf("[%d-%s] expected %s, but got %s", idx, tc.name, tc.expectedType, fileType)
   224  		}
   225  	}
   226  }
   227  
   228  func isOperationNotPermittedError(err error) bool {
   229  	return strings.Contains(err.Error(), "Operation not permitted")
   230  }
   231  

View as plain text