func TestOperationExecutor_UnmountVolume_ConcurrentUnmountForAllPlugins(t *testing.T) { // Arrange ch, quit, oe := setup() volumesToUnmount := make([]MountedVolume, numAttachableVolumesToUnmount+numNonAttachableVolumesToUnmount) pdName := "pd-volume" secretName := "secret-volume" // Act for i := 0; i < numNonAttachableVolumesToUnmount+numAttachableVolumesToUnmount; i++ { podName := "pod-" + strconv.Itoa(i+1) if i < numNonAttachableVolumesToUnmount { pod := getTestPodWithSecret(podName, secretName) volumesToUnmount[i] = MountedVolume{ PodName: volumetypes.UniquePodName(podName), VolumeName: v1.UniqueVolumeName(secretName), PodUID: pod.UID, } } else { pod := getTestPodWithGCEPD(podName, pdName) volumesToUnmount[i] = MountedVolume{ PodName: volumetypes.UniquePodName(podName), VolumeName: v1.UniqueVolumeName(pdName), PodUID: pod.UID, } } oe.UnmountVolume(volumesToUnmount[i], nil /* actualStateOfWorldMounterUpdater */) } // Assert if !isOperationRunConcurrently(ch, quit, numNonAttachableVolumesToUnmount+numAttachableVolumesToUnmount) { t.Fatalf("Unable to start unmount operations concurrently for volume plugins") } }
// Populates desiredStateOfWorld cache with one node/volume/pod tuple. // Calls Run() // Verifies there is one attach call and no detach calls. // Marks the node/volume as unmounted. // Deletes the node/volume/pod tuple from desiredStateOfWorld cache. // Verifies there is one detach call and no (new) attach calls. func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithUnmountedVolume(t *testing.T) { // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) fakeKubeClient := controllervolumetesting.CreateTestClient() ad := operationexecutor.NewOperationExecutor(fakeKubeClient, volumePluginMgr) nsu := statusupdater.NewFakeNodeStatusUpdater(false /* returnError */) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, dsw, asw, ad, nsu) podName := "pod-uid" volumeName := api.UniqueVolumeName("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) } generatedVolumeName, podAddErr := dsw.AddPod(types.UniquePodName(podName), controllervolumetesting.NewPod(podName, podName), volumeSpec, nodeName) if podAddErr != nil { t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podAddErr) } // Act ch := make(chan struct{}) go reconciler.Run(ch) defer close(ch) // 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(types.UniquePodName(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.SetVolumeMountedByNode(generatedVolumeName, nodeName, true /* mounted */) asw.SetVolumeMountedByNode(generatedVolumeName, nodeName, false /* mounted */) // Assert 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) }
// Populates data struct with pod1/volume/node and pod2/volume/node. // Calls DeleteNode() to delete the pod1/volume/node. // Verifies volume still exists, and one volumes to attach. func Test_DeletePod_Positive_2PodsExistNodeExistsVolumesExist(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) pod1Name := "pod1-uid" pod2Name := "pod2-uid" volumeName := v1.UniqueVolumeName("volume-name") volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName) nodeName := k8stypes.NodeName("node-name") dsw.AddNode(nodeName) generatedVolumeName1, pod1AddErr := dsw.AddPod(types.UniquePodName(pod1Name), controllervolumetesting.NewPod(pod1Name, pod1Name), volumeSpec, nodeName) if pod1AddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", pod1Name, pod1AddErr) } generatedVolumeName2, pod2AddErr := dsw.AddPod(types.UniquePodName(pod2Name), controllervolumetesting.NewPod(pod2Name, pod2Name), volumeSpec, nodeName) if pod2AddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", pod2Name, pod2AddErr) } if generatedVolumeName1 != generatedVolumeName2 { t.Fatalf( "Generated volume names for the same volume should be the same but they are not: %q and %q", generatedVolumeName1, generatedVolumeName2) } volumeExists := dsw.VolumeExists(generatedVolumeName1, nodeName) if !volumeExists { t.Fatalf( "Volume %q does not exist under node %q, it should.", generatedVolumeName1, nodeName) } // Act dsw.DeletePod(types.UniquePodName(pod1Name), generatedVolumeName1, nodeName) // Assert volumeExists = dsw.VolumeExists(generatedVolumeName1, nodeName) if !volumeExists { t.Fatalf( "Volume %q under node %q should still exist, but it does not.", generatedVolumeName1, nodeName) } volumesToAttach := dsw.GetVolumesToAttach() if len(volumesToAttach) != 1 { t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach)) } verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName1, string(volumeName)) }
// Populates data struct with pod/volume1/node. // Calls DeleteNode() to delete the pod/volume2/node. // Verifies volume still exists, and one volumes to attach. func Test_DeletePod_Positive_VolumeDoesNotExist(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) podName := "pod-uid" volume1Name := v1.UniqueVolumeName("volume1-name") volume1Spec := controllervolumetesting.GetTestVolumeSpec(string(volume1Name), volume1Name) nodeName := k8stypes.NodeName("node-name") dsw.AddNode(nodeName) generatedVolume1Name, podAddErr := dsw.AddPod(types.UniquePodName(podName), controllervolumetesting.NewPod(podName, podName), volume1Spec, nodeName) if podAddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", podName, podAddErr) } volumeExists := dsw.VolumeExists(generatedVolume1Name, nodeName) if !volumeExists { t.Fatalf( "Added pod %q to volume %q/node %q. Volume does not exist, it should.", podName, generatedVolume1Name, nodeName) } volume2Name := v1.UniqueVolumeName("volume2-name") // Act dsw.DeletePod(types.UniquePodName(podName), volume2Name, nodeName) // Assert volumeExists = dsw.VolumeExists(generatedVolume1Name, nodeName) if !volumeExists { t.Fatalf( "Volume %q/node %q does not exist, it should.", generatedVolume1Name, nodeName) } volumeExists = dsw.VolumeExists(volume2Name, nodeName) if volumeExists { t.Fatalf( "volume %q exists, it should not.", volume2Name) } volumesToAttach := dsw.GetVolumesToAttach() if len(volumesToAttach) != 1 { t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach)) } verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolume1Name, string(volume1Name)) }
// Populates data struct with two nodes with one volume/pod each and an extra // pod for the second node/volume pair. // Calls GetVolumesToAttach() // Verifies two volumes to attach. func Test_GetVolumesToAttach_Positive_TwoNodesOneVolumeEachExtraPod(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) node1Name := k8stypes.NodeName("node1-name") pod1Name := "pod1-uid" volume1Name := v1.UniqueVolumeName("volume1-name") volume1Spec := controllervolumetesting.GetTestVolumeSpec(string(volume1Name), volume1Name) dsw.AddNode(node1Name) generatedVolume1Name, podAddErr := dsw.AddPod(types.UniquePodName(pod1Name), controllervolumetesting.NewPod(pod1Name, pod1Name), volume1Spec, node1Name) if podAddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", pod1Name, podAddErr) } node2Name := k8stypes.NodeName("node2-name") pod2Name := "pod2-uid" volume2Name := v1.UniqueVolumeName("volume2-name") volume2Spec := controllervolumetesting.GetTestVolumeSpec(string(volume2Name), volume2Name) dsw.AddNode(node2Name) generatedVolume2Name, podAddErr := dsw.AddPod(types.UniquePodName(pod2Name), controllervolumetesting.NewPod(pod2Name, pod2Name), volume2Spec, node2Name) if podAddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", pod2Name, podAddErr) } pod3Name := "pod3-uid" dsw.AddPod(types.UniquePodName(pod3Name), controllervolumetesting.NewPod(pod3Name, pod3Name), volume2Spec, node2Name) _, podAddErr = dsw.AddPod(types.UniquePodName(pod3Name), controllervolumetesting.NewPod(pod3Name, pod3Name), volume2Spec, node2Name) if podAddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", pod3Name, podAddErr) } // Act volumesToAttach := dsw.GetVolumesToAttach() // Assert if len(volumesToAttach) != 2 { t.Fatalf("len(volumesToAttach) Expected: <2> Actual: <%v>", len(volumesToAttach)) } verifyVolumeToAttach(t, volumesToAttach, node1Name, generatedVolume1Name, string(volume1Name)) verifyVolumeToAttach(t, volumesToAttach, node2Name, generatedVolume2Name, string(volume2Name)) }
// Populates data struct with new pod/volume/node. // Calls VolumeExists() on that volume/node. // Verifies volume/node exists, and one volume to attach. func Test_VolumeExists_Positive_VolumeExistsNodeExists(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) nodeName := k8stypes.NodeName("node-name") dsw.AddNode(nodeName) podName := "pod-uid" volumeName := v1.UniqueVolumeName("volume-name") volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName) generatedVolumeName, _ := dsw.AddPod(types.UniquePodName(podName), controllervolumetesting.NewPod(podName, podName), volumeSpec, nodeName) // Act volumeExists := dsw.VolumeExists(generatedVolumeName, nodeName) // Assert if !volumeExists { t.Fatalf("Volume %q does not exist, it should.", generatedVolumeName) } volumesToAttach := dsw.GetVolumesToAttach() if len(volumesToAttach) != 1 { t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach)) } verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName, string(volumeName)) }
// 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 := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) ad := operationexecutor.NewOperationExecutor(volumePluginMgr) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, dsw, asw, ad) podName := types.UniquePodName("pod-uid") volumeName := api.UniqueVolumeName("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) }
// podVolumesExist checks wiht the volume manager and returns true any of the // pods for the specified volume are mounted. func (kl *Kubelet) podVolumesExist(podUID types.UID) bool { if mountedVolumes := kl.volumeManager.GetMountedVolumesForPod( volumetypes.UniquePodName(podUID)); len(mountedVolumes) > 0 { return true } return false }
// Populates data struct with new pod/volume/node. // Calls DeleteNode() to delete the pod/volume/node. // Verifies volume no longer exists, and zero volumes to attach. func Test_DeletePod_Positive_PodExistsNodeExistsVolumeExists(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) podName := "pod-uid" volumeName := v1.UniqueVolumeName("volume-name") volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName) nodeName := k8stypes.NodeName("node-name") dsw.AddNode(nodeName) generatedVolumeName, podAddErr := dsw.AddPod(types.UniquePodName(podName), controllervolumetesting.NewPod(podName, podName), volumeSpec, nodeName) if podAddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", podName, podAddErr) } volumeExists := dsw.VolumeExists(generatedVolumeName, nodeName) if !volumeExists { t.Fatalf( "Added pod %q to volume %q/node %q. Volume does not exist, it should.", podName, generatedVolumeName, nodeName) } // Act dsw.DeletePod(types.UniquePodName(podName), generatedVolumeName, nodeName) // Assert 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) } volumesToAttach := dsw.GetVolumesToAttach() if len(volumesToAttach) != 0 { t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach)) } }
// ListVolumesForPod returns a map of the mounted volumes for the given pod. // The key in the map is the OuterVolumeSpecName (i.e. pod.Spec.Volumes[x].Name) func (kl *Kubelet) ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) { volumesToReturn := make(map[string]volume.Volume) podVolumes := kl.volumeManager.GetMountedVolumesForPod( volumetypes.UniquePodName(podUID)) for outerVolumeSpecName, volume := range podVolumes { volumesToReturn[outerVolumeSpecName] = volume.Mounter } return volumesToReturn, len(volumesToReturn) > 0 }
func Test_NewGoRoutineMap_Positive_TwoSubOps(t *testing.T) { // Arrange grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */) volumeName := v1.UniqueVolumeName("volume-name") operation1PodName := types.UniquePodName("operation1-podname") operation2PodName := types.UniquePodName("operation2-podname") operation := func() error { return nil } // Act err1 := grm.Run(volumeName, operation1PodName, operation) err2 := grm.Run(volumeName, operation2PodName, operation) // Assert if err1 != nil { t.Fatalf("NewGoRoutine %q failed. Expected: <no error> Actual: <%v>", operation1PodName, err1) } if err2 != nil { t.Fatalf("NewGoRoutine %q failed. Expected: <no error> Actual: <%v>", operation2PodName, err2) } }
func (oe *operationExecutor) UnmountVolume( volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) error { unmountFunc, err := oe.generateUnmountVolumeFunc(volumeToUnmount, actualStateOfWorld) if err != nil { return err } // All volume plugins can execute mount for multiple pods referencing the // same volume in parallel podName := volumetypes.UniquePodName(volumeToUnmount.PodUID) return oe.pendingOperations.Run( volumeToUnmount.VolumeName, podName, unmountFunc) }
// ListVolumesForPod returns a map of the mounted volumes for the given pod. // The key in the map is the OuterVolumeSpecName (i.e. pod.Spec.Volumes[x].Name) func (kl *Kubelet) ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) { volumesToReturn := make(map[string]volume.Volume) podVolumes := kl.volumeManager.GetMountedVolumesForPod( volumetypes.UniquePodName(podUID)) for outerVolumeSpecName, volume := range podVolumes { // TODO: volume.Mounter could be nil if volume object is recovered // from reconciler's sync state process. PR 33616 will fix this problem // to create Mounter object when recovering volume state. if volume.Mounter == nil { continue } volumesToReturn[outerVolumeSpecName] = volume.Mounter } return volumesToReturn, len(volumesToReturn) > 0 }
// getVolumesFromPodDir scans through the volumes directories under the given pod directory. // It returns a list of pod volume information including pod's uid, volume's plugin name, mount path, // and volume spec name. func getVolumesFromPodDir(podDir string) ([]podVolume, error) { podsDirInfo, err := ioutil.ReadDir(podDir) if err != nil { return nil, err } volumes := []podVolume{} for i := range podsDirInfo { if !podsDirInfo[i].IsDir() { continue } podName := podsDirInfo[i].Name() podDir := path.Join(podDir, podName) volumesDir := path.Join(podDir, options.DefaultKubeletVolumesDirName) volumesDirInfo, err := ioutil.ReadDir(volumesDir) if err != nil { glog.Errorf("Could not read volume directory %q: %v", volumesDir, err) continue } for _, volumeDir := range volumesDirInfo { pluginName := volumeDir.Name() volumePluginPath := path.Join(volumesDir, pluginName) volumePluginDirs, err := ioutil.ReadDir(volumePluginPath) if err != nil { glog.Errorf("Could not read volume plugin directory %q: %v", volumePluginPath, err) continue } unescapePluginName := strings.UnescapeQualifiedNameForDisk(pluginName) for _, volumeNameDir := range volumePluginDirs { if volumeNameDir != nil { volumeName := volumeNameDir.Name() mountPath := path.Join(volumePluginPath, volumeName) volumes = append(volumes, podVolume{ podName: volumetypes.UniquePodName(podName), volumeSpecName: volumeName, mountPath: mountPath, pluginName: unescapePluginName, }) } } } } glog.V(10).Infof("Get volumes from pod directory %q %+v", podDir, volumes) return volumes, nil }
func TestGetMountedVolumesForPodAndGetVolumesInUse(t *testing.T) { tmpDir, err := utiltesting.MkTmpdir("volumeManagerTest") if err != nil { t.Fatalf("can't make a temp dir: %v", err) } defer os.RemoveAll(tmpDir) podManager := kubepod.NewBasicPodManager(podtest.NewFakeMirrorClient()) node, pod, pv, claim := createObjects() kubeClient := fake.NewSimpleClientset(node, pod, pv, claim) manager, err := newTestVolumeManager(tmpDir, podManager, kubeClient) if err != nil { t.Fatalf("Failed to initialize volume manager: %v", err) } stopCh := make(chan struct{}) go manager.Run(stopCh) defer close(stopCh) podManager.SetPods([]*api.Pod{pod}) // Fake node status update go simulateVolumeInUseUpdate( api.UniqueVolumeName(node.Status.VolumesAttached[0].Name), stopCh, manager) err = manager.WaitForAttachAndMount(pod) if err != nil { t.Errorf("Expected success: %v", err) } expectedMounted := pod.Spec.Volumes[0].Name actualMounted := manager.GetMountedVolumesForPod(types.UniquePodName(pod.ObjectMeta.UID)) if _, ok := actualMounted[expectedMounted]; !ok || (len(actualMounted) != 1) { t.Errorf("Expected %v to be mounted to pod but got %v", expectedMounted, actualMounted) } expectedInUse := []api.UniqueVolumeName{api.UniqueVolumeName(node.Status.VolumesAttached[0].Name)} actualInUse := manager.GetVolumesInUse() if !reflect.DeepEqual(expectedInUse, actualInUse) { t.Errorf("Expected %v to be in use but got %v", expectedInUse, actualInUse) } }
func (oe *operationExecutor) MountVolume( waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) error { mountFunc, err := oe.generateMountVolumeFunc( waitForAttachTimeout, volumeToMount, actualStateOfWorld) if err != nil { return err } podName := volumetypes.UniquePodName("") if !volumeToMount.PluginIsAttachable { // Non-attachable volume plugins can execute mount for multiple pods // referencing the same volume in parallel podName = volumehelper.GetUniquePodName(volumeToMount.Pod) } return oe.pendingOperations.Run( volumeToMount.VolumeName, podName, mountFunc) }
// Populates data struct with a single node no volume. // Calls AddPod() with the same node and new pod/volume. // Verifies node/volume exists, and 1 volumes to attach. func Test_AddPod_Positive_NewPodNodeExistsVolumeDoesntExist(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) podName := types.UniquePodName("pod-uid") volumeName := api.UniqueVolumeName("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) } // Act generatedVolumeName, podErr := dsw.AddPod(podName, volumeSpec, nodeName) // Assert if podErr != nil { t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podErr) } volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName) if !volumeExists { t.Fatalf( "Added pod %q to volume %q/node %q. Volume does not exist, it should.", podName, generatedVolumeName, nodeName) } volumesToAttach := dsw.GetVolumesToAttach() if len(volumesToAttach) != 1 { t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach)) } verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName, string(volumeName)) }
// 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 := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) fakeKubeClient := controllervolumetesting.CreateTestClient() fakeRecorder := &record.FakeRecorder{} ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(fakeKubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) nsu := statusupdater.NewFakeNodeStatusUpdater(false /* returnError */) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, syncLoopPeriod, false, dsw, asw, ad, nsu) podName := "pod-uid" volumeName := v1.UniqueVolumeName("volume-name") volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName) nodeName := k8stypes.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(types.UniquePodName(podName), controllervolumetesting.NewPod(podName, podName), volumeSpec, nodeName) if podErr != nil { t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podErr) } // Act ch := make(chan struct{}) go reconciler.Run(ch) defer close(ch) // Assert waitForNewAttacherCallCount(t, 1 /* expectedCallCount */, fakePlugin) waitForAttachCallCount(t, 1 /* expectedAttachCallCount */, fakePlugin) verifyNewDetacherCallCount(t, true /* expectZeroNewDetacherCallCount */, fakePlugin) }
// Populates data struct with new pod/volume/node. // Calls DeleteNode() to delete the node. // Verifies call fails because node still contains child volumes. func Test_DeleteNode_Negative_NodeExistsHasChildVolumes(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) nodeName := k8stypes.NodeName("node-name") dsw.AddNode(nodeName) podName := "pod-uid" volumeName := v1.UniqueVolumeName("volume-name") volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName) generatedVolumeName, podAddErr := dsw.AddPod(types.UniquePodName(podName), controllervolumetesting.NewPod(podName, podName), volumeSpec, nodeName) if podAddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", podName, podAddErr) } // Act err := dsw.DeleteNode(nodeName) // Assert if err == nil { t.Fatalf("DeleteNode did not fail. Expected: <\"failed to delete node...the node still contains volumes in its list of volumes to attach\"> Actual: <no error>") } nodeExists := dsw.NodeExists(nodeName) if !nodeExists { t.Fatalf("Node %q no longer exists, it should.", nodeName) } volumesToAttach := dsw.GetVolumesToAttach() if len(volumesToAttach) != 1 { t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach)) } verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName, string(volumeName)) }
// Calls AddPod() with new pod/volume/node on empty data struct. // Verifies call fails because node does not exist. func Test_AddPod_Negative_NewPodNodeDoesntExistVolumeDoesntExist(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) podName := "pod-uid" volumeName := v1.UniqueVolumeName("volume-name") volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName) nodeName := k8stypes.NodeName("node-name") volumeExists := dsw.VolumeExists(volumeName, nodeName) if volumeExists { t.Fatalf( "Volume %q/node %q should not exist, but it does.", volumeName, nodeName) } // Act _, podErr := dsw.AddPod(types.UniquePodName(podName), controllervolumetesting.NewPod(podName, podName), volumeSpec, nodeName) // Assert if podErr == nil { t.Fatalf("AddPod did not fail. Expected: <\"failed to add pod...no node with that name exists in the list of managed nodes\"> Actual: <no error>") } volumeExists = dsw.VolumeExists(volumeName, nodeName) if volumeExists { t.Fatalf( "Volume %q/node %q should not exist, but it does.", volumeName, nodeName) } volumesToAttach := dsw.GetVolumesToAttach() if len(volumesToAttach) != 0 { t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach)) } }
func Test_NewGoRoutineMap_Negative_SecondSubOpBeforeFirstCompletes(t *testing.T) { // Arrange grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */) volumeName := v1.UniqueVolumeName("volume-name") operationPodName := types.UniquePodName("operation-podname") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateWaitFunc(operation1DoneCh) err1 := grm.Run(volumeName, operationPodName, operation1) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1) } operation2 := generateNoopFunc() // Act err2 := grm.Run(volumeName, operationPodName, operation2) // Assert if err2 == nil { t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", volumeName) } if !IsAlreadyExists(err2) { t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2) } }
package nestedpendingoperations import ( "fmt" "sync" "github.com/golang/glog" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff" k8sRuntime "k8s.io/kubernetes/pkg/util/runtime" "k8s.io/kubernetes/pkg/volume/util/types" ) const ( // emptyUniquePodName is a UniquePodName for empty string. emptyUniquePodName types.UniquePodName = types.UniquePodName("") // emptyUniqueVolumeName is a UniqueVolumeName for empty string emptyUniqueVolumeName v1.UniqueVolumeName = v1.UniqueVolumeName("") ) // NestedPendingOperations defines the supported set of operations. type NestedPendingOperations interface { // Run adds the concatenation of volumeName and podName to the list of // running operations and spawns a new go routine to execute operationFunc. // If an operation with the same volumeName and same or empty podName // exists, an AlreadyExists or ExponentialBackoff error is returned. // This enables multiple operations to execute in parallel for the same // volumeName as long as they have different podName. // Once the operation is complete, the go routine is terminated and the // concatenation of volumeName and podName is removed from the list of
// GetUniquePodName returns a unique identifier to reference a pod by func GetUniquePodName(pod *api.Pod) types.UniquePodName { return types.UniquePodName(pod.UID) }
// Populates data struct with two nodes with one volume/pod on one node and two // volume/pod pairs on the other node. // Calls GetVolumesToAttach() // Verifies three volumes to attach. func Test_GetVolumesToAttach_Positive_TwoNodesThreeVolumes(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) node1Name := k8stypes.NodeName("node1-name") pod1Name := "pod1-uid" volume1Name := v1.UniqueVolumeName("volume1-name") volume1Spec := controllervolumetesting.GetTestVolumeSpec(string(volume1Name), volume1Name) dsw.AddNode(node1Name) generatedVolume1Name, podAddErr := dsw.AddPod(types.UniquePodName(pod1Name), controllervolumetesting.NewPod(pod1Name, pod1Name), volume1Spec, node1Name) if podAddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", pod1Name, podAddErr) } node2Name := k8stypes.NodeName("node2-name") pod2aName := "pod2a-name" volume2Name := v1.UniqueVolumeName("volume2-name") volume2Spec := controllervolumetesting.GetTestVolumeSpec(string(volume2Name), volume2Name) dsw.AddNode(node2Name) generatedVolume2Name1, podAddErr := dsw.AddPod(types.UniquePodName(pod2aName), controllervolumetesting.NewPod(pod2aName, pod2aName), volume2Spec, node2Name) if podAddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", pod2aName, podAddErr) } pod2bName := "pod2b-name" generatedVolume2Name2, podAddErr := dsw.AddPod(types.UniquePodName(pod2bName), controllervolumetesting.NewPod(pod2bName, pod2bName), volume2Spec, node2Name) if podAddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", pod2bName, podAddErr) } if generatedVolume2Name1 != generatedVolume2Name2 { t.Fatalf( "Generated volume names for the same volume should be the same but they are not: %q and %q", generatedVolume2Name1, generatedVolume2Name2) } pod3Name := "pod3-uid" volume3Name := v1.UniqueVolumeName("volume3-name") volume3Spec := controllervolumetesting.GetTestVolumeSpec(string(volume3Name), volume3Name) generatedVolume3Name, podAddErr := dsw.AddPod(types.UniquePodName(pod3Name), controllervolumetesting.NewPod(pod3Name, pod3Name), volume3Spec, node1Name) if podAddErr != nil { t.Fatalf( "AddPod failed for pod %q. Expected: <no error> Actual: <%v>", pod3Name, podAddErr) } // Act volumesToAttach := dsw.GetVolumesToAttach() // Assert if len(volumesToAttach) != 3 { t.Fatalf("len(volumesToAttach) Expected: <3> Actual: <%v>", len(volumesToAttach)) } verifyVolumeToAttach(t, volumesToAttach, node1Name, generatedVolume1Name, string(volume1Name)) verifyVolumeToAttach(t, volumesToAttach, node2Name, generatedVolume2Name1, string(volume2Name)) verifyVolumeToAttach(t, volumesToAttach, node1Name, generatedVolume3Name, string(volume3Name)) }