mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-27 21:26:03 +00:00
Fixes Attach Detach Controller reconciler race reading ActualStateOfWorld and operation pending states; fixes reconciler_test mock detach to account for multiple attaches on a node
This commit is contained in:
@@ -98,7 +98,8 @@ type ActualStateOfWorld interface {
|
||||
|
||||
// IsVolumeAttachedToNode returns true if the specified volume/node combo exists
|
||||
// in the underlying store indicating the specified volume is attached to
|
||||
// the specified node.
|
||||
// the specified node, and false if either the combo does not exist, or the
|
||||
// attached state is marked as uncertain.
|
||||
IsVolumeAttachedToNode(volumeName v1.UniqueVolumeName, nodeName types.NodeName) bool
|
||||
|
||||
// GetAttachedVolumes generates and returns a list of volumes/node pairs
|
||||
|
@@ -142,6 +142,7 @@ func (rc *reconciler) reconcile() {
|
||||
for _, attachedVolume := range rc.actualStateOfWorld.GetAttachedVolumes() {
|
||||
if !rc.desiredStateOfWorld.VolumeExists(
|
||||
attachedVolume.VolumeName, attachedVolume.NodeName) {
|
||||
|
||||
// Check whether there already exist an operation pending, and don't even
|
||||
// try to start an operation if there is already one running.
|
||||
// This check must be done before we do any other checks, as otherwise the other checks
|
||||
@@ -161,6 +162,28 @@ func (rc *reconciler) reconcile() {
|
||||
}
|
||||
}
|
||||
|
||||
// Because the detach operation updates the ActualStateOfWorld before
|
||||
// marking itself complete, it's possible for the volume to be removed
|
||||
// from the ActualStateOfWorld between the GetAttachedVolumes() check
|
||||
// and the IsOperationPending() check above.
|
||||
// Check the ActualStateOfWorld again to avoid issuing an unnecessary
|
||||
// detach.
|
||||
// See https://github.com/kubernetes/kubernetes/issues/93902
|
||||
attachedVolumesForNode := rc.actualStateOfWorld.GetAttachedVolumesForNode(attachedVolume.NodeName)
|
||||
stillAttached := false
|
||||
for _, volForNode := range attachedVolumesForNode {
|
||||
if volForNode.VolumeName == attachedVolume.VolumeName {
|
||||
stillAttached = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !stillAttached {
|
||||
if klog.V(5).Enabled() {
|
||||
klog.Infof(attachedVolume.GenerateMsgDetailed("Volume detached--skipping", ""))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Set the detach request time
|
||||
elapsedTime, err := rc.actualStateOfWorld.SetDetachRequestTime(attachedVolume.VolumeName, attachedVolume.NodeName)
|
||||
if err != nil {
|
||||
@@ -226,6 +249,29 @@ func (rc *reconciler) reconcile() {
|
||||
func (rc *reconciler) attachDesiredVolumes() {
|
||||
// Ensure volumes that should be attached are attached.
|
||||
for _, volumeToAttach := range rc.desiredStateOfWorld.GetVolumesToAttach() {
|
||||
if util.IsMultiAttachAllowed(volumeToAttach.VolumeSpec) {
|
||||
// Don't even try to start an operation if there is already one running for the given volume and node.
|
||||
if rc.attacherDetacher.IsOperationPending(volumeToAttach.VolumeName, "" /* podName */, volumeToAttach.NodeName) {
|
||||
if klog.V(10).Enabled() {
|
||||
klog.Infof("Operation for volume %q is already running for node %q. Can't start attach", volumeToAttach.VolumeName, volumeToAttach.NodeName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Don't even try to start an operation if there is already one running for the given volume
|
||||
if rc.attacherDetacher.IsOperationPending(volumeToAttach.VolumeName, "" /* podName */, "" /* nodeName */) {
|
||||
if klog.V(10).Enabled() {
|
||||
klog.Infof("Operation for volume %q is already running. Can't start attach for %q", volumeToAttach.VolumeName, volumeToAttach.NodeName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Because the attach operation updates the ActualStateOfWorld before
|
||||
// marking itself complete, IsOperationPending() must be checked before
|
||||
// IsVolumeAttachedToNode() to guarantee the ActualStateOfWorld is
|
||||
// up-to-date when it's read.
|
||||
// See https://github.com/kubernetes/kubernetes/issues/93902
|
||||
if rc.actualStateOfWorld.IsVolumeAttachedToNode(volumeToAttach.VolumeName, volumeToAttach.NodeName) {
|
||||
// Volume/Node exists, touch it to reset detachRequestedTime
|
||||
if klog.V(5).Enabled() {
|
||||
@@ -235,26 +281,7 @@ func (rc *reconciler) attachDesiredVolumes() {
|
||||
continue
|
||||
}
|
||||
|
||||
if util.IsMultiAttachAllowed(volumeToAttach.VolumeSpec) {
|
||||
|
||||
// Don't even try to start an operation if there is already one running for the given volume and node.
|
||||
if rc.attacherDetacher.IsOperationPending(volumeToAttach.VolumeName, "" /* podName */, volumeToAttach.NodeName) {
|
||||
if klog.V(10).Enabled() {
|
||||
klog.Infof("Operation for volume %q is already running for node %q. Can't start attach", volumeToAttach.VolumeName, volumeToAttach.NodeName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Don't even try to start an operation if there is already one running for the given volume
|
||||
if rc.attacherDetacher.IsOperationPending(volumeToAttach.VolumeName, "" /* podName */, "" /* nodeName */) {
|
||||
if klog.V(10).Enabled() {
|
||||
klog.Infof("Operation for volume %q is already running. Can't start attach for %q", volumeToAttach.VolumeName, volumeToAttach.NodeName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !util.IsMultiAttachAllowed(volumeToAttach.VolumeSpec) {
|
||||
nodes := rc.actualStateOfWorld.GetNodesForAttachedVolume(volumeToAttach.VolumeName)
|
||||
if len(nodes) > 0 {
|
||||
if !volumeToAttach.MultiAttachErrorReported {
|
||||
@@ -263,7 +290,6 @@ func (rc *reconciler) attachDesiredVolumes() {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Volume/Node doesn't exist, spawn a goroutine to attach it
|
||||
|
@@ -347,7 +347,7 @@ func Test_Run_Negative_OneDesiredVolumeAttachThenDetachWithUnmountedVolumeUpdate
|
||||
}
|
||||
|
||||
// Creates a volume with accessMode ReadWriteMany
|
||||
// Populates desiredStateOfWorld cache with two ode/volume/pod tuples pointing to the created volume
|
||||
// Populates desiredStateOfWorld cache with two node/volume/pod tuples pointing to the created volume
|
||||
// Calls Run()
|
||||
// Verifies there are two attach calls and no detach calls.
|
||||
// Deletes the first node/volume/pod tuple from desiredStateOfWorld cache without first marking the node/volume as unmounted.
|
||||
@@ -536,7 +536,7 @@ func Test_Run_OneVolumeAttachAndDetachMultipleNodesWithReadWriteOnce(t *testing.
|
||||
// Creates a volume with accessMode ReadWriteOnce
|
||||
// First create a pod which will try to attach the volume to the a node named "uncertain-node". The attach call for this node will
|
||||
// fail for timeout, but the volume will be actually attached to the node after the call.
|
||||
// Secondly, delete the this pod.
|
||||
// Secondly, delete this pod.
|
||||
// Lastly, create a pod scheduled to a normal node which will trigger attach volume to the node. The attach should return successfully.
|
||||
func Test_Run_OneVolumeAttachAndDetachUncertainNodesWithReadWriteOnce(t *testing.T) {
|
||||
// Arrange
|
||||
|
Reference in New Issue
Block a user