// 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) } }
// Populates desiredStateOfWorld cache with one volume/pod. // Enables controllerAttachDetachEnabled. // Calls Run() // Verifies there is one mount call and no unmount calls. // Verifies there are no attach/detach calls. func Test_Run_Positive_VolumeMountControllerAttachEnabled(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, true, /* 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) _, 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) waitForAttach(t, fakePlugin, asw) // Assert assert.NoError(t, volumetesting.VerifyZeroAttachCalls(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)) }
// 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>") } verifyVolumeDoesntExistInAttachedVolumes(t, volumeName, asw) verifyPodDoesntExistInVolumeAsw( t, podName, volumeName, false, /* expectVolumeToExist */ asw) }
func TestListVolumesForPod(t *testing.T) { testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) kubelet := testKubelet.kubelet pod := podWithUidNameNsSpec("12345678", "foo", "test", v1.PodSpec{ Volumes: []v1.Volume{ { Name: "vol1", VolumeSource: v1.VolumeSource{ GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ PDName: "fake-device1", }, }, }, { Name: "vol2", VolumeSource: v1.VolumeSource{ GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ PDName: "fake-device2", }, }, }, }, }) stopCh := runVolumeManager(kubelet) defer func() { close(stopCh) }() kubelet.podManager.SetPods([]*v1.Pod{pod}) err := kubelet.volumeManager.WaitForAttachAndMount(pod) assert.NoError(t, err) podName := volumehelper.GetUniquePodName(pod) volumesToReturn, volumeExsit := kubelet.ListVolumesForPod(types.UID(podName)) if !volumeExsit { t.Errorf("Expected to find volumes for pod %q, but ListVolumesForPod find no volume", podName) } outerVolumeSpecName1 := "vol1" if volumesToReturn[outerVolumeSpecName1] == nil { t.Errorf("Value of map volumesToReturn is not expected to be nil, which key is : %s", outerVolumeSpecName1) } outerVolumeSpecName2 := "vol2" if volumesToReturn[outerVolumeSpecName2] == nil { t.Errorf("Value of map volumesToReturn is not expected to be nil, which key is : %s", outerVolumeSpecName2) } }
// GenerateRunContainerOptions generates the RunContainerOptions, which can be used by // the container runtime to set parameters for launching a container. func (kl *Kubelet) GenerateRunContainerOptions(pod *api.Pod, container *api.Container, podIP string) (*kubecontainer.RunContainerOptions, error) { var err error opts := &kubecontainer.RunContainerOptions{CgroupParent: kl.cgroupRoot} hostname, hostDomainName, err := kl.GeneratePodHostNameAndDomain(pod) if err != nil { return nil, err } opts.Hostname = hostname podName := volumehelper.GetUniquePodName(pod) volumes := kl.volumeManager.GetMountedVolumesForPod(podName) opts.PortMappings = makePortMappings(container) // Docker does not relabel volumes if the container is running // in the host pid or ipc namespaces so the kubelet must // relabel the volumes if pod.Spec.SecurityContext != nil && (pod.Spec.SecurityContext.HostIPC || pod.Spec.SecurityContext.HostPID) { err = kl.relabelVolumes(pod, volumes) if err != nil { return nil, err } } opts.Mounts, err = makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIP, volumes) if err != nil { return nil, err } opts.Envs, err = kl.makeEnvironmentVariables(pod, container, podIP) if err != nil { return nil, err } if len(container.TerminationMessagePath) != 0 { p := kl.getPodContainerDir(pod.UID, container.Name) if err := os.MkdirAll(p, 0750); err != nil { glog.Errorf("Error on creating %q: %v", p, err) } else { opts.PodContainerDir = p } } opts.DNS, opts.DNSSearch, err = kl.GetClusterDNS(pod) if err != nil { return nil, err } return opts, nil }
// processPodVolumes processes the volumes in the given pod and adds them to the // desired state of the world. func (dswp *desiredStateOfWorldPopulator) processPodVolumes(pod *api.Pod) { if pod == nil { return } uniquePodName := volumehelper.GetUniquePodName(pod) if dswp.podPreviouslyProcessed(uniquePodName) { return } // Process volume spec for each volume defined in pod for _, podVolume := range pod.Spec.Volumes { volumeSpec, volumeGidValue, err := dswp.createVolumeSpec(podVolume, pod.Namespace) if err != nil { glog.Errorf( "Error processing volume %q for pod %q/%q: %v", podVolume.Name, pod.Namespace, pod.Name, err) continue } // Add volume to desired state of world _, err = dswp.desiredStateOfWorld.AddPodToVolume( uniquePodName, pod, volumeSpec, podVolume.Name, volumeGidValue) if err != nil { glog.Errorf( "Failed to add volume %q (specName: %q) for pod %q to desiredStateOfWorld. err=%v", podVolume.Name, volumeSpec.Name(), uniquePodName, err) } glog.V(10).Infof( "Added volume %q (volSpec=%q) for pod %q to desired state.", podVolume.Name, volumeSpec.Name(), uniquePodName) } dswp.markPodProcessed(uniquePodName) }
func TestVolumeAttachAndMountControllerDisabled(t *testing.T) { testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) kubelet := testKubelet.kubelet pod := podWithUidNameNsSpec("12345678", "foo", "test", api.PodSpec{ Volumes: []api.Volume{ { Name: "vol1", VolumeSource: api.VolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{ PDName: "fake-device", }, }, }, }, }) stopCh := runVolumeManager(kubelet) defer func() { close(stopCh) }() kubelet.podManager.SetPods([]*api.Pod{pod}) err := kubelet.volumeManager.WaitForAttachAndMount(pod) assert.NoError(t, err) podVolumes := kubelet.volumeManager.GetMountedVolumesForPod( volumehelper.GetUniquePodName(pod)) expectedPodVolumes := []string{"vol1"} assert.Len(t, podVolumes, len(expectedPodVolumes), "Volumes for pod %+v", pod) for _, name := range expectedPodVolumes { assert.Contains(t, podVolumes, name, "Volumes for pod %+v", pod) } assert.True(t, testKubelet.volumePlugin.GetNewAttacherCallCount() >= 1, "Expected plugin NewAttacher to be called at least once") assert.NoError(t, volumetest.VerifyWaitForAttachCallCount( 1 /* expectedWaitForAttachCallCount */, testKubelet.volumePlugin)) assert.NoError(t, volumetest.VerifyAttachCallCount( 1 /* expectedAttachCallCount */, testKubelet.volumePlugin)) assert.NoError(t, volumetest.VerifyMountDeviceCallCount( 1 /* expectedMountDeviceCallCount */, testKubelet.volumePlugin)) assert.NoError(t, volumetest.VerifySetUpCallCount( 1 /* expectedSetUpCallCount */, testKubelet.volumePlugin)) }
// Populates data struct with a new volume/pod // Calls DeletePodFromVolume() to removes the pod // Verifies newly added pod/volume are deleted func Test_DeletePodFromVolume_Positive_PodExistsVolumeExists(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) pod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod3", UID: "pod3uid", }, Spec: v1.PodSpec{ Volumes: []v1.Volume{ { Name: "volume-name", VolumeSource: v1.VolumeSource{ GCEPersistentDisk: &v1.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 */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) } verifyVolumeExistsDsw(t, generatedVolumeName, dsw) verifyVolumeExistsInVolumesToMount( t, generatedVolumeName, false /* expectReportedInUse */, dsw) verifyPodExistsInVolumeDsw(t, podName, generatedVolumeName, dsw) // Act dsw.DeletePodFromVolume(podName, generatedVolumeName) // Assert verifyVolumeDoesntExist(t, generatedVolumeName, dsw) verifyVolumeDoesntExistInVolumesToMount(t, generatedVolumeName, dsw) verifyPodDoesntExistInVolumeDsw(t, podName, generatedVolumeName, dsw) }
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) }
func (vm *volumeManager) GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int64 { podName := volumehelper.GetUniquePodName(pod) supplementalGroups := sets.NewString() for _, mountedVolume := range vm.actualStateOfWorld.GetMountedVolumesForPod(podName) { if mountedVolume.VolumeGidValue != "" { supplementalGroups.Insert(mountedVolume.VolumeGidValue) } } result := make([]int64, 0, supplementalGroups.Len()) for _, group := range supplementalGroups.List() { iGroup, extra := getExtraSupplementalGid(group, pod) if !extra { continue } result = append(result, int64(iGroup)) } return result }
func (vm *volumeManager) WaitForAttachAndMount(pod *v1.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 unmountedVolumes := vm.getUnmountedVolumes(uniquePodName, expectedVolumes) if len(unmountedVolumes) == 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.Namespace, pod.Name, unmountedVolumes) } glog.V(3).Infof("All volumes are attached and mounted for pod %q", format.Pod(pod)) return nil }
// Calls AddPodToVolume() twice to add the same pod to the same volume // Verifies newly added pod/volume exists via // PodExistsInVolume() VolumeExists() and GetVolumesToMount() and no errors. func Test_AddPodToVolume_Positive_ExistingPodExistingVolume(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "pod3", UID: "pod3uid", }, 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) // Act generatedVolumeName, err := dsw.AddPodToVolume( podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) // Assert if err != nil { t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) } verifyVolumeExists(t, generatedVolumeName, dsw) verifyVolumeExistsInVolumesToMount(t, generatedVolumeName, dsw) verifyPodExistsInVolumeDsw(t, podName, generatedVolumeName, dsw) }
// 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 }
// GenerateRunContainerOptions generates the RunContainerOptions, which can be used by // the container runtime to set parameters for launching a container. func (kl *Kubelet) GenerateRunContainerOptions(pod *api.Pod, container *api.Container, podIP string) (*kubecontainer.RunContainerOptions, error) { var err error opts := &kubecontainer.RunContainerOptions{CgroupParent: kl.cgroupRoot} hostname, hostDomainName, err := kl.GeneratePodHostNameAndDomain(pod) if err != nil { return nil, err } opts.Hostname = hostname podName := volumehelper.GetUniquePodName(pod) volumes := kl.volumeManager.GetMountedVolumesForPod(podName) opts.PortMappings = makePortMappings(container) opts.Mounts, err = makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIP, volumes) if err != nil { return nil, err } opts.Envs, err = kl.makeEnvironmentVariables(pod, container, podIP) if err != nil { return nil, err } if len(container.TerminationMessagePath) != 0 { p := kl.getPodContainerDir(pod.UID, container.Name) if err := os.MkdirAll(p, 0750); err != nil { glog.Errorf("Error on creating %q: %v", p, err) } else { opts.PodContainerDir = p } } opts.DNS, opts.DNSSearch, err = kl.GetClusterDNS(pod) if err != nil { return nil, err } return opts, nil }
// Calls AddPodToVolume() to add three new volumes to data struct // Verifies newly added pod/volume exists via PodExistsInVolume() // VolumeExists() and GetVolumesToMount() // Marks only second volume as reported in use. // Verifies only that volume is marked reported in use // Marks only first volume as reported in use. // Verifies only that volume is marked reported in use func Test_MarkVolumesReportedInUse_Positive_NewPodNewVolume(t *testing.T) { // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := NewDesiredStateOfWorld(volumePluginMgr) pod1 := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod1", UID: "pod1uid", }, Spec: v1.PodSpec{ Volumes: []v1.Volume{ { Name: "volume1-name", VolumeSource: v1.VolumeSource{ GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ PDName: "fake-device1", }, }, }, }, }, } volume1Spec := &volume.Spec{Volume: &pod1.Spec.Volumes[0]} pod1Name := volumehelper.GetUniquePodName(pod1) pod2 := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod2", UID: "pod2uid", }, Spec: v1.PodSpec{ Volumes: []v1.Volume{ { Name: "volume2-name", VolumeSource: v1.VolumeSource{ GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ PDName: "fake-device2", }, }, }, }, }, } volume2Spec := &volume.Spec{Volume: &pod2.Spec.Volumes[0]} pod2Name := volumehelper.GetUniquePodName(pod2) pod3 := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod3", UID: "pod3uid", }, Spec: v1.PodSpec{ Volumes: []v1.Volume{ { Name: "volume3-name", VolumeSource: v1.VolumeSource{ GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ PDName: "fake-device3", }, }, }, }, }, } volume3Spec := &volume.Spec{Volume: &pod3.Spec.Volumes[0]} pod3Name := volumehelper.GetUniquePodName(pod3) generatedVolume1Name, err := dsw.AddPodToVolume( pod1Name, pod1, volume1Spec, volume1Spec.Name(), "" /* volumeGidValue */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) } generatedVolume2Name, err := dsw.AddPodToVolume( pod2Name, pod2, volume2Spec, volume2Spec.Name(), "" /* volumeGidValue */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) } generatedVolume3Name, err := dsw.AddPodToVolume( pod3Name, pod3, volume3Spec, volume3Spec.Name(), "" /* volumeGidValue */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) } // Act volumesReportedInUse := []v1.UniqueVolumeName{generatedVolume2Name} dsw.MarkVolumesReportedInUse(volumesReportedInUse) // Assert verifyVolumeExistsDsw(t, generatedVolume1Name, dsw) verifyVolumeExistsInVolumesToMount( t, generatedVolume1Name, false /* expectReportedInUse */, dsw) verifyPodExistsInVolumeDsw(t, pod1Name, generatedVolume1Name, dsw) verifyVolumeExistsDsw(t, generatedVolume2Name, dsw) verifyVolumeExistsInVolumesToMount( t, generatedVolume2Name, true /* expectReportedInUse */, dsw) verifyPodExistsInVolumeDsw(t, pod2Name, generatedVolume2Name, dsw) verifyVolumeExistsDsw(t, generatedVolume3Name, dsw) verifyVolumeExistsInVolumesToMount( t, generatedVolume3Name, false /* expectReportedInUse */, dsw) verifyPodExistsInVolumeDsw(t, pod3Name, generatedVolume3Name, dsw) // Act volumesReportedInUse = []v1.UniqueVolumeName{generatedVolume3Name} dsw.MarkVolumesReportedInUse(volumesReportedInUse) // Assert verifyVolumeExistsDsw(t, generatedVolume1Name, dsw) verifyVolumeExistsInVolumesToMount( t, generatedVolume1Name, false /* expectReportedInUse */, dsw) verifyPodExistsInVolumeDsw(t, pod1Name, generatedVolume1Name, dsw) verifyVolumeExistsDsw(t, generatedVolume2Name, dsw) verifyVolumeExistsInVolumesToMount( t, generatedVolume2Name, false /* expectReportedInUse */, dsw) verifyPodExistsInVolumeDsw(t, pod2Name, generatedVolume2Name, dsw) verifyVolumeExistsDsw(t, generatedVolume3Name, dsw) verifyVolumeExistsInVolumesToMount( t, generatedVolume3Name, true /* expectReportedInUse */, dsw) verifyPodExistsInVolumeDsw(t, pod3Name, generatedVolume3Name, dsw) }
func TestVolumeUnmountAndDetachControllerDisabled(t *testing.T) { testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) kubelet := testKubelet.kubelet pod := podWithUidNameNsSpec("12345678", "foo", "test", api.PodSpec{ Volumes: []api.Volume{ { Name: "vol1", VolumeSource: api.VolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{ PDName: "fake-device", }, }, }, }, }) stopCh := runVolumeManager(kubelet) defer func() { close(stopCh) }() // Add pod kubelet.podManager.SetPods([]*api.Pod{pod}) // Verify volumes attached err := kubelet.volumeManager.WaitForAttachAndMount(pod) assert.NoError(t, err) podVolumes := kubelet.volumeManager.GetMountedVolumesForPod( volumehelper.GetUniquePodName(pod)) expectedPodVolumes := []string{"vol1"} assert.Len(t, podVolumes, len(expectedPodVolumes), "Volumes for pod %+v", pod) for _, name := range expectedPodVolumes { assert.Contains(t, podVolumes, name, "Volumes for pod %+v", pod) } assert.True(t, testKubelet.volumePlugin.GetNewAttacherCallCount() >= 1, "Expected plugin NewAttacher to be called at least once") assert.NoError(t, volumetest.VerifyWaitForAttachCallCount( 1 /* expectedWaitForAttachCallCount */, testKubelet.volumePlugin)) assert.NoError(t, volumetest.VerifyAttachCallCount( 1 /* expectedAttachCallCount */, testKubelet.volumePlugin)) assert.NoError(t, volumetest.VerifyMountDeviceCallCount( 1 /* expectedMountDeviceCallCount */, testKubelet.volumePlugin)) assert.NoError(t, volumetest.VerifySetUpCallCount( 1 /* expectedSetUpCallCount */, testKubelet.volumePlugin)) // Remove pod kubelet.podManager.SetPods([]*api.Pod{}) assert.NoError(t, waitForVolumeUnmount(kubelet.volumeManager, pod)) // Verify volumes unmounted podVolumes = kubelet.volumeManager.GetMountedVolumesForPod( volumehelper.GetUniquePodName(pod)) assert.Len(t, podVolumes, 0, "Expected volumes to be unmounted and detached. But some volumes are still mounted: %#v", podVolumes) assert.NoError(t, volumetest.VerifyTearDownCallCount( 1 /* expectedTearDownCallCount */, testKubelet.volumePlugin)) // Verify volumes detached and no longer reported as in use assert.NoError(t, waitForVolumeDetach(api.UniqueVolumeName("fake/vol1"), kubelet.volumeManager)) assert.True(t, testKubelet.volumePlugin.GetNewAttacherCallCount() >= 1, "Expected plugin NewAttacher to be called at least once") assert.NoError(t, volumetest.VerifyDetachCallCount( 1 /* expectedDetachCallCount */, testKubelet.volumePlugin)) }
// Populates desiredStateOfWorld cache with one volume/pod. // Calls Run() // Verifies there is one attach/mount/etc call and no detach calls. // Deletes volume/pod from desired state of world. // Verifies detach/unmount calls are issued. func Test_Run_Positive_VolumeAttachMountUnmountDetach(t *testing.T) { // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) kubeClient := createTestClient() fakeRecorder := &record.FakeRecorder{} oex := operationexecutor.NewOperationExecutor(kubeClient, volumePluginMgr, fakeRecorder) reconciler := NewReconciler( kubeClient, false, /* controllerAttachDetachEnabled */ reconcilerLoopSleepDuration, reconcilerReconstructSleepPeriod, waitForAttachTimeout, nodeName, dsw, asw, oex, &mount.FakeMounter{}, volumePluginMgr, kubeletPodsDir) 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 runReconciler(reconciler) 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)) // Act dsw.DeletePodFromVolume(podName, generatedVolumeName) waitForDetach(t, fakePlugin, generatedVolumeName, asw) // Assert assert.NoError(t, volumetesting.VerifyTearDownCallCount( 1 /* expectedTearDownCallCount */, fakePlugin)) assert.NoError(t, volumetesting.VerifyDetachCallCount( 1 /* expectedDetachCallCount */, fakePlugin)) }
// 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 }
func TestVolumeUnmountAndDetachControllerEnabled(t *testing.T) { testKubelet := newTestKubelet(t, true /* controllerAttachDetachEnabled */) kubelet := testKubelet.kubelet kubeClient := testKubelet.fakeKubeClient kubeClient.AddReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) { return true, &api.Node{ ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}, Status: api.NodeStatus{ VolumesAttached: []api.AttachedVolume{ { Name: "fake/vol1", DevicePath: "fake/path", }, }}, Spec: api.NodeSpec{ExternalID: testKubeletHostname}, }, nil }) kubeClient.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) { return true, nil, fmt.Errorf("no reaction implemented for %s", action) }) pod := podWithUidNameNsSpec("12345678", "foo", "test", api.PodSpec{ Volumes: []api.Volume{ { Name: "vol1", VolumeSource: api.VolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{ PDName: "fake-device", }, }, }, }, }) stopCh := runVolumeManager(kubelet) defer func() { close(stopCh) }() // Add pod kubelet.podManager.SetPods([]*api.Pod{pod}) // Fake node status update go simulateVolumeInUseUpdate( api.UniqueVolumeName("fake/vol1"), stopCh, kubelet.volumeManager) // Verify volumes attached assert.NoError(t, kubelet.volumeManager.WaitForAttachAndMount(pod)) podVolumes := kubelet.volumeManager.GetMountedVolumesForPod( volumehelper.GetUniquePodName(pod)) expectedPodVolumes := []string{"vol1"} assert.Len(t, podVolumes, len(expectedPodVolumes), "Volumes for pod %+v", pod) for _, name := range expectedPodVolumes { assert.Contains(t, podVolumes, name, "Volumes for pod %+v", pod) } assert.True(t, testKubelet.volumePlugin.GetNewAttacherCallCount() >= 1, "Expected plugin NewAttacher to be called at least once") assert.NoError(t, volumetest.VerifyWaitForAttachCallCount( 1 /* expectedWaitForAttachCallCount */, testKubelet.volumePlugin)) assert.NoError(t, volumetest.VerifyZeroAttachCalls(testKubelet.volumePlugin)) assert.NoError(t, volumetest.VerifyMountDeviceCallCount( 1 /* expectedMountDeviceCallCount */, testKubelet.volumePlugin)) assert.NoError(t, volumetest.VerifySetUpCallCount( 1 /* expectedSetUpCallCount */, testKubelet.volumePlugin)) // Remove pod kubelet.podManager.SetPods([]*api.Pod{}) assert.NoError(t, waitForVolumeUnmount(kubelet.volumeManager, pod)) // Verify volumes unmounted podVolumes = kubelet.volumeManager.GetMountedVolumesForPod( volumehelper.GetUniquePodName(pod)) assert.Len(t, podVolumes, 0, "Expected volumes to be unmounted and detached. But some volumes are still mounted: %#v", podVolumes) assert.NoError(t, volumetest.VerifyTearDownCallCount( 1 /* expectedTearDownCallCount */, testKubelet.volumePlugin)) // Verify volumes detached and no longer reported as in use assert.NoError(t, waitForVolumeDetach(api.UniqueVolumeName("fake/vol1"), kubelet.volumeManager)) assert.True(t, testKubelet.volumePlugin.GetNewAttacherCallCount() >= 1, "Expected plugin NewAttacher to be called at least once") assert.NoError(t, volumetest.VerifyZeroDetachCallCount(testKubelet.volumePlugin)) }