passthrough readOnly to subpath

pull/8/head
Michelle Au 2018-04-23 17:13:27 -07:00
parent d23ad1f894
commit 6b1947c9ba
7 changed files with 232 additions and 16 deletions

View File

@ -207,6 +207,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h
VolumePath: volumePath, VolumePath: volumePath,
PodDir: podDir, PodDir: podDir,
ContainerName: container.Name, ContainerName: container.Name,
ReadOnly: mount.ReadOnly || vol.Mounter.GetAttributes().ReadOnly,
}) })
if err != nil { if err != nil {
// Don't pass detailed error back to the user because it could give information about host filesystem // Don't pass detailed error back to the user because it could give information about host filesystem

View File

@ -66,7 +66,7 @@ func TestBindMount(t *testing.T) {
expectedArgs = []string{"-t", fsType, "-o", "bind", sourcePath, destinationPath} expectedArgs = []string{"-t", fsType, "-o", "bind", sourcePath, destinationPath}
case 2: case 2:
// mount -t fstype -o "remount,opts" source target // mount -t fstype -o "remount,opts" source target
expectedArgs = []string{"-t", fsType, "-o", "remount," + strings.Join(mountOptions, ","), sourcePath, destinationPath} expectedArgs = []string{"-t", fsType, "-o", "bind,remount," + strings.Join(mountOptions, ","), sourcePath, destinationPath}
} }
if !reflect.DeepEqual(expectedArgs, args) { if !reflect.DeepEqual(expectedArgs, args) {
t.Errorf("expected arguments %q, got %q", strings.Join(expectedArgs, " "), strings.Join(args, " ")) t.Errorf("expected arguments %q, got %q", strings.Join(expectedArgs, " "), strings.Join(args, " "))

View File

@ -58,8 +58,10 @@ func (f *FakeMounter) Mount(source string, target string, fstype string, options
f.mutex.Lock() f.mutex.Lock()
defer f.mutex.Unlock() defer f.mutex.Unlock()
// find 'bind' option opts := []string{}
for _, option := range options { for _, option := range options {
// find 'bind' option
if option == "bind" { if option == "bind" {
// This is a bind-mount. In order to mimic linux behaviour, we must // This is a bind-mount. In order to mimic linux behaviour, we must
// use the original device of the bind-mount as the real source. // use the original device of the bind-mount as the real source.
@ -78,7 +80,11 @@ func (f *FakeMounter) Mount(source string, target string, fstype string, options
break break
} }
} }
break }
// find 'ro' option
if option == "ro" {
// reuse MountPoint.Opts field to mark mount as readonly
opts = append(opts, "ro")
} }
} }
@ -88,7 +94,7 @@ func (f *FakeMounter) Mount(source string, target string, fstype string, options
absTarget = target absTarget = target
} }
f.MountPoints = append(f.MountPoints, MountPoint{Device: source, Path: absTarget, Type: fstype}) f.MountPoints = append(f.MountPoints, MountPoint{Device: source, Path: absTarget, Type: fstype, Opts: opts})
glog.V(5).Infof("Fake mounter: mounted %s to %s", source, absTarget) glog.V(5).Infof("Fake mounter: mounted %s to %s", source, absTarget)
f.Log = append(f.Log, FakeAction{Action: FakeActionMount, Target: absTarget, Source: source, FSType: fstype}) f.Log = append(f.Log, FakeAction{Action: FakeActionMount, Target: absTarget, Source: source, FSType: fstype})
return nil return nil

View File

@ -123,6 +123,8 @@ type Subpath struct {
PodDir string PodDir string
// Name of the container // Name of the container
ContainerName string ContainerName string
// True if the mount needs to be readonly
ReadOnly 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,
@ -274,7 +276,13 @@ func IsNotMountPoint(mounter Interface, file string) (bool, error) {
// The list equals: // The list equals:
// options - 'bind' + 'remount' (no duplicate) // options - 'bind' + 'remount' (no duplicate)
func isBind(options []string) (bool, []string) { func isBind(options []string) (bool, []string) {
bindRemountOpts := []string{"remount"} // Because we have an FD opened on the subpath bind mount, the "bind" option
// needs to be included, otherwise the mount target will error as busy if you
// remount as readonly.
//
// As a consequence, all read only bind mounts will no longer change the underlying
// volume mount to be read only.
bindRemountOpts := []string{"bind", "remount"}
bind := false bind := false
if len(options) != 0 { if len(options) != 0 {

View File

@ -777,8 +777,13 @@ func doBindSubPath(mounter Interface, subpath Subpath, kubeletPid int) (hostPath
mountSource := fmt.Sprintf("/proc/%d/fd/%v", kubeletPid, fd) mountSource := fmt.Sprintf("/proc/%d/fd/%v", kubeletPid, fd)
// Do the bind mount // Do the bind mount
options := []string{"bind"}
if subpath.ReadOnly {
options = append(options, "ro")
}
glog.V(5).Infof("bind mounting %q at %q", mountSource, bindPathTarget) glog.V(5).Infof("bind mounting %q at %q", mountSource, bindPathTarget)
if err = mounter.Mount(mountSource, bindPathTarget, "" /*fstype*/, []string{"bind"}); err != nil { if err = mounter.Mount(mountSource, bindPathTarget, "" /*fstype*/, options); err != nil {
return "", fmt.Errorf("error mounting %s: %s", subpath.Path, err) return "", fmt.Errorf("error mounting %s: %s", subpath.Path, err)
} }

View File

@ -1009,6 +1009,7 @@ func getTestPaths(base string) (string, string) {
func TestBindSubPath(t *testing.T) { func TestBindSubPath(t *testing.T) {
defaultPerm := os.FileMode(0750) defaultPerm := os.FileMode(0750)
readOnlyPerm := os.FileMode(0444)
tests := []struct { tests := []struct {
name string name string
@ -1016,6 +1017,7 @@ func TestBindSubPath(t *testing.T) {
// base. // base.
prepare func(base string) ([]string, string, string, error) prepare func(base string) ([]string, string, string, error)
expectError bool expectError bool
readOnly bool
}{ }{
{ {
name: "subpath-dir", name: "subpath-dir",
@ -1220,6 +1222,55 @@ func TestBindSubPath(t *testing.T) {
}, },
expectError: false, expectError: false,
}, },
{
name: "subpath-dir-readonly",
prepare: func(base string) ([]string, string, string, error) {
volpath, _ := getTestPaths(base)
subpath := filepath.Join(volpath, "dir0")
return nil, volpath, subpath, os.MkdirAll(subpath, defaultPerm)
},
expectError: false,
readOnly: true,
},
{
name: "subpath-file-readonly",
prepare: func(base string) ([]string, string, string, error) {
volpath, _ := getTestPaths(base)
subpath := filepath.Join(volpath, "file0")
if err := os.MkdirAll(volpath, defaultPerm); err != nil {
return nil, "", "", err
}
return nil, volpath, subpath, ioutil.WriteFile(subpath, []byte{}, defaultPerm)
},
expectError: false,
readOnly: true,
},
{
name: "subpath-dir-and-volume-readonly",
prepare: func(base string) ([]string, string, string, error) {
volpath, _ := getTestPaths(base)
subpath := filepath.Join(volpath, "dir0")
if err := os.MkdirAll(subpath, defaultPerm); err != nil {
return nil, "", "", err
}
return nil, volpath, subpath, os.Chmod(subpath, readOnlyPerm)
},
expectError: false,
readOnly: true,
},
{
name: "subpath-file-and-vol-readonly",
prepare: func(base string) ([]string, string, string, error) {
volpath, _ := getTestPaths(base)
subpath := filepath.Join(volpath, "file0")
if err := os.MkdirAll(volpath, defaultPerm); err != nil {
return nil, "", "", err
}
return nil, volpath, subpath, ioutil.WriteFile(subpath, []byte{}, readOnlyPerm)
},
expectError: false,
readOnly: true,
},
} }
for _, test := range tests { for _, test := range tests {
@ -1244,6 +1295,7 @@ func TestBindSubPath(t *testing.T) {
VolumePath: volPath, VolumePath: volPath,
PodDir: filepath.Join(base, "pod0"), PodDir: filepath.Join(base, "pod0"),
ContainerName: testContainer, ContainerName: testContainer,
ReadOnly: test.readOnly,
} }
_, subpathMount := getTestPaths(base) _, subpathMount := getTestPaths(base)
@ -1269,12 +1321,39 @@ func TestBindSubPath(t *testing.T) {
if err = validateFileExists(subpathMount); err != nil { if err = validateFileExists(subpathMount); err != nil {
t.Errorf("test %q failed: %v", test.name, err) t.Errorf("test %q failed: %v", test.name, err)
} }
if err = validateReadOnlyMount(test.readOnly, bindPathTarget, fm); err != nil {
t.Errorf("test %q failed: %v", test.name, err)
}
} }
os.RemoveAll(base) os.RemoveAll(base)
} }
} }
func validateReadOnlyMount(expectedReadOnly bool, bindPathTarget string, mounter *FakeMounter) error {
mps, err := mounter.List()
if err != nil {
return fmt.Errorf("fakeMounter.List() returned error: %v", err)
}
for _, mp := range mps {
if mp.Path == bindPathTarget {
foundReadOnly := false
for _, opts := range mp.Opts {
if opts == "ro" {
foundReadOnly = true
break
}
}
if expectedReadOnly != foundReadOnly {
return fmt.Errorf("expected readOnly %v, got %v for mount point %v", expectedReadOnly, foundReadOnly, bindPathTarget)
} else {
return nil
}
}
}
return fmt.Errorf("failed to find mountPoint %v", bindPathTarget)
}
func TestParseMountInfo(t *testing.T) { func TestParseMountInfo(t *testing.T) {
info := info :=
`62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered `62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered

View File

@ -56,13 +56,14 @@ type volInfo struct {
type volSource interface { type volSource interface {
createVolume(f *framework.Framework) volInfo createVolume(f *framework.Framework) volInfo
cleanupVolume(f *framework.Framework) cleanupVolume(f *framework.Framework)
getReadOnlyVolumeSpec() *v1.VolumeSource
} }
var initVolSources = map[string]func() volSource{ var initVolSources = map[string]func() volSource{
"hostPath": initHostpath, "hostPath": initHostpath,
"hostPathSymlink": initHostpathSymlink, "hostPathSymlink": initHostpathSymlink,
"emptyDir": initEmptydir, "emptyDir": initEmptydir,
"gcePDPVC": initGCEPD, "gcePDPVC": initGCEPDPVC,
"gcePDPartitioned": initGCEPDPartition, "gcePDPartitioned": initGCEPDPartition,
"nfs": initNFS, "nfs": initNFS,
"nfsPVC": initNFSPVC, "nfsPVC": initNFSPVC,
@ -271,6 +272,46 @@ var _ = utils.SIGDescribe("Subpath", func() {
} }
testSubpathReconstruction(f, pod, true) testSubpathReconstruction(f, pod, true)
}) })
It("should support readOnly directory specified in the volumeMount", func() {
// Create the directory
setInitCommand(pod, fmt.Sprintf("mkdir -p %s", subPathDir))
// Write the file in the volume from container 1
setWriteCommand(filePathInVolume, &pod.Spec.Containers[1])
// Read it from inside the subPath from container 0
pod.Spec.Containers[0].VolumeMounts[0].ReadOnly = true
testReadFile(f, filePathInSubpath, pod, 0)
})
It("should support readOnly file specified in the volumeMount", func() {
// Create the file
setInitCommand(pod, fmt.Sprintf("touch %s", subPathDir))
// Write the file in the volume from container 1
setWriteCommand(subPathDir, &pod.Spec.Containers[1])
// Read it from inside the subPath from container 0
pod.Spec.Containers[0].VolumeMounts[0].ReadOnly = true
testReadFile(f, volumePath, pod, 0)
})
It("should support readOnly directory specified in the volumeSource", func() {
roVol := vol.getReadOnlyVolumeSpec()
if roVol == nil {
framework.Skipf("Volume type %v doesn't support readOnly source", curVolType)
}
// Initialize content in the volume while it's writable
initVolumeContent(f, pod, filePathInVolume, filePathInSubpath)
// Set volume source to read only
pod.Spec.Volumes[0].VolumeSource = *roVol
// Read it from inside the subPath from container 0
testReadFile(f, filePathInSubpath, pod, 0)
})
}) })
} }
@ -375,6 +416,12 @@ func testPodSubpath(f *framework.Framework, subpath, volumeType string, source *
} }
} }
func clearSubpathPodCommands(pod *v1.Pod) {
pod.Spec.InitContainers[0].Command = nil
pod.Spec.Containers[0].Args = nil
pod.Spec.Containers[1].Args = nil
}
func setInitCommand(pod *v1.Pod, command string) { func setInitCommand(pod *v1.Pod, command string) {
pod.Spec.InitContainers[0].Command = []string{"/bin/sh", "-ec", command} pod.Spec.InitContainers[0].Command = []string{"/bin/sh", "-ec", command}
} }
@ -570,6 +617,23 @@ func testSubpathReconstruction(f *framework.Framework, pod *v1.Pod, forceDelete
utils.TestVolumeUnmountsFromDeletedPodWithForceOption(f.ClientSet, f, pod, forceDelete, true) utils.TestVolumeUnmountsFromDeletedPodWithForceOption(f.ClientSet, f, pod, forceDelete, true)
} }
func initVolumeContent(f *framework.Framework, pod *v1.Pod, volumeFilepath, subpathFilepath string) {
setWriteCommand(volumeFilepath, &pod.Spec.Containers[1])
setReadCommand(subpathFilepath, &pod.Spec.Containers[0])
By(fmt.Sprintf("Creating pod to write volume content %s", pod.Name))
f.TestContainerOutput("subpath", pod, 0, []string{
"content of file \"" + subpathFilepath + "\": mount-tester new file",
})
By(fmt.Sprintf("Deleting pod %s", pod.Name))
err := framework.DeletePodWithWait(f, f.ClientSet, pod)
Expect(err).NotTo(HaveOccurred(), "while deleting pod")
// This pod spec is going to be reused; reset all the commands
clearSubpathPodCommands(pod)
}
func podContainerExec(pod *v1.Pod, containerIndex int, bashExec string) (string, error) { func podContainerExec(pod *v1.Pod, containerIndex int, bashExec string) (string, error) {
return framework.RunKubectl("exec", fmt.Sprintf("--namespace=%s", pod.Namespace), pod.Name, "--container", pod.Spec.Containers[containerIndex].Name, "--", "/bin/sh", "-c", bashExec) return framework.RunKubectl("exec", fmt.Sprintf("--namespace=%s", pod.Namespace), pod.Name, "--container", pod.Spec.Containers[containerIndex].Name, "--", "/bin/sh", "-c", bashExec)
} }
@ -591,6 +655,10 @@ func (s *hostpathSource) createVolume(f *framework.Framework) volInfo {
} }
} }
func (s *hostpathSource) getReadOnlyVolumeSpec() *v1.VolumeSource {
return nil
}
func (s *hostpathSource) cleanupVolume(f *framework.Framework) { func (s *hostpathSource) cleanupVolume(f *framework.Framework) {
} }
@ -666,6 +734,10 @@ func (s *hostpathSymlinkSource) createVolume(f *framework.Framework) volInfo {
} }
} }
func (s *hostpathSymlinkSource) getReadOnlyVolumeSpec() *v1.VolumeSource {
return nil
}
func (s *hostpathSymlinkSource) cleanupVolume(f *framework.Framework) { func (s *hostpathSymlinkSource) cleanupVolume(f *framework.Framework) {
} }
@ -684,19 +756,23 @@ func (s *emptydirSource) createVolume(f *framework.Framework) volInfo {
} }
} }
func (s *emptydirSource) getReadOnlyVolumeSpec() *v1.VolumeSource {
return nil
}
func (s *emptydirSource) cleanupVolume(f *framework.Framework) { func (s *emptydirSource) cleanupVolume(f *framework.Framework) {
} }
type gcepdSource struct { type gcepdPVCSource struct {
pvc *v1.PersistentVolumeClaim pvc *v1.PersistentVolumeClaim
} }
func initGCEPD() volSource { func initGCEPDPVC() volSource {
framework.SkipUnlessProviderIs("gce", "gke") framework.SkipUnlessProviderIs("gce", "gke")
return &gcepdSource{} return &gcepdPVCSource{}
} }
func (s *gcepdSource) createVolume(f *framework.Framework) volInfo { func (s *gcepdPVCSource) createVolume(f *framework.Framework) volInfo {
var err error var err error
framework.Logf("Creating GCE PD volume via dynamic provisioning") framework.Logf("Creating GCE PD volume via dynamic provisioning")
@ -718,7 +794,16 @@ func (s *gcepdSource) createVolume(f *framework.Framework) volInfo {
} }
} }
func (s *gcepdSource) cleanupVolume(f *framework.Framework) { func (s *gcepdPVCSource) getReadOnlyVolumeSpec() *v1.VolumeSource {
return &v1.VolumeSource{
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
ClaimName: s.pvc.Name,
ReadOnly: true,
},
}
}
func (s *gcepdPVCSource) cleanupVolume(f *framework.Framework) {
if s.pvc != nil { if s.pvc != nil {
err := f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Delete(s.pvc.Name, nil) err := f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Delete(s.pvc.Name, nil)
framework.ExpectNoError(err, "Error deleting PVC") framework.ExpectNoError(err, "Error deleting PVC")
@ -756,6 +841,10 @@ func (s *gcepdPartitionSource) createVolume(f *framework.Framework) volInfo {
} }
} }
func (s *gcepdPartitionSource) getReadOnlyVolumeSpec() *v1.VolumeSource {
return nil
}
func (s *gcepdPartitionSource) cleanupVolume(f *framework.Framework) { func (s *gcepdPartitionSource) cleanupVolume(f *framework.Framework) {
if s.diskName != "" { if s.diskName != "" {
// err := framework.DeletePDWithRetry(s.diskName) // err := framework.DeletePDWithRetry(s.diskName)
@ -765,6 +854,7 @@ func (s *gcepdPartitionSource) cleanupVolume(f *framework.Framework) {
type nfsSource struct { type nfsSource struct {
serverPod *v1.Pod serverPod *v1.Pod
serverIP string
} }
func initNFS() volSource { func initNFS() volSource {
@ -772,21 +862,29 @@ func initNFS() volSource {
} }
func (s *nfsSource) createVolume(f *framework.Framework) volInfo { func (s *nfsSource) createVolume(f *framework.Framework) volInfo {
var serverIP string
framework.Logf("Creating NFS server") framework.Logf("Creating NFS server")
_, s.serverPod, serverIP = framework.NewNFSServer(f.ClientSet, f.Namespace.Name, []string{"-G", "777", "/exports"}) _, s.serverPod, s.serverIP = framework.NewNFSServer(f.ClientSet, f.Namespace.Name, []string{"-G", "777", "/exports"})
return volInfo{ return volInfo{
source: &v1.VolumeSource{ source: &v1.VolumeSource{
NFS: &v1.NFSVolumeSource{ NFS: &v1.NFSVolumeSource{
Server: serverIP, Server: s.serverIP,
Path: "/exports", Path: "/exports",
}, },
}, },
} }
} }
func (s *nfsSource) getReadOnlyVolumeSpec() *v1.VolumeSource {
return &v1.VolumeSource{
NFS: &v1.NFSVolumeSource{
Server: s.serverIP,
Path: "/exports",
ReadOnly: true,
},
}
}
func (s *nfsSource) cleanupVolume(f *framework.Framework) { func (s *nfsSource) cleanupVolume(f *framework.Framework) {
if s.serverPod != nil { if s.serverPod != nil {
framework.DeletePodWithWait(f, f.ClientSet, s.serverPod) framework.DeletePodWithWait(f, f.ClientSet, s.serverPod)
@ -816,6 +914,16 @@ func (s *glusterSource) createVolume(f *framework.Framework) volInfo {
} }
} }
func (s *glusterSource) getReadOnlyVolumeSpec() *v1.VolumeSource {
return &v1.VolumeSource{
Glusterfs: &v1.GlusterfsVolumeSource{
EndpointsName: "gluster-server",
Path: "test_vol",
ReadOnly: true,
},
}
}
func (s *glusterSource) cleanupVolume(f *framework.Framework) { func (s *glusterSource) cleanupVolume(f *framework.Framework) {
if s.serverPod != nil { if s.serverPod != nil {
framework.DeletePodWithWait(f, f.ClientSet, s.serverPod) framework.DeletePodWithWait(f, f.ClientSet, s.serverPod)
@ -875,6 +983,15 @@ func (s *nfsPVCSource) createVolume(f *framework.Framework) volInfo {
} }
} }
func (s *nfsPVCSource) getReadOnlyVolumeSpec() *v1.VolumeSource {
return &v1.VolumeSource{
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
ClaimName: s.pvc.Name,
ReadOnly: true,
},
}
}
func (s *nfsPVCSource) cleanupVolume(f *framework.Framework) { func (s *nfsPVCSource) cleanupVolume(f *framework.Framework) {
if s.pvc != nil || s.pv != nil { if s.pvc != nil || s.pv != nil {
if errs := framework.PVPVCCleanup(f.ClientSet, f.Namespace.Name, s.pv, s.pvc); len(errs) != 0 { if errs := framework.PVPVCCleanup(f.ClientSet, f.Namespace.Name, s.pv, s.pvc); len(errs) != 0 {