k8s.io/kubernetes@v1.29.3/pkg/volume/util/hostutil/hostutil_test.go (about) 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 }