Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tmpfs support as a flag on emptyDir #5166

Merged
merged 2 commits into from
Mar 13, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 19 additions & 2 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to calling this Memory instead of tmpfs, since tmpfs is not cross-platform.

)

// Protocol defines network protocols supported for things like conatiner ports.
type Protocol string
Expand Down
14 changes: 13 additions & 1 deletion pkg/api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 13 additions & 1 deletion pkg/api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand Down
14 changes: 13 additions & 1 deletion pkg/api/v1beta3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
118 changes: 113 additions & 5 deletions pkg/kubelet/volume/empty_dir/empty_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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
}
39 changes: 39 additions & 0 deletions pkg/kubelet/volume/empty_dir/empty_dir_linux.go
Original file line number Diff line number Diff line change
@@ -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
}