Merge pull request #81794 from codenrhoden/split-host-utils2

Split HostUtil functionality into its own files
This commit is contained in:
Kubernetes Prow Robot 2019-08-24 22:28:21 -07:00 committed by GitHub
commit 7ebbe34d9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1576 additions and 1303 deletions

View File

@ -6,6 +6,11 @@ go_library(
"doc.go", "doc.go",
"exec.go", "exec.go",
"fake.go", "fake.go",
"fake_hostutil.go",
"hostutil.go",
"hostutil_linux.go",
"hostutil_unsupported.go",
"hostutil_windows.go",
"mount.go", "mount.go",
"mount_helper_common.go", "mount_helper_common.go",
"mount_helper_unix.go", "mount_helper_unix.go",
@ -20,11 +25,38 @@ go_library(
"//vendor/k8s.io/klog:go_default_library", "//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library",
] + select({ ] + select({
"@io_bazel_rules_go//go/platform:android": [
"//vendor/k8s.io/utils/io:go_default_library",
],
"@io_bazel_rules_go//go/platform:darwin": [
"//vendor/k8s.io/utils/io:go_default_library",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"//vendor/k8s.io/utils/io:go_default_library",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"//vendor/k8s.io/utils/io:go_default_library",
],
"@io_bazel_rules_go//go/platform:linux": [ "@io_bazel_rules_go//go/platform:linux": [
"//vendor/golang.org/x/sys/unix:go_default_library", "//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/k8s.io/utils/io:go_default_library", "//vendor/k8s.io/utils/io:go_default_library",
"//vendor/k8s.io/utils/path:go_default_library", "//vendor/k8s.io/utils/path:go_default_library",
], ],
"@io_bazel_rules_go//go/platform:nacl": [
"//vendor/k8s.io/utils/io:go_default_library",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"//vendor/k8s.io/utils/io:go_default_library",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"//vendor/k8s.io/utils/io:go_default_library",
],
"@io_bazel_rules_go//go/platform:plan9": [
"//vendor/k8s.io/utils/io:go_default_library",
],
"@io_bazel_rules_go//go/platform:solaris": [
"//vendor/k8s.io/utils/io:go_default_library",
],
"@io_bazel_rules_go//go/platform:windows": [ "@io_bazel_rules_go//go/platform:windows": [
"//vendor/k8s.io/utils/keymutex:go_default_library", "//vendor/k8s.io/utils/keymutex:go_default_library",
"//vendor/k8s.io/utils/path:go_default_library", "//vendor/k8s.io/utils/path:go_default_library",
@ -36,7 +68,11 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = [
"hostutil_linux_test.go",
"hostutil_windows_test.go",
"mount_helper_test.go", "mount_helper_test.go",
"mount_helper_unix_test.go",
"mount_helper_windows_test.go",
"mount_linux_test.go", "mount_linux_test.go",
"mount_test.go", "mount_test.go",
"mount_windows_test.go", "mount_windows_test.go",

View File

@ -17,7 +17,6 @@ limitations under the License.
package mount package mount
import ( import (
"errors"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
@ -185,96 +184,3 @@ func (f *FakeMounter) GetMountRefs(pathname string) ([]string, error) {
} }
return getMountRefsByDev(f, realpath) return getMountRefsByDev(f, realpath)
} }
// FakeHostUtil is a fake mount.HostUtils implementation for testing
type FakeHostUtil struct {
MountPoints []MountPoint
Filesystem map[string]FileType
mutex sync.Mutex
}
var _ HostUtils = &FakeHostUtil{}
// DeviceOpened checks if block device referenced by pathname is in use by
// checking if is listed as a device in the in-memory mountpoint table.
func (hu *FakeHostUtil) DeviceOpened(pathname string) (bool, error) {
hu.mutex.Lock()
defer hu.mutex.Unlock()
for _, mp := range hu.MountPoints {
if mp.Device == pathname {
return true, nil
}
}
return false, nil
}
// PathIsDevice always returns true
func (hu *FakeHostUtil) PathIsDevice(pathname string) (bool, error) {
return true, nil
}
// GetDeviceNameFromMount given a mount point, find the volume id
func (hu *FakeHostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return getDeviceNameFromMount(mounter, mountPath, pluginMountDir)
}
// MakeRShared checks if path is shared and bind-mounts it as rshared if needed.
// No-op for testing
func (hu *FakeHostUtil) MakeRShared(path string) error {
return nil
}
// GetFileType checks for file/directory/socket/block/character devices.
// Defaults to Directory if otherwise unspecified.
func (hu *FakeHostUtil) GetFileType(pathname string) (FileType, error) {
if t, ok := hu.Filesystem[pathname]; ok {
return t, nil
}
return FileType("Directory"), nil
}
// MakeDir creates a new directory.
// No-op for testing
func (hu *FakeHostUtil) MakeDir(pathname string) error {
return nil
}
// MakeFile creates a new file.
// No-op for testing
func (hu *FakeHostUtil) MakeFile(pathname string) error {
return nil
}
// PathExists checks if pathname exists.
func (hu *FakeHostUtil) PathExists(pathname string) (bool, error) {
if _, ok := hu.Filesystem[pathname]; ok {
return true, nil
}
return false, nil
}
// EvalHostSymlinks returns the path name after evaluating symlinks.
// No-op for testing
func (hu *FakeHostUtil) EvalHostSymlinks(pathname string) (string, error) {
return pathname, nil
}
// GetOwner returns the integer ID for the user and group of the given path
// Not implemented for testing
func (hu *FakeHostUtil) GetOwner(pathname string) (int64, int64, error) {
return -1, -1, errors.New("GetOwner not implemented")
}
// GetSELinuxSupport tests if pathname is on a mount that supports SELinux.
// Not implemented for testing
func (hu *FakeHostUtil) GetSELinuxSupport(pathname string) (bool, error) {
return false, errors.New("GetSELinuxSupport not implemented")
}
// GetMode returns permissions of pathname.
// Not implemented for testing
func (hu *FakeHostUtil) GetMode(pathname string) (os.FileMode, error) {
return 0, errors.New("not implemented")
}

View File

@ -0,0 +1,116 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mount
import (
"errors"
"os"
"sync"
)
// FakeHostUtil is a fake mount.HostUtils implementation for testing
type FakeHostUtil struct {
MountPoints []MountPoint
Filesystem map[string]FileType
mutex sync.Mutex
}
var _ HostUtils = &FakeHostUtil{}
// DeviceOpened checks if block device referenced by pathname is in use by
// checking if is listed as a device in the in-memory mountpoint table.
func (hu *FakeHostUtil) DeviceOpened(pathname string) (bool, error) {
hu.mutex.Lock()
defer hu.mutex.Unlock()
for _, mp := range hu.MountPoints {
if mp.Device == pathname {
return true, nil
}
}
return false, nil
}
// PathIsDevice always returns true
func (hu *FakeHostUtil) PathIsDevice(pathname string) (bool, error) {
return true, nil
}
// GetDeviceNameFromMount given a mount point, find the volume id
func (hu *FakeHostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return getDeviceNameFromMount(mounter, mountPath, pluginMountDir)
}
// MakeRShared checks if path is shared and bind-mounts it as rshared if needed.
// No-op for testing
func (hu *FakeHostUtil) MakeRShared(path string) error {
return nil
}
// GetFileType checks for file/directory/socket/block/character devices.
// Defaults to Directory if otherwise unspecified.
func (hu *FakeHostUtil) GetFileType(pathname string) (FileType, error) {
if t, ok := hu.Filesystem[pathname]; ok {
return t, nil
}
return FileType("Directory"), nil
}
// MakeDir creates a new directory.
// No-op for testing
func (hu *FakeHostUtil) MakeDir(pathname string) error {
return nil
}
// MakeFile creates a new file.
// No-op for testing
func (hu *FakeHostUtil) MakeFile(pathname string) error {
return nil
}
// PathExists checks if pathname exists.
func (hu *FakeHostUtil) PathExists(pathname string) (bool, error) {
if _, ok := hu.Filesystem[pathname]; ok {
return true, nil
}
return false, nil
}
// EvalHostSymlinks returns the path name after evaluating symlinks.
// No-op for testing
func (hu *FakeHostUtil) EvalHostSymlinks(pathname string) (string, error) {
return pathname, nil
}
// GetOwner returns the integer ID for the user and group of the given path
// Not implemented for testing
func (hu *FakeHostUtil) GetOwner(pathname string) (int64, int64, error) {
return -1, -1, errors.New("GetOwner not implemented")
}
// GetSELinuxSupport tests if pathname is on a mount that supports SELinux.
// Not implemented for testing
func (hu *FakeHostUtil) GetSELinuxSupport(pathname string) (bool, error) {
return false, errors.New("GetSELinuxSupport not implemented")
}
// GetMode returns permissions of pathname.
// Not implemented for testing
func (hu *FakeHostUtil) GetMode(pathname string) (os.FileMode, error) {
return 0, errors.New("not implemented")
}

114
pkg/util/mount/hostutil.go Normal file
View File

@ -0,0 +1,114 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// TODO(thockin): This whole pkg is pretty linux-centric. As soon as we have
// an alternate platform, we will need to abstract further.
package mount
import (
"fmt"
"os"
)
// FileType enumerates the known set of possible file types.
type FileType string
const (
// FileTypeBlockDev defines a constant for the block device FileType.
FileTypeBlockDev FileType = "BlockDevice"
// FileTypeCharDev defines a constant for the character device FileType.
FileTypeCharDev FileType = "CharDevice"
// FileTypeDirectory defines a constant for the directory FileType.
FileTypeDirectory FileType = "Directory"
// FileTypeFile defines a constant for the file FileType.
FileTypeFile FileType = "File"
// FileTypeSocket defines a constant for the socket FileType.
FileTypeSocket FileType = "Socket"
// FileTypeUnknown defines a constant for an unknown FileType.
FileTypeUnknown FileType = ""
)
// HostUtils defines the set of methods for interacting with paths on a host.
type HostUtils interface {
// DeviceOpened determines if the device (e.g. /dev/sdc) is in use elsewhere
// on the system, i.e. still mounted.
DeviceOpened(pathname string) (bool, error)
// PathIsDevice determines if a path is a device.
PathIsDevice(pathname string) (bool, error)
// GetDeviceNameFromMount finds the device name by checking the mount path
// to get the global mount path within its plugin directory.
GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error)
// MakeRShared checks that given path is on a mount with 'rshared' mount
// propagation. If not, it bind-mounts the path as rshared.
MakeRShared(path string) error
// GetFileType checks for file/directory/socket/block/character devices.
GetFileType(pathname string) (FileType, error)
// MakeFile creates an empty file.
MakeFile(pathname string) error
// MakeDir creates a new directory.
MakeDir(pathname string) error
// PathExists tests if the given path already exists
// Error is returned on any other error than "file not found".
PathExists(pathname string) (bool, error)
// EvalHostSymlinks returns the path name after evaluating symlinks.
EvalHostSymlinks(pathname string) (string, error)
// GetOwner returns the integer ID for the user and group of the given path
GetOwner(pathname string) (int64, int64, error)
// GetSELinuxSupport returns true if given path is on a mount that supports
// SELinux.
GetSELinuxSupport(pathname string) (bool, error)
// GetMode returns permissions of the path.
GetMode(pathname string) (os.FileMode, error)
}
// Compile-time check to ensure all HostUtil implementations satisfy
// the HostUtils Interface.
var _ HostUtils = &hostUtil{}
// getFileType checks for file/directory/socket and block/character devices.
func getFileType(pathname string) (FileType, error) {
var pathType FileType
info, err := os.Stat(pathname)
if os.IsNotExist(err) {
return pathType, fmt.Errorf("path %q does not exist", pathname)
}
// err in call to os.Stat
if err != nil {
return pathType, err
}
// checks whether the mode is the target mode.
isSpecificMode := func(mode, targetMode os.FileMode) bool {
return mode&targetMode == targetMode
}
mode := info.Mode()
if mode.IsDir() {
return FileTypeDirectory, nil
} else if mode.IsRegular() {
return FileTypeFile, nil
} else if isSpecificMode(mode, os.ModeSocket) {
return FileTypeSocket, nil
} else if isSpecificMode(mode, os.ModeDevice) {
if isSpecificMode(mode, os.ModeCharDevice) {
return FileTypeCharDev, nil
}
return FileTypeBlockDev, nil
}
return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device")
}

View File

@ -0,0 +1,294 @@
// +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"
"path/filepath"
"strings"
"syscall"
"golang.org/x/sys/unix"
"k8s.io/klog"
utilpath "k8s.io/utils/path"
)
type hostUtil struct {
}
// NewHostUtil returns a struct that implements the HostUtils interface on
// linux platforms
func NewHostUtil() HostUtils {
return &hostUtil{}
}
// DeviceOpened checks if block device in use by calling Open with O_EXCL flag.
// If pathname is not a device, log and return false with nil error.
// If open returns errno EBUSY, return true with nil error.
// If open returns nil, return false with nil error.
// Otherwise, return false with error
func (hu *hostUtil) DeviceOpened(pathname string) (bool, error) {
return ExclusiveOpenFailsOnDevice(pathname)
}
// PathIsDevice uses FileInfo returned from os.Stat to check if path refers
// to a device.
func (hu *hostUtil) PathIsDevice(pathname string) (bool, error) {
pathType, err := hu.GetFileType(pathname)
isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev
return isDevice, err
}
// ExclusiveOpenFailsOnDevice is shared with NsEnterMounter
func ExclusiveOpenFailsOnDevice(pathname string) (bool, error) {
var isDevice bool
finfo, err := os.Stat(pathname)
if os.IsNotExist(err) {
isDevice = false
}
// err in call to os.Stat
if err != nil {
return false, fmt.Errorf(
"PathIsDevice failed for path %q: %v",
pathname,
err)
}
// path refers to a device
if finfo.Mode()&os.ModeDevice != 0 {
isDevice = true
}
if !isDevice {
klog.Errorf("Path %q is not referring to a device.", pathname)
return false, nil
}
fd, errno := unix.Open(pathname, unix.O_RDONLY|unix.O_EXCL|unix.O_CLOEXEC, 0)
// If the device is in use, open will return an invalid fd.
// When this happens, it is expected that Close will fail and throw an error.
defer unix.Close(fd)
if errno == nil {
// device not in use
return false, nil
} else if errno == unix.EBUSY {
// device is in use
return true, nil
}
// error during call to Open
return false, errno
}
//GetDeviceNameFromMount: given a mount point, find the device name from its global mount point
func (hu *hostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir)
}
func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir)
}
// GetDeviceNameFromMountLinux find the device name from /proc/mounts in which
// the mount path reference should match the given plugin mount directory. In case no mount path reference
// matches, returns the volume name taken from its given mountPath
// This implementation is shared with NsEnterMounter
func GetDeviceNameFromMountLinux(mounter Interface, mountPath, pluginMountDir 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 {
klog.V(4).Infof("Directory %s is not mounted", mountPath)
return "", fmt.Errorf("directory %s is not mounted", mountPath)
}
for _, ref := range refs {
if strings.HasPrefix(ref, pluginMountDir) {
volumeID, err := filepath.Rel(pluginMountDir, 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
}
func (hu *hostUtil) MakeRShared(path string) error {
return DoMakeRShared(path, procMountInfoPath)
}
func (hu *hostUtil) GetFileType(pathname string) (FileType, error) {
return getFileType(pathname)
}
func (hu *hostUtil) MakeDir(pathname string) error {
err := os.MkdirAll(pathname, os.FileMode(0755))
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
}
func (hu *hostUtil) 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
}
func (hu *hostUtil) PathExists(pathname string) (bool, error) {
return utilpath.Exists(utilpath.CheckFollowSymlink, pathname)
}
func (hu *hostUtil) EvalHostSymlinks(pathname string) (string, error) {
return filepath.EvalSymlinks(pathname)
}
// isShared returns true, if given path is on a mount point that has shared
// mount propagation.
func isShared(mount string, mountInfoPath string) (bool, error) {
info, err := findMountInfo(mount, mountInfoPath)
if err != nil {
return false, err
}
// parse optional parameters
for _, opt := range info.optionalFields {
if strings.HasPrefix(opt, "shared:") {
return true, nil
}
}
return false, nil
}
func findMountInfo(path, mountInfoPath string) (mountInfo, error) {
infos, err := parseMountInfo(mountInfoPath)
if err != nil {
return mountInfo{}, err
}
// process /proc/xxx/mountinfo in backward order and find the first mount
// point that is prefix of 'path' - that's the mount where path resides
var info *mountInfo
for i := len(infos) - 1; i >= 0; i-- {
if PathWithinBase(path, infos[i].mountPoint) {
info = &infos[i]
break
}
}
if info == nil {
return mountInfo{}, fmt.Errorf("cannot find mount point for %q", path)
}
return *info, nil
}
// DoMakeRShared is common implementation of MakeRShared on Linux. It checks if
// path is shared and bind-mounts it as rshared if needed. mountCmd and
// mountArgs are expected to contain mount-like command, DoMakeRShared will add
// '--bind <path> <path>' and '--make-rshared <path>' to mountArgs.
func DoMakeRShared(path string, mountInfoFilename string) error {
shared, err := isShared(path, mountInfoFilename)
if err != nil {
return err
}
if shared {
klog.V(4).Infof("Directory %s is already on a shared mount", path)
return nil
}
klog.V(2).Infof("Bind-mounting %q with shared mount propagation", path)
// mount --bind /var/lib/kubelet /var/lib/kubelet
if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_BIND, "" /*data*/); err != nil {
return fmt.Errorf("failed to bind-mount %s: %v", path, err)
}
// mount --make-rshared /var/lib/kubelet
if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_SHARED|syscall.MS_REC, "" /*data*/); err != nil {
return fmt.Errorf("failed to make %s rshared: %v", path, err)
}
return nil
}
// GetSELinux is common implementation of GetSELinuxSupport on Linux.
func GetSELinux(path string, mountInfoFilename string) (bool, error) {
info, err := findMountInfo(path, mountInfoFilename)
if err != nil {
return false, err
}
// "seclabel" can be both in mount options and super options.
for _, opt := range info.superOptions {
if opt == "seclabel" {
return true, nil
}
}
for _, opt := range info.mountOptions {
if opt == "seclabel" {
return true, nil
}
}
return false, nil
}
func (hu *hostUtil) GetSELinuxSupport(pathname string) (bool, error) {
return GetSELinux(pathname, procMountInfoPath)
}
// GetOwner returns the integer ID for the user and group of the given path
func (hu *hostUtil) GetOwner(pathname string) (int64, int64, error) {
realpath, err := filepath.EvalSymlinks(pathname)
if err != nil {
return -1, -1, err
}
return GetOwnerLinux(realpath)
}
func (hu *hostUtil) GetMode(pathname string) (os.FileMode, error) {
return GetModeLinux(pathname)
}
// GetOwnerLinux is shared between Linux and NsEnterMounter
// pathname must already be evaluated for symlinks
func GetOwnerLinux(pathname string) (int64, int64, error) {
info, err := os.Stat(pathname)
if err != nil {
return -1, -1, err
}
stat := info.Sys().(*syscall.Stat_t)
return int64(stat.Uid), int64(stat.Gid), nil
}
// GetModeLinux is shared between Linux and NsEnterMounter
func GetModeLinux(pathname string) (os.FileMode, error) {
info, err := os.Stat(pathname)
if err != nil {
return 0, err
}
return info.Mode(), nil
}

View File

@ -0,0 +1,313 @@
// +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"
"io/ioutil"
"net"
"os"
"path/filepath"
"strings"
"testing"
"k8s.io/utils/exec"
)
func TestIsSharedSuccess(t *testing.T) {
successMountInfo :=
`62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
76 62 8:1 / /boot rw,relatime shared:29 - ext4 /dev/sda1 rw,seclabel,data=ordered
78 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
80 62 0:42 / /var/lib/nfs/rpc_pipefs rw,relatime shared:31 - rpc_pipefs sunrpc rw
82 62 0:43 / /var/lib/foo rw,relatime shared:32 - tmpfs tmpfs rw
83 63 0:44 / /var/lib/bar rw,relatime - tmpfs tmpfs rw
227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
224 62 253:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
`
tempDir, filename, err := writeFile(successMountInfo)
if err != nil {
t.Fatalf("cannot create temporary file: %v", err)
}
defer os.RemoveAll(tempDir)
tests := []struct {
name string
path string
expectedResult bool
}{
{
// /var/lib/kubelet is a directory on mount '/' that is shared
// This is the most common case.
"shared",
"/var/lib/kubelet",
true,
},
{
// 8a2a... is a directory on mount /var/lib/docker/devicemapper
// that is private.
"private",
"/var/lib/docker/devicemapper/mnt/8a2a5c19eefb06d6f851dfcb240f8c113427f5b49b19658b5c60168e88267693/",
false,
},
{
// 'directory' is a directory on mount
// /var/lib/docker/devicemapper/test/shared that is shared, but one
// of its parent is private.
"nested-shared",
"/var/lib/docker/devicemapper/test/shared/my/test/directory",
true,
},
{
// /var/lib/foo is a mount point and it's shared
"shared-mount",
"/var/lib/foo",
true,
},
{
// /var/lib/bar is a mount point and it's private
"private-mount",
"/var/lib/bar",
false,
},
}
for _, test := range tests {
ret, err := isShared(test.path, filename)
if err != nil {
t.Errorf("test %s got unexpected error: %v", test.name, err)
}
if ret != test.expectedResult {
t.Errorf("test %s expected %v, got %v", test.name, test.expectedResult, ret)
}
}
}
func TestIsSharedFailure(t *testing.T) {
errorTests := []struct {
name string
content string
}{
{
// the first line is too short
name: "too-short-line",
content: `62 0 253:0 / / rw,relatime
76 62 8:1 / /boot rw,relatime shared:29 - ext4 /dev/sda1 rw,seclabel,data=ordered
78 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
80 62 0:42 / /var/lib/nfs/rpc_pipefs rw,relatime shared:31 - rpc_pipefs sunrpc rw
227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
224 62 253:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
`,
},
{
// there is no root mount
name: "no-root-mount",
content: `76 62 8:1 / /boot rw,relatime shared:29 - ext4 /dev/sda1 rw,seclabel,data=ordered
78 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
80 62 0:42 / /var/lib/nfs/rpc_pipefs rw,relatime shared:31 - rpc_pipefs sunrpc rw
227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
224 62 253:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
`,
},
}
for _, test := range errorTests {
tempDir, filename, err := writeFile(test.content)
if err != nil {
t.Fatalf("cannot create temporary file: %v", err)
}
defer os.RemoveAll(tempDir)
_, err = isShared("/", filename)
if err == nil {
t.Errorf("test %q: expected error, got none", test.name)
}
}
}
func TestGetSELinuxSupport(t *testing.T) {
info :=
`62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
78 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
83 63 0:44 / /var/lib/bar rw,relatime - tmpfs tmpfs rw
227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
150 23 1:58 / /media/nfs_vol rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
`
tempDir, filename, err := writeFile(info)
if err != nil {
t.Fatalf("cannot create temporary file: %v", err)
}
defer os.RemoveAll(tempDir)
tests := []struct {
name string
mountPoint string
expectedResult bool
}{
{
"ext4 on /",
"/",
true,
},
{
"tmpfs on /var/lib/bar",
"/var/lib/bar",
false,
},
{
"nfsv4",
"/media/nfs_vol",
false,
},
}
for _, test := range tests {
out, err := GetSELinux(test.mountPoint, filename)
if err != nil {
t.Errorf("Test %s failed with error: %s", test.name, err)
}
if test.expectedResult != out {
t.Errorf("Test %s failed: expected %v, got %v", test.name, test.expectedResult, out)
}
}
}
func createSocketFile(socketDir string) (string, error) {
testSocketFile := filepath.Join(socketDir, "mt.sock")
// Switch to volume path and create the socket file
// socket file can not have length of more than 108 character
// and hence we must use relative path
oldDir, _ := os.Getwd()
err := os.Chdir(socketDir)
if err != nil {
return "", err
}
defer func() {
os.Chdir(oldDir)
}()
_, socketCreateError := net.Listen("unix", "mt.sock")
return testSocketFile, socketCreateError
}
func TestGetFileType(t *testing.T) {
hu := NewHostUtil()
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
},
},
{
"Socket Test",
FileTypeSocket,
func() (string, string, error) {
tempDir, err := ioutil.TempDir("", "test-get-filetype-")
if err != nil {
return "", "", err
}
tempSocketFile, err := createSocketFile(tempDir)
return tempSocketFile, tempDir, err
},
},
{
"Block Device Test",
FileTypeBlockDev,
func() (string, string, error) {
tempDir, err := ioutil.TempDir("", "test-get-filetype-")
if err != nil {
return "", "", err
}
tempBlockFile := filepath.Join(tempDir, "test_blk_dev")
outputBytes, err := exec.New().Command("mknod", tempBlockFile, "b", "89", "1").CombinedOutput()
if err != nil {
err = fmt.Errorf("%v: %s ", err, outputBytes)
}
return tempBlockFile, tempDir, err
},
},
{
"Character Device Test",
FileTypeCharDev,
func() (string, string, error) {
tempDir, err := ioutil.TempDir("", "test-get-filetype-")
if err != nil {
return "", "", err
}
tempCharFile := filepath.Join(tempDir, "test_char_dev")
outputBytes, err := exec.New().Command("mknod", tempCharFile, "c", "89", "1").CombinedOutput()
if err != nil {
err = fmt.Errorf("%v: %s ", err, outputBytes)
}
return tempCharFile, tempDir, err
},
},
}
for idx, tc := range testCase {
path, cleanUpPath, err := tc.setUp()
if err != nil {
// Locally passed, but upstream CI is not friendly to create such device files
// Leave "Operation not permitted" out, which can be covered in an e2e test
if isOperationNotPermittedError(err) {
continue
}
t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err)
}
if len(cleanUpPath) > 0 {
defer os.RemoveAll(cleanUpPath)
}
fileType, err := hu.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 isOperationNotPermittedError(err error) bool {
if strings.Contains(err.Error(), "Operation not permitted") {
return true
}
return false
}

View File

@ -0,0 +1,85 @@
// +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 (
"os"
)
type hostUtil struct{}
// NewHostUtil returns a struct that implements the HostUtils interface on
// unsupported platforms
func NewHostUtil() HostUtils {
return &hostUtil{}
}
// DeviceOpened determines if the device is in use elsewhere
func (hu *hostUtil) DeviceOpened(pathname string) (bool, error) {
return false, errUnsupported
}
// PathIsDevice determines if a path is a device.
func (hu *hostUtil) PathIsDevice(pathname string) (bool, error) {
return true, errUnsupported
}
// GetDeviceNameFromMount finds the device name by checking the mount path
// to get the global mount path within its plugin directory
func (hu *hostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return "", errUnsupported
}
func (hu *hostUtil) MakeRShared(path string) error {
return errUnsupported
}
func (hu *hostUtil) GetFileType(pathname string) (FileType, error) {
return FileType("fake"), errUnsupported
}
func (hu *hostUtil) MakeFile(pathname string) error {
return errUnsupported
}
func (hu *hostUtil) MakeDir(pathname string) error {
return errUnsupported
}
func (hu *hostUtil) PathExists(pathname string) (bool, error) {
return true, errUnsupported
}
// EvalHostSymlinks returns the path name after evaluating symlinks
func (hu *hostUtil) EvalHostSymlinks(pathname string) (string, error) {
return "", errUnsupported
}
// GetOwner returns the integer ID for the user and group of the given path
func (hu *hostUtil) GetOwner(pathname string) (int64, int64, error) {
return -1, -1, errUnsupported
}
func (hu *hostUtil) GetSELinuxSupport(pathname string) (bool, error) {
return false, errUnsupported
}
func (hu *hostUtil) GetMode(pathname string) (os.FileMode, error) {
return 0, errUnsupported
}

View File

@ -0,0 +1,145 @@
// +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"
"path"
"path/filepath"
"strings"
"k8s.io/klog"
utilpath "k8s.io/utils/path"
)
type hostUtil struct{}
// NewHostUtil returns a struct that implements the HostUtils interface on
// windows platforms
func NewHostUtil() HostUtils {
return &hostUtil{}
}
// GetDeviceNameFromMount given a mnt point, find the device
func (hu *hostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return getDeviceNameFromMount(mounter, mountPath, pluginMountDir)
}
// getDeviceNameFromMount find the device(drive) name in which
// the mount path reference should match the given plugin mount directory. In case no mount path reference
// matches, returns the volume name taken from its given mountPath
func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir 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(pluginMountDir)
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 (hu *hostUtil) DeviceOpened(pathname string) (bool, error) {
return false, nil
}
// PathIsDevice determines if a path is a device.
func (hu *hostUtil) 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 (hu *hostUtil) MakeRShared(path string) error {
return nil
}
// GetFileType checks for sockets/block/character devices
func (hu *(hostUtil)) GetFileType(pathname string) (FileType, error) {
return getFileType(pathname)
}
// MakeFile creates a new directory
func (hu *hostUtil) 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 (hu *hostUtil) 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
}
// PathExists checks whether the path exists
func (hu *hostUtil) PathExists(pathname string) (bool, error) {
return utilpath.Exists(utilpath.CheckFollowSymlink, pathname)
}
// EvalHostSymlinks returns the path name after evaluating symlinks
func (hu *hostUtil) EvalHostSymlinks(pathname string) (string, error) {
return filepath.EvalSymlinks(pathname)
}
// GetOwner returns the integer ID for the user and group of the given path
// Note that on windows, it always returns 0. We actually don't set Group on
// windows platform, see SetVolumeOwnership implementation.
func (hu *hostUtil) GetOwner(pathname string) (int64, int64, error) {
return -1, -1, nil
}
func (hu *hostUtil) GetSELinuxSupport(pathname string) (bool, error) {
// Windows does not support SELinux.
return false, nil
}
func (hu *hostUtil) GetMode(pathname string) (os.FileMode, error) {
info, err := os.Stat(pathname)
if err != nil {
return 0, err
}
return info.Mode(), nil
}

View File

@ -0,0 +1,74 @@
// +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 (
"io/ioutil"
"os"
"testing"
)
func TestGetFileType(t *testing.T) {
hu := NewHostUtil()
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 := hu.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)
}
}
}

View File

@ -20,31 +20,14 @@ limitations under the License.
package mount package mount
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
) )
// FileType enumerates the known set of possible file types.
type FileType string
const ( const (
// Default mount command if mounter path is not specified. // Default mount command if mounter path is not specified.
defaultMountCommand = "mount" defaultMountCommand = "mount"
// FileTypeBlockDev defines a constant for the block device FileType.
FileTypeBlockDev FileType = "BlockDevice"
// FileTypeCharDev defines a constant for the character device FileType.
FileTypeCharDev FileType = "CharDevice"
// FileTypeDirectory defines a constant for the directory FileType.
FileTypeDirectory FileType = "Directory"
// FileTypeFile defines a constant for the file FileType.
FileTypeFile FileType = "File"
// FileTypeSocket defines a constant for the socket FileType.
FileTypeSocket FileType = "Socket"
// FileTypeUnknown defines a constant for an unknown FileType.
FileTypeUnknown FileType = ""
) )
// Interface defines the set of methods to allow for mount operations on a system. // Interface defines the set of methods to allow for mount operations on a system.
@ -72,39 +55,6 @@ type Interface interface {
GetMountRefs(pathname string) ([]string, error) GetMountRefs(pathname string) ([]string, error)
} }
// HostUtils defines the set of methods for interacting with paths on a host.
type HostUtils interface {
// DeviceOpened determines if the device (e.g. /dev/sdc) is in use elsewhere
// on the system, i.e. still mounted.
DeviceOpened(pathname string) (bool, error)
// PathIsDevice determines if a path is a device.
PathIsDevice(pathname string) (bool, error)
// GetDeviceNameFromMount finds the device name by checking the mount path
// to get the global mount path within its plugin directory.
GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error)
// MakeRShared checks that given path is on a mount with 'rshared' mount
// propagation. If not, it bind-mounts the path as rshared.
MakeRShared(path string) error
// GetFileType checks for file/directory/socket/block/character devices.
GetFileType(pathname string) (FileType, error)
// MakeFile creates an empty file.
MakeFile(pathname string) error
// MakeDir creates a new directory.
MakeDir(pathname string) error
// PathExists tests if the given path already exists
// Error is returned on any other error than "file not found".
PathExists(pathname string) (bool, error)
// EvalHostSymlinks returns the path name after evaluating symlinks.
EvalHostSymlinks(pathname string) (string, error)
// GetOwner returns the integer ID for the user and group of the given path
GetOwner(pathname string) (int64, int64, error)
// GetSELinuxSupport returns true if given path is on a mount that supports
// SELinux.
GetSELinuxSupport(pathname string) (bool, error)
// GetMode returns permissions of the path.
GetMode(pathname string) (os.FileMode, error)
}
// Exec is an interface for executing commands on systems. // Exec is an interface for executing commands on systems.
type Exec interface { type Exec interface {
// Run executes a command and returns its stdout + stderr combined in one // Run executes a command and returns its stdout + stderr combined in one
@ -116,10 +66,6 @@ type Exec interface {
// the mount interface. // the mount interface.
var _ Interface = &Mounter{} var _ Interface = &Mounter{}
// Compile-time check to ensure all HostUtil implementations satisfy
// the HostUtils Interface.
var _ HostUtils = &hostUtil{}
// MountPoint represents a single line in /proc/mounts or /etc/fstab. // MountPoint represents a single line in /proc/mounts or /etc/fstab.
type MountPoint struct { type MountPoint struct {
Device string Device string
@ -320,37 +266,3 @@ func StartsWithBackstep(rel string) bool {
// normalize to / and check for ../ // normalize to / and check for ../
return rel == ".." || strings.HasPrefix(filepath.ToSlash(rel), "../") return rel == ".." || strings.HasPrefix(filepath.ToSlash(rel), "../")
} }
// getFileType checks for file/directory/socket and block/character devices.
func getFileType(pathname string) (FileType, error) {
var pathType FileType
info, err := os.Stat(pathname)
if os.IsNotExist(err) {
return pathType, fmt.Errorf("path %q does not exist", pathname)
}
// err in call to os.Stat
if err != nil {
return pathType, err
}
// checks whether the mode is the target mode.
isSpecificMode := func(mode, targetMode os.FileMode) bool {
return mode&targetMode == targetMode
}
mode := info.Mode()
if mode.IsDir() {
return FileTypeDirectory, nil
} else if mode.IsRegular() {
return FileTypeFile, nil
} else if isSpecificMode(mode, os.ModeSocket) {
return FileTypeSocket, nil
} else if isSpecificMode(mode, os.ModeDevice) {
if isSpecificMode(mode, os.ModeCharDevice) {
return FileTypeCharDev, nil
}
return FileTypeBlockDev, nil
}
return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device")
}

View File

@ -19,8 +19,20 @@ limitations under the License.
package mount package mount
import ( import (
"fmt"
"os" "os"
"strconv"
"strings"
"syscall" "syscall"
utilio "k8s.io/utils/io"
)
const (
// At least number of fields per line in /proc/<pid>/mountinfo.
expectedAtLeastNumFieldsPerMountInfo = 10
// How many times to retry for a consistent read of /proc/mounts.
maxListTries = 3
) )
// IsCorruptedMnt return true if err is about corrupted mount point // IsCorruptedMnt return true if err is about corrupted mount point
@ -42,3 +54,80 @@ func IsCorruptedMnt(err error) bool {
return underlyingError == syscall.ENOTCONN || underlyingError == syscall.ESTALE || underlyingError == syscall.EIO || underlyingError == syscall.EACCES return underlyingError == syscall.ENOTCONN || underlyingError == syscall.ESTALE || underlyingError == syscall.EIO || underlyingError == syscall.EACCES
} }
// This represents a single line in /proc/<pid>/mountinfo.
type mountInfo struct {
// Unique ID for the mount (maybe reused after umount).
id int
// The ID of the parent mount (or of self for the root of this mount namespace's mount tree).
parentID int
// The value of `st_dev` for files on this filesystem.
majorMinor string
// The pathname of the directory in the filesystem which forms the root of this mount.
root string
// Mount source, filesystem-specific information. e.g. device, tmpfs name.
source string
// Mount point, the pathname of the mount point.
mountPoint string
// Optional fieds, zero or more fields of the form "tag[:value]".
optionalFields []string
// The filesystem type in the form "type[.subtype]".
fsType string
// Per-mount options.
mountOptions []string
// Per-superblock options.
superOptions []string
}
// parseMountInfo parses /proc/xxx/mountinfo.
func parseMountInfo(filename string) ([]mountInfo, error) {
content, err := utilio.ConsistentRead(filename, maxListTries)
if err != nil {
return []mountInfo{}, err
}
contentStr := string(content)
infos := []mountInfo{}
for _, line := range strings.Split(contentStr, "\n") {
if line == "" {
// the last split() item is empty string following the last \n
continue
}
// See `man proc` for authoritative description of format of the file.
fields := strings.Fields(line)
if len(fields) < expectedAtLeastNumFieldsPerMountInfo {
return nil, fmt.Errorf("wrong number of fields in (expected at least %d, got %d): %s", expectedAtLeastNumFieldsPerMountInfo, len(fields), line)
}
id, err := strconv.Atoi(fields[0])
if err != nil {
return nil, err
}
parentID, err := strconv.Atoi(fields[1])
if err != nil {
return nil, err
}
info := mountInfo{
id: id,
parentID: parentID,
majorMinor: fields[2],
root: fields[3],
mountPoint: fields[4],
mountOptions: strings.Split(fields[5], ","),
}
// All fields until "-" are "optional fields".
i := 6
for ; i < len(fields) && fields[i] != "-"; i++ {
info.optionalFields = append(info.optionalFields, fields[i])
}
// Parse the rest 3 fields.
i++
if len(fields)-i < 3 {
return nil, fmt.Errorf("expect 3 fields in %s, got %d", line, len(fields)-i)
}
info.fsType = fields[i]
info.source = fields[i+1]
info.superOptions = strings.Split(fields[i+2], ",")
infos = append(infos, info)
}
return infos, nil
}

View File

@ -0,0 +1,215 @@
// +build !windows
/*
Copyright 2019 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"
"path/filepath"
"reflect"
"testing"
)
func writeFile(content string) (string, string, error) {
tempDir, err := ioutil.TempDir("", "mounter_shared_test")
if err != nil {
return "", "", err
}
filename := filepath.Join(tempDir, "mountinfo")
err = ioutil.WriteFile(filename, []byte(content), 0600)
if err != nil {
os.RemoveAll(tempDir)
return "", "", err
}
return tempDir, filename, nil
}
func TestParseMountInfo(t *testing.T) {
info :=
`62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
78 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
80 62 0:42 / /var/lib/nfs/rpc_pipefs rw,relatime shared:31 - rpc_pipefs sunrpc rw
82 62 0:43 / /var/lib/foo rw,relatime shared:32 - tmpfs tmpfs rw
83 63 0:44 / /var/lib/bar rw,relatime - tmpfs tmpfs rw
227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
224 62 253:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
76 17 8:1 / /mnt/stateful_partition rw,nosuid,nodev,noexec,relatime - ext4 /dev/sda1 rw,commit=30,data=ordered
80 17 8:1 /var /var rw,nosuid,nodev,noexec,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
189 80 8:1 /var/lib/kubelet /var/lib/kubelet rw,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
818 77 8:40 / /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdc rw,data=ordered
819 78 8:48 / /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
900 100 8:48 /dir1 /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volume-subpaths/vol1/subpath1/0 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
901 101 8:1 /dir1 /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volume-subpaths/vol1/subpath1/1 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
902 102 8:1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volumes/kubernetes.io~empty-dir/vol1/dir1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volume-subpaths/vol1/subpath1/0 rw,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
903 103 8:1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volumes/kubernetes.io~empty-dir/vol2/dir1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volume-subpaths/vol1/subpath1/1 rw,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
178 25 253:0 /etc/bar /var/lib/kubelet/pods/12345/volume-subpaths/vol1/subpath1/0 rw,relatime shared:1 - ext4 /dev/sdb2 rw,errors=remount-ro,data=ordered
698 186 0:41 /tmp1/dir1 /var/lib/kubelet/pods/41135147-e697-11e7-9342-42010a800002/volume-subpaths/vol1/subpath1/0 rw shared:26 - tmpfs tmpfs rw
918 77 8:50 / /var/lib/kubelet/pods/2345/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdc rw,data=ordered
919 78 8:58 / /var/lib/kubelet/pods/2345/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
920 100 8:50 /dir1 /var/lib/kubelet/pods/2345/volume-subpaths/vol1/subpath1/0 rw,relatime shared:290 - ext4 /dev/sdc rw,data=ordered
150 23 1:58 / /media/nfs_vol rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
151 24 1:58 / /media/nfs_bindmount rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs/foo rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
134 23 0:58 / /var/lib/kubelet/pods/43219158-e5e1-11e7-a392-0e858b8eaf40/volumes/kubernetes.io~nfs/nfs1 rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
187 23 0:58 / /var/lib/kubelet/pods/1fc5ea21-eff4-11e7-ac80-0e858b8eaf40/volumes/kubernetes.io~nfs/nfs2 rw,relatime shared:96 - nfs4 172.18.4.223:/srv/nfs2 rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
188 24 0:58 / /var/lib/kubelet/pods/43219158-e5e1-11e7-a392-0e858b8eaf40/volume-subpaths/nfs1/subpath1/0 rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs/foo rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
347 60 0:71 / /var/lib/kubelet/pods/13195d46-f9fa-11e7-bbf1-5254007a695a/volumes/kubernetes.io~nfs/vol2 rw,relatime shared:170 - nfs 172.17.0.3:/exports/2 rw,vers=3,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=172.17.0.3,mountvers=3,mountport=20048,mountproto=udp,local_lock=none,addr=172.17.0.3
222 24 253:0 /tmp/src /mnt/dst rw,relatime shared:1 - ext4 /dev/mapper/vagrant--vg-root rw,errors=remount-ro,data=ordered
28 18 0:24 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:9 - tmpfs tmpfs ro,mode=755
29 28 0:25 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
31 28 0:27 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:13 - cgroup cgroup rw,cpuset
32 28 0:28 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:14 - cgroup cgroup rw,cpu,cpuacct
33 28 0:29 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,freezer
34 28 0:30 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,net_cls,net_prio
35 28 0:31 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,pids
36 28 0:32 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,devices
37 28 0:33 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,hugetlb
38 28 0:34 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:20 - cgroup cgroup rw,blkio
39 28 0:35 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:21 - cgroup cgroup rw,memory
40 28 0:36 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:22 - cgroup cgroup rw,perf_event
`
tempDir, filename, err := writeFile(info)
if err != nil {
t.Fatalf("cannot create temporary file: %v", err)
}
defer os.RemoveAll(tempDir)
tests := []struct {
name string
id int
expectedInfo mountInfo
}{
{
"simple bind mount",
189,
mountInfo{
id: 189,
parentID: 80,
majorMinor: "8:1",
root: "/var/lib/kubelet",
source: "/dev/sda1",
mountPoint: "/var/lib/kubelet",
optionalFields: []string{"shared:30"},
fsType: "ext4",
mountOptions: []string{"rw", "relatime"},
superOptions: []string{"rw", "commit=30", "data=ordered"},
},
},
{
"bind mount a directory",
222,
mountInfo{
id: 222,
parentID: 24,
majorMinor: "253:0",
root: "/tmp/src",
source: "/dev/mapper/vagrant--vg-root",
mountPoint: "/mnt/dst",
optionalFields: []string{"shared:1"},
fsType: "ext4",
mountOptions: []string{"rw", "relatime"},
superOptions: []string{"rw", "errors=remount-ro", "data=ordered"},
},
},
{
"more than one optional fields",
224,
mountInfo{
id: 224,
parentID: 62,
majorMinor: "253:0",
root: "/var/lib/docker/devicemapper/test/shared",
source: "/dev/mapper/ssd-root",
mountPoint: "/var/lib/docker/devicemapper/test/shared",
optionalFields: []string{"master:1", "shared:44"},
fsType: "ext4",
mountOptions: []string{"rw", "relatime"},
superOptions: []string{"rw", "seclabel", "data=ordered"},
},
},
{
"cgroup-mountpoint",
28,
mountInfo{
id: 28,
parentID: 18,
majorMinor: "0:24",
root: "/",
source: "tmpfs",
mountPoint: "/sys/fs/cgroup",
optionalFields: []string{"shared:9"},
fsType: "tmpfs",
mountOptions: []string{"ro", "nosuid", "nodev", "noexec"},
superOptions: []string{"ro", "mode=755"},
},
},
{
"cgroup-subsystem-systemd-mountpoint",
29,
mountInfo{
id: 29,
parentID: 28,
majorMinor: "0:25",
root: "/",
source: "cgroup",
mountPoint: "/sys/fs/cgroup/systemd",
optionalFields: []string{"shared:10"},
fsType: "cgroup",
mountOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
superOptions: []string{"rw", "xattr", "release_agent=/lib/systemd/systemd-cgroups-agent", "name=systemd"},
},
},
{
"cgroup-subsystem-cpuset-mountpoint",
31,
mountInfo{
id: 31,
parentID: 28,
majorMinor: "0:27",
root: "/",
source: "cgroup",
mountPoint: "/sys/fs/cgroup/cpuset",
optionalFields: []string{"shared:13"},
fsType: "cgroup",
mountOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
superOptions: []string{"rw", "cpuset"},
},
},
}
infos, err := parseMountInfo(filename)
if err != nil {
t.Fatalf("Cannot parse %s: %s", filename, err)
}
for _, test := range tests {
found := false
for _, info := range infos {
if info.id == test.id {
found = true
if !reflect.DeepEqual(info, test.expectedInfo) {
t.Errorf("Test case %q:\n expected: %+v\n got: %+v", test.name, test.expectedInfo, info)
}
break
}
}
if !found {
t.Errorf("Test case %q: mountPoint %d not found", test.name, test.id)
}
}
}

View File

@ -19,7 +19,10 @@ limitations under the License.
package mount package mount
import ( import (
"fmt"
"os" "os"
"strconv"
"strings"
"syscall" "syscall"
"k8s.io/klog" "k8s.io/klog"
@ -66,3 +69,25 @@ func IsCorruptedMnt(err error) bool {
return false return false
} }
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
}

View File

@ -0,0 +1,65 @@
// +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 (
"testing"
)
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)
}
}

View File

@ -23,26 +23,19 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"golang.org/x/sys/unix"
"k8s.io/klog" "k8s.io/klog"
utilexec "k8s.io/utils/exec" utilexec "k8s.io/utils/exec"
utilio "k8s.io/utils/io" utilio "k8s.io/utils/io"
utilpath "k8s.io/utils/path"
) )
const ( const (
// How many times to retry for a consistent read of /proc/mounts.
maxListTries = 3
// Number of fields per line in /proc/mounts as per the fstab man page. // Number of fields per line in /proc/mounts as per the fstab man page.
expectedNumFieldsPerLine = 6 expectedNumFieldsPerLine = 6
// At least number of fields per line in /proc/<pid>/mountinfo.
expectedAtLeastNumFieldsPerMountInfo = 10
// Location of the mount file to use // Location of the mount file to use
procMountsPath = "/proc/mounts" procMountsPath = "/proc/mounts"
// Location of the mountinfo file // Location of the mountinfo file
@ -452,345 +445,6 @@ func parseProcMounts(content []byte) ([]MountPoint, error) {
return out, nil return out, nil
} }
type hostUtil struct {
}
// NewHostUtil returns a struct that implements the HostUtils interface on
// linux platforms
func NewHostUtil() HostUtils {
return &hostUtil{}
}
// DeviceOpened checks if block device in use by calling Open with O_EXCL flag.
// If pathname is not a device, log and return false with nil error.
// If open returns errno EBUSY, return true with nil error.
// If open returns nil, return false with nil error.
// Otherwise, return false with error
func (hu *hostUtil) DeviceOpened(pathname string) (bool, error) {
return ExclusiveOpenFailsOnDevice(pathname)
}
// PathIsDevice uses FileInfo returned from os.Stat to check if path refers
// to a device.
func (hu *hostUtil) PathIsDevice(pathname string) (bool, error) {
pathType, err := hu.GetFileType(pathname)
isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev
return isDevice, err
}
// ExclusiveOpenFailsOnDevice is shared with NsEnterMounter
func ExclusiveOpenFailsOnDevice(pathname string) (bool, error) {
var isDevice bool
finfo, err := os.Stat(pathname)
if os.IsNotExist(err) {
isDevice = false
}
// err in call to os.Stat
if err != nil {
return false, fmt.Errorf(
"PathIsDevice failed for path %q: %v",
pathname,
err)
}
// path refers to a device
if finfo.Mode()&os.ModeDevice != 0 {
isDevice = true
}
if !isDevice {
klog.Errorf("Path %q is not referring to a device.", pathname)
return false, nil
}
fd, errno := unix.Open(pathname, unix.O_RDONLY|unix.O_EXCL|unix.O_CLOEXEC, 0)
// If the device is in use, open will return an invalid fd.
// When this happens, it is expected that Close will fail and throw an error.
defer unix.Close(fd)
if errno == nil {
// device not in use
return false, nil
} else if errno == unix.EBUSY {
// device is in use
return true, nil
}
// error during call to Open
return false, errno
}
//GetDeviceNameFromMount: given a mount point, find the device name from its global mount point
func (hu *hostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir)
}
func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir)
}
// GetDeviceNameFromMountLinux find the device name from /proc/mounts in which
// the mount path reference should match the given plugin mount directory. In case no mount path reference
// matches, returns the volume name taken from its given mountPath
// This implementation is shared with NsEnterMounter
func GetDeviceNameFromMountLinux(mounter Interface, mountPath, pluginMountDir 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 {
klog.V(4).Infof("Directory %s is not mounted", mountPath)
return "", fmt.Errorf("directory %s is not mounted", mountPath)
}
for _, ref := range refs {
if strings.HasPrefix(ref, pluginMountDir) {
volumeID, err := filepath.Rel(pluginMountDir, 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
}
func (hu *hostUtil) MakeRShared(path string) error {
return DoMakeRShared(path, procMountInfoPath)
}
func (hu *hostUtil) GetFileType(pathname string) (FileType, error) {
return getFileType(pathname)
}
func (hu *hostUtil) MakeDir(pathname string) error {
err := os.MkdirAll(pathname, os.FileMode(0755))
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
}
func (hu *hostUtil) 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
}
func (hu *hostUtil) PathExists(pathname string) (bool, error) {
return utilpath.Exists(utilpath.CheckFollowSymlink, pathname)
}
func (hu *hostUtil) EvalHostSymlinks(pathname string) (string, error) {
return filepath.EvalSymlinks(pathname)
}
// isShared returns true, if given path is on a mount point that has shared
// mount propagation.
func isShared(mount string, mountInfoPath string) (bool, error) {
info, err := findMountInfo(mount, mountInfoPath)
if err != nil {
return false, err
}
// parse optional parameters
for _, opt := range info.optionalFields {
if strings.HasPrefix(opt, "shared:") {
return true, nil
}
}
return false, nil
}
// This represents a single line in /proc/<pid>/mountinfo.
type mountInfo struct {
// Unique ID for the mount (maybe reused after umount).
id int
// The ID of the parent mount (or of self for the root of this mount namespace's mount tree).
parentID int
// The value of `st_dev` for files on this filesystem.
majorMinor string
// The pathname of the directory in the filesystem which forms the root of this mount.
root string
// Mount source, filesystem-specific information. e.g. device, tmpfs name.
source string
// Mount point, the pathname of the mount point.
mountPoint string
// Optional fieds, zero or more fields of the form "tag[:value]".
optionalFields []string
// The filesystem type in the form "type[.subtype]".
fsType string
// Per-mount options.
mountOptions []string
// Per-superblock options.
superOptions []string
}
// parseMountInfo parses /proc/xxx/mountinfo.
func parseMountInfo(filename string) ([]mountInfo, error) {
content, err := utilio.ConsistentRead(filename, maxListTries)
if err != nil {
return []mountInfo{}, err
}
contentStr := string(content)
infos := []mountInfo{}
for _, line := range strings.Split(contentStr, "\n") {
if line == "" {
// the last split() item is empty string following the last \n
continue
}
// See `man proc` for authoritative description of format of the file.
fields := strings.Fields(line)
if len(fields) < expectedAtLeastNumFieldsPerMountInfo {
return nil, fmt.Errorf("wrong number of fields in (expected at least %d, got %d): %s", expectedAtLeastNumFieldsPerMountInfo, len(fields), line)
}
id, err := strconv.Atoi(fields[0])
if err != nil {
return nil, err
}
parentID, err := strconv.Atoi(fields[1])
if err != nil {
return nil, err
}
info := mountInfo{
id: id,
parentID: parentID,
majorMinor: fields[2],
root: fields[3],
mountPoint: fields[4],
mountOptions: strings.Split(fields[5], ","),
}
// All fields until "-" are "optional fields".
i := 6
for ; i < len(fields) && fields[i] != "-"; i++ {
info.optionalFields = append(info.optionalFields, fields[i])
}
// Parse the rest 3 fields.
i++
if len(fields)-i < 3 {
return nil, fmt.Errorf("expect 3 fields in %s, got %d", line, len(fields)-i)
}
info.fsType = fields[i]
info.source = fields[i+1]
info.superOptions = strings.Split(fields[i+2], ",")
infos = append(infos, info)
}
return infos, nil
}
func findMountInfo(path, mountInfoPath string) (mountInfo, error) {
infos, err := parseMountInfo(mountInfoPath)
if err != nil {
return mountInfo{}, err
}
// process /proc/xxx/mountinfo in backward order and find the first mount
// point that is prefix of 'path' - that's the mount where path resides
var info *mountInfo
for i := len(infos) - 1; i >= 0; i-- {
if PathWithinBase(path, infos[i].mountPoint) {
info = &infos[i]
break
}
}
if info == nil {
return mountInfo{}, fmt.Errorf("cannot find mount point for %q", path)
}
return *info, nil
}
// DoMakeRShared is common implementation of MakeRShared on Linux. It checks if
// path is shared and bind-mounts it as rshared if needed. mountCmd and
// mountArgs are expected to contain mount-like command, DoMakeRShared will add
// '--bind <path> <path>' and '--make-rshared <path>' to mountArgs.
func DoMakeRShared(path string, mountInfoFilename string) error {
shared, err := isShared(path, mountInfoFilename)
if err != nil {
return err
}
if shared {
klog.V(4).Infof("Directory %s is already on a shared mount", path)
return nil
}
klog.V(2).Infof("Bind-mounting %q with shared mount propagation", path)
// mount --bind /var/lib/kubelet /var/lib/kubelet
if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_BIND, "" /*data*/); err != nil {
return fmt.Errorf("failed to bind-mount %s: %v", path, err)
}
// mount --make-rshared /var/lib/kubelet
if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_SHARED|syscall.MS_REC, "" /*data*/); err != nil {
return fmt.Errorf("failed to make %s rshared: %v", path, err)
}
return nil
}
// GetSELinux is common implementation of GetSELinuxSupport on Linux.
func GetSELinux(path string, mountInfoFilename string) (bool, error) {
info, err := findMountInfo(path, mountInfoFilename)
if err != nil {
return false, err
}
// "seclabel" can be both in mount options and super options.
for _, opt := range info.superOptions {
if opt == "seclabel" {
return true, nil
}
}
for _, opt := range info.mountOptions {
if opt == "seclabel" {
return true, nil
}
}
return false, nil
}
func (hu *hostUtil) GetSELinuxSupport(pathname string) (bool, error) {
return GetSELinux(pathname, procMountInfoPath)
}
// GetOwner returns the integer ID for the user and group of the given path
func (hu *hostUtil) GetOwner(pathname string) (int64, int64, error) {
realpath, err := filepath.EvalSymlinks(pathname)
if err != nil {
return -1, -1, err
}
return GetOwnerLinux(realpath)
}
func (hu *hostUtil) GetMode(pathname string) (os.FileMode, error) {
return GetModeLinux(pathname)
}
// GetOwnerLinux is shared between Linux and NsEnterMounter
// pathname must already be evaluated for symlinks
func GetOwnerLinux(pathname string) (int64, int64, error) {
info, err := os.Stat(pathname)
if err != nil {
return -1, -1, err
}
stat := info.Sys().(*syscall.Stat_t)
return int64(stat.Uid), int64(stat.Gid), nil
}
// GetModeLinux is shared between Linux and NsEnterMounter
func GetModeLinux(pathname string) (os.FileMode, error) {
info, err := os.Stat(pathname)
if err != nil {
return 0, err
}
return info.Mode(), nil
}
// SearchMountPoints finds all mount references to the source, returns a list of // SearchMountPoints finds all mount references to the source, returns a list of
// mountpoints. // mountpoints.
// This function assumes source cannot be device. // This function assumes source cannot be device.

View File

@ -19,16 +19,10 @@ limitations under the License.
package mount package mount
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"path/filepath"
"reflect" "reflect"
"strings"
"testing" "testing"
"k8s.io/utils/exec"
) )
func TestReadProcMountsFrom(t *testing.T) { func TestReadProcMountsFrom(t *testing.T) {
@ -209,129 +203,6 @@ func TestGetMountRefsByDev(t *testing.T) {
} }
} }
func writeFile(content string) (string, string, error) {
tempDir, err := ioutil.TempDir("", "mounter_shared_test")
if err != nil {
return "", "", err
}
filename := filepath.Join(tempDir, "mountinfo")
err = ioutil.WriteFile(filename, []byte(content), 0600)
if err != nil {
os.RemoveAll(tempDir)
return "", "", err
}
return tempDir, filename, nil
}
func TestIsSharedSuccess(t *testing.T) {
successMountInfo :=
`62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
76 62 8:1 / /boot rw,relatime shared:29 - ext4 /dev/sda1 rw,seclabel,data=ordered
78 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
80 62 0:42 / /var/lib/nfs/rpc_pipefs rw,relatime shared:31 - rpc_pipefs sunrpc rw
82 62 0:43 / /var/lib/foo rw,relatime shared:32 - tmpfs tmpfs rw
83 63 0:44 / /var/lib/bar rw,relatime - tmpfs tmpfs rw
227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
224 62 253:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
`
tempDir, filename, err := writeFile(successMountInfo)
if err != nil {
t.Fatalf("cannot create temporary file: %v", err)
}
defer os.RemoveAll(tempDir)
tests := []struct {
name string
path string
expectedResult bool
}{
{
// /var/lib/kubelet is a directory on mount '/' that is shared
// This is the most common case.
"shared",
"/var/lib/kubelet",
true,
},
{
// 8a2a... is a directory on mount /var/lib/docker/devicemapper
// that is private.
"private",
"/var/lib/docker/devicemapper/mnt/8a2a5c19eefb06d6f851dfcb240f8c113427f5b49b19658b5c60168e88267693/",
false,
},
{
// 'directory' is a directory on mount
// /var/lib/docker/devicemapper/test/shared that is shared, but one
// of its parent is private.
"nested-shared",
"/var/lib/docker/devicemapper/test/shared/my/test/directory",
true,
},
{
// /var/lib/foo is a mount point and it's shared
"shared-mount",
"/var/lib/foo",
true,
},
{
// /var/lib/bar is a mount point and it's private
"private-mount",
"/var/lib/bar",
false,
},
}
for _, test := range tests {
ret, err := isShared(test.path, filename)
if err != nil {
t.Errorf("test %s got unexpected error: %v", test.name, err)
}
if ret != test.expectedResult {
t.Errorf("test %s expected %v, got %v", test.name, test.expectedResult, ret)
}
}
}
func TestIsSharedFailure(t *testing.T) {
errorTests := []struct {
name string
content string
}{
{
// the first line is too short
name: "too-short-line",
content: `62 0 253:0 / / rw,relatime
76 62 8:1 / /boot rw,relatime shared:29 - ext4 /dev/sda1 rw,seclabel,data=ordered
78 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
80 62 0:42 / /var/lib/nfs/rpc_pipefs rw,relatime shared:31 - rpc_pipefs sunrpc rw
227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
224 62 253:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
`,
},
{
// there is no root mount
name: "no-root-mount",
content: `76 62 8:1 / /boot rw,relatime shared:29 - ext4 /dev/sda1 rw,seclabel,data=ordered
78 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
80 62 0:42 / /var/lib/nfs/rpc_pipefs rw,relatime shared:31 - rpc_pipefs sunrpc rw
227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
224 62 253:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
`,
},
}
for _, test := range errorTests {
tempDir, filename, err := writeFile(test.content)
if err != nil {
t.Fatalf("cannot create temporary file: %v", err)
}
defer os.RemoveAll(tempDir)
_, err = isShared("/", filename)
if err == nil {
t.Errorf("test %q: expected error, got none", test.name)
}
}
}
func TestPathWithinBase(t *testing.T) { func TestPathWithinBase(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -420,353 +291,6 @@ func TestPathWithinBase(t *testing.T) {
} }
} }
func TestParseMountInfo(t *testing.T) {
info :=
`62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
78 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
80 62 0:42 / /var/lib/nfs/rpc_pipefs rw,relatime shared:31 - rpc_pipefs sunrpc rw
82 62 0:43 / /var/lib/foo rw,relatime shared:32 - tmpfs tmpfs rw
83 63 0:44 / /var/lib/bar rw,relatime - tmpfs tmpfs rw
227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
224 62 253:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
76 17 8:1 / /mnt/stateful_partition rw,nosuid,nodev,noexec,relatime - ext4 /dev/sda1 rw,commit=30,data=ordered
80 17 8:1 /var /var rw,nosuid,nodev,noexec,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
189 80 8:1 /var/lib/kubelet /var/lib/kubelet rw,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
818 77 8:40 / /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdc rw,data=ordered
819 78 8:48 / /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
900 100 8:48 /dir1 /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volume-subpaths/vol1/subpath1/0 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
901 101 8:1 /dir1 /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volume-subpaths/vol1/subpath1/1 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
902 102 8:1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volumes/kubernetes.io~empty-dir/vol1/dir1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volume-subpaths/vol1/subpath1/0 rw,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
903 103 8:1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volumes/kubernetes.io~empty-dir/vol2/dir1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volume-subpaths/vol1/subpath1/1 rw,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
178 25 253:0 /etc/bar /var/lib/kubelet/pods/12345/volume-subpaths/vol1/subpath1/0 rw,relatime shared:1 - ext4 /dev/sdb2 rw,errors=remount-ro,data=ordered
698 186 0:41 /tmp1/dir1 /var/lib/kubelet/pods/41135147-e697-11e7-9342-42010a800002/volume-subpaths/vol1/subpath1/0 rw shared:26 - tmpfs tmpfs rw
918 77 8:50 / /var/lib/kubelet/pods/2345/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdc rw,data=ordered
919 78 8:58 / /var/lib/kubelet/pods/2345/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
920 100 8:50 /dir1 /var/lib/kubelet/pods/2345/volume-subpaths/vol1/subpath1/0 rw,relatime shared:290 - ext4 /dev/sdc rw,data=ordered
150 23 1:58 / /media/nfs_vol rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
151 24 1:58 / /media/nfs_bindmount rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs/foo rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
134 23 0:58 / /var/lib/kubelet/pods/43219158-e5e1-11e7-a392-0e858b8eaf40/volumes/kubernetes.io~nfs/nfs1 rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
187 23 0:58 / /var/lib/kubelet/pods/1fc5ea21-eff4-11e7-ac80-0e858b8eaf40/volumes/kubernetes.io~nfs/nfs2 rw,relatime shared:96 - nfs4 172.18.4.223:/srv/nfs2 rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
188 24 0:58 / /var/lib/kubelet/pods/43219158-e5e1-11e7-a392-0e858b8eaf40/volume-subpaths/nfs1/subpath1/0 rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs/foo rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
347 60 0:71 / /var/lib/kubelet/pods/13195d46-f9fa-11e7-bbf1-5254007a695a/volumes/kubernetes.io~nfs/vol2 rw,relatime shared:170 - nfs 172.17.0.3:/exports/2 rw,vers=3,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=172.17.0.3,mountvers=3,mountport=20048,mountproto=udp,local_lock=none,addr=172.17.0.3
222 24 253:0 /tmp/src /mnt/dst rw,relatime shared:1 - ext4 /dev/mapper/vagrant--vg-root rw,errors=remount-ro,data=ordered
28 18 0:24 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:9 - tmpfs tmpfs ro,mode=755
29 28 0:25 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
31 28 0:27 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:13 - cgroup cgroup rw,cpuset
32 28 0:28 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:14 - cgroup cgroup rw,cpu,cpuacct
33 28 0:29 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,freezer
34 28 0:30 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,net_cls,net_prio
35 28 0:31 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,pids
36 28 0:32 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,devices
37 28 0:33 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,hugetlb
38 28 0:34 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:20 - cgroup cgroup rw,blkio
39 28 0:35 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:21 - cgroup cgroup rw,memory
40 28 0:36 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:22 - cgroup cgroup rw,perf_event
`
tempDir, filename, err := writeFile(info)
if err != nil {
t.Fatalf("cannot create temporary file: %v", err)
}
defer os.RemoveAll(tempDir)
tests := []struct {
name string
id int
expectedInfo mountInfo
}{
{
"simple bind mount",
189,
mountInfo{
id: 189,
parentID: 80,
majorMinor: "8:1",
root: "/var/lib/kubelet",
source: "/dev/sda1",
mountPoint: "/var/lib/kubelet",
optionalFields: []string{"shared:30"},
fsType: "ext4",
mountOptions: []string{"rw", "relatime"},
superOptions: []string{"rw", "commit=30", "data=ordered"},
},
},
{
"bind mount a directory",
222,
mountInfo{
id: 222,
parentID: 24,
majorMinor: "253:0",
root: "/tmp/src",
source: "/dev/mapper/vagrant--vg-root",
mountPoint: "/mnt/dst",
optionalFields: []string{"shared:1"},
fsType: "ext4",
mountOptions: []string{"rw", "relatime"},
superOptions: []string{"rw", "errors=remount-ro", "data=ordered"},
},
},
{
"more than one optional fields",
224,
mountInfo{
id: 224,
parentID: 62,
majorMinor: "253:0",
root: "/var/lib/docker/devicemapper/test/shared",
source: "/dev/mapper/ssd-root",
mountPoint: "/var/lib/docker/devicemapper/test/shared",
optionalFields: []string{"master:1", "shared:44"},
fsType: "ext4",
mountOptions: []string{"rw", "relatime"},
superOptions: []string{"rw", "seclabel", "data=ordered"},
},
},
{
"cgroup-mountpoint",
28,
mountInfo{
id: 28,
parentID: 18,
majorMinor: "0:24",
root: "/",
source: "tmpfs",
mountPoint: "/sys/fs/cgroup",
optionalFields: []string{"shared:9"},
fsType: "tmpfs",
mountOptions: []string{"ro", "nosuid", "nodev", "noexec"},
superOptions: []string{"ro", "mode=755"},
},
},
{
"cgroup-subsystem-systemd-mountpoint",
29,
mountInfo{
id: 29,
parentID: 28,
majorMinor: "0:25",
root: "/",
source: "cgroup",
mountPoint: "/sys/fs/cgroup/systemd",
optionalFields: []string{"shared:10"},
fsType: "cgroup",
mountOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
superOptions: []string{"rw", "xattr", "release_agent=/lib/systemd/systemd-cgroups-agent", "name=systemd"},
},
},
{
"cgroup-subsystem-cpuset-mountpoint",
31,
mountInfo{
id: 31,
parentID: 28,
majorMinor: "0:27",
root: "/",
source: "cgroup",
mountPoint: "/sys/fs/cgroup/cpuset",
optionalFields: []string{"shared:13"},
fsType: "cgroup",
mountOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
superOptions: []string{"rw", "cpuset"},
},
},
}
infos, err := parseMountInfo(filename)
if err != nil {
t.Fatalf("Cannot parse %s: %s", filename, err)
}
for _, test := range tests {
found := false
for _, info := range infos {
if info.id == test.id {
found = true
if !reflect.DeepEqual(info, test.expectedInfo) {
t.Errorf("Test case %q:\n expected: %+v\n got: %+v", test.name, test.expectedInfo, info)
}
break
}
}
if !found {
t.Errorf("Test case %q: mountPoint %d not found", test.name, test.id)
}
}
}
func TestGetSELinuxSupport(t *testing.T) {
info :=
`62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
78 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
83 63 0:44 / /var/lib/bar rw,relatime - tmpfs tmpfs rw
227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
150 23 1:58 / /media/nfs_vol rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
`
tempDir, filename, err := writeFile(info)
if err != nil {
t.Fatalf("cannot create temporary file: %v", err)
}
defer os.RemoveAll(tempDir)
tests := []struct {
name string
mountPoint string
expectedResult bool
}{
{
"ext4 on /",
"/",
true,
},
{
"tmpfs on /var/lib/bar",
"/var/lib/bar",
false,
},
{
"nfsv4",
"/media/nfs_vol",
false,
},
}
for _, test := range tests {
out, err := GetSELinux(test.mountPoint, filename)
if err != nil {
t.Errorf("Test %s failed with error: %s", test.name, err)
}
if test.expectedResult != out {
t.Errorf("Test %s failed: expected %v, got %v", test.name, test.expectedResult, out)
}
}
}
func createSocketFile(socketDir string) (string, error) {
testSocketFile := filepath.Join(socketDir, "mt.sock")
// Switch to volume path and create the socket file
// socket file can not have length of more than 108 character
// and hence we must use relative path
oldDir, _ := os.Getwd()
err := os.Chdir(socketDir)
if err != nil {
return "", err
}
defer func() {
os.Chdir(oldDir)
}()
_, socketCreateError := net.Listen("unix", "mt.sock")
return testSocketFile, socketCreateError
}
func TestGetFileType(t *testing.T) {
hu := NewHostUtil()
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
},
},
{
"Socket Test",
FileTypeSocket,
func() (string, string, error) {
tempDir, err := ioutil.TempDir("", "test-get-filetype-")
if err != nil {
return "", "", err
}
tempSocketFile, err := createSocketFile(tempDir)
return tempSocketFile, tempDir, err
},
},
{
"Block Device Test",
FileTypeBlockDev,
func() (string, string, error) {
tempDir, err := ioutil.TempDir("", "test-get-filetype-")
if err != nil {
return "", "", err
}
tempBlockFile := filepath.Join(tempDir, "test_blk_dev")
outputBytes, err := exec.New().Command("mknod", tempBlockFile, "b", "89", "1").CombinedOutput()
if err != nil {
err = fmt.Errorf("%v: %s ", err, outputBytes)
}
return tempBlockFile, tempDir, err
},
},
{
"Character Device Test",
FileTypeCharDev,
func() (string, string, error) {
tempDir, err := ioutil.TempDir("", "test-get-filetype-")
if err != nil {
return "", "", err
}
tempCharFile := filepath.Join(tempDir, "test_char_dev")
outputBytes, err := exec.New().Command("mknod", tempCharFile, "c", "89", "1").CombinedOutput()
if err != nil {
err = fmt.Errorf("%v: %s ", err, outputBytes)
}
return tempCharFile, tempDir, err
},
},
}
for idx, tc := range testCase {
path, cleanUpPath, err := tc.setUp()
if err != nil {
// Locally passed, but upstream CI is not friendly to create such device files
// Leave "Operation not permitted" out, which can be covered in an e2e test
if isOperationNotPermittedError(err) {
continue
}
t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err)
}
if len(cleanUpPath) > 0 {
defer os.RemoveAll(cleanUpPath)
}
fileType, err := hu.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 isOperationNotPermittedError(err error) bool {
if strings.Contains(err.Error(), "Operation not permitted") {
return true
}
return false
}
func TestSearchMountPoints(t *testing.T) { func TestSearchMountPoints(t *testing.T) {
base := ` base := `
19 25 0:18 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw 19 25 0:18 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw

View File

@ -20,7 +20,6 @@ package mount
import ( import (
"errors" "errors"
"os"
) )
// Mounter implements mount.Interface for unsupported platforms // Mounter implements mount.Interface for unsupported platforms
@ -80,65 +79,3 @@ func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, erro
func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return "", errUnsupported return "", errUnsupported
} }
type hostUtil struct{}
// NewHostUtil returns a struct that implements the HostUtils interface on
// unsupported platforms
func NewHostUtil() HostUtils {
return &hostUtil{}
}
// DeviceOpened determines if the device is in use elsewhere
func (hu *hostUtil) DeviceOpened(pathname string) (bool, error) {
return false, errUnsupported
}
// PathIsDevice determines if a path is a device.
func (hu *hostUtil) PathIsDevice(pathname string) (bool, error) {
return true, errUnsupported
}
// GetDeviceNameFromMount finds the device name by checking the mount path
// to get the global mount path within its plugin directory
func (hu *hostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return "", errUnsupported
}
func (hu *hostUtil) MakeRShared(path string) error {
return errUnsupported
}
func (hu *hostUtil) GetFileType(pathname string) (FileType, error) {
return FileType("fake"), errUnsupported
}
func (hu *hostUtil) MakeFile(pathname string) error {
return errUnsupported
}
func (hu *hostUtil) MakeDir(pathname string) error {
return errUnsupported
}
func (hu *hostUtil) PathExists(pathname string) (bool, error) {
return true, errUnsupported
}
// EvalHostSymlinks returns the path name after evaluating symlinks
func (hu *hostUtil) EvalHostSymlinks(pathname string) (string, error) {
return "", errUnsupported
}
// GetOwner returns the integer ID for the user and group of the given path
func (hu *hostUtil) GetOwner(pathname string) (int64, int64, error) {
return -1, -1, errUnsupported
}
func (hu *hostUtil) GetSELinuxSupport(pathname string) (bool, error) {
return false, errUnsupported
}
func (hu *hostUtil) GetMode(pathname string) (os.FileMode, error) {
return 0, errUnsupported
}

View File

@ -22,15 +22,11 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"k8s.io/klog" "k8s.io/klog"
"k8s.io/utils/keymutex" "k8s.io/utils/keymutex"
utilpath "k8s.io/utils/path"
) )
// Mounter provides the default implementation of mount.Interface // Mounter provides the default implementation of mount.Interface
@ -55,7 +51,7 @@ var getSMBMountMutex = keymutex.NewHashed(0)
// Mount : mounts source to target with given options. // Mount : mounts source to target with given options.
// currently only supports cifs(smb), bind mount(for disk) // currently only supports cifs(smb), bind mount(for disk)
func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error { func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
target = normalizeWindowsPath(target) target = NormalizeWindowsPath(target)
if source == "tmpfs" { if source == "tmpfs" {
klog.V(3).Infof("mounting source (%q), target (%q), with options (%q)", source, target, options) klog.V(3).Infof("mounting source (%q), target (%q), with options (%q)", source, target, options)
@ -74,7 +70,7 @@ func (mounter *Mounter) Mount(source string, target string, fstype string, optio
// tell it's going to mount azure disk or azure file according to options // tell it's going to mount azure disk or azure file according to options
if bind, _, _ := IsBind(options); bind { if bind, _, _ := IsBind(options); bind {
// mount azure disk // mount azure disk
bindSource = normalizeWindowsPath(source) bindSource = NormalizeWindowsPath(source)
} else { } else {
if len(options) < 2 { if len(options) < 2 {
klog.Warningf("mount options(%q) command number(%d) less than 2, source:%q, target:%q, skip mounting", klog.Warningf("mount options(%q) command number(%d) less than 2, source:%q, target:%q, skip mounting",
@ -155,7 +151,7 @@ func removeSMBMapping(remotepath string) (string, error) {
// Unmount unmounts the target. // Unmount unmounts the target.
func (mounter *Mounter) Unmount(target string) error { func (mounter *Mounter) Unmount(target string) error {
klog.V(4).Infof("azureMount: Unmount target (%q)", target) klog.V(4).Infof("azureMount: Unmount target (%q)", target)
target = normalizeWindowsPath(target) target = NormalizeWindowsPath(target)
if output, err := exec.Command("cmd", "/c", "rmdir", target).CombinedOutput(); err != nil { if output, err := exec.Command("cmd", "/c", "rmdir", target).CombinedOutput(); err != nil {
klog.Errorf("rmdir failed: %v, output: %q", err, string(output)) klog.Errorf("rmdir failed: %v, output: %q", err, string(output))
return err return err
@ -198,7 +194,7 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
// GetMountRefs : empty implementation here since there is no place to query all mount points on Windows // GetMountRefs : empty implementation here since there is no place to query all mount points on Windows
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
windowsPath := normalizeWindowsPath(pathname) windowsPath := NormalizeWindowsPath(pathname)
pathExists, pathErr := PathExists(windowsPath) pathExists, pathErr := PathExists(windowsPath)
if !pathExists { if !pathExists {
return []string{}, nil return []string{}, nil
@ -238,7 +234,7 @@ func (mounter *SafeFormatAndMount) formatAndMount(source string, target string,
return err return err
} }
driverPath := driveLetter + ":" driverPath := driveLetter + ":"
target = normalizeWindowsPath(target) target = NormalizeWindowsPath(target)
klog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, driverPath, 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 { if output, err := mounter.Exec.Run("cmd", "/c", "mklink", "/D", target, driverPath); err != nil {
klog.Errorf("mklink failed: %v, output: %q", err, string(output)) klog.Errorf("mklink failed: %v, output: %q", err, string(output))
@ -247,28 +243,6 @@ func (mounter *SafeFormatAndMount) formatAndMount(source string, target string,
return nil 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 // Get drive letter according to windows disk number
func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) { func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) {
cmd := fmt.Sprintf("(Get-Partition -DiskNumber %s).DriveLetter", diskNum) cmd := fmt.Sprintf("(Get-Partition -DiskNumber %s).DriveLetter", diskNum)
@ -308,117 +282,3 @@ func getAllParentLinks(path string) ([]string, error) {
return links, nil return links, nil
} }
type hostUtil struct{}
// NewHostUtil returns a struct that implements the HostUtils interface on
// windows platforms
func NewHostUtil() HostUtils {
return &hostUtil{}
}
// GetDeviceNameFromMount given a mnt point, find the device
func (hu *hostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
return getDeviceNameFromMount(mounter, mountPath, pluginMountDir)
}
// getDeviceNameFromMount find the device(drive) name in which
// the mount path reference should match the given plugin mount directory. In case no mount path reference
// matches, returns the volume name taken from its given mountPath
func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir 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(pluginMountDir)
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 (hu *hostUtil) DeviceOpened(pathname string) (bool, error) {
return false, nil
}
// PathIsDevice determines if a path is a device.
func (hu *hostUtil) 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 (hu *hostUtil) MakeRShared(path string) error {
return nil
}
// GetFileType checks for sockets/block/character devices
func (hu *(hostUtil)) GetFileType(pathname string) (FileType, error) {
return getFileType(pathname)
}
// MakeFile creates a new directory
func (hu *hostUtil) 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 (hu *hostUtil) 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
}
// PathExists checks whether the path exists
func (hu *hostUtil) PathExists(pathname string) (bool, error) {
return utilpath.Exists(utilpath.CheckFollowSymlink, pathname)
}
// EvalHostSymlinks returns the path name after evaluating symlinks
func (hu *hostUtil) EvalHostSymlinks(pathname string) (string, error) {
return filepath.EvalSymlinks(pathname)
}
// GetOwner returns the integer ID for the user and group of the given path
// Note that on windows, it always returns 0. We actually don't set Group on
// windows platform, see SetVolumeOwnership implementation.
func (hu *hostUtil) GetOwner(pathname string) (int64, int64, error) {
return -1, -1, nil
}
func (hu *hostUtil) GetSELinuxSupport(pathname string) (bool, error) {
// Windows does not support SELinux.
return false, nil
}
func (hu *hostUtil) GetMode(pathname string) (os.FileMode, error) {
info, err := os.Stat(pathname)
if err != nil {
return 0, err
}
return info.Mode(), nil
}

View File

@ -30,48 +30,6 @@ import (
"github.com/stretchr/testify/assert" "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 { func makeLink(link, target string) error {
if output, err := exec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput(); err != nil { 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 fmt.Errorf("mklink failed: %v, link(%q) target(%q) output: %q", err, link, target, string(output))
@ -174,55 +132,6 @@ func TestPathWithinBase(t *testing.T) {
} }
} }
func TestGetFileType(t *testing.T) {
hu := NewHostUtil()
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 := hu.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) { func TestIsLikelyNotMountPoint(t *testing.T) {
mounter := Mounter{"fake/path"} mounter := Mounter{"fake/path"}