From 17d0661f49ea751fd99a3dff13409d45c401da3f Mon Sep 17 00:00:00 2001 From: Ivan Shvedunov Date: Fri, 8 Feb 2019 23:10:23 +0300 Subject: [PATCH] Split filesystem-related utils into fs package --- cmd/flexvolume_driver/flexvolume_driver.go | 3 +- cmd/virtlet/virtlet.go | 4 +- pkg/flexvolume/flexvolume.go | 13 +- pkg/flexvolume/flexvolume_test.go | 111 ++------ pkg/fs/fake/fakefs.go | 163 +++++++++++ pkg/fs/fs.go | 266 ++++++++++++++++++ .../mounter_linux.go => fs/fs_linux.go} | 19 +- pkg/fs/fs_test.go | 152 ++++++++++ .../fs_unsupported.go} | 14 +- pkg/{utils => fs}/fsstat_linux.go | 4 +- pkg/{utils => fs}/fsstat_unsupported.go | 2 +- pkg/fs/iso.go | 37 +++ pkg/{utils/files.go => fs/writefiles.go} | 20 +- pkg/image/download.go | 2 +- pkg/image/image.go | 4 +- pkg/libvirttools/TestUpdateCpusets.out.yaml | 20 +- pkg/libvirttools/cloudinit.go | 11 +- pkg/libvirttools/cpusets.go | 4 +- pkg/libvirttools/fileownership.go | 80 ------ pkg/libvirttools/filesystemvolume.go | 9 +- pkg/libvirttools/gc.go | 2 +- pkg/libvirttools/root_volumesource_test.go | 3 +- pkg/libvirttools/virtualization.go | 49 ++-- pkg/libvirttools/virtualization_test.go | 15 +- pkg/libvirttools/volumes.go | 3 +- pkg/manager/manager.go | 9 +- pkg/manager/runtime_test.go | 7 +- pkg/utils/cgroups/controllers.go | 23 +- pkg/utils/fake/files_manipulator.go | 81 ------ pkg/utils/files_manipulator.go | 88 ------ pkg/utils/files_test.go | 60 ---- pkg/utils/mounter.go | 42 --- pkg/utils/mountinfo.go | 126 --------- tests/integration/container_test.go | 3 +- 34 files changed, 757 insertions(+), 692 deletions(-) create mode 100644 pkg/fs/fake/fakefs.go create mode 100644 pkg/fs/fs.go rename pkg/{utils/mounter_linux.go => fs/fs_linux.go} (70%) create mode 100644 pkg/fs/fs_test.go rename pkg/{utils/mounter_unsupported.go => fs/fs_unsupported.go} (62%) rename pkg/{utils => fs}/fsstat_linux.go (96%) rename pkg/{utils => fs}/fsstat_unsupported.go (98%) create mode 100644 pkg/fs/iso.go rename pkg/{utils/files.go => fs/writefiles.go} (70%) delete mode 100644 pkg/libvirttools/fileownership.go delete mode 100644 pkg/utils/fake/files_manipulator.go delete mode 100644 pkg/utils/files_manipulator.go delete mode 100644 pkg/utils/files_test.go delete mode 100644 pkg/utils/mounter.go delete mode 100644 pkg/utils/mountinfo.go diff --git a/cmd/flexvolume_driver/flexvolume_driver.go b/cmd/flexvolume_driver/flexvolume_driver.go index a37c20fb1..f7231ee7f 100644 --- a/cmd/flexvolume_driver/flexvolume_driver.go +++ b/cmd/flexvolume_driver/flexvolume_driver.go @@ -22,11 +22,12 @@ import ( "time" "github.com/Mirantis/virtlet/pkg/flexvolume" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/utils" ) func main() { rand.Seed(time.Now().UnixNano()) - driver := flexvolume.NewDriver(utils.NewUUID, utils.NewMounter()) + driver := flexvolume.NewDriver(utils.NewUUID, fs.RealFileSystem) os.Stdout.WriteString(driver.Run(os.Args[1:])) } diff --git a/cmd/virtlet/virtlet.go b/cmd/virtlet/virtlet.go index a95a15139..01100c4ea 100644 --- a/cmd/virtlet/virtlet.go +++ b/cmd/virtlet/virtlet.go @@ -31,7 +31,7 @@ import ( "github.com/Mirantis/virtlet/pkg/cni" "github.com/Mirantis/virtlet/pkg/config" "github.com/Mirantis/virtlet/pkg/diag" - "github.com/Mirantis/virtlet/pkg/libvirttools" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/manager" "github.com/Mirantis/virtlet/pkg/nsfix" "github.com/Mirantis/virtlet/pkg/tapmanager" @@ -85,7 +85,7 @@ func runTapManager(config *v1.VirtletConfig) { glog.Errorf("FD server returned error: %v", err) os.Exit(1) } - if err := libvirttools.ChownForEmulator(*config.FDServerSocketPath, false); err != nil { + if err := fs.RealFileSystem.ChownForEmulator(*config.FDServerSocketPath, false); err != nil { glog.Warningf("Couldn't set tapmanager socket permissions: %v", err) } for { diff --git a/pkg/flexvolume/flexvolume.go b/pkg/flexvolume/flexvolume.go index 80348730a..286fbb3db 100644 --- a/pkg/flexvolume/flexvolume.go +++ b/pkg/flexvolume/flexvolume.go @@ -25,6 +25,7 @@ import ( "strconv" "strings" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/utils" ) @@ -53,12 +54,12 @@ type UUIDGen func() string // https://kubernetes.io/docs/concepts/storage/volumes/#flexVolume type Driver struct { uuidGen UUIDGen - mounter utils.Mounter + fs fs.FileSystem } // NewDriver creates a Driver struct -func NewDriver(uuidGen UUIDGen, mounter utils.Mounter) *Driver { - return &Driver{uuidGen: uuidGen, mounter: mounter} +func NewDriver(uuidGen UUIDGen, fs fs.FileSystem) *Driver { + return &Driver{uuidGen: uuidGen, fs: fs} } func (d *Driver) populateVolumeDir(targetDir string, opts map[string]interface{}) error { @@ -119,7 +120,7 @@ func (d *Driver) mount(targetMountDir, jsonOptions string) (map[string]interface return nil, err } - if err := d.mounter.Mount("tmpfs", targetMountDir, "tmpfs", false); err != nil { + if err := d.fs.Mount("tmpfs", targetMountDir, "tmpfs", false); err != nil { return nil, fmt.Errorf("error mounting tmpfs at %q: %v", targetMountDir, err) } @@ -127,7 +128,7 @@ func (d *Driver) mount(targetMountDir, jsonOptions string) (map[string]interface defer func() { // try to unmount upon error or panic if !done { - d.mounter.Unmount(targetMountDir, true) + d.fs.Unmount(targetMountDir, true) } }() @@ -141,7 +142,7 @@ func (d *Driver) mount(targetMountDir, jsonOptions string) (map[string]interface // Invocation: unmount func (d *Driver) unmount(targetMountDir string) (map[string]interface{}, error) { - if err := d.mounter.Unmount(targetMountDir, true); err != nil { + if err := d.fs.Unmount(targetMountDir, true); err != nil { return nil, fmt.Errorf("unmount %q: %v", targetMountDir, err.Error()) } diff --git a/pkg/flexvolume/flexvolume_test.go b/pkg/flexvolume/flexvolume_test.go index b0103696e..bef9b0272 100644 --- a/pkg/flexvolume/flexvolume_test.go +++ b/pkg/flexvolume/flexvolume_test.go @@ -18,15 +18,14 @@ package flexvolume import ( "encoding/json" - "fmt" "io/ioutil" "os" "path" - "path/filepath" "reflect" "strings" "testing" + fakefs "github.com/Mirantis/virtlet/pkg/fs/fake" "github.com/Mirantis/virtlet/pkg/utils" testutils "github.com/Mirantis/virtlet/pkg/utils/testing" ) @@ -35,75 +34,6 @@ const ( fakeUUID = "abb67e3c-71b3-4ddd-5505-8c4215d5c4eb" ) -type fakeMounter struct { - t *testing.T - tmpDir string - journal []string -} - -var _ utils.Mounter = &fakeMounter{} - -func newFakeMounter(t *testing.T, tmpDir string) *fakeMounter { - return &fakeMounter{t: t, tmpDir: tmpDir} -} - -func (mounter *fakeMounter) validatePath(target string) { - if filepath.Dir(target) != filepath.Clean(mounter.tmpDir) { - mounter.t.Fatalf("bad path encountered by the mounter: %q (tmpDir %q)", target, mounter.tmpDir) - } -} - -func (mounter *fakeMounter) Mount(source string, target string, fstype string, bind bool) error { - mounter.validatePath(target) - mounter.journal = append(mounter.journal, fmt.Sprintf("mount: %s %s %s %v", source, target, fstype, bind)) - - // We want to check directory contents both before & after mount, - // see comment in FlexVolumeDriver.mount() in flexvolume.go. - // So we move the original contents to .shadowed subdir. - shadowedPath := filepath.Join(target, ".shadowed") - if err := os.Mkdir(shadowedPath, 0755); err != nil { - mounter.t.Fatalf("os.Mkdir(): %v", err) - } - - pathsToShadow, err := filepath.Glob(filepath.Join(target, "*")) - if err != nil { - mounter.t.Fatalf("filepath.Glob(): %v", err) - } - for _, pathToShadow := range pathsToShadow { - filename := filepath.Base(pathToShadow) - if filename == ".shadowed" { - continue - } - if err := os.Rename(pathToShadow, filepath.Join(shadowedPath, filename)); err != nil { - mounter.t.Fatalf("os.Rename(): %v", err) - } - } - return nil -} - -func (mounter *fakeMounter) Unmount(target string, detach bool) error { - // we make sure that path is under our tmpdir before wiping it - mounter.validatePath(target) - mounter.journal = append(mounter.journal, fmt.Sprintf("unmount: %s %v", target, detach)) - - paths, err := filepath.Glob(filepath.Join(target, "*")) - if err != nil { - mounter.t.Fatalf("filepath.Glob(): %v", err) - } - for _, path := range paths { - if filepath.Base(path) != ".shadowed" { - continue - } - if err := os.RemoveAll(path); err != nil { - mounter.t.Fatalf("os.RemoveAll(): %v", err) - } - } - - // We don't clean up '.shadowed' dir here because flexvolume driver - // recursively removes the whole dir tree anyway. - return nil -} - func TestFlexVolume(t *testing.T) { tmpDir, err := ioutil.TempDir("", "flexvolume-test") if err != nil { @@ -134,7 +64,7 @@ func TestFlexVolume(t *testing.T) { message string fields map[string]interface{} files map[string]interface{} - mountJournal []string + mountJournal []*testutils.Record }{ { name: "init", @@ -185,8 +115,11 @@ func TestFlexVolume(t *testing.T) { "virtlet-flexvolume.json": utils.ToJSONUnindented(cephJSONVolumeInfo), }, }, - mountJournal: []string{ - fmt.Sprintf("mount: tmpfs %s tmpfs false", cephDir), + mountJournal: []*testutils.Record{ + { + Name: "Mount", + Value: []interface{}{"tmpfs", cephDir, "tmpfs", false}, + }, }, }, { @@ -194,8 +127,11 @@ func TestFlexVolume(t *testing.T) { args: []string{"unmount", cephDir}, status: "Success", subdir: "ceph", - mountJournal: []string{ - fmt.Sprintf("unmount: %s true", cephDir), + mountJournal: []*testutils.Record{ + { + Name: "Unmount", + Value: []interface{}{cephDir, true}, + }, }, }, { @@ -209,8 +145,11 @@ func TestFlexVolume(t *testing.T) { "virtlet-flexvolume.json": utils.ToJSONUnindented(cephJSONVolumeInfo), }, }, - mountJournal: []string{ - fmt.Sprintf("mount: tmpfs %s tmpfs false", cephDir), + mountJournal: []*testutils.Record{ + { + Name: "Mount", + Value: []interface{}{"tmpfs", cephDir, "tmpfs", false}, + }, }, }, { @@ -218,8 +157,11 @@ func TestFlexVolume(t *testing.T) { args: []string{"unmount", cephDir}, status: "Success", subdir: "ceph", - mountJournal: []string{ - fmt.Sprintf("unmount: %s true", cephDir), + mountJournal: []*testutils.Record{ + { + Name: "Unmount", + Value: []interface{}{cephDir, true}, + }, }, }, { @@ -256,10 +198,11 @@ func TestFlexVolume(t *testing.T) { t.Run(step.name, func(t *testing.T) { var subdir string args := step.args - mounter := newFakeMounter(t, tmpDir) + rec := testutils.NewToplevelRecorder() + fs := fakefs.NewFakeFileSystem(t, rec, tmpDir, nil) d := NewDriver(func() string { return fakeUUID - }, mounter) + }, fs) result := d.Run(args) var m map[string]interface{} if err := json.Unmarshal([]byte(result), &m); err != nil { @@ -299,8 +242,8 @@ func TestFlexVolume(t *testing.T) { t.Errorf("bad file content.\n%s\n-- instead of --\n%s", utils.ToJSON(files), utils.ToJSON(step.files)) } } - if !reflect.DeepEqual(mounter.journal, step.mountJournal) { - t.Errorf("unexpected mount journal: %#v instead of %#v", mounter.journal, step.mountJournal) + if !reflect.DeepEqual(rec.Content(), step.mountJournal) { + t.Errorf("unexpected mount journal: %#v instead of %#v", rec.Content(), step.mountJournal) } }) } diff --git a/pkg/fs/fake/fakefs.go b/pkg/fs/fake/fakefs.go new file mode 100644 index 000000000..955b7dbee --- /dev/null +++ b/pkg/fs/fake/fakefs.go @@ -0,0 +1,163 @@ +/* +Copyright 2019 Mirantis + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/Mirantis/virtlet/pkg/fs" + testutils "github.com/Mirantis/virtlet/pkg/utils/testing" +) + +type fakeDelimitedReader struct { + rec testutils.Recorder + fileData string +} + +var _ fs.DelimitedReader = &fakeDelimitedReader{} + +// ReadString implements ReadString method of utils.FileReader interface +func (fr *fakeDelimitedReader) ReadString(delim byte) (line string, err error) { + lines := strings.SplitN(fr.fileData, string(delim), 1) + line = lines[0] + if len(lines) > 1 { + fr.fileData = lines[1] + } else { + err = io.EOF + } + fr.rec.Rec("ReadString", line) + return +} + +// Close implements Close method of utils.FileReader interface +func (fr *fakeDelimitedReader) Close() error { + return nil +} + +// FakeFileSystem is a fake implementation of FileSystem interface +// that uses a Recorder to record the operations performed on it. +type FakeFileSystem struct { + t *testing.T + rec testutils.Recorder + mountParentDir string + files map[string]string +} + +var _ fs.FileSystem = &FakeFileSystem{} + +// NewFakeFileSystem creates a new instance of FakeFileSystem using +// the provided recorder and a directory that should be parent for all +// the fake mountpoints. It also takes map with fake files that will +// be accessible with GetDelimitedReader (besides those written with +// WriteFile). +func NewFakeFileSystem(t *testing.T, rec testutils.Recorder, mountParentDir string, files map[string]string) *FakeFileSystem { + return &FakeFileSystem{t: t, rec: rec, mountParentDir: mountParentDir, files: files} +} + +func (fs *FakeFileSystem) validateMountPath(target string) { + if fs.mountParentDir == "" || filepath.Dir(target) != filepath.Clean(fs.mountParentDir) { + fs.t.Fatalf("bad path encountered by the fs: %q (mountParentDir %q)", target, fs.mountParentDir) + } +} + +// Mount implements the Mount method of FileSystem interface. +func (fs *FakeFileSystem) Mount(source string, target string, fstype string, bind bool) error { + fs.validateMountPath(target) + fs.rec.Rec("Mount", []interface{}{source, target, fstype, bind}) + + // We want to check directory contents both before & after mount, + // see comment in FlexVolumeDriver.mount() in flexvolume.go. + // So we move the original contents to .shadowed subdir. + shadowedPath := filepath.Join(target, ".shadowed") + if err := os.Mkdir(shadowedPath, 0755); err != nil { + fs.t.Fatalf("os.Mkdir(): %v", err) + } + + pathsToShadow, err := filepath.Glob(filepath.Join(target, "*")) + if err != nil { + fs.t.Fatalf("filepath.Glob(): %v", err) + } + for _, pathToShadow := range pathsToShadow { + filename := filepath.Base(pathToShadow) + if filename == ".shadowed" { + continue + } + if err := os.Rename(pathToShadow, filepath.Join(shadowedPath, filename)); err != nil { + fs.t.Fatalf("os.Rename(): %v", err) + } + } + return nil +} + +// Unmount implements the Unmount method of FileSystem interface. +func (fs *FakeFileSystem) Unmount(target string, detach bool) error { + // we make sure that path is under our tmpdir before wiping it + fs.validateMountPath(target) + fs.rec.Rec("Unmount", []interface{}{target, detach}) + + paths, err := filepath.Glob(filepath.Join(target, "*")) + if err != nil { + fs.t.Fatalf("filepath.Glob(): %v", err) + } + for _, path := range paths { + if filepath.Base(path) != ".shadowed" { + continue + } + if err := os.RemoveAll(path); err != nil { + fs.t.Fatalf("os.RemoveAll(): %v", err) + } + } + + // We don't clean up '.shadowed' dir here because flexvolume driver + // recursively removes the whole dir tree anyway. + return nil +} + +// IsPathAnNs implements the IsPathAnNs method of FileSystem interface. +func (fs *FakeFileSystem) IsPathAnNs(path string) bool { + return false +} + +// ChownForEmulator implements ChownForEmulator method of FileSystem interface. +func (fs *FakeFileSystem) ChownForEmulator(filePath string, recursive bool) error { + fs.rec.Rec("ChownForEmulator", []interface{}{filePath, recursive}) + return nil +} + +// GetDelimitedReader implements the FileReader method of FileSystem interface. +func (fs *FakeFileSystem) GetDelimitedReader(path string) (fs.DelimitedReader, error) { + data, ok := fs.files[path] + if !ok { + fs.rec.Rec("GetDelimitedReader", fmt.Sprintf("undefined path %q", path)) + return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("file not found")} + } + fs.rec.Rec("GetDelimitedReader", path) + return &fakeDelimitedReader{rec: fs.rec, fileData: data}, nil +} + +// WriteFile implements the WriteFile method of FilesManipulator interface. +func (fs *FakeFileSystem) WriteFile(path string, data []byte, perm os.FileMode) error { + fs.rec.Rec("WriteFile", []interface{}{path, string(data)}) + fs.files[path] = string(data) + return nil +} diff --git a/pkg/fs/fs.go b/pkg/fs/fs.go new file mode 100644 index 000000000..cf2fa21e1 --- /dev/null +++ b/pkg/fs/fs.go @@ -0,0 +1,266 @@ +/* +Copyright 2019 Mirantis + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fs + +import ( + "bufio" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/user" + "path/filepath" + "strconv" + "strings" + "sync" + + "github.com/golang/glog" +) + +const ( + emulatorUserName = "libvirt-qemu" + defaultMountInfoPath = "/proc/self/mountinfo" +) + +// DelimitedReader is an interface for reading a delimeter-separated +// data from files. It can be used for reading /sys and /proc +// information, for example. +type DelimitedReader interface { + // ReadString returns next part of data up to (and including it) + // the delimeter byte. + ReadString(delim byte) (string, error) + // Close closes the reader. + Close() error +} + +// FileSystem defines a filesystem interface interface +type FileSystem interface { + // Mount mounts the specified source under the target path. + // For bind mounts, bind must be true. + Mount(source string, target string, fstype string, bind bool) error + // Unmount unmounts the specified target directory. If detach + // is true, MNT_DETACH option is used (disconnect the + // filesystem for the new accesses even if it's busy). + Unmount(target string, detach bool) error + // IsPathAnNs verifies if the path is a mountpoint with nsfs filesystem type. + IsPathAnNs(string) bool + // ChownForEmulator makes a file or directory owned by the emulator user. + ChownForEmulator(filePath string, recursive bool) error + // GetDelimitedReader returns a DelimitedReader for the specified path. + GetDelimitedReader(path string) (DelimitedReader, error) + // WriteFile creates a new file with the specified path or truncates + // the existing one, setting the specified permissions and writing + // the data to it. Returns an error if any occured + // during the operation. + WriteFile(path string, data []byte, perm os.FileMode) error +} + +type nullFileSystem struct{} + +// NullFileSystem is a fs that's used for testing and does nothing +// instead of mounting/unmounting. +var NullFileSystem FileSystem = nullFileSystem{} + +func (fs nullFileSystem) Mount(source string, target string, fstype string, bind bool) error { + return nil +} + +func (fs nullFileSystem) Unmount(target string, detach bool) error { + return nil +} + +func (fs nullFileSystem) IsPathAnNs(path string) bool { + return false +} + +func (fs nullFileSystem) ChownForEmulator(filePath string, recursive bool) error { + return nil +} + +func (fs nullFileSystem) GetDelimitedReader(path string) (DelimitedReader, error) { + return nil, errors.New("not implemented") +} + +func (fs nullFileSystem) WriteFile(path string, data []byte, perm os.FileMode) error { + return nil +} + +type mountEntry struct { + source, fsType string +} + +type realFileSystem struct { + sync.Mutex + mountInfo map[string]mountEntry + gotEmulatorUser bool + uid, gid int + mountInfoPath string +} + +// RealFileSystem provides access to the real filesystem. +var RealFileSystem FileSystem = &realFileSystem{} + +func (fs *realFileSystem) ensureMountInfo() error { + fs.Lock() + defer fs.Unlock() + if fs.mountInfo != nil { + return nil + } + + mountInfoPath := fs.mountInfoPath + if mountInfoPath == "" { + mountInfoPath = defaultMountInfoPath + } + + reader, err := fs.GetDelimitedReader(mountInfoPath) + if err != nil { + return err + } + defer reader.Close() + + fs.mountInfo = make(map[string]mountEntry) +LineReader: + for { + line, err := reader.ReadString('\n') + switch err { + case io.EOF: + break LineReader + case nil: + // strip eol + line = strings.Trim(line, "\n") + + // split and parse the entries acording to section 3.5 in + // https://www.kernel.org/doc/Documentation/filesystems/proc.txt + // TODO: whitespaces and control chars in names are encoded as + // octal values (e.g. for "x x": "x\040x") what should be expanded + // in both mount point source and target + parts := strings.Split(line, " ") + if len(parts) < 10 { + glog.Errorf("bad mountinfo entry: %q", line) + } else { + fs.mountInfo[parts[4]] = mountEntry{source: parts[9], fsType: parts[8]} + } + default: + return err + } + } + return nil +} + +func (fs *realFileSystem) getMountInfo(path string) (mountEntry, bool, error) { + if err := fs.ensureMountInfo(); err != nil { + return mountEntry{}, false, err + } + + fs.Lock() + defer fs.Unlock() + entry, ok := fs.mountInfo[path] + return entry, ok, nil +} + +func (fs *realFileSystem) IsPathAnNs(path string) bool { + _, err := os.Stat(path) + if err != nil { + if !os.IsNotExist(err) { + glog.Errorf("Can't check if %q exists: %v", path, err) + } + return false + } + realpath, err := filepath.EvalSymlinks(path) + if err != nil { + glog.Errorf("Can't get the real path of %q: %v", path, err) + return false + } + + entry, isMountPoint, err := fs.getMountInfo(realpath) + if err != nil { + glog.Errorf("Can't check if %q is a namespace: error getting mount info: %v", path, err) + return false + } + + return isMountPoint && (entry.fsType == "nsfs" || entry.fsType == "proc") +} + +func (fs *realFileSystem) getEmulatorUidGid() (int, int, error) { + fs.Lock() + defer fs.Unlock() + if !fs.gotEmulatorUser { + u, err := user.Lookup(emulatorUserName) + if err != nil { + return 0, 0, fmt.Errorf("can't find user %q: %v", emulatorUserName, err) + } + fs.uid, err = strconv.Atoi(u.Uid) + if err != nil { + return 0, 0, fmt.Errorf("bad uid %q for user %q: %v", u.Uid, emulatorUserName, err) + } + fs.gid, err = strconv.Atoi(u.Gid) + if err != nil { + return 0, 0, fmt.Errorf("bad gid %q for user %q: %v", u.Gid, emulatorUserName, err) + } + } + return fs.uid, fs.gid, nil +} + +func (fs *realFileSystem) ChownForEmulator(filePath string, recursive bool) error { + // don't hold the mutex for the duration of chown + uid, gid, err := fs.getEmulatorUidGid() + if err != nil { + return err + } + + chown := os.Chown + if recursive { + chown = chownR + } + if err := chown(filePath, uid, gid); err != nil { + return fmt.Errorf("can't set the owner of %q: %v", filePath, err) + } + return nil +} + +func (fs *realFileSystem) GetDelimitedReader(path string) (DelimitedReader, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + return &delimitedReader{f, bufio.NewReader(f)}, nil +} + +func (fs *realFileSystem) WriteFile(path string, data []byte, perm os.FileMode) error { + return ioutil.WriteFile(path, data, perm) +} + +type delimitedReader struct { + *os.File + *bufio.Reader +} + +var _ DelimitedReader = &delimitedReader{} + +// chownR makes a file or directory owned by the emulator user recursively. +func chownR(path string, uid, gid int) error { + return filepath.Walk(path, func(name string, info os.FileInfo, err error) error { + if err == nil { + err = os.Chown(name, uid, gid) + if err != nil { + glog.Warningf("Failed to change the owner of %q: %v", name, err) + } + } + return err + }) +} diff --git a/pkg/utils/mounter_linux.go b/pkg/fs/fs_linux.go similarity index 70% rename from pkg/utils/mounter_linux.go rename to pkg/fs/fs_linux.go index a7929d184..5636a238c 100644 --- a/pkg/utils/mounter_linux.go +++ b/pkg/fs/fs_linux.go @@ -1,7 +1,7 @@ // +build linux /* -Copyright 2018 Mirantis +Copyright 2019 Mirantis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,20 +16,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package fs import "syscall" -type mounter struct{} - -var _ Mounter = &mounter{} - -// NewMounter creates linux mounter struct -func NewMounter() Mounter { - return &mounter{} -} - -func (mounter *mounter) Mount(source string, target string, fstype string, bind bool) error { +// Mount inplements Mount method of FileSystem interface. +func (fs *realFileSystem) Mount(source string, target string, fstype string, bind bool) error { flags := uintptr(0) if bind { flags = syscall.MS_BIND | syscall.MS_REC @@ -37,7 +29,8 @@ func (mounter *mounter) Mount(source string, target string, fstype string, bind return syscall.Mount(source, target, fstype, flags, "") } -func (mounter *mounter) Unmount(target string, detach bool) error { +// Unmount inplements Unmount method of FileSystem interface. +func (fs *realFileSystem) Unmount(target string, detach bool) error { flags := 0 if detach { flags = syscall.MNT_DETACH diff --git a/pkg/fs/fs_test.go b/pkg/fs/fs_test.go new file mode 100644 index 000000000..d39b2bb35 --- /dev/null +++ b/pkg/fs/fs_test.go @@ -0,0 +1,152 @@ +/* +Copyright 2019 Mirantis + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fs + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + testutils "github.com/Mirantis/virtlet/pkg/utils/testing" +) + +const sampleMountInfo = `28 0 252:1 / / rw,relatime shared:1 - ext4 /dev/vda1 rw,data=ordered +25 28 0:6 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=16457220k,nr_inodes=4114305,mode=755 +26 25 0:23 / /dev/pts rw,nosuid,noexec,relatime shared:3 - devpts devpts rw,gid=5,mode=620,ptmxmode=000 +27 28 0:24 / /run rw,nosuid,noexec,relatime shared:5 - tmpfs tmpfs rw,size=3293976k,mode=755 +632 27 0:3 net:[4026532228] PATH/run/docker/netns/5d119181c6d0 rw shared:195 - nsfs nsfs rw +671 27 0:3 net:[4026532301] PATH/run/docker/netns/421c937a8f90 rw shared:199 - nsfs nsfs rw +` + +func TestFileSystem(t *testing.T) { + // FIXME: this test is not comprehensive enough right now + tmpDir, err := ioutil.TempDir("", "fs-test") + if err != nil { + t.Fatalf("ioutil.TempDir(): %v", err) + } + realTmpDir, err := filepath.EvalSymlinks(tmpDir) + if err != nil { + t.Fatalf("Can't get the real path of %q: %v", tmpDir, err) + } + defer os.RemoveAll(tmpDir) + + mountInfoPath := filepath.Join(tmpDir, "mountinfo") + mountInfo := strings.Replace(sampleMountInfo, "PATH", realTmpDir, -1) + if err := ioutil.WriteFile(mountInfoPath, []byte(mountInfo), 0644); err != nil { + t.Fatalf("ioutil.WriteFile(): %v", err) + } + + fs := realFileSystem{mountInfoPath: mountInfoPath} + sampleFilePath := filepath.Join(tmpDir, "foobar") + if err := fs.WriteFile(sampleFilePath, []byte("foo\nbar\n"), 0777); err != nil { + t.Fatalf("fs.WriteFile(): %v", err) + } + r, err := fs.GetDelimitedReader(sampleFilePath) + if err != nil { + t.Fatalf("GetDelimitedReader(): %v", err) + } + + for _, expected := range []string{"foo\n", "bar\n"} { + line, err := r.ReadString('\n') + if err != nil { + t.Fatalf("ReadString(): %v", err) + } + if line != expected { + t.Errorf("Bad line 1: %q instead of %q", line, expected) + } + } + + _, err = r.ReadString('\n') + switch err { + case nil: + t.Errorf("Didn't get an io.EOF error") + case io.EOF: + // ok + default: + t.Errorf("Wrong error type at EOF: %T: %v", err, err) + } + + for _, tc := range []struct { + path string + isNs bool + }{ + { + path: "run/docker/netns/5d119181c6d0", + isNs: true, + }, + { + path: "run/docker/netns/421c937a8f90", + isNs: true, + }, + { + path: "run", + isNs: false, + }, + { + path: "etc", + isNs: false, + }, + } { + path := filepath.Join(realTmpDir, tc.path) + if err := os.MkdirAll(path, 0777); err != nil { + t.Fatalf("MkdirAll(): %v", err) + } + + isNs := fs.IsPathAnNs(path) + if isNs != tc.isNs { + t.Errorf("IsPathAnNs(%q) = %v but expected to be %v", path, isNs, tc.isNs) + } + } + // TODO: when running in a build container, also test ChownForEmulator and mounting +} + +func TestFileUtils(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "genisoimage-test") + if err != nil { + t.Fatalf("ioutil.TempDir(): %v", err) + } + defer os.RemoveAll(tmpDir) + + if err := WriteFiles(tmpDir, map[string][]byte{ + "image.cd/file1.txt": []byte("foo"), + "image.cd/anotherfile.txt": []byte("bar"), + }); err != nil { + t.Fatalf("WriteFiles(): %v", err) + } + + isoPath := filepath.Join(tmpDir, "image.iso") + srcPath := filepath.Join(tmpDir, "image.cd") + if err := GenIsoImage(isoPath, "isoimage", srcPath); err != nil { + t.Fatalf("GenIsoImage(): %v", err) + } + + m, err := testutils.IsoToMap(isoPath) + if err != nil { + t.Fatalf("IsoToMap(): %v", err) + } + expectedFiles := map[string]interface{}{ + "file1.txt": "foo", + "anotherfile.txt": "bar", + } + if !reflect.DeepEqual(m, expectedFiles) { + t.Errorf("bad iso content: %#v instead of %#v", m, expectedFiles) + } +} diff --git a/pkg/utils/mounter_unsupported.go b/pkg/fs/fs_unsupported.go similarity index 62% rename from pkg/utils/mounter_unsupported.go rename to pkg/fs/fs_unsupported.go index e481428d4..3872de28c 100644 --- a/pkg/utils/mounter_unsupported.go +++ b/pkg/fs/fs_unsupported.go @@ -1,7 +1,7 @@ // +build !linux /* -Copyright 2018 Mirantis +Copyright 2019 Mirantis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,10 +16,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package fs -// NewMounter is a placeholder for an function that is not implemented -// on non-Linux platforms. -func NewMounter() Mounter { +// Mount inplements Mount method of FileSystem interface. +func (fs *realFileSystem) Mount(source string, target string, fstype string, bind bool) error { + panic("Not implemented") +} + +// Unmount inplements Unmount method of FileSystem interface. +func (fs *realFileSystem) Unmount(target string, detach bool) error { panic("Not implemented") } diff --git a/pkg/utils/fsstat_linux.go b/pkg/fs/fsstat_linux.go similarity index 96% rename from pkg/utils/fsstat_linux.go rename to pkg/fs/fsstat_linux.go index 835491e37..3ba46fb1e 100644 --- a/pkg/utils/fsstat_linux.go +++ b/pkg/fs/fsstat_linux.go @@ -1,7 +1,7 @@ // +build linux /* -Copyright 2018 Mirantis +Copyright 2019 Mirantis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package fs import ( "syscall" diff --git a/pkg/utils/fsstat_unsupported.go b/pkg/fs/fsstat_unsupported.go similarity index 98% rename from pkg/utils/fsstat_unsupported.go rename to pkg/fs/fsstat_unsupported.go index be1b8031e..a50a51c67 100644 --- a/pkg/utils/fsstat_unsupported.go +++ b/pkg/fs/fsstat_unsupported.go @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package fs import ( "errors" diff --git a/pkg/fs/iso.go b/pkg/fs/iso.go new file mode 100644 index 000000000..3a3370c12 --- /dev/null +++ b/pkg/fs/iso.go @@ -0,0 +1,37 @@ +/* +Copyright 2019 Mirantis + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fs + +import ( + "fmt" + "os/exec" +) + +// GenIsoImage generates an ISO 9660 filesystem image containing +// files from srcDir. It uses specified volumeID as the volume id. +func GenIsoImage(isoPath string, volumeID string, srcDir string) error { + out, err := exec.Command("genisoimage", "-o", isoPath, "-V", volumeID, "-r", "-J", srcDir).CombinedOutput() + if err != nil { + outStr := "" + if len(out) != 0 { + outStr = ". Output:\n" + string(out) + } + return fmt.Errorf("error generating iso: %v%s", err, outStr) + } + + return nil +} diff --git a/pkg/utils/files.go b/pkg/fs/writefiles.go similarity index 70% rename from pkg/utils/files.go rename to pkg/fs/writefiles.go index 8d9638063..22566a934 100644 --- a/pkg/utils/files.go +++ b/pkg/fs/writefiles.go @@ -1,5 +1,5 @@ /* -Copyright 2017 Mirantis +Copyright 2019 Mirantis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,13 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package fs import ( "fmt" "io/ioutil" "os" - "os/exec" "path/filepath" ) @@ -40,18 +39,3 @@ func WriteFiles(targetDir string, content map[string][]byte) error { } return nil } - -// GenIsoImage generates an ISO 9660 filesystem image containing -// files from srcDir. It uses specified volumeID as the volume id. -func GenIsoImage(isoPath string, volumeID string, srcDir string) error { - out, err := exec.Command("genisoimage", "-o", isoPath, "-V", volumeID, "-r", "-J", srcDir).CombinedOutput() - if err != nil { - outStr := "" - if len(out) != 0 { - outStr = ". Output:\n" + string(out) - } - return fmt.Errorf("error generating iso: %v%s", err, outStr) - } - - return nil -} diff --git a/pkg/image/download.go b/pkg/image/download.go index d85faa5c4..7cfcd5e03 100644 --- a/pkg/image/download.go +++ b/pkg/image/download.go @@ -213,7 +213,7 @@ func (d *defaultDownloader) DownloadFile(ctx context.Context, endpoint Endpoint, } if f, ok := w.(*os.File); ok { - glog.V(2).Infof("Data from url %s saved as %q, sha256 digest = %s", url, f.Name()) + glog.V(2).Infof("Data from url %s saved as %q", url, f.Name()) } return nil } diff --git a/pkg/image/image.go b/pkg/image/image.go index f902dbc3a..49efb0cab 100644 --- a/pkg/image/image.go +++ b/pkg/image/image.go @@ -30,8 +30,8 @@ import ( "github.com/golang/glog" digest "github.com/opencontainers/go-digest" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/metadata/types" - "github.com/Mirantis/virtlet/pkg/utils" ) // Image describes an image. @@ -595,7 +595,7 @@ func GetHexDigest(imageSpec string) string { // metadata store sizes of images and sum them, or even retrieve precalculated // sum. That's because same filesystem could be used by other things than images. func (s *FileStore) FilesystemStats() (*types.FilesystemStats, error) { - occupiedBytes, occupiedInodes, err := utils.GetFsStatsForPath(s.dir) + occupiedBytes, occupiedInodes, err := fs.GetFsStatsForPath(s.dir) if err != nil { return nil, err } diff --git a/pkg/libvirttools/TestUpdateCpusets.out.yaml b/pkg/libvirttools/TestUpdateCpusets.out.yaml index ad108e64a..89a2e85e4 100755 --- a/pkg/libvirttools/TestUpdateCpusets.out.yaml +++ b/pkg/libvirttools/TestUpdateCpusets.out.yaml @@ -83,17 +83,19 @@ - name: Calling setting cpuset for emulator proces -- name: FileReader +- name: GetDelimitedReader value: /run/libvirt/qemu/virtlet-231700d5-c9a6-testName_0.pid -- name: 'ReadString: 4242' - value: {} -- name: FileReader +- name: ReadString + value: "4242" +- name: GetDelimitedReader value: /proc/4242/cgroup -- name: | - ReadString: 3:cpuset:/somepath/in/cgroups/emulator - value: {} -- name: 'WriteFile: /sys/fs/cgroup/cpuset/somepath/in/cgroups/emulator/cpuset.cpus' - value: "42" +- name: ReadString + value: | + 3:cpuset:/somepath/in/cgroups/emulator +- name: WriteFile + value: + - /sys/fs/cgroup/cpuset/somepath/in/cgroups/emulator/cpuset.cpus + - "42" - name: Calling setting cpuset for domain definition - name: 'domain conn: virtlet-231700d5-c9a6-container1: Undefine' - name: 'domain conn: DefineDomain' diff --git a/pkg/libvirttools/cloudinit.go b/pkg/libvirttools/cloudinit.go index 1136ceaff..0c29061b0 100644 --- a/pkg/libvirttools/cloudinit.go +++ b/pkg/libvirttools/cloudinit.go @@ -37,6 +37,7 @@ import ( libvirtxml "github.com/libvirt/libvirt-go-xml" "github.com/Mirantis/virtlet/pkg/flexvolume" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/metadata/types" "github.com/Mirantis/virtlet/pkg/network" "github.com/Mirantis/virtlet/pkg/utils" @@ -334,10 +335,10 @@ func (g *CloudInitGenerator) generateNetworkConfigurationConfigDrive() ([]byte, return nil, err } linkConf := map[string]interface{}{ - "type": "phy", - "id": iface.Name, + "type": "phy", + "id": iface.Name, "ethernet_mac_address": iface.Mac, - "mtu": mtu, + "mtu": mtu, } links = append(links, linkConf) } @@ -471,7 +472,7 @@ func (g *CloudInitGenerator) GenerateImage(volumeMap diskPathMap) error { if networkConfiguration != nil { fileMap[networkConfigLocation] = networkConfiguration } - if err := utils.WriteFiles(tmpDir, fileMap); err != nil { + if err := fs.WriteFiles(tmpDir, fileMap); err != nil { return fmt.Errorf("can't write user-data: %v", err) } @@ -479,7 +480,7 @@ func (g *CloudInitGenerator) GenerateImage(volumeMap diskPathMap) error { return fmt.Errorf("error making iso directory %q: %v", g.isoDir, err) } - if err := utils.GenIsoImage(g.IsoPath(), volumeName, tmpDir); err != nil { + if err := fs.GenIsoImage(g.IsoPath(), volumeName, tmpDir); err != nil { if rmErr := os.Remove(g.IsoPath()); rmErr != nil { glog.Warningf("Error removing iso file %s: %v", g.IsoPath(), rmErr) } diff --git a/pkg/libvirttools/cpusets.go b/pkg/libvirttools/cpusets.go index 3768c8d29..e913ce24d 100644 --- a/pkg/libvirttools/cpusets.go +++ b/pkg/libvirttools/cpusets.go @@ -76,7 +76,7 @@ func (v *VirtualizationTool) UpdateCpusetsForEmulatorProcess(containerID, cpuset return false, err } - f, err := v.filesManipulator.FileReader(pidFilePath) + f, err := v.fsys.GetDelimitedReader(pidFilePath) if err != nil { // File not found - so there is no emulator yet if _, ok := err.(*os.PathError); ok { @@ -95,7 +95,7 @@ func (v *VirtualizationTool) UpdateCpusetsForEmulatorProcess(containerID, cpuset } } - cm := cgroups.NewManager(pid, v.filesManipulator) + cm := cgroups.NewManager(pid, v.fsys) controller, err := cm.GetProcessController("cpuset") if err != nil { return false, err diff --git a/pkg/libvirttools/fileownership.go b/pkg/libvirttools/fileownership.go deleted file mode 100644 index ad7df740d..000000000 --- a/pkg/libvirttools/fileownership.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2017 Mirantis - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package libvirttools - -import ( - "fmt" - "os" - "os/user" - "path/filepath" - "strconv" - "sync" - - "github.com/golang/glog" -) - -const ( - emulatorUserName = "libvirt-qemu" -) - -var emulatorUser struct { - sync.Mutex - initialized bool - uid, gid int -} - -// ChownForEmulator makes a file or directory owned by the emulator user. -func ChownForEmulator(filePath string, recursive bool) error { - emulatorUser.Lock() - defer emulatorUser.Unlock() - if !emulatorUser.initialized { - u, err := user.Lookup(emulatorUserName) - if err != nil { - return fmt.Errorf("can't find user %q: %v", emulatorUserName, err) - } - emulatorUser.uid, err = strconv.Atoi(u.Uid) - if err != nil { - return fmt.Errorf("bad uid %q for user %q: %v", u.Uid, emulatorUserName, err) - } - emulatorUser.gid, err = strconv.Atoi(u.Gid) - if err != nil { - return fmt.Errorf("bad gid %q for user %q: %v", u.Gid, emulatorUserName, err) - } - } - - chown := os.Chown - if recursive { - chown = ChownR - } - if err := chown(filePath, emulatorUser.uid, emulatorUser.gid); err != nil { - return fmt.Errorf("can't set the owner of %q: %v", filePath, err) - } - return nil -} - -// ChownR makes a file or directory owned by the emulator user recursively. -func ChownR(path string, uid, gid int) error { - return filepath.Walk(path, func(name string, info os.FileInfo, err error) error { - if err == nil { - err = os.Chown(name, uid, gid) - if err != nil { - glog.Warningf("Failed to change the owner of %q: %v", name, err) - } - } - return err - }) -} diff --git a/pkg/libvirttools/filesystemvolume.go b/pkg/libvirttools/filesystemvolume.go index 01eaea727..3d9e9e010 100644 --- a/pkg/libvirttools/filesystemvolume.go +++ b/pkg/libvirttools/filesystemvolume.go @@ -39,15 +39,16 @@ var _ VMVolume = &filesystemVolume{} func (v *filesystemVolume) UUID() string { return "" } func (v *filesystemVolume) Setup() (*libvirtxml.DomainDisk, *libvirtxml.DomainFilesystem, error) { + fsys := v.owner.FileSystem() err := os.MkdirAll(v.volumeMountPoint, 0777) if err == nil { - err = ChownForEmulator(v.volumeMountPoint, false) + err = fsys.ChownForEmulator(v.volumeMountPoint, false) } if err == nil { - err = v.owner.Mounter().Mount(v.mount.HostPath, v.volumeMountPoint, "bind", true) + err = fsys.Mount(v.mount.HostPath, v.volumeMountPoint, "bind", true) } if err == nil { - err = ChownForEmulator(v.volumeMountPoint, v.chownRecursively) + err = fsys.ChownForEmulator(v.volumeMountPoint, v.chownRecursively) } if err != nil { return nil, nil, fmt.Errorf("failed to create vm pod path: %v", err) @@ -65,7 +66,7 @@ func (v *filesystemVolume) Setup() (*libvirtxml.DomainDisk, *libvirtxml.DomainFi func (v *filesystemVolume) Teardown() error { var err error if _, err = os.Stat(v.volumeMountPoint); err == nil { - err = v.owner.Mounter().Unmount(v.volumeMountPoint, true) + err = v.owner.FileSystem().Unmount(v.volumeMountPoint, true) } if err == nil { err = os.Remove(v.volumeMountPoint) diff --git a/pkg/libvirttools/gc.go b/pkg/libvirttools/gc.go index 19ee9645b..99a14e367 100644 --- a/pkg/libvirttools/gc.go +++ b/pkg/libvirttools/gc.go @@ -95,7 +95,7 @@ func (v *VirtualizationTool) checkSandboxNetNs(sandbox metadata.PodSandboxMetada return err } - if !v.mountPointChecker.IsPathAnNs(sinfo.ContainerSideNetwork.NsPath) { + if !v.fsys.IsPathAnNs(sinfo.ContainerSideNetwork.NsPath) { // NS didn't found, need RunSandbox again if err := sandbox.Save(func(s *types.PodSandboxInfo) (*types.PodSandboxInfo, error) { if s != nil { diff --git a/pkg/libvirttools/root_volumesource_test.go b/pkg/libvirttools/root_volumesource_test.go index c61aa81e4..abd13324c 100644 --- a/pkg/libvirttools/root_volumesource_test.go +++ b/pkg/libvirttools/root_volumesource_test.go @@ -23,6 +23,7 @@ import ( libvirtxml "github.com/libvirt/libvirt-go-xml" digest "github.com/opencontainers/go-digest" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/metadata/types" "github.com/Mirantis/virtlet/pkg/utils" fakeutils "github.com/Mirantis/virtlet/pkg/utils/fake" @@ -299,7 +300,7 @@ func (vo fakeVolumeOwner) KubeletRootDir() string { return "" } func (vo fakeVolumeOwner) VolumePoolName() string { return "" } -func (vo fakeVolumeOwner) Mounter() utils.Mounter { return utils.NullMounter } +func (vo fakeVolumeOwner) FileSystem() fs.FileSystem { return fs.NullFileSystem } func (vo fakeVolumeOwner) SharedFilesystemPath() string { return "/var/lib/virtlet/fs" } diff --git a/pkg/libvirttools/virtualization.go b/pkg/libvirttools/virtualization.go index 5ccbdfff6..739ef16ca 100644 --- a/pkg/libvirttools/virtualization.go +++ b/pkg/libvirttools/virtualization.go @@ -30,6 +30,7 @@ import ( kubetypes "k8s.io/kubernetes/pkg/kubelet/types" vconfig "github.com/Mirantis/virtlet/pkg/config" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/metadata" "github.com/Mirantis/virtlet/pkg/metadata/types" "github.com/Mirantis/virtlet/pkg/utils" @@ -235,17 +236,15 @@ type VirtualizationConfig struct { // VirtualizationTool provides methods to operate on libvirt. type VirtualizationTool struct { - domainConn virt.DomainConnection - storageConn virt.StorageConnection - imageManager ImageManager - metadataStore metadata.Store - clock clockwork.Clock - volumeSource VMVolumeSource - config VirtualizationConfig - mounter utils.Mounter - mountPointChecker utils.MountPointChecker - filesManipulator utils.FilesManipulator - commander utils.Commander + domainConn virt.DomainConnection + storageConn virt.StorageConnection + imageManager ImageManager + metadataStore metadata.Store + clock clockwork.Clock + volumeSource VMVolumeSource + config VirtualizationConfig + fsys fs.FileSystem + commander utils.Commander } var _ volumeOwner = &VirtualizationTool{} @@ -255,22 +254,18 @@ var _ volumeOwner = &VirtualizationTool{} func NewVirtualizationTool(domainConn virt.DomainConnection, storageConn virt.StorageConnection, imageManager ImageManager, metadataStore metadata.Store, volumeSource VMVolumeSource, - config VirtualizationConfig, mounter utils.Mounter, - mountPointChecker utils.MountPointChecker, - filesManipulator utils.FilesManipulator, + config VirtualizationConfig, fsys fs.FileSystem, commander utils.Commander) *VirtualizationTool { return &VirtualizationTool{ - domainConn: domainConn, - storageConn: storageConn, - imageManager: imageManager, - metadataStore: metadataStore, - clock: clockwork.NewRealClock(), - volumeSource: volumeSource, - config: config, - mounter: mounter, - mountPointChecker: mountPointChecker, - filesManipulator: filesManipulator, - commander: commander, + domainConn: domainConn, + storageConn: storageConn, + imageManager: imageManager, + metadataStore: metadataStore, + clock: clockwork.NewRealClock(), + volumeSource: volumeSource, + config: config, + fsys: fsys, + commander: commander, } } @@ -944,8 +939,8 @@ func (v *VirtualizationTool) KubeletRootDir() string { return v.config.KubeletRo // VolumePoolName implements volumeOwner VolumePoolName method func (v *VirtualizationTool) VolumePoolName() string { return v.config.VolumePoolName } -// Mounter implements volumeOwner Mounter method -func (v *VirtualizationTool) Mounter() utils.Mounter { return v.mounter } +// FileSystem implements volumeOwner FileSystem method +func (v *VirtualizationTool) FileSystem() fs.FileSystem { return v.fsys } // SharedFilesystemPath implements volumeOwner SharedFilesystemPath method func (v *VirtualizationTool) SharedFilesystemPath() string { return v.config.SharedFilesystemPath } diff --git a/pkg/libvirttools/virtualization_test.go b/pkg/libvirttools/virtualization_test.go index c9961561b..98fc7f537 100644 --- a/pkg/libvirttools/virtualization_test.go +++ b/pkg/libvirttools/virtualization_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016-2017 Mirantis +Copyright 2016-2019 Mirantis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -29,6 +29,8 @@ import ( "github.com/jonboulle/clockwork" "github.com/Mirantis/virtlet/pkg/flexvolume" + "github.com/Mirantis/virtlet/pkg/fs" + fakefs "github.com/Mirantis/virtlet/pkg/fs/fake" "github.com/Mirantis/virtlet/pkg/metadata" fakemeta "github.com/Mirantis/virtlet/pkg/metadata/fake" "github.com/Mirantis/virtlet/pkg/metadata/types" @@ -94,15 +96,12 @@ func newContainerTester(t *testing.T, rec *testutils.TopLevelRecorder, cmds []fa } fakeCommander := fakeutils.NewCommander(rec, cmds) fakeCommander.ReplaceTempPath("__pods__", "/fakedev") - fm := utils.DefaultFilesManipulator - if files != nil { - fm = fakeutils.NewFakeFilesManipulator(rec, files) - } + + fs := fakefs.NewFakeFileSystem(t, rec, "", files) ct.virtTool = NewVirtualizationTool( ct.domainConn, ct.storageConn, imageManager, ct.metadataStore, - GetDefaultVolumeSource(), virtConfig, utils.NullMounter, - utils.FakeMountPointChecker, fm, + GetDefaultVolumeSource(), virtConfig, fs, fakeCommander) ct.virtTool.SetClock(ct.clock) @@ -339,7 +338,7 @@ func TestDomainDefinitions(t *testing.T) { flexVolumeDriver := flexvolume.NewDriver(func() string { // note that this is only good for just one flexvolume return fakeUUID - }, utils.NullMounter) + }, fs.NullFileSystem) for _, tc := range []struct { name string annotations map[string]string diff --git a/pkg/libvirttools/volumes.go b/pkg/libvirttools/volumes.go index 9c9a8148c..7f51c0d21 100644 --- a/pkg/libvirttools/volumes.go +++ b/pkg/libvirttools/volumes.go @@ -20,6 +20,7 @@ import ( libvirtxml "github.com/libvirt/libvirt-go-xml" digest "github.com/opencontainers/go-digest" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/metadata/types" "github.com/Mirantis/virtlet/pkg/utils" "github.com/Mirantis/virtlet/pkg/virt" @@ -45,7 +46,7 @@ type volumeOwner interface { RawDevices() []string KubeletRootDir() string VolumePoolName() string - Mounter() utils.Mounter + FileSystem() fs.FileSystem SharedFilesystemPath() string Commander() utils.Commander } diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 5c624663e..7adb9e17c 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -26,6 +26,7 @@ import ( "github.com/Mirantis/virtlet/pkg/api/virtlet.k8s/v1" "github.com/Mirantis/virtlet/pkg/diag" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/image" "github.com/Mirantis/virtlet/pkg/imagetranslation" "github.com/Mirantis/virtlet/pkg/libvirttools" @@ -134,16 +135,10 @@ func (v *VirtletManager) Run() error { virtConfig.StreamerSocketPath = streamerSocketPath } - mpc, err := utils.NewMountPointChecker() - if err != nil { - return fmt.Errorf("couldn't create mountpoint checker: %v", err) - } - volSrc := libvirttools.GetDefaultVolumeSource() v.virtTool = libvirttools.NewVirtualizationTool( conn, conn, v.imageStore, v.metadataStore, volSrc, virtConfig, - utils.NewMounter(), mpc, utils.DefaultFilesManipulator, - utils.DefaultCommander) + fs.RealFileSystem, utils.DefaultCommander) runtimeService := NewVirtletRuntimeService(v.virtTool, v.metadataStore, v.fdManager, streamServer, v.imageStore, nil) imageService := NewVirtletImageService(v.imageStore, translator, nil) diff --git a/pkg/manager/runtime_test.go b/pkg/manager/runtime_test.go index 971b6d315..59d12433f 100644 --- a/pkg/manager/runtime_test.go +++ b/pkg/manager/runtime_test.go @@ -38,6 +38,8 @@ import ( "github.com/Mirantis/virtlet/pkg/cni" "github.com/Mirantis/virtlet/pkg/flexvolume" + "github.com/Mirantis/virtlet/pkg/fs" + fakefs "github.com/Mirantis/virtlet/pkg/fs/fake" "github.com/Mirantis/virtlet/pkg/image" fakeimage "github.com/Mirantis/virtlet/pkg/image/fake" "github.com/Mirantis/virtlet/pkg/libvirttools" @@ -241,8 +243,7 @@ func makeVirtletCRITester(t *testing.T) *virtletCRITester { virtTool := libvirttools.NewVirtualizationTool( domainConn, storageConn, imageStore, metadataStore, libvirttools.GetDefaultVolumeSource(), virtConfig, - utils.NullMounter, utils.FakeMountPointChecker, - utils.DefaultFilesManipulator, commander) + fakefs.NewFakeFileSystem(t, rec, "", nil), commander) virtTool.SetClock(clock) streamServer := newFakeStreamServer(rec.Child("streamServer")) criHandler := &criHandler{ @@ -296,7 +297,7 @@ func (tst *virtletCRITester) invoke(name string, req interface{}, failOnError bo func (tst *virtletCRITester) getSampleFlexvolMounts(podSandboxID string) []*kubeapi.Mount { flexVolumeDriver := flexvolume.NewDriver(func() string { return "abb67e3c-71b3-4ddd-5505-8c4215d5c4eb" - }, utils.NullMounter) + }, fs.NullFileSystem) flexVolDir := filepath.Join(tst.kubeletRootDir, podSandboxID, "volumes/virtlet~flexvolume_driver", "vol1") flexVolDef := map[string]interface{}{ "type": "qcow2", diff --git a/pkg/utils/cgroups/controllers.go b/pkg/utils/cgroups/controllers.go index a44331814..6d56c27f3 100644 --- a/pkg/utils/cgroups/controllers.go +++ b/pkg/utils/cgroups/controllers.go @@ -22,6 +22,7 @@ import ( "path/filepath" "strings" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/utils" ) @@ -31,7 +32,7 @@ const ( // Controller represents a named controller for a process type Controller struct { - fm utils.FilesManipulator + fsys fs.FileSystem name string path string } @@ -50,24 +51,24 @@ type Manager interface { // RealManager provides an implementation of Manager which is // using default linux system paths to access info about cgroups for processes. type RealManager struct { - fm utils.FilesManipulator - pid string + fsys fs.FileSystem + pid string } var _ Manager = &RealManager{} // NewManager returns an instance of RealManager -func NewManager(pid interface{}, fm utils.FilesManipulator) Manager { - if fm == nil { - fm = utils.DefaultFilesManipulator +func NewManager(pid interface{}, fsys fs.FileSystem) Manager { + if fsys == nil { + fsys = fs.RealFileSystem } - return &RealManager{fm: fm, pid: utils.Stringify(pid)} + return &RealManager{fsys: fsys, pid: utils.Stringify(pid)} } // GetProcessControllers is an implementation of GetProcessControllers method // of Manager interface. func (c *RealManager) GetProcessControllers() (map[string]string, error) { - fr, err := c.fm.FileReader(filepath.Join("/proc", c.pid, "cgroup")) + fr, err := c.fsys.GetDelimitedReader(filepath.Join("/proc", c.pid, "cgroup")) if err != nil { return nil, err } @@ -118,7 +119,7 @@ func (c *RealManager) GetProcessController(controllerName string) (*Controller, } return &Controller{ - fm: c.fm, + fsys: c.fsys, name: controllerName, path: controllerPath, }, nil @@ -126,7 +127,7 @@ func (c *RealManager) GetProcessController(controllerName string) (*Controller, // MoveProcess implements MoveProcess method of Manager func (c *RealManager) MoveProcess(controller, path string) error { - return c.fm.WriteFile( + return c.fsys.WriteFile( filepath.Join(cgroupfs, controller, path, "cgroup.procs"), []byte(utils.Stringify(c.pid)), 0644, @@ -135,7 +136,7 @@ func (c *RealManager) MoveProcess(controller, path string) error { // Set sets the value of a controller setting func (c *Controller) Set(name string, value interface{}) error { - return c.fm.WriteFile( + return c.fsys.WriteFile( filepath.Join(cgroupfs, c.name, c.path, c.name+"."+name), []byte(utils.Stringify(value)), 0644, diff --git a/pkg/utils/fake/files_manipulator.go b/pkg/utils/fake/files_manipulator.go deleted file mode 100644 index 3bc67d96a..000000000 --- a/pkg/utils/fake/files_manipulator.go +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright 2019 Mirantis - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package fake - -import ( - "errors" - "io" - "os" - "strings" - - "github.com/Mirantis/virtlet/pkg/utils" - testutils "github.com/Mirantis/virtlet/pkg/utils/testing" -) - -type fakeFileReader struct { - rec testutils.Recorder - fileData string -} - -var _ utils.FileReader = &fakeFileReader{} - -// ReadString implements ReadString method of utils.FileReader interface -func (fr *fakeFileReader) ReadString(delim byte) (line string, err error) { - lines := strings.SplitN(fr.fileData, string(delim), 1) - line = lines[0] - if len(lines) > 1 { - fr.fileData = lines[1] - } else { - err = io.EOF - } - fr.rec.Rec("ReadString: "+line, err) - return -} - -// Close implements Close method of utils.FileReader interface -func (fr *fakeFileReader) Close() error { - return nil -} - -type fakeFilesManipulator struct { - readerData map[string]string - rec testutils.Recorder -} - -var _ utils.FilesManipulator = &fakeFilesManipulator{} - -// FileReader implements the FileReader method of FilesManipulator interface -func (fm *fakeFilesManipulator) FileReader(path string) (utils.FileReader, error) { - data, ok := fm.readerData[path] - if !ok { - fm.rec.Rec("FileReader - undefined path", path) - return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("file not found")} - } - fm.rec.Rec("FileReader", path) - return &fakeFileReader{rec: fm.rec, fileData: data}, nil -} - -// WriteFile implements the WriteFile method of FilesManipulator interface -func (fm *fakeFilesManipulator) WriteFile(path string, data []byte, perm os.FileMode) error { - fm.rec.Rec("WriteFile: "+path, string(data)) - return nil -} - -// NewFakeFilesManipulator returns fakeFilesManipulator instance as utils.FilesManipulator interface -func NewFakeFilesManipulator(rec testutils.Recorder, files map[string]string) utils.FilesManipulator { - return &fakeFilesManipulator{rec: rec, readerData: files} -} diff --git a/pkg/utils/files_manipulator.go b/pkg/utils/files_manipulator.go deleted file mode 100644 index d21e6850a..000000000 --- a/pkg/utils/files_manipulator.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -Copyright 2019 Mirantis - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package utils - -import ( - "bufio" - "io/ioutil" - "os" -) - -// FilesManipulator provides an interface to files manipulation -type FilesManipulator interface { - // FileReader given a path returns fileReader instance for data under - // that path or any occured during the operation error. - FileReader(path string) (FileReader, error) - // WriteFile given a path creates new file or truncates - // existing one, setting for it provided permissions and filling it - // with provided data. Returns an error if any occured - // during the operation. - WriteFile(path string, data []byte, perm os.FileMode) error -} - -type realFilesManipulator struct{} - -// DefaultFilesManipulator is an implementation of FilesManager which is -// accessing real filesystem to provide requested data. -var DefaultFilesManipulator FilesManipulator = &realFilesManipulator{} - -// FileReader provides an interface to file reading -type FileReader interface { - // ReadString returns next part of data up to (and including it) - // delimeter byte. - ReadString(delim byte) (string, error) - // Close closes the reader. - Close() error -} - -type fileReader struct { - f *os.File - r *bufio.Reader -} - -var _ FileReader = &fileReader{} - -// ReadString implements the ReadString method of FileReader interface -func (fr *fileReader) ReadString(delim byte) (string, error) { - return fr.r.ReadString(delim) -} - -// Close implements the Close method of FileReader interface -func (fr *fileReader) Close() error { - return fr.f.Close() -} - -// FileReader implements the FileReader method of FilesManipulator interface -func (fm *realFilesManipulator) FileReader(path string) (FileReader, error) { - file, err := os.Open(path) - if err != nil { - return nil, err - } - f := &fileReader{f: file} - - r := bufio.NewReader(f.f) - if err != nil { - return nil, err - } - f.r = r - return f, nil -} - -// WriteFile implements the WriteFile method of FilesManipulator interface -func (fm *realFilesManipulator) WriteFile(path string, data []byte, perm os.FileMode) error { - return ioutil.WriteFile(path, data, perm) -} diff --git a/pkg/utils/files_test.go b/pkg/utils/files_test.go deleted file mode 100644 index 83de95ac4..000000000 --- a/pkg/utils/files_test.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 2017 Mirantis - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package utils - -import ( - "io/ioutil" - "os" - "path/filepath" - "reflect" - "testing" - - testutils "github.com/Mirantis/virtlet/pkg/utils/testing" -) - -func TestFileUtils(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "genisoimage-test") - if err != nil { - t.Fatalf("ioutil.TempDir(): %v", err) - } - defer os.RemoveAll(tmpDir) - - if err := WriteFiles(tmpDir, map[string][]byte{ - "image.cd/file1.txt": []byte("foo"), - "image.cd/anotherfile.txt": []byte("bar"), - }); err != nil { - t.Fatalf("WriteFiles(): %v", err) - } - - isoPath := filepath.Join(tmpDir, "image.iso") - srcPath := filepath.Join(tmpDir, "image.cd") - if err := GenIsoImage(isoPath, "isoimage", srcPath); err != nil { - t.Fatalf("GenIsoImage(): %v", err) - } - - m, err := testutils.IsoToMap(isoPath) - if err != nil { - t.Fatalf("IsoToMap(): %v", err) - } - expectedFiles := map[string]interface{}{ - "file1.txt": "foo", - "anotherfile.txt": "bar", - } - if !reflect.DeepEqual(m, expectedFiles) { - t.Errorf("bad iso content: %#v instead of %#v", m, expectedFiles) - } -} diff --git a/pkg/utils/mounter.go b/pkg/utils/mounter.go deleted file mode 100644 index 8bb3716de..000000000 --- a/pkg/utils/mounter.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2018 Mirantis - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package utils - -// Mounter defines mount/unmount interface -type Mounter interface { - // Mount mounts the specified source under the target path. - // For bind mounts, bind must be true. - Mount(source string, target string, fstype string, bind bool) error - // Unmount unmounts the specified target directory. If detach - // is true, MNT_DETACH option is used (disconnect the - // filesystem for the new accesses even if it's busy). - Unmount(target string, detach bool) error -} - -type nullMounter struct{} - -func (m *nullMounter) Mount(source string, target string, fstype string, bind bool) error { - return nil -} - -func (m *nullMounter) Unmount(target string, detach bool) error { - return nil -} - -// NullMounter is a mounter that's used for testing and does nothing -// instead of mounting/unmounting. -var NullMounter Mounter = &nullMounter{} diff --git a/pkg/utils/mountinfo.go b/pkg/utils/mountinfo.go deleted file mode 100644 index 2fe9fa1b8..000000000 --- a/pkg/utils/mountinfo.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -Copyright 2018 Mirantis - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package utils - -import ( - "bufio" - "io" - "os" - "path/filepath" - "strings" - - "github.com/golang/glog" -) - -type mountEntry struct { - Source string - Fs string -} - -// MountPointChecker is used to check if a directory entry is a mount point. -// It returns its source info and filesystem type. -type MountPointChecker interface { - // CheckMountPointInfo checks if entry is a mountpoint (second returned value will be true) - // and if so returns mountInfo for it. In other case it returns false as a second value. - CheckMountPointInfo(string) (mountEntry, bool) - // IsPathAnNs verifies if the path is a mountpoint with nsfs filesystem type - IsPathAnNs(string) bool -} - -type mountPointChecker struct { - mountInfo map[string]mountEntry -} - -var _ MountPointChecker = mountPointChecker{} - -// NewMountPointChecker returns a new instance of MountPointChecker -func NewMountPointChecker() (MountPointChecker, error) { - file, err := os.Open("/proc/self/mountinfo") - if err != nil { - return mountPointChecker{}, err - } - defer file.Close() - - mi := make(map[string]mountEntry) - - reader := bufio.NewReader(file) -LineReader: - for { - line, err := reader.ReadString('\n') - switch err { - case io.EOF: - break LineReader - case nil: - // strip eol - line = strings.Trim(line, "\n") - - // split and parse entries acording to section 3.5 in - // https://www.kernel.org/doc/Documentation/filesystems/proc.txt - // TODO: whitespaces and control chars in names are encoded as - // octal values (e.g. for "x x": "x\040x") what should be expanded - // in both mount point source and target - parts := strings.Split(line, " ") - mi[parts[4]] = mountEntry{Source: parts[9], Fs: parts[8]} - default: - return mountPointChecker{}, err - } - } - return mountPointChecker{mountInfo: mi}, nil -} - -// CheckMountPointInfo implements CheckMountPointInfo method of MountPointChecker interface -func (mpc mountPointChecker) CheckMountPointInfo(path string) (mountEntry, bool) { - entry, ok := mpc.mountInfo[path] - return entry, ok -} - -// IsPathNs implements IsPathAnNs method of MountPointChecker interface -func (mpc mountPointChecker) IsPathAnNs(path string) bool { - _, err := os.Stat(path) - if err != nil { - if !os.IsNotExist(err) { - glog.Errorf("Cannot verify existence of %q: %v", path, err) - } - return false - } - realpath, err := filepath.EvalSymlinks(path) - if err != nil { - glog.Errorf("Cannot verify real path of %q: %v", path, err) - return false - } - - entry, isMountPoint := mpc.CheckMountPointInfo(realpath) - if !isMountPoint { - return false - } - return entry.Fs == "nsfs" || entry.Fs == "proc" -} - -type fakeMountPointChecker struct{} - -// FakeMountPointChecker is defined there for unittests -var FakeMountPointChecker MountPointChecker = fakeMountPointChecker{} - -// CheckMountPointInfo is a fake implementation for MountPointChecker interface -func (mpc fakeMountPointChecker) CheckMountPointInfo(path string) (mountEntry, bool) { - return mountEntry{}, false -} - -// IsPathAnNs is a fake implementation for MountPointChecker interface -func (mpc fakeMountPointChecker) IsPathAnNs(path string) bool { - return false -} diff --git a/tests/integration/container_test.go b/tests/integration/container_test.go index f9a8e5b47..972739051 100644 --- a/tests/integration/container_test.go +++ b/tests/integration/container_test.go @@ -28,6 +28,7 @@ import ( kubeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2" "github.com/Mirantis/virtlet/pkg/flexvolume" + "github.com/Mirantis/virtlet/pkg/fs" "github.com/Mirantis/virtlet/pkg/utils" "github.com/Mirantis/virtlet/tests/criapi" ) @@ -67,7 +68,7 @@ func newContainerTester(t *testing.T) *containerTester { {Image: imageCirrosUrl}, {Image: imageCopyCirrosUrl}, }, - fv: flexvolume.NewDriver(utils.NewUUID, utils.NewMounter()), + fv: flexvolume.NewDriver(utils.NewUUID, fs.RealFileSystem), } }