mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-08-09 07:30:53 +00:00
Implement kubelet support for emptyDir mode
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
95
pkg/volume/emptydir/empty_dir_windows_test.go
Normal file
95
pkg/volume/emptydir/empty_dir_windows_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user