Implement kubelet support for emptyDir mode

This commit is contained in:
nispriha
2026-07-15 14:59:26 +05:30
parent af84bbfc3d
commit 4190c07b05
3 changed files with 256 additions and 1 deletions

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/util/swap"
@@ -50,6 +51,16 @@ import (
// https://issue.k8s.io/2630
const perm os.FileMode = 0777
// unixModeToFileMode converts a Unix-style permission value (0-01777) to
// Go's os.FileMode. The sticky bit (01000) maps to os.ModeSticky.
func unixModeToFileMode(mode int32) os.FileMode {
fm := os.FileMode(mode & 0777)
if mode&01000 != 0 {
fm |= os.ModeSticky
}
return fm
}
// ProbeVolumePlugins is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{
@@ -145,6 +156,7 @@ func calculateEmptyDirMemorySize(nodeAllocatableMemory *resource.Quantity, spec
func (plugin *emptyDirPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, mounter mount.Interface, mountDetector mountDetector) (volume.Mounter, error) {
medium := v1.StorageMediumDefault
sizeLimit := &resource.Quantity{}
var mode *int32
if spec.Volume.EmptyDir != nil { // Support a non-specified source as EmptyDir.
medium = spec.Volume.EmptyDir.Medium
if medium == v1.StorageMediumMemory {
@@ -154,12 +166,16 @@ func (plugin *emptyDirPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod,
}
sizeLimit = calculateEmptyDirMemorySize(nodeAllocatable.Memory(), spec, pod)
}
if utilfeature.DefaultFeatureGate.Enabled(features.EmptyDirVolumeMode) {
mode = spec.Volume.EmptyDir.Mode
}
}
return &emptyDir{
pod: pod,
volName: spec.Name(),
medium: medium,
sizeLimit: sizeLimit,
mode: mode,
mounter: mounter,
mountDetector: mountDetector,
plugin: plugin,
@@ -214,6 +230,7 @@ type emptyDir struct {
volName string
sizeLimit *resource.Quantity
medium v1.StorageMedium
mode *int32
mounter mount.Interface
mountDetector mountDetector
plugin *emptyDirPlugin
@@ -447,13 +464,21 @@ func getPageSizeMountOption(medium v1.StorageMedium, pod *v1.Pod) (string, error
return fmt.Sprintf("%s=%s", hugePagesPageSizeMountOption, pageSize.String()), nil
}
// setupDir creates the directory with the default permissions specified by the perm constant.
// setupDir creates the directory with the requested mode, or defaults to 0777.
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
}
if ed.mode != nil && runtime.GOOS != "windows" {
effectivePerm := unixModeToFileMode(*ed.mode)
if err := os.Chmod(dir, effectivePerm); err != nil {
return err
}
return nil
}
// stat the directory to read permission bits
fileinfo, err := os.Lstat(dir)
if err != nil {

View File

@@ -43,6 +43,7 @@ import (
volumetest "k8s.io/kubernetes/pkg/volume/testing"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
"k8s.io/mount-utils"
"k8s.io/utils/ptr"
)
// Construct an instance of a plugin, by name.
@@ -1294,6 +1295,7 @@ func TestResizeEphemeralVolume(t *testing.T) {
},
},
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: types.UID("poduid"),
@@ -1346,3 +1348,136 @@ func TestResizeEphemeralVolume(t *testing.T) {
})
}
}
func TestEmptyDirVolumeMode(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EmptyDirVolumeMode, true)
testCases := []struct {
name string
mode *int32
expectedPerm os.FileMode
expectSticky bool
}{
{
name: "mode 0750",
mode: ptr.To[int32](0o750),
expectedPerm: os.FileMode(0o750),
},
{
name: "mode 01777 with sticky bit",
mode: ptr.To[int32](0o1777),
expectedPerm: os.FileMode(0o777),
expectSticky: true,
},
{
name: "nil mode defaults to 0777",
mode: nil,
expectedPerm: os.FileMode(0o777),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
basePath, err := utiltesting.MkTmpdir("emptydir_mode_test")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
t.Cleanup(func() { _ = os.RemoveAll(basePath) })
plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", basePath)
spec := &v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{Mode: tc.mode},
},
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: types.UID("poduid"),
},
}
mounter, err := plug.(*emptyDirPlugin).newMounterInternal(
volume.NewSpecFromVolume(spec),
pod,
mount.NewFakeMounter(nil),
&fakeMountDetector{},
)
if err != nil {
t.Fatalf("Failed to make a new Mounter: %v", err)
}
if err := mounter.SetUp(volume.MounterArgs{}); err != nil {
t.Fatalf("SetUp failed: %v", err)
}
volPath := mounter.GetPath()
fileinfo, err := os.Stat(volPath)
if err != nil {
t.Fatalf("Stat failed: %v", err)
}
if fileinfo.Mode().Perm() != tc.expectedPerm {
t.Errorf("expected permissions %v, got %v", tc.expectedPerm, fileinfo.Mode().Perm())
}
if tc.expectSticky {
if fileinfo.Mode()&os.ModeSticky == 0 {
t.Errorf("expected sticky bit to be set, got mode %v", fileinfo.Mode())
}
}
})
}
}
func TestEmptyDirVolumeModeFeatureGateDisabled(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EmptyDirVolumeMode, false)
basePath, err := utiltesting.MkTmpdir("emptydir_mode_gate_test")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
t.Cleanup(func() { _ = os.RemoveAll(basePath) })
plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", basePath)
mode := int32(0o750)
spec := &v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{Mode: &mode},
},
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: types.UID("poduid"),
},
}
mounter, err := plug.(*emptyDirPlugin).newMounterInternal(
volume.NewSpecFromVolume(spec),
pod,
mount.NewFakeMounter(nil),
&fakeMountDetector{},
)
if err != nil {
t.Fatalf("Failed to make a new Mounter: %v", err)
}
if err := mounter.SetUp(volume.MounterArgs{}); err != nil {
t.Fatalf("SetUp failed: %v", err)
}
volPath := mounter.GetPath()
fileinfo, err := os.Stat(volPath)
if err != nil {
t.Fatalf("Stat failed: %v", err)
}
if fileinfo.Mode().Perm() != perm.Perm() {
t.Errorf("expected default permissions %v when feature gate is disabled, got %v", perm.Perm(), fileinfo.Mode().Perm())
}
}

View File

@@ -0,0 +1,95 @@
//go:build windows
/*
Copyright 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 (
"os"
"testing"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utiltesting "k8s.io/client-go/util/testing"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
"k8s.io/mount-utils"
)
type fakeMountDetector struct {
medium v1.StorageMedium
isMount bool
}
func (fake *fakeMountDetector) GetMountMedium(path string, requestedMedium v1.StorageMedium) (v1.StorageMedium, bool, *resource.Quantity, error) {
return fake.medium, fake.isMount, nil, nil
}
func TestEmptyDirVolumeModeWindows(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EmptyDirVolumeMode, true)
basePath, err := utiltesting.MkTmpdir("emptydir_mode_windows_test")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(basePath)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), nil, volumetest.NewFakeVolumeHost(t, basePath, nil, nil))
plug, err := plugMgr.FindPluginByName("kubernetes.io/empty-dir")
if err != nil {
t.Fatalf("Can't find the plugin by name: %v", err)
}
mode := int32(0o750)
spec := &v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{Mode: &mode},
},
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: types.UID("poduid"),
},
}
mounter, err := plug.(*emptyDirPlugin).newMounterInternal(
volume.NewSpecFromVolume(spec),
pod,
mount.NewFakeMounter(nil),
&fakeMountDetector{},
)
if err != nil {
t.Fatalf("Failed to make a new Mounter: %v", err)
}
if err := mounter.SetUp(volume.MounterArgs{}); err != nil {
t.Fatalf("SetUp should not fail on Windows with mode set, got: %v", err)
}
volPath := mounter.GetPath()
if _, err := os.Stat(volPath); err != nil {
t.Fatalf("directory should exist after SetUp: %v", err)
}
}