mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
Merge pull request #81794 from codenrhoden/split-host-utils2
Split HostUtil functionality into its own files
This commit is contained in:
commit
7ebbe34d9c
@ -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",
|
||||||
|
@ -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")
|
|
||||||
}
|
|
||||||
|
116
pkg/util/mount/fake_hostutil.go
Normal file
116
pkg/util/mount/fake_hostutil.go
Normal 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
114
pkg/util/mount/hostutil.go
Normal 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")
|
||||||
|
}
|
294
pkg/util/mount/hostutil_linux.go
Normal file
294
pkg/util/mount/hostutil_linux.go
Normal 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
|
||||||
|
}
|
313
pkg/util/mount/hostutil_linux_test.go
Normal file
313
pkg/util/mount/hostutil_linux_test.go
Normal 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
|
||||||
|
}
|
85
pkg/util/mount/hostutil_unsupported.go
Normal file
85
pkg/util/mount/hostutil_unsupported.go
Normal 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
|
||||||
|
}
|
145
pkg/util/mount/hostutil_windows.go
Normal file
145
pkg/util/mount/hostutil_windows.go
Normal 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
|
||||||
|
}
|
74
pkg/util/mount/hostutil_windows_test.go
Normal file
74
pkg/util/mount/hostutil_windows_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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")
|
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
215
pkg/util/mount/mount_helper_unix_test.go
Normal file
215
pkg/util/mount/mount_helper_unix_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
65
pkg/util/mount/mount_helper_windows_test.go
Normal file
65
pkg/util/mount/mount_helper_windows_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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"}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user