Skip to content

Commit

Permalink
Merge branch 'fix-kubelet-volumemanager' into 'qcloud/v1.10.5'
Browse files Browse the repository at this point in the history
Fix race between MountVolume and UnmountDevice (non-official backport to 1.10.5)

Fix race between MountVolume and UnmountDevice (non-official backport to 1.10.5)

FYI. this issue is fixed in by kubernetes#71074

k8s version	fixed version
v1.10	        no fix
v1.11	        1.11.7
v1.12	        1.12.5
v1.13	        no such issue


See merge request !55
  • Loading branch information
timxbxu committed Aug 8, 2019
2 parents e8d9a4f + f40362a commit 40cdb7f
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 4 deletions.
4 changes: 3 additions & 1 deletion pkg/kubelet/volumemanager/cache/actual_state_of_world.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,9 @@ func (asw *actualStateOfWorld) SetVolumeGloballyMounted(

volumeObj.globallyMounted = globallyMounted
volumeObj.deviceMountPath = deviceMountPath
volumeObj.devicePath = devicePath
if devicePath != "" {
volumeObj.devicePath = devicePath
}
asw.attachedVolumes[volumeName] = volumeObj
return nil
}
Expand Down
96 changes: 94 additions & 2 deletions pkg/kubelet/volumemanager/reconciler/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1029,8 +1029,8 @@ func createTestClient() *fake.Clientset {
Status: v1.NodeStatus{
VolumesAttached: []v1.AttachedVolume{
{
Name: "fake-plugin/volume-name",
DevicePath: "fake/path",
Name: "fake-plugin/fake-device1",
DevicePath: "/fake/path",
},
}},
Spec: v1.NodeSpec{ExternalID: string(nodeName)},
Expand All @@ -1045,3 +1045,95 @@ func createTestClient() *fake.Clientset {
func runReconciler(reconciler Reconciler) {
go reconciler.Run(wait.NeverStop)
}

func Test_Run_Positive_VolumeMountControllerAttachEnabledRace(t *testing.T) {
// Arrange
volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)

dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
kubeClient := createTestClient()
fakeRecorder := &record.FakeRecorder{}
fakeHandler := volumetesting.NewBlockVolumePathHandler()
oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
kubeClient,
volumePluginMgr,
fakeRecorder,
false, /* checkNodeCapabilitiesBeforeMount */
fakeHandler))
reconciler := NewReconciler(
kubeClient,
true, /* controllerAttachDetachEnabled */
reconcilerLoopSleepDuration,
reconcilerSyncStatesSleepPeriod,
waitForAttachTimeout,
nodeName,
dsw,
asw,
hasAddedPods,
oex,
&mount.FakeMounter{},
volumePluginMgr,
kubeletPodsDir)
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
UID: "pod1uid",
},
Spec: v1.PodSpec{
Volumes: []v1.Volume{
{
Name: "volume-name",
VolumeSource: v1.VolumeSource{
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
PDName: "fake-device1",
},
},
},
},
},
}

// Some steps are executes out of order in callbacks, follow the numbers.

// 1. Add a volume to DSW and wait until it's mounted
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})

if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
}
runReconciler(reconciler)
waitForMount(t, fakePlugin, generatedVolumeName, asw)

finished := make(chan interface{})
fakePlugin.UnmountDeviceHook = func(mountPath string) error {
// Act:
// 3. While a volume is being unmounted, add it back to the desired state of world
generatedVolumeName, err = dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})
return nil
}

fakePlugin.WaitForAttachHook = func(spec *volume.Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error) {
// Assert
// 4. When the volume is mounted again, expect that UnmountDevice operation did not clear devicePath
if devicePath == "" {
t.Errorf("Expected WaitForAttach called with devicePath from Node.Status")
close(finished)
return "", fmt.Errorf("Expected devicePath from Node.Status")
}
close(finished)
return devicePath, nil
}

// 2. Delete the volume from DSW (and wait for callbacks)
dsw.DeletePodFromVolume(podName, generatedVolumeName)

<-finished
waitForMount(t, fakePlugin, generatedVolumeName, asw)
}
19 changes: 18 additions & 1 deletion pkg/volume/testing/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ type FakeVolumePlugin struct {
NewAttacherCallCount int
NewDetacherCallCount int

// Add callbacks as needed
WaitForAttachHook func(spec *Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error)
UnmountDeviceHook func(globalMountPath string) error

Mounters []*FakeVolume
Unmounters []*FakeVolume
Attachers []*FakeVolume
Expand All @@ -231,7 +235,10 @@ var _ ProvisionableVolumePlugin = &FakeVolumePlugin{}
var _ AttachableVolumePlugin = &FakeVolumePlugin{}

func (plugin *FakeVolumePlugin) getFakeVolume(list *[]*FakeVolume) *FakeVolume {
volume := &FakeVolume{}
volume := &FakeVolume{
WaitForAttachHook: plugin.WaitForAttachHook,
UnmountDeviceHook: plugin.UnmountDeviceHook,
}
*list = append(*list, volume)
return volume
}
Expand Down Expand Up @@ -474,6 +481,10 @@ type FakeVolume struct {
Plugin *FakeVolumePlugin
MetricsNil

// Add callbacks as needed
WaitForAttachHook func(spec *Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error)
UnmountDeviceHook func(globalMountPath string) error

SetUpCallCount int
TearDownCallCount int
AttachCallCount int
Expand Down Expand Up @@ -631,6 +642,9 @@ func (fv *FakeVolume) WaitForAttach(spec *Spec, devicePath string, pod *v1.Pod,
fv.Lock()
defer fv.Unlock()
fv.WaitForAttachCallCount++
if fv.WaitForAttachHook != nil {
return fv.WaitForAttachHook(spec, devicePath, pod, spectimeout)
}
return "/dev/sdb", nil
}

Expand Down Expand Up @@ -683,6 +697,9 @@ func (fv *FakeVolume) UnmountDevice(globalMountPath string) error {
fv.Lock()
defer fv.Unlock()
fv.UnmountDeviceCallCount++
if fv.UnmountDeviceHook != nil {
return fv.UnmountDeviceHook(globalMountPath)
}
return nil
}

Expand Down

0 comments on commit 40cdb7f

Please sign in to comment.