2019-01-12 04:58:27 +00:00
/ *
Copyright 2014 The Kubernetes Authors .
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 emptydir
import (
"fmt"
"os"
2019-08-30 18:33:25 +00:00
"path/filepath"
2019-01-12 04:58:27 +00:00
2019-12-12 01:27:03 +00:00
"k8s.io/klog"
"k8s.io/utils/mount"
utilstrings "k8s.io/utils/strings"
v1 "k8s.io/api/core/v1"
2019-01-12 04:58:27 +00:00
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
2019-09-27 21:51:53 +00:00
"k8s.io/kubernetes/pkg/volume/util/fsquota"
2019-01-12 04:58:27 +00:00
)
// 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.
//
// http://issue.k8s.io/2630
const perm os . FileMode = 0777
// ProbeVolumePlugins is the primary entrypoint for volume plugins.
func ProbeVolumePlugins ( ) [ ] volume . VolumePlugin {
return [ ] volume . VolumePlugin {
& emptyDirPlugin { nil } ,
}
}
type emptyDirPlugin struct {
host volume . VolumeHost
}
var _ volume . VolumePlugin = & emptyDirPlugin { }
const (
emptyDirPluginName = "kubernetes.io/empty-dir"
hugePagesPageSizeMountOption = "pagesize"
)
func getPath ( uid types . UID , volName string , host volume . VolumeHost ) string {
2019-04-07 17:07:55 +00:00
return host . GetPodVolumeDir ( uid , utilstrings . EscapeQualifiedName ( emptyDirPluginName ) , volName )
2019-01-12 04:58:27 +00:00
}
func ( plugin * emptyDirPlugin ) Init ( host volume . VolumeHost ) error {
plugin . host = host
return nil
}
func ( plugin * emptyDirPlugin ) GetPluginName ( ) string {
return emptyDirPluginName
}
func ( plugin * emptyDirPlugin ) GetVolumeName ( spec * volume . Spec ) ( string , error ) {
volumeSource , _ := getVolumeSource ( spec )
if volumeSource == nil {
return "" , fmt . Errorf ( "Spec does not reference an EmptyDir volume type" )
}
// Return user defined volume name, since this is an ephemeral volume type
return spec . Name ( ) , nil
}
func ( plugin * emptyDirPlugin ) CanSupport ( spec * volume . Spec ) bool {
if spec . Volume != nil && spec . Volume . EmptyDir != nil {
return true
}
return false
}
func ( plugin * emptyDirPlugin ) RequiresRemount ( ) bool {
return false
}
func ( plugin * emptyDirPlugin ) SupportsMountOption ( ) bool {
return false
}
func ( plugin * emptyDirPlugin ) SupportsBulkVolumeVerification ( ) bool {
return false
}
func ( plugin * emptyDirPlugin ) NewMounter ( spec * volume . Spec , pod * v1 . Pod , opts volume . VolumeOptions ) ( volume . Mounter , error ) {
return plugin . newMounterInternal ( spec , pod , plugin . host . GetMounter ( plugin . GetPluginName ( ) ) , & realMountDetector { plugin . host . GetMounter ( plugin . GetPluginName ( ) ) } , opts )
}
func ( plugin * emptyDirPlugin ) newMounterInternal ( spec * volume . Spec , pod * v1 . Pod , mounter mount . Interface , mountDetector mountDetector , opts volume . VolumeOptions ) ( volume . Mounter , error ) {
medium := v1 . StorageMediumDefault
if spec . Volume . EmptyDir != nil { // Support a non-specified source as EmptyDir.
medium = spec . Volume . EmptyDir . Medium
}
return & emptyDir {
pod : pod ,
volName : spec . Name ( ) ,
medium : medium ,
mounter : mounter ,
mountDetector : mountDetector ,
plugin : plugin ,
MetricsProvider : volume . NewMetricsDu ( getPath ( pod . UID , spec . Name ( ) , plugin . host ) ) ,
} , nil
}
func ( plugin * emptyDirPlugin ) NewUnmounter ( volName string , podUID types . UID ) ( volume . Unmounter , error ) {
// Inject real implementations here, test through the internal function.
return plugin . newUnmounterInternal ( volName , podUID , plugin . host . GetMounter ( plugin . GetPluginName ( ) ) , & realMountDetector { plugin . host . GetMounter ( plugin . GetPluginName ( ) ) } )
}
func ( plugin * emptyDirPlugin ) newUnmounterInternal ( volName string , podUID types . UID , mounter mount . Interface , mountDetector mountDetector ) ( volume . Unmounter , error ) {
ed := & emptyDir {
pod : & v1 . Pod { ObjectMeta : metav1 . ObjectMeta { UID : podUID } } ,
volName : volName ,
medium : v1 . StorageMediumDefault , // might be changed later
mounter : mounter ,
mountDetector : mountDetector ,
plugin : plugin ,
MetricsProvider : volume . NewMetricsDu ( getPath ( podUID , volName , plugin . host ) ) ,
}
return ed , nil
}
func ( plugin * emptyDirPlugin ) ConstructVolumeSpec ( volName , mountPath string ) ( * volume . Spec , error ) {
emptyDirVolume := & v1 . Volume {
Name : volName ,
VolumeSource : v1 . VolumeSource {
EmptyDir : & v1 . EmptyDirVolumeSource { } ,
} ,
}
return volume . NewSpecFromVolume ( emptyDirVolume ) , nil
}
// mountDetector abstracts how to find what kind of mount a path is backed by.
type mountDetector interface {
// GetMountMedium determines what type of medium a given path is backed
// by and whether that path is a mount point. For example, if this
// returns (v1.StorageMediumMemory, false, nil), the caller knows that the path is
// on a memory FS (tmpfs on Linux) but is not the root mountpoint of
// that tmpfs.
2020-03-26 21:07:15 +00:00
GetMountMedium ( path string , requestedMedium v1 . StorageMedium ) ( v1 . StorageMedium , bool , * resource . Quantity , error )
2019-01-12 04:58:27 +00:00
}
// EmptyDir volumes are temporary directories exposed to the pod.
// These do not persist beyond the lifetime of a pod.
type emptyDir struct {
pod * v1 . Pod
volName string
medium v1 . StorageMedium
mounter mount . Interface
mountDetector mountDetector
plugin * emptyDirPlugin
volume . MetricsProvider
}
func ( ed * emptyDir ) GetAttributes ( ) volume . Attributes {
return volume . Attributes {
ReadOnly : false ,
Managed : true ,
SupportsSELinux : true ,
}
}
// Checks prior to mount operations to verify that the required components (binaries, etc.)
// to mount the volume are available on the underlying node.
// If not, it returns an error
func ( ed * emptyDir ) CanMount ( ) error {
return nil
}
// SetUp creates new directory.
2019-08-30 18:33:25 +00:00
func ( ed * emptyDir ) SetUp ( mounterArgs volume . MounterArgs ) error {
return ed . SetUpAt ( ed . GetPath ( ) , mounterArgs )
2019-01-12 04:58:27 +00:00
}
// SetUpAt creates new directory.
2019-08-30 18:33:25 +00:00
func ( ed * emptyDir ) SetUpAt ( dir string , mounterArgs volume . MounterArgs ) error {
2019-01-12 04:58:27 +00:00
notMnt , err := ed . mounter . IsLikelyNotMountPoint ( 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 == v1 . StorageMediumMemory && ! notMnt {
return nil
} else if ed . medium == v1 . StorageMediumDefault {
return nil
}
}
2020-03-26 21:07:15 +00:00
switch {
case ed . medium == v1 . StorageMediumDefault :
2019-01-12 04:58:27 +00:00
err = ed . setupDir ( dir )
2020-03-26 21:07:15 +00:00
case ed . medium == v1 . StorageMediumMemory :
2019-01-12 04:58:27 +00:00
err = ed . setupTmpfs ( dir )
2020-03-26 21:07:15 +00:00
case v1helper . IsHugePageMedium ( ed . medium ) :
2019-01-12 04:58:27 +00:00
err = ed . setupHugepages ( dir )
default :
err = fmt . Errorf ( "unknown storage medium %q" , ed . medium )
}
2020-03-26 21:07:15 +00:00
volume . SetVolumeOwnership ( ed , mounterArgs . FsGroup , nil /*fsGroupChangePolicy*/ )
2019-01-12 04:58:27 +00:00
2019-08-30 18:33:25 +00:00
// If setting up the quota fails, just log a message but don't actually error out.
// We'll use the old du mechanism in this case, at least until we support
// enforcement.
2019-01-12 04:58:27 +00:00
if err == nil {
volumeutil . SetReady ( ed . getMetaDir ( ) )
2019-08-30 18:33:25 +00:00
if mounterArgs . DesiredSize != nil {
// Deliberately shadow the outer use of err as noted
// above.
2019-09-27 21:51:53 +00:00
hasQuotas , err := fsquota . SupportsQuotas ( ed . mounter , dir )
2019-08-30 18:33:25 +00:00
if err != nil {
klog . V ( 3 ) . Infof ( "Unable to check for quota support on %s: %s" , dir , err . Error ( ) )
} else if hasQuotas {
klog . V ( 4 ) . Infof ( "emptydir trying to assign quota %v on %s" , mounterArgs . DesiredSize , dir )
2019-09-27 21:51:53 +00:00
err := fsquota . AssignQuota ( ed . mounter , dir , ed . pod . UID , mounterArgs . DesiredSize )
2019-08-30 18:33:25 +00:00
if err != nil {
klog . V ( 3 ) . Infof ( "Set quota on %s failed %s" , dir , err . Error ( ) )
}
}
}
2019-01-12 04:58:27 +00:00
}
return err
}
// setupTmpfs creates a tmpfs mount at the specified directory.
func ( ed * emptyDir ) setupTmpfs ( dir string ) error {
if ed . mounter == nil {
return fmt . Errorf ( "memory storage requested, but mounter is nil" )
}
if err := ed . setupDir ( dir ) ; err != nil {
return err
}
// Make SetUp idempotent.
2020-03-26 21:07:15 +00:00
medium , isMnt , _ , err := ed . mountDetector . GetMountMedium ( dir , ed . medium )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
// If the directory is a mountpoint with medium memory, there is no
// work to do since we are already in the desired state.
if isMnt && medium == v1 . StorageMediumMemory {
return nil
}
klog . V ( 3 ) . Infof ( "pod %v: mounting tmpfs for volume %v" , ed . pod . UID , ed . volName )
return ed . mounter . Mount ( "tmpfs" , dir , "tmpfs" , nil /* options */ )
}
// setupHugepages creates a hugepage mount at the specified directory.
func ( ed * emptyDir ) setupHugepages ( dir string ) error {
if ed . mounter == nil {
return fmt . Errorf ( "memory storage requested, but mounter is nil" )
}
if err := ed . setupDir ( dir ) ; err != nil {
return err
}
// Make SetUp idempotent.
2020-03-26 21:07:15 +00:00
medium , isMnt , mountPageSize , err := ed . mountDetector . GetMountMedium ( dir , ed . medium )
klog . V ( 3 ) . Infof ( "pod %v: setupHugepages: medium: %s, isMnt: %v, dir: %s, err: %v" , ed . pod . UID , medium , isMnt , dir , err )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2020-03-26 21:07:15 +00:00
// If the directory is a mountpoint with medium hugepages of the same page size,
// there is no work to do since we are already in the desired state.
if isMnt && v1helper . IsHugePageMedium ( medium ) {
// Medium is: Hugepages
if ed . medium == v1 . StorageMediumHugePages {
return nil
}
if mountPageSize == nil {
return fmt . Errorf ( "pod %v: mounted dir %s pagesize is not determined" , ed . pod . UID , dir )
}
// Medium is: Hugepages-<size>
// Mounted page size and medium size must be equal
mediumSize , err := v1helper . HugePageSizeFromMedium ( ed . medium )
if err != nil {
return err
}
if mountPageSize == nil || mediumSize . Cmp ( * mountPageSize ) != 0 {
return fmt . Errorf ( "pod %v: mounted dir %s pagesize '%s' and requested medium size '%s' differ" , ed . pod . UID , dir , mountPageSize . String ( ) , mediumSize . String ( ) )
}
2019-01-12 04:58:27 +00:00
return nil
}
2020-03-26 21:07:15 +00:00
pageSizeMountOption , err := getPageSizeMountOption ( ed . medium , ed . pod )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
klog . V ( 3 ) . Infof ( "pod %v: mounting hugepages for volume %v" , ed . pod . UID , ed . volName )
return ed . mounter . Mount ( "nodev" , dir , "hugetlbfs" , [ ] string { pageSizeMountOption } )
}
2020-03-26 21:07:15 +00:00
// getPageSizeMountOption retrieves pageSize mount option from Pod's resources
// and medium and validates pageSize options in all containers of given Pod.
func getPageSizeMountOption ( medium v1 . StorageMedium , pod * v1 . Pod ) ( string , error ) {
2019-01-12 04:58:27 +00:00
pageSizeFound := false
pageSize := resource . Quantity { }
2020-03-26 21:07:15 +00:00
var mediumPageSize resource . Quantity
if medium != v1 . StorageMediumHugePages {
// medium is: Hugepages-<size>
var err error
mediumPageSize , err = v1helper . HugePageSizeFromMedium ( medium )
if err != nil {
return "" , err
}
}
// In some rare cases init containers can also consume Huge pages
for _ , container := range append ( pod . Spec . Containers , pod . Spec . InitContainers ... ) {
2019-01-12 04:58:27 +00:00
// We can take request because limit and requests must match.
for requestName := range container . Resources . Requests {
2020-03-26 21:07:15 +00:00
if ! v1helper . IsHugePageResourceName ( requestName ) {
continue
}
currentPageSize , err := v1helper . HugePageSizeFromResourceName ( requestName )
if err != nil {
return "" , err
}
if medium == v1 . StorageMediumHugePages { // medium is: Hugepages, size is not specified
// PageSize for all volumes in a POD must be equal if medium is "Hugepages"
2019-01-12 04:58:27 +00:00
if pageSizeFound && pageSize . Cmp ( currentPageSize ) != 0 {
2020-03-26 21:07:15 +00:00
return "" , fmt . Errorf ( "medium: %s can't be used if container requests multiple huge page sizes" , medium )
2019-01-12 04:58:27 +00:00
}
2020-03-26 21:07:15 +00:00
2019-01-12 04:58:27 +00:00
pageSizeFound = true
2020-03-26 21:07:15 +00:00
pageSize = currentPageSize
} else { // medium is: Hugepages-<size>
if currentPageSize . Cmp ( mediumPageSize ) == 0 {
pageSizeFound = true
pageSize = currentPageSize
}
2019-01-12 04:58:27 +00:00
}
}
}
if ! pageSizeFound {
2020-03-26 21:07:15 +00:00
return "" , fmt . Errorf ( "medium %s: hugePages storage requested, but there is no resource request for huge pages" , medium )
2019-01-12 04:58:27 +00:00
}
return fmt . Sprintf ( "%s=%s" , hugePagesPageSizeMountOption , pageSize . String ( ) ) , nil
}
// setupDir creates the directory with the default permissions specified by the perm constant.
func ( ed * emptyDir ) setupDir ( dir string ) error {
// Create the directory if it doesn't already exist.
if err := os . MkdirAll ( dir , perm ) ; err != nil {
return err
}
// 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 ( ) {
klog . Errorf ( "Expected directory %q permissions to be: %s; got: %s" , dir , perm . Perm ( ) , fileinfo . Mode ( ) . Perm ( ) )
}
}
return nil
}
func ( ed * emptyDir ) GetPath ( ) string {
return getPath ( ed . pod . UID , ed . volName , ed . plugin . host )
}
// TearDown simply discards everything in the directory.
func ( ed * emptyDir ) TearDown ( ) error {
return ed . TearDownAt ( ed . GetPath ( ) )
}
// TearDownAt simply discards everything in the directory.
func ( ed * emptyDir ) TearDownAt ( dir string ) error {
2019-04-07 17:07:55 +00:00
if pathExists , pathErr := mount . PathExists ( dir ) ; pathErr != nil {
2019-01-12 04:58:27 +00:00
return fmt . Errorf ( "Error checking if path exists: %v" , pathErr )
} else if ! pathExists {
klog . Warningf ( "Warning: Unmount skipped because path does not exist: %v" , dir )
return nil
}
// Figure out the medium.
2020-03-26 21:07:15 +00:00
medium , isMnt , _ , err := ed . mountDetector . GetMountMedium ( dir , ed . medium )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
if isMnt {
if medium == v1 . StorageMediumMemory {
ed . medium = v1 . StorageMediumMemory
return ed . teardownTmpfsOrHugetlbfs ( dir )
} else if medium == v1 . StorageMediumHugePages {
ed . medium = v1 . StorageMediumHugePages
return ed . teardownTmpfsOrHugetlbfs ( dir )
}
}
// assume StorageMediumDefault
return ed . teardownDefault ( dir )
}
func ( ed * emptyDir ) teardownDefault ( dir string ) error {
2019-08-30 18:33:25 +00:00
// Remove any quota
2019-09-27 21:51:53 +00:00
err := fsquota . ClearQuota ( ed . mounter , dir )
2019-08-30 18:33:25 +00:00
if err != nil {
klog . Warningf ( "Warning: Failed to clear quota on %s: %v" , dir , err )
}
2019-01-12 04:58:27 +00:00
// Renaming the directory is not required anymore because the operation executor
// now handles duplicate operations on the same volume
2019-08-30 18:33:25 +00:00
err = os . RemoveAll ( dir )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
return nil
}
func ( ed * emptyDir ) teardownTmpfsOrHugetlbfs ( dir string ) error {
if ed . mounter == nil {
return fmt . Errorf ( "memory storage requested, but mounter is nil" )
}
if err := ed . mounter . Unmount ( dir ) ; err != nil {
return err
}
if err := os . RemoveAll ( dir ) ; err != nil {
return err
}
return nil
}
func ( ed * emptyDir ) getMetaDir ( ) string {
2019-08-30 18:33:25 +00:00
return filepath . Join ( ed . plugin . host . GetPodPluginDir ( ed . pod . UID , utilstrings . EscapeQualifiedName ( emptyDirPluginName ) ) , ed . volName )
2019-01-12 04:58:27 +00:00
}
func getVolumeSource ( spec * volume . Spec ) ( * v1 . EmptyDirVolumeSource , bool ) {
var readOnly bool
var volumeSource * v1 . EmptyDirVolumeSource
if spec . Volume != nil && spec . Volume . EmptyDir != nil {
volumeSource = spec . Volume . EmptyDir
readOnly = spec . ReadOnly
}
return volumeSource , readOnly
}