github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/fs/fake/fakefs.go (about) 1 /* 2 Copyright 2019 Mirantis 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 fake 18 19 import ( 20 "errors" 21 "fmt" 22 "io" 23 "os" 24 "path/filepath" 25 "strings" 26 "testing" 27 28 "github.com/Mirantis/virtlet/pkg/fs" 29 testutils "github.com/Mirantis/virtlet/pkg/utils/testing" 30 ) 31 32 const pathMarker = "/__fs__/" 33 34 func fixPath(s string) string { 35 p := strings.Index(s, pathMarker) 36 if p < 0 { 37 return s 38 } 39 return "/" + s[p+len(pathMarker):] 40 } 41 42 type fakeDelimitedReader struct { 43 rec testutils.Recorder 44 fileData string 45 } 46 47 var _ fs.DelimitedReader = &fakeDelimitedReader{} 48 49 // ReadString implements ReadString method of utils.FileReader interface 50 func (fr *fakeDelimitedReader) ReadString(delim byte) (line string, err error) { 51 lines := strings.SplitN(fr.fileData, string(delim), 1) 52 line = lines[0] 53 if len(lines) > 1 { 54 fr.fileData = lines[1] 55 } else { 56 err = io.EOF 57 } 58 fr.rec.Rec("ReadString", line) 59 return 60 } 61 62 // Close implements Close method of utils.FileReader interface 63 func (fr *fakeDelimitedReader) Close() error { 64 return nil 65 } 66 67 // FakeFileSystem is a fake implementation of FileSystem interface 68 // that uses a Recorder to record the operations performed on it. 69 type FakeFileSystem struct { 70 t *testing.T 71 rec testutils.Recorder 72 mountParentDir string 73 files map[string]string 74 } 75 76 var _ fs.FileSystem = &FakeFileSystem{} 77 78 // NewFakeFileSystem creates a new instance of FakeFileSystem using 79 // the provided recorder and a directory that should be parent for all 80 // the fake mountpoints. It also takes map with fake files that will 81 // be accessible with GetDelimitedReader (besides those written with 82 // WriteFile). 83 func NewFakeFileSystem(t *testing.T, rec testutils.Recorder, mountParentDir string, files map[string]string) *FakeFileSystem { 84 return &FakeFileSystem{t: t, rec: rec, mountParentDir: mountParentDir, files: files} 85 } 86 87 func (fs *FakeFileSystem) validateMountPath(target string) { 88 if fs.mountParentDir == "" || filepath.Dir(target) != filepath.Clean(fs.mountParentDir) { 89 fs.t.Fatalf("bad path encountered by the fs: %q (mountParentDir %q)", target, fs.mountParentDir) 90 } 91 } 92 93 // Mount implements the Mount method of FileSystem interface. 94 func (fs *FakeFileSystem) Mount(source string, target string, fstype string, bind bool) error { 95 fs.validateMountPath(target) 96 fs.rec.Rec("Mount", []interface{}{fixPath(source), fixPath(target), fstype, bind}) 97 98 // We want to check directory contents both before & after mount, 99 // see comment in FlexVolumeDriver.mount() in flexvolume.go. 100 // So we move the original contents to .shadowed subdir. 101 shadowedPath := filepath.Join(target, ".shadowed") 102 if err := os.Mkdir(shadowedPath, 0755); err != nil { 103 fs.t.Fatalf("os.Mkdir(): %v", err) 104 } 105 106 pathsToShadow, err := filepath.Glob(filepath.Join(target, "*")) 107 if err != nil { 108 fs.t.Fatalf("filepath.Glob(): %v", err) 109 } 110 for _, pathToShadow := range pathsToShadow { 111 filename := filepath.Base(pathToShadow) 112 if filename == ".shadowed" { 113 continue 114 } 115 if err := os.Rename(pathToShadow, filepath.Join(shadowedPath, filename)); err != nil { 116 fs.t.Fatalf("os.Rename(): %v", err) 117 } 118 } 119 return nil 120 } 121 122 // Unmount implements the Unmount method of FileSystem interface. 123 func (fs *FakeFileSystem) Unmount(target string, detach bool) error { 124 // we make sure that path is under our tmpdir before wiping it 125 fs.validateMountPath(target) 126 fs.rec.Rec("Unmount", []interface{}{fixPath(target), detach}) 127 128 paths, err := filepath.Glob(filepath.Join(target, "*")) 129 if err != nil { 130 fs.t.Fatalf("filepath.Glob(): %v", err) 131 } 132 for _, path := range paths { 133 if filepath.Base(path) != ".shadowed" { 134 continue 135 } 136 if err := os.RemoveAll(path); err != nil { 137 fs.t.Fatalf("os.RemoveAll(): %v", err) 138 } 139 } 140 141 // We don't clean up '.shadowed' dir here because flexvolume driver 142 // recursively removes the whole dir tree anyway. 143 return nil 144 } 145 146 // IsPathAnNs implements the IsPathAnNs method of FileSystem interface. 147 func (fs *FakeFileSystem) IsPathAnNs(path string) bool { 148 return false 149 } 150 151 // ChownForEmulator implements ChownForEmulator method of FileSystem interface. 152 func (fs *FakeFileSystem) ChownForEmulator(filePath string, recursive bool) error { 153 fs.rec.Rec("ChownForEmulator", []interface{}{fixPath(filePath), recursive}) 154 return nil 155 } 156 157 // GetDelimitedReader implements the FileReader method of FileSystem interface. 158 func (fs *FakeFileSystem) GetDelimitedReader(path string) (fs.DelimitedReader, error) { 159 data, ok := fs.files[path] 160 if !ok { 161 fs.rec.Rec("GetDelimitedReader", fmt.Sprintf("undefined path %q", path)) 162 return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("file not found")} 163 } 164 fs.rec.Rec("GetDelimitedReader", path) 165 return &fakeDelimitedReader{rec: fs.rec, fileData: data}, nil 166 } 167 168 // WriteFile implements the WriteFile method of FilesManipulator interface. 169 func (fs *FakeFileSystem) WriteFile(path string, data []byte, perm os.FileMode) error { 170 fs.rec.Rec("WriteFile", []interface{}{path, string(data)}) 171 fs.files[path] = string(data) 172 return nil 173 }