From 50c96789e7fdc1cbcb9514c19a22cb0c1db06741 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 7 Mar 2015 12:33:26 -0800 Subject: [PATCH 1/2] Add an action log to FakeMounter. # *** ERROR: *** docs are out of sync between cli and markdown # run hack/run-gendocs.sh > docs/kubectl.md to regenerate # # Your commit will be aborted unless you regenerate docs. COMMIT_BLOCKED_ON_GENDOCS --- pkg/util/mount/fake.go | 21 ++++++++++++++++++++- pkg/util/mount/mount_linux_test.go | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pkg/util/mount/fake.go b/pkg/util/mount/fake.go index 271f7b49e81..9377c6c780f 100644 --- a/pkg/util/mount/fake.go +++ b/pkg/util/mount/fake.go @@ -16,16 +16,35 @@ limitations under the License. package mount -// FakeMounter implements mount.Interface. +// FakeMounter implements mount.Interface for tests. type FakeMounter struct { MountPoints []MountPoint + Log []FakeAction +} + +// Values for FakeAction.Action +const FakeActionMount = "mount" +const FakeActionUnmount = "unmount" + +// FakeAction objects are logged every time a fake mount or unmount is called. +type FakeAction struct { + Action string // "mount" or "unmount" + Target string // applies to both mount and unmount actions + Source string // applies only to "mount" actions + FSType string // applies only to "mount" actions +} + +func (f *FakeMounter) ResetLog() { + f.Log = []FakeAction{} } func (f *FakeMounter) Mount(source string, target string, fstype string, flags uintptr, data string) error { + f.Log = append(f.Log, FakeAction{Action: FakeActionMount, Target: target, Source: source, FSType: fstype}) return nil } func (f *FakeMounter) Unmount(target string, flags int) error { + f.Log = append(f.Log, FakeAction{Action: FakeActionUnmount, Target: target}) return nil } diff --git a/pkg/util/mount/mount_linux_test.go b/pkg/util/mount/mount_linux_test.go index 98a80945834..7cf067ca74d 100644 --- a/pkg/util/mount/mount_linux_test.go +++ b/pkg/util/mount/mount_linux_test.go @@ -94,7 +94,7 @@ func slicesEqual(a, b []string) bool { func TestGetMountRefs(t *testing.T) { fm := &FakeMounter{ - []MountPoint{ + MountPoints: []MountPoint{ {Device: "/dev/sdb", Path: "/var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/gce-pd"}, {Device: "/dev/sdb", Path: "/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd-in-pod"}, {Device: "/dev/sdc", Path: "/var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/gce-pd2"}, From caca5e73586c7863b6bbd82ca68e7f6ff81c5ddd Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 7 Mar 2015 12:35:00 -0800 Subject: [PATCH 2/2] Add tmpfs support to EmptyDir # *** ERROR: *** docs are out of sync between cli and markdown # run hack/run-gendocs.sh > docs/kubectl.md to regenerate # # Your commit will be aborted unless you regenerate docs. COMMIT_BLOCKED_ON_GENDOCS --- pkg/api/types.go | 21 ++- pkg/api/v1beta1/types.go | 14 +- pkg/api/v1beta2/types.go | 14 +- pkg/api/v1beta3/types.go | 14 +- pkg/kubelet/volume/empty_dir/empty_dir.go | 118 ++++++++++++- .../volume/empty_dir/empty_dir_linux.go | 39 +++++ .../volume/empty_dir/empty_dir_test.go | 159 +++++++++++++----- .../volume/empty_dir/empty_dir_unsupported.go | 26 +++ 8 files changed, 357 insertions(+), 48 deletions(-) create mode 100644 pkg/kubelet/volume/empty_dir/empty_dir_linux.go create mode 100644 pkg/kubelet/volume/empty_dir/empty_dir_unsupported.go diff --git a/pkg/api/types.go b/pkg/api/types.go index 4630cbd7644..72abf94d2be 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -182,12 +182,29 @@ type VolumeSource struct { Secret *SecretVolumeSource `json:"secret"` } -// HostPathVolumeSource represents bare host directory volume. +// HostPathVolumeSource represents a host directory mapped into a pod. type HostPathVolumeSource struct { Path string `json:"path"` } -type EmptyDirVolumeSource struct{} +// EmptyDirVolumeSource represents an empty directory for a pod. +type EmptyDirVolumeSource struct { + // TODO: Longer term we want to represent the selection of underlying + // media more like a scheduling problem - user says what traits they + // need, we give them a backing store that satisifies that. For now + // this will cover the most common needs. + // Optional: what type of storage medium should back this directory. + // The default is "" which means to use the node's default medium. + Medium StorageType `json:"medium"` +} + +// StorageType defines ways that storage can be allocated to a volume. +type StorageType string + +const ( + StorageTypeDefault StorageType = "" // use whatever the default is for the node + StorageTypeMemory StorageType = "Memory" // use memory (tmpfs) +) // Protocol defines network protocols supported for things like conatiner ports. type Protocol string diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 0fe8cd65b24..0bd7a80c8d4 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -112,7 +112,19 @@ type HostPathVolumeSource struct { Path string `json:"path" description:"path of the directory on the host"` } -type EmptyDirVolumeSource struct{} +type EmptyDirVolumeSource struct { + // Optional: what type of storage medium should back this directory. + // The default is "" which means to use the node's default medium. + Medium StorageType `json:"medium" description:"type of storage used to back the volume; must be an empty string (default) or Memory"` +} + +// StorageType defines ways that storage can be allocated to a volume. +type StorageType string + +const ( + StorageTypeDefault StorageType = "" // use whatever the default is for the node + StorageTypeMemory StorageType = "Memory" // use memory (tmpfs) +) // Protocol defines network protocols supported for things like conatiner ports. type Protocol string diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index bee5487f30d..a9c2784f6c1 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -90,7 +90,19 @@ type HostPathVolumeSource struct { // Represents an empty directory volume. // // https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/volumes.md#emptydir -type EmptyDirVolumeSource struct{} +type EmptyDirVolumeSource struct { + // Optional: what type of storage medium should back this directory. + // The default is "" which means to use the node's default medium. + Medium StorageType `json:"medium" description:"type of storage used to back the volume; must be an empty string (default) or Memory"` +} + +// StorageType defines ways that storage can be allocated to a volume. +type StorageType string + +const ( + StorageTypeDefault StorageType = "" // use whatever the default is for the node + StorageTypeMemory StorageType = "Memory" // use memory (tmpfs) +) // SecretVolumeSource adapts a Secret into a VolumeSource // diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 61b7a751857..d8d2f4fd1e5 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -206,7 +206,19 @@ type HostPathVolumeSource struct { Path string `json:"path" description:"path of the directory on the host"` } -type EmptyDirVolumeSource struct{} +type EmptyDirVolumeSource struct { + // Optional: what type of storage medium should back this directory. + // The default is "" which means to use the node's default medium. + Medium StorageType `json:"medium" description:"type of storage used to back the volume; must be an empty string (default) or Memory"` +} + +// StorageType defines ways that storage can be allocated to a volume. +type StorageType string + +const ( + StorageTypeDefault StorageType = "" // use whatever the default is for the node + StorageTypeMemory StorageType = "Memory" // use memory (tmpfs) +) // Protocol defines network protocols supported for things like conatiner ports. type Protocol string diff --git a/pkg/kubelet/volume/empty_dir/empty_dir.go b/pkg/kubelet/volume/empty_dir/empty_dir.go index 2e7fb1db290..d40eef169b4 100644 --- a/pkg/kubelet/volume/empty_dir/empty_dir.go +++ b/pkg/kubelet/volume/empty_dir/empty_dir.go @@ -24,6 +24,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume" "github.com/GoogleCloudPlatform/kubernetes/pkg/types" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount" ) // This is the primary entrypoint for volume plugins. @@ -70,26 +71,83 @@ func (plugin *emptyDirPlugin) CanSupport(spec *api.Volume) bool { } func (plugin *emptyDirPlugin) NewBuilder(spec *api.Volume, podRef *api.ObjectReference) (volume.Builder, error) { + // Inject real implementations here, test through the internal function. + return plugin.newBuilderInternal(spec, podRef, mount.New(), &realMediumer{}) +} + +func (plugin *emptyDirPlugin) newBuilderInternal(spec *api.Volume, podRef *api.ObjectReference, mounter mount.Interface, mediumer mediumer) (volume.Builder, error) { if plugin.legacyMode { // Legacy mode instances can be cleaned up but not created anew. return nil, fmt.Errorf("legacy mode: can not create new instances") } - return &emptyDir{podRef.UID, spec.Name, plugin, false}, nil + medium := api.StorageTypeDefault + if spec.EmptyDir != nil { // Support a non-specified source as EmptyDir. + medium = spec.EmptyDir.Medium + } + return &emptyDir{ + podUID: podRef.UID, + volName: spec.Name, + medium: medium, + mediumer: mediumer, + mounter: mounter, + plugin: plugin, + legacyMode: false, + }, nil } func (plugin *emptyDirPlugin) NewCleaner(volName string, podUID types.UID) (volume.Cleaner, error) { + // Inject real implementations here, test through the internal function. + return plugin.newCleanerInternal(volName, podUID, mount.New(), &realMediumer{}) +} + +func (plugin *emptyDirPlugin) newCleanerInternal(volName string, podUID types.UID, mounter mount.Interface, mediumer mediumer) (volume.Cleaner, error) { legacy := false if plugin.legacyMode { legacy = true } - return &emptyDir{podUID, volName, plugin, legacy}, nil + ed := &emptyDir{ + podUID: podUID, + volName: volName, + medium: api.StorageTypeDefault, // might be changed later + mounter: mounter, + mediumer: mediumer, + plugin: plugin, + legacyMode: legacy, + } + // Figure out the medium. + if medium, err := mediumer.GetMedium(ed.GetPath()); err != nil { + return nil, err + } else { + switch medium { + case mediumMemory: + ed.medium = api.StorageTypeMemory + default: + // assume StorageTypeDefault + } + } + return ed, nil } +// mediumer abstracts how to find what storageMedium a path is backed by. +type mediumer interface { + GetMedium(path string) (storageMedium, error) +} + +type storageMedium int + +const ( + mediumUnknown storageMedium = 0 // assume anything we don't explicitly handle is this + mediumMemory storageMedium = 1 // memory (e.g. tmpfs on linux) +) + // 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 volName string + medium api.StorageType + mounter mount.Interface + mediumer mediumer plugin *emptyDirPlugin legacyMode bool } @@ -99,8 +157,34 @@ func (ed *emptyDir) SetUp() error { if ed.legacyMode { return fmt.Errorf("legacy mode: can not create new instances") } - path := ed.GetPath() - return os.MkdirAll(path, 0750) + switch ed.medium { + case api.StorageTypeDefault: + return ed.setupDefault() + case api.StorageTypeMemory: + return ed.setupTmpfs() + default: + return fmt.Errorf("unknown storage medium %q", ed.medium) + } +} + +func (ed *emptyDir) setupDefault() error { + return os.MkdirAll(ed.GetPath(), 0750) +} + +func (ed *emptyDir) setupTmpfs() error { + if ed.mounter == nil { + return fmt.Errorf("memory storage requested, but mounter is nil") + } + if err := os.MkdirAll(ed.GetPath(), 0750); err != nil { + return err + } + // Make SetUp idempotent. + if medium, err := ed.mediumer.GetMedium(ed.GetPath()); err != nil { + return err + } else if medium == mediumMemory { + return nil // current state is what we expect + } + return ed.mounter.Mount("tmpfs", ed.GetPath(), "tmpfs", 0, "") } func (ed *emptyDir) GetPath() string { @@ -111,8 +195,19 @@ func (ed *emptyDir) GetPath() string { return ed.plugin.host.GetPodVolumeDir(ed.podUID, volume.EscapePluginName(name), ed.volName) } -// TearDown simply deletes everything in the directory. +// TearDown simply discards everything in the directory. func (ed *emptyDir) TearDown() error { + switch ed.medium { + case api.StorageTypeDefault: + return ed.teardownDefault() + case api.StorageTypeMemory: + return ed.teardownTmpfs() + default: + return fmt.Errorf("unknown storage medium %q", ed.medium) + } +} + +func (ed *emptyDir) teardownDefault() error { tmpDir, err := volume.RenameDirectory(ed.GetPath(), ed.volName+".deleting~") if err != nil { return err @@ -123,3 +218,16 @@ func (ed *emptyDir) TearDown() error { } return nil } + +func (ed *emptyDir) teardownTmpfs() error { + if ed.mounter == nil { + return fmt.Errorf("memory storage requested, but mounter is nil") + } + if err := ed.mounter.Unmount(ed.GetPath(), 0); err != nil { + return err + } + if err := os.RemoveAll(ed.GetPath()); err != nil { + return err + } + return nil +} diff --git a/pkg/kubelet/volume/empty_dir/empty_dir_linux.go b/pkg/kubelet/volume/empty_dir/empty_dir_linux.go new file mode 100644 index 00000000000..16141abd89b --- /dev/null +++ b/pkg/kubelet/volume/empty_dir/empty_dir_linux.go @@ -0,0 +1,39 @@ +/* +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. +*/ + +package empty_dir + +import ( + "fmt" + "syscall" +) + +// Defined by Linux - the type number for tmpfs mounts. +const linuxTmpfsMagic = 0x01021994 + +// realMediumer implements mediumer in terms of syscalls. +type realMediumer struct{} + +func (m *realMediumer) GetMedium(path string) (storageMedium, error) { + buf := syscall.Statfs_t{} + if err := syscall.Statfs(path, &buf); err != nil { + return 0, fmt.Errorf("statfs(%q): %v", path, err) + } + if buf.Type == linuxTmpfsMagic { + return mediumMemory, nil + } + return mediumUnknown, nil +} diff --git a/pkg/kubelet/volume/empty_dir/empty_dir_test.go b/pkg/kubelet/volume/empty_dir/empty_dir_test.go index bbfda9e4414..81206c6ba0f 100644 --- a/pkg/kubelet/volume/empty_dir/empty_dir_test.go +++ b/pkg/kubelet/volume/empty_dir/empty_dir_test.go @@ -18,21 +18,33 @@ package empty_dir import ( "os" + "path" "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume" "github.com/GoogleCloudPlatform/kubernetes/pkg/types" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount" ) -func TestCanSupport(t *testing.T) { - plugMgr := volume.PluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil}) +// The dir where volumes will be stored. +const basePath = "/tmp/fake" - plug, err := plugMgr.FindPluginByName("kubernetes.io/empty-dir") +// Construct an instance of a plugin, by name. +func makePluginUnderTest(t *testing.T, plugName string) volume.Plugin { + plugMgr := volume.PluginMgr{} + plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{basePath, nil}) + + plug, err := plugMgr.FindPluginByName(plugName) if err != nil { t.Errorf("Can't find the plugin by name") } + return plug +} + +func TestCanSupport(t *testing.T) { + plug := makePluginUnderTest(t, "kubernetes.io/empty-dir") + if plug.Name() != "kubernetes.io/empty-dir" { t.Errorf("Wrong name: %s", plug.Name()) } @@ -44,19 +56,24 @@ func TestCanSupport(t *testing.T) { } } -func TestPlugin(t *testing.T) { - plugMgr := volume.PluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil}) +type fakeMediumer struct { + typeToReturn storageMedium +} + +func (fake *fakeMediumer) GetMedium(path string) (storageMedium, error) { + return fake.typeToReturn, nil +} + +func TestPlugin(t *testing.T) { + plug := makePluginUnderTest(t, "kubernetes.io/empty-dir") - plug, err := plugMgr.FindPluginByName("kubernetes.io/empty-dir") - if err != nil { - t.Errorf("Can't find the plugin by name") - } spec := &api.Volume{ Name: "vol1", - VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}, + VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{Medium: api.StorageTypeDefault}}, } - builder, err := plug.NewBuilder(spec, &api.ObjectReference{UID: types.UID("poduid")}) + mounter := mount.FakeMounter{} + mediumer := fakeMediumer{} + builder, err := plug.(*emptyDirPlugin).newBuilderInternal(spec, &api.ObjectReference{UID: types.UID("poduid")}, &mounter, &mediumer) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } @@ -64,23 +81,27 @@ func TestPlugin(t *testing.T) { t.Errorf("Got a nil Builder: %v") } - path := builder.GetPath() - if path != "/tmp/fake/pods/poduid/volumes/kubernetes.io~empty-dir/vol1" { - t.Errorf("Got unexpected path: %s", path) + volPath := builder.GetPath() + if volPath != path.Join(basePath, "pods/poduid/volumes/kubernetes.io~empty-dir/vol1") { + t.Errorf("Got unexpected path: %s", volPath) } if err := builder.SetUp(); err != nil { t.Errorf("Expected success, got: %v", err) } - if _, err := os.Stat(path); err != nil { + if _, err := os.Stat(volPath); err != nil { if os.IsNotExist(err) { - t.Errorf("SetUp() failed, volume path not created: %s", path) + t.Errorf("SetUp() failed, volume path not created: %s", volPath) } else { t.Errorf("SetUp() failed: %v", err) } } + if len(mounter.Log) != 0 { + t.Errorf("Expected 0 mounter calls, got %#v", mounter.Log) + } + mounter.ResetLog() - cleaner, err := plug.NewCleaner("vol1", types.UID("poduid")) + cleaner, err := plug.(*emptyDirPlugin).newCleanerInternal("vol1", types.UID("poduid"), &mounter, &fakeMediumer{}) if err != nil { t.Errorf("Failed to make a new Cleaner: %v", err) } @@ -91,21 +112,87 @@ func TestPlugin(t *testing.T) { if err := cleaner.TearDown(); err != nil { t.Errorf("Expected success, got: %v", err) } - if _, err := os.Stat(path); err == nil { - t.Errorf("TearDown() failed, volume path still exists: %s", path) + if _, err := os.Stat(volPath); err == nil { + t.Errorf("TearDown() failed, volume path still exists: %s", volPath) } else if !os.IsNotExist(err) { t.Errorf("SetUp() failed: %v", err) } + if len(mounter.Log) != 0 { + t.Errorf("Expected 0 mounter calls, got %#v", mounter.Log) + } + mounter.ResetLog() +} + +func TestPluginTmpfs(t *testing.T) { + plug := makePluginUnderTest(t, "kubernetes.io/empty-dir") + + spec := &api.Volume{ + Name: "vol1", + VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{Medium: api.StorageTypeMemory}}, + } + mounter := mount.FakeMounter{} + mediumer := fakeMediumer{} + builder, err := plug.(*emptyDirPlugin).newBuilderInternal(spec, &api.ObjectReference{UID: types.UID("poduid")}, &mounter, &mediumer) + if err != nil { + t.Errorf("Failed to make a new Builder: %v", err) + } + if builder == nil { + t.Errorf("Got a nil Builder: %v") + } + + volPath := builder.GetPath() + if volPath != path.Join(basePath, "pods/poduid/volumes/kubernetes.io~empty-dir/vol1") { + t.Errorf("Got unexpected path: %s", volPath) + } + + if err := builder.SetUp(); err != nil { + t.Errorf("Expected success, got: %v", err) + } + if _, err := os.Stat(volPath); err != nil { + if os.IsNotExist(err) { + t.Errorf("SetUp() failed, volume path not created: %s", volPath) + } else { + t.Errorf("SetUp() failed: %v", err) + } + } + if len(mounter.Log) != 1 { + t.Errorf("Expected 1 mounter call, got %#v", mounter.Log) + } else { + if mounter.Log[0].Action != mount.FakeActionMount || mounter.Log[0].FSType != "tmpfs" { + t.Errorf("Unexpected mounter action: %#v", mounter.Log[0]) + } + } + mounter.ResetLog() + + cleaner, err := plug.(*emptyDirPlugin).newCleanerInternal("vol1", types.UID("poduid"), &mounter, &fakeMediumer{mediumMemory}) + if err != nil { + t.Errorf("Failed to make a new Cleaner: %v", err) + } + if cleaner == nil { + t.Errorf("Got a nil Cleaner: %v") + } + + if err := cleaner.TearDown(); err != nil { + t.Errorf("Expected success, got: %v", err) + } + if _, err := os.Stat(volPath); err == nil { + t.Errorf("TearDown() failed, volume path still exists: %s", volPath) + } else if !os.IsNotExist(err) { + t.Errorf("SetUp() failed: %v", err) + } + if len(mounter.Log) != 1 { + t.Errorf("Expected 1 mounter call, got %#v", mounter.Log) + } else { + if mounter.Log[0].Action != mount.FakeActionUnmount { + t.Errorf("Unexpected mounter action: %#v", mounter.Log[0]) + } + } + mounter.ResetLog() } func TestPluginBackCompat(t *testing.T) { - plugMgr := volume.PluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil}) + plug := makePluginUnderTest(t, "kubernetes.io/empty-dir") - plug, err := plugMgr.FindPluginByName("kubernetes.io/empty-dir") - if err != nil { - t.Errorf("Can't find the plugin by name") - } spec := &api.Volume{ Name: "vol1", } @@ -117,20 +204,15 @@ func TestPluginBackCompat(t *testing.T) { t.Errorf("Got a nil Builder: %v") } - path := builder.GetPath() - if path != "/tmp/fake/pods/poduid/volumes/kubernetes.io~empty-dir/vol1" { - t.Errorf("Got unexpected path: %s", path) + volPath := builder.GetPath() + if volPath != path.Join(basePath, "pods/poduid/volumes/kubernetes.io~empty-dir/vol1") { + t.Errorf("Got unexpected path: %s", volPath) } } func TestPluginLegacy(t *testing.T) { - plugMgr := volume.PluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil}) + plug := makePluginUnderTest(t, "empty") - plug, err := plugMgr.FindPluginByName("empty") - if err != nil { - t.Errorf("Can't find the plugin by name") - } if plug.Name() != "empty" { t.Errorf("Wrong name: %s", plug.Name()) } @@ -138,11 +220,12 @@ func TestPluginLegacy(t *testing.T) { t.Errorf("Expected false") } - if _, err := plug.NewBuilder(&api.Volume{VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, &api.ObjectReference{UID: types.UID("poduid")}); err == nil { + spec := api.Volume{VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}} + if _, err := plug.(*emptyDirPlugin).newBuilderInternal(&spec, &api.ObjectReference{UID: types.UID("poduid")}, &mount.FakeMounter{}, &fakeMediumer{}); err == nil { t.Errorf("Expected failiure") } - cleaner, err := plug.NewCleaner("vol1", types.UID("poduid")) + cleaner, err := plug.(*emptyDirPlugin).newCleanerInternal("vol1", types.UID("poduid"), &mount.FakeMounter{}, &fakeMediumer{}) if err != nil { t.Errorf("Failed to make a new Cleaner: %v", err) } diff --git a/pkg/kubelet/volume/empty_dir/empty_dir_unsupported.go b/pkg/kubelet/volume/empty_dir/empty_dir_unsupported.go new file mode 100644 index 00000000000..2b905bd2a93 --- /dev/null +++ b/pkg/kubelet/volume/empty_dir/empty_dir_unsupported.go @@ -0,0 +1,26 @@ +// +build !linux + +/* +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. +*/ + +package empty_dir + +// realMediumer pretends to implement mediumer. +type realMediumer struct{} + +func (m *realMediumer) GetMedium(path string) (storageMedium, error) { + return mediumUnknown, nil +}