mirror of https://github.com/k3s-io/k3s
Copy mount files to pkg/volume/util/subpath
Files in pkg/util/mount that contain significant code implementation for subpaths are moved to a new package at pkg/volume/util/subpath. This move is done in order to preserve git history.pull/564/head
parent
d648ba856b
commit
f2438cacf5
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,470 @@
|
|||
// +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"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/utils/nsenter"
|
||||
utilpath "k8s.io/utils/path"
|
||||
)
|
||||
|
||||
const (
|
||||
// hostProcMountsPath is the default mount path for rootfs
|
||||
hostProcMountsPath = "/rootfs/proc/1/mounts"
|
||||
// hostProcMountinfoPath is the default mount info path for rootfs
|
||||
hostProcMountinfoPath = "/rootfs/proc/1/mountinfo"
|
||||
)
|
||||
|
||||
// Currently, all docker containers receive their own mount namespaces.
|
||||
// NsenterMounter works by executing nsenter to run commands in
|
||||
// the host's mount namespace.
|
||||
type NsenterMounter struct {
|
||||
ne *nsenter.Nsenter
|
||||
// rootDir is location of /var/lib/kubelet directory.
|
||||
rootDir string
|
||||
}
|
||||
|
||||
// NewNsenterMounter creates a new mounter for kubelet that runs as a container.
|
||||
func NewNsenterMounter(rootDir string, ne *nsenter.Nsenter) *NsenterMounter {
|
||||
return &NsenterMounter{
|
||||
rootDir: rootDir,
|
||||
ne: ne,
|
||||
}
|
||||
}
|
||||
|
||||
// NsenterMounter implements mount.Interface
|
||||
var _ = Interface(&NsenterMounter{})
|
||||
|
||||
// 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, bindOpts, bindRemountOpts := isBind(options)
|
||||
|
||||
if bind {
|
||||
err := n.doNsenterMount(source, target, fstype, bindOpts)
|
||||
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 {
|
||||
klog.V(5).Infof("nsenter mount %s %s %s %v", source, target, fstype, options)
|
||||
cmd, args := n.makeNsenterArgs(source, target, fstype, options)
|
||||
outputBytes, err := n.ne.Exec(cmd, args).CombinedOutput()
|
||||
if len(outputBytes) != 0 {
|
||||
klog.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, []string) {
|
||||
mountCmd := n.ne.AbsHostPath("mount")
|
||||
mountArgs := makeMountArgs(source, target, fstype, options)
|
||||
|
||||
if systemdRunPath, hasSystemd := n.ne.SupportsSystemd(); hasSystemd {
|
||||
// Complete command line:
|
||||
// nsenter --mount=/rootfs/proc/1/ns/mnt -- /bin/systemd-run --description=... --scope -- /bin/mount -t <type> <what> <where>
|
||||
// Expected flow is:
|
||||
// * nsenter breaks out of container's mount namespace and executes
|
||||
// host's systemd-run.
|
||||
// * systemd-run creates a transient scope (=~ cgroup) and executes its
|
||||
// argument (/bin/mount) there.
|
||||
// * mount does its job, forks a fuse daemon if necessary and finishes.
|
||||
// (systemd-run --scope finishes at this point, returning mount's exit
|
||||
// code and stdout/stderr - thats one of --scope benefits).
|
||||
// * systemd keeps the fuse daemon running in the scope (i.e. in its own
|
||||
// cgroup) until the fuse daemon dies (another --scope benefit).
|
||||
// Kubelet container can be restarted and the fuse daemon survives.
|
||||
// * When the daemon dies (e.g. during unmount) systemd removes the
|
||||
// scope automatically.
|
||||
mountCmd, mountArgs = addSystemdScope(systemdRunPath, target, mountCmd, mountArgs)
|
||||
} else {
|
||||
// Fall back to simple mount when the host has no systemd.
|
||||
// Complete command line:
|
||||
// nsenter --mount=/rootfs/proc/1/ns/mnt -- /bin/mount -t <type> <what> <where>
|
||||
// Expected flow is:
|
||||
// * nsenter breaks out of container's mount namespace and executes host's /bin/mount.
|
||||
// * mount does its job, forks a fuse daemon if necessary and finishes.
|
||||
// * Any fuse daemon runs in cgroup of kubelet docker container,
|
||||
// restart of kubelet container will kill it!
|
||||
|
||||
// No code here, mountCmd and mountArgs use /bin/mount
|
||||
}
|
||||
|
||||
return mountCmd, mountArgs
|
||||
}
|
||||
|
||||
// Unmount runs umount(8) in the host's mount namespace.
|
||||
func (n *NsenterMounter) Unmount(target string) error {
|
||||
args := []string{target}
|
||||
// No need to execute systemd-run here, it's enough that unmount is executed
|
||||
// in the host's mount namespace. It will finish appropriate fuse daemon(s)
|
||||
// running in any scope.
|
||||
klog.V(5).Infof("nsenter unmount args: %v", args)
|
||||
outputBytes, err := n.ne.Exec("umount", args).CombinedOutput()
|
||||
if len(outputBytes) != 0 {
|
||||
klog.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) {
|
||||
klog.V(5).Infof("findmnt: directory %s does not exist", file)
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Resolve any symlinks in file, kernel would do the same and use the resolved path in /proc/mounts
|
||||
resolvedFile, err := n.EvalHostSymlinks(file)
|
||||
if err != nil {
|
||||
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{"-o", "target,fstype", "--noheadings", "--first-only", "--target", resolvedFile}
|
||||
klog.V(5).Infof("nsenter findmnt args: %v", args)
|
||||
out, err := n.ne.Exec("findmnt", args).CombinedOutput()
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Failed findmnt command for path %s: %s %v", resolvedFile, out, 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, err := parseFindMnt(string(out))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
klog.V(5).Infof("IsLikelyNotMountPoint findmnt output for path %s: %v:", resolvedFile, mountTarget)
|
||||
|
||||
if mountTarget == resolvedFile {
|
||||
klog.V(5).Infof("IsLikelyNotMountPoint: %s is a mount point", resolvedFile)
|
||||
return false, nil
|
||||
}
|
||||
klog.V(5).Infof("IsLikelyNotMountPoint: %s is not a mount point", resolvedFile)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// parse output of "findmnt -o target,fstype" and return just the target
|
||||
func parseFindMnt(out string) (string, error) {
|
||||
// cut trailing newline
|
||||
out = strings.TrimSuffix(out, "\n")
|
||||
// cut everything after the last space - it's the filesystem type
|
||||
i := strings.LastIndex(out, " ")
|
||||
if i == -1 {
|
||||
return "", fmt.Errorf("error parsing findmnt output, expected at least one space: %q", out)
|
||||
}
|
||||
return out[:i], 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) {
|
||||
pathType, err := n.GetFileType(pathname)
|
||||
isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev
|
||||
return isDevice, err
|
||||
}
|
||||
|
||||
//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) MakeRShared(path string) error {
|
||||
return doMakeRShared(path, hostProcMountinfoPath)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) GetFileType(pathname string) (FileType, error) {
|
||||
var pathType FileType
|
||||
outputBytes, err := mounter.ne.Exec("stat", []string{"-L", "--printf=%F", pathname}).CombinedOutput()
|
||||
if err != nil {
|
||||
if strings.Contains(string(outputBytes), "No such file") {
|
||||
err = fmt.Errorf("%s does not exist", pathname)
|
||||
} else {
|
||||
err = fmt.Errorf("stat %s error: %v", pathname, string(outputBytes))
|
||||
}
|
||||
return pathType, err
|
||||
}
|
||||
|
||||
switch string(outputBytes) {
|
||||
case "socket":
|
||||
return FileTypeSocket, nil
|
||||
case "character special file":
|
||||
return FileTypeCharDev, nil
|
||||
case "block special file":
|
||||
return FileTypeBlockDev, nil
|
||||
case "directory":
|
||||
return FileTypeDirectory, nil
|
||||
case "regular file":
|
||||
return FileTypeFile, nil
|
||||
}
|
||||
|
||||
return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device")
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) MakeDir(pathname string) error {
|
||||
args := []string{"-p", pathname}
|
||||
if _, err := mounter.ne.Exec("mkdir", args).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) MakeFile(pathname string) error {
|
||||
args := []string{pathname}
|
||||
if _, err := mounter.ne.Exec("touch", args).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) ExistsPath(pathname string) (bool, error) {
|
||||
// Resolve the symlinks but allow the target not to exist. EvalSymlinks
|
||||
// would return an generic error when the target does not exist.
|
||||
hostPath, err := mounter.ne.EvalSymlinks(pathname, false /* mustExist */)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
kubeletpath := mounter.ne.KubeletPath(hostPath)
|
||||
return utilpath.Exists(utilpath.CheckFollowSymlink, kubeletpath)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) EvalHostSymlinks(pathname string) (string, error) {
|
||||
return mounter.ne.EvalSymlinks(pathname, true)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) CleanSubPaths(podDir string, volumeName string) error {
|
||||
return doCleanSubPaths(mounter, podDir, volumeName)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
|
||||
// Bind-mount the subpath to avoid using symlinks in subpaths.
|
||||
newHostPath, err = doNsEnterBindSubPath(mounter, subPath)
|
||||
|
||||
// There is no action when the container starts. Bind-mount will be cleaned
|
||||
// when container stops by CleanSubPaths.
|
||||
cleanupAction = nil
|
||||
return newHostPath, cleanupAction, err
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
|
||||
fullSubdirPath := filepath.Join(base, subdir)
|
||||
evaluatedSubdirPath, err := mounter.ne.EvalSymlinks(fullSubdirPath, false /* mustExist */)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error resolving symlinks in %s: %s", fullSubdirPath, err)
|
||||
}
|
||||
evaluatedSubdirPath = filepath.Clean(evaluatedSubdirPath)
|
||||
|
||||
evaluatedBase, err := mounter.ne.EvalSymlinks(base, true /* mustExist */)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
|
||||
}
|
||||
evaluatedBase = filepath.Clean(evaluatedBase)
|
||||
|
||||
rootDir := filepath.Clean(mounter.rootDir)
|
||||
if PathWithinBase(evaluatedBase, rootDir) {
|
||||
// Base is in /var/lib/kubelet. This directory is shared between the
|
||||
// container with kubelet and the host. We don't need to add '/rootfs'.
|
||||
// This is useful when /rootfs is mounted as read-only - we can still
|
||||
// create subpaths for paths in /var/lib/kubelet.
|
||||
return doSafeMakeDir(evaluatedSubdirPath, evaluatedBase, perm)
|
||||
}
|
||||
|
||||
// Base is somewhere on the host's filesystem. Add /rootfs and try to make
|
||||
// the directory there.
|
||||
// This requires /rootfs to be writable.
|
||||
kubeletSubdirPath := mounter.ne.KubeletPath(evaluatedSubdirPath)
|
||||
kubeletBase := mounter.ne.KubeletPath(evaluatedBase)
|
||||
return doSafeMakeDir(kubeletSubdirPath, kubeletBase, perm)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) GetMountRefs(pathname string) ([]string, error) {
|
||||
exists, err := mounter.ExistsPath(pathname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return []string{}, nil
|
||||
}
|
||||
hostpath, err := mounter.ne.EvalSymlinks(pathname, true /* mustExist */)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return searchMountPoints(hostpath, hostProcMountinfoPath)
|
||||
}
|
||||
|
||||
func doNsEnterBindSubPath(mounter *NsenterMounter, subpath Subpath) (hostPath string, err error) {
|
||||
// Linux, kubelet runs in a container:
|
||||
// - safely open the subpath
|
||||
// - bind-mount the subpath to target (this can be unsafe)
|
||||
// - check that we mounted the right thing by comparing device ID and inode
|
||||
// of the subpath (via safely opened fd) and the target (that's under our
|
||||
// control)
|
||||
|
||||
// Evaluate all symlinks here once for all subsequent functions.
|
||||
evaluatedHostVolumePath, err := mounter.ne.EvalSymlinks(subpath.VolumePath, true /*mustExist*/)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.VolumePath, err)
|
||||
}
|
||||
evaluatedHostSubpath, err := mounter.ne.EvalSymlinks(subpath.Path, true /*mustExist*/)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.Path, err)
|
||||
}
|
||||
klog.V(5).Infof("doBindSubPath %q (%q) for volumepath %q", subpath.Path, evaluatedHostSubpath, subpath.VolumePath)
|
||||
subpath.VolumePath = mounter.ne.KubeletPath(evaluatedHostVolumePath)
|
||||
subpath.Path = mounter.ne.KubeletPath(evaluatedHostSubpath)
|
||||
|
||||
// Check the subpath is correct and open it
|
||||
fd, err := safeOpenSubPath(mounter, subpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
alreadyMounted, bindPathTarget, err := prepareSubpathTarget(mounter, subpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if alreadyMounted {
|
||||
return bindPathTarget, nil
|
||||
}
|
||||
|
||||
success := false
|
||||
defer func() {
|
||||
// Cleanup subpath on error
|
||||
if !success {
|
||||
klog.V(4).Infof("doNsEnterBindSubPath() failed for %q, cleaning up subpath", bindPathTarget)
|
||||
if cleanErr := cleanSubPath(mounter, subpath); cleanErr != nil {
|
||||
klog.Errorf("Failed to clean subpath %q: %v", bindPathTarget, cleanErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Leap of faith: optimistically expect that nobody has modified previously
|
||||
// expanded evalSubPath with evil symlinks and bind-mount it.
|
||||
// Mount is done on the host! don't use kubelet path!
|
||||
klog.V(5).Infof("bind mounting %q at %q", evaluatedHostSubpath, bindPathTarget)
|
||||
if err = mounter.Mount(evaluatedHostSubpath, bindPathTarget, "" /*fstype*/, []string{"bind"}); err != nil {
|
||||
return "", fmt.Errorf("error mounting %s: %s", evaluatedHostSubpath, err)
|
||||
}
|
||||
|
||||
// Check that the bind-mount target is the same inode and device as the
|
||||
// source that we keept open, i.e. we mounted the right thing.
|
||||
err = checkDeviceInode(fd, bindPathTarget)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error checking bind mount for subpath %s: %s", subpath.VolumePath, err)
|
||||
}
|
||||
|
||||
success = true
|
||||
klog.V(3).Infof("Bound SubPath %s into %s", subpath.Path, bindPathTarget)
|
||||
return bindPathTarget, nil
|
||||
}
|
||||
|
||||
// checkDeviceInode checks that opened file and path represent the same file.
|
||||
func checkDeviceInode(fd int, path string) error {
|
||||
var srcStat, dstStat unix.Stat_t
|
||||
err := unix.Fstat(fd, &srcStat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running fstat on subpath FD: %v", err)
|
||||
}
|
||||
|
||||
err = unix.Stat(path, &dstStat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running fstat on %s: %v", path, err)
|
||||
}
|
||||
|
||||
if srcStat.Dev != dstStat.Dev {
|
||||
return fmt.Errorf("different device number")
|
||||
}
|
||||
if srcStat.Ino != dstStat.Ino {
|
||||
return fmt.Errorf("different inode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) GetFSGroup(pathname string) (int64, error) {
|
||||
hostPath, err := mounter.ne.EvalSymlinks(pathname, true /* mustExist */)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
kubeletpath := mounter.ne.KubeletPath(hostPath)
|
||||
return getFSGroup(kubeletpath)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) GetSELinuxSupport(pathname string) (bool, error) {
|
||||
return getSELinuxSupport(pathname, hostProcMountsPath)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) GetMode(pathname string) (os.FileMode, error) {
|
||||
hostPath, err := mounter.ne.EvalSymlinks(pathname, true /* mustExist */)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
kubeletpath := mounter.ne.KubeletPath(hostPath)
|
||||
return getMode(kubeletpath)
|
||||
}
|
|
@ -0,0 +1,724 @@
|
|||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2017 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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"k8s.io/utils/nsenter"
|
||||
)
|
||||
|
||||
func TestParseFindMnt(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
target string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
// standard mount name, e.g. for AWS
|
||||
"/var/lib/kubelet/plugins/kubernetes.io/aws-ebs/mounts/aws/us-east-1d/vol-020f82b0759f72389 ext4\n",
|
||||
"/var/lib/kubelet/plugins/kubernetes.io/aws-ebs/mounts/aws/us-east-1d/vol-020f82b0759f72389",
|
||||
false,
|
||||
},
|
||||
{
|
||||
// mount name with space, e.g. vSphere
|
||||
"/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[datastore1] kubevols/kubernetes-dynamic-pvc-4aacaa9b-6ba5-11e7-8f64-0050569f1b82.vmdk ext2\n",
|
||||
"/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[datastore1] kubevols/kubernetes-dynamic-pvc-4aacaa9b-6ba5-11e7-8f64-0050569f1b82.vmdk",
|
||||
false,
|
||||
},
|
||||
{
|
||||
// hypotetic mount with several spaces
|
||||
"/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[ d a t a s t o r e 1 ] kubevols/kubernetes-dynamic-pvc-4aacaa9b-6ba5-11e7-8f64-0050569f1b82.vmdk ext2\n",
|
||||
"/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[ d a t a s t o r e 1 ] kubevols/kubernetes-dynamic-pvc-4aacaa9b-6ba5-11e7-8f64-0050569f1b82.vmdk",
|
||||
false,
|
||||
},
|
||||
{
|
||||
// invalid output - no filesystem type
|
||||
"/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/blabla",
|
||||
"",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
target, err := parseFindMnt(test.input)
|
||||
if test.expectError && err == nil {
|
||||
t.Errorf("test %d expected error, got nil", i)
|
||||
}
|
||||
if !test.expectError && err != nil {
|
||||
t.Errorf("test %d returned error: %s", i, err)
|
||||
}
|
||||
if target != test.target {
|
||||
t.Errorf("test %d expected %q, got %q", i, test.target, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDeviceInode(t *testing.T) {
|
||||
testDir, err := ioutil.TempDir("", "nsenter-mounter-device-")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create temporary directory: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
srcPath string
|
||||
dstPath string
|
||||
expectError string
|
||||
}{
|
||||
{
|
||||
name: "the same file",
|
||||
srcPath: filepath.Join(testDir, "1"),
|
||||
dstPath: filepath.Join(testDir, "1"),
|
||||
expectError: "",
|
||||
},
|
||||
{
|
||||
name: "different file on the same FS",
|
||||
srcPath: filepath.Join(testDir, "2.1"),
|
||||
dstPath: filepath.Join(testDir, "2.2"),
|
||||
expectError: "different inode",
|
||||
},
|
||||
{
|
||||
name: "different file on different device",
|
||||
srcPath: filepath.Join(testDir, "3"),
|
||||
// /proc is always on a different "device" than /tmp (or $TEMP)
|
||||
dstPath: "/proc/self/status",
|
||||
expectError: "different device",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if err := ioutil.WriteFile(test.srcPath, []byte{}, 0644); err != nil {
|
||||
t.Errorf("Test %q: cannot create srcPath %s: %s", test.name, test.srcPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Don't create dst if it exists
|
||||
if _, err := os.Stat(test.dstPath); os.IsNotExist(err) {
|
||||
if err := ioutil.WriteFile(test.dstPath, []byte{}, 0644); err != nil {
|
||||
t.Errorf("Test %q: cannot create dstPath %s: %s", test.name, test.dstPath, err)
|
||||
continue
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("Test %q: cannot check existence of dstPath %s: %s", test.name, test.dstPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
fd, err := unix.Open(test.srcPath, unix.O_CREAT, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test %q: cannot open srcPath %s: %s", test.name, test.srcPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = checkDeviceInode(fd, test.dstPath)
|
||||
|
||||
if test.expectError == "" && err != nil {
|
||||
t.Errorf("Test %q: expected no error, got %s", test.name, err)
|
||||
}
|
||||
if test.expectError != "" {
|
||||
if err == nil {
|
||||
t.Errorf("Test %q: expected error, got none", test.name)
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), test.expectError) {
|
||||
t.Errorf("Test %q: expected error %q, got %q", test.name, test.expectError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newFakeNsenterMounter(tmpdir string, t *testing.T) (mounter *NsenterMounter, rootfsPath string, varlibPath string, err error) {
|
||||
rootfsPath = filepath.Join(tmpdir, "rootfs")
|
||||
if err := os.Mkdir(rootfsPath, 0755); err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
ne, err := nsenter.NewFakeNsenter(rootfsPath)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
varlibPath = filepath.Join(tmpdir, "/var/lib/kubelet")
|
||||
if err := os.MkdirAll(varlibPath, 0755); err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
return NewNsenterMounter(varlibPath, ne), rootfsPath, varlibPath, nil
|
||||
}
|
||||
|
||||
func TestNsenterExistsFile(t *testing.T) {
|
||||
var isRoot bool
|
||||
usr, err := user.Current()
|
||||
if err == nil {
|
||||
isRoot = usr.Username == "root"
|
||||
} else {
|
||||
switch err.(type) {
|
||||
case user.UnknownUserIdError:
|
||||
// Root should be always known, this is some random UID
|
||||
isRoot = false
|
||||
default:
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(base, rootfs string) (string, error)
|
||||
expectedOutput bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple existing file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
// On the host: /base/file
|
||||
path := filepath.Join(base, "file")
|
||||
if err := ioutil.WriteFile(path, []byte{}, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// In kubelet: /rootfs/base/file
|
||||
if _, err := writeRootfsFile(rootfs, path, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
},
|
||||
expectedOutput: true,
|
||||
},
|
||||
{
|
||||
name: "simple non-existing file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
path := filepath.Join(base, "file")
|
||||
return path, nil
|
||||
},
|
||||
expectedOutput: false,
|
||||
},
|
||||
{
|
||||
name: "simple non-accessible file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
// On the host:
|
||||
// create /base/dir/file, then make the dir inaccessible
|
||||
dir := filepath.Join(base, "dir")
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := filepath.Join(dir, "file")
|
||||
if err := ioutil.WriteFile(path, []byte{}, 0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.Chmod(dir, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// In kubelet: do the same with /rootfs/base/dir/file
|
||||
rootfsPath, err := writeRootfsFile(rootfs, path, 0777)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rootfsDir := filepath.Dir(rootfsPath)
|
||||
if err := os.Chmod(rootfsDir, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
},
|
||||
expectedOutput: isRoot, // ExistsPath success when running as root
|
||||
expectError: !isRoot, // ExistsPath must fail when running as not-root
|
||||
},
|
||||
{
|
||||
name: "relative symlink to existing file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
// On the host: /base/link -> file
|
||||
file := filepath.Join(base, "file")
|
||||
if err := ioutil.WriteFile(file, []byte{}, 0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := filepath.Join(base, "link")
|
||||
if err := os.Symlink("file", path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// In kubelet: /rootfs/base/file
|
||||
if _, err := writeRootfsFile(rootfs, file, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
},
|
||||
expectedOutput: true,
|
||||
},
|
||||
{
|
||||
name: "absolute symlink to existing file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
// On the host: /base/link -> /base/file
|
||||
file := filepath.Join(base, "file")
|
||||
if err := ioutil.WriteFile(file, []byte{}, 0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := filepath.Join(base, "link")
|
||||
if err := os.Symlink(file, path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// In kubelet: /rootfs/base/file
|
||||
if _, err := writeRootfsFile(rootfs, file, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
},
|
||||
expectedOutput: true,
|
||||
},
|
||||
{
|
||||
name: "relative symlink to non-existing file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
path := filepath.Join(base, "link")
|
||||
if err := os.Symlink("file", path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
},
|
||||
expectedOutput: false,
|
||||
},
|
||||
{
|
||||
name: "absolute symlink to non-existing file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
file := filepath.Join(base, "file")
|
||||
path := filepath.Join(base, "link")
|
||||
if err := os.Symlink(file, path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
},
|
||||
expectedOutput: false,
|
||||
},
|
||||
{
|
||||
name: "symlink loop",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
path := filepath.Join(base, "link")
|
||||
if err := os.Symlink(path, path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
},
|
||||
expectedOutput: false,
|
||||
// TODO: realpath -m is not able to detect symlink loop. Should we care?
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
tmpdir, err := ioutil.TempDir("", "nsenter-exists-file")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
testBase := filepath.Join(tmpdir, "base")
|
||||
if err := os.Mkdir(testBase, 0755); err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
mounter, rootfs, _, err := newFakeNsenterMounter(tmpdir, t)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
path, err := test.prepare(testBase, rootfs)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
out, err := mounter.ExistsPath(path)
|
||||
if err != nil && !test.expectError {
|
||||
t.Errorf("Test %q: unexpected error: %s", test.name, err)
|
||||
}
|
||||
if err == nil && test.expectError {
|
||||
t.Errorf("Test %q: expected error, got none", test.name)
|
||||
}
|
||||
|
||||
if out != test.expectedOutput {
|
||||
t.Errorf("Test %q: expected return value %v, got %v", test.name, test.expectedOutput, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNsenterGetMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(base, rootfs string) (string, error)
|
||||
expectedMode os.FileMode
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
// On the host: /base/file
|
||||
path := filepath.Join(base, "file")
|
||||
if err := ioutil.WriteFile(path, []byte{}, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Prepare a different file as /rootfs/base/file (="the host
|
||||
// visible from container") to check that NsEnterMounter calls
|
||||
// stat on this file and not on /base/file.
|
||||
// Visible from kubelet: /rootfs/base/file
|
||||
if _, err := writeRootfsFile(rootfs, path, 0777); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
},
|
||||
expectedMode: 0777,
|
||||
},
|
||||
{
|
||||
name: "non-existing file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
path := filepath.Join(base, "file")
|
||||
return path, nil
|
||||
},
|
||||
expectedMode: 0,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "absolute symlink to existing file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
// On the host: /base/link -> /base/file
|
||||
file := filepath.Join(base, "file")
|
||||
if err := ioutil.WriteFile(file, []byte{}, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := filepath.Join(base, "link")
|
||||
if err := os.Symlink(file, path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Visible from kubelet:
|
||||
// /rootfs/base/file
|
||||
if _, err := writeRootfsFile(rootfs, file, 0747); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
},
|
||||
expectedMode: 0747,
|
||||
},
|
||||
{
|
||||
name: "relative symlink to existing file",
|
||||
prepare: func(base, rootfs string) (string, error) {
|
||||
// On the host: /base/link -> file
|
||||
file := filepath.Join(base, "file")
|
||||
if err := ioutil.WriteFile(file, []byte{}, 0741); err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := filepath.Join(base, "link")
|
||||
if err := os.Symlink("file", path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Visible from kubelet:
|
||||
// /rootfs/base/file
|
||||
if _, err := writeRootfsFile(rootfs, file, 0647); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
},
|
||||
expectedMode: 0647,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
tmpdir, err := ioutil.TempDir("", "nsenter-get-mode-")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
testBase := filepath.Join(tmpdir, "base")
|
||||
if err := os.Mkdir(testBase, 0755); err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
mounter, rootfs, _, err := newFakeNsenterMounter(tmpdir, t)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
path, err := test.prepare(testBase, rootfs)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
mode, err := mounter.GetMode(path)
|
||||
if err != nil && !test.expectError {
|
||||
t.Errorf("Test %q: unexpected error: %s", test.name, err)
|
||||
}
|
||||
if err == nil && test.expectError {
|
||||
t.Errorf("Test %q: expected error, got none", test.name)
|
||||
}
|
||||
|
||||
if mode != test.expectedMode {
|
||||
t.Errorf("Test %q: expected return value %v, got %v", test.name, test.expectedMode, mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeRootfsFile(rootfs, path string, mode os.FileMode) (string, error) {
|
||||
fullPath := filepath.Join(rootfs, path)
|
||||
dir := filepath.Dir(fullPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := ioutil.WriteFile(fullPath, []byte{}, mode); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Use chmod, io.WriteFile is affected by umask
|
||||
if err := os.Chmod(fullPath, mode); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fullPath, nil
|
||||
}
|
||||
|
||||
func TestNsenterSafeMakeDir(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(base, rootfs, varlib string) (expectedDir string, err error)
|
||||
subdir string
|
||||
expectError bool
|
||||
// If true, "base" directory for SafeMakeDir will be /var/lib/kubelet
|
||||
baseIsVarLib bool
|
||||
}{
|
||||
{
|
||||
name: "simple directory",
|
||||
// evaluated in base
|
||||
subdir: "some/subdirectory/structure",
|
||||
prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
|
||||
// expected to be created in /roots/
|
||||
expectedDir = filepath.Join(rootfs, base, "some/subdirectory/structure")
|
||||
return expectedDir, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple existing directory",
|
||||
// evaluated in base
|
||||
subdir: "some/subdirectory/structure",
|
||||
prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
|
||||
// On the host: directory exists
|
||||
hostPath := filepath.Join(base, "some/subdirectory/structure")
|
||||
if err := os.MkdirAll(hostPath, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// In rootfs: directory exists
|
||||
kubeletPath := filepath.Join(rootfs, hostPath)
|
||||
if err := os.MkdirAll(kubeletPath, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// expected to be created in /roots/
|
||||
expectedDir = kubeletPath
|
||||
return expectedDir, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "absolute symlink into safe place",
|
||||
// evaluated in base
|
||||
subdir: "some/subdirectory/structure",
|
||||
prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
|
||||
// On the host: /base/other/subdirectory exists, /base/some is link to /base/other
|
||||
hostPath := filepath.Join(base, "other/subdirectory")
|
||||
if err := os.MkdirAll(hostPath, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
somePath := filepath.Join(base, "some")
|
||||
otherPath := filepath.Join(base, "other")
|
||||
if err := os.Symlink(otherPath, somePath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// In rootfs: /base/other/subdirectory exists
|
||||
kubeletPath := filepath.Join(rootfs, hostPath)
|
||||
if err := os.MkdirAll(kubeletPath, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// expected 'structure' to be created
|
||||
expectedDir = filepath.Join(rootfs, hostPath, "structure")
|
||||
return expectedDir, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative symlink into safe place",
|
||||
// evaluated in base
|
||||
subdir: "some/subdirectory/structure",
|
||||
prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
|
||||
// On the host: /base/other/subdirectory exists, /base/some is link to other
|
||||
hostPath := filepath.Join(base, "other/subdirectory")
|
||||
if err := os.MkdirAll(hostPath, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
somePath := filepath.Join(base, "some")
|
||||
if err := os.Symlink("other", somePath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// In rootfs: /base/other/subdirectory exists
|
||||
kubeletPath := filepath.Join(rootfs, hostPath)
|
||||
if err := os.MkdirAll(kubeletPath, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// expected 'structure' to be created
|
||||
expectedDir = filepath.Join(rootfs, hostPath, "structure")
|
||||
return expectedDir, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink into unsafe place",
|
||||
// evaluated in base
|
||||
subdir: "some/subdirectory/structure",
|
||||
prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
|
||||
// On the host: /base/some is link to /bin/other
|
||||
somePath := filepath.Join(base, "some")
|
||||
if err := os.Symlink("/bin", somePath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "simple directory in /var/lib/kubelet",
|
||||
// evaluated in varlib
|
||||
subdir: "some/subdirectory/structure",
|
||||
baseIsVarLib: true,
|
||||
prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
|
||||
// expected to be created in /base/var/lib/kubelet, not in /rootfs!
|
||||
expectedDir = filepath.Join(varlib, "some/subdirectory/structure")
|
||||
return expectedDir, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "safe symlink in /var/lib/kubelet",
|
||||
// evaluated in varlib
|
||||
subdir: "some/subdirectory/structure",
|
||||
baseIsVarLib: true,
|
||||
prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
|
||||
// On the host: /varlib/kubelet/other/subdirectory exists, /varlib/some is link to other
|
||||
hostPath := filepath.Join(varlib, "other/subdirectory")
|
||||
if err := os.MkdirAll(hostPath, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
somePath := filepath.Join(varlib, "some")
|
||||
if err := os.Symlink("other", somePath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// expected to be created in /base/var/lib/kubelet, not in /rootfs!
|
||||
expectedDir = filepath.Join(varlib, "other/subdirectory/structure")
|
||||
return expectedDir, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unsafe symlink in /var/lib/kubelet",
|
||||
// evaluated in varlib
|
||||
subdir: "some/subdirectory/structure",
|
||||
baseIsVarLib: true,
|
||||
prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
|
||||
// On the host: /varlib/some is link to /bin
|
||||
somePath := filepath.Join(varlib, "some")
|
||||
if err := os.Symlink("/bin", somePath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", nil
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
tmpdir, err := ioutil.TempDir("", "nsenter-get-mode-")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
mounter, rootfs, varlib, err := newFakeNsenterMounter(tmpdir, t)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
// Prepare base directory for the test
|
||||
testBase := filepath.Join(tmpdir, "base")
|
||||
if err := os.Mkdir(testBase, 0755); err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
// Prepare base directory also in /rootfs
|
||||
rootfsBase := filepath.Join(rootfs, testBase)
|
||||
if err := os.MkdirAll(rootfsBase, 0755); err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
expectedDir := ""
|
||||
if test.prepare != nil {
|
||||
expectedDir, err = test.prepare(testBase, rootfs, varlib)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if test.baseIsVarLib {
|
||||
// use /var/lib/kubelet as the test base so we can test creating
|
||||
// subdirs there directly in /var/lib/kubenet and not in
|
||||
// /rootfs/var/lib/kubelet
|
||||
testBase = varlib
|
||||
}
|
||||
|
||||
err = mounter.SafeMakeDir(test.subdir, testBase, 0755)
|
||||
if err != nil && !test.expectError {
|
||||
t.Errorf("Test %q: unexpected error: %s", test.name, err)
|
||||
}
|
||||
if test.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("Test %q: expected error, got none", test.name)
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "is outside of allowed base") {
|
||||
t.Errorf("Test %q: expected error to contain \"is outside of allowed base\", got this one instead: %s", test.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if expectedDir != "" {
|
||||
_, err := os.Stat(expectedDir)
|
||||
if err != nil {
|
||||
t.Errorf("Test %q: expected %q to exist, got error: %s", test.name, expectedDir, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
// +build !linux,!windows
|
||||
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Mounter struct {
|
||||
mounterPath string
|
||||
}
|
||||
|
||||
var unsupportedErr = errors.New("util/mount on this platform is not supported")
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
|
||||
return unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) Unmount(target string) error {
|
||||
return unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) List() ([]MountPoint, error) {
|
||||
return []MountPoint{}, unsupportedErr
|
||||
}
|
||||
|
||||
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, unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
|
||||
return "", unsupportedErr
|
||||
}
|
||||
|
||||
func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) {
|
||||
return "", unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
|
||||
return false, unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) {
|
||||
return true, unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) MakeRShared(path string) error {
|
||||
return unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
|
||||
return mounter.Interface.Mount(source, target, fstype, options)
|
||||
}
|
||||
|
||||
func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, error) {
|
||||
return true, unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) GetFileType(pathname string) (FileType, error) {
|
||||
return FileType("fake"), unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) MakeDir(pathname string) error {
|
||||
return unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) MakeFile(pathname string) error {
|
||||
return unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) ExistsPath(pathname string) (bool, error) {
|
||||
return true, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (mounter *Mounter) EvalHostSymlinks(pathname string) (string, error) {
|
||||
return "", unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
|
||||
return subPath.Path, nil, unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) CleanSubPaths(podDir string, volumeName string) error {
|
||||
return unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) SafeMakeDir(pathname string, base string, perm os.FileMode) error {
|
||||
return unsupportedErr
|
||||
}
|
||||
|
||||
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (mounter *Mounter) GetFSGroup(pathname string) (int64, error) {
|
||||
return -1, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (mounter *Mounter) GetSELinuxSupport(pathname string) (bool, error) {
|
||||
return false, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (mounter *Mounter) GetMode(pathname string) (os.FileMode, error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
|
@ -0,0 +1,647 @@
|
|||
// +build windows
|
||||
|
||||
/*
|
||||
Copyright 2017 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"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
utilpath "k8s.io/utils/path"
|
||||
)
|
||||
|
||||
// Mounter provides the default implementation of mount.Interface
|
||||
// for the windows platform. This implementation assumes that the
|
||||
// kubelet is running in the host's root mount namespace.
|
||||
type Mounter struct {
|
||||
mounterPath string
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// Mount : mounts source to target with given options.
|
||||
// currently only supports cifs(smb), bind mount(for disk)
|
||||
func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
|
||||
target = normalizeWindowsPath(target)
|
||||
|
||||
if source == "tmpfs" {
|
||||
klog.V(3).Infof("mounting source (%q), target (%q), with options (%q)", source, target, options)
|
||||
return os.MkdirAll(target, 0755)
|
||||
}
|
||||
|
||||
parentDir := filepath.Dir(target)
|
||||
if err := os.MkdirAll(parentDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
klog.V(4).Infof("mount options(%q) source:%q, target:%q, fstype:%q, begin to mount",
|
||||
options, source, target, fstype)
|
||||
bindSource := source
|
||||
|
||||
// tell it's going to mount azure disk or azure file according to options
|
||||
if bind, _, _ := isBind(options); bind {
|
||||
// mount azure disk
|
||||
bindSource = normalizeWindowsPath(source)
|
||||
} else {
|
||||
if len(options) < 2 {
|
||||
klog.Warningf("mount options(%q) command number(%d) less than 2, source:%q, target:%q, skip mounting",
|
||||
options, len(options), source, target)
|
||||
return nil
|
||||
}
|
||||
|
||||
// currently only cifs mount is supported
|
||||
if strings.ToLower(fstype) != "cifs" {
|
||||
return fmt.Errorf("only cifs mount is supported now, fstype: %q, mounting source (%q), target (%q), with options (%q)", fstype, source, target, options)
|
||||
}
|
||||
|
||||
if output, err := newSMBMapping(options[0], options[1], source); err != nil {
|
||||
if isSMBMappingExist(source) {
|
||||
klog.V(2).Infof("SMB Mapping(%s) already exists, now begin to remove and remount", source)
|
||||
if output, err := removeSMBMapping(source); err != nil {
|
||||
return fmt.Errorf("Remove-SmbGlobalMapping failed: %v, output: %q", err, output)
|
||||
}
|
||||
if output, err := newSMBMapping(options[0], options[1], source); err != nil {
|
||||
return fmt.Errorf("New-SmbGlobalMapping remount failed: %v, output: %q", err, output)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("New-SmbGlobalMapping failed: %v, output: %q", err, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if output, err := exec.Command("cmd", "/c", "mklink", "/D", target, bindSource).CombinedOutput(); err != nil {
|
||||
klog.Errorf("mklink failed: %v, source(%q) target(%q) output: %q", err, bindSource, target, string(output))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// do the SMB mount with username, password, remotepath
|
||||
// return (output, error)
|
||||
func newSMBMapping(username, password, remotepath string) (string, error) {
|
||||
if username == "" || password == "" || remotepath == "" {
|
||||
return "", fmt.Errorf("invalid parameter(username: %s, password: %s, remoteapth: %s)", username, password, remotepath)
|
||||
}
|
||||
|
||||
// use PowerShell Environment Variables to store user input string to prevent command line injection
|
||||
// https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-5.1
|
||||
cmdLine := `$PWord = ConvertTo-SecureString -String $Env:smbpassword -AsPlainText -Force` +
|
||||
`;$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Env:smbuser, $PWord` +
|
||||
`;New-SmbGlobalMapping -RemotePath $Env:smbremotepath -Credential $Credential`
|
||||
cmd := exec.Command("powershell", "/c", cmdLine)
|
||||
cmd.Env = append(os.Environ(),
|
||||
fmt.Sprintf("smbuser=%s", username),
|
||||
fmt.Sprintf("smbpassword=%s", password),
|
||||
fmt.Sprintf("smbremotepath=%s", remotepath))
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
// check whether remotepath is already mounted
|
||||
func isSMBMappingExist(remotepath string) bool {
|
||||
cmd := exec.Command("powershell", "/c", `Get-SmbGlobalMapping -RemotePath $Env:smbremotepath`)
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("smbremotepath=%s", remotepath))
|
||||
_, err := cmd.CombinedOutput()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// remove SMB mapping
|
||||
func removeSMBMapping(remotepath string) (string, error) {
|
||||
cmd := exec.Command("powershell", "/c", `Remove-SmbGlobalMapping -RemotePath $Env:smbremotepath -Force`)
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("smbremotepath=%s", remotepath))
|
||||
output, err := cmd.CombinedOutput()
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
// Unmount unmounts the target.
|
||||
func (mounter *Mounter) Unmount(target string) error {
|
||||
klog.V(4).Infof("azureMount: Unmount target (%q)", target)
|
||||
target = normalizeWindowsPath(target)
|
||||
if output, err := exec.Command("cmd", "/c", "rmdir", target).CombinedOutput(); err != nil {
|
||||
klog.Errorf("rmdir failed: %v, output: %q", err, string(output))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns a list of all mounted filesystems. todo
|
||||
func (mounter *Mounter) List() ([]MountPoint, error) {
|
||||
return []MountPoint{}, nil
|
||||
}
|
||||
|
||||
// IsMountPointMatch determines if the mountpoint matches the dir
|
||||
func (mounter *Mounter) IsMountPointMatch(mp MountPoint, dir string) bool {
|
||||
return mp.Path == dir
|
||||
}
|
||||
|
||||
// IsNotMountPoint determines if a directory is a mountpoint.
|
||||
func (mounter *Mounter) IsNotMountPoint(dir string) (bool, error) {
|
||||
return isNotMountPoint(mounter, dir)
|
||||
}
|
||||
|
||||
// IsLikelyNotMountPoint determines if a directory is not a mountpoint.
|
||||
func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
|
||||
stat, err := os.Lstat(file)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
// If current file is a symlink, then it is a mountpoint.
|
||||
if stat.Mode()&os.ModeSymlink != 0 {
|
||||
target, err := os.Readlink(file)
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("readlink error: %v", err)
|
||||
}
|
||||
exists, err := mounter.ExistsPath(target)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return !exists, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetDeviceNameFromMount given a mnt point, find the device
|
||||
func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
|
||||
return getDeviceNameFromMount(mounter, mountPath, pluginDir)
|
||||
}
|
||||
|
||||
// getDeviceNameFromMount find the device(drive) name 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 := mounter.GetMountRefs(mountPath)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err)
|
||||
return "", err
|
||||
}
|
||||
if len(refs) == 0 {
|
||||
return "", fmt.Errorf("directory %s is not mounted", mountPath)
|
||||
}
|
||||
basemountPath := normalizeWindowsPath(path.Join(pluginDir, MountsInGlobalPDPath))
|
||||
for _, ref := range refs {
|
||||
if strings.Contains(ref, basemountPath) {
|
||||
volumeID, err := filepath.Rel(normalizeWindowsPath(basemountPath), ref)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err)
|
||||
return "", err
|
||||
}
|
||||
return volumeID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return path.Base(mountPath), nil
|
||||
}
|
||||
|
||||
// DeviceOpened determines if the device is in use elsewhere
|
||||
func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// PathIsDevice determines if a path is a device.
|
||||
func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// MakeRShared checks that given path is on a mount with 'rshared' mount
|
||||
// propagation. Empty implementation here.
|
||||
func (mounter *Mounter) MakeRShared(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFileType checks for sockets/block/character devices
|
||||
func (mounter *Mounter) GetFileType(pathname string) (FileType, error) {
|
||||
return getFileType(pathname)
|
||||
}
|
||||
|
||||
// MakeFile creates a new directory
|
||||
func (mounter *Mounter) MakeDir(pathname string) error {
|
||||
err := os.MkdirAll(pathname, os.FileMode(0755))
|
||||
if err != nil {
|
||||
if !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeFile creates an empty file
|
||||
func (mounter *Mounter) MakeFile(pathname string) error {
|
||||
f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644))
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
if !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExistsPath checks whether the path exists
|
||||
func (mounter *Mounter) ExistsPath(pathname string) (bool, error) {
|
||||
return utilpath.Exists(utilpath.CheckFollowSymlink, pathname)
|
||||
}
|
||||
|
||||
// EvalHostSymlinks returns the path name after evaluating symlinks
|
||||
func (mounter *Mounter) EvalHostSymlinks(pathname string) (string, error) {
|
||||
return filepath.EvalSymlinks(pathname)
|
||||
}
|
||||
|
||||
// check whether hostPath is within volume path
|
||||
// this func will lock all intermediate subpath directories, need to close handle outside of this func after container started
|
||||
func lockAndCheckSubPath(volumePath, hostPath string) ([]uintptr, error) {
|
||||
if len(volumePath) == 0 || len(hostPath) == 0 {
|
||||
return []uintptr{}, nil
|
||||
}
|
||||
|
||||
finalSubPath, err := filepath.EvalSymlinks(hostPath)
|
||||
if err != nil {
|
||||
return []uintptr{}, fmt.Errorf("cannot read link %s: %s", hostPath, err)
|
||||
}
|
||||
finalVolumePath, err := filepath.EvalSymlinks(volumePath)
|
||||
if err != nil {
|
||||
return []uintptr{}, fmt.Errorf("cannot read link %s: %s", volumePath, err)
|
||||
}
|
||||
|
||||
return lockAndCheckSubPathWithoutSymlink(finalVolumePath, finalSubPath)
|
||||
}
|
||||
|
||||
// lock all intermediate subPath directories and check they are all within volumePath
|
||||
// volumePath & subPath should not contain any symlink, otherwise it will return error
|
||||
func lockAndCheckSubPathWithoutSymlink(volumePath, subPath string) ([]uintptr, error) {
|
||||
if len(volumePath) == 0 || len(subPath) == 0 {
|
||||
return []uintptr{}, nil
|
||||
}
|
||||
|
||||
// get relative path to volumePath
|
||||
relSubPath, err := filepath.Rel(volumePath, subPath)
|
||||
if err != nil {
|
||||
return []uintptr{}, fmt.Errorf("Rel(%s, %s) error: %v", volumePath, subPath, err)
|
||||
}
|
||||
if startsWithBackstep(relSubPath) {
|
||||
return []uintptr{}, fmt.Errorf("SubPath %q not within volume path %q", subPath, volumePath)
|
||||
}
|
||||
|
||||
if relSubPath == "." {
|
||||
// volumePath and subPath are equal
|
||||
return []uintptr{}, nil
|
||||
}
|
||||
|
||||
fileHandles := []uintptr{}
|
||||
var errorResult error
|
||||
|
||||
currentFullPath := volumePath
|
||||
dirs := strings.Split(relSubPath, string(os.PathSeparator))
|
||||
for _, dir := range dirs {
|
||||
// lock intermediate subPath directory first
|
||||
currentFullPath = filepath.Join(currentFullPath, dir)
|
||||
handle, err := lockPath(currentFullPath)
|
||||
if err != nil {
|
||||
errorResult = fmt.Errorf("cannot lock path %s: %s", currentFullPath, err)
|
||||
break
|
||||
}
|
||||
fileHandles = append(fileHandles, handle)
|
||||
|
||||
// make sure intermediate subPath directory does not contain symlink any more
|
||||
stat, err := os.Lstat(currentFullPath)
|
||||
if err != nil {
|
||||
errorResult = fmt.Errorf("Lstat(%q) error: %v", currentFullPath, err)
|
||||
break
|
||||
}
|
||||
if stat.Mode()&os.ModeSymlink != 0 {
|
||||
errorResult = fmt.Errorf("subpath %q is an unexpected symlink after EvalSymlinks", currentFullPath)
|
||||
break
|
||||
}
|
||||
|
||||
if !PathWithinBase(currentFullPath, volumePath) {
|
||||
errorResult = fmt.Errorf("SubPath %q not within volume path %q", currentFullPath, volumePath)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return fileHandles, errorResult
|
||||
}
|
||||
|
||||
// unlockPath unlock directories
|
||||
func unlockPath(fileHandles []uintptr) {
|
||||
if fileHandles != nil {
|
||||
for _, handle := range fileHandles {
|
||||
syscall.CloseHandle(syscall.Handle(handle))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lockPath locks a directory or symlink, return handle, exec "syscall.CloseHandle(handle)" to unlock the path
|
||||
func lockPath(path string) (uintptr, error) {
|
||||
if len(path) == 0 {
|
||||
return uintptr(syscall.InvalidHandle), syscall.ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
pathp, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return uintptr(syscall.InvalidHandle), err
|
||||
}
|
||||
access := uint32(syscall.GENERIC_READ)
|
||||
sharemode := uint32(syscall.FILE_SHARE_READ)
|
||||
createmode := uint32(syscall.OPEN_EXISTING)
|
||||
flags := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
|
||||
fd, err := syscall.CreateFile(pathp, access, sharemode, nil, createmode, flags, 0)
|
||||
return uintptr(fd), err
|
||||
}
|
||||
|
||||
// Lock all directories in subPath and check they're not symlinks.
|
||||
func (mounter *Mounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
|
||||
handles, err := lockAndCheckSubPath(subPath.VolumePath, subPath.Path)
|
||||
|
||||
// Unlock the directories when the container starts
|
||||
cleanupAction = func() {
|
||||
unlockPath(handles)
|
||||
}
|
||||
return subPath.Path, cleanupAction, err
|
||||
}
|
||||
|
||||
// No bind-mounts for subpaths are necessary on Windows
|
||||
func (mounter *Mounter) CleanSubPaths(podDir string, volumeName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
|
||||
// Try to mount the disk
|
||||
klog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, source, target)
|
||||
|
||||
if err := ValidateDiskNumber(source); err != nil {
|
||||
klog.Errorf("diskMount: formatAndMount failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(fstype) == 0 {
|
||||
// Use 'NTFS' as the default
|
||||
fstype = "NTFS"
|
||||
}
|
||||
|
||||
// format disk if it is unformatted(raw)
|
||||
cmd := fmt.Sprintf("Get-Disk -Number %s | Where partitionstyle -eq 'raw' | Initialize-Disk -PartitionStyle MBR -PassThru"+
|
||||
" | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -FileSystem %s -Confirm:$false", source, fstype)
|
||||
if output, err := mounter.Exec.Run("powershell", "/c", cmd); err != nil {
|
||||
return fmt.Errorf("diskMount: format disk failed, error: %v, output: %q", err, string(output))
|
||||
}
|
||||
klog.V(4).Infof("diskMount: Disk successfully formatted, disk: %q, fstype: %q", source, fstype)
|
||||
|
||||
driveLetter, err := getDriveLetterByDiskNumber(source, mounter.Exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
driverPath := driveLetter + ":"
|
||||
target = normalizeWindowsPath(target)
|
||||
klog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, driverPath, target)
|
||||
if output, err := mounter.Exec.Run("cmd", "/c", "mklink", "/D", target, driverPath); err != nil {
|
||||
klog.Errorf("mklink failed: %v, output: %q", err, string(output))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeWindowsPath(path string) string {
|
||||
normalizedPath := strings.Replace(path, "/", "\\", -1)
|
||||
if strings.HasPrefix(normalizedPath, "\\") {
|
||||
normalizedPath = "c:" + normalizedPath
|
||||
}
|
||||
return normalizedPath
|
||||
}
|
||||
|
||||
// ValidateDiskNumber : disk number should be a number in [0, 99]
|
||||
func ValidateDiskNumber(disk string) error {
|
||||
diskNum, err := strconv.Atoi(disk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrong disk number format: %q, err:%v", disk, err)
|
||||
}
|
||||
|
||||
if diskNum < 0 || diskNum > 99 {
|
||||
return fmt.Errorf("disk number out of range: %q", disk)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get drive letter according to windows disk number
|
||||
func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) {
|
||||
cmd := fmt.Sprintf("(Get-Partition -DiskNumber %s).DriveLetter", diskNum)
|
||||
output, err := exec.Run("powershell", "/c", cmd)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("azureMount: Get Drive Letter failed: %v, output: %q", err, string(output))
|
||||
}
|
||||
if len(string(output)) < 1 {
|
||||
return "", fmt.Errorf("azureMount: Get Drive Letter failed, output is empty")
|
||||
}
|
||||
return string(output)[:1], nil
|
||||
}
|
||||
|
||||
// getAllParentLinks walks all symbolic links and return all the parent targets recursively
|
||||
func getAllParentLinks(path string) ([]string, error) {
|
||||
const maxIter = 255
|
||||
links := []string{}
|
||||
for {
|
||||
links = append(links, path)
|
||||
if len(links) > maxIter {
|
||||
return links, fmt.Errorf("unexpected length of parent links: %v", links)
|
||||
}
|
||||
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return links, fmt.Errorf("Lstat: %v", err)
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
path, err = os.Readlink(path)
|
||||
if err != nil {
|
||||
return links, fmt.Errorf("Readlink error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return links, nil
|
||||
}
|
||||
|
||||
// GetMountRefs : empty implementation here since there is no place to query all mount points on Windows
|
||||
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
|
||||
if _, err := os.Stat(normalizeWindowsPath(pathname)); os.IsNotExist(err) {
|
||||
return []string{}, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []string{pathname}, nil
|
||||
}
|
||||
|
||||
// Note that on windows, it always returns 0. We actually don't set FSGroup on
|
||||
// windows platform, see SetVolumeOwnership implementation.
|
||||
func (mounter *Mounter) GetFSGroup(pathname string) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (mounter *Mounter) GetSELinuxSupport(pathname string) (bool, error) {
|
||||
// Windows does not support SELinux.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (mounter *Mounter) GetMode(pathname string) (os.FileMode, error) {
|
||||
info, err := os.Stat(pathname)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return info.Mode(), nil
|
||||
}
|
||||
|
||||
// SafeMakeDir makes sure that the created directory does not escape given base directory mis-using symlinks.
|
||||
func (mounter *Mounter) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
|
||||
realBase, err := filepath.EvalSymlinks(base)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
|
||||
}
|
||||
|
||||
realFullPath := filepath.Join(realBase, subdir)
|
||||
return doSafeMakeDir(realFullPath, realBase, perm)
|
||||
}
|
||||
|
||||
func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
|
||||
klog.V(4).Infof("Creating directory %q within base %q", pathname, base)
|
||||
|
||||
if !PathWithinBase(pathname, base) {
|
||||
return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
|
||||
}
|
||||
|
||||
// Quick check if the directory already exists
|
||||
s, err := os.Stat(pathname)
|
||||
if err == nil {
|
||||
// Path exists
|
||||
if s.IsDir() {
|
||||
// The directory already exists. It can be outside of the parent,
|
||||
// but there is no race-proof check.
|
||||
klog.V(4).Infof("Directory %s already exists", pathname)
|
||||
return nil
|
||||
}
|
||||
return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
|
||||
}
|
||||
|
||||
// Find all existing directories
|
||||
existingPath, toCreate, err := findExistingPrefix(base, pathname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening directory %s: %s", pathname, err)
|
||||
}
|
||||
if len(toCreate) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure the existing directory is inside allowed base
|
||||
fullExistingPath, err := filepath.EvalSymlinks(existingPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening existing directory %s: %s", existingPath, err)
|
||||
}
|
||||
fullBasePath, err := filepath.EvalSymlinks(base)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read link %s: %s", base, err)
|
||||
}
|
||||
if !PathWithinBase(fullExistingPath, fullBasePath) {
|
||||
return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
|
||||
}
|
||||
|
||||
// lock all intermediate directories from fullBasePath to fullExistingPath (top to bottom)
|
||||
fileHandles, err := lockAndCheckSubPathWithoutSymlink(fullBasePath, fullExistingPath)
|
||||
defer unlockPath(fileHandles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
|
||||
currentPath := fullExistingPath
|
||||
// create the directories one by one, making sure nobody can change
|
||||
// created directory into symlink by lock that directory immediately
|
||||
for _, dir := range toCreate {
|
||||
currentPath = filepath.Join(currentPath, dir)
|
||||
klog.V(4).Infof("Creating %s", dir)
|
||||
if err := os.Mkdir(currentPath, perm); err != nil {
|
||||
return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
|
||||
}
|
||||
handle, err := lockPath(currentPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot lock path %s: %s", currentPath, err)
|
||||
}
|
||||
defer syscall.CloseHandle(syscall.Handle(handle))
|
||||
// make sure newly created directory does not contain symlink after lock
|
||||
stat, err := os.Lstat(currentPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Lstat(%q) error: %v", currentPath, err)
|
||||
}
|
||||
if stat.Mode()&os.ModeSymlink != 0 {
|
||||
return fmt.Errorf("subpath %q is an unexpected symlink after Mkdir", currentPath)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findExistingPrefix finds prefix of pathname that exists. In addition, it
|
||||
// returns list of remaining directories that don't exist yet.
|
||||
func findExistingPrefix(base, pathname string) (string, []string, error) {
|
||||
rel, err := filepath.Rel(base, pathname)
|
||||
if err != nil {
|
||||
return base, nil, err
|
||||
}
|
||||
|
||||
if startsWithBackstep(rel) {
|
||||
return base, nil, fmt.Errorf("pathname(%s) is not within base(%s)", pathname, base)
|
||||
}
|
||||
|
||||
if rel == "." {
|
||||
// base and pathname are equal
|
||||
return pathname, []string{}, nil
|
||||
}
|
||||
|
||||
dirs := strings.Split(rel, string(filepath.Separator))
|
||||
|
||||
parent := base
|
||||
currentPath := base
|
||||
for i, dir := range dirs {
|
||||
parent = currentPath
|
||||
currentPath = filepath.Join(parent, dir)
|
||||
if _, err := os.Lstat(currentPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return parent, dirs[i:], nil
|
||||
}
|
||||
return base, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pathname, []string{}, nil
|
||||
}
|
|
@ -0,0 +1,827 @@
|
|||
// +build windows
|
||||
|
||||
/*
|
||||
Copyright 2017 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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNormalizeWindowsPath(t *testing.T) {
|
||||
path := `/var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4/volumes/kubernetes.io~azure-disk`
|
||||
normalizedPath := normalizeWindowsPath(path)
|
||||
if normalizedPath != `c:\var\lib\kubelet\pods\146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~azure-disk` {
|
||||
t.Errorf("normizeWindowsPath test failed, normalizedPath : %q", normalizedPath)
|
||||
}
|
||||
|
||||
path = `/var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~azure-disk`
|
||||
normalizedPath = normalizeWindowsPath(path)
|
||||
if normalizedPath != `c:\var\lib\kubelet\pods\146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~azure-disk` {
|
||||
t.Errorf("normizeWindowsPath test failed, normalizedPath : %q", normalizedPath)
|
||||
}
|
||||
|
||||
path = `/`
|
||||
normalizedPath = normalizeWindowsPath(path)
|
||||
if normalizedPath != `c:\` {
|
||||
t.Errorf("normizeWindowsPath test failed, normalizedPath : %q", normalizedPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDiskNumber(t *testing.T) {
|
||||
diskNum := "0"
|
||||
if err := ValidateDiskNumber(diskNum); err != nil {
|
||||
t.Errorf("TestValidateDiskNumber test failed, disk number : %s", diskNum)
|
||||
}
|
||||
|
||||
diskNum = "99"
|
||||
if err := ValidateDiskNumber(diskNum); err != nil {
|
||||
t.Errorf("TestValidateDiskNumber test failed, disk number : %s", diskNum)
|
||||
}
|
||||
|
||||
diskNum = "ab"
|
||||
if err := ValidateDiskNumber(diskNum); err == nil {
|
||||
t.Errorf("TestValidateDiskNumber test failed, disk number : %s", diskNum)
|
||||
}
|
||||
|
||||
diskNum = "100"
|
||||
if err := ValidateDiskNumber(diskNum); err == nil {
|
||||
t.Errorf("TestValidateDiskNumber test failed, disk number : %s", diskNum)
|
||||
}
|
||||
}
|
||||
|
||||
func makeLink(link, target string) error {
|
||||
if output, err := exec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("mklink failed: %v, link(%q) target(%q) output: %q", err, link, target, string(output))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeLink(link string) error {
|
||||
if output, err := exec.Command("cmd", "/c", "rmdir", link).CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("rmdir failed: %v, output: %q", err, string(output))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setEquivalent(set1, set2 []string) bool {
|
||||
map1 := make(map[string]bool)
|
||||
map2 := make(map[string]bool)
|
||||
for _, s := range set1 {
|
||||
map1[s] = true
|
||||
}
|
||||
for _, s := range set2 {
|
||||
map2[s] = true
|
||||
}
|
||||
|
||||
for s := range map1 {
|
||||
if !map2[s] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for s := range map2 {
|
||||
if !map1[s] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// this func must run in admin mode, otherwise it will fail
|
||||
func TestGetMountRefs(t *testing.T) {
|
||||
tests := []struct {
|
||||
mountPath string
|
||||
expectedRefs []string
|
||||
}{
|
||||
{
|
||||
mountPath: `c:\windows`,
|
||||
expectedRefs: []string{`c:\windows`},
|
||||
},
|
||||
{
|
||||
mountPath: `c:\doesnotexist`,
|
||||
expectedRefs: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
mounter := Mounter{"fake/path"}
|
||||
|
||||
for _, test := range tests {
|
||||
if refs, err := mounter.GetMountRefs(test.mountPath); err != nil || !setEquivalent(test.expectedRefs, refs) {
|
||||
t.Errorf("getMountRefs(%q) = %v, error: %v; expected %v", test.mountPath, refs, err, test.expectedRefs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoSafeMakeDir(t *testing.T) {
|
||||
base, err := ioutil.TempDir("", "TestDoSafeMakeDir")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
testingVolumePath := filepath.Join(base, "testingVolumePath")
|
||||
os.MkdirAll(testingVolumePath, 0755)
|
||||
defer os.RemoveAll(testingVolumePath)
|
||||
|
||||
tests := []struct {
|
||||
volumePath string
|
||||
subPath string
|
||||
expectError bool
|
||||
symlinkTarget string
|
||||
}{
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: ``,
|
||||
expectError: true,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `x`),
|
||||
expectError: false,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a\b\c\d`),
|
||||
expectError: false,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `symlink`),
|
||||
expectError: false,
|
||||
symlinkTarget: base,
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `symlink\c\d`),
|
||||
expectError: true,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `symlink\y926`),
|
||||
expectError: true,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a\b\symlink`),
|
||||
expectError: false,
|
||||
symlinkTarget: base,
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a\x\symlink`),
|
||||
expectError: false,
|
||||
symlinkTarget: filepath.Join(testingVolumePath, `a`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if len(test.volumePath) > 0 && len(test.subPath) > 0 && len(test.symlinkTarget) > 0 {
|
||||
// make all parent sub directories
|
||||
if parent := filepath.Dir(test.subPath); parent != "." {
|
||||
os.MkdirAll(parent, 0755)
|
||||
}
|
||||
|
||||
// make last element as symlink
|
||||
linkPath := test.subPath
|
||||
if _, err := os.Stat(linkPath); err != nil && os.IsNotExist(err) {
|
||||
if err := makeLink(linkPath, test.symlinkTarget); err != nil {
|
||||
t.Fatalf("unexpected error: %v", fmt.Errorf("mklink link(%q) target(%q) error: %q", linkPath, test.symlinkTarget, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := doSafeMakeDir(test.subPath, test.volumePath, os.FileMode(0755))
|
||||
if test.expectError {
|
||||
assert.NotNil(t, err, "Expect error during doSafeMakeDir(%s, %s)", test.subPath, test.volumePath)
|
||||
continue
|
||||
}
|
||||
assert.Nil(t, err, "Expect no error during doSafeMakeDir(%s, %s)", test.subPath, test.volumePath)
|
||||
if _, err := os.Stat(test.subPath); os.IsNotExist(err) {
|
||||
t.Errorf("subPath should exists after doSafeMakeDir(%s, %s)", test.subPath, test.volumePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLockAndCheckSubPath(t *testing.T) {
|
||||
base, err := ioutil.TempDir("", "TestLockAndCheckSubPath")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
testingVolumePath := filepath.Join(base, "testingVolumePath")
|
||||
|
||||
tests := []struct {
|
||||
volumePath string
|
||||
subPath string
|
||||
expectedHandleCount int
|
||||
expectError bool
|
||||
symlinkTarget string
|
||||
}{
|
||||
{
|
||||
volumePath: `c:\`,
|
||||
subPath: ``,
|
||||
expectedHandleCount: 0,
|
||||
expectError: false,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: ``,
|
||||
subPath: `a`,
|
||||
expectedHandleCount: 0,
|
||||
expectError: false,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a`),
|
||||
expectedHandleCount: 1,
|
||||
expectError: false,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a\b\c\d`),
|
||||
expectedHandleCount: 4,
|
||||
expectError: false,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `symlink`),
|
||||
expectedHandleCount: 0,
|
||||
expectError: true,
|
||||
symlinkTarget: base,
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a\b\c\symlink`),
|
||||
expectedHandleCount: 0,
|
||||
expectError: true,
|
||||
symlinkTarget: base,
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a\b\c\d\symlink`),
|
||||
expectedHandleCount: 2,
|
||||
expectError: false,
|
||||
symlinkTarget: filepath.Join(testingVolumePath, `a\b`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if len(test.volumePath) > 0 && len(test.subPath) > 0 {
|
||||
os.MkdirAll(test.volumePath, 0755)
|
||||
if len(test.symlinkTarget) == 0 {
|
||||
// make all intermediate sub directories
|
||||
os.MkdirAll(test.subPath, 0755)
|
||||
} else {
|
||||
// make all parent sub directories
|
||||
if parent := filepath.Dir(test.subPath); parent != "." {
|
||||
os.MkdirAll(parent, 0755)
|
||||
}
|
||||
|
||||
// make last element as symlink
|
||||
linkPath := test.subPath
|
||||
if _, err := os.Stat(linkPath); err != nil && os.IsNotExist(err) {
|
||||
if err := makeLink(linkPath, test.symlinkTarget); err != nil {
|
||||
t.Fatalf("unexpected error: %v", fmt.Errorf("mklink link(%q) target(%q) error: %q", linkPath, test.symlinkTarget, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileHandles, err := lockAndCheckSubPath(test.volumePath, test.subPath)
|
||||
unlockPath(fileHandles)
|
||||
assert.Equal(t, test.expectedHandleCount, len(fileHandles))
|
||||
if test.expectError {
|
||||
assert.NotNil(t, err, "Expect error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
|
||||
continue
|
||||
}
|
||||
assert.Nil(t, err, "Expect no error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
|
||||
}
|
||||
|
||||
// remove dir will happen after closing all file handles
|
||||
assert.Nil(t, os.RemoveAll(testingVolumePath), "Expect no error during remove dir %s", testingVolumePath)
|
||||
}
|
||||
|
||||
func TestLockAndCheckSubPathWithoutSymlink(t *testing.T) {
|
||||
base, err := ioutil.TempDir("", "TestLockAndCheckSubPathWithoutSymlink")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
testingVolumePath := filepath.Join(base, "testingVolumePath")
|
||||
|
||||
tests := []struct {
|
||||
volumePath string
|
||||
subPath string
|
||||
expectedHandleCount int
|
||||
expectError bool
|
||||
symlinkTarget string
|
||||
}{
|
||||
{
|
||||
volumePath: `c:\`,
|
||||
subPath: ``,
|
||||
expectedHandleCount: 0,
|
||||
expectError: false,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: ``,
|
||||
subPath: `a`,
|
||||
expectedHandleCount: 0,
|
||||
expectError: false,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a`),
|
||||
expectedHandleCount: 1,
|
||||
expectError: false,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a\b\c\d`),
|
||||
expectedHandleCount: 4,
|
||||
expectError: false,
|
||||
symlinkTarget: "",
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `symlink`),
|
||||
expectedHandleCount: 1,
|
||||
expectError: true,
|
||||
symlinkTarget: base,
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a\b\c\symlink`),
|
||||
expectedHandleCount: 4,
|
||||
expectError: true,
|
||||
symlinkTarget: base,
|
||||
},
|
||||
{
|
||||
volumePath: testingVolumePath,
|
||||
subPath: filepath.Join(testingVolumePath, `a\b\c\d\symlink`),
|
||||
expectedHandleCount: 5,
|
||||
expectError: true,
|
||||
symlinkTarget: filepath.Join(testingVolumePath, `a\b`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if len(test.volumePath) > 0 && len(test.subPath) > 0 {
|
||||
os.MkdirAll(test.volumePath, 0755)
|
||||
if len(test.symlinkTarget) == 0 {
|
||||
// make all intermediate sub directories
|
||||
os.MkdirAll(test.subPath, 0755)
|
||||
} else {
|
||||
// make all parent sub directories
|
||||
if parent := filepath.Dir(test.subPath); parent != "." {
|
||||
os.MkdirAll(parent, 0755)
|
||||
}
|
||||
|
||||
// make last element as symlink
|
||||
linkPath := test.subPath
|
||||
if _, err := os.Stat(linkPath); err != nil && os.IsNotExist(err) {
|
||||
if err := makeLink(linkPath, test.symlinkTarget); err != nil {
|
||||
t.Fatalf("unexpected error: %v", fmt.Errorf("mklink link(%q) target(%q) error: %q", linkPath, test.symlinkTarget, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileHandles, err := lockAndCheckSubPathWithoutSymlink(test.volumePath, test.subPath)
|
||||
unlockPath(fileHandles)
|
||||
assert.Equal(t, test.expectedHandleCount, len(fileHandles))
|
||||
if test.expectError {
|
||||
assert.NotNil(t, err, "Expect error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
|
||||
continue
|
||||
}
|
||||
assert.Nil(t, err, "Expect no error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
|
||||
}
|
||||
|
||||
// remove dir will happen after closing all file handles
|
||||
assert.Nil(t, os.RemoveAll(testingVolumePath), "Expect no error during remove dir %s", testingVolumePath)
|
||||
}
|
||||
|
||||
func TestFindExistingPrefix(t *testing.T) {
|
||||
base, err := ioutil.TempDir("", "TestFindExistingPrefix")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
testingVolumePath := filepath.Join(base, "testingVolumePath")
|
||||
|
||||
tests := []struct {
|
||||
base string
|
||||
pathname string
|
||||
expectError bool
|
||||
expectedExistingPath string
|
||||
expectedToCreateDirs []string
|
||||
createSubPathBeforeTest bool
|
||||
}{
|
||||
{
|
||||
base: `c:\tmp\a`,
|
||||
pathname: `c:\tmp\b`,
|
||||
expectError: true,
|
||||
expectedExistingPath: "",
|
||||
expectedToCreateDirs: []string{},
|
||||
createSubPathBeforeTest: false,
|
||||
},
|
||||
{
|
||||
base: ``,
|
||||
pathname: `c:\tmp\b`,
|
||||
expectError: true,
|
||||
expectedExistingPath: "",
|
||||
expectedToCreateDirs: []string{},
|
||||
createSubPathBeforeTest: false,
|
||||
},
|
||||
{
|
||||
base: `c:\tmp\a`,
|
||||
pathname: `d:\tmp\b`,
|
||||
expectError: true,
|
||||
expectedExistingPath: "",
|
||||
expectedToCreateDirs: []string{},
|
||||
createSubPathBeforeTest: false,
|
||||
},
|
||||
{
|
||||
base: testingVolumePath,
|
||||
pathname: testingVolumePath,
|
||||
expectError: false,
|
||||
expectedExistingPath: testingVolumePath,
|
||||
expectedToCreateDirs: []string{},
|
||||
createSubPathBeforeTest: false,
|
||||
},
|
||||
{
|
||||
base: testingVolumePath,
|
||||
pathname: filepath.Join(testingVolumePath, `a\b`),
|
||||
expectError: false,
|
||||
expectedExistingPath: filepath.Join(testingVolumePath, `a\b`),
|
||||
expectedToCreateDirs: []string{},
|
||||
createSubPathBeforeTest: true,
|
||||
},
|
||||
{
|
||||
base: testingVolumePath,
|
||||
pathname: filepath.Join(testingVolumePath, `a\b\c\`),
|
||||
expectError: false,
|
||||
expectedExistingPath: filepath.Join(testingVolumePath, `a\b`),
|
||||
expectedToCreateDirs: []string{`c`},
|
||||
createSubPathBeforeTest: false,
|
||||
},
|
||||
{
|
||||
base: testingVolumePath,
|
||||
pathname: filepath.Join(testingVolumePath, `a\b\c\d`),
|
||||
expectError: false,
|
||||
expectedExistingPath: filepath.Join(testingVolumePath, `a\b`),
|
||||
expectedToCreateDirs: []string{`c`, `d`},
|
||||
createSubPathBeforeTest: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if test.createSubPathBeforeTest {
|
||||
os.MkdirAll(test.pathname, 0755)
|
||||
}
|
||||
|
||||
existingPath, toCreate, err := findExistingPrefix(test.base, test.pathname)
|
||||
if test.expectError {
|
||||
assert.NotNil(t, err, "Expect error during findExistingPrefix(%s, %s)", test.base, test.pathname)
|
||||
continue
|
||||
}
|
||||
assert.Nil(t, err, "Expect no error during findExistingPrefix(%s, %s)", test.base, test.pathname)
|
||||
|
||||
assert.Equal(t, test.expectedExistingPath, existingPath, "Expect result not equal with findExistingPrefix(%s, %s) return: %q, expected: %q",
|
||||
test.base, test.pathname, existingPath, test.expectedExistingPath)
|
||||
|
||||
assert.Equal(t, test.expectedToCreateDirs, toCreate, "Expect result not equal with findExistingPrefix(%s, %s) return: %q, expected: %q",
|
||||
test.base, test.pathname, toCreate, test.expectedToCreateDirs)
|
||||
|
||||
}
|
||||
// remove dir will happen after closing all file handles
|
||||
assert.Nil(t, os.RemoveAll(testingVolumePath), "Expect no error during remove dir %s", testingVolumePath)
|
||||
}
|
||||
|
||||
func TestPathWithinBase(t *testing.T) {
|
||||
tests := []struct {
|
||||
fullPath string
|
||||
basePath string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
fullPath: `c:\tmp\a\b\c`,
|
||||
basePath: `c:\tmp`,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
fullPath: `c:\tmp1`,
|
||||
basePath: `c:\tmp2`,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
fullPath: `c:\tmp`,
|
||||
basePath: `c:\tmp`,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
fullPath: `c:\tmp`,
|
||||
basePath: `c:\tmp\a\b\c`,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
fullPath: `c:\kubelet\pods\uuid\volumes\kubernetes.io~configmap\config\..timestamp\file.txt`,
|
||||
basePath: `c:\kubelet\pods\uuid\volumes\kubernetes.io~configmap\config`,
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := PathWithinBase(test.fullPath, test.basePath)
|
||||
assert.Equal(t, result, test.expectedResult, "Expect result not equal with PathWithinBase(%s, %s) return: %q, expected: %q",
|
||||
test.fullPath, test.basePath, result, test.expectedResult)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFileType(t *testing.T) {
|
||||
mounter := New("fake/path")
|
||||
|
||||
testCase := []struct {
|
||||
name string
|
||||
expectedType FileType
|
||||
setUp func() (string, string, error)
|
||||
}{
|
||||
{
|
||||
"Directory Test",
|
||||
FileTypeDirectory,
|
||||
func() (string, string, error) {
|
||||
tempDir, err := ioutil.TempDir("", "test-get-filetype-")
|
||||
return tempDir, tempDir, err
|
||||
},
|
||||
},
|
||||
{
|
||||
"File Test",
|
||||
FileTypeFile,
|
||||
func() (string, string, error) {
|
||||
tempFile, err := ioutil.TempFile("", "test-get-filetype")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
tempFile.Close()
|
||||
return tempFile.Name(), tempFile.Name(), nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for idx, tc := range testCase {
|
||||
path, cleanUpPath, err := tc.setUp()
|
||||
if err != nil {
|
||||
t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err)
|
||||
}
|
||||
if len(cleanUpPath) > 0 {
|
||||
defer os.RemoveAll(cleanUpPath)
|
||||
}
|
||||
|
||||
fileType, err := mounter.GetFileType(path)
|
||||
if err != nil {
|
||||
t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err)
|
||||
}
|
||||
if fileType != tc.expectedType {
|
||||
t.Fatalf("[%d-%s] expected %s, but got %s", idx, tc.name, tc.expectedType, fileType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLikelyNotMountPoint(t *testing.T) {
|
||||
mounter := Mounter{"fake/path"}
|
||||
|
||||
tests := []struct {
|
||||
fileName string
|
||||
targetLinkName string
|
||||
setUp func(base, fileName, targetLinkName string) error
|
||||
expectedResult bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
"Dir",
|
||||
"",
|
||||
func(base, fileName, targetLinkName string) error {
|
||||
return os.Mkdir(filepath.Join(base, fileName), 0750)
|
||||
},
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"InvalidDir",
|
||||
"",
|
||||
func(base, fileName, targetLinkName string) error {
|
||||
return nil
|
||||
},
|
||||
true,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ValidSymLink",
|
||||
"targetSymLink",
|
||||
func(base, fileName, targetLinkName string) error {
|
||||
targeLinkPath := filepath.Join(base, targetLinkName)
|
||||
if err := os.Mkdir(targeLinkPath, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filePath := filepath.Join(base, fileName)
|
||||
if err := makeLink(filePath, targeLinkPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"InvalidSymLink",
|
||||
"targetSymLink2",
|
||||
func(base, fileName, targetLinkName string) error {
|
||||
targeLinkPath := filepath.Join(base, targetLinkName)
|
||||
if err := os.Mkdir(targeLinkPath, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filePath := filepath.Join(base, fileName)
|
||||
if err := makeLink(filePath, targeLinkPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return removeLink(targeLinkPath)
|
||||
},
|
||||
true,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
base, err := ioutil.TempDir("", test.fileName)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
if err := test.setUp(base, test.fileName, test.targetLinkName); err != nil {
|
||||
t.Fatalf("unexpected error in setUp(%s, %s): %v", test.fileName, test.targetLinkName, err)
|
||||
}
|
||||
|
||||
filePath := filepath.Join(base, test.fileName)
|
||||
result, err := mounter.IsLikelyNotMountPoint(filePath)
|
||||
assert.Equal(t, result, test.expectedResult, "Expect result not equal with IsLikelyNotMountPoint(%s) return: %q, expected: %q",
|
||||
filePath, result, test.expectedResult)
|
||||
|
||||
if test.expectError {
|
||||
assert.NotNil(t, err, "Expect error during IsLikelyNotMountPoint(%s)", filePath)
|
||||
} else {
|
||||
assert.Nil(t, err, "Expect error is nil during IsLikelyNotMountPoint(%s)", filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatAndMount(t *testing.T) {
|
||||
fakeMounter := ErrorMounter{&FakeMounter{}, 0, nil}
|
||||
execCallback := func(cmd string, args ...string) ([]byte, error) {
|
||||
for j := range args {
|
||||
if strings.Contains(args[j], "Get-Disk -Number") {
|
||||
return []byte("0"), nil
|
||||
}
|
||||
|
||||
if strings.Contains(args[j], "Get-Partition -DiskNumber") {
|
||||
return []byte("0"), nil
|
||||
}
|
||||
|
||||
if strings.Contains(args[j], "mklink") {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Unexpected cmd %s, args %v", cmd, args)
|
||||
}
|
||||
fakeExec := NewFakeExec(execCallback)
|
||||
|
||||
mounter := SafeFormatAndMount{
|
||||
Interface: &fakeMounter,
|
||||
Exec: fakeExec,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
device string
|
||||
target string
|
||||
fstype string
|
||||
mountOptions []string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
"0",
|
||||
"disk",
|
||||
"NTFS",
|
||||
[]string{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"0",
|
||||
"disk",
|
||||
"",
|
||||
[]string{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalidDevice",
|
||||
"disk",
|
||||
"NTFS",
|
||||
[]string{},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
base, err := ioutil.TempDir("", test.device)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
target := filepath.Join(base, test.target)
|
||||
err = mounter.FormatAndMount(test.device, target, test.fstype, test.mountOptions)
|
||||
if test.expectError {
|
||||
assert.NotNil(t, err, "Expect error during FormatAndMount(%s, %s, %s, %v)", test.device, test.target, test.fstype, test.mountOptions)
|
||||
} else {
|
||||
assert.Nil(t, err, "Expect error is nil during FormatAndMount(%s, %s, %s, %v)", test.device, test.target, test.fstype, test.mountOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSMBMapping(t *testing.T) {
|
||||
tests := []struct {
|
||||
username string
|
||||
password string
|
||||
remotepath string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
"",
|
||||
"password",
|
||||
`\\remotepath`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"username",
|
||||
"",
|
||||
`\\remotepath`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"username",
|
||||
"password",
|
||||
"",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, err := newSMBMapping(test.username, test.password, test.remotepath)
|
||||
if test.expectError {
|
||||
assert.NotNil(t, err, "Expect error during newSMBMapping(%s, %s, %s, %v)", test.username, test.password, test.remotepath)
|
||||
} else {
|
||||
assert.Nil(t, err, "Expect error is nil during newSMBMapping(%s, %s, %s, %v)", test.username, test.password, test.remotepath)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue