diff options
author | Matthew Heon <matthew.heon@gmail.com> | 2017-11-01 11:24:59 -0400 |
---|---|---|
committer | Matthew Heon <matthew.heon@gmail.com> | 2017-11-01 11:24:59 -0400 |
commit | a031b83a09a8628435317a03f199cdc18b78262f (patch) | |
tree | bc017a96769ce6de33745b8b0b1304ccf38e9df0 /vendor/k8s.io/kubernetes/pkg/util/mount | |
parent | 2b74391cd5281f6fdf391ff8ad50fd1490f6bf89 (diff) | |
download | podman-a031b83a09a8628435317a03f199cdc18b78262f.tar.gz podman-a031b83a09a8628435317a03f199cdc18b78262f.tar.bz2 podman-a031b83a09a8628435317a03f199cdc18b78262f.zip |
Initial checkin from CRI-O repo
Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
Diffstat (limited to 'vendor/k8s.io/kubernetes/pkg/util/mount')
7 files changed, 1239 insertions, 0 deletions
diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/doc.go b/vendor/k8s.io/kubernetes/pkg/util/mount/doc.go new file mode 100644 index 000000000..15179e53f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors. + +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 mount defines an interface to mounting filesystems. +package mount // import "k8s.io/kubernetes/pkg/util/mount" diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/fake.go b/vendor/k8s.io/kubernetes/pkg/util/mount/fake.go new file mode 100644 index 000000000..2b71fa0a7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/fake.go @@ -0,0 +1,173 @@ +/* +Copyright 2015 The Kubernetes Authors. + +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 mount + +import ( + "path/filepath" + "sync" + + "github.com/golang/glog" +) + +// FakeMounter implements mount.Interface for tests. +type FakeMounter struct { + MountPoints []MountPoint + Log []FakeAction + // Some tests run things in parallel, make sure the mounter does not produce + // any golang's DATA RACE warnings. + mutex sync.Mutex +} + +var _ Interface = &FakeMounter{} + +// Values for FakeAction.Action +const FakeActionMount = "mount" +const FakeActionUnmount = "unmount" + +// FakeAction objects are logged every time a fake mount or unmount is called. +type FakeAction struct { + Action string // "mount" or "unmount" + Target string // applies to both mount and unmount actions + Source string // applies only to "mount" actions + FSType string // applies only to "mount" actions +} + +func (f *FakeMounter) ResetLog() { + f.mutex.Lock() + defer f.mutex.Unlock() + + f.Log = []FakeAction{} +} + +func (f *FakeMounter) Mount(source string, target string, fstype string, options []string) error { + f.mutex.Lock() + defer f.mutex.Unlock() + + // find 'bind' option + for _, option := range options { + if option == "bind" { + // This is a bind-mount. In order to mimic linux behaviour, we must + // use the original device of the bind-mount as the real source. + // E.g. when mounted /dev/sda like this: + // $ mount /dev/sda /mnt/test + // $ mount -o bind /mnt/test /mnt/bound + // then /proc/mount contains: + // /dev/sda /mnt/test + // /dev/sda /mnt/bound + // (and not /mnt/test /mnt/bound) + // I.e. we must use /dev/sda as source instead of /mnt/test in the + // bind mount. + for _, mnt := range f.MountPoints { + if source == mnt.Path { + source = mnt.Device + break + } + } + break + } + } + + // If target is a symlink, get its absolute path + absTarget, err := filepath.EvalSymlinks(target) + if err != nil { + absTarget = target + } + + f.MountPoints = append(f.MountPoints, MountPoint{Device: source, Path: absTarget, Type: fstype}) + glog.V(5).Infof("Fake mounter: mounted %s to %s", source, absTarget) + f.Log = append(f.Log, FakeAction{Action: FakeActionMount, Target: absTarget, Source: source, FSType: fstype}) + return nil +} + +func (f *FakeMounter) Unmount(target string) error { + f.mutex.Lock() + defer f.mutex.Unlock() + + // If target is a symlink, get its absolute path + absTarget, err := filepath.EvalSymlinks(target) + if err != nil { + absTarget = target + } + + newMountpoints := []MountPoint{} + for _, mp := range f.MountPoints { + if mp.Path == absTarget { + glog.V(5).Infof("Fake mounter: unmounted %s from %s", mp.Device, absTarget) + // Don't copy it to newMountpoints + continue + } + newMountpoints = append(newMountpoints, MountPoint{Device: mp.Device, Path: mp.Path, Type: mp.Type}) + } + f.MountPoints = newMountpoints + f.Log = append(f.Log, FakeAction{Action: FakeActionUnmount, Target: absTarget}) + return nil +} + +func (f *FakeMounter) List() ([]MountPoint, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + return f.MountPoints, nil +} + +func (f *FakeMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return (mp.Path == dir) +} + +func (f *FakeMounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(f, dir) +} + +func (f *FakeMounter) IsLikelyNotMountPoint(file string) (bool, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + // If file is a symlink, get its absolute path + absFile, err := filepath.EvalSymlinks(file) + if err != nil { + absFile = file + } + + for _, mp := range f.MountPoints { + if mp.Path == absFile { + glog.V(5).Infof("isLikelyNotMountPoint for %s: mounted %s, false", file, mp.Path) + return false, nil + } + } + glog.V(5).Infof("isLikelyNotMountPoint for %s: true", file) + return true, nil +} + +func (f *FakeMounter) DeviceOpened(pathname string) (bool, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + for _, mp := range f.MountPoints { + if mp.Device == pathname { + return true, nil + } + } + return false, nil +} + +func (f *FakeMounter) PathIsDevice(pathname string) (bool, error) { + return true, nil +} + +func (f *FakeMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return getDeviceNameFromMount(f, mountPath, pluginDir) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/mount.go b/vendor/k8s.io/kubernetes/pkg/util/mount/mount.go new file mode 100644 index 000000000..0c458d64b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/mount.go @@ -0,0 +1,245 @@ +/* +Copyright 2014 The Kubernetes Authors. + +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. +*/ + +// TODO(thockin): This whole pkg is pretty linux-centric. As soon as we have +// an alternate platform, we will need to abstract further. +package mount + +import ( + "fmt" + "path" + "path/filepath" + "strings" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/util/exec" +) + +const ( + // Default mount command if mounter path is not specified + defaultMountCommand = "mount" + MountsInGlobalPDPath = "mounts" +) + +type Interface interface { + // Mount mounts source to target as fstype with given options. + Mount(source string, target string, fstype string, options []string) error + // Unmount unmounts given target. + Unmount(target string) error + // List returns a list of all mounted filesystems. This can be large. + // On some platforms, reading mounts is not guaranteed consistent (i.e. + // it could change between chunked reads). This is guaranteed to be + // consistent. + List() ([]MountPoint, error) + // IsMountPointMatch determines if the mountpoint matches the dir + IsMountPointMatch(mp MountPoint, dir string) bool + // IsNotMountPoint determines if a directory is a mountpoint. + // It should return ErrNotExist when the directory does not exist. + // IsNotMountPoint is more expensive than IsLikelyNotMountPoint. + // IsNotMountPoint detects bind mounts in linux. + // IsNotMountPoint enumerates all the mountpoints using List() and + // the list of mountpoints may be large, then it uses + // IsMountPointMatch to evaluate whether the directory is a mountpoint + IsNotMountPoint(file string) (bool, error) + // IsLikelyNotMountPoint uses heuristics to determine if a directory + // is a mountpoint. + // It should return ErrNotExist when the directory does not exist. + // IsLikelyNotMountPoint does NOT properly detect all mountpoint types + // most notably linux bind mounts. + IsLikelyNotMountPoint(file string) (bool, error) + // DeviceOpened determines if the device is in use elsewhere + // on the system, i.e. still mounted. + DeviceOpened(pathname string) (bool, error) + // PathIsDevice determines if a path is a device. + PathIsDevice(pathname string) (bool, error) + // GetDeviceNameFromMount finds the device name by checking the mount path + // to get the global mount path which matches its plugin directory + GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) +} + +// Compile-time check to ensure all Mounter implementations satisfy +// the mount interface +var _ Interface = &Mounter{} + +// This represents a single line in /proc/mounts or /etc/fstab. +type MountPoint struct { + Device string + Path string + Type string + Opts []string + Freq int + Pass int +} + +// SafeFormatAndMount probes a device to see if it is formatted. +// Namely it checks to see if a file system is present. If so it +// mounts it otherwise the device is formatted first then mounted. +type SafeFormatAndMount struct { + Interface + Runner exec.Interface +} + +// FormatAndMount formats the given disk, if needed, and mounts it. +// That is if the disk is not formatted and it is not being mounted as +// read-only it will format it first then mount it. Otherwise, if the +// disk is already formatted or it is being mounted as read-only, it +// will be mounted without formatting. +func (mounter *SafeFormatAndMount) FormatAndMount(source string, target string, fstype string, options []string) error { + // Don't attempt to format if mounting as readonly. Go straight to mounting. + for _, option := range options { + if option == "ro" { + return mounter.Interface.Mount(source, target, fstype, options) + } + } + return mounter.formatAndMount(source, target, fstype, options) +} + +// New returns a mount.Interface for the current system. +// It provides options to override the default mounter behavior. +// mounterPath allows using an alternative to `/bin/mount` for mounting. +func New(mounterPath string) Interface { + return &Mounter{ + mounterPath: mounterPath, + } +} + +// GetMountRefs finds all other references to the device referenced +// by mountPath; returns a list of paths. +func GetMountRefs(mounter Interface, mountPath string) ([]string, error) { + mps, err := mounter.List() + if err != nil { + return nil, err + } + + // Find the device name. + deviceName := "" + // If mountPath is symlink, need get its target path. + slTarget, err := filepath.EvalSymlinks(mountPath) + if err != nil { + slTarget = mountPath + } + for i := range mps { + if mps[i].Path == slTarget { + deviceName = mps[i].Device + break + } + } + + // Find all references to the device. + var refs []string + if deviceName == "" { + glog.Warningf("could not determine device for path: %q", mountPath) + } else { + for i := range mps { + if mps[i].Device == deviceName && mps[i].Path != slTarget { + refs = append(refs, mps[i].Path) + } + } + } + return refs, nil +} + +// GetDeviceNameFromMount: given a mnt point, find the device from /proc/mounts +// returns the device name, reference count, and error code +func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, error) { + mps, err := mounter.List() + if err != nil { + return "", 0, err + } + + // Find the device name. + // FIXME if multiple devices mounted on the same mount path, only the first one is returned + device := "" + // If mountPath is symlink, need get its target path. + slTarget, err := filepath.EvalSymlinks(mountPath) + if err != nil { + slTarget = mountPath + } + for i := range mps { + if mps[i].Path == slTarget { + device = mps[i].Device + break + } + } + + // Find all references to the device. + refCount := 0 + for i := range mps { + if mps[i].Device == device { + refCount++ + } + } + return device, refCount, nil +} + +// getDeviceNameFromMount find the device name from /proc/mounts in which +// the mount path reference should match the given plugin directory. In case no mount path reference +// matches, returns the volume name taken from its given mountPath +func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) { + refs, err := GetMountRefs(mounter, mountPath) + if err != nil { + glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) + return "", err + } + if len(refs) == 0 { + glog.V(4).Infof("Directory %s is not mounted", mountPath) + return "", fmt.Errorf("directory %s is not mounted", mountPath) + } + basemountPath := path.Join(pluginDir, MountsInGlobalPDPath) + for _, ref := range refs { + if strings.HasPrefix(ref, basemountPath) { + volumeID, err := filepath.Rel(basemountPath, ref) + if err != nil { + glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) + return "", err + } + return volumeID, nil + } + } + + return path.Base(mountPath), nil +} + +// IsNotMountPoint determines if a directory is a mountpoint. +// It should return ErrNotExist when the directory does not exist. +// This method uses the List() of all mountpoints +// It is more extensive than IsLikelyNotMountPoint +// and it detects bind mounts in linux +func IsNotMountPoint(mounter Interface, file string) (bool, error) { + // IsLikelyNotMountPoint provides a quick check + // to determine whether file IS A mountpoint + notMnt, notMntErr := mounter.IsLikelyNotMountPoint(file) + if notMntErr != nil { + return notMnt, notMntErr + } + // identified as mountpoint, so return this fact + if notMnt == false { + return notMnt, nil + } + // check all mountpoints since IsLikelyNotMountPoint + // is not reliable for some mountpoint types + mountPoints, mountPointsErr := mounter.List() + if mountPointsErr != nil { + return notMnt, mountPointsErr + } + for _, mp := range mountPoints { + if mounter.IsMountPointMatch(mp, file) { + notMnt = false + break + } + } + return notMnt, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go b/vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go new file mode 100644 index 000000000..4c141ad5b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go @@ -0,0 +1,432 @@ +// +build linux + +/* +Copyright 2014 The Kubernetes Authors. + +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 mount + +import ( + "bufio" + "fmt" + "hash/fnv" + "io" + "os" + "os/exec" + "strconv" + "strings" + "syscall" + + "github.com/golang/glog" + "k8s.io/apimachinery/pkg/util/sets" + utilexec "k8s.io/kubernetes/pkg/util/exec" +) + +const ( + // How many times to retry for a consistent read of /proc/mounts. + maxListTries = 3 + // Number of fields per line in /proc/mounts as per the fstab man page. + expectedNumFieldsPerLine = 6 + // Location of the mount file to use + procMountsPath = "/proc/mounts" +) + +const ( + // 'fsck' found errors and corrected them + fsckErrorsCorrected = 1 + // 'fsck' found errors but exited without correcting them + fsckErrorsUncorrected = 4 +) + +// Mounter provides the default implementation of mount.Interface +// for the linux platform. This implementation assumes that the +// kubelet is running in the host's root mount namespace. +type Mounter struct { + mounterPath string +} + +// Mount mounts source to target as fstype with given options. 'source' and 'fstype' must +// be an emtpy string in case it's not required, e.g. for remount, or for auto filesystem +// type, where kernel handles fs type for you. The mount 'options' is a list of options, +// currently come from mount(8), e.g. "ro", "remount", "bind", etc. If no more option is +// required, call Mount with an empty string list or nil. +func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error { + // Path to mounter binary if containerized mounter is needed. Otherwise, it is set to empty. + // All Linux distros are expected to be shipped with a mount utility that an support bind mounts. + mounterPath := "" + bind, bindRemountOpts := isBind(options) + if bind { + err := doMount(mounterPath, defaultMountCommand, source, target, fstype, []string{"bind"}) + if err != nil { + return err + } + return doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts) + } + // The list of filesystems that require containerized mounter on GCI image cluster + fsTypesNeedMounter := sets.NewString("nfs", "glusterfs", "ceph", "cifs") + if fsTypesNeedMounter.Has(fstype) { + mounterPath = mounter.mounterPath + } + return doMount(mounterPath, defaultMountCommand, source, target, fstype, options) +} + +// isBind detects whether a bind mount is being requested and makes the remount options to +// use in case of bind mount, due to the fact that bind mount doesn't respect mount options. +// The list equals: +// options - 'bind' + 'remount' (no duplicate) +func isBind(options []string) (bool, []string) { + bindRemountOpts := []string{"remount"} + bind := false + + if len(options) != 0 { + for _, option := range options { + switch option { + case "bind": + bind = true + break + case "remount": + break + default: + bindRemountOpts = append(bindRemountOpts, option) + } + } + } + + return bind, bindRemountOpts +} + +// doMount runs the mount command. mounterPath is the path to mounter binary if containerized mounter is used. +func doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string) error { + mountArgs := makeMountArgs(source, target, fstype, options) + if len(mounterPath) > 0 { + mountArgs = append([]string{mountCmd}, mountArgs...) + mountCmd = mounterPath + } + + glog.V(4).Infof("Mounting cmd (%s) with arguments (%s)", mountCmd, mountArgs) + command := exec.Command(mountCmd, mountArgs...) + output, err := command.CombinedOutput() + if err != nil { + glog.Errorf("Mount failed: %v\nMounting command: %s\nMounting arguments: %s %s %s %v\nOutput: %s\n", err, mountCmd, source, target, fstype, options, string(output)) + return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s %s %s %v\nOutput: %s\n", + err, mountCmd, source, target, fstype, options, string(output)) + } + return err +} + +// makeMountArgs makes the arguments to the mount(8) command. +func makeMountArgs(source, target, fstype string, options []string) []string { + // Build mount command as follows: + // mount [-t $fstype] [-o $options] [$source] $target + mountArgs := []string{} + if len(fstype) > 0 { + mountArgs = append(mountArgs, "-t", fstype) + } + if len(options) > 0 { + mountArgs = append(mountArgs, "-o", strings.Join(options, ",")) + } + if len(source) > 0 { + mountArgs = append(mountArgs, source) + } + mountArgs = append(mountArgs, target) + + return mountArgs +} + +// Unmount unmounts the target. +func (mounter *Mounter) Unmount(target string) error { + glog.V(4).Infof("Unmounting %s", target) + command := exec.Command("umount", target) + output, err := command.CombinedOutput() + if err != nil { + return fmt.Errorf("Unmount failed: %v\nUnmounting arguments: %s\nOutput: %s\n", err, target, string(output)) + } + return nil +} + +// List returns a list of all mounted filesystems. +func (*Mounter) List() ([]MountPoint, error) { + return listProcMounts(procMountsPath) +} + +func (mounter *Mounter) IsMountPointMatch(mp MountPoint, dir string) bool { + deletedDir := fmt.Sprintf("%s\\040(deleted)", dir) + return ((mp.Path == dir) || (mp.Path == deletedDir)) +} + +func (mounter *Mounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(mounter, dir) +} + +// IsLikelyNotMountPoint determines if a directory is not a mountpoint. +// It is fast but not necessarily ALWAYS correct. If the path is in fact +// a bind mount from one part of a mount to another it will not be detected. +// mkdir /tmp/a /tmp/b; mount --bin /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b") +// will return true. When in fact /tmp/b is a mount point. If this situation +// if of interest to you, don't use this function... +func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { + stat, err := os.Stat(file) + if err != nil { + return true, err + } + rootStat, err := os.Lstat(file + "/..") + if err != nil { + return true, err + } + // If the directory has a different device as parent, then it is a mountpoint. + if stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev { + return false, nil + } + + return true, nil +} + +// DeviceOpened checks if block device in use by calling Open with O_EXCL flag. +// If pathname is not a device, log and return false with nil error. +// If open returns errno EBUSY, return true with nil error. +// If open returns nil, return false with nil error. +// Otherwise, return false with error +func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { + return exclusiveOpenFailsOnDevice(pathname) +} + +// PathIsDevice uses FileInfo returned from os.Stat to check if path refers +// to a device. +func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) { + return pathIsDevice(pathname) +} + +func exclusiveOpenFailsOnDevice(pathname string) (bool, error) { + isDevice, err := pathIsDevice(pathname) + if err != nil { + return false, fmt.Errorf( + "PathIsDevice failed for path %q: %v", + pathname, + err) + } + if !isDevice { + glog.Errorf("Path %q is not refering to a device.", pathname) + return false, nil + } + fd, errno := syscall.Open(pathname, syscall.O_RDONLY|syscall.O_EXCL, 0) + // If the device is in use, open will return an invalid fd. + // When this happens, it is expected that Close will fail and throw an error. + defer syscall.Close(fd) + if errno == nil { + // device not in use + return false, nil + } else if errno == syscall.EBUSY { + // device is in use + return true, nil + } + // error during call to Open + return false, errno +} + +func pathIsDevice(pathname string) (bool, error) { + finfo, err := os.Stat(pathname) + if os.IsNotExist(err) { + return false, nil + } + // err in call to os.Stat + if err != nil { + return false, err + } + // path refers to a device + if finfo.Mode()&os.ModeDevice != 0 { + return true, nil + } + // path does not refer to device + return false, nil +} + +//GetDeviceNameFromMount: given a mount point, find the device name from its global mount point +func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return getDeviceNameFromMount(mounter, mountPath, pluginDir) +} + +func listProcMounts(mountFilePath string) ([]MountPoint, error) { + hash1, err := readProcMounts(mountFilePath, nil) + if err != nil { + return nil, err + } + + for i := 0; i < maxListTries; i++ { + mps := []MountPoint{} + hash2, err := readProcMounts(mountFilePath, &mps) + if err != nil { + return nil, err + } + if hash1 == hash2 { + // Success + return mps, nil + } + hash1 = hash2 + } + return nil, fmt.Errorf("failed to get a consistent snapshot of %v after %d tries", mountFilePath, maxListTries) +} + +// readProcMounts reads the given mountFilePath (normally /proc/mounts) and produces a hash +// of the contents. If the out argument is not nil, this fills it with MountPoint structs. +func readProcMounts(mountFilePath string, out *[]MountPoint) (uint32, error) { + file, err := os.Open(mountFilePath) + if err != nil { + return 0, err + } + defer file.Close() + return readProcMountsFrom(file, out) +} + +func readProcMountsFrom(file io.Reader, out *[]MountPoint) (uint32, error) { + hash := fnv.New32a() + scanner := bufio.NewReader(file) + for { + line, err := scanner.ReadString('\n') + if err == io.EOF { + break + } + fields := strings.Fields(line) + if len(fields) != expectedNumFieldsPerLine { + return 0, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line) + } + + fmt.Fprintf(hash, "%s", line) + + if out != nil { + mp := MountPoint{ + Device: fields[0], + Path: fields[1], + Type: fields[2], + Opts: strings.Split(fields[3], ","), + } + + freq, err := strconv.Atoi(fields[4]) + if err != nil { + return 0, err + } + mp.Freq = freq + + pass, err := strconv.Atoi(fields[5]) + if err != nil { + return 0, err + } + mp.Pass = pass + + *out = append(*out, mp) + } + } + return hash.Sum32(), nil +} + +// formatAndMount uses unix utils to format and mount the given disk +func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error { + options = append(options, "defaults") + + // Run fsck on the disk to fix repairable issues + glog.V(4).Infof("Checking for issues with fsck on disk: %s", source) + args := []string{"-a", source} + cmd := mounter.Runner.Command("fsck", args...) + out, err := cmd.CombinedOutput() + if err != nil { + ee, isExitError := err.(utilexec.ExitError) + switch { + case err == utilexec.ErrExecutableNotFound: + glog.Warningf("'fsck' not found on system; continuing mount without running 'fsck'.") + case isExitError && ee.ExitStatus() == fsckErrorsCorrected: + glog.Infof("Device %s has errors which were corrected by fsck.", source) + case isExitError && ee.ExitStatus() == fsckErrorsUncorrected: + return fmt.Errorf("'fsck' found errors on device %s but could not correct them: %s.", source, string(out)) + case isExitError && ee.ExitStatus() > fsckErrorsUncorrected: + glog.Infof("`fsck` error %s", string(out)) + } + } + + // Try to mount the disk + glog.V(4).Infof("Attempting to mount disk: %s %s %s", fstype, source, target) + mountErr := mounter.Interface.Mount(source, target, fstype, options) + if mountErr != nil { + // Mount failed. This indicates either that the disk is unformatted or + // it contains an unexpected filesystem. + existingFormat, err := mounter.getDiskFormat(source) + if err != nil { + return err + } + if existingFormat == "" { + // Disk is unformatted so format it. + args = []string{source} + // Use 'ext4' as the default + if len(fstype) == 0 { + fstype = "ext4" + } + + if fstype == "ext4" || fstype == "ext3" { + args = []string{"-F", source} + } + glog.Infof("Disk %q appears to be unformatted, attempting to format as type: %q with options: %v", source, fstype, args) + cmd := mounter.Runner.Command("mkfs."+fstype, args...) + _, err := cmd.CombinedOutput() + if err == nil { + // the disk has been formatted successfully try to mount it again. + glog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target) + return mounter.Interface.Mount(source, target, fstype, options) + } + glog.Errorf("format of disk %q failed: type:(%q) target:(%q) options:(%q)error:(%v)", source, fstype, target, options, err) + return err + } else { + // Disk is already formatted and failed to mount + if len(fstype) == 0 || fstype == existingFormat { + // This is mount error + return mountErr + } else { + // Block device is formatted with unexpected filesystem, let the user know + return fmt.Errorf("failed to mount the volume as %q, it already contains %s. Mount error: %v", fstype, existingFormat, mountErr) + } + } + } + return mountErr +} + +// diskLooksUnformatted uses 'lsblk' to see if the given disk is unformated +func (mounter *SafeFormatAndMount) getDiskFormat(disk string) (string, error) { + args := []string{"-n", "-o", "FSTYPE", disk} + cmd := mounter.Runner.Command("lsblk", args...) + glog.V(4).Infof("Attempting to determine if disk %q is formatted using lsblk with args: (%v)", disk, args) + dataOut, err := cmd.CombinedOutput() + output := string(dataOut) + glog.V(4).Infof("Output: %q", output) + + if err != nil { + glog.Errorf("Could not determine if disk %q is formatted (%v)", disk, err) + return "", err + } + + // Split lsblk output into lines. Unformatted devices should contain only + // "\n". Beware of "\n\n", that's a device with one empty partition. + output = strings.TrimSuffix(output, "\n") // Avoid last empty line + lines := strings.Split(output, "\n") + if lines[0] != "" { + // The device is formatted + return lines[0], nil + } + + if len(lines) == 1 { + // The device is unformatted and has no dependent devices + return "", nil + } + + // The device has dependent devices, most probably partitions (LVM, LUKS + // and MD RAID are reported as FSTYPE and caught above). + return "unknown data, probably partitions", nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/mount_unsupported.go b/vendor/k8s.io/kubernetes/pkg/util/mount/mount_unsupported.go new file mode 100644 index 000000000..632ad0606 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/mount_unsupported.go @@ -0,0 +1,67 @@ +// +build !linux + +/* +Copyright 2014 The Kubernetes Authors. + +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 mount + +type Mounter struct { + mounterPath string +} + +func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error { + return nil +} + +func (mounter *Mounter) Unmount(target string) error { + return nil +} + +func (mounter *Mounter) List() ([]MountPoint, error) { + return []MountPoint{}, nil +} + +func (mounter *Mounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return (mp.Path == dir) +} + +func (mounter *Mounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(mounter, dir) +} + +func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { + return true, nil +} + +func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return "", nil +} + +func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { + return false, nil +} + +func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) { + return true, nil +} + +func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error { + return nil +} + +func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, error) { + return true, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount.go b/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount.go new file mode 100644 index 000000000..4af8ef0d8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount.go @@ -0,0 +1,241 @@ +// +build linux + +/* +Copyright 2014 The Kubernetes Authors. + +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 mount + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/util/exec" +) + +// NsenterMounter is part of experimental support for running the kubelet +// in a container. Currently, all docker containers receive their own mount +// namespaces. NsenterMounter works by executing nsenter to run commands in +// the host's mount namespace. +// +// NsenterMounter requires: +// +// 1. Docker >= 1.6 due to the dependency on the slave propagation mode +// of the bind-mount of the kubelet root directory in the container. +// Docker 1.5 used a private propagation mode for bind-mounts, so mounts +// performed in the host's mount namespace do not propagate out to the +// bind-mount in this docker version. +// 2. The host's root filesystem must be available at /rootfs +// 3. The nsenter binary must be on the Kubelet process' PATH in the container's +// filesystem. +// 4. The Kubelet process must have CAP_SYS_ADMIN (required by nsenter); at +// the present, this effectively means that the kubelet is running in a +// privileged container. +// 5. The volume path used by the Kubelet must be the same inside and outside +// the container and be writable by the container (to initialize volume) +// contents. TODO: remove this requirement. +// 6. The host image must have mount, findmnt, and umount binaries in /bin, +// /usr/sbin, or /usr/bin +// +// For more information about mount propagation modes, see: +// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt +type NsenterMounter struct { + // a map of commands to their paths on the host filesystem + paths map[string]string +} + +func NewNsenterMounter() *NsenterMounter { + m := &NsenterMounter{ + paths: map[string]string{ + "mount": "", + "findmnt": "", + "umount": "", + }, + } + // search for the mount command in other locations besides /usr/bin + for binary := range m.paths { + // default to root + m.paths[binary] = filepath.Join("/", binary) + for _, path := range []string{"/bin", "/usr/sbin", "/usr/bin"} { + binPath := filepath.Join(path, binary) + if _, err := os.Stat(filepath.Join(hostRootFsPath, binPath)); err != nil { + continue + } + m.paths[binary] = binPath + break + } + // TODO: error, so that the kubelet can stop if the mounts don't exist + } + return m +} + +// NsenterMounter implements mount.Interface +var _ = Interface(&NsenterMounter{}) + +const ( + hostRootFsPath = "/rootfs" + hostProcMountsPath = "/rootfs/proc/1/mounts" + nsenterPath = "nsenter" +) + +// Mount runs mount(8) in the host's root mount namespace. Aside from this +// aspect, Mount has the same semantics as the mounter returned by mount.New() +func (n *NsenterMounter) Mount(source string, target string, fstype string, options []string) error { + bind, bindRemountOpts := isBind(options) + + if bind { + err := n.doNsenterMount(source, target, fstype, []string{"bind"}) + if err != nil { + return err + } + return n.doNsenterMount(source, target, fstype, bindRemountOpts) + } + + return n.doNsenterMount(source, target, fstype, options) +} + +// doNsenterMount nsenters the host's mount namespace and performs the +// requested mount. +func (n *NsenterMounter) doNsenterMount(source, target, fstype string, options []string) error { + glog.V(5).Infof("nsenter Mounting %s %s %s %v", source, target, fstype, options) + args := n.makeNsenterArgs(source, target, fstype, options) + + glog.V(5).Infof("Mount command: %v %v", nsenterPath, args) + exec := exec.New() + outputBytes, err := exec.Command(nsenterPath, args...).CombinedOutput() + if len(outputBytes) != 0 { + glog.V(5).Infof("Output of mounting %s to %s: %v", source, target, string(outputBytes)) + } + + return err +} + +// makeNsenterArgs makes a list of argument to nsenter in order to do the +// requested mount. +func (n *NsenterMounter) makeNsenterArgs(source, target, fstype string, options []string) []string { + nsenterArgs := []string{ + "--mount=/rootfs/proc/1/ns/mnt", + "--", + n.absHostPath("mount"), + } + + args := makeMountArgs(source, target, fstype, options) + + return append(nsenterArgs, args...) +} + +// Unmount runs umount(8) in the host's mount namespace. +func (n *NsenterMounter) Unmount(target string) error { + args := []string{ + "--mount=/rootfs/proc/1/ns/mnt", + "--", + n.absHostPath("umount"), + target, + } + + glog.V(5).Infof("Unmount command: %v %v", nsenterPath, args) + exec := exec.New() + outputBytes, err := exec.Command(nsenterPath, args...).CombinedOutput() + if len(outputBytes) != 0 { + glog.V(5).Infof("Output of unmounting %s: %v", target, string(outputBytes)) + } + + return err +} + +// List returns a list of all mounted filesystems in the host's mount namespace. +func (*NsenterMounter) List() ([]MountPoint, error) { + return listProcMounts(hostProcMountsPath) +} + +func (m *NsenterMounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(m, dir) +} + +func (*NsenterMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + deletedDir := fmt.Sprintf("%s\\040(deleted)", dir) + return ((mp.Path == dir) || (mp.Path == deletedDir)) +} + +// IsLikelyNotMountPoint determines whether a path is a mountpoint by calling findmnt +// in the host's root mount namespace. +func (n *NsenterMounter) IsLikelyNotMountPoint(file string) (bool, error) { + file, err := filepath.Abs(file) + if err != nil { + return true, err + } + + // Check the directory exists + if _, err = os.Stat(file); os.IsNotExist(err) { + glog.V(5).Infof("findmnt: directory %s does not exist", file) + return true, err + } + // Add --first-only option: since we are testing for the absence of a mountpoint, it is sufficient to get only + // the first of multiple possible mountpoints using --first-only. + // Also add fstype output to make sure that the output of target file will give the full path + // TODO: Need more refactoring for this function. Track the solution with issue #26996 + args := []string{"--mount=/rootfs/proc/1/ns/mnt", "--", n.absHostPath("findmnt"), "-o", "target,fstype", "--noheadings", "--first-only", "--target", file} + glog.V(5).Infof("findmnt command: %v %v", nsenterPath, args) + + exec := exec.New() + out, err := exec.Command(nsenterPath, args...).CombinedOutput() + if err != nil { + glog.V(2).Infof("Failed findmnt command for path %s: %v", file, err) + // Different operating systems behave differently for paths which are not mount points. + // On older versions (e.g. 2.20.1) we'd get error, on newer ones (e.g. 2.26.2) we'd get "/". + // It's safer to assume that it's not a mount point. + return true, nil + } + mountTarget := strings.Split(string(out), " ")[0] + mountTarget = strings.TrimSuffix(mountTarget, "\n") + glog.V(5).Infof("IsLikelyNotMountPoint findmnt output for path %s: %v:", file, mountTarget) + + if mountTarget == file { + glog.V(5).Infof("IsLikelyNotMountPoint: %s is a mount point", file) + return false, nil + } + glog.V(5).Infof("IsLikelyNotMountPoint: %s is not a mount point", file) + return true, nil +} + +// DeviceOpened checks if block device in use by calling Open with O_EXCL flag. +// Returns true if open returns errno EBUSY, and false if errno is nil. +// Returns an error if errno is any error other than EBUSY. +// Returns with error if pathname is not a device. +func (n *NsenterMounter) DeviceOpened(pathname string) (bool, error) { + return exclusiveOpenFailsOnDevice(pathname) +} + +// PathIsDevice uses FileInfo returned from os.Stat to check if path refers +// to a device. +func (n *NsenterMounter) PathIsDevice(pathname string) (bool, error) { + return pathIsDevice(pathname) +} + +//GetDeviceNameFromMount given a mount point, find the volume id from checking /proc/mounts +func (n *NsenterMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return getDeviceNameFromMount(n, mountPath, pluginDir) +} + +func (n *NsenterMounter) absHostPath(command string) string { + path, ok := n.paths[command] + if !ok { + return command + } + return path +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount_unsupported.go b/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount_unsupported.go new file mode 100644 index 000000000..e955e1b78 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount_unsupported.go @@ -0,0 +1,63 @@ +// +build !linux + +/* +Copyright 2014 The Kubernetes Authors. + +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 mount + +type NsenterMounter struct{} + +func NewNsenterMounter() *NsenterMounter { + return &NsenterMounter{} +} + +var _ = Interface(&NsenterMounter{}) + +func (*NsenterMounter) Mount(source string, target string, fstype string, options []string) error { + return nil +} + +func (*NsenterMounter) Unmount(target string) error { + return nil +} + +func (*NsenterMounter) List() ([]MountPoint, error) { + return []MountPoint{}, nil +} + +func (m *NsenterMounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(m, dir) +} + +func (*NsenterMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return (mp.Path == dir) +} + +func (*NsenterMounter) IsLikelyNotMountPoint(file string) (bool, error) { + return true, nil +} + +func (*NsenterMounter) DeviceOpened(pathname string) (bool, error) { + return false, nil +} + +func (*NsenterMounter) PathIsDevice(pathname string) (bool, error) { + return true, nil +} + +func (*NsenterMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return "", nil +} |