// Populates desiredStateOfWorld cache with one node/volume/pod tuple. // Calls Run() // Verifies there is one attach call and no detach calls. func Test_Run_Positive_OneDesiredVolumeAttach(t *testing.T) { // Arrange volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t)) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, dsw, asw, ad) podName := "pod-name" volumeName := api.UniqueDeviceName("volume-name") volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName) nodeName := "node-name" dsw.AddNode(nodeName) volumeExists := dsw.VolumeExists(volumeName, nodeName) if volumeExists { t.Fatalf( "Volume %q/node %q should not exist, but it does.", volumeName, nodeName) } _, podErr := dsw.AddPod(podName, volumeSpec, nodeName) if podErr != nil { t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podErr) } // Act go reconciler.Run(wait.NeverStop) // Assert waitForNewAttacherCallCount(t, 1 /* expectedCallCount */, fakePlugin) waitForAttachCallCount(t, 1 /* expectedAttachCallCount */, fakePlugin) verifyNewDetacherCallCount(t, true /* expectZeroNewDetacherCallCount */, fakePlugin) }
// NewAttachDetachController returns a new instance of AttachDetachController. func NewAttachDetachController( kubeClient internalclientset.Interface, podInformer framework.SharedInformer, nodeInformer framework.SharedInformer, pvcInformer framework.SharedInformer, pvInformer framework.SharedInformer, cloud cloudprovider.Interface, plugins []volume.VolumePlugin) (AttachDetachController, error) { // TODO: The default resyncPeriod for shared informers is 12 hours, this is // unacceptable for the attach/detach controller. For example, if a pod is // skipped because the node it is scheduled to didn't set its annotation in // time, we don't want to have to wait 12hrs before processing the pod // again. // Luckily https://github.com/kubernetes/kubernetes/issues/23394 is being // worked on and will split resync in to resync and relist. Once that // happens the resync period can be set to something much faster (30 // seconds). // If that issue is not resolved in time, then this controller will have to // consider some unappealing alternate options: use a non-shared informer // and set a faster resync period even if it causes relist, or requeue // dropped pods so they are continuously processed until it is accepted or // deleted (probably can't do this with sharedInformer), etc. adc := &attachDetachController{ kubeClient: kubeClient, pvcInformer: pvcInformer, pvInformer: pvInformer, cloud: cloud, } podInformer.AddEventHandler(framework.ResourceEventHandlerFuncs{ AddFunc: adc.podAdd, UpdateFunc: adc.podUpdate, DeleteFunc: adc.podDelete, }) nodeInformer.AddEventHandler(framework.ResourceEventHandlerFuncs{ AddFunc: adc.nodeAdd, UpdateFunc: adc.nodeUpdate, DeleteFunc: adc.nodeDelete, }) if err := adc.volumePluginMgr.InitPlugins(plugins, adc); err != nil { return nil, fmt.Errorf("Could not initialize volume plugins for Attach/Detach Controller: %+v", err) } adc.desiredStateOfWorld = cache.NewDesiredStateOfWorld(&adc.volumePluginMgr) adc.actualStateOfWorld = cache.NewActualStateOfWorld(&adc.volumePluginMgr) adc.attacherDetacher = attacherdetacher.NewAttacherDetacher(&adc.volumePluginMgr) adc.reconciler = reconciler.NewReconciler( reconcilerLoopPeriod, reconcilerMaxWaitForUnmountDuration, adc.desiredStateOfWorld, adc.actualStateOfWorld, adc.attacherDetacher) return adc, nil }
func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithMarkVolume(t *testing.T) { // Arrange volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t)) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr) reconciler := NewReconciler( reconcilerLoopPeriod, maxSafeToDetachDuration, dsw, asw, ad) podName := "pod-name" volumeName := "volume-name" volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName) nodeName := "node-name" dsw.AddNode(nodeName) volumeExists := dsw.VolumeExists(volumeName, nodeName) if volumeExists { t.Fatalf( "Volume %q/node %q should not exist, but it does.", volumeName, nodeName) } generatedVolumeName, podAddErr := dsw.AddPod(podName, volumeSpec, nodeName) if podAddErr != nil { t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podAddErr) } // Act go reconciler.Run(wait.NeverStop) // Assert waitForNewAttacherCallCount(t, 1 /* expectedCallCount */, fakePlugin) verifyNewAttacherCallCount(t, false /* expectZeroNewAttacherCallCount */, fakePlugin) waitForAttachCallCount(t, 1 /* expectedAttachCallCount */, fakePlugin) verifyNewDetacherCallCount(t, true /* expectZeroNewDetacherCallCount */, fakePlugin) waitForDetachCallCount(t, 0 /* expectedDetachCallCount */, fakePlugin) // Act dsw.DeletePod(podName, generatedVolumeName, nodeName) volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName) if volumeExists { t.Fatalf( "Deleted pod %q from volume %q/node %q. Volume should also be deleted but it still exists.", podName, generatedVolumeName, nodeName) } asw.MarkVolumeNodeSafeToDetach(generatedVolumeName, nodeName) // Assert -- Marked SafeToDetach waitForNewDetacherCallCount(t, 1 /* expectedCallCount */, fakePlugin) verifyNewAttacherCallCount(t, false /* expectZeroNewAttacherCallCount */, fakePlugin) waitForAttachCallCount(t, 1 /* expectedAttachCallCount */, fakePlugin) verifyNewDetacherCallCount(t, false /* expectZeroNewDetacherCallCount */, fakePlugin) waitForDetachCallCount(t, 1 /* expectedDetachCallCount */, fakePlugin) }
// Calls Run() // Verifies there are no calls to attach or detach. func Test_Run_Positive_DoNothing(t *testing.T) { // Arrange volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t)) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, dsw, asw, ad) // Act go reconciler.Run(wait.NeverStop) // Assert waitForNewAttacherCallCount(t, 0 /* expectedCallCount */, fakePlugin) verifyNewAttacherCallCount(t, true /* expectZeroNewAttacherCallCount */, fakePlugin) verifyNewDetacherCallCount(t, true /* expectZeroNewDetacherCallCount */, fakePlugin) waitForAttachCallCount(t, 0 /* expectedAttachCallCount */, fakePlugin) waitForDetachCallCount(t, 0 /* expectedDetachCallCount */, fakePlugin) }