mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 04:33:26 +00:00
Enforce ReadWriteOncePod access mode during mount
This commit is contained in:
parent
7491d01651
commit
2b98f8edc7
@ -425,6 +425,26 @@ func (asw *actualStateOfWorld) GetVolumeMountState(volumeName v1.UniqueVolumeNam
|
|||||||
return podObj.volumeMountStateForPod
|
return podObj.volumeMountStateForPod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) IsVolumeMountedElsewhere(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool {
|
||||||
|
asw.RLock()
|
||||||
|
defer asw.RUnlock()
|
||||||
|
|
||||||
|
volumeObj, volumeExists := asw.attachedVolumes[volumeName]
|
||||||
|
if !volumeExists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, podObj := range volumeObj.mountedPods {
|
||||||
|
if podName != podObj.podName {
|
||||||
|
// Treat uncertain mount state as mounted until certain.
|
||||||
|
if podObj.volumeMountStateForPod != operationexecutor.VolumeNotMounted {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// addVolume adds the given volume to the cache indicating the specified
|
// addVolume adds the given volume to the cache indicating the specified
|
||||||
// volume is attached to this node. If no volume name is supplied, a unique
|
// volume is attached to this node. If no volume name is supplied, a unique
|
||||||
// volume name is generated from the volumeSpec and returned on success. If a
|
// volume name is generated from the volumeSpec and returned on success. If a
|
||||||
|
@ -241,6 +241,7 @@ func Test_AddPodToVolume_Positive_ExistingVolumeNewNode(t *testing.T) {
|
|||||||
verifyVolumeDoesntExistInGloballyMountedVolumes(t, generatedVolumeName, asw)
|
verifyVolumeDoesntExistInGloballyMountedVolumes(t, generatedVolumeName, asw)
|
||||||
verifyPodExistsInVolumeAsw(t, podName, generatedVolumeName, "fake/device/path" /* expectedDevicePath */, asw)
|
verifyPodExistsInVolumeAsw(t, podName, generatedVolumeName, "fake/device/path" /* expectedDevicePath */, asw)
|
||||||
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
||||||
|
verifyVolumeMountedElsewhere(t, podName, generatedVolumeName, false /*expectedMountedElsewhere */, asw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populates data struct with a volume
|
// Populates data struct with a volume
|
||||||
@ -321,6 +322,7 @@ func Test_AddPodToVolume_Positive_ExistingVolumeExistingNode(t *testing.T) {
|
|||||||
verifyVolumeDoesntExistInGloballyMountedVolumes(t, generatedVolumeName, asw)
|
verifyVolumeDoesntExistInGloballyMountedVolumes(t, generatedVolumeName, asw)
|
||||||
verifyPodExistsInVolumeAsw(t, podName, generatedVolumeName, "fake/device/path" /* expectedDevicePath */, asw)
|
verifyPodExistsInVolumeAsw(t, podName, generatedVolumeName, "fake/device/path" /* expectedDevicePath */, asw)
|
||||||
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
||||||
|
verifyVolumeMountedElsewhere(t, podName, generatedVolumeName, false /*expectedMountedElsewhere */, asw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populates data struct with a volume
|
// Populates data struct with a volume
|
||||||
@ -451,6 +453,8 @@ func Test_AddTwoPodsToVolume_Positive(t *testing.T) {
|
|||||||
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName2, volumeSpec2.Name(), asw)
|
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName2, volumeSpec2.Name(), asw)
|
||||||
verifyVolumeSpecNameInVolumeAsw(t, podName1, []*volume.Spec{volumeSpec1}, asw)
|
verifyVolumeSpecNameInVolumeAsw(t, podName1, []*volume.Spec{volumeSpec1}, asw)
|
||||||
verifyVolumeSpecNameInVolumeAsw(t, podName2, []*volume.Spec{volumeSpec2}, asw)
|
verifyVolumeSpecNameInVolumeAsw(t, podName2, []*volume.Spec{volumeSpec2}, asw)
|
||||||
|
verifyVolumeMountedElsewhere(t, podName1, generatedVolumeName1, true /*expectedMountedElsewhere */, asw)
|
||||||
|
verifyVolumeMountedElsewhere(t, podName2, generatedVolumeName2, true /*expectedMountedElsewhere */, asw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calls AddPodToVolume() to add pod to empty data struct
|
// Calls AddPodToVolume() to add pod to empty data struct
|
||||||
@ -488,6 +492,10 @@ func Test_AddPodToVolume_Negative_VolumeDoesntExist(t *testing.T) {
|
|||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generatedVolumeName, err := util.GetUniqueVolumeNameFromSpec(
|
||||||
|
plugin, volumeSpec)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
blockplugin, err := volumePluginMgr.FindMapperPluginBySpec(volumeSpec)
|
blockplugin, err := volumePluginMgr.FindMapperPluginBySpec(volumeSpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
@ -538,6 +546,7 @@ func Test_AddPodToVolume_Negative_VolumeDoesntExist(t *testing.T) {
|
|||||||
false, /* expectVolumeToExist */
|
false, /* expectVolumeToExist */
|
||||||
asw)
|
asw)
|
||||||
verifyVolumeDoesntExistWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
verifyVolumeDoesntExistWithSpecNameInVolumeAsw(t, podName, volumeSpec.Name(), asw)
|
||||||
|
verifyVolumeMountedElsewhere(t, podName, generatedVolumeName, false /*expectedMountedElsewhere */, asw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calls MarkVolumeAsAttached() once to add volume
|
// Calls MarkVolumeAsAttached() once to add volume
|
||||||
@ -773,6 +782,21 @@ func verifyPodExistsInVolumeAsw(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyVolumeMountedElsewhere(
|
||||||
|
t *testing.T,
|
||||||
|
expectedPodName volumetypes.UniquePodName,
|
||||||
|
expectedVolumeName v1.UniqueVolumeName,
|
||||||
|
expectedMountedElsewhere bool,
|
||||||
|
asw ActualStateOfWorld) {
|
||||||
|
mountedElsewhere := asw.IsVolumeMountedElsewhere(expectedVolumeName, expectedPodName)
|
||||||
|
if mountedElsewhere != expectedMountedElsewhere {
|
||||||
|
t.Fatalf(
|
||||||
|
"IsVolumeMountedElsewhere assertion failure. Expected : <%t> Actual: <%t>",
|
||||||
|
expectedMountedElsewhere,
|
||||||
|
mountedElsewhere)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func verifyPodDoesntExistInVolumeAsw(
|
func verifyPodDoesntExistInVolumeAsw(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
podToCheck volumetypes.UniquePodName,
|
podToCheck volumetypes.UniquePodName,
|
||||||
|
@ -203,6 +203,9 @@ type ActualStateOfWorldMounterUpdater interface {
|
|||||||
// GetVolumeMountState returns mount state of the volume for the Pod
|
// GetVolumeMountState returns mount state of the volume for the Pod
|
||||||
GetVolumeMountState(volumName v1.UniqueVolumeName, podName volumetypes.UniquePodName) VolumeMountState
|
GetVolumeMountState(volumName v1.UniqueVolumeName, podName volumetypes.UniquePodName) VolumeMountState
|
||||||
|
|
||||||
|
// IsVolumeMountedElsewhere returns whether the supplied volume is mounted in a Pod other than the supplied one
|
||||||
|
IsVolumeMountedElsewhere(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool
|
||||||
|
|
||||||
// MarkForInUseExpansionError marks the volume to have in-use error during expansion.
|
// MarkForInUseExpansionError marks the volume to have in-use error during expansion.
|
||||||
// volume expansion must not be retried for this volume
|
// volume expansion must not be retried for this volume
|
||||||
MarkForInUseExpansionError(volumeName v1.UniqueVolumeName)
|
MarkForInUseExpansionError(volumeName v1.UniqueVolumeName)
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
volerr "k8s.io/cloud-provider/volume/errors"
|
volerr "k8s.io/cloud-provider/volume/errors"
|
||||||
csitrans "k8s.io/csi-translation-lib"
|
csitrans "k8s.io/csi-translation-lib"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
kevents "k8s.io/kubernetes/pkg/kubelet/events"
|
kevents "k8s.io/kubernetes/pkg/kubelet/events"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
@ -537,12 +538,23 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
mountCheckError := checkMountOptionSupport(og, volumeToMount, volumePlugin)
|
mountCheckError := checkMountOptionSupport(og, volumeToMount, volumePlugin)
|
||||||
|
|
||||||
if mountCheckError != nil {
|
if mountCheckError != nil {
|
||||||
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.MountOptionSupport check failed", mountCheckError)
|
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.MountOptionSupport check failed", mountCheckError)
|
||||||
return volumetypes.NewOperationContext(eventErr, detailedErr, migrated)
|
return volumetypes.NewOperationContext(eventErr, detailedErr, migrated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enforce ReadWriteOncePod access mode if it is the only one present. This is also enforced during scheduling.
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod) &&
|
||||||
|
actualStateOfWorld.IsVolumeMountedElsewhere(volumeToMount.VolumeName, volumeToMount.PodName) &&
|
||||||
|
// Because we do not know what access mode the pod intends to use if there are multiple.
|
||||||
|
len(volumeToMount.VolumeSpec.PersistentVolume.Spec.AccessModes) == 1 &&
|
||||||
|
v1helper.ContainsAccessMode(volumeToMount.VolumeSpec.PersistentVolume.Spec.AccessModes, v1.ReadWriteOncePod) {
|
||||||
|
|
||||||
|
err = goerrors.New("volume uses the ReadWriteOncePod access mode and is already in use by another pod")
|
||||||
|
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.SetUp failed", err)
|
||||||
|
return volumetypes.NewOperationContext(eventErr, detailedErr, migrated)
|
||||||
|
}
|
||||||
|
|
||||||
// Get attacher, if possible
|
// Get attacher, if possible
|
||||||
attachableVolumePlugin, _ :=
|
attachableVolumePlugin, _ :=
|
||||||
og.volumePluginMgr.FindAttachablePluginBySpec(volumeToMount.VolumeSpec)
|
og.volumePluginMgr.FindAttachablePluginBySpec(volumeToMount.VolumeSpec)
|
||||||
@ -1027,6 +1039,18 @@ func (og *operationGenerator) GenerateMapVolumeFunc(
|
|||||||
|
|
||||||
migrated := getMigratedStatusBySpec(volumeToMount.VolumeSpec)
|
migrated := getMigratedStatusBySpec(volumeToMount.VolumeSpec)
|
||||||
|
|
||||||
|
// Enforce ReadWriteOncePod access mode. This is also enforced during scheduling.
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod) &&
|
||||||
|
actualStateOfWorld.IsVolumeMountedElsewhere(volumeToMount.VolumeName, volumeToMount.PodName) &&
|
||||||
|
// Because we do not know what access mode the pod intends to use if there are multiple.
|
||||||
|
len(volumeToMount.VolumeSpec.PersistentVolume.Spec.AccessModes) == 1 &&
|
||||||
|
v1helper.ContainsAccessMode(volumeToMount.VolumeSpec.PersistentVolume.Spec.AccessModes, v1.ReadWriteOncePod) {
|
||||||
|
|
||||||
|
err = goerrors.New("volume uses the ReadWriteOncePod access mode and is already in use by another pod")
|
||||||
|
eventErr, detailedErr := volumeToMount.GenerateError("MapVolume.SetUpDevice failed", err)
|
||||||
|
return volumetypes.NewOperationContext(eventErr, detailedErr, migrated)
|
||||||
|
}
|
||||||
|
|
||||||
// Set up global map path under the given plugin directory using symbolic link
|
// Set up global map path under the given plugin directory using symbolic link
|
||||||
globalMapPath, err :=
|
globalMapPath, err :=
|
||||||
blockVolumeMapper.GetGlobalMapPath(volumeToMount.VolumeSpec)
|
blockVolumeMapper.GetGlobalMapPath(volumeToMount.VolumeSpec)
|
||||||
|
Loading…
Reference in New Issue
Block a user