Commit 5394aa97 authored by Paul Morie's avatar Paul Morie

Make emptyDir volumes work for non-root UIDs

parent 63cf00d2
# Copyright 2015 Google Inc. All rights reserved.
#
# 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.
FROM gcr.io/google-containers/mounttest:0.3
USER 1001
all: push
TAG = 0.1
image:
sudo docker build -t gcr.io/google_containers/mounttest-user:$(TAG) .
push: image
gcloud docker push gcr.io/google_containers/mounttest-user:$(TAG)
all: push all: push
TAG = 0.2 TAG = 0.3
mt: mt.go mt: mt.go
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' ./mt.go CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' ./mt.go
......
...@@ -25,17 +25,23 @@ import ( ...@@ -25,17 +25,23 @@ import (
) )
var ( var (
fsTypePath = "" fsTypePath = ""
fileModePath = "" fileModePath = ""
readFileContentPath = "" filePermPath = ""
readWriteNewFilePath = "" readFileContentPath = ""
newFilePath0644 = ""
newFilePath0666 = ""
newFilePath0777 = ""
) )
func init() { func init() {
flag.StringVar(&fsTypePath, "fs_type", "", "Path to print the fs type for") flag.StringVar(&fsTypePath, "fs_type", "", "Path to print the fs type for")
flag.StringVar(&fileModePath, "file_mode", "", "Path to print the filemode of") flag.StringVar(&fileModePath, "file_mode", "", "Path to print the mode bits of")
flag.StringVar(&filePermPath, "file_perm", "", "Path to print the perms of")
flag.StringVar(&readFileContentPath, "file_content", "", "Path to read the file content from") flag.StringVar(&readFileContentPath, "file_content", "", "Path to read the file content from")
flag.StringVar(&readWriteNewFilePath, "rw_new_file", "", "Path to write to and read from") flag.StringVar(&newFilePath0644, "new_file_0644", "", "Path to write to and read from with perm 0644")
flag.StringVar(&newFilePath0666, "new_file_0666", "", "Path to write to and read from with perm 0666")
flag.StringVar(&newFilePath0777, "new_file_0777", "", "Path to write to and read from with perm 0777")
} }
// This program performs some tests on the filesystem as dictated by the // This program performs some tests on the filesystem as dictated by the
...@@ -48,6 +54,9 @@ func main() { ...@@ -48,6 +54,9 @@ func main() {
errs = []error{} errs = []error{}
) )
// Clear the umask so we can set any mode bits we want.
syscall.Umask(0000)
// NOTE: the ordering of execution of the various command line // NOTE: the ordering of execution of the various command line
// flags is intentional and allows a single command to: // flags is intentional and allows a single command to:
// //
...@@ -62,7 +71,17 @@ func main() { ...@@ -62,7 +71,17 @@ func main() {
errs = append(errs, err) errs = append(errs, err)
} }
err = readWriteNewFile(readWriteNewFilePath) err = readWriteNewFile(newFilePath0644, 0644)
if err != nil {
errs = append(errs, err)
}
err = readWriteNewFile(newFilePath0666, 0666)
if err != nil {
errs = append(errs, err)
}
err = readWriteNewFile(newFilePath0777, 0777)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
...@@ -72,6 +91,11 @@ func main() { ...@@ -72,6 +91,11 @@ func main() {
errs = append(errs, err) errs = append(errs, err)
} }
err = filePerm(filePermPath)
if err != nil {
errs = append(errs, err)
}
err = readFileContent(readFileContentPath) err = readFileContent(readFileContentPath)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
...@@ -94,7 +118,7 @@ func fsType(path string) error { ...@@ -94,7 +118,7 @@ func fsType(path string) error {
buf := syscall.Statfs_t{} buf := syscall.Statfs_t{}
if err := syscall.Statfs(path, &buf); err != nil { if err := syscall.Statfs(path, &buf); err != nil {
fmt.Printf("error from statfs(%q): %v", path, err) fmt.Printf("error from statfs(%q): %v\n", path, err)
return err return err
} }
...@@ -122,6 +146,21 @@ func fileMode(path string) error { ...@@ -122,6 +146,21 @@ func fileMode(path string) error {
return nil return nil
} }
func filePerm(path string) error {
if path == "" {
return nil
}
fileinfo, err := os.Lstat(path)
if err != nil {
fmt.Printf("error from Lstat(%q): %v\n", path, err)
return err
}
fmt.Printf("perms of file %q: %v\n", path, fileinfo.Mode().Perm())
return nil
}
func readFileContent(path string) error { func readFileContent(path string) error {
if path == "" { if path == "" {
return nil return nil
...@@ -138,13 +177,13 @@ func readFileContent(path string) error { ...@@ -138,13 +177,13 @@ func readFileContent(path string) error {
return nil return nil
} }
func readWriteNewFile(path string) error { func readWriteNewFile(path string, perm os.FileMode) error {
if path == "" { if path == "" {
return nil return nil
} }
content := "mount-tester new file\n" content := "mount-tester new file\n"
err := ioutil.WriteFile(path, []byte(content), 0644) err := ioutil.WriteFile(path, []byte(content), perm)
if err != nil { if err != nil {
fmt.Printf("error writing new file %q: %v\n", path, err) fmt.Printf("error writing new file %q: %v\n", path, err)
return err return err
......
...@@ -16,7 +16,12 @@ limitations under the License. ...@@ -16,7 +16,12 @@ limitations under the License.
package securitycontext package securitycontext
import "github.com/GoogleCloudPlatform/kubernetes/pkg/api" import (
"fmt"
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
// HasPrivilegedRequest returns the value of SecurityContext.Privileged, taking into account // HasPrivilegedRequest returns the value of SecurityContext.Privileged, taking into account
// the possibility of nils // the possibility of nils
...@@ -41,3 +46,23 @@ func HasCapabilitiesRequest(container *api.Container) bool { ...@@ -41,3 +46,23 @@ func HasCapabilitiesRequest(container *api.Container) bool {
} }
return len(container.SecurityContext.Capabilities.Add) > 0 || len(container.SecurityContext.Capabilities.Drop) > 0 return len(container.SecurityContext.Capabilities.Add) > 0 || len(container.SecurityContext.Capabilities.Drop) > 0
} }
const expectedSELinuxContextFields = 4
// ParseSELinuxOptions parses a string containing a full SELinux context
// (user, role, type, and level) into an SELinuxOptions object. If the
// context is malformed, an error is returned.
func ParseSELinuxOptions(context string) (*api.SELinuxOptions, error) {
fields := strings.SplitN(context, ":", expectedSELinuxContextFields)
if len(fields) != expectedSELinuxContextFields {
return nil, fmt.Errorf("expected %v fields in selinuxcontext; got %v (context: %v)", expectedSELinuxContextFields, len(fields), context)
}
return &api.SELinuxOptions{
User: fields[0],
Role: fields[1],
Type: fields[2],
Level: fields[3],
}, nil
}
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
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 securitycontext
import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
func TestParseSELinuxOptions(t *testing.T) {
cases := []struct {
name string
input string
expected *api.SELinuxOptions
}{
{
name: "simple",
input: "user_t:role_t:type_t:s0",
expected: &api.SELinuxOptions{
User: "user_t",
Role: "role_t",
Type: "type_t",
Level: "s0",
},
},
{
name: "simple + categories",
input: "user_t:role_t:type_t:s0:c0",
expected: &api.SELinuxOptions{
User: "user_t",
Role: "role_t",
Type: "type_t",
Level: "s0:c0",
},
},
{
name: "not enough fields",
input: "type_t:s0:c0",
},
}
for _, tc := range cases {
result, err := ParseSELinuxOptions(tc.input)
if err != nil {
if tc.expected == nil {
continue
} else {
t.Errorf("%v: unexpected error: %v", tc.name, err)
}
}
compareContexts(tc.name, tc.expected, result, t)
}
}
func compareContexts(name string, ex, ac *api.SELinuxOptions, t *testing.T) {
if e, a := ex.User, ac.User; e != a {
t.Errorf("%v: expected user: %v, got: %v", name, e, a)
}
if e, a := ex.Role, ac.Role; e != a {
t.Errorf("%v: expected role: %v, got: %v", name, e, a)
}
if e, a := ex.Type, ac.Type; e != a {
t.Errorf("%v: expected type: %v, got: %v", name, e, a)
}
if e, a := ex.Level, ac.Level; e != a {
t.Errorf("%v: expected level: %v, got: %v", name, e, a)
}
}
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
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 empty_dir
// chconRunner knows how to chcon a directory.
type chconRunner interface {
SetContext(dir, context string) error
}
// newChconRunner returns a new chconRunner.
func newChconRunner() chconRunner {
return &realChconRunner{}
}
// +build linux
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
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 empty_dir
import (
"github.com/docker/libcontainer/selinux"
)
type realChconRunner struct{}
func (_ *realChconRunner) SetContext(dir, context string) error {
// If SELinux is not enabled, return an empty string
if !selinux.SelinuxEnabled() {
return nil
}
return selinux.Setfilecon(dir, context)
}
// +build !linux
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
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 empty_dir
type realChconRunner struct{}
func (_ *realChconRunner) SetContext(dir, context string) error {
// NOP
return nil
}
...@@ -19,15 +19,24 @@ package empty_dir ...@@ -19,15 +19,24 @@ package empty_dir
import ( import (
"fmt" "fmt"
"os" "os"
"path"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/types" "github.com/GoogleCloudPlatform/kubernetes/pkg/types"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume" "github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
volumeutil "github.com/GoogleCloudPlatform/kubernetes/pkg/volume/util"
"github.com/golang/glog" "github.com/golang/glog"
) )
// TODO: in the near future, this will be changed to be more restrictive
// and the group will be set to allow containers to use emptyDir volumes
// from the group attribute.
//
// https://github.com/GoogleCloudPlatform/kubernetes/issues/2630
const perm os.FileMode = 0777
// This is the primary entrypoint for volume plugins. // This is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin { func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{ return []volume.VolumePlugin{
...@@ -61,22 +70,23 @@ func (plugin *emptyDirPlugin) CanSupport(spec *volume.Spec) bool { ...@@ -61,22 +70,23 @@ func (plugin *emptyDirPlugin) CanSupport(spec *volume.Spec) bool {
} }
func (plugin *emptyDirPlugin) NewBuilder(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions, mounter mount.Interface) (volume.Builder, error) { func (plugin *emptyDirPlugin) NewBuilder(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions, mounter mount.Interface) (volume.Builder, error) {
return plugin.newBuilderInternal(spec, pod, mounter, &realMountDetector{mounter}, opts) return plugin.newBuilderInternal(spec, pod, mounter, &realMountDetector{mounter}, opts, newChconRunner())
} }
func (plugin *emptyDirPlugin) newBuilderInternal(spec *volume.Spec, pod *api.Pod, mounter mount.Interface, mountDetector mountDetector, opts volume.VolumeOptions) (volume.Builder, error) { func (plugin *emptyDirPlugin) newBuilderInternal(spec *volume.Spec, pod *api.Pod, mounter mount.Interface, mountDetector mountDetector, opts volume.VolumeOptions, chconRunner chconRunner) (volume.Builder, error) {
medium := api.StorageMediumDefault medium := api.StorageMediumDefault
if spec.VolumeSource.EmptyDir != nil { // Support a non-specified source as EmptyDir. if spec.VolumeSource.EmptyDir != nil { // Support a non-specified source as EmptyDir.
medium = spec.VolumeSource.EmptyDir.Medium medium = spec.VolumeSource.EmptyDir.Medium
} }
return &emptyDir{ return &emptyDir{
podUID: pod.UID, pod: pod,
volName: spec.Name, volName: spec.Name,
medium: medium, medium: medium,
mounter: mounter, mounter: mounter,
mountDetector: mountDetector, mountDetector: mountDetector,
plugin: plugin, plugin: plugin,
rootContext: opts.RootContext, rootContext: opts.RootContext,
chconRunner: chconRunner,
}, nil }, nil
} }
...@@ -87,7 +97,7 @@ func (plugin *emptyDirPlugin) NewCleaner(volName string, podUID types.UID, mount ...@@ -87,7 +97,7 @@ func (plugin *emptyDirPlugin) NewCleaner(volName string, podUID types.UID, mount
func (plugin *emptyDirPlugin) newCleanerInternal(volName string, podUID types.UID, mounter mount.Interface, mountDetector mountDetector) (volume.Cleaner, error) { func (plugin *emptyDirPlugin) newCleanerInternal(volName string, podUID types.UID, mounter mount.Interface, mountDetector mountDetector) (volume.Cleaner, error) {
ed := &emptyDir{ ed := &emptyDir{
podUID: podUID, pod: &api.Pod{ObjectMeta: api.ObjectMeta{UID: podUID}},
volName: volName, volName: volName,
medium: api.StorageMediumDefault, // might be changed later medium: api.StorageMediumDefault, // might be changed later
mounter: mounter, mounter: mounter,
...@@ -117,13 +127,14 @@ const ( ...@@ -117,13 +127,14 @@ const (
// EmptyDir volumes are temporary directories exposed to the pod. // EmptyDir volumes are temporary directories exposed to the pod.
// These do not persist beyond the lifetime of a pod. // These do not persist beyond the lifetime of a pod.
type emptyDir struct { type emptyDir struct {
podUID types.UID pod *api.Pod
volName string volName string
medium api.StorageMedium medium api.StorageMedium
mounter mount.Interface mounter mount.Interface
mountDetector mountDetector mountDetector mountDetector
plugin *emptyDirPlugin plugin *emptyDirPlugin
rootContext string rootContext string
chconRunner chconRunner
} }
// SetUp creates new directory. // SetUp creates new directory.
...@@ -133,29 +144,58 @@ func (ed *emptyDir) SetUp() error { ...@@ -133,29 +144,58 @@ func (ed *emptyDir) SetUp() error {
// SetUpAt creates new directory. // SetUpAt creates new directory.
func (ed *emptyDir) SetUpAt(dir string) error { func (ed *emptyDir) SetUpAt(dir string) error {
isMnt, err := ed.mounter.IsMountPoint(dir)
// Getting an os.IsNotExist err from is a contingency; the directory
// may not exist yet, in which case, setup should run.
if err != nil && !os.IsNotExist(err) {
return err
}
// If the plugin readiness file is present for this volume, and the
// storage medium is the default, then the volume is ready. If the
// medium is memory, and a mountpoint is present, then the volume is
// ready.
if volumeutil.IsReady(ed.getMetaDir()) {
if ed.medium == api.StorageMediumMemory && isMnt {
return nil
} else if ed.medium == api.StorageMediumDefault {
return nil
}
}
// Determine the effective SELinuxOptions to use for this volume.
securityContext := ""
if selinuxEnabled() {
securityContext = ed.rootContext
}
switch ed.medium { switch ed.medium {
case api.StorageMediumDefault: case api.StorageMediumDefault:
return ed.setupDefault(dir) err = ed.setupDir(dir, securityContext)
case api.StorageMediumMemory: case api.StorageMediumMemory:
return ed.setupTmpfs(dir) err = ed.setupTmpfs(dir, securityContext)
default: default:
return fmt.Errorf("unknown storage medium %q", ed.medium) err = fmt.Errorf("unknown storage medium %q", ed.medium)
}
if err == nil {
volumeutil.SetReady(ed.getMetaDir())
} }
return err
} }
func (ed *emptyDir) IsReadOnly() bool { func (ed *emptyDir) IsReadOnly() bool {
return false return false
} }
func (ed *emptyDir) setupDefault(dir string) error { // setupTmpfs creates a tmpfs mount at the specified directory with the
return os.MkdirAll(dir, 0750) // specified SELinux context.
} func (ed *emptyDir) setupTmpfs(dir string, selinuxContext string) error {
func (ed *emptyDir) setupTmpfs(dir string) error {
if ed.mounter == nil { if ed.mounter == nil {
return fmt.Errorf("memory storage requested, but mounter is nil") return fmt.Errorf("memory storage requested, but mounter is nil")
} }
if err := os.MkdirAll(dir, 0750); err != nil { if err := ed.setupDir(dir, selinuxContext); err != nil {
return err return err
} }
// Make SetUp idempotent. // Make SetUp idempotent.
...@@ -170,28 +210,66 @@ func (ed *emptyDir) setupTmpfs(dir string) error { ...@@ -170,28 +210,66 @@ func (ed *emptyDir) setupTmpfs(dir string) error {
} }
// By default a tmpfs mount will receive a different SELinux context // By default a tmpfs mount will receive a different SELinux context
// from that of the Kubelet root directory which is not readable from // which is not readable from the SELinux context of a docker container.
// the SELinux context of a docker container. var opts []string
// if selinuxContext != "" {
// getTmpfsMountOptions gets the mount option to set the context of opts = []string{fmt.Sprintf("rootcontext=\"%v\"", selinuxContext)}
// the tmpfs mount so that it can be read from the SELinux context of } else {
// the container. opts = []string{}
opts := ed.getTmpfsMountOptions() }
glog.V(3).Infof("pod %v: mounting tmpfs for volume %v with opts %v", ed.podUID, ed.volName, opts)
glog.V(3).Infof("pod %v: mounting tmpfs for volume %v with opts %v", ed.pod.UID, ed.volName, opts)
return ed.mounter.Mount("tmpfs", dir, "tmpfs", opts) return ed.mounter.Mount("tmpfs", dir, "tmpfs", opts)
} }
func (ed *emptyDir) getTmpfsMountOptions() []string { // setupDir creates the directory with the specified SELinux context and
if ed.rootContext == "" { // the default permissions specified by the perm constant.
return []string{""} func (ed *emptyDir) setupDir(dir, selinuxContext string) error {
// Create the directory if it doesn't already exist.
if err := os.MkdirAll(dir, perm); err != nil {
return err
} }
return []string{fmt.Sprintf("rootcontext=\"%v\"", ed.rootContext)} // stat the directory to read permission bits
fileinfo, err := os.Lstat(dir)
if err != nil {
return err
}
if fileinfo.Mode().Perm() != perm.Perm() {
// If the permissions on the created directory are wrong, the
// kubelet is probably running with a umask set. In order to
// avoid clearing the umask for the entire process or locking
// the thread, clearing the umask, creating the dir, restoring
// the umask, and unlocking the thread, we do a chmod to set
// the specific bits we need.
err := os.Chmod(dir, perm)
if err != nil {
return err
}
fileinfo, err = os.Lstat(dir)
if err != nil {
return err
}
if fileinfo.Mode().Perm() != perm.Perm() {
glog.Errorf("Expected directory %q permissions to be: %s; got: %s", dir, perm.Perm(), fileinfo.Mode().Perm())
}
}
// Set the context on the directory, if appropriate
if selinuxContext != "" {
glog.V(3).Infof("Setting SELinux context for %v to %v", dir, selinuxContext)
return ed.chconRunner.SetContext(dir, selinuxContext)
}
return nil
} }
func (ed *emptyDir) GetPath() string { func (ed *emptyDir) GetPath() string {
name := emptyDirPluginName name := emptyDirPluginName
return ed.plugin.host.GetPodVolumeDir(ed.podUID, util.EscapeQualifiedNameForDisk(name), ed.volName) return ed.plugin.host.GetPodVolumeDir(ed.pod.UID, util.EscapeQualifiedNameForDisk(name), ed.volName)
} }
// TearDown simply discards everything in the directory. // TearDown simply discards everything in the directory.
...@@ -238,3 +316,7 @@ func (ed *emptyDir) teardownTmpfs(dir string) error { ...@@ -238,3 +316,7 @@ func (ed *emptyDir) teardownTmpfs(dir string) error {
} }
return nil return nil
} }
func (ed *emptyDir) getMetaDir() string {
return path.Join(ed.plugin.host.GetPodPluginDir(ed.pod.UID, util.EscapeQualifiedNameForDisk(emptyDirPluginName)), ed.volName)
}
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"syscall" "syscall"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
"github.com/docker/libcontainer/selinux"
"github.com/golang/glog" "github.com/golang/glog"
) )
...@@ -51,3 +52,8 @@ func (m *realMountDetector) GetMountMedium(path string) (storageMedium, bool, er ...@@ -51,3 +52,8 @@ func (m *realMountDetector) GetMountMedium(path string) (storageMedium, bool, er
} }
return mediumUnknown, isMnt, nil return mediumUnknown, isMnt, nil
} }
// selinuxEnabled determines whether SELinux is enabled.
func selinuxEnabled() bool {
return selinux.SelinuxEnabled()
}
...@@ -18,7 +18,9 @@ limitations under the License. ...@@ -18,7 +18,9 @@ limitations under the License.
package empty_dir package empty_dir
import "github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount" import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
)
// realMountDetector pretends to implement mediumer. // realMountDetector pretends to implement mediumer.
type realMountDetector struct { type realMountDetector struct {
...@@ -28,3 +30,7 @@ type realMountDetector struct { ...@@ -28,3 +30,7 @@ type realMountDetector struct {
func (m *realMountDetector) GetMountMedium(path string) (storageMedium, bool, error) { func (m *realMountDetector) GetMountMedium(path string) (storageMedium, bool, error) {
return mediumUnknown, false, nil return mediumUnknown, false, nil
} }
func selinuxEnabled() bool {
return false
}
...@@ -27,51 +27,182 @@ import ( ...@@ -27,51 +27,182 @@ import (
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
) )
const (
testImageRootUid = "gcr.io/google_containers/mounttest:0.3"
testImageNonRootUid = "gcr.io/google_containers/mounttest-user:0.1"
)
var _ = Describe("EmptyDir volumes", func() { var _ = Describe("EmptyDir volumes", func() {
f := NewFramework("emptydir") f := NewFramework("emptydir")
It("should have the correct mode", func() { It("volume on tmpfs should have the correct mode", func() {
volumePath := "/test-volume" doTestVolumeMode(f, testImageRootUid, api.StorageMediumMemory)
source := &api.EmptyDirVolumeSource{ })
Medium: api.StorageMediumMemory,
} It("should support (root,0644,tmpfs)", func() {
pod := testPodWithVolume(volumePath, source) doTest0644(f, testImageRootUid, api.StorageMediumMemory)
})
pod.Spec.Containers[0].Args = []string{
fmt.Sprintf("--fs_type=%v", volumePath), It("should support (root,0666,tmpfs)", func() {
fmt.Sprintf("--file_mode=%v", volumePath), doTest0666(f, testImageRootUid, api.StorageMediumMemory)
} })
f.TestContainerOutput("emptydir r/w on tmpfs", pod, 0, []string{
"mount type of \"/test-volume\": tmpfs", It("should support (root,0777,tmpfs)", func() {
"mode of file \"/test-volume\": dtrwxrwxrwx", // we expect the sticky bit (mode flag t) to be set for the dir doTest0777(f, testImageRootUid, api.StorageMediumMemory)
}) })
It("should support (non-root,0644,tmpfs)", func() {
doTest0644(f, testImageNonRootUid, api.StorageMediumMemory)
})
It("should support (non-root,0666,tmpfs)", func() {
doTest0666(f, testImageNonRootUid, api.StorageMediumMemory)
})
It("should support (non-root,0777,tmpfs)", func() {
doTest0777(f, testImageNonRootUid, api.StorageMediumMemory)
})
It("volume on default medium should have the correct mode", func() {
doTestVolumeMode(f, testImageRootUid, api.StorageMediumDefault)
})
It("should support (root,0644,default)", func() {
doTest0644(f, testImageRootUid, api.StorageMediumDefault)
})
It("should support (root,0666,default)", func() {
doTest0666(f, testImageRootUid, api.StorageMediumDefault)
})
It("should support (root,0777,default)", func() {
doTest0777(f, testImageRootUid, api.StorageMediumDefault)
}) })
It("should support r/w", func() { It("should support (non-root,0644,default)", func() {
volumePath := "/test-volume" doTest0644(f, testImageNonRootUid, api.StorageMediumDefault)
filePath := path.Join(volumePath, "test-file") })
source := &api.EmptyDirVolumeSource{
Medium: api.StorageMediumMemory, It("should support (non-root,0666,default)", func() {
} doTest0666(f, testImageNonRootUid, api.StorageMediumDefault)
pod := testPodWithVolume(volumePath, source) })
pod.Spec.Containers[0].Args = []string{ It("should support (non-root,0777,default)", func() {
fmt.Sprintf("--fs_type=%v", volumePath), doTest0777(f, testImageNonRootUid, api.StorageMediumDefault)
fmt.Sprintf("--rw_new_file=%v", filePath),
fmt.Sprintf("--file_mode=%v", filePath),
}
f.TestContainerOutput("emptydir r/w on tmpfs", pod, 0, []string{
"mount type of \"/test-volume\": tmpfs",
"mode of file \"/test-volume/test-file\": -rw-r--r--",
"content of file \"/test-volume/test-file\": mount-tester new file",
})
}) })
}) })
const containerName = "test-container" const (
const volumeName = "test-volume" containerName = "test-container"
volumeName = "test-volume"
)
func doTestVolumeMode(f *Framework, image string, medium api.StorageMedium) {
var (
volumePath = "/test-volume"
source = &api.EmptyDirVolumeSource{Medium: medium}
pod = testPodWithVolume(testImageRootUid, volumePath, source)
)
pod.Spec.Containers[0].Args = []string{
fmt.Sprintf("--fs_type=%v", volumePath),
fmt.Sprintf("--file_perm=%v", volumePath),
}
msg := fmt.Sprintf("emptydir volume type on %v", formatMedium(medium))
out := []string{
"perms of file \"/test-volume\": -rwxrwxrwx",
}
if medium == api.StorageMediumMemory {
out = append(out, "mount type of \"/test-volume\": tmpfs")
}
f.TestContainerOutput(msg, pod, 0, out)
}
func doTest0644(f *Framework, image string, medium api.StorageMedium) {
var (
volumePath = "/test-volume"
filePath = path.Join(volumePath, "test-file")
source = &api.EmptyDirVolumeSource{Medium: medium}
pod = testPodWithVolume(image, volumePath, source)
)
pod.Spec.Containers[0].Args = []string{
fmt.Sprintf("--fs_type=%v", volumePath),
fmt.Sprintf("--new_file_0644=%v", filePath),
fmt.Sprintf("--file_perm=%v", filePath),
}
msg := fmt.Sprintf("emptydir 0644 on %v", formatMedium(medium))
out := []string{
"perms of file \"/test-volume/test-file\": -rw-r--r--",
"content of file \"/test-volume/test-file\": mount-tester new file",
}
if medium == api.StorageMediumMemory {
out = append(out, "mount type of \"/test-volume\": tmpfs")
}
f.TestContainerOutput(msg, pod, 0, out)
}
func doTest0666(f *Framework, image string, medium api.StorageMedium) {
var (
volumePath = "/test-volume"
filePath = path.Join(volumePath, "test-file")
source = &api.EmptyDirVolumeSource{Medium: medium}
pod = testPodWithVolume(image, volumePath, source)
)
pod.Spec.Containers[0].Args = []string{
fmt.Sprintf("--fs_type=%v", volumePath),
fmt.Sprintf("--new_file_0666=%v", filePath),
fmt.Sprintf("--file_perm=%v", filePath),
}
msg := fmt.Sprintf("emptydir 0666 on %v", formatMedium(medium))
out := []string{
"perms of file \"/test-volume/test-file\": -rw-rw-rw-",
"content of file \"/test-volume/test-file\": mount-tester new file",
}
if medium == api.StorageMediumMemory {
out = append(out, "mount type of \"/test-volume\": tmpfs")
}
f.TestContainerOutput(msg, pod, 0, out)
}
func doTest0777(f *Framework, image string, medium api.StorageMedium) {
var (
volumePath = "/test-volume"
filePath = path.Join(volumePath, "test-file")
source = &api.EmptyDirVolumeSource{Medium: medium}
pod = testPodWithVolume(image, volumePath, source)
)
pod.Spec.Containers[0].Args = []string{
fmt.Sprintf("--fs_type=%v", volumePath),
fmt.Sprintf("--new_file_0777=%v", filePath),
fmt.Sprintf("--file_perm=%v", filePath),
}
msg := fmt.Sprintf("emptydir 0777 on %v", formatMedium(medium))
out := []string{
"perms of file \"/test-volume/test-file\": -rwxrwxrwx",
"content of file \"/test-volume/test-file\": mount-tester new file",
}
if medium == api.StorageMediumMemory {
out = append(out, "mount type of \"/test-volume\": tmpfs")
}
f.TestContainerOutput(msg, pod, 0, out)
}
func formatMedium(medium api.StorageMedium) string {
if medium == api.StorageMediumMemory {
return "tmpfs"
}
return "node default medium"
}
func testPodWithVolume(path string, source *api.EmptyDirVolumeSource) *api.Pod { func testPodWithVolume(image, path string, source *api.EmptyDirVolumeSource) *api.Pod {
podName := "pod-" + string(util.NewUUID()) podName := "pod-" + string(util.NewUUID())
return &api.Pod{ return &api.Pod{
...@@ -86,7 +217,7 @@ func testPodWithVolume(path string, source *api.EmptyDirVolumeSource) *api.Pod { ...@@ -86,7 +217,7 @@ func testPodWithVolume(path string, source *api.EmptyDirVolumeSource) *api.Pod {
Containers: []api.Container{ Containers: []api.Container{
{ {
Name: containerName, Name: containerName,
Image: "gcr.io/google_containers/mounttest:0.2", Image: image,
VolumeMounts: []api.VolumeMount{ VolumeMounts: []api.VolumeMount{
{ {
Name: volumeName, Name: volumeName,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment