Commit 5dff0496 authored by Mike Danese's avatar Mike Danese

Merge pull request #9384 from pmorie/emptydir-nonroot

Support emptydir volumes for containers running as non-root
parents 4ddfa5d5 5394aa97
# 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
TAG = 0.2
TAG = 0.3
mt: mt.go
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' ./mt.go
......
......@@ -25,17 +25,23 @@ import (
)
var (
fsTypePath = ""
fileModePath = ""
readFileContentPath = ""
readWriteNewFilePath = ""
fsTypePath = ""
fileModePath = ""
filePermPath = ""
readFileContentPath = ""
newFilePath0644 = ""
newFilePath0666 = ""
newFilePath0777 = ""
)
func init() {
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(&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
......@@ -48,6 +54,9 @@ func main() {
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
// flags is intentional and allows a single command to:
//
......@@ -62,7 +71,17 @@ func main() {
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 {
errs = append(errs, err)
}
......@@ -72,6 +91,11 @@ func main() {
errs = append(errs, err)
}
err = filePerm(filePermPath)
if err != nil {
errs = append(errs, err)
}
err = readFileContent(readFileContentPath)
if err != nil {
errs = append(errs, err)
......@@ -94,7 +118,7 @@ func fsType(path string) error {
buf := syscall.Statfs_t{}
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
}
......@@ -122,6 +146,21 @@ func fileMode(path string) error {
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 {
if path == "" {
return nil
......@@ -138,13 +177,13 @@ func readFileContent(path string) error {
return nil
}
func readWriteNewFile(path string) error {
func readWriteNewFile(path string, perm os.FileMode) error {
if path == "" {
return nil
}
content := "mount-tester new file\n"
err := ioutil.WriteFile(path, []byte(content), 0644)
err := ioutil.WriteFile(path, []byte(content), perm)
if err != nil {
fmt.Printf("error writing new file %q: %v\n", path, err)
return err
......
......@@ -16,7 +16,12 @@ limitations under the License.
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
// the possibility of nils
......@@ -41,3 +46,23 @@ func HasCapabilitiesRequest(container *api.Container) bool {
}
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
import (
"fmt"
"os"
"path"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
volumeutil "github.com/GoogleCloudPlatform/kubernetes/pkg/volume/util"
"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.
func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{
......@@ -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) {
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
if spec.VolumeSource.EmptyDir != nil { // Support a non-specified source as EmptyDir.
medium = spec.VolumeSource.EmptyDir.Medium
}
return &emptyDir{
podUID: pod.UID,
pod: pod,
volName: spec.Name,
medium: medium,
mounter: mounter,
mountDetector: mountDetector,
plugin: plugin,
rootContext: opts.RootContext,
chconRunner: chconRunner,
}, nil
}
......@@ -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) {
ed := &emptyDir{
podUID: podUID,
pod: &api.Pod{ObjectMeta: api.ObjectMeta{UID: podUID}},
volName: volName,
medium: api.StorageMediumDefault, // might be changed later
mounter: mounter,
......@@ -117,13 +127,14 @@ const (
// EmptyDir volumes are temporary directories exposed to the pod.
// These do not persist beyond the lifetime of a pod.
type emptyDir struct {
podUID types.UID
pod *api.Pod
volName string
medium api.StorageMedium
mounter mount.Interface
mountDetector mountDetector
plugin *emptyDirPlugin
rootContext string
chconRunner chconRunner
}
// SetUp creates new directory.
......@@ -133,29 +144,58 @@ func (ed *emptyDir) SetUp() error {
// SetUpAt creates new directory.
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 {
case api.StorageMediumDefault:
return ed.setupDefault(dir)
err = ed.setupDir(dir, securityContext)
case api.StorageMediumMemory:
return ed.setupTmpfs(dir)
err = ed.setupTmpfs(dir, securityContext)
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 {
return false
}
func (ed *emptyDir) setupDefault(dir string) error {
return os.MkdirAll(dir, 0750)
}
func (ed *emptyDir) setupTmpfs(dir string) error {
// setupTmpfs creates a tmpfs mount at the specified directory with the
// specified SELinux context.
func (ed *emptyDir) setupTmpfs(dir string, selinuxContext string) error {
if ed.mounter == 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
}
// Make SetUp idempotent.
......@@ -170,28 +210,66 @@ func (ed *emptyDir) setupTmpfs(dir string) error {
}
// By default a tmpfs mount will receive a different SELinux context
// from that of the Kubelet root directory which is not readable from
// the SELinux context of a docker container.
//
// getTmpfsMountOptions gets the mount option to set the context of
// the tmpfs mount so that it can be read from the SELinux context of
// the container.
opts := ed.getTmpfsMountOptions()
glog.V(3).Infof("pod %v: mounting tmpfs for volume %v with opts %v", ed.podUID, ed.volName, opts)
// which is not readable from the SELinux context of a docker container.
var opts []string
if selinuxContext != "" {
opts = []string{fmt.Sprintf("rootcontext=\"%v\"", selinuxContext)}
} else {
opts = []string{}
}
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)
}
func (ed *emptyDir) getTmpfsMountOptions() []string {
if ed.rootContext == "" {
return []string{""}
// setupDir creates the directory with the specified SELinux context and
// the default permissions specified by the perm constant.
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 {
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.
......@@ -238,3 +316,7 @@ func (ed *emptyDir) teardownTmpfs(dir string) error {
}
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 (
"syscall"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
"github.com/docker/libcontainer/selinux"
"github.com/golang/glog"
)
......@@ -51,3 +52,8 @@ func (m *realMountDetector) GetMountMedium(path string) (storageMedium, bool, er
}
return mediumUnknown, isMnt, nil
}
// selinuxEnabled determines whether SELinux is enabled.
func selinuxEnabled() bool {
return selinux.SelinuxEnabled()
}
......@@ -18,7 +18,9 @@ limitations under the License.
package empty_dir
import "github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
)
// realMountDetector pretends to implement mediumer.
type realMountDetector struct {
......@@ -28,3 +30,7 @@ type realMountDetector struct {
func (m *realMountDetector) GetMountMedium(path string) (storageMedium, bool, error) {
return mediumUnknown, false, nil
}
func selinuxEnabled() bool {
return false
}
......@@ -27,51 +27,182 @@ import (
. "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() {
f := NewFramework("emptydir")
It("should have the correct mode", func() {
volumePath := "/test-volume"
source := &api.EmptyDirVolumeSource{
Medium: api.StorageMediumMemory,
}
pod := testPodWithVolume(volumePath, source)
pod.Spec.Containers[0].Args = []string{
fmt.Sprintf("--fs_type=%v", volumePath),
fmt.Sprintf("--file_mode=%v", volumePath),
}
f.TestContainerOutput("emptydir r/w on tmpfs", pod, 0, []string{
"mount type of \"/test-volume\": tmpfs",
"mode of file \"/test-volume\": dtrwxrwxrwx", // we expect the sticky bit (mode flag t) to be set for the dir
})
It("volume on tmpfs should have the correct mode", func() {
doTestVolumeMode(f, testImageRootUid, api.StorageMediumMemory)
})
It("should support (root,0644,tmpfs)", func() {
doTest0644(f, testImageRootUid, api.StorageMediumMemory)
})
It("should support (root,0666,tmpfs)", func() {
doTest0666(f, testImageRootUid, api.StorageMediumMemory)
})
It("should support (root,0777,tmpfs)", func() {
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() {
volumePath := "/test-volume"
filePath := path.Join(volumePath, "test-file")
source := &api.EmptyDirVolumeSource{
Medium: api.StorageMediumMemory,
}
pod := testPodWithVolume(volumePath, source)
pod.Spec.Containers[0].Args = []string{
fmt.Sprintf("--fs_type=%v", volumePath),
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",
})
It("should support (non-root,0644,default)", func() {
doTest0644(f, testImageNonRootUid, api.StorageMediumDefault)
})
It("should support (non-root,0666,default)", func() {
doTest0666(f, testImageNonRootUid, api.StorageMediumDefault)
})
It("should support (non-root,0777,default)", func() {
doTest0777(f, testImageNonRootUid, api.StorageMediumDefault)
})
})
const containerName = "test-container"
const volumeName = "test-volume"
const (
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())
return &api.Pod{
......@@ -86,7 +217,7 @@ func testPodWithVolume(path string, source *api.EmptyDirVolumeSource) *api.Pod {
Containers: []api.Container{
{
Name: containerName,
Image: "gcr.io/google_containers/mounttest:0.2",
Image: image,
VolumeMounts: []api.VolumeMount{
{
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