Merge pull request #51771 from dixudx/refactor_nsenter

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Refactor nsenter

**What this PR does / why we need it**:

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #51273

**Special notes for your reviewer**:
/assign @jsafrane 

**Release note**:

```release-note
None
```
pull/6/head
Kubernetes Submit Queue 2017-10-08 23:27:32 -07:00 committed by GitHub
commit 85b252d47e
23 changed files with 666 additions and 581 deletions

View File

@ -79,6 +79,22 @@ func (mi *fakeMountInterface) MakeRShared(path string) error {
return nil return nil
} }
func (mi *fakeMountInterface) GetFileType(pathname string) (mount.FileType, error) {
return mount.FileType("fake"), nil
}
func (mi *fakeMountInterface) MakeDir(pathname string) error {
return nil
}
func (mi *fakeMountInterface) MakeFile(pathname string) error {
return nil
}
func (mi *fakeMountInterface) ExistsPath(pathname string) bool {
return true
}
func fakeContainerMgrMountInt() mount.Interface { func fakeContainerMgrMountInt() mount.Interface {
return &fakeMountInterface{ return &fakeMountInterface{
[]mount.MountPoint{ []mount.MountPoint{

View File

@ -69,7 +69,6 @@ func (kl *Kubelet) newVolumeMounterFromPlugins(spec *volume.Spec, pod *v1.Pod, o
if err != nil { if err != nil {
return nil, fmt.Errorf("can't use volume plugins for %s: %v", spec.Name(), err) return nil, fmt.Errorf("can't use volume plugins for %s: %v", spec.Name(), err)
} }
opts.Containerized = kl.kubeletConfiguration.Containerized
physicalMounter, err := plugin.NewMounter(spec, pod, opts) physicalMounter, err := plugin.NewMounter(spec, pod, opts)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to instantiate mounter for volume: %s using plugin: %s with a root cause: %v", spec.Name(), plugin.GetPluginName(), err) return nil, fmt.Errorf("failed to instantiate mounter for volume: %s using plugin: %s with a root cause: %v", spec.Name(), plugin.GetPluginName(), err)

View File

@ -38,6 +38,7 @@ filegroup(
"//pkg/util/net:all-srcs", "//pkg/util/net:all-srcs",
"//pkg/util/netsh:all-srcs", "//pkg/util/netsh:all-srcs",
"//pkg/util/node:all-srcs", "//pkg/util/node:all-srcs",
"//pkg/util/nsenter:all-srcs",
"//pkg/util/oom:all-srcs", "//pkg/util/oom:all-srcs",
"//pkg/util/parsers:all-srcs", "//pkg/util/parsers:all-srcs",
"//pkg/util/pointer:all-srcs", "//pkg/util/pointer:all-srcs",

View File

@ -31,6 +31,7 @@ go_library(
] + select({ ] + select({
"@io_bazel_rules_go//go/platform:linux_amd64": [ "@io_bazel_rules_go//go/platform:linux_amd64": [
"//pkg/util/io:go_default_library", "//pkg/util/io:go_default_library",
"//pkg/util/nsenter:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library", "//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
], ],

View File

@ -125,7 +125,7 @@ func (f *FakeMounter) List() ([]MountPoint, error) {
} }
func (f *FakeMounter) IsMountPointMatch(mp MountPoint, dir string) bool { func (f *FakeMounter) IsMountPointMatch(mp MountPoint, dir string) bool {
return (mp.Path == dir) return mp.Path == dir
} }
func (f *FakeMounter) IsNotMountPoint(dir string) (bool, error) { func (f *FakeMounter) IsNotMountPoint(dir string) (bool, error) {
@ -175,3 +175,19 @@ func (f *FakeMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (strin
func (f *FakeMounter) MakeRShared(path string) error { func (f *FakeMounter) MakeRShared(path string) error {
return nil return nil
} }
func (f *FakeMounter) GetFileType(pathname string) (FileType, error) {
return FileType("fake"), nil
}
func (f *FakeMounter) MakeDir(pathname string) error {
return nil
}
func (f *FakeMounter) MakeFile(pathname string) error {
return nil
}
func (f *FakeMounter) ExistsPath(pathname string) bool {
return false
}

View File

@ -27,10 +27,17 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
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"
MountsInGlobalPDPath = "mounts" MountsInGlobalPDPath = "mounts"
FileTypeDirectory FileType = "Directory"
FileTypeFile FileType = "File"
FileTypeSocket FileType = "Socket"
FileTypeCharDev FileType = "CharDevice"
FileTypeBlockDev FileType = "BlockDevice"
) )
type Interface interface { type Interface interface {
@ -70,6 +77,18 @@ type Interface interface {
// MakeRShared checks that given path is on a mount with 'rshared' mount // MakeRShared checks that given path is on a mount with 'rshared' mount
// propagation. If not, it bind-mounts the path as rshared. // propagation. If not, it bind-mounts the path as rshared.
MakeRShared(path string) error MakeRShared(path string) error
// GetFileType checks for file/directory/socket/block/character devices.
// Will operate in the host mount namespace if kubelet is running in a container
GetFileType(pathname string) (FileType, error)
// MakeFile creates an empty file.
// Will operate in the host mount namespace if kubelet is running in a container
MakeFile(pathname string) error
// MakeDir creates a new directory.
// Will operate in the host mount namespace if kubelet is running in a container
MakeDir(pathname string) error
// ExistsPath checks whether the path exists.
// Will operate in the host mount namespace if kubelet is running in a container
ExistsPath(pathname string) bool
} }
// Exec executes command where mount utilities are. This can be either the host, // Exec executes command where mount utilities are. This can be either the host,

View File

@ -252,17 +252,29 @@ func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
// PathIsDevice uses FileInfo returned from os.Stat to check if path refers // PathIsDevice uses FileInfo returned from os.Stat to check if path refers
// to a device. // to a device.
func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) { func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) {
return pathIsDevice(pathname) pathType, err := mounter.GetFileType(pathname)
isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev
return isDevice, err
} }
func exclusiveOpenFailsOnDevice(pathname string) (bool, error) { func exclusiveOpenFailsOnDevice(pathname string) (bool, error) {
isDevice, err := pathIsDevice(pathname) var isDevice bool
finfo, err := os.Stat(pathname)
if os.IsNotExist(err) {
isDevice = false
}
// err in call to os.Stat
if err != nil { if err != nil {
return false, fmt.Errorf( return false, fmt.Errorf(
"PathIsDevice failed for path %q: %v", "PathIsDevice failed for path %q: %v",
pathname, pathname,
err) err)
} }
// path refers to a device
if finfo.Mode()&os.ModeDevice != 0 {
isDevice = true
}
if !isDevice { if !isDevice {
glog.Errorf("Path %q is not refering to a device.", pathname) glog.Errorf("Path %q is not refering to a device.", pathname)
return false, nil return false, nil
@ -282,23 +294,6 @@ func exclusiveOpenFailsOnDevice(pathname string) (bool, error) {
return false, errno return false, errno
} }
func pathIsDevice(pathname string) (bool, error) {
finfo, err := os.Stat(pathname)
if os.IsNotExist(err) {
return false, nil
}
// err in call to os.Stat
if err != nil {
return false, err
}
// path refers to a device
if finfo.Mode()&os.ModeDevice != 0 {
return true, nil
}
// path does not refer to device
return false, nil
}
//GetDeviceNameFromMount: given a mount point, find the device name from its global mount point //GetDeviceNameFromMount: given a mount point, find the device name from its global mount point
func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
return getDeviceNameFromMount(mounter, mountPath, pluginDir) return getDeviceNameFromMount(mounter, mountPath, pluginDir)
@ -350,9 +345,64 @@ func parseProcMounts(content []byte) ([]MountPoint, error) {
} }
func (mounter *Mounter) MakeRShared(path string) error { func (mounter *Mounter) MakeRShared(path string) error {
mountCmd := defaultMountCommand return doMakeRShared(path, procMountInfoPath)
mountArgs := []string{} }
return doMakeRShared(path, procMountInfoPath, mountCmd, mountArgs)
func (mounter *Mounter) GetFileType(pathname string) (FileType, error) {
var pathType FileType
finfo, 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
}
mode := finfo.Sys().(*syscall.Stat_t).Mode
switch mode & syscall.S_IFMT {
case syscall.S_IFSOCK:
return FileTypeSocket, nil
case syscall.S_IFBLK:
return FileTypeBlockDev, nil
case syscall.S_IFCHR:
return FileTypeBlockDev, nil
case syscall.S_IFDIR:
return FileTypeDirectory, nil
case syscall.S_IFREG:
return FileTypeFile, nil
}
return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device")
}
func (mounter *Mounter) MakeDir(pathname string) error {
err := os.MkdirAll(pathname, os.FileMode(0755))
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
}
func (mounter *Mounter) MakeFile(pathname string) error {
f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644))
defer f.Close()
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
}
func (mounter *Mounter) ExistsPath(pathname string) bool {
_, err := os.Stat(pathname)
if err != nil {
return false
}
return true
} }
// formatAndMount uses unix utils to format and mount the given disk // formatAndMount uses unix utils to format and mount the given disk
@ -523,7 +573,7 @@ func parseMountInfo(filename string) ([]mountInfo, error) {
// path is shared and bind-mounts it as rshared if needed. mountCmd and // path is shared and bind-mounts it as rshared if needed. mountCmd and
// mountArgs are expected to contain mount-like command, doMakeRShared will add // mountArgs are expected to contain mount-like command, doMakeRShared will add
// '--bind <path> <path>' and '--make-rshared <path>' to mountArgs. // '--bind <path> <path>' and '--make-rshared <path>' to mountArgs.
func doMakeRShared(path string, mountInfoFilename string, mountCmd string, mountArgs []string) error { func doMakeRShared(path string, mountInfoFilename string) error {
shared, err := isShared(path, mountInfoFilename) shared, err := isShared(path, mountInfoFilename)
if err != nil { if err != nil {
return err return err

View File

@ -18,6 +18,10 @@ limitations under the License.
package mount package mount
import (
"errors"
)
type Mounter struct { type Mounter struct {
mounterPath string mounterPath string
} }
@ -74,3 +78,23 @@ func (mounter *Mounter) MakeRShared(path string) error {
func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error { func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
return nil return nil
} }
func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, error) {
return true, nil
}
func (mounter *Mounter) GetFileType(pathname string) (FileType, error) {
return FileType("fake"), errors.New("not implemented")
}
func (mounter *Mounter) MakeDir(pathname string) error {
return nil
}
func (mounter *Mounter) MakeFile(pathname string) error {
return nil
}
func (mounter *Mounter) ExistsPath(pathname string) bool {
return true
}

View File

@ -25,6 +25,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall"
"github.com/golang/glog" "github.com/golang/glog"
) )
@ -167,6 +168,67 @@ func (mounter *Mounter) MakeRShared(path string) error {
return nil return nil
} }
// GetFileType checks for sockets/block/character devices
func (mounter *Mounter) 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
}
mode := info.Sys().(*syscall.Win32FileAttributeData).FileAttributes
switch mode & syscall.S_IFMT {
case syscall.S_IFSOCK:
return FileTypeSocket, nil
case syscall.S_IFBLK:
return FileTypeBlockDev, nil
case syscall.S_IFCHR:
return FileTypeCharDev, nil
case syscall.S_IFDIR:
return FileTypeDirectory, nil
case syscall.S_IFREG:
return FileTypeFile, nil
}
return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device")
}
// MakeFile creates a new directory
func (mounter *Mounter) MakeDir(pathname string) error {
err := os.MkdirAll(pathname, os.FileMode(0755))
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
}
// MakeFile creates an empty file
func (mounter *Mounter) MakeFile(pathname string) error {
f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644))
defer f.Close()
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
}
// ExistsPath checks whether the path exists
func (mounter *Mounter) ExistsPath(pathname string) bool {
_, err := os.Stat(pathname)
if err != nil {
return false
}
return true
}
func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error { func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
// Try to mount the disk // Try to mount the disk
glog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, source, target) glog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, source, target)

View File

@ -25,78 +25,30 @@ import (
"strings" "strings"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/utils/exec" "k8s.io/kubernetes/pkg/util/nsenter"
) )
// NsenterMounter is part of experimental support for running the kubelet const (
// in a container. Currently, all docker containers receive their own mount // hostProcMountsPath is the default mount path for rootfs
// namespaces. NsenterMounter works by executing nsenter to run commands in hostProcMountsPath = "/rootfs/proc/1/mounts"
// hostProcMountinfoPath is the default mount info path for rootfs
hostProcMountinfoPath = "/rootfs/proc/1/mountinfo"
)
// Currently, all docker containers receive their own mount namespaces.
// NsenterMounter works by executing nsenter to run commands in
// the host's mount namespace. // the host's mount namespace.
//
// NsenterMounter requires:
//
// 1. Docker >= 1.6 due to the dependency on the slave propagation mode
// of the bind-mount of the kubelet root directory in the container.
// Docker 1.5 used a private propagation mode for bind-mounts, so mounts
// performed in the host's mount namespace do not propagate out to the
// bind-mount in this docker version.
// 2. The host's root filesystem must be available at /rootfs
// 3. The nsenter binary must be on the Kubelet process' PATH in the container's
// filesystem.
// 4. The Kubelet process must have CAP_SYS_ADMIN (required by nsenter); at
// the present, this effectively means that the kubelet is running in a
// privileged container.
// 5. The volume path used by the Kubelet must be the same inside and outside
// the container and be writable by the container (to initialize volume)
// contents. TODO: remove this requirement.
// 6. The host image must have mount, findmnt, and umount binaries in /bin,
// /usr/sbin, or /usr/bin
// 7. The host image should have systemd-run in /bin, /usr/sbin, or /usr/bin
// For more information about mount propagation modes, see:
// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
type NsenterMounter struct { type NsenterMounter struct {
// a map of commands to their paths on the host filesystem ne *nsenter.Nsenter
paths map[string]string
} }
func NewNsenterMounter() *NsenterMounter { func NewNsenterMounter() *NsenterMounter {
m := &NsenterMounter{ return &NsenterMounter{ne: nsenter.NewNsenter()}
paths: map[string]string{
"mount": "",
"findmnt": "",
"umount": "",
"systemd-run": "",
},
}
// search for the mount command in other locations besides /usr/bin
for binary := range m.paths {
// default to root
m.paths[binary] = filepath.Join("/", binary)
for _, path := range []string{"/bin", "/usr/sbin", "/usr/bin"} {
binPath := filepath.Join(path, binary)
if _, err := os.Stat(filepath.Join(hostRootFsPath, binPath)); err != nil {
continue
}
m.paths[binary] = binPath
break
}
// TODO: error, so that the kubelet can stop if the mounts don't exist
// (don't forget that systemd-run is optional)
}
return m
} }
// NsenterMounter implements mount.Interface // NsenterMounter implements mount.Interface
var _ = Interface(&NsenterMounter{}) var _ = Interface(&NsenterMounter{})
const (
hostRootFsPath = "/rootfs"
hostProcMountsPath = "/rootfs/proc/1/mounts"
hostProcMountinfoPath = "/rootfs/proc/1/mountinfo"
hostMountNamespacePath = "/rootfs/proc/1/ns/mnt"
nsenterPath = "nsenter"
)
// Mount runs mount(8) in the host's root mount namespace. Aside from this // Mount runs mount(8) in the host's root mount namespace. Aside from this
// aspect, Mount has the same semantics as the mounter returned by mount.New() // aspect, Mount has the same semantics as the mounter returned by mount.New()
func (n *NsenterMounter) Mount(source string, target string, fstype string, options []string) error { func (n *NsenterMounter) Mount(source string, target string, fstype string, options []string) error {
@ -116,26 +68,22 @@ func (n *NsenterMounter) Mount(source string, target string, fstype string, opti
// doNsenterMount nsenters the host's mount namespace and performs the // doNsenterMount nsenters the host's mount namespace and performs the
// requested mount. // requested mount.
func (n *NsenterMounter) doNsenterMount(source, target, fstype string, options []string) error { func (n *NsenterMounter) doNsenterMount(source, target, fstype string, options []string) error {
glog.V(5).Infof("nsenter Mounting %s %s %s %v", source, target, fstype, options) glog.V(5).Infof("nsenter mount %s %s %s %v", source, target, fstype, options)
args := n.makeNsenterArgs(source, target, fstype, options) cmd, args := n.makeNsenterArgs(source, target, fstype, options)
outputBytes, err := n.ne.Exec(cmd, args).CombinedOutput()
glog.V(5).Infof("Mount command: %v %v", nsenterPath, args)
exec := exec.New()
outputBytes, err := exec.Command(nsenterPath, args...).CombinedOutput()
if len(outputBytes) != 0 { if len(outputBytes) != 0 {
glog.V(5).Infof("Output of mounting %s to %s: %v", source, target, string(outputBytes)) glog.V(5).Infof("Output of mounting %s to %s: %v", source, target, string(outputBytes))
} }
return err return err
} }
// makeNsenterArgs makes a list of argument to nsenter in order to do the // makeNsenterArgs makes a list of argument to nsenter in order to do the
// requested mount. // requested mount.
func (n *NsenterMounter) makeNsenterArgs(source, target, fstype string, options []string) []string { func (n *NsenterMounter) makeNsenterArgs(source, target, fstype string, options []string) (string, []string) {
mountCmd := n.absHostPath("mount") mountCmd := n.ne.AbsHostPath("mount")
mountArgs := makeMountArgs(source, target, fstype, options) mountArgs := makeMountArgs(source, target, fstype, options)
if systemdRunPath, hasSystemd := n.paths["systemd-run"]; hasSystemd { if systemdRunPath, hasSystemd := n.ne.SupportsSystemd(); hasSystemd {
// Complete command line: // Complete command line:
// nsenter --mount=/rootfs/proc/1/ns/mnt -- /bin/systemd-run --description=... --scope -- /bin/mount -t <type> <what> <where> // nsenter --mount=/rootfs/proc/1/ns/mnt -- /bin/systemd-run --description=... --scope -- /bin/mount -t <type> <what> <where>
// Expected flow is: // Expected flow is:
@ -165,34 +113,20 @@ func (n *NsenterMounter) makeNsenterArgs(source, target, fstype string, options
// No code here, mountCmd and mountArgs use /bin/mount // No code here, mountCmd and mountArgs use /bin/mount
} }
nsenterArgs := []string{ return mountCmd, mountArgs
"--mount=" + hostMountNamespacePath,
"--",
mountCmd,
}
nsenterArgs = append(nsenterArgs, mountArgs...)
return nsenterArgs
} }
// Unmount runs umount(8) in the host's mount namespace. // Unmount runs umount(8) in the host's mount namespace.
func (n *NsenterMounter) Unmount(target string) error { func (n *NsenterMounter) Unmount(target string) error {
args := []string{ args := []string{target}
"--mount=" + hostMountNamespacePath,
"--",
n.absHostPath("umount"),
target,
}
// No need to execute systemd-run here, it's enough that unmount is executed // No need to execute systemd-run here, it's enough that unmount is executed
// in the host's mount namespace. It will finish appropriate fuse daemon(s) // in the host's mount namespace. It will finish appropriate fuse daemon(s)
// running in any scope. // running in any scope.
glog.V(5).Infof("Unmount command: %v %v", nsenterPath, args) glog.V(5).Infof("nsenter unmount args: %v", args)
exec := exec.New() outputBytes, err := n.ne.Exec("umount", args).CombinedOutput()
outputBytes, err := exec.Command(nsenterPath, args...).CombinedOutput()
if len(outputBytes) != 0 { if len(outputBytes) != 0 {
glog.V(5).Infof("Output of unmounting %s: %v", target, string(outputBytes)) glog.V(5).Infof("Output of unmounting %s: %v", target, string(outputBytes))
} }
return err return err
} }
@ -207,7 +141,7 @@ func (m *NsenterMounter) IsNotMountPoint(dir string) (bool, error) {
func (*NsenterMounter) IsMountPointMatch(mp MountPoint, dir string) bool { func (*NsenterMounter) IsMountPointMatch(mp MountPoint, dir string) bool {
deletedDir := fmt.Sprintf("%s\\040(deleted)", dir) deletedDir := fmt.Sprintf("%s\\040(deleted)", dir)
return ((mp.Path == dir) || (mp.Path == deletedDir)) return (mp.Path == dir) || (mp.Path == deletedDir)
} }
// IsLikelyNotMountPoint determines whether a path is a mountpoint by calling findmnt // IsLikelyNotMountPoint determines whether a path is a mountpoint by calling findmnt
@ -227,11 +161,9 @@ func (n *NsenterMounter) IsLikelyNotMountPoint(file string) (bool, error) {
// the first of multiple possible mountpoints using --first-only. // the first of multiple possible mountpoints using --first-only.
// Also add fstype output to make sure that the output of target file will give the full path // Also add fstype output to make sure that the output of target file will give the full path
// TODO: Need more refactoring for this function. Track the solution with issue #26996 // TODO: Need more refactoring for this function. Track the solution with issue #26996
args := []string{"--mount=" + hostMountNamespacePath, "--", n.absHostPath("findmnt"), "-o", "target,fstype", "--noheadings", "--first-only", "--target", file} args := []string{"-o", "target,fstype", "--noheadings", "--first-only", "--target", file}
glog.V(5).Infof("findmnt command: %v %v", nsenterPath, args) glog.V(5).Infof("nsenter findmnt args: %v", args)
out, err := n.ne.Exec("findmnt", args).CombinedOutput()
exec := exec.New()
out, err := exec.Command(nsenterPath, args...).CombinedOutput()
if err != nil { if err != nil {
glog.V(2).Infof("Failed findmnt command for path %s: %v", file, err) glog.V(2).Infof("Failed findmnt command for path %s: %v", file, err)
// Different operating systems behave differently for paths which are not mount points. // Different operating systems behave differently for paths which are not mount points.
@ -277,7 +209,9 @@ func (n *NsenterMounter) DeviceOpened(pathname string) (bool, error) {
// PathIsDevice uses FileInfo returned from os.Stat to check if path refers // PathIsDevice uses FileInfo returned from os.Stat to check if path refers
// to a device. // to a device.
func (n *NsenterMounter) PathIsDevice(pathname string) (bool, error) { func (n *NsenterMounter) PathIsDevice(pathname string) (bool, error) {
return pathIsDevice(pathname) pathType, err := n.GetFileType(pathname)
isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev
return isDevice, err
} }
//GetDeviceNameFromMount given a mount point, find the volume id from checking /proc/mounts //GetDeviceNameFromMount given a mount point, find the volume id from checking /proc/mounts
@ -285,20 +219,54 @@ func (n *NsenterMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (st
return getDeviceNameFromMount(n, mountPath, pluginDir) return getDeviceNameFromMount(n, mountPath, pluginDir)
} }
func (n *NsenterMounter) absHostPath(command string) string { func (n *NsenterMounter) MakeRShared(path string) error {
path, ok := n.paths[command] return doMakeRShared(path, hostProcMountinfoPath)
if !ok {
return command
}
return path
} }
func (n *NsenterMounter) MakeRShared(path string) error { func (mounter *NsenterMounter) GetFileType(pathname string) (FileType, error) {
nsenterCmd := nsenterPath var pathType FileType
nsenterArgs := []string{ outputBytes, err := mounter.ne.Exec("stat", []string{"-L", `--printf "%F"`, pathname}).CombinedOutput()
"--mount=" + hostMountNamespacePath, if err != nil {
"--", return pathType, err
n.absHostPath("mount"),
} }
return doMakeRShared(path, hostProcMountinfoPath, nsenterCmd, nsenterArgs)
switch string(outputBytes) {
case "socket":
return FileTypeSocket, nil
case "character special file":
return FileTypeCharDev, nil
case "block special file":
return FileTypeBlockDev, nil
case "directory":
return FileTypeDirectory, nil
case "regular file":
return FileTypeFile, nil
}
return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device")
}
func (mounter *NsenterMounter) MakeDir(pathname string) error {
args := []string{"-p", pathname}
if _, err := mounter.ne.Exec("mkdir", args).CombinedOutput(); err != nil {
return err
}
return nil
}
func (mounter *NsenterMounter) MakeFile(pathname string) error {
args := []string{pathname}
if _, err := mounter.ne.Exec("touch", args).CombinedOutput(); err != nil {
return err
}
return nil
}
func (mounter *NsenterMounter) ExistsPath(pathname string) bool {
args := []string{pathname}
_, err := mounter.ne.Exec("ls", args).CombinedOutput()
if err == nil {
return true
}
return false
} }

View File

@ -18,6 +18,10 @@ limitations under the License.
package mount package mount
import (
"errors"
)
type NsenterMounter struct{} type NsenterMounter struct{}
func NewNsenterMounter() *NsenterMounter { func NewNsenterMounter() *NsenterMounter {
@ -65,3 +69,19 @@ func (*NsenterMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (stri
func (*NsenterMounter) MakeRShared(path string) error { func (*NsenterMounter) MakeRShared(path string) error {
return nil return nil
} }
func (*NsenterMounter) GetFileType(_ string) (FileType, error) {
return FileType("fake"), errors.New("not implemented")
}
func (*NsenterMounter) MakeDir(pathname string) error {
return nil
}
func (*NsenterMounter) MakeFile(pathname string) error {
return nil
}
func (*NsenterMounter) ExistsPath(pathname string) bool {
return true
}

36
pkg/util/nsenter/BUILD Normal file
View File

@ -0,0 +1,36 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"nsenter_unsupported.go",
] + select({
"@io_bazel_rules_go//go/platform:linux_amd64": [
"nsenter.go",
],
"//conditions:default": [],
}),
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/utils/exec:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:linux_amd64": [
"//vendor/github.com/golang/glog:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

124
pkg/util/nsenter/nsenter.go Normal file
View File

@ -0,0 +1,124 @@
// +build linux
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package nsenter
import (
"fmt"
"os"
"path/filepath"
"k8s.io/utils/exec"
"github.com/golang/glog"
)
const (
hostRootFsPath = "/rootfs"
// hostProcMountNsPath is the default mount namespace for rootfs
hostProcMountNsPath = "/rootfs/proc/1/ns/mnt"
// nsenterPath is the default nsenter command
nsenterPath = "nsenter"
)
// Nsenter is part of experimental support for running the kubelet
// in a container.
//
// Nsenter requires:
//
// 1. Docker >= 1.6 due to the dependency on the slave propagation mode
// of the bind-mount of the kubelet root directory in the container.
// Docker 1.5 used a private propagation mode for bind-mounts, so mounts
// performed in the host's mount namespace do not propagate out to the
// bind-mount in this docker version.
// 2. The host's root filesystem must be available at /rootfs
// 3. The nsenter binary must be on the Kubelet process' PATH in the container's
// filesystem.
// 4. The Kubelet process must have CAP_SYS_ADMIN (required by nsenter); at
// the present, this effectively means that the kubelet is running in a
// privileged container.
// 5. The volume path used by the Kubelet must be the same inside and outside
// the container and be writable by the container (to initialize volume)
// contents. TODO: remove this requirement.
// 6. The host image must have "mount", "findmnt", "umount", "stat", "touch",
// "mkdir", "ls", "sh" and "chmod" binaries in /bin, /usr/sbin, or /usr/bin
// 7. The host image should have systemd-run in /bin, /usr/sbin, or /usr/bin
// For more information about mount propagation modes, see:
// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
type Nsenter struct {
// a map of commands to their paths on the host filesystem
paths map[string]string
}
// NewNsenter constructs a new instance of Nsenter
func NewNsenter() *Nsenter {
ne := &Nsenter{
paths: map[string]string{
"mount": "",
"findmnt": "",
"umount": "",
"systemd-run": "",
"stat": "",
"touch": "",
"mkdir": "",
"ls": "",
"sh": "",
"chmod": "",
},
}
// search for the required commands in other locations besides /usr/bin
for binary := range ne.paths {
// default to root
ne.paths[binary] = filepath.Join("/", binary)
for _, path := range []string{"/bin", "/usr/sbin", "/usr/bin"} {
binPath := filepath.Join(path, binary)
if _, err := os.Stat(filepath.Join(hostRootFsPath, binPath)); err != nil {
continue
}
ne.paths[binary] = binPath
break
}
// TODO: error, so that the kubelet can stop if the paths don't exist
// (don't forget that systemd-run is optional)
}
return ne
}
// Exec executes nsenter commands in hostProcMountNsPath mount namespace
func (ne *Nsenter) Exec(cmd string, args []string) exec.Cmd {
fullArgs := append([]string{fmt.Sprintf("--mount=%s", hostProcMountNsPath), "--"},
append([]string{ne.AbsHostPath(cmd)}, args...)...)
glog.V(5).Infof("Running nsenter command: %v %v", nsenterPath, fullArgs)
exec := exec.New()
return exec.Command(nsenterPath, fullArgs...)
}
// AbsHostPath returns the absolute runnable path for a specified command
func (ne *Nsenter) AbsHostPath(command string) string {
path, ok := ne.paths[command]
if !ok {
return command
}
return path
}
// SupportsSystemd checks whether command systemd-run exists
func (ne *Nsenter) SupportsSystemd() (string, bool) {
systemdRunPath, hasSystemd := ne.paths["systemd-run"]
return systemdRunPath, hasSystemd
}

View File

@ -0,0 +1,50 @@
// +build !linux
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package nsenter
import (
"k8s.io/utils/exec"
)
// Nsenter is part of experimental support for running the kubelet
// in a container.
type Nsenter struct {
// a map of commands to their paths on the host filesystem
Paths map[string]string
}
// NewNsenter constructs a new instance of Nsenter
func NewNsenter() *Nsenter {
return &Nsenter{}
}
// Exec executes nsenter commands in hostProcMountNsPath mount namespace
func (ne *Nsenter) Exec(args ...string) exec.Cmd {
return nil
}
// AbsHostPath returns the absolute runnable path for a specified command
func (ne *Nsenter) AbsHostPath(command string) string {
return ""
}
// SupportsSystemd checks whether command systemd-run exists
func (ne *Nsenter) SupportsSystemd() (string, bool) {
return "", false
}

View File

@ -34,27 +34,51 @@ var _ mount.Interface = &fakeMounter{}
func (mounter *fakeMounter) Mount(source string, target string, fstype string, options []string) error { func (mounter *fakeMounter) Mount(source string, target string, fstype string, options []string) error {
return errors.New("not implemented") return errors.New("not implemented")
} }
func (mounter *fakeMounter) Unmount(target string) error { func (mounter *fakeMounter) Unmount(target string) error {
return errors.New("not implemented") return errors.New("not implemented")
} }
func (mounter *fakeMounter) List() ([]mount.MountPoint, error) { func (mounter *fakeMounter) List() ([]mount.MountPoint, error) {
return nil, errors.New("not implemented") return nil, errors.New("not implemented")
} }
func (mounter fakeMounter) DeviceOpened(pathname string) (bool, error) { func (mounter fakeMounter) DeviceOpened(pathname string) (bool, error) {
return false, errors.New("not implemented") return false, errors.New("not implemented")
} }
func (mounter *fakeMounter) PathIsDevice(pathname string) (bool, error) { func (mounter *fakeMounter) PathIsDevice(pathname string) (bool, error) {
return false, errors.New("not implemented") return false, errors.New("not implemented")
} }
func (mounter *fakeMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { func (mounter *fakeMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
return "", errors.New("not implemented") return "", errors.New("not implemented")
} }
func (mounter *fakeMounter) IsMountPointMatch(mp mount.MountPoint, dir string) bool { func (mounter *fakeMounter) IsMountPointMatch(mp mount.MountPoint, dir string) bool {
return (mp.Path == dir) return mp.Path == dir
} }
func (mounter *fakeMounter) IsNotMountPoint(dir string) (bool, error) { func (mounter *fakeMounter) IsNotMountPoint(dir string) (bool, error) {
return mount.IsNotMountPoint(mounter, dir) return mount.IsNotMountPoint(mounter, dir)
} }
func (mounter *fakeMounter) GetFileType(pathname string) (mount.FileType, error) {
return mount.FileType("fake"), errors.New("not implemented")
}
func (mounter *fakeMounter) MakeDir(pathname string) error {
return nil
}
func (mounter *fakeMounter) MakeFile(pathname string) error {
return nil
}
func (mounter *fakeMounter) ExistsPath(pathname string) bool {
return true
}
func (mounter *fakeMounter) IsLikelyNotMountPoint(file string) (bool, error) { func (mounter *fakeMounter) IsLikelyNotMountPoint(file string) (bool, error) {
name := path.Base(file) name := path.Base(file)
if strings.HasPrefix(name, "mount") { if strings.HasPrefix(name, "mount") {

View File

@ -11,21 +11,9 @@ go_library(
srcs = [ srcs = [
"doc.go", "doc.go",
"host_path.go", "host_path.go",
"nsenter_unsupported.go", ],
] + select({
"@io_bazel_rules_go//go/platform:darwin_amd64": [
"host_path_unix.go",
],
"@io_bazel_rules_go//go/platform:linux_amd64": [
"host_path_unix.go",
"nsenter.go",
],
"@io_bazel_rules_go//go/platform:windows_amd64": [
"host_path_windows.go",
],
"//conditions:default": [],
}),
deps = [ deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library", "//pkg/volume:go_default_library",
"//pkg/volume/util/volumehelper:go_default_library", "//pkg/volume/util/volumehelper:go_default_library",
"//pkg/volume/validation:go_default_library", "//pkg/volume/validation:go_default_library",
@ -33,37 +21,25 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
] + select({ ],
"@io_bazel_rules_go//go/platform:linux_amd64": [
"//vendor/k8s.io/utils/exec:go_default_library",
],
"//conditions:default": [],
}),
) )
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = select({ srcs = ["host_path_test.go"],
"@io_bazel_rules_go//go/platform:linux_amd64": [
"host_path_test.go",
],
"//conditions:default": [],
}),
library = ":go_default_library", library = ":go_default_library",
deps = select({ deps = [
"@io_bazel_rules_go//go/platform:linux_amd64": [ "//pkg/util/file:go_default_library",
"//pkg/util/file:go_default_library", "//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library", "//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library", "//pkg/volume/testing:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
], ],
"//conditions:default": [],
}),
) )
filegroup( filegroup(

View File

@ -25,6 +25,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util/volumehelper" "k8s.io/kubernetes/pkg/volume/util/volumehelper"
"k8s.io/kubernetes/pkg/volume/validation" "k8s.io/kubernetes/pkg/volume/validation"
@ -113,8 +114,9 @@ func (plugin *hostPathPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts vo
pathType = hostPathVolumeSource.Type pathType = hostPathVolumeSource.Type
} }
return &hostPathMounter{ return &hostPathMounter{
hostPath: &hostPath{path: path, pathType: pathType, containerized: opts.Containerized}, hostPath: &hostPath{path: path, pathType: pathType},
readOnly: readOnly, readOnly: readOnly,
mounter: plugin.host.GetMounter(plugin.GetPluginName()),
}, nil }, nil
} }
@ -182,9 +184,8 @@ func newProvisioner(options volume.VolumeOptions, host volume.VolumeHost, plugin
// HostPath volumes represent a bare host file or directory mount. // HostPath volumes represent a bare host file or directory mount.
// The direct at the specified path will be directly exposed to the container. // The direct at the specified path will be directly exposed to the container.
type hostPath struct { type hostPath struct {
path string path string
pathType *v1.HostPathType pathType *v1.HostPathType
containerized bool
volume.MetricsNil volume.MetricsNil
} }
@ -195,6 +196,7 @@ func (hp *hostPath) GetPath() string {
type hostPathMounter struct { type hostPathMounter struct {
*hostPath *hostPath
readOnly bool readOnly bool
mounter mount.Interface
} }
var _ volume.Mounter = &hostPathMounter{} var _ volume.Mounter = &hostPathMounter{}
@ -224,7 +226,7 @@ func (b *hostPathMounter) SetUp(fsGroup *int64) error {
if *b.pathType == v1.HostPathUnset { if *b.pathType == v1.HostPathUnset {
return nil return nil
} }
return checkType(b.GetPath(), b.pathType, b.containerized) return checkType(b.GetPath(), b.pathType, b.mounter)
} }
// SetUpAt does not make sense for host paths - probably programmer error. // SetUpAt does not make sense for host paths - probably programmer error.
@ -340,132 +342,77 @@ type hostPathTypeChecker interface {
GetPath() string GetPath() string
} }
type fileTypeChecker interface { type fileTypeChecker struct {
getFileType(fileInfo os.FileInfo) (v1.HostPathType, error)
}
// this is implemented in per-OS files
type defaultFileTypeChecker struct{}
type osFileTypeChecker struct {
path string path string
exists bool exists bool
info os.FileInfo mounter mount.Interface
checker fileTypeChecker
} }
func (ftc *osFileTypeChecker) Exists() bool { func (ftc *fileTypeChecker) Exists() bool {
return ftc.exists return ftc.mounter.ExistsPath(ftc.path)
} }
func (ftc *osFileTypeChecker) IsFile() bool { func (ftc *fileTypeChecker) IsFile() bool {
if !ftc.Exists() { if !ftc.Exists() {
return false return false
} }
return !ftc.info.IsDir() return !ftc.IsDir()
} }
func (ftc *osFileTypeChecker) MakeFile() error { func (ftc *fileTypeChecker) MakeFile() error {
f, err := os.OpenFile(ftc.path, os.O_CREATE, os.FileMode(0644)) return ftc.mounter.MakeFile(ftc.path)
defer f.Close()
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
} }
func (ftc *osFileTypeChecker) IsDir() bool { func (ftc *fileTypeChecker) IsDir() bool {
if !ftc.Exists() { if !ftc.Exists() {
return false return false
} }
return ftc.info.IsDir() pathType, err := ftc.mounter.GetFileType(ftc.path)
}
func (ftc *osFileTypeChecker) MakeDir() error {
err := os.MkdirAll(ftc.path, os.FileMode(0755))
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
}
func (ftc *osFileTypeChecker) IsBlock() bool {
if !ftc.Exists() {
return false
}
blkDevType, err := ftc.checker.getFileType(ftc.info)
if err != nil { if err != nil {
return false return false
} }
return blkDevType == v1.HostPathBlockDev return string(pathType) == string(v1.HostPathDirectory)
} }
func (ftc *osFileTypeChecker) IsChar() bool { func (ftc *fileTypeChecker) MakeDir() error {
if !ftc.Exists() { return ftc.mounter.MakeDir(ftc.path)
return false }
}
charDevType, err := ftc.checker.getFileType(ftc.info) func (ftc *fileTypeChecker) IsBlock() bool {
blkDevType, err := ftc.mounter.GetFileType(ftc.path)
if err != nil { if err != nil {
return false return false
} }
return charDevType == v1.HostPathCharDev return string(blkDevType) == string(v1.HostPathBlockDev)
} }
func (ftc *osFileTypeChecker) IsSocket() bool { func (ftc *fileTypeChecker) IsChar() bool {
if !ftc.Exists() { charDevType, err := ftc.mounter.GetFileType(ftc.path)
return false
}
socketType, err := ftc.checker.getFileType(ftc.info)
if err != nil { if err != nil {
return false return false
} }
return socketType == v1.HostPathSocket return string(charDevType) == string(v1.HostPathCharDev)
} }
func (ftc *osFileTypeChecker) GetPath() string { func (ftc *fileTypeChecker) IsSocket() bool {
socketType, err := ftc.mounter.GetFileType(ftc.path)
if err != nil {
return false
}
return string(socketType) == string(v1.HostPathSocket)
}
func (ftc *fileTypeChecker) GetPath() string {
return ftc.path return ftc.path
} }
func newOSFileTypeChecker(path string, checker fileTypeChecker) (hostPathTypeChecker, error) { func newFileTypeChecker(path string, mounter mount.Interface) hostPathTypeChecker {
ftc := osFileTypeChecker{path: path, checker: checker} return &fileTypeChecker{path: path, mounter: mounter}
info, err := os.Stat(path)
if err != nil {
ftc.exists = false
if !os.IsNotExist(err) {
return nil, err
}
} else {
ftc.info = info
ftc.exists = true
}
return &ftc, nil
} }
func checkType(path string, pathType *v1.HostPathType, containerized bool) error { // checkType checks whether the given path is the exact pathType
var ftc hostPathTypeChecker func checkType(path string, pathType *v1.HostPathType, mounter mount.Interface) error {
var err error return checkTypeInternal(newFileTypeChecker(path, mounter), pathType)
if containerized {
// For a containerized kubelet, use nsenter to run commands in
// the host's mount namespace.
// TODO(dixudx): setns into docker's mount namespace, and then run the exact same go code for checks/setup
ftc, err = newNsenterFileTypeChecker(path)
if err != nil {
return err
}
} else {
ftc, err = newOSFileTypeChecker(path, &defaultFileTypeChecker{})
if err != nil {
return err
}
}
return checkTypeInternal(ftc, pathType)
} }
func checkTypeInternal(ftc hostPathTypeChecker, pathType *v1.HostPathType) error { func checkTypeInternal(ftc hostPathTypeChecker, pathType *v1.HostPathType) error {

View File

@ -1,5 +1,3 @@
// +build linux
/* /*
Copyright 2014 The Kubernetes Authors. Copyright 2014 The Kubernetes Authors.
@ -30,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
utilfile "k8s.io/kubernetes/pkg/util/file" utilfile "k8s.io/kubernetes/pkg/util/file"
utilmount "k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing" volumetest "k8s.io/kubernetes/pkg/volume/testing"
) )
@ -323,8 +322,58 @@ type fakeFileTypeChecker struct {
desiredType string desiredType string
} }
func (fftc *fakeFileTypeChecker) getFileType(_ os.FileInfo) (v1.HostPathType, error) { func (fftc *fakeFileTypeChecker) Mount(source string, target string, fstype string, options []string) error {
return *newHostPathType(fftc.desiredType), nil return nil
}
func (fftc *fakeFileTypeChecker) Unmount(target string) error {
return nil
}
func (fftc *fakeFileTypeChecker) List() ([]utilmount.MountPoint, error) {
return nil, nil
}
func (fftc *fakeFileTypeChecker) IsMountPointMatch(mp utilmount.MountPoint, dir string) bool {
return false
}
func (fftc *fakeFileTypeChecker) IsNotMountPoint(file string) (bool, error) {
return false, nil
}
func (fftc *fakeFileTypeChecker) IsLikelyNotMountPoint(file string) (bool, error) {
return false, nil
}
func (fftc *fakeFileTypeChecker) DeviceOpened(pathname string) (bool, error) {
return false, nil
}
func (fftc *fakeFileTypeChecker) PathIsDevice(pathname string) (bool, error) {
return false, nil
}
func (fftc *fakeFileTypeChecker) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
return "fake", nil
}
func (fftc *fakeFileTypeChecker) MakeRShared(path string) error {
return nil
}
func (fftc *fakeFileTypeChecker) MakeFile(pathname string) error {
return nil
}
func (fftc *fakeFileTypeChecker) MakeDir(pathname string) error {
return nil
}
func (fftc *fakeFileTypeChecker) ExistsPath(pathname string) bool {
return true
}
func (fftc *fakeFileTypeChecker) GetFileType(_ string) (utilmount.FileType, error) {
return utilmount.FileType(fftc.desiredType), nil
} }
func setUp() error { func setUp() error {
@ -363,14 +412,16 @@ func TestOSFileTypeChecker(t *testing.T) {
isChar bool isChar bool
}{ }{
{ {
name: "Existing Folder", name: "Existing Folder",
path: "/tmp/ExistingFolder", path: "/tmp/ExistingFolder",
isDir: true, desiredType: string(utilmount.FileTypeDirectory),
isDir: true,
}, },
{ {
name: "Existing File", name: "Existing File",
path: "/tmp/ExistingFolder/foo", path: "/tmp/ExistingFolder/foo",
isFile: true, desiredType: string(utilmount.FileTypeFile),
isFile: true,
}, },
{ {
name: "Existing Socket File", name: "Existing Socket File",
@ -393,11 +444,8 @@ func TestOSFileTypeChecker(t *testing.T) {
} }
for i, tc := range testCases { for i, tc := range testCases {
oftc, err := newOSFileTypeChecker(tc.path, fakeFTC := &fakeFileTypeChecker{desiredType: tc.desiredType}
&fakeFileTypeChecker{desiredType: tc.desiredType}) oftc := newFileTypeChecker(tc.path, fakeFTC)
if err != nil {
t.Errorf("[%d: %q] expect nil, but got %v", i, tc.name, err)
}
path := oftc.GetPath() path := oftc.GetPath()
if path != tc.path { if path != tc.path {

View File

@ -1,40 +0,0 @@
// +build linux darwin
/*
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 host_path
import (
"fmt"
"os"
"syscall"
"k8s.io/api/core/v1"
)
func (dftc *defaultFileTypeChecker) getFileType(info os.FileInfo) (v1.HostPathType, error) {
mode := info.Sys().(*syscall.Stat_t).Mode
switch mode & syscall.S_IFMT {
case syscall.S_IFSOCK:
return v1.HostPathSocket, nil
case syscall.S_IFBLK:
return v1.HostPathBlockDev, nil
case syscall.S_IFCHR:
return v1.HostPathCharDev, nil
}
return "", fmt.Errorf("only recognise socket, block device and character device")
}

View File

@ -1,38 +0,0 @@
/*
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 host_path
import (
"fmt"
"os"
"syscall"
"k8s.io/api/core/v1"
)
func (dftc *defaultFileTypeChecker) getFileType(info os.FileInfo) (v1.HostPathType, error) {
mode := info.Sys().(*syscall.Win32FileAttributeData).FileAttributes
switch mode & syscall.S_IFMT {
case syscall.S_IFSOCK:
return v1.HostPathSocket, nil
case syscall.S_IFBLK:
return v1.HostPathBlockDev, nil
case syscall.S_IFCHR:
return v1.HostPathCharDev, nil
}
return "", fmt.Errorf("only recognise socket, block device and character device")
}

View File

@ -1,150 +0,0 @@
// +build linux
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package host_path
import (
"fmt"
"k8s.io/utils/exec"
)
const (
hostProcMountsNamespace = "/rootfs/proc/1/ns/mnt"
nsenterCmd = "nsenter"
statCmd = "stat"
touchCmd = "touch"
mkdirCmd = "mkdir"
)
// nsenterFileTypeChecker is part of experimental support for running the kubelet
// in a container. nsenterFileTypeChecker works by executing "nsenter" to run commands in
// the host's mount namespace.
//
// nsenterFileTypeChecker requires:
//
// 1. The host's root filesystem must be available at "/rootfs";
// 2. The "nsenter" binary must be on the Kubelet process' PATH in the container's
// filesystem;
// 3. The Kubelet process must have CAP_SYS_ADMIN (required by "nsenter"); at
// the present, this effectively means that the kubelet is running in a
// privileged container;
// 4. The host image must have "stat", "touch", "mkdir" binaries in "/bin", "/usr/sbin", or "/usr/bin";
type nsenterFileTypeChecker struct {
path string
exists bool
}
func newNsenterFileTypeChecker(path string) (hostPathTypeChecker, error) {
ftc := &nsenterFileTypeChecker{path: path}
ftc.Exists()
return ftc, nil
}
func (ftc *nsenterFileTypeChecker) Exists() bool {
args := []string{
fmt.Sprintf("--mount=%s", hostProcMountsNamespace),
"--",
"ls",
ftc.path,
}
exec := exec.New()
_, err := exec.Command(nsenterCmd, args...).CombinedOutput()
if err == nil {
ftc.exists = true
}
return ftc.exists
}
func (ftc *nsenterFileTypeChecker) IsFile() bool {
if !ftc.Exists() {
return false
}
return !ftc.IsDir()
}
func (ftc *nsenterFileTypeChecker) MakeFile() error {
args := []string{
fmt.Sprintf("--mount=%s", hostProcMountsNamespace),
"--",
touchCmd,
ftc.path,
}
exec := exec.New()
if _, err := exec.Command(nsenterCmd, args...).CombinedOutput(); err != nil {
return err
}
return nil
}
func (ftc *nsenterFileTypeChecker) IsDir() bool {
return ftc.checkMimetype("directory")
}
func (ftc *nsenterFileTypeChecker) MakeDir() error {
args := []string{
fmt.Sprintf("--mount=%s", hostProcMountsNamespace),
"--",
mkdirCmd,
"-p",
ftc.path,
}
exec := exec.New()
if _, err := exec.Command(nsenterCmd, args...).CombinedOutput(); err != nil {
return err
}
return nil
}
func (ftc *nsenterFileTypeChecker) IsBlock() bool {
return ftc.checkMimetype("block special file")
}
func (ftc *nsenterFileTypeChecker) IsChar() bool {
return ftc.checkMimetype("character special file")
}
func (ftc *nsenterFileTypeChecker) IsSocket() bool {
return ftc.checkMimetype("socket")
}
func (ftc *nsenterFileTypeChecker) GetPath() string {
return ftc.path
}
func (ftc *nsenterFileTypeChecker) checkMimetype(checkedType string) bool {
if !ftc.Exists() {
return false
}
args := []string{
fmt.Sprintf("--mount=%s", hostProcMountsNamespace),
"--",
statCmd,
"-L",
`--printf "%F"`,
ftc.path,
}
exec := exec.New()
outputBytes, err := exec.Command(nsenterCmd, args...).CombinedOutput()
if err != nil {
return false
}
return string(outputBytes) == checkedType
}

View File

@ -1,66 +0,0 @@
// +build !linux
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package host_path
type nsenterFileTypeChecker struct {
path string
exists bool
}
func newNsenterFileTypeChecker(path string) (hostPathTypeChecker, error) {
ftc := &nsenterFileTypeChecker{path: path}
ftc.Exists()
return ftc, nil
}
func (ftc *nsenterFileTypeChecker) Exists() bool {
return false
}
func (ftc *nsenterFileTypeChecker) IsFile() bool {
return false
}
func (ftc *nsenterFileTypeChecker) MakeFile() error {
return nil
}
func (ftc *nsenterFileTypeChecker) IsDir() bool {
return false
}
func (ftc *nsenterFileTypeChecker) MakeDir() error {
return nil
}
func (ftc *nsenterFileTypeChecker) IsBlock() bool {
return false
}
func (ftc *nsenterFileTypeChecker) IsChar() bool {
return false
}
func (ftc *nsenterFileTypeChecker) IsSocket() bool {
return false
}
func (ftc *nsenterFileTypeChecker) GetPath() string {
return ftc.path
}

View File

@ -69,8 +69,6 @@ type VolumeOptions struct {
CloudTags *map[string]string CloudTags *map[string]string
// Volume provisioning parameters from StorageClass // Volume provisioning parameters from StorageClass
Parameters map[string]string Parameters map[string]string
// This flag helps identify whether kubelet is running in a container
Containerized bool
} }
type DynamicPluginProber interface { type DynamicPluginProber interface {