| /* |
| Copyright 2014 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package testing |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| goruntime "runtime" |
| "strings" |
| "sync" |
| "testing" |
| "time" |
| |
| "k8s.io/klog/v2" |
| |
| "k8s.io/apimachinery/pkg/util/sets" |
| "k8s.io/utils/exec" |
| testingexec "k8s.io/utils/exec/testing" |
| utilstrings "k8s.io/utils/strings" |
| |
| v1 "k8s.io/api/core/v1" |
| "k8s.io/apimachinery/pkg/api/resource" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/types" |
| "k8s.io/apimachinery/pkg/util/uuid" |
| utiltesting "k8s.io/client-go/util/testing" |
| "k8s.io/kubernetes/pkg/volume" |
| "k8s.io/kubernetes/pkg/volume/util" |
| "k8s.io/kubernetes/pkg/volume/util/recyclerclient" |
| volumetypes "k8s.io/kubernetes/pkg/volume/util/types" |
| "k8s.io/kubernetes/pkg/volume/util/volumepathhandler" |
| ) |
| |
| const ( |
| // A hook specified in storage class to indicate it's provisioning |
| // is expected to fail. |
| ExpectProvisionFailureKey = "expect-provision-failure" |
| // The node is marked as uncertain. The attach operation will fail and return timeout error |
| // for the first attach call. The following call will return successfully. |
| UncertainAttachNode = "uncertain-attach-node" |
| // The detach operation will keep failing on the node. |
| FailDetachNode = "fail-detach-node" |
| // The node is marked as timeout. The attach operation will always fail and return timeout error |
| // but the operation is actually succeeded. |
| TimeoutAttachNode = "timeout-attach-node" |
| // The node is marked as multi-attach which means it is allowed to attach the volume to multiple nodes. |
| MultiAttachNode = "multi-attach-node" |
| // TimeoutOnSetupVolumeName will cause Setup call to timeout but volume will finish mounting. |
| TimeoutOnSetupVolumeName = "timeout-setup-volume" |
| // FailOnSetupVolumeName will cause setup call to fail |
| FailOnSetupVolumeName = "fail-setup-volume" |
| //TimeoutAndFailOnSetupVolumeName will first timeout and then fail the setup |
| TimeoutAndFailOnSetupVolumeName = "timeout-and-fail-setup-volume" |
| // SuccessAndTimeoutSetupVolumeName will cause first mount operation to succeed but subsequent attempts to timeout |
| SuccessAndTimeoutSetupVolumeName = "success-and-timeout-setup-volume-name" |
| // SuccessAndFailOnSetupVolumeName will cause first mount operation to succeed but subsequent attempts to fail |
| SuccessAndFailOnSetupVolumeName = "success-and-failed-setup-device-name" |
| |
| // TimeoutOnMountDeviceVolumeName will cause MountDevice call to timeout but Setup will finish. |
| TimeoutOnMountDeviceVolumeName = "timeout-mount-device-volume" |
| // TimeoutAndFailOnMountDeviceVolumeName will cause first MountDevice call to timeout but second call will fail |
| TimeoutAndFailOnMountDeviceVolumeName = "timeout-and-fail-mount-device-name" |
| // FailMountDeviceVolumeName will cause MountDevice operation on volume to fail |
| FailMountDeviceVolumeName = "fail-mount-device-volume-name" |
| // SuccessAndTimeoutDeviceName will cause first mount operation to succeed but subsequent attempts to timeout |
| SuccessAndTimeoutDeviceName = "success-and-timeout-device-name" |
| // SuccessAndFailOnMountDeviceName will cause first mount operation to succeed but subsequent attempts to fail |
| SuccessAndFailOnMountDeviceName = "success-and-failed-mount-device-name" |
| |
| // FailWithInUseVolumeName will cause NodeExpandVolume to result in FailedPrecondition error |
| FailWithInUseVolumeName = "fail-expansion-in-use" |
| FailWithUnSupportedVolumeName = "fail-expansion-unsupported" |
| |
| FailVolumeExpansion = "fail-expansion-test" |
| |
| AlwaysFailNodeExpansion = "always-fail-node-expansion" |
| |
| deviceNotMounted = "deviceNotMounted" |
| deviceMountUncertain = "deviceMountUncertain" |
| deviceMounted = "deviceMounted" |
| |
| volumeNotMounted = "volumeNotMounted" |
| volumeMountUncertain = "volumeMountUncertain" |
| volumeMounted = "volumeMounted" |
| |
| FailNewMounter = "fail-new-mounter" |
| ) |
| |
| // CommandScript is used to pre-configure a command that will be executed and |
| // optionally set it's output (stdout and stderr combined) and return code. |
| type CommandScript struct { |
| // Cmd is the command to execute, e.g. "ls" |
| Cmd string |
| // Args is a slice of arguments to pass to the command, e.g. "-a" |
| Args []string |
| // Output is the combined stdout and stderr of the command to return |
| Output string |
| // ReturnCode is the exit code for the command. Setting this to non-zero will |
| // cause the command to return an error with this exit code set. |
| ReturnCode int |
| } |
| |
| // ScriptCommands configures fe, the FakeExec, to have a pre-configured list of |
| // commands to expect. Calling more commands using fe than those scripted will |
| // result in a panic. By default, the fe does not enforce command argument checking |
| // or order -- if you have given an Output to the command, the first command scripted |
| // will return its output on the first command call, even if the command called is |
| // different than the one scripted. This is mostly useful to make sure that the |
| // right number of commands were called. If you want to check the exact commands |
| // and arguments were called, set fe.ExectOrder to true. |
| func ScriptCommands(fe *testingexec.FakeExec, scripts []CommandScript) { |
| fe.DisableScripts = false |
| for _, script := range scripts { |
| fakeCmd := &testingexec.FakeCmd{} |
| cmdAction := makeFakeCmd(fakeCmd, script.Cmd, script.Args...) |
| outputAction := makeFakeOutput(script.Output, script.ReturnCode) |
| fakeCmd.CombinedOutputScript = append(fakeCmd.CombinedOutputScript, outputAction) |
| fe.CommandScript = append(fe.CommandScript, cmdAction) |
| } |
| } |
| |
| func makeFakeCmd(fakeCmd *testingexec.FakeCmd, cmd string, args ...string) testingexec.FakeCommandAction { |
| fc := fakeCmd |
| c := cmd |
| a := args |
| return func(cmd string, args ...string) exec.Cmd { |
| command := testingexec.InitFakeCmd(fc, c, a...) |
| return command |
| } |
| } |
| |
| func makeFakeOutput(output string, rc int) testingexec.FakeAction { |
| o := output |
| var e error |
| if rc != 0 { |
| e = testingexec.FakeExitError{Status: rc} |
| } |
| return func() ([]byte, []byte, error) { |
| return []byte(o), nil, e |
| } |
| } |
| |
| func ProbeVolumePlugins(config volume.VolumeConfig) []volume.VolumePlugin { |
| if _, ok := config.OtherAttributes["fake-property"]; ok { |
| return []volume.VolumePlugin{ |
| &FakeVolumePlugin{ |
| PluginName: "fake-plugin", |
| Host: nil, |
| // SomeFakeProperty: config.OtherAttributes["fake-property"] -- string, may require parsing by plugin |
| }, |
| } |
| } |
| return []volume.VolumePlugin{&FakeVolumePlugin{PluginName: "fake-plugin"}} |
| } |
| |
| // FakeVolumePlugin is useful for testing. It tries to be a fully compliant |
| // plugin, but all it does is make empty directories. |
| // Use as: |
| // |
| // volume.RegisterPlugin(&FakePlugin{"fake-name"}) |
| type FakeVolumePlugin struct { |
| sync.RWMutex |
| PluginName string |
| Host volume.VolumeHost |
| Config volume.VolumeConfig |
| LastProvisionerOptions volume.VolumeOptions |
| NewAttacherCallCount int |
| NewDetacherCallCount int |
| NodeExpandCallCount int |
| VolumeLimits map[string]int64 |
| VolumeLimitsError error |
| LimitKey string |
| ProvisionDelaySeconds int |
| SupportsRemount bool |
| SupportsSELinux bool |
| DisableNodeExpansion bool |
| CanSupportFn func(*volume.Spec) bool |
| |
| // default to false which means it is attachable by default |
| NonAttachable bool |
| |
| // Add callbacks as needed |
| WaitForAttachHook func(spec *volume.Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error) |
| UnmountDeviceHook func(globalMountPath string) error |
| |
| Mounters []*FakeVolume |
| Unmounters []*FakeVolume |
| Attachers []*FakeVolume |
| Detachers []*FakeVolume |
| BlockVolumeMappers []*FakeVolume |
| BlockVolumeUnmappers []*FakeVolume |
| } |
| |
| var _ volume.VolumePlugin = &FakeVolumePlugin{} |
| var _ volume.BlockVolumePlugin = &FakeVolumePlugin{} |
| var _ volume.RecyclableVolumePlugin = &FakeVolumePlugin{} |
| var _ volume.DeletableVolumePlugin = &FakeVolumePlugin{} |
| var _ volume.ProvisionableVolumePlugin = &FakeVolumePlugin{} |
| var _ volume.AttachableVolumePlugin = &FakeVolumePlugin{} |
| var _ volume.VolumePluginWithAttachLimits = &FakeVolumePlugin{} |
| var _ volume.DeviceMountableVolumePlugin = &FakeVolumePlugin{} |
| var _ volume.NodeExpandableVolumePlugin = &FakeVolumePlugin{} |
| |
| func (plugin *FakeVolumePlugin) getFakeVolume(list *[]*FakeVolume) *FakeVolume { |
| if list != nil { |
| volumeList := *list |
| if len(volumeList) > 0 { |
| volume := volumeList[0] |
| volume.Lock() |
| defer volume.Unlock() |
| volume.WaitForAttachHook = plugin.WaitForAttachHook |
| volume.UnmountDeviceHook = plugin.UnmountDeviceHook |
| return volume |
| } |
| } |
| volume := &FakeVolume{ |
| WaitForAttachHook: plugin.WaitForAttachHook, |
| UnmountDeviceHook: plugin.UnmountDeviceHook, |
| } |
| volume.VolumesAttached = make(map[string]sets.String) |
| volume.DeviceMountState = make(map[string]string) |
| volume.VolumeMountState = make(map[string]string) |
| if list != nil { |
| *list = append(*list, volume) |
| } |
| return volume |
| } |
| |
| func (plugin *FakeVolumePlugin) Init(host volume.VolumeHost) error { |
| plugin.Lock() |
| defer plugin.Unlock() |
| plugin.Host = host |
| return nil |
| } |
| |
| func (plugin *FakeVolumePlugin) GetPluginName() string { |
| plugin.RLock() |
| defer plugin.RUnlock() |
| return plugin.PluginName |
| } |
| |
| func (plugin *FakeVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) { |
| var volumeName string |
| if spec.Volume != nil && spec.Volume.GCEPersistentDisk != nil { |
| volumeName = spec.Volume.GCEPersistentDisk.PDName |
| } else if spec.Volume != nil && spec.Volume.RBD != nil { |
| volumeName = spec.Volume.RBD.RBDImage |
| } else if spec.PersistentVolume != nil && |
| spec.PersistentVolume.Spec.GCEPersistentDisk != nil { |
| volumeName = spec.PersistentVolume.Spec.GCEPersistentDisk.PDName |
| } else if spec.PersistentVolume != nil && |
| spec.PersistentVolume.Spec.RBD != nil { |
| volumeName = spec.PersistentVolume.Spec.RBD.RBDImage |
| } else if spec.Volume != nil && spec.Volume.CSI != nil { |
| volumeName = spec.Volume.CSI.Driver |
| } |
| if volumeName == "" { |
| volumeName = spec.Name() |
| } |
| return volumeName, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) CanSupport(spec *volume.Spec) bool { |
| if plugin.CanSupportFn != nil { |
| return plugin.CanSupportFn(spec) |
| } |
| |
| return true |
| } |
| |
| func (plugin *FakeVolumePlugin) RequiresRemount(spec *volume.Spec) bool { |
| return plugin.SupportsRemount |
| } |
| |
| func (plugin *FakeVolumePlugin) SupportsMountOption() bool { |
| return true |
| } |
| |
| func (plugin *FakeVolumePlugin) SupportsBulkVolumeVerification() bool { |
| return false |
| } |
| |
| func (plugin *FakeVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { |
| return plugin.SupportsSELinux, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { |
| plugin.Lock() |
| defer plugin.Unlock() |
| if spec.Name() == FailNewMounter { |
| return nil, fmt.Errorf("AlwaysFailNewMounter") |
| } |
| fakeVolume := plugin.getFakeVolume(&plugin.Mounters) |
| fakeVolume.Lock() |
| defer fakeVolume.Unlock() |
| fakeVolume.PodUID = pod.UID |
| fakeVolume.VolName = spec.Name() |
| fakeVolume.Plugin = plugin |
| fakeVolume.MetricsNil = volume.MetricsNil{} |
| return fakeVolume, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) GetMounters() (Mounters []*FakeVolume) { |
| plugin.RLock() |
| defer plugin.RUnlock() |
| return plugin.Mounters |
| } |
| |
| func (plugin *FakeVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { |
| plugin.Lock() |
| defer plugin.Unlock() |
| fakeVolume := plugin.getFakeVolume(&plugin.Unmounters) |
| fakeVolume.Lock() |
| defer fakeVolume.Unlock() |
| fakeVolume.PodUID = podUID |
| fakeVolume.VolName = volName |
| fakeVolume.Plugin = plugin |
| fakeVolume.MetricsNil = volume.MetricsNil{} |
| return fakeVolume, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) GetUnmounters() (Unmounters []*FakeVolume) { |
| plugin.RLock() |
| defer plugin.RUnlock() |
| return plugin.Unmounters |
| } |
| |
| // Block volume support |
| func (plugin *FakeVolumePlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.BlockVolumeMapper, error) { |
| plugin.Lock() |
| defer plugin.Unlock() |
| volume := plugin.getFakeVolume(&plugin.BlockVolumeMappers) |
| volume.Lock() |
| defer volume.Unlock() |
| if pod != nil { |
| volume.PodUID = pod.UID |
| } |
| volume.VolName = spec.Name() |
| volume.Plugin = plugin |
| return volume, nil |
| } |
| |
| // Block volume support |
| func (plugin *FakeVolumePlugin) GetBlockVolumeMapper() (BlockVolumeMappers []*FakeVolume) { |
| plugin.RLock() |
| defer plugin.RUnlock() |
| return plugin.BlockVolumeMappers |
| } |
| |
| // Block volume support |
| func (plugin *FakeVolumePlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (volume.BlockVolumeUnmapper, error) { |
| plugin.Lock() |
| defer plugin.Unlock() |
| volume := plugin.getFakeVolume(&plugin.BlockVolumeUnmappers) |
| volume.Lock() |
| defer volume.Unlock() |
| volume.PodUID = podUID |
| volume.VolName = volName |
| volume.Plugin = plugin |
| return volume, nil |
| } |
| |
| // Block volume support |
| func (plugin *FakeVolumePlugin) GetBlockVolumeUnmapper() (BlockVolumeUnmappers []*FakeVolume) { |
| plugin.RLock() |
| defer plugin.RUnlock() |
| return plugin.BlockVolumeUnmappers |
| } |
| |
| func (plugin *FakeVolumePlugin) NewAttacher() (volume.Attacher, error) { |
| plugin.Lock() |
| defer plugin.Unlock() |
| plugin.NewAttacherCallCount = plugin.NewAttacherCallCount + 1 |
| return plugin.getFakeVolume(&plugin.Attachers), nil |
| } |
| |
| func (plugin *FakeVolumePlugin) NewDeviceMounter() (volume.DeviceMounter, error) { |
| return plugin.NewAttacher() |
| } |
| |
| func (plugin *FakeVolumePlugin) GetAttachers() (Attachers []*FakeVolume) { |
| plugin.RLock() |
| defer plugin.RUnlock() |
| return plugin.Attachers |
| } |
| |
| func (plugin *FakeVolumePlugin) GetNewAttacherCallCount() int { |
| plugin.RLock() |
| defer plugin.RUnlock() |
| return plugin.NewAttacherCallCount |
| } |
| |
| func (plugin *FakeVolumePlugin) NewDetacher() (volume.Detacher, error) { |
| plugin.Lock() |
| defer plugin.Unlock() |
| plugin.NewDetacherCallCount = plugin.NewDetacherCallCount + 1 |
| detacher := plugin.getFakeVolume(&plugin.Detachers) |
| attacherList := plugin.Attachers |
| if len(attacherList) > 0 { |
| detacherList := plugin.Detachers |
| if len(detacherList) > 0 { |
| detacherList[0].VolumesAttached = attacherList[0].VolumesAttached |
| } |
| |
| } |
| return detacher, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) { |
| return plugin.NewDetacher() |
| } |
| |
| func (plugin *FakeVolumePlugin) GetDetachers() (Detachers []*FakeVolume) { |
| plugin.RLock() |
| defer plugin.RUnlock() |
| return plugin.Detachers |
| } |
| |
| func (plugin *FakeVolumePlugin) GetNewDetacherCallCount() int { |
| plugin.RLock() |
| defer plugin.RUnlock() |
| return plugin.NewDetacherCallCount |
| } |
| |
| func (plugin *FakeVolumePlugin) CanAttach(spec *volume.Spec) (bool, error) { |
| return !plugin.NonAttachable, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) CanDeviceMount(spec *volume.Spec) (bool, error) { |
| return true, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) Recycle(pvName string, spec *volume.Spec, eventRecorder recyclerclient.RecycleEventRecorder) error { |
| return nil |
| } |
| |
| func (plugin *FakeVolumePlugin) NewDeleter(logger klog.Logger, spec *volume.Spec) (volume.Deleter, error) { |
| return &FakeDeleter{"/attributesTransferredFromSpec", volume.MetricsNil{}}, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) NewProvisioner(logger klog.Logger, options volume.VolumeOptions) (volume.Provisioner, error) { |
| plugin.Lock() |
| defer plugin.Unlock() |
| plugin.LastProvisionerOptions = options |
| return &FakeProvisioner{options, plugin.Host, plugin.ProvisionDelaySeconds}, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { |
| return []v1.PersistentVolumeAccessMode{} |
| } |
| |
| func (plugin *FakeVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { |
| return volume.ReconstructedVolume{ |
| Spec: &volume.Spec{ |
| Volume: &v1.Volume{ |
| Name: volumeName, |
| }, |
| }, |
| }, nil |
| } |
| |
| // Block volume support |
| func (plugin *FakeVolumePlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mountPath string) (*volume.Spec, error) { |
| return &volume.Spec{ |
| Volume: &v1.Volume{ |
| Name: volumeName, |
| }, |
| }, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { |
| return []string{}, nil |
| } |
| |
| // Expandable volume support |
| func (plugin *FakeVolumePlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) { |
| return resource.Quantity{}, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) RequiresFSResize() bool { |
| return !plugin.DisableNodeExpansion |
| } |
| |
| func (plugin *FakeVolumePlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) { |
| plugin.NodeExpandCallCount++ |
| if resizeOptions.VolumeSpec.Name() == FailWithInUseVolumeName { |
| return false, volumetypes.NewFailedPreconditionError("volume-in-use") |
| } |
| if resizeOptions.VolumeSpec.Name() == FailWithUnSupportedVolumeName { |
| return false, volumetypes.NewOperationNotSupportedError("volume-unsupported") |
| } |
| |
| if resizeOptions.VolumeSpec.Name() == AlwaysFailNodeExpansion { |
| return false, fmt.Errorf("test failure: NodeExpand") |
| } |
| |
| if resizeOptions.VolumeSpec.Name() == FailVolumeExpansion { |
| return false, fmt.Errorf("fail volume expansion for volume: %s", FailVolumeExpansion) |
| } |
| return true, nil |
| } |
| |
| func (plugin *FakeVolumePlugin) GetVolumeLimits() (map[string]int64, error) { |
| return plugin.VolumeLimits, plugin.VolumeLimitsError |
| } |
| |
| func (plugin *FakeVolumePlugin) VolumeLimitKey(spec *volume.Spec) string { |
| return plugin.LimitKey |
| } |
| |
| // FakeBasicVolumePlugin implements a basic volume plugin. It wrappers on |
| // FakeVolumePlugin but implements VolumePlugin interface only. |
| // It is useful to test logic involving plugin interfaces. |
| type FakeBasicVolumePlugin struct { |
| Plugin FakeVolumePlugin |
| } |
| |
| func (f *FakeBasicVolumePlugin) GetPluginName() string { |
| return f.Plugin.GetPluginName() |
| } |
| |
| func (f *FakeBasicVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) { |
| return f.Plugin.GetVolumeName(spec) |
| } |
| |
| // CanSupport tests whether the plugin supports a given volume specification by |
| // testing volume spec name begins with plugin name or not. |
| // This is useful to choose plugin by volume in testing. |
| func (f *FakeBasicVolumePlugin) CanSupport(spec *volume.Spec) bool { |
| return strings.HasPrefix(spec.Name(), f.GetPluginName()) |
| } |
| |
| func (f *FakeBasicVolumePlugin) ConstructVolumeSpec(ame, mountPath string) (volume.ReconstructedVolume, error) { |
| return f.Plugin.ConstructVolumeSpec(ame, mountPath) |
| } |
| |
| func (f *FakeBasicVolumePlugin) Init(ost volume.VolumeHost) error { |
| return f.Plugin.Init(ost) |
| } |
| |
| func (f *FakeBasicVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { |
| return f.Plugin.NewMounter(spec, pod, opts) |
| } |
| |
| func (f *FakeBasicVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { |
| return f.Plugin.NewUnmounter(volName, podUID) |
| } |
| |
| func (f *FakeBasicVolumePlugin) RequiresRemount(spec *volume.Spec) bool { |
| return f.Plugin.RequiresRemount(spec) |
| } |
| |
| func (f *FakeBasicVolumePlugin) SupportsBulkVolumeVerification() bool { |
| return f.Plugin.SupportsBulkVolumeVerification() |
| } |
| |
| func (f *FakeBasicVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { |
| return f.Plugin.SupportsSELinuxContextMount(spec) |
| } |
| |
| func (f *FakeBasicVolumePlugin) SupportsMountOption() bool { |
| return f.Plugin.SupportsMountOption() |
| } |
| |
| var _ volume.VolumePlugin = &FakeBasicVolumePlugin{} |
| |
| // FakeDeviceMountableVolumePlugin implements an device mountable plugin based on FakeBasicVolumePlugin. |
| type FakeDeviceMountableVolumePlugin struct { |
| FakeBasicVolumePlugin |
| } |
| |
| func (f *FakeDeviceMountableVolumePlugin) CanDeviceMount(spec *volume.Spec) (bool, error) { |
| return true, nil |
| } |
| |
| func (f *FakeDeviceMountableVolumePlugin) NewDeviceMounter() (volume.DeviceMounter, error) { |
| return f.Plugin.NewDeviceMounter() |
| } |
| |
| func (f *FakeDeviceMountableVolumePlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) { |
| return f.Plugin.NewDeviceUnmounter() |
| } |
| |
| func (f *FakeDeviceMountableVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { |
| return f.Plugin.GetDeviceMountRefs(deviceMountPath) |
| } |
| |
| var _ volume.VolumePlugin = &FakeDeviceMountableVolumePlugin{} |
| var _ volume.DeviceMountableVolumePlugin = &FakeDeviceMountableVolumePlugin{} |
| |
| // FakeAttachableVolumePlugin implements an attachable plugin based on FakeDeviceMountableVolumePlugin. |
| type FakeAttachableVolumePlugin struct { |
| FakeDeviceMountableVolumePlugin |
| } |
| |
| func (f *FakeAttachableVolumePlugin) NewAttacher() (volume.Attacher, error) { |
| return f.Plugin.NewAttacher() |
| } |
| |
| func (f *FakeAttachableVolumePlugin) NewDetacher() (volume.Detacher, error) { |
| return f.Plugin.NewDetacher() |
| } |
| |
| func (f *FakeAttachableVolumePlugin) CanAttach(spec *volume.Spec) (bool, error) { |
| return true, nil |
| } |
| |
| var _ volume.VolumePlugin = &FakeAttachableVolumePlugin{} |
| var _ volume.AttachableVolumePlugin = &FakeAttachableVolumePlugin{} |
| |
| type FakeFileVolumePlugin struct { |
| } |
| |
| func (plugin *FakeFileVolumePlugin) Init(host volume.VolumeHost) error { |
| return nil |
| } |
| |
| func (plugin *FakeFileVolumePlugin) GetPluginName() string { |
| return "fake-file-plugin" |
| } |
| |
| func (plugin *FakeFileVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) { |
| return "", nil |
| } |
| |
| func (plugin *FakeFileVolumePlugin) CanSupport(spec *volume.Spec) bool { |
| return true |
| } |
| |
| func (plugin *FakeFileVolumePlugin) RequiresRemount(spec *volume.Spec) bool { |
| return false |
| } |
| |
| func (plugin *FakeFileVolumePlugin) SupportsMountOption() bool { |
| return false |
| } |
| |
| func (plugin *FakeFileVolumePlugin) SupportsBulkVolumeVerification() bool { |
| return false |
| } |
| |
| func (plugin *FakeFileVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { |
| return false, nil |
| } |
| |
| func (plugin *FakeFileVolumePlugin) NewMounter(spec *volume.Spec, podRef *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { |
| return nil, nil |
| } |
| |
| func (plugin *FakeFileVolumePlugin) NewUnmounter(name string, podUID types.UID) (volume.Unmounter, error) { |
| return nil, nil |
| } |
| |
| func (plugin *FakeFileVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { |
| return volume.ReconstructedVolume{}, nil |
| } |
| |
| func NewFakeFileVolumePlugin() []volume.VolumePlugin { |
| return []volume.VolumePlugin{&FakeFileVolumePlugin{}} |
| } |
| |
| type FakeVolume struct { |
| sync.RWMutex |
| PodUID types.UID |
| VolName string |
| Plugin *FakeVolumePlugin |
| volume.MetricsNil |
| VolumesAttached map[string]sets.String |
| DeviceMountState map[string]string |
| VolumeMountState map[string]string |
| |
| // Add callbacks as needed |
| WaitForAttachHook func(spec *volume.Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error) |
| UnmountDeviceHook func(globalMountPath string) error |
| |
| SetUpCallCount int |
| TearDownCallCount int |
| AttachCallCount int |
| DetachCallCount int |
| WaitForAttachCallCount int |
| MountDeviceCallCount int |
| UnmountDeviceCallCount int |
| GetDeviceMountPathCallCount int |
| SetUpDeviceCallCount int |
| TearDownDeviceCallCount int |
| MapPodDeviceCallCount int |
| UnmapPodDeviceCallCount int |
| GlobalMapPathCallCount int |
| PodDeviceMapPathCallCount int |
| } |
| |
| func getUniqueVolumeName(spec *volume.Spec) (string, error) { |
| var volumeName string |
| if spec.Volume != nil && spec.Volume.GCEPersistentDisk != nil { |
| volumeName = spec.Volume.GCEPersistentDisk.PDName |
| } else if spec.Volume != nil && spec.Volume.RBD != nil { |
| volumeName = spec.Volume.RBD.RBDImage |
| } else if spec.PersistentVolume != nil && |
| spec.PersistentVolume.Spec.GCEPersistentDisk != nil { |
| volumeName = spec.PersistentVolume.Spec.GCEPersistentDisk.PDName |
| } else if spec.PersistentVolume != nil && |
| spec.PersistentVolume.Spec.RBD != nil { |
| volumeName = spec.PersistentVolume.Spec.RBD.RBDImage |
| } |
| if volumeName == "" { |
| volumeName = spec.Name() |
| } |
| return volumeName, nil |
| } |
| |
| func (_ *FakeVolume) GetAttributes() volume.Attributes { |
| return volume.Attributes{ |
| ReadOnly: false, |
| Managed: true, |
| SELinuxRelabel: true, |
| } |
| } |
| |
| func (fv *FakeVolume) SetUp(mounterArgs volume.MounterArgs) error { |
| fv.Lock() |
| defer fv.Unlock() |
| err := fv.setupInternal(mounterArgs) |
| fv.SetUpCallCount++ |
| return err |
| } |
| |
| func (fv *FakeVolume) setupInternal(mounterArgs volume.MounterArgs) error { |
| if fv.VolName == TimeoutOnSetupVolumeName { |
| fv.VolumeMountState[fv.VolName] = volumeMountUncertain |
| return volumetypes.NewUncertainProgressError("time out on setup") |
| } |
| |
| if fv.VolName == FailOnSetupVolumeName { |
| fv.VolumeMountState[fv.VolName] = volumeNotMounted |
| return fmt.Errorf("mounting volume failed") |
| } |
| |
| if fv.VolName == TimeoutAndFailOnSetupVolumeName { |
| _, ok := fv.VolumeMountState[fv.VolName] |
| if !ok { |
| fv.VolumeMountState[fv.VolName] = volumeMountUncertain |
| return volumetypes.NewUncertainProgressError("time out on setup") |
| } |
| fv.VolumeMountState[fv.VolName] = volumeNotMounted |
| return fmt.Errorf("mounting volume failed") |
| |
| } |
| |
| if fv.VolName == SuccessAndFailOnSetupVolumeName { |
| _, ok := fv.VolumeMountState[fv.VolName] |
| if ok { |
| fv.VolumeMountState[fv.VolName] = volumeNotMounted |
| return fmt.Errorf("mounting volume failed") |
| } |
| } |
| |
| if fv.VolName == SuccessAndTimeoutSetupVolumeName { |
| _, ok := fv.VolumeMountState[fv.VolName] |
| if ok { |
| fv.VolumeMountState[fv.VolName] = volumeMountUncertain |
| return volumetypes.NewUncertainProgressError("time out on setup") |
| } |
| } |
| |
| fv.VolumeMountState[fv.VolName] = volumeNotMounted |
| return fv.SetUpAt(fv.getPath(), mounterArgs) |
| } |
| |
| func (fv *FakeVolume) GetSetUpCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.SetUpCallCount |
| } |
| |
| func (fv *FakeVolume) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { |
| return os.MkdirAll(dir, 0750) |
| } |
| |
| func (fv *FakeVolume) GetPath() string { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.getPath() |
| } |
| |
| func (fv *FakeVolume) getPath() string { |
| return filepath.Join(fv.Plugin.Host.GetPodVolumeDir(fv.PodUID, utilstrings.EscapeQualifiedName(fv.Plugin.PluginName), fv.VolName)) |
| } |
| |
| func (fv *FakeVolume) TearDown() error { |
| fv.Lock() |
| defer fv.Unlock() |
| fv.TearDownCallCount++ |
| return fv.TearDownAt(fv.getPath()) |
| } |
| |
| func (fv *FakeVolume) GetTearDownCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.TearDownCallCount |
| } |
| |
| func (fv *FakeVolume) TearDownAt(dir string) error { |
| return os.RemoveAll(dir) |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) SetUpDevice() (string, error) { |
| fv.Lock() |
| defer fv.Unlock() |
| if fv.VolName == TimeoutOnMountDeviceVolumeName { |
| fv.DeviceMountState[fv.VolName] = deviceMountUncertain |
| return "", volumetypes.NewUncertainProgressError("mount failed") |
| } |
| if fv.VolName == FailMountDeviceVolumeName { |
| fv.DeviceMountState[fv.VolName] = deviceNotMounted |
| return "", fmt.Errorf("error mapping disk: %s", fv.VolName) |
| } |
| |
| if fv.VolName == TimeoutAndFailOnMountDeviceVolumeName { |
| _, ok := fv.DeviceMountState[fv.VolName] |
| if !ok { |
| fv.DeviceMountState[fv.VolName] = deviceMountUncertain |
| return "", volumetypes.NewUncertainProgressError("timed out mounting error") |
| } |
| fv.DeviceMountState[fv.VolName] = deviceNotMounted |
| return "", fmt.Errorf("error mapping disk: %s", fv.VolName) |
| } |
| |
| if fv.VolName == SuccessAndTimeoutDeviceName { |
| _, ok := fv.DeviceMountState[fv.VolName] |
| if ok { |
| fv.DeviceMountState[fv.VolName] = deviceMountUncertain |
| return "", volumetypes.NewUncertainProgressError("error mounting state") |
| } |
| } |
| if fv.VolName == SuccessAndFailOnMountDeviceName { |
| _, ok := fv.DeviceMountState[fv.VolName] |
| if ok { |
| return "", fmt.Errorf("error mapping disk: %s", fv.VolName) |
| } |
| } |
| |
| fv.DeviceMountState[fv.VolName] = deviceMounted |
| fv.SetUpDeviceCallCount++ |
| |
| return "", nil |
| } |
| |
| func (fv *FakeVolume) GetStagingPath() string { |
| return filepath.Join(fv.Plugin.Host.GetVolumeDevicePluginDir(utilstrings.EscapeQualifiedName(fv.Plugin.PluginName)), "staging", fv.VolName) |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) GetSetUpDeviceCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.SetUpDeviceCallCount |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) GetGlobalMapPath(spec *volume.Spec) (string, error) { |
| fv.Lock() |
| defer fv.Unlock() |
| fv.GlobalMapPathCallCount++ |
| return fv.getGlobalMapPath() |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) getGlobalMapPath() (string, error) { |
| return filepath.Join(fv.Plugin.Host.GetVolumeDevicePluginDir(utilstrings.EscapeQualifiedName(fv.Plugin.PluginName)), "pluginDependentPath"), nil |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) GetGlobalMapPathCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.GlobalMapPathCallCount |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) GetPodDeviceMapPath() (string, string) { |
| fv.RLock() |
| defer fv.RUnlock() |
| fv.PodDeviceMapPathCallCount++ |
| return fv.getPodDeviceMapPath() |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) getPodDeviceMapPath() (string, string) { |
| return filepath.Join(fv.Plugin.Host.GetPodVolumeDeviceDir(fv.PodUID, utilstrings.EscapeQualifiedName(fv.Plugin.PluginName))), fv.VolName |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) GetPodDeviceMapPathCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.PodDeviceMapPathCallCount |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) TearDownDevice(mapPath string, devicePath string) error { |
| fv.Lock() |
| defer fv.Unlock() |
| fv.TearDownDeviceCallCount++ |
| return nil |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) GetTearDownDeviceCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.TearDownDeviceCallCount |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) UnmapPodDevice() error { |
| fv.Lock() |
| defer fv.Unlock() |
| fv.UnmapPodDeviceCallCount++ |
| return nil |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) GetUnmapPodDeviceCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.UnmapPodDeviceCallCount |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) MapPodDevice() (string, error) { |
| fv.Lock() |
| defer fv.Unlock() |
| |
| if fv.VolName == TimeoutOnSetupVolumeName { |
| fv.VolumeMountState[fv.VolName] = volumeMountUncertain |
| return "", volumetypes.NewUncertainProgressError("time out on setup") |
| } |
| |
| if fv.VolName == FailOnSetupVolumeName { |
| fv.VolumeMountState[fv.VolName] = volumeNotMounted |
| return "", fmt.Errorf("mounting volume failed") |
| } |
| |
| if fv.VolName == TimeoutAndFailOnSetupVolumeName { |
| _, ok := fv.VolumeMountState[fv.VolName] |
| if !ok { |
| fv.VolumeMountState[fv.VolName] = volumeMountUncertain |
| return "", volumetypes.NewUncertainProgressError("time out on setup") |
| } |
| fv.VolumeMountState[fv.VolName] = volumeNotMounted |
| return "", fmt.Errorf("mounting volume failed") |
| |
| } |
| |
| if fv.VolName == SuccessAndFailOnSetupVolumeName { |
| _, ok := fv.VolumeMountState[fv.VolName] |
| if ok { |
| fv.VolumeMountState[fv.VolName] = volumeNotMounted |
| return "", fmt.Errorf("mounting volume failed") |
| } |
| } |
| |
| if fv.VolName == SuccessAndTimeoutSetupVolumeName { |
| _, ok := fv.VolumeMountState[fv.VolName] |
| if ok { |
| fv.VolumeMountState[fv.VolName] = volumeMountUncertain |
| return "", volumetypes.NewUncertainProgressError("time out on setup") |
| } |
| } |
| |
| fv.VolumeMountState[fv.VolName] = volumeMounted |
| fv.MapPodDeviceCallCount++ |
| return "", nil |
| } |
| |
| // Block volume support |
| func (fv *FakeVolume) GetMapPodDeviceCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.MapPodDeviceCallCount |
| } |
| |
| func (fv *FakeVolume) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) { |
| fv.Lock() |
| defer fv.Unlock() |
| fv.AttachCallCount++ |
| |
| volumeName, err := getUniqueVolumeName(spec) |
| if err != nil { |
| return "", err |
| } |
| volumeNodes, exist := fv.VolumesAttached[volumeName] |
| if exist { |
| if nodeName == UncertainAttachNode { |
| return "/dev/vdb-test", nil |
| } |
| // even if volume was previously attached to time out, we need to keep returning error |
| // so as reconciler can not confirm this volume as attached. |
| if nodeName == TimeoutAttachNode { |
| return "", fmt.Errorf("timed out to attach volume %q to node %q", volumeName, nodeName) |
| } |
| if volumeNodes.Has(string(nodeName)) || volumeNodes.Has(MultiAttachNode) || nodeName == MultiAttachNode { |
| volumeNodes.Insert(string(nodeName)) |
| return "/dev/vdb-test", nil |
| } |
| return "", fmt.Errorf("volume %q trying to attach to node %q is already attached to node %q", volumeName, nodeName, volumeNodes) |
| } |
| |
| fv.VolumesAttached[volumeName] = sets.NewString(string(nodeName)) |
| if nodeName == UncertainAttachNode || nodeName == TimeoutAttachNode { |
| return "", fmt.Errorf("timed out to attach volume %q to node %q", volumeName, nodeName) |
| } |
| return "/dev/vdb-test", nil |
| } |
| |
| func (fv *FakeVolume) GetAttachCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.AttachCallCount |
| } |
| |
| func (fv *FakeVolume) WaitForAttach(spec *volume.Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error) { |
| fv.Lock() |
| defer fv.Unlock() |
| fv.WaitForAttachCallCount++ |
| if fv.WaitForAttachHook != nil { |
| return fv.WaitForAttachHook(spec, devicePath, pod, spectimeout) |
| } |
| return "/dev/sdb", nil |
| } |
| |
| func (fv *FakeVolume) GetWaitForAttachCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.WaitForAttachCallCount |
| } |
| |
| func (fv *FakeVolume) GetDeviceMountPath(spec *volume.Spec) (string, error) { |
| fv.Lock() |
| defer fv.Unlock() |
| fv.GetDeviceMountPathCallCount++ |
| return "", nil |
| } |
| |
| func (fv *FakeVolume) mountDeviceInternal(spec *volume.Spec, devicePath string, deviceMountPath string) error { |
| fv.Lock() |
| defer fv.Unlock() |
| if spec.Name() == TimeoutOnMountDeviceVolumeName { |
| fv.DeviceMountState[spec.Name()] = deviceMountUncertain |
| return volumetypes.NewUncertainProgressError("mount failed") |
| } |
| |
| if spec.Name() == FailMountDeviceVolumeName { |
| fv.DeviceMountState[spec.Name()] = deviceNotMounted |
| return fmt.Errorf("error mounting disk: %s", devicePath) |
| } |
| |
| if spec.Name() == TimeoutAndFailOnMountDeviceVolumeName { |
| _, ok := fv.DeviceMountState[spec.Name()] |
| if !ok { |
| fv.DeviceMountState[spec.Name()] = deviceMountUncertain |
| return volumetypes.NewUncertainProgressError("timed out mounting error") |
| } |
| fv.DeviceMountState[spec.Name()] = deviceNotMounted |
| return fmt.Errorf("error mounting disk: %s", devicePath) |
| } |
| |
| if spec.Name() == SuccessAndTimeoutDeviceName { |
| _, ok := fv.DeviceMountState[spec.Name()] |
| if ok { |
| fv.DeviceMountState[spec.Name()] = deviceMountUncertain |
| return volumetypes.NewUncertainProgressError("error mounting state") |
| } |
| } |
| |
| if spec.Name() == SuccessAndFailOnMountDeviceName { |
| _, ok := fv.DeviceMountState[spec.Name()] |
| if ok { |
| return fmt.Errorf("error mounting disk: %s", devicePath) |
| } |
| } |
| fv.DeviceMountState[spec.Name()] = deviceMounted |
| fv.MountDeviceCallCount++ |
| return nil |
| } |
| |
| func (fv *FakeVolume) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error { |
| return fv.mountDeviceInternal(spec, devicePath, deviceMountPath) |
| } |
| |
| func (fv *FakeVolume) GetMountDeviceCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.MountDeviceCallCount |
| } |
| |
| func (fv *FakeVolume) GetUnmountDeviceCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.UnmountDeviceCallCount |
| } |
| |
| func (fv *FakeVolume) Detach(volumeName string, nodeName types.NodeName) error { |
| fv.Lock() |
| defer fv.Unlock() |
| |
| node := string(nodeName) |
| volumeNodes, exist := fv.VolumesAttached[volumeName] |
| if !exist || !volumeNodes.Has(node) { |
| return fmt.Errorf("trying to detach volume %q that is not attached to the node %q", volumeName, node) |
| } |
| |
| fv.DetachCallCount++ |
| if nodeName == FailDetachNode { |
| return fmt.Errorf("fail to detach volume %q to node %q", volumeName, nodeName) |
| } |
| |
| volumeNodes.Delete(node) |
| if volumeNodes.Len() == 0 { |
| delete(fv.VolumesAttached, volumeName) |
| } |
| |
| return nil |
| } |
| |
| func (fv *FakeVolume) VolumesAreAttached(spec []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) { |
| fv.Lock() |
| defer fv.Unlock() |
| return nil, nil |
| } |
| |
| func (fv *FakeVolume) GetDetachCallCount() int { |
| fv.RLock() |
| defer fv.RUnlock() |
| return fv.DetachCallCount |
| } |
| |
| func (fv *FakeVolume) UnmountDevice(globalMountPath string) error { |
| fv.Lock() |
| defer fv.Unlock() |
| fv.UnmountDeviceCallCount++ |
| if fv.UnmountDeviceHook != nil { |
| return fv.UnmountDeviceHook(globalMountPath) |
| } |
| return nil |
| } |
| |
| type FakeDeleter struct { |
| path string |
| volume.MetricsNil |
| } |
| |
| func (fd *FakeDeleter) Delete() error { |
| // nil is success, else error |
| return nil |
| } |
| |
| func (fd *FakeDeleter) GetPath() string { |
| return fd.path |
| } |
| |
| type FakeProvisioner struct { |
| Options volume.VolumeOptions |
| Host volume.VolumeHost |
| ProvisionDelaySeconds int |
| } |
| |
| func (fc *FakeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) { |
| // Add provision failure hook |
| if fc.Options.Parameters != nil { |
| if _, ok := fc.Options.Parameters[ExpectProvisionFailureKey]; ok { |
| return nil, fmt.Errorf("expected error") |
| } |
| } |
| fullpath := fmt.Sprintf("/%s/hostpath_pv/%s", os.TempDir(), uuid.NewUUID()) |
| |
| pv := &v1.PersistentVolume{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: fc.Options.PVName, |
| Annotations: map[string]string{ |
| util.VolumeDynamicallyCreatedByKey: "fakeplugin-provisioner", |
| }, |
| }, |
| Spec: v1.PersistentVolumeSpec{ |
| PersistentVolumeReclaimPolicy: fc.Options.PersistentVolumeReclaimPolicy, |
| AccessModes: fc.Options.PVC.Spec.AccessModes, |
| Capacity: v1.ResourceList{ |
| v1.ResourceName(v1.ResourceStorage): fc.Options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)], |
| }, |
| PersistentVolumeSource: v1.PersistentVolumeSource{ |
| HostPath: &v1.HostPathVolumeSource{ |
| Path: fullpath, |
| }, |
| }, |
| }, |
| } |
| |
| if fc.ProvisionDelaySeconds > 0 { |
| time.Sleep(time.Duration(fc.ProvisionDelaySeconds) * time.Second) |
| } |
| |
| return pv, nil |
| } |
| |
| var _ volumepathhandler.BlockVolumePathHandler = &FakeVolumePathHandler{} |
| |
| // NewDeviceHandler Create a new IoHandler implementation |
| func NewBlockVolumePathHandler() volumepathhandler.BlockVolumePathHandler { |
| return &FakeVolumePathHandler{} |
| } |
| |
| type FakeVolumePathHandler struct { |
| sync.RWMutex |
| } |
| |
| func (fv *FakeVolumePathHandler) MapDevice(devicePath string, mapDir string, linkName string, bindMount bool) error { |
| // nil is success, else error |
| return nil |
| } |
| |
| func (fv *FakeVolumePathHandler) UnmapDevice(mapDir string, linkName string, bindMount bool) error { |
| // nil is success, else error |
| return nil |
| } |
| |
| func (fv *FakeVolumePathHandler) RemoveMapPath(mapPath string) error { |
| // nil is success, else error |
| return nil |
| } |
| |
| func (fv *FakeVolumePathHandler) IsSymlinkExist(mapPath string) (bool, error) { |
| // nil is success, else error |
| return true, nil |
| } |
| |
| func (fv *FakeVolumePathHandler) IsDeviceBindMountExist(mapPath string) (bool, error) { |
| // nil is success, else error |
| return true, nil |
| } |
| |
| func (fv *FakeVolumePathHandler) GetDeviceBindMountRefs(devPath string, mapPath string) ([]string, error) { |
| // nil is success, else error |
| return []string{}, nil |
| } |
| |
| func (fv *FakeVolumePathHandler) FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error) { |
| // nil is success, else error |
| return "", nil |
| } |
| |
| func (fv *FakeVolumePathHandler) AttachFileDevice(path string) (string, error) { |
| // nil is success, else error |
| return "", nil |
| } |
| |
| func (fv *FakeVolumePathHandler) DetachFileDevice(path string) error { |
| // nil is success, else error |
| return nil |
| } |
| |
| func (fv *FakeVolumePathHandler) GetLoopDevice(path string) (string, error) { |
| // nil is success, else error |
| return "/dev/loop1", nil |
| } |
| |
| // FindEmptyDirectoryUsageOnTmpfs finds the expected usage of an empty directory existing on |
| // a tmpfs filesystem on this system. |
| func FindEmptyDirectoryUsageOnTmpfs() (*resource.Quantity, error) { |
| // The command below does not exist on Windows. Additionally, empty folders have size 0 on Windows. |
| if goruntime.GOOS == "windows" { |
| used, err := resource.ParseQuantity("0") |
| return &used, err |
| } |
| tmpDir, err := utiltesting.MkTmpdir("metrics_du_test") |
| if err != nil { |
| return nil, err |
| } |
| defer os.RemoveAll(tmpDir) |
| out, err := exec.New().Command("nice", "-n", "19", "du", "-x", "-s", "-B", "1", tmpDir).CombinedOutput() |
| if err != nil { |
| return nil, fmt.Errorf("failed command 'du' on %s with error %v", tmpDir, err) |
| } |
| used, err := resource.ParseQuantity(strings.Fields(string(out))[0]) |
| if err != nil { |
| return nil, fmt.Errorf("failed to parse 'du' output %s due to error %v", out, err) |
| } |
| used.Format = resource.BinarySI |
| return &used, nil |
| } |
| |
| // VerifyAttachCallCount ensures that at least one of the Attachers for this |
| // plugin has the expectedAttachCallCount number of calls. Otherwise it returns |
| // an error. |
| func VerifyAttachCallCount( |
| expectedAttachCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, attacher := range fakeVolumePlugin.GetAttachers() { |
| actualCallCount := attacher.GetAttachCallCount() |
| if actualCallCount >= expectedAttachCallCount { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No attachers have expected AttachCallCount. Expected: <%v>.", |
| expectedAttachCallCount) |
| } |
| |
| // VerifyZeroAttachCalls ensures that all of the Attachers for this plugin have |
| // a zero AttachCallCount. Otherwise it returns an error. |
| func VerifyZeroAttachCalls(fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, attacher := range fakeVolumePlugin.GetAttachers() { |
| actualCallCount := attacher.GetAttachCallCount() |
| if actualCallCount != 0 { |
| return fmt.Errorf( |
| "At least one attacher has non-zero AttachCallCount: <%v>.", |
| actualCallCount) |
| } |
| } |
| |
| return nil |
| } |
| |
| // VerifyWaitForAttachCallCount ensures that at least one of the Mounters for |
| // this plugin has the expectedWaitForAttachCallCount number of calls. Otherwise |
| // it returns an error. |
| func VerifyWaitForAttachCallCount( |
| expectedWaitForAttachCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, attacher := range fakeVolumePlugin.GetAttachers() { |
| actualCallCount := attacher.GetWaitForAttachCallCount() |
| if actualCallCount >= expectedWaitForAttachCallCount { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No Attachers have expected WaitForAttachCallCount. Expected: <%v>.", |
| expectedWaitForAttachCallCount) |
| } |
| |
| // VerifyZeroWaitForAttachCallCount ensures that all Attachers for this plugin |
| // have a zero WaitForAttachCallCount. Otherwise it returns an error. |
| func VerifyZeroWaitForAttachCallCount(fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, attacher := range fakeVolumePlugin.GetAttachers() { |
| actualCallCount := attacher.GetWaitForAttachCallCount() |
| if actualCallCount != 0 { |
| return fmt.Errorf( |
| "At least one attacher has non-zero WaitForAttachCallCount: <%v>.", |
| actualCallCount) |
| } |
| } |
| |
| return nil |
| } |
| |
| // VerifyMountDeviceCallCount ensures that at least one of the Mounters for |
| // this plugin has the expectedMountDeviceCallCount number of calls. Otherwise |
| // it returns an error. |
| func VerifyMountDeviceCallCount( |
| expectedMountDeviceCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, attacher := range fakeVolumePlugin.GetAttachers() { |
| actualCallCount := attacher.GetMountDeviceCallCount() |
| if actualCallCount >= expectedMountDeviceCallCount { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No Attachers have expected MountDeviceCallCount. Expected: <%v>.", |
| expectedMountDeviceCallCount) |
| } |
| |
| func VerifyUnmountDeviceCallCount(expectedCallCount int, fakeVolumePlugin *FakeVolumePlugin) error { |
| detachers := fakeVolumePlugin.GetDetachers() |
| if len(detachers) == 0 && (expectedCallCount == 0) { |
| return nil |
| } |
| actualCallCount := 0 |
| for _, detacher := range detachers { |
| actualCallCount = detacher.GetUnmountDeviceCallCount() |
| if expectedCallCount == 0 && actualCallCount == expectedCallCount { |
| return nil |
| } |
| |
| if (expectedCallCount > 0) && (actualCallCount >= expectedCallCount) { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "Expected DeviceUnmount Call %d, got %d", |
| expectedCallCount, actualCallCount) |
| } |
| |
| // VerifyZeroMountDeviceCallCount ensures that all Attachers for this plugin |
| // have a zero MountDeviceCallCount. Otherwise it returns an error. |
| func VerifyZeroMountDeviceCallCount(fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, attacher := range fakeVolumePlugin.GetAttachers() { |
| actualCallCount := attacher.GetMountDeviceCallCount() |
| if actualCallCount != 0 { |
| return fmt.Errorf( |
| "At least one attacher has non-zero MountDeviceCallCount: <%v>.", |
| actualCallCount) |
| } |
| } |
| |
| return nil |
| } |
| |
| // VerifySetUpCallCount ensures that at least one of the Mounters for this |
| // plugin has the expectedSetUpCallCount number of calls. Otherwise it returns |
| // an error. |
| func VerifySetUpCallCount( |
| expectedSetUpCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, mounter := range fakeVolumePlugin.GetMounters() { |
| actualCallCount := mounter.GetSetUpCallCount() |
| if actualCallCount >= expectedSetUpCallCount { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No Mounters have expected SetUpCallCount. Expected: <%v>.", |
| expectedSetUpCallCount) |
| } |
| |
| // VerifyZeroSetUpCallCount ensures that all Mounters for this plugin have a |
| // zero SetUpCallCount. Otherwise it returns an error. |
| func VerifyZeroSetUpCallCount(fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, mounter := range fakeVolumePlugin.GetMounters() { |
| actualCallCount := mounter.GetSetUpCallCount() |
| if actualCallCount != 0 { |
| return fmt.Errorf( |
| "At least one mounter has non-zero SetUpCallCount: <%v>.", |
| actualCallCount) |
| } |
| } |
| |
| return nil |
| } |
| |
| // VerifyTearDownCallCount ensures that at least one of the Unounters for this |
| // plugin has the expectedTearDownCallCount number of calls. Otherwise it |
| // returns an error. |
| func VerifyTearDownCallCount( |
| expectedTearDownCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| unmounters := fakeVolumePlugin.GetUnmounters() |
| if len(unmounters) == 0 && (expectedTearDownCallCount == 0) { |
| return nil |
| } |
| |
| for _, unmounter := range unmounters { |
| actualCallCount := unmounter.GetTearDownCallCount() |
| if expectedTearDownCallCount == 0 && actualCallCount == expectedTearDownCallCount { |
| return nil |
| } |
| |
| if (expectedTearDownCallCount > 0) && (actualCallCount >= expectedTearDownCallCount) { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No Unmounters have expected SetUpCallCount. Expected: <%v>.", |
| expectedTearDownCallCount) |
| } |
| |
| // VerifyZeroTearDownCallCount ensures that all Mounters for this plugin have a |
| // zero TearDownCallCount. Otherwise it returns an error. |
| func VerifyZeroTearDownCallCount(fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, mounter := range fakeVolumePlugin.GetMounters() { |
| actualCallCount := mounter.GetTearDownCallCount() |
| if actualCallCount != 0 { |
| return fmt.Errorf( |
| "At least one mounter has non-zero TearDownCallCount: <%v>.", |
| actualCallCount) |
| } |
| } |
| |
| return nil |
| } |
| |
| // VerifyDetachCallCount ensures that at least one of the Attachers for this |
| // plugin has the expectedDetachCallCount number of calls. Otherwise it returns |
| // an error. |
| func VerifyDetachCallCount( |
| expectedDetachCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, detacher := range fakeVolumePlugin.GetDetachers() { |
| actualCallCount := detacher.GetDetachCallCount() |
| if actualCallCount == expectedDetachCallCount { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No Detachers have expected DetachCallCount. Expected: <%v>.", |
| expectedDetachCallCount) |
| } |
| |
| // VerifyZeroDetachCallCount ensures that all Detachers for this plugin have a |
| // zero DetachCallCount. Otherwise it returns an error. |
| func VerifyZeroDetachCallCount(fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, detacher := range fakeVolumePlugin.GetDetachers() { |
| actualCallCount := detacher.GetDetachCallCount() |
| if actualCallCount != 0 { |
| return fmt.Errorf( |
| "At least one detacher has non-zero DetachCallCount: <%v>.", |
| actualCallCount) |
| } |
| } |
| |
| return nil |
| } |
| |
| // VerifyTearDownDeviceCallCount ensures that at least one of the Unmappers for this |
| // plugin has the expectedTearDownDeviceCallCount number of calls. Otherwise it |
| // returns an error. |
| func VerifyTearDownDeviceCallCount( |
| expectedTearDownDeviceCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, unmapper := range fakeVolumePlugin.GetBlockVolumeUnmapper() { |
| actualCallCount := unmapper.GetTearDownDeviceCallCount() |
| if actualCallCount >= expectedTearDownDeviceCallCount { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No Unmapper have expected TearDownDeviceCallCount. Expected: <%v>.", |
| expectedTearDownDeviceCallCount) |
| } |
| |
| // VerifyZeroTearDownDeviceCallCount ensures that all Mappers for this plugin have a |
| // zero TearDownDeviceCallCount. Otherwise it returns an error. |
| func VerifyZeroTearDownDeviceCallCount(fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, unmapper := range fakeVolumePlugin.GetBlockVolumeUnmapper() { |
| actualCallCount := unmapper.GetTearDownDeviceCallCount() |
| if actualCallCount != 0 { |
| return fmt.Errorf( |
| "At least one unmapper has non-zero TearDownDeviceCallCount: <%v>.", |
| actualCallCount) |
| } |
| } |
| |
| return nil |
| } |
| |
| // VerifyUnmapPodDeviceCallCount ensures that at least one of the Unmappers for this |
| // plugin has the expected number of UnmapPodDevice calls. Otherwise it |
| // returns an error. |
| func VerifyUnmapPodDeviceCallCount( |
| expectedUnmapPodDeviceCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, unmapper := range fakeVolumePlugin.GetBlockVolumeUnmapper() { |
| actualCallCount := unmapper.GetUnmapPodDeviceCallCount() |
| if actualCallCount >= expectedUnmapPodDeviceCallCount { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No Unmapper have expected UnmapPodDeviceCallCount. Expected: <%v>.", |
| expectedUnmapPodDeviceCallCount) |
| } |
| |
| // VerifyZeroUnmapPodDeviceCallCount ensures that all Mappers for this plugin have a |
| // zero UnmapPodDevice calls. Otherwise it returns an error. |
| func VerifyZeroUnmapPodDeviceCallCount(fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, unmapper := range fakeVolumePlugin.GetBlockVolumeUnmapper() { |
| actualCallCount := unmapper.GetUnmapPodDeviceCallCount() |
| if actualCallCount != 0 { |
| return fmt.Errorf( |
| "At least one unmapper has non-zero UnmapPodDeviceCallCount: <%v>.", |
| actualCallCount) |
| } |
| } |
| |
| return nil |
| } |
| |
| // VerifyGetGlobalMapPathCallCount ensures that at least one of the Mappers for this |
| // plugin has the expectedGlobalMapPathCallCount number of calls. Otherwise it returns |
| // an error. |
| func VerifyGetGlobalMapPathCallCount( |
| expectedGlobalMapPathCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, mapper := range fakeVolumePlugin.GetBlockVolumeMapper() { |
| actualCallCount := mapper.GetGlobalMapPathCallCount() |
| if actualCallCount == expectedGlobalMapPathCallCount { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No Mappers have expected GetGlobalMapPathCallCount. Expected: <%v>.", |
| expectedGlobalMapPathCallCount) |
| } |
| |
| // VerifyGetPodDeviceMapPathCallCount ensures that at least one of the Mappers for this |
| // plugin has the expectedPodDeviceMapPathCallCount number of calls. Otherwise it returns |
| // an error. |
| func VerifyGetPodDeviceMapPathCallCount( |
| expectedPodDeviceMapPathCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, mapper := range fakeVolumePlugin.GetBlockVolumeMapper() { |
| actualCallCount := mapper.GetPodDeviceMapPathCallCount() |
| if actualCallCount == expectedPodDeviceMapPathCallCount { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No Mappers have expected GetPodDeviceMapPathCallCount. Expected: <%v>.", |
| expectedPodDeviceMapPathCallCount) |
| } |
| |
| // VerifyGetMapPodDeviceCallCount ensures that at least one of the Mappers for this |
| // plugin has the expectedMapPodDeviceCallCount number of calls. Otherwise it |
| // returns an error. |
| func VerifyGetMapPodDeviceCallCount( |
| expectedMapPodDeviceCallCount int, |
| fakeVolumePlugin *FakeVolumePlugin) error { |
| for _, mapper := range fakeVolumePlugin.GetBlockVolumeMapper() { |
| actualCallCount := mapper.GetMapPodDeviceCallCount() |
| if actualCallCount >= expectedMapPodDeviceCallCount { |
| return nil |
| } |
| } |
| |
| return fmt.Errorf( |
| "No Mapper have expected MapPodDeviceCallCount. Expected: <%v>.", |
| expectedMapPodDeviceCallCount) |
| } |
| |
| // GetTestVolumePluginMgr creates, initializes, and returns a test volume plugin |
| // manager and fake volume plugin using a fake volume host. |
| func GetTestVolumePluginMgr(t *testing.T) (*volume.VolumePluginMgr, *FakeVolumePlugin) { |
| plugins := ProbeVolumePlugins(volume.VolumeConfig{}) |
| v := NewFakeVolumeHost( |
| t, |
| "", /* rootDir */ |
| nil, /* kubeClient */ |
| plugins, /* plugins */ |
| ) |
| return v.GetPluginMgr(), plugins[0].(*FakeVolumePlugin) |
| } |
| |
| func GetTestKubeletVolumePluginMgr(t *testing.T) (*volume.VolumePluginMgr, *FakeVolumePlugin) { |
| plugins := ProbeVolumePlugins(volume.VolumeConfig{}) |
| v := NewFakeKubeletVolumeHost( |
| t, |
| "", /* rootDir */ |
| nil, /* kubeClient */ |
| plugins, /* plugins */ |
| ) |
| return v.GetPluginMgr(), plugins[0].(*FakeVolumePlugin) |
| } |
| |
| func GetTestKubeletVolumePluginMgrWithNode(t *testing.T, node *v1.Node) (*volume.VolumePluginMgr, *FakeVolumePlugin) { |
| plugins := ProbeVolumePlugins(volume.VolumeConfig{}) |
| v := NewFakeKubeletVolumeHost( |
| t, |
| "", /* rootDir */ |
| nil, /* kubeClient */ |
| plugins, /* plugins */ |
| ) |
| v.WithNode(node) |
| |
| return v.GetPluginMgr(), plugins[0].(*FakeVolumePlugin) |
| } |
| |
| func GetTestKubeletVolumePluginMgrWithNodeAndRoot(t *testing.T, node *v1.Node, rootDir string) (*volume.VolumePluginMgr, *FakeVolumePlugin) { |
| plugins := ProbeVolumePlugins(volume.VolumeConfig{}) |
| v := NewFakeKubeletVolumeHost( |
| t, |
| rootDir, /* rootDir */ |
| nil, /* kubeClient */ |
| plugins, /* plugins */ |
| ) |
| v.WithNode(node) |
| |
| return v.GetPluginMgr(), plugins[0].(*FakeVolumePlugin) |
| } |
| |
| // CreateTestPVC returns a provisionable PVC for tests |
| func CreateTestPVC(capacity string, accessModes []v1.PersistentVolumeAccessMode) *v1.PersistentVolumeClaim { |
| claim := v1.PersistentVolumeClaim{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "dummy", |
| Namespace: "default", |
| }, |
| Spec: v1.PersistentVolumeClaimSpec{ |
| AccessModes: accessModes, |
| Resources: v1.VolumeResourceRequirements{ |
| Requests: v1.ResourceList{ |
| v1.ResourceName(v1.ResourceStorage): resource.MustParse(capacity), |
| }, |
| }, |
| }, |
| } |
| return &claim |
| } |
| |
| func MetricsEqualIgnoreTimestamp(a *volume.Metrics, b *volume.Metrics) bool { |
| available := a.Available == b.Available |
| capacity := a.Capacity == b.Capacity |
| used := a.Used == b.Used |
| inodes := a.Inodes == b.Inodes |
| inodesFree := a.InodesFree == b.InodesFree |
| inodesUsed := a.InodesUsed == b.InodesUsed |
| return available && capacity && used && inodes && inodesFree && inodesUsed |
| } |
| |
| func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { |
| for _, m := range modes { |
| if m == mode { |
| return true |
| } |
| } |
| return false |
| } |