// Populates data struct with a volume // Calls AddPodToVolume() twice to add the same pod to the volume // Verifies volume/pod combo exist using PodExistsInVolume() and the second call // did not fail. func Test_AddPodToVolume_Positive_ExistingVolumeExistingNode(t *testing.T) { // Arrange volumePluginMgr, plugin := volumetesting.GetTestVolumePluginMgr(t) asw := NewActualStateOfWorld("mynode" /* nodeName */, volumePluginMgr) devicePath := "fake/device/path" pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "pod1", UID: "pod1uid", }, Spec: api.PodSpec{ Volumes: []api.Volume{ { Name: "volume-name", VolumeSource: api.VolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{ PDName: "fake-device1", }, }, }, }, }, } volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} volumeName, err := volumehelper.GetUniqueVolumeNameFromSpec( plugin, volumeSpec) generatedVolumeName, err := asw.AddVolume(volumeSpec, devicePath) if err != nil { t.Fatalf("AddVolume failed. Expected: <no error> Actual: <%v>", err) } podName := volumehelper.GetUniquePodName(pod) mounter, err := plugin.NewMounter(volumeSpec, pod, volume.VolumeOptions{}) if err != nil { t.Fatalf("NewUnmounter failed. Expected: <no error> Actual: <%v>", err) } err = asw.AddPodToVolume( podName, pod.UID, volumeName, mounter, volumeSpec.Name(), "" /* volumeGidValue */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) } // Act err = asw.AddPodToVolume( podName, pod.UID, volumeName, mounter, volumeSpec.Name(), "" /* volumeGidValue */) // Assert if err != nil { t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) } verifyVolumeExistsAsw(t, generatedVolumeName, true /* shouldExist */, asw) verifyVolumeDoesntExistInUnmountedVolumes(t, generatedVolumeName, asw) verifyVolumeDoesntExistInGloballyMountedVolumes(t, generatedVolumeName, asw) verifyPodExistsInVolumeAsw(t, podName, generatedVolumeName, "fake/device/path" /* expectedDevicePath */, asw) }
// Iterate through all pods in desired state of world, and remove if they no // longer exist in the informer func (dswp *desiredStateOfWorldPopulator) findAndRemoveDeletedPods() { for dswPodUID, dswPodToAdd := range dswp.desiredStateOfWorld.GetPodToAdd() { dswPodKey, err := kcache.MetaNamespaceKeyFunc(dswPodToAdd.Pod) if err != nil { glog.Errorf("MetaNamespaceKeyFunc failed for pod %q (UID %q) with: %v", dswPodKey, dswPodUID, err) continue } // retrieve the pod object from pod informer with the namespace key informerPodObj, exists, err := dswp.podInformer.GetStore().GetByKey(dswPodKey) if err != nil || informerPodObj == nil { glog.Errorf("podInformer GetByKey failed for pod %q (UID %q) with %v", dswPodKey, dswPodUID, err) continue } if exists { informerPod, ok := informerPodObj.(*api.Pod) if !ok { glog.Errorf("Failed to cast obj %#v to pod object for pod %q (UID %q)", informerPod, dswPodKey, dswPodUID) continue } informerPodUID := volumehelper.GetUniquePodName(informerPod) // Check whether the unique idenfier of the pod from dsw matches the one retrived from pod informer if informerPodUID == dswPodUID { glog.V(10).Infof( "Verified pod %q (UID %q) from dsw exists in pod informer.", dswPodKey, dswPodUID) continue } } // the pod from dsw does not exist in pod informer, or it does not match the unique idenfier retrieved // from the informer, delete it from dsw glog.V(1).Infof( "Removing pod %q (UID %q) from dsw because it does not exist in pod informer.", dswPodKey, dswPodUID) dswp.desiredStateOfWorld.DeletePod(dswPodUID, dswPodToAdd.VolumeName, dswPodToAdd.NodeName) } }
func (vm *volumeManager) WaitForAttachAndMount(pod *api.Pod) error { expectedVolumes := getExpectedVolumes(pod) if len(expectedVolumes) == 0 { // No volumes to verify return nil } glog.V(3).Infof("Waiting for volumes to attach and mount for pod %q", format.Pod(pod)) uniquePodName := volumehelper.GetUniquePodName(pod) // Some pods expect to have Setup called over and over again to update. // Remount plugins for which this is true. (Atomically updating volumes, // like Downward API, depend on this to update the contents of the volume). vm.desiredStateOfWorldPopulator.ReprocessPod(uniquePodName) vm.actualStateOfWorld.MarkRemountRequired(uniquePodName) err := wait.Poll( podAttachAndMountRetryInterval, podAttachAndMountTimeout, vm.verifyVolumesMountedFunc(uniquePodName, expectedVolumes)) if err != nil { // Timeout expired ummountedVolumes := vm.getUnmountedVolumes(uniquePodName, expectedVolumes) if len(ummountedVolumes) == 0 { return nil } return fmt.Errorf( "timeout expired waiting for volumes to attach/mount for pod %q/%q. list of unattached/unmounted volumes=%v", pod.Name, pod.Namespace, ummountedVolumes) } glog.V(3).Infof("All volumes are attached and mounted for pod %q", format.Pod(pod)) return nil }
// getVolumesForPodHelper is a helper method implements the common logic for // the GetVolumesForPod methods. // XXX: https://github.com/kubernetes/kubernetes/issues/27197 mutating the pod // object is bad, and should be avoided. func (vm *volumeManager) getVolumesForPodHelper( podName types.UniquePodName, pod *api.Pod) container.VolumeMap { if pod != nil { podName = volumehelper.GetUniquePodName(pod) } podVolumes := make(container.VolumeMap) for _, mountedVolume := range vm.actualStateOfWorld.GetMountedVolumesForPod(podName) { podVolumes[mountedVolume.OuterVolumeSpecName] = container.VolumeInfo{Mounter: mountedVolume.Mounter} if pod != nil { err := applyPersistentVolumeAnnotations( mountedVolume.VolumeGidValue, pod) if err != nil { glog.Errorf("applyPersistentVolumeAnnotations failed for pod %q volume %q with: %v", podName, mountedVolume.VolumeName, err) } } } return podVolumes }
// Calls AddPodToVolume() to add pod to empty data stuct // Verifies call fails with "volume does not exist" error. func Test_AddPodToVolume_Negative_VolumeDoesntExist(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) asw := NewActualStateOfWorld("mynode" /* nodeName */, volumePluginMgr) pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "pod1", UID: "pod1uid", }, Spec: api.PodSpec{ Volumes: []api.Volume{ { Name: "volume-name", VolumeSource: api.VolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{ PDName: "fake-device1", }, }, }, }, }, } volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} plugin, err := volumePluginMgr.FindPluginBySpec(volumeSpec) if err != nil { t.Fatalf( "volumePluginMgr.FindPluginBySpec failed to find volume plugin for %#v with: %v", volumeSpec, err) } volumeName, err := volumehelper.GetUniqueVolumeNameFromSpec( plugin, volumeSpec) podName := volumehelper.GetUniquePodName(pod) mounter, err := plugin.NewMounter(volumeSpec, pod, volume.VolumeOptions{}) if err != nil { t.Fatalf("NewUnmounter failed. Expected: <no error> Actual: <%v>", err) } // Act err = asw.AddPodToVolume( podName, pod.UID, volumeName, mounter, volumeSpec.Name(), "" /* volumeGidValue */) // Assert if err == nil { t.Fatalf("AddPodToVolume did not fail. Expected: <\"no volume with the name ... exists in the list of attached volumes\"> Actual: <no error>") } verifyVolumeExistsAsw(t, volumeName, false /* shouldExist */, asw) verifyVolumeDoesntExistInUnmountedVolumes(t, volumeName, asw) verifyVolumeDoesntExistInGloballyMountedVolumes(t, volumeName, asw) verifyPodDoesntExistInVolumeAsw( t, podName, volumeName, false, /* expectVolumeToExist */ asw) }
// processPodVolumes processes the volumes in the given pod and adds them to the // desired state of the world if addVolumes is true, otherwise it removes them. func (adc *attachDetachController) processPodVolumes( pod *api.Pod, addVolumes bool) { if pod == nil { return } if len(pod.Spec.Volumes) <= 0 { return } if !adc.desiredStateOfWorld.NodeExists(pod.Spec.NodeName) { // If the node the pod is scheduled to does not exist in the desired // state of the world data structure, that indicates the node is not // yet managed by the controller. Therefore, ignore the pod. // If the node is added to the list of managed nodes in the future, // future adds and updates to the pod will be processed. glog.V(10).Infof( "Skipping processing of pod %q/%q: it is scheduled to node %q which is not managed by the controller.", pod.Namespace, pod.Name, pod.Spec.NodeName) return } // Process volume spec for each volume defined in pod for _, podVolume := range pod.Spec.Volumes { volumeSpec, err := adc.createVolumeSpec(podVolume, pod.Namespace) if err != nil { glog.V(10).Infof( "Error processing volume %q for pod %q/%q: %v", podVolume.Name, pod.Namespace, pod.Name, err) continue } attachableVolumePlugin, err := adc.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec) if err != nil || attachableVolumePlugin == nil { glog.V(10).Infof( "Skipping volume %q for pod %q/%q: it does not implement attacher interface. err=%v", podVolume.Name, pod.Namespace, pod.Name, err) continue } uniquePodName := volumehelper.GetUniquePodName(pod) if addVolumes { // Add volume to desired state of world _, err := adc.desiredStateOfWorld.AddPod( uniquePodName, pod, volumeSpec, pod.Spec.NodeName) if err != nil { glog.V(10).Infof( "Failed to add volume %q for pod %q/%q to desiredStateOfWorld. %v", podVolume.Name, pod.Namespace, pod.Name, err) } } else { // Remove volume from desired state of world uniqueVolumeName, err := volumehelper.GetUniqueVolumeNameFromSpec( attachableVolumePlugin, volumeSpec) if err != nil { glog.V(10).Infof( "Failed to delete volume %q for pod %q/%q from desiredStateOfWorld. GetUniqueVolumeNameFromSpec failed with %v", podVolume.Name, pod.Namespace, pod.Name, err) continue } adc.desiredStateOfWorld.DeletePod( uniquePodName, uniqueVolumeName, pod.Spec.NodeName) } } return }
// Populates desiredStateOfWorld cache with one volume/pod. // Calls Run() // Verifies there is are attach/mount/etc calls and no detach/unmount calls. func Test_Run_Positive_VolumeAttachAndMount(t *testing.T) { // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) kubeClient := createTestClient() oex := operationexecutor.NewOperationExecutor(kubeClient, volumePluginMgr) reconciler := NewReconciler( kubeClient, false, /* controllerAttachDetachEnabled */ reconcilerLoopSleepDuration, waitForAttachTimeout, nodeName, dsw, asw, oex) pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "pod1", UID: "pod1uid", }, Spec: api.PodSpec{ Volumes: []api.Volume{ { Name: "volume-name", VolumeSource: api.VolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{ PDName: "fake-device1", }, }, }, }, }, } volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} podName := volumehelper.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) // Assert if err != nil { t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) } // Act go reconciler.Run(wait.NeverStop) waitForMount(t, fakePlugin, generatedVolumeName, asw) // Assert assert.NoError(t, volumetesting.VerifyAttachCallCount( 1 /* expectedAttachCallCount */, fakePlugin)) assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount( 1 /* expectedWaitForAttachCallCount */, fakePlugin)) assert.NoError(t, volumetesting.VerifyMountDeviceCallCount( 1 /* expectedMountDeviceCallCount */, fakePlugin)) assert.NoError(t, volumetesting.VerifySetUpCallCount( 1 /* expectedSetUpCallCount */, fakePlugin)) assert.NoError(t, volumetesting.VerifyZeroTearDownCallCount(fakePlugin)) assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin)) }