Commit f0962765 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #46182 from FengyunPan/check_detach

Automatic merge from submit-queue Check volume's status before detaching volume When volume's status is 'detaching', controllermanager will detach it again and return err. It is necessary to check volume's status before detaching volume. same issue: #44536
parents c79df643 300f5313
...@@ -35,6 +35,7 @@ go_library( ...@@ -35,6 +35,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
], ],
) )
......
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/util/exec" "k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
...@@ -41,7 +42,13 @@ var _ volume.Attacher = &cinderDiskAttacher{} ...@@ -41,7 +42,13 @@ var _ volume.Attacher = &cinderDiskAttacher{}
var _ volume.AttachableVolumePlugin = &cinderPlugin{} var _ volume.AttachableVolumePlugin = &cinderPlugin{}
const ( const (
checkSleepDuration = time.Second checkSleepDuration = 1 * time.Second
operationFinishInitDealy = 1 * time.Second
operationFinishFactor = 1.1
operationFinishSteps = 10
diskDetachInitDealy = 1 * time.Second
diskDetachFactor = 1.2
diskDetachSteps = 13
) )
func (plugin *cinderPlugin) NewAttacher() (volume.Attacher, error) { func (plugin *cinderPlugin) NewAttacher() (volume.Attacher, error) {
...@@ -267,6 +274,63 @@ func (plugin *cinderPlugin) NewDetacher() (volume.Detacher, error) { ...@@ -267,6 +274,63 @@ func (plugin *cinderPlugin) NewDetacher() (volume.Detacher, error) {
}, nil }, nil
} }
func (detacher *cinderDiskDetacher) waitOperationFinished(volumeID string) error {
backoff := wait.Backoff{
Duration: operationFinishInitDealy,
Factor: operationFinishFactor,
Steps: operationFinishSteps,
}
var volumeStatus string
err := wait.ExponentialBackoff(backoff, func() (bool, error) {
var pending bool
var err error
pending, volumeStatus, err = detacher.cinderProvider.OperationPending(volumeID)
if err == nil {
if pending == false {
return true, nil
} else {
return false, nil
}
} else {
return false, err
}
})
if err == wait.ErrWaitTimeout {
err = fmt.Errorf("Volume %q is %s, can't finish within the alloted time", volumeID, volumeStatus)
}
return err
}
func (detacher *cinderDiskDetacher) waitDiskDetached(instanceID, volumeID string) error {
backoff := wait.Backoff{
Duration: diskDetachInitDealy,
Factor: diskDetachFactor,
Steps: diskDetachSteps,
}
err := wait.ExponentialBackoff(backoff, func() (bool, error) {
attached, err := detacher.cinderProvider.DiskIsAttached(instanceID, volumeID)
if err == nil {
if attached == false {
return true, nil
} else {
return false, nil
}
} else {
return false, err
}
})
if err == wait.ErrWaitTimeout {
err = fmt.Errorf("Volume %q failed to detach within the alloted time", volumeID)
}
return err
}
func (detacher *cinderDiskDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error { func (detacher *cinderDiskDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error {
volumeID := path.Base(deviceMountPath) volumeID := path.Base(deviceMountPath)
instances, res := detacher.cinderProvider.Instances() instances, res := detacher.cinderProvider.Instances()
...@@ -278,6 +342,10 @@ func (detacher *cinderDiskDetacher) Detach(deviceMountPath string, nodeName type ...@@ -278,6 +342,10 @@ func (detacher *cinderDiskDetacher) Detach(deviceMountPath string, nodeName type
instanceID = instanceID[(ind + 1):] instanceID = instanceID[(ind + 1):]
} }
if err := detacher.waitOperationFinished(volumeID); err != nil {
return err
}
attached, err := detacher.cinderProvider.DiskIsAttached(instanceID, volumeID) attached, err := detacher.cinderProvider.DiskIsAttached(instanceID, volumeID)
if err != nil { if err != nil {
// Log error and continue with detach // Log error and continue with detach
...@@ -293,10 +361,14 @@ func (detacher *cinderDiskDetacher) Detach(deviceMountPath string, nodeName type ...@@ -293,10 +361,14 @@ func (detacher *cinderDiskDetacher) Detach(deviceMountPath string, nodeName type
} }
if err = detacher.cinderProvider.DetachDisk(instanceID, volumeID); err != nil { if err = detacher.cinderProvider.DetachDisk(instanceID, volumeID); err != nil {
glog.Errorf("Error detaching volume %q: %v", volumeID, err) glog.Errorf("Error detaching volume %q from node %q: %v", volumeID, nodeName, err)
return err
}
if err = detacher.waitDiskDetached(instanceID, volumeID); err != nil {
glog.Errorf("Error waiting for volume %q to detach from node %q: %v", volumeID, nodeName, err)
return err return err
} }
glog.Infof("detached volume %q from instance %q", volumeID, instanceID) glog.Infof("detached volume %q from node %q", volumeID, nodeName)
return nil return nil
} }
......
...@@ -36,6 +36,8 @@ import ( ...@@ -36,6 +36,8 @@ import (
const VolumeStatusPending = "pending" const VolumeStatusPending = "pending"
const VolumeStatusDone = "done" const VolumeStatusDone = "done"
var VolumeIsNotAttached = true
func TestGetDeviceName_Volume(t *testing.T) { func TestGetDeviceName_Volume(t *testing.T) {
plugin := newPlugin() plugin := newPlugin()
name := "my-cinder-volume" name := "my-cinder-volume"
...@@ -96,6 +98,7 @@ type testcase struct { ...@@ -96,6 +98,7 @@ type testcase struct {
disksAreAttached disksAreAttachedCall disksAreAttached disksAreAttachedCall
diskPath diskPathCall diskPath diskPathCall
t *testing.T t *testing.T
notAttached *bool
instanceID string instanceID string
// Actual test to run // Actual test to run
...@@ -118,6 +121,7 @@ func TestAttachDetach(t *testing.T) { ...@@ -118,6 +121,7 @@ func TestAttachDetach(t *testing.T) {
diskCheckError := errors.New("Fake DiskIsAttached error") diskCheckError := errors.New("Fake DiskIsAttached error")
diskPathError := errors.New("Fake GetAttachmentDiskPath error") diskPathError := errors.New("Fake GetAttachmentDiskPath error")
disksCheckError := errors.New("Fake DisksAreAttached error") disksCheckError := errors.New("Fake DisksAreAttached error")
operationFinishTimeout := errors.New("Fake waitOperationFinished error")
tests := []testcase{ tests := []testcase{
// Successful Attach call // Successful Attach call
{ {
...@@ -247,10 +251,11 @@ func TestAttachDetach(t *testing.T) { ...@@ -247,10 +251,11 @@ func TestAttachDetach(t *testing.T) {
// Detach succeeds // Detach succeeds
{ {
name: "Detach_Positive", name: "Detach_Positive",
instanceID: instanceID, instanceID: instanceID,
diskIsAttached: diskIsAttachedCall{instanceID, volumeID, true, nil}, operationPending: operationPendingCall{volumeID, false, done, nil},
detach: detachCall{instanceID, volumeID, nil}, diskIsAttached: diskIsAttachedCall{instanceID, volumeID, true, nil},
detach: detachCall{instanceID, volumeID, nil},
test: func(testcase *testcase) (string, error) { test: func(testcase *testcase) (string, error) {
detacher := newDetacher(testcase) detacher := newDetacher(testcase)
return "", detacher.Detach(volumeID, nodeName) return "", detacher.Detach(volumeID, nodeName)
...@@ -259,9 +264,10 @@ func TestAttachDetach(t *testing.T) { ...@@ -259,9 +264,10 @@ func TestAttachDetach(t *testing.T) {
// Disk is already detached // Disk is already detached
{ {
name: "Detach_Positive_AlreadyDetached", name: "Detach_Positive_AlreadyDetached",
instanceID: instanceID, instanceID: instanceID,
diskIsAttached: diskIsAttachedCall{instanceID, volumeID, false, nil}, operationPending: operationPendingCall{volumeID, false, done, nil},
diskIsAttached: diskIsAttachedCall{instanceID, volumeID, false, nil},
test: func(testcase *testcase) (string, error) { test: func(testcase *testcase) (string, error) {
detacher := newDetacher(testcase) detacher := newDetacher(testcase)
return "", detacher.Detach(volumeID, nodeName) return "", detacher.Detach(volumeID, nodeName)
...@@ -270,10 +276,11 @@ func TestAttachDetach(t *testing.T) { ...@@ -270,10 +276,11 @@ func TestAttachDetach(t *testing.T) {
// Detach succeeds when DiskIsAttached fails // Detach succeeds when DiskIsAttached fails
{ {
name: "Detach_Positive_CheckFails", name: "Detach_Positive_CheckFails",
instanceID: instanceID, instanceID: instanceID,
diskIsAttached: diskIsAttachedCall{instanceID, volumeID, false, diskCheckError}, operationPending: operationPendingCall{volumeID, false, done, nil},
detach: detachCall{instanceID, volumeID, nil}, diskIsAttached: diskIsAttachedCall{instanceID, volumeID, false, diskCheckError},
detach: detachCall{instanceID, volumeID, nil},
test: func(testcase *testcase) (string, error) { test: func(testcase *testcase) (string, error) {
detacher := newDetacher(testcase) detacher := newDetacher(testcase)
return "", detacher.Detach(volumeID, nodeName) return "", detacher.Detach(volumeID, nodeName)
...@@ -282,20 +289,36 @@ func TestAttachDetach(t *testing.T) { ...@@ -282,20 +289,36 @@ func TestAttachDetach(t *testing.T) {
// Detach fails // Detach fails
{ {
name: "Detach_Negative", name: "Detach_Negative",
instanceID: instanceID, instanceID: instanceID,
diskIsAttached: diskIsAttachedCall{instanceID, volumeID, false, diskCheckError}, operationPending: operationPendingCall{volumeID, false, done, nil},
detach: detachCall{instanceID, volumeID, detachError}, diskIsAttached: diskIsAttachedCall{instanceID, volumeID, false, diskCheckError},
detach: detachCall{instanceID, volumeID, detachError},
test: func(testcase *testcase) (string, error) { test: func(testcase *testcase) (string, error) {
detacher := newDetacher(testcase) detacher := newDetacher(testcase)
return "", detacher.Detach(volumeID, nodeName) return "", detacher.Detach(volumeID, nodeName)
}, },
expectedError: detachError, expectedError: detachError,
}, },
// // Disk is detaching
{
name: "Detach_Is_Detaching",
instanceID: instanceID,
operationPending: operationPendingCall{volumeID, true, pending, operationFinishTimeout},
test: func(testcase *testcase) (string, error) {
detacher := newDetacher(testcase)
return "", detacher.Detach(volumeID, nodeName)
},
expectedError: operationFinishTimeout,
},
} }
for _, testcase := range tests { for _, testcase := range tests {
testcase.t = t testcase.t = t
// set VolumeIsNotAttached to test detach case, attach case ignore it
VolumeIsNotAttached = false
testcase.notAttached = &VolumeIsNotAttached
result, err := testcase.test(&testcase) result, err := testcase.test(&testcase)
if err != testcase.expectedError { if err != testcase.expectedError {
t.Errorf("%s failed: expected err=%q, got %q", testcase.name, testcase.expectedError, err) t.Errorf("%s failed: expected err=%q, got %q", testcase.name, testcase.expectedError, err)
...@@ -457,6 +480,8 @@ func (testcase *testcase) AttachDisk(instanceID, volumeID string) (string, error ...@@ -457,6 +480,8 @@ func (testcase *testcase) AttachDisk(instanceID, volumeID string) (string, error
glog.V(4).Infof("AttachDisk call: %s, %s, returning %q, %v", volumeID, instanceID, expected.retDeviceName, expected.ret) glog.V(4).Infof("AttachDisk call: %s, %s, returning %q, %v", volumeID, instanceID, expected.retDeviceName, expected.ret)
VolumeIsNotAttached = false
testcase.notAttached = &VolumeIsNotAttached
return expected.retDeviceName, expected.ret return expected.retDeviceName, expected.ret
} }
...@@ -482,6 +507,8 @@ func (testcase *testcase) DetachDisk(instanceID, volumeID string) error { ...@@ -482,6 +507,8 @@ func (testcase *testcase) DetachDisk(instanceID, volumeID string) error {
glog.V(4).Infof("DetachDisk call: %s, %s, returning %v", volumeID, instanceID, expected.ret) glog.V(4).Infof("DetachDisk call: %s, %s, returning %v", volumeID, instanceID, expected.ret)
VolumeIsNotAttached = true
testcase.notAttached = &VolumeIsNotAttached
return expected.ret return expected.ret
} }
...@@ -500,6 +527,10 @@ func (testcase *testcase) OperationPending(diskName string) (bool, string, error ...@@ -500,6 +527,10 @@ func (testcase *testcase) OperationPending(diskName string) (bool, string, error
func (testcase *testcase) DiskIsAttached(instanceID, volumeID string) (bool, error) { func (testcase *testcase) DiskIsAttached(instanceID, volumeID string) (bool, error) {
expected := &testcase.diskIsAttached expected := &testcase.diskIsAttached
// If testcase call DetachDisk*, return false
if *testcase.notAttached == true {
return false, nil
}
if expected.volumeID == "" && expected.instanceID == "" { if expected.volumeID == "" && expected.instanceID == "" {
// testcase.diskIsAttached looks uninitialized, test did not expect to // testcase.diskIsAttached looks uninitialized, test did not expect to
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment