// 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) }
func (asw *actualStateOfWorld) AddVolumeNode( volumeSpec *volume.Spec, nodeName types.NodeName, devicePath string) (v1.UniqueVolumeName, error) { asw.Lock() defer asw.Unlock() attachableVolumePlugin, err := asw.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec) if err != nil || attachableVolumePlugin == nil { return "", fmt.Errorf( "failed to get AttachablePlugin from volumeSpec for volume %q err=%v", volumeSpec.Name(), err) } volumeName, err := volumehelper.GetUniqueVolumeNameFromSpec( attachableVolumePlugin, volumeSpec) if err != nil { return "", fmt.Errorf( "failed to GetUniqueVolumeNameFromSpec for volumeSpec %q err=%v", volumeSpec.Name(), err) } volumeObj, volumeExists := asw.attachedVolumes[volumeName] if !volumeExists { volumeObj = attachedVolume{ volumeName: volumeName, spec: volumeSpec, nodesAttachedTo: make(map[types.NodeName]nodeAttachedTo), devicePath: devicePath, } } else { // If volume object already exists, it indicates that the information would be out of date. // Update the fields for volume object except the nodes attached to the volumes. volumeObj.devicePath = devicePath volumeObj.spec = volumeSpec glog.V(2).Infof("Volume %q is already added to attachedVolume list to node %q, update device path %q", volumeName, nodeName, devicePath) } asw.attachedVolumes[volumeName] = volumeObj _, nodeExists := volumeObj.nodesAttachedTo[nodeName] if !nodeExists { // Create object if it doesn't exist. volumeObj.nodesAttachedTo[nodeName] = nodeAttachedTo{ nodeName: nodeName, mountedByNode: true, // Assume mounted, until proven otherwise mountedByNodeSetCount: 0, detachRequestedTime: time.Time{}, } } else { glog.V(5).Infof("Volume %q is already added to attachedVolume list to the node %q", volumeName, nodeName) } asw.addVolumeToReportAsAttached(volumeName, nodeName) return volumeName, nil }
// 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) }
// addVolume adds the given volume to the cache indicating the specified // volume is attached to this node. If no volume name is supplied, a unique // volume name is generated from the volumeSpec and returned on success. If a // volume with the same generated name already exists, this is a noop. If no // volume plugin can support the given volumeSpec or more than one plugin can // support it, an error is returned. func (asw *actualStateOfWorld) addVolume( volumeName api.UniqueVolumeName, volumeSpec *volume.Spec, devicePath string) error { asw.Lock() defer asw.Unlock() volumePlugin, err := asw.volumePluginMgr.FindPluginBySpec(volumeSpec) if err != nil || volumePlugin == nil { return fmt.Errorf( "failed to get Plugin from volumeSpec for volume %q err=%v", volumeSpec.Name(), err) } if len(volumeName) == 0 { volumeName, err = volumehelper.GetUniqueVolumeNameFromSpec(volumePlugin, volumeSpec) if err != nil { return fmt.Errorf( "failed to GetUniqueVolumeNameFromSpec for volumeSpec %q using volume plugin %q err=%v", volumeSpec.Name(), volumePlugin.GetPluginName(), err) } } pluginIsAttachable := false if _, ok := volumePlugin.(volume.AttachableVolumePlugin); ok { pluginIsAttachable = true } volumeObj, volumeExists := asw.attachedVolumes[volumeName] if !volumeExists { volumeObj = attachedVolume{ volumeName: volumeName, spec: volumeSpec, mountedPods: make(map[volumetypes.UniquePodName]mountedPod), pluginName: volumePlugin.GetPluginName(), pluginIsAttachable: pluginIsAttachable, globallyMounted: false, devicePath: devicePath, } } else { // If volume object already exists, update the fields such as device path volumeObj.devicePath = devicePath volumeObj.spec = volumeSpec glog.V(2).Infof("Volume %q is already added to attachedVolume list, update device path %q", volumeName, devicePath) } asw.attachedVolumes[volumeName] = volumeObj return nil }
func (dsw *desiredStateOfWorld) AddPod( podName types.UniquePodName, podToAdd *v1.Pod, volumeSpec *volume.Spec, nodeName k8stypes.NodeName) (v1.UniqueVolumeName, error) { dsw.Lock() defer dsw.Unlock() nodeObj, nodeExists := dsw.nodesManaged[nodeName] if !nodeExists { return "", fmt.Errorf( "no node with the name %q exists in the list of managed nodes", nodeName) } attachableVolumePlugin, err := dsw.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec) if err != nil || attachableVolumePlugin == nil { return "", fmt.Errorf( "failed to get AttachablePlugin from volumeSpec for volume %q err=%v", volumeSpec.Name(), err) } volumeName, err := volumehelper.GetUniqueVolumeNameFromSpec( attachableVolumePlugin, volumeSpec) if err != nil { return "", fmt.Errorf( "failed to GetUniqueVolumeNameFromSpec for volumeSpec %q err=%v", volumeSpec.Name(), err) } volumeObj, volumeExists := nodeObj.volumesToAttach[volumeName] if !volumeExists { volumeObj = volumeToAttach{ volumeName: volumeName, spec: volumeSpec, scheduledPods: make(map[types.UniquePodName]pod), } dsw.nodesManaged[nodeName].volumesToAttach[volumeName] = volumeObj } if _, podExists := volumeObj.scheduledPods[podName]; !podExists { dsw.nodesManaged[nodeName].volumesToAttach[volumeName].scheduledPods[podName] = pod{ podName: podName, podObj: podToAdd, } } return volumeName, nil }
func (dsw *desiredStateOfWorld) AddPodToVolume( podName types.UniquePodName, pod *api.Pod, volumeSpec *volume.Spec, outerVolumeSpecName string, volumeGidValue string) (api.UniqueVolumeName, error) { dsw.Lock() defer dsw.Unlock() volumePlugin, err := dsw.volumePluginMgr.FindPluginBySpec(volumeSpec) if err != nil || volumePlugin == nil { return "", fmt.Errorf( "failed to get Plugin from volumeSpec for volume %q err=%v", volumeSpec.Name(), err) } volumeName, err := volumehelper.GetUniqueVolumeNameFromSpec(volumePlugin, volumeSpec) if err != nil { return "", fmt.Errorf( "failed to GetUniqueVolumeNameFromSpec for volumeSpec %q using volume plugin %q err=%v", volumeSpec.Name(), volumePlugin.GetPluginName(), err) } volumeObj, volumeExists := dsw.volumesToMount[volumeName] if !volumeExists { volumeObj = volumeToMount{ volumeName: volumeName, podsToMount: make(map[types.UniquePodName]podToMount), pluginIsAttachable: dsw.isAttachableVolume(volumeSpec), volumeGidValue: volumeGidValue, reportedInUse: false, } dsw.volumesToMount[volumeName] = volumeObj } // Create new podToMount object. If it already exists, it is refreshed with // updated values (this is required for volumes that require remounting on // pod update, like Downward API volumes). dsw.volumesToMount[volumeName].podsToMount[podName] = podToMount{ podName: podName, pod: pod, spec: volumeSpec, outerVolumeSpecName: outerVolumeSpecName, } return volumeName, nil }
func (asw *actualStateOfWorld) AddVolumeNode( volumeSpec *volume.Spec, nodeName string, devicePath string) (api.UniqueVolumeName, error) { asw.Lock() defer asw.Unlock() attachableVolumePlugin, err := asw.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec) if err != nil || attachableVolumePlugin == nil { return "", fmt.Errorf( "failed to get AttachablePlugin from volumeSpec for volume %q err=%v", volumeSpec.Name(), err) } volumeName, err := volumehelper.GetUniqueVolumeNameFromSpec( attachableVolumePlugin, volumeSpec) if err != nil { return "", fmt.Errorf( "failed to GetUniqueVolumeNameFromSpec for volumeSpec %q err=%v", volumeSpec.Name(), err) } volumeObj, volumeExists := asw.attachedVolumes[volumeName] if !volumeExists { volumeObj = attachedVolume{ volumeName: volumeName, spec: volumeSpec, nodesAttachedTo: make(map[string]nodeAttachedTo), devicePath: devicePath, } asw.attachedVolumes[volumeName] = volumeObj } _, nodeExists := volumeObj.nodesAttachedTo[nodeName] if !nodeExists { // Create object if it doesn't exist. volumeObj.nodesAttachedTo[nodeName] = nodeAttachedTo{ nodeName: nodeName, mountedByNode: true, // Assume mounted, until proven otherwise mountedByNodeSetCount: 0, detachRequestedTime: time.Time{}, } } else { glog.V(5).Infof("Volume %q is already added to attachedVolume list to the node %q", volumeName, nodeName) } asw.addVolumeToReportAsAttached(volumeName, nodeName) return volumeName, nil }
func (asw *actualStateOfWorld) AddVolumeNode( volumeSpec *volume.Spec, nodeName string) (api.UniqueVolumeName, error) { asw.Lock() defer asw.Unlock() attachableVolumePlugin, err := asw.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec) if err != nil || attachableVolumePlugin == nil { return "", fmt.Errorf( "failed to get AttachablePlugin from volumeSpec for volume %q err=%v", volumeSpec.Name(), err) } volumeName, err := volumehelper.GetUniqueVolumeNameFromSpec( attachableVolumePlugin, volumeSpec) if err != nil { return "", fmt.Errorf( "failed to GetUniqueVolumeNameFromSpec for volumeSpec %q err=%v", volumeSpec.Name(), err) } volumeObj, volumeExists := asw.attachedVolumes[volumeName] if !volumeExists { volumeObj = attachedVolume{ volumeName: volumeName, spec: volumeSpec, nodesAttachedTo: make(map[string]nodeAttachedTo), } asw.attachedVolumes[volumeName] = volumeObj } nodeObj, nodeExists := volumeObj.nodesAttachedTo[nodeName] if !nodeExists { // Create object if it doesn't exist. volumeObj.nodesAttachedTo[nodeName] = nodeAttachedTo{ nodeName: nodeName, mountedByNode: true, // Assume mounted, until proven otherwise mountedByNodeSetCount: 0, detachRequestedTime: time.Time{}, } } else if !nodeObj.detachRequestedTime.IsZero() { // Reset detachRequestedTime values if object exists and time is non-zero nodeObj.detachRequestedTime = time.Time{} volumeObj.nodesAttachedTo[nodeName] = nodeObj } return volumeName, nil }
// addVolume adds the given volume to the cache indicating the specified // volume is attached to this node. If no volume name is supplied, a unique // volume name is generated from the volumeSpec and returned on success. If a // volume with the same generated name already exists, this is a noop. If no // volume plugin can support the given volumeSpec or more than one plugin can // support it, an error is returned. func (asw *actualStateOfWorld) addVolume( volumeName api.UniqueVolumeName, volumeSpec *volume.Spec, devicePath string) error { asw.Lock() defer asw.Unlock() volumePlugin, err := asw.volumePluginMgr.FindPluginBySpec(volumeSpec) if err != nil || volumePlugin == nil { return fmt.Errorf( "failed to get Plugin from volumeSpec for volume %q err=%v", volumeSpec.Name(), err) } if len(volumeName) == 0 { volumeName, err = volumehelper.GetUniqueVolumeNameFromSpec(volumePlugin, volumeSpec) if err != nil { return fmt.Errorf( "failed to GetUniqueVolumeNameFromSpec for volumeSpec %q using volume plugin %q err=%v", volumeSpec.Name(), volumePlugin.GetPluginName(), err) } } pluginIsAttachable := false if _, ok := volumePlugin.(volume.AttachableVolumePlugin); ok { pluginIsAttachable = true } volumeObj, volumeExists := asw.attachedVolumes[volumeName] if !volumeExists { volumeObj = attachedVolume{ volumeName: volumeName, spec: volumeSpec, mountedPods: make(map[volumetypes.UniquePodName]mountedPod), pluginName: volumePlugin.GetPluginName(), pluginIsAttachable: pluginIsAttachable, globallyMounted: false, devicePath: devicePath, } asw.attachedVolumes[volumeName] = volumeObj } return nil }
// Calls MarkVolumeAsAttached() once to add volume // Calls MarkDeviceAsMounted() to mark volume as globally mounted. // Verifies newly added volume exists in GetUnmountedVolumes() // Verifies newly added volume exists in GetGloballyMountedVolumes() func Test_MarkDeviceAsMounted_Positive_NewVolume(t *testing.T) { // Arrange volumePluginMgr, plugin := 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]} devicePath := "fake/device/path" generatedVolumeName, err := volumehelper.GetUniqueVolumeNameFromSpec(plugin, volumeSpec) err = asw.MarkVolumeAsAttached(emptyVolumeName, volumeSpec, "" /* nodeName */, devicePath) if err != nil { t.Fatalf("MarkVolumeAsAttached failed. Expected: <no error> Actual: <%v>", err) } // Act err = asw.MarkDeviceAsMounted(generatedVolumeName) // Assert if err != nil { t.Fatalf("MarkDeviceAsMounted failed. Expected: <no error> Actual: <%v>", err) } verifyVolumeExistsAsw(t, generatedVolumeName, true /* shouldExist */, asw) verifyVolumeExistsInUnmountedVolumes(t, generatedVolumeName, asw) verifyVolumeExistsInGloballyMountedVolumes(t, generatedVolumeName, 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 }
func (dsw *desiredStateOfWorld) AddPodToVolume( podName types.UniquePodName, pod *v1.Pod, volumeSpec *volume.Spec, outerVolumeSpecName string, volumeGidValue string) (v1.UniqueVolumeName, error) { dsw.Lock() defer dsw.Unlock() volumePlugin, err := dsw.volumePluginMgr.FindPluginBySpec(volumeSpec) if err != nil || volumePlugin == nil { return "", fmt.Errorf( "failed to get Plugin from volumeSpec for volume %q err=%v", volumeSpec.Name(), err) } var volumeName v1.UniqueVolumeName // The unique volume name used depends on whether the volume is attachable // or not. attachable := dsw.isAttachableVolume(volumeSpec) if attachable { // For attachable volumes, use the unique volume name as reported by // the plugin. volumeName, err = volumehelper.GetUniqueVolumeNameFromSpec(volumePlugin, volumeSpec) if err != nil { return "", fmt.Errorf( "failed to GetUniqueVolumeNameFromSpec for volumeSpec %q using volume plugin %q err=%v", volumeSpec.Name(), volumePlugin.GetPluginName(), err) } } else { // For non-attachable volumes, generate a unique name based on the pod // namespace and name and the name of the volume within the pod. volumeName = volumehelper.GetUniqueVolumeNameForNonAttachableVolume(podName, volumePlugin, volumeSpec) } volumeObj, volumeExists := dsw.volumesToMount[volumeName] if !volumeExists { volumeObj = volumeToMount{ volumeName: volumeName, podsToMount: make(map[types.UniquePodName]podToMount), pluginIsAttachable: attachable, volumeGidValue: volumeGidValue, reportedInUse: false, } dsw.volumesToMount[volumeName] = volumeObj } // Create new podToMount object. If it already exists, it is refreshed with // updated values (this is required for volumes that require remounting on // pod update, like Downward API volumes). dsw.volumesToMount[volumeName].podsToMount[podName] = podToMount{ podName: podName, pod: pod, spec: volumeSpec, outerVolumeSpecName: outerVolumeSpecName, } return volumeName, nil }