mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 10:53:34 +00:00
vendor updates
This commit is contained in:
5
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/BUILD
generated
vendored
5
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/BUILD
generated
vendored
@ -23,7 +23,7 @@ go_library(
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//pkg/volume/util/operationexecutor:go_default_library",
|
||||
"//pkg/volume/util/volumehelper:go_default_library",
|
||||
"//pkg/volume/util/volumepathhandler:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
@ -42,8 +42,7 @@ go_library(
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["attach_detach_controller_test.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach",
|
||||
library = ":go_default_library",
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/volume/attachdetach/cache:go_default_library",
|
||||
|
14
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/attach_detach_controller.go
generated
vendored
14
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/attach_detach_controller.go
generated
vendored
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package volume implements a controller to manage volume attach and detach
|
||||
// Package attachdetach implements a controller to manage volume attach and detach
|
||||
// operations.
|
||||
package attachdetach
|
||||
|
||||
@ -47,7 +47,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumeutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
|
||||
)
|
||||
|
||||
// TimerConfig contains configuration of internal attach/detach timers and
|
||||
@ -137,7 +137,7 @@ func NewAttachDetachController(
|
||||
eventBroadcaster.StartLogging(glog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")})
|
||||
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "attachdetach-controller"})
|
||||
blkutil := volumeutil.NewBlockVolumePathHandler()
|
||||
blkutil := volumepathhandler.NewBlockVolumePathHandler()
|
||||
|
||||
adc.desiredStateOfWorld = cache.NewDesiredStateOfWorld(&adc.volumePluginMgr)
|
||||
adc.actualStateOfWorld = cache.NewActualStateOfWorld(&adc.volumePluginMgr)
|
||||
@ -335,7 +335,7 @@ func (adc *attachDetachController) populateDesiredStateOfWorld() error {
|
||||
}
|
||||
for _, pod := range pods {
|
||||
podToAdd := pod
|
||||
adc.podAdd(&podToAdd)
|
||||
adc.podAdd(podToAdd)
|
||||
for _, podVolume := range podToAdd.Spec.Volumes {
|
||||
// The volume specs present in the ActualStateOfWorld are nil, let's replace those
|
||||
// with the correct ones found on pods. The present in the ASW with no corresponding
|
||||
@ -361,7 +361,7 @@ func (adc *attachDetachController) populateDesiredStateOfWorld() error {
|
||||
err)
|
||||
continue
|
||||
}
|
||||
volumeName, err := volumehelper.GetUniqueVolumeNameFromSpec(plugin, volumeSpec)
|
||||
volumeName, err := volumeutil.GetUniqueVolumeNameFromSpec(plugin, volumeSpec)
|
||||
if err != nil {
|
||||
glog.Errorf(
|
||||
"Failed to find unique name for volume %q, pod %q/%q: %v",
|
||||
@ -587,10 +587,10 @@ func (adc *attachDetachController) GetExec(pluginName string) mount.Exec {
|
||||
}
|
||||
|
||||
func (adc *attachDetachController) addNodeToDswp(node *v1.Node, nodeName types.NodeName) {
|
||||
if _, exists := node.Annotations[volumehelper.ControllerManagedAttachAnnotation]; exists {
|
||||
if _, exists := node.Annotations[volumeutil.ControllerManagedAttachAnnotation]; exists {
|
||||
keepTerminatedPodVolumes := false
|
||||
|
||||
if t, ok := node.Annotations[volumehelper.KeepTerminatedPodVolumesAnnotation]; ok {
|
||||
if t, ok := node.Annotations[volumeutil.KeepTerminatedPodVolumesAnnotation]; ok {
|
||||
keepTerminatedPodVolumes = (t == "true")
|
||||
}
|
||||
|
||||
|
5
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache/BUILD
generated
vendored
5
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache/BUILD
generated
vendored
@ -15,9 +15,9 @@ go_library(
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache",
|
||||
deps = [
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//pkg/volume/util/operationexecutor:go_default_library",
|
||||
"//pkg/volume/util/types:go_default_library",
|
||||
"//pkg/volume/util/volumehelper:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
@ -30,8 +30,7 @@ go_test(
|
||||
"actual_state_of_world_test.go",
|
||||
"desired_state_of_world_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache",
|
||||
library = ":go_default_library",
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/controller/volume/attachdetach/testing:go_default_library",
|
||||
"//pkg/volume/testing:go_default_library",
|
||||
|
@ -31,8 +31,8 @@ import (
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
)
|
||||
|
||||
// ActualStateOfWorld defines a set of thread-safe operations supported on
|
||||
@ -73,7 +73,7 @@ type ActualStateOfWorld interface {
|
||||
// SetNodeStatusUpdateNeeded sets statusUpdateNeeded for the specified
|
||||
// node to true indicating the AttachedVolume field in the Node's Status
|
||||
// object needs to be updated by the node updater again.
|
||||
// If the specifed node does not exist in the nodesToUpdateStatusFor list,
|
||||
// If the specified node does not exist in the nodesToUpdateStatusFor list,
|
||||
// log the error and return
|
||||
SetNodeStatusUpdateNeeded(nodeName types.NodeName)
|
||||
|
||||
@ -131,8 +131,8 @@ type ActualStateOfWorld interface {
|
||||
type AttachedVolume struct {
|
||||
operationexecutor.AttachedVolume
|
||||
|
||||
// MountedByNode indicates that this volume has been been mounted by the
|
||||
// node and is unsafe to detach.
|
||||
// MountedByNode indicates that this volume has been mounted by the node and
|
||||
// is unsafe to detach.
|
||||
// The value is set and unset by SetVolumeMountedByNode(...).
|
||||
MountedByNode bool
|
||||
|
||||
@ -275,7 +275,7 @@ func (asw *actualStateOfWorld) AddVolumeNode(
|
||||
err)
|
||||
}
|
||||
|
||||
volumeName, err = volumehelper.GetUniqueVolumeNameFromSpec(
|
||||
volumeName, err = util.GetUniqueVolumeNameFromSpec(
|
||||
attachableVolumePlugin, volumeSpec)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
@ -467,13 +467,13 @@ func (asw *actualStateOfWorld) addVolumeToReportAsAttached(
|
||||
|
||||
// Update the flag statusUpdateNeeded to indicate whether node status is already updated or
|
||||
// needs to be updated again by the node status updater.
|
||||
// If the specifed node does not exist in the nodesToUpdateStatusFor list, log the error and return
|
||||
// If the specified node does not exist in the nodesToUpdateStatusFor list, log the error and return
|
||||
// This is an internal function and caller should acquire and release the lock
|
||||
func (asw *actualStateOfWorld) updateNodeStatusUpdateNeeded(nodeName types.NodeName, needed bool) error {
|
||||
nodeToUpdate, nodeToUpdateExists := asw.nodesToUpdateStatusFor[nodeName]
|
||||
if !nodeToUpdateExists {
|
||||
// should not happen
|
||||
errMsg := fmt.Sprintf("Failed to set statusUpdateNeeded to needed %t because nodeName=%q does not exist",
|
||||
errMsg := fmt.Sprintf("Failed to set statusUpdateNeeded to needed %t, because nodeName=%q does not exist",
|
||||
needed, nodeName)
|
||||
return fmt.Errorf(errMsg)
|
||||
}
|
||||
@ -488,7 +488,7 @@ func (asw *actualStateOfWorld) SetNodeStatusUpdateNeeded(nodeName types.NodeName
|
||||
asw.Lock()
|
||||
defer asw.Unlock()
|
||||
if err := asw.updateNodeStatusUpdateNeeded(nodeName, true); err != nil {
|
||||
glog.Errorf("Failed to update statusUpdateNeeded field in actual state of world: %v", err)
|
||||
glog.Warningf("Failed to update statusUpdateNeeded field in actual state of world: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,9 @@ import (
|
||||
"k8s.io/api/core/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
|
||||
"k8s.io/kubernetes/pkg/volume/util/types"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
)
|
||||
|
||||
// DesiredStateOfWorld defines a set of thread-safe operations supported on
|
||||
@ -40,14 +40,14 @@ import (
|
||||
// should be attached to the specified node, and pods are the pods that
|
||||
// reference the volume and are scheduled to that node.
|
||||
// Note: This is distinct from the DesiredStateOfWorld implemented by the
|
||||
// kubelet volume manager. The both keep track of different objects. This
|
||||
// kubelet volume manager. They both keep track of different objects. This
|
||||
// contains attach/detach controller specific state.
|
||||
type DesiredStateOfWorld interface {
|
||||
// AddNode adds the given node to the list of nodes managed by the attach/
|
||||
// detach controller.
|
||||
// If the node already exists this is a no-op.
|
||||
// keepTerminatedPodVolumes is a property of the node that determines
|
||||
// if for terminated pods volumes should be mounted and attached.
|
||||
// if volumes should be mounted and attached for terminated pods.
|
||||
AddNode(nodeName k8stypes.NodeName, keepTerminatedPodVolumes bool)
|
||||
|
||||
// AddPod adds the given pod to the list of pods that reference the
|
||||
@ -105,6 +105,10 @@ type DesiredStateOfWorld interface {
|
||||
// Mark multiattach error as reported to prevent spamming multiple
|
||||
// events for same error
|
||||
SetMultiAttachError(v1.UniqueVolumeName, k8stypes.NodeName)
|
||||
|
||||
// GetPodsOnNodes returns list of pods ("namespace/name") that require
|
||||
// given volume on given nodes.
|
||||
GetVolumePodsOnNodes(nodes []k8stypes.NodeName, volumeName v1.UniqueVolumeName) []*v1.Pod
|
||||
}
|
||||
|
||||
// VolumeToAttach represents a volume that should be attached to a node.
|
||||
@ -152,7 +156,7 @@ type nodeManaged struct {
|
||||
|
||||
// volumesToAttach is a map containing the set of volumes that should be
|
||||
// attached to this node. The key in the map is the name of the volume and
|
||||
// the value is a pod object containing more information about the volume.
|
||||
// the value is a volumeToAttach object containing more information about the volume.
|
||||
volumesToAttach map[v1.UniqueVolumeName]volumeToAttach
|
||||
|
||||
// keepTerminatedPodVolumes determines if for terminated pods(on this node) - volumes
|
||||
@ -160,10 +164,10 @@ type nodeManaged struct {
|
||||
keepTerminatedPodVolumes bool
|
||||
}
|
||||
|
||||
// The volume object represents a volume that should be attached to a node.
|
||||
// The volumeToAttach object represents a volume that should be attached to a node.
|
||||
type volumeToAttach struct {
|
||||
// multiAttachErrorReported indicates whether the multi-attach error has been reported for the given volume.
|
||||
// It is used to to prevent reporting the error from being reported more than once for a given volume.
|
||||
// It is used to prevent reporting the error from being reported more than once for a given volume.
|
||||
multiAttachErrorReported bool
|
||||
|
||||
// volumeName contains the unique identifier for this volume.
|
||||
@ -227,11 +231,12 @@ func (dsw *desiredStateOfWorld) AddPod(
|
||||
err)
|
||||
}
|
||||
|
||||
volumeName, err := volumehelper.GetUniqueVolumeNameFromSpec(
|
||||
volumeName, err := util.GetUniqueVolumeNameFromSpec(
|
||||
attachableVolumePlugin, volumeSpec)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"failed to GetUniqueVolumeNameFromSpec for volumeSpec %q err=%v",
|
||||
"failed to get UniqueVolumeName from volumeSpec for plugin=%q and volume=%q err=%v",
|
||||
attachableVolumePlugin.GetPluginName(),
|
||||
volumeSpec.Name(),
|
||||
err)
|
||||
}
|
||||
@ -412,3 +417,24 @@ func (dsw *desiredStateOfWorld) GetPodToAdd() map[types.UniquePodName]PodToAdd {
|
||||
}
|
||||
return pods
|
||||
}
|
||||
|
||||
func (dsw *desiredStateOfWorld) GetVolumePodsOnNodes(nodes []k8stypes.NodeName, volumeName v1.UniqueVolumeName) []*v1.Pod {
|
||||
dsw.RLock()
|
||||
defer dsw.RUnlock()
|
||||
|
||||
pods := []*v1.Pod{}
|
||||
for _, nodeName := range nodes {
|
||||
node, ok := dsw.nodesManaged[nodeName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
volume, ok := node.volumesToAttach[volumeName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, pod := range volume.scheduledPods {
|
||||
pods = append(pods, pod.podObj)
|
||||
}
|
||||
}
|
||||
return pods
|
||||
}
|
||||
|
@ -1032,3 +1032,49 @@ func verifyVolumeToAttach(
|
||||
|
||||
t.Fatalf("volumesToAttach (%v) should contain %q/%q. It does not.", volumesToAttach, expectedVolumeName, expectedNodeName)
|
||||
}
|
||||
|
||||
func Test_GetPodsOnNodes(t *testing.T) {
|
||||
volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t)
|
||||
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||
|
||||
// 2 nodes, each with one pod with a different volume
|
||||
node1Name := k8stypes.NodeName("node1-name")
|
||||
pod1Name := "pod1-uid"
|
||||
volume1Name := v1.UniqueVolumeName("volume1-name")
|
||||
volume1Spec := controllervolumetesting.GetTestVolumeSpec(string(volume1Name), volume1Name)
|
||||
dsw.AddNode(node1Name, false /*keepTerminatedPodVolumes*/)
|
||||
generatedVolume1Name, podAddErr := dsw.AddPod(types.UniquePodName(pod1Name), controllervolumetesting.NewPod(pod1Name, pod1Name), volume1Spec, node1Name)
|
||||
if podAddErr != nil {
|
||||
t.Fatalf(
|
||||
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||
pod1Name,
|
||||
podAddErr)
|
||||
}
|
||||
node2Name := k8stypes.NodeName("node2-name")
|
||||
pod2Name := "pod2-uid"
|
||||
volume2Name := v1.UniqueVolumeName("volume2-name")
|
||||
volume2Spec := controllervolumetesting.GetTestVolumeSpec(string(volume2Name), volume2Name)
|
||||
dsw.AddNode(node2Name, false /*keepTerminatedPodVolumes*/)
|
||||
_, podAddErr = dsw.AddPod(types.UniquePodName(pod2Name), controllervolumetesting.NewPod(pod2Name, pod2Name), volume2Spec, node2Name)
|
||||
if podAddErr != nil {
|
||||
t.Fatalf(
|
||||
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||
pod2Name,
|
||||
podAddErr)
|
||||
}
|
||||
|
||||
// Third node without any pod
|
||||
node3Name := k8stypes.NodeName("node3-name")
|
||||
dsw.AddNode(node3Name, false /*keepTerminatedPodVolumes*/)
|
||||
|
||||
// Act
|
||||
pods := dsw.GetVolumePodsOnNodes([]k8stypes.NodeName{node1Name, node2Name, node3Name, "non-existing-node"}, generatedVolume1Name)
|
||||
|
||||
// Assert
|
||||
if len(pods) != 1 {
|
||||
t.Fatalf("Expected 1 pod, got %d", len(pods))
|
||||
}
|
||||
if pods[0].Name != pod1Name {
|
||||
t.Errorf("Expected pod %s/%s, got %s", pod1Name, pod1Name, pods[0].Name)
|
||||
}
|
||||
}
|
||||
|
7
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/populator/BUILD
generated
vendored
7
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/populator/BUILD
generated
vendored
@ -14,7 +14,7 @@ go_library(
|
||||
"//pkg/controller/volume/attachdetach/cache:go_default_library",
|
||||
"//pkg/controller/volume/attachdetach/util:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util/volumehelper:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
@ -41,13 +41,12 @@ filegroup(
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["desired_state_of_world_populator_test.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach/populator",
|
||||
library = ":go_default_library",
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/volume/attachdetach/cache:go_default_library",
|
||||
"//pkg/volume/testing:go_default_library",
|
||||
"//pkg/volume/util/volumehelper:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
|
@ -33,7 +33,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/attachdetach/util"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
volutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
// DesiredStateOfWorldPopulator periodically verifies that the pods in the
|
||||
@ -133,7 +133,7 @@ func (dswp *desiredStateOfWorldPopulator) findAndRemoveDeletedPods() {
|
||||
true /* default volume action */)
|
||||
|
||||
if volumeActionFlag {
|
||||
informerPodUID := volumehelper.GetUniquePodName(informerPod)
|
||||
informerPodUID := volutil.GetUniquePodName(informerPod)
|
||||
// Check whether the unique identifier of the pod from dsw matches the one retrieved from pod informer
|
||||
if informerPodUID == dswPodUID {
|
||||
glog.V(10).Infof("Verified pod %q (UID %q) from dsw exists in pod informer.", dswPodKey, dswPodUID)
|
||||
@ -142,7 +142,7 @@ func (dswp *desiredStateOfWorldPopulator) findAndRemoveDeletedPods() {
|
||||
}
|
||||
}
|
||||
|
||||
// the pod from dsw does not exist in pod informer, or it does not match the unique identifer retrieved
|
||||
// the pod from dsw does not exist in pod informer, or it does not match the unique identifier 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)
|
||||
@ -158,7 +158,7 @@ func (dswp *desiredStateOfWorldPopulator) findAndAddActivePods() {
|
||||
dswp.timeOfLastListPods = time.Now()
|
||||
|
||||
for _, pod := range pods {
|
||||
if volumehelper.IsPodTerminated(pod, pod.Status) {
|
||||
if volutil.IsPodTerminated(pod, pod.Status) {
|
||||
// Do not add volumes for terminated pods
|
||||
continue
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache"
|
||||
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
func TestFindAndAddActivePods_FindAndRemoveDeletedPods(t *testing.T) {
|
||||
@ -66,7 +66,7 @@ func TestFindAndAddActivePods_FindAndRemoveDeletedPods(t *testing.T) {
|
||||
|
||||
fakePodInformer.Informer().GetStore().Add(pod)
|
||||
|
||||
podName := volumehelper.GetUniquePodName(pod)
|
||||
podName := util.GetUniquePodName(pod)
|
||||
|
||||
generatedVolumeName := "fake-plugin/" + pod.Spec.Volumes[0].Name
|
||||
|
||||
|
5
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/reconciler/BUILD
generated
vendored
5
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/reconciler/BUILD
generated
vendored
@ -19,6 +19,7 @@ go_library(
|
||||
"//pkg/volume/util/operationexecutor:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
],
|
||||
@ -27,13 +28,13 @@ go_library(
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["reconciler_test.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach/reconciler",
|
||||
library = ":go_default_library",
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/volume/attachdetach/cache:go_default_library",
|
||||
"//pkg/controller/volume/attachdetach/statusupdater:go_default_library",
|
||||
"//pkg/controller/volume/attachdetach/testing:go_default_library",
|
||||
"//pkg/util/strings:go_default_library",
|
||||
"//pkg/volume/testing:go_default_library",
|
||||
"//pkg/volume/util/operationexecutor:go_default_library",
|
||||
"//pkg/volume/util/types:go_default_library",
|
||||
|
85
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/reconciler/reconciler.go
generated
vendored
85
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/reconciler/reconciler.go
generated
vendored
@ -21,10 +21,12 @@ package reconciler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache"
|
||||
@ -145,7 +147,7 @@ func (rc *reconciler) isMultiAttachForbidden(volumeSpec *volume.Spec) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Only if this volume is a persistent volume, we have reliable information on wether it's allowed or not to
|
||||
// Only if this volume is a persistent volume, we have reliable information on whether it's allowed or not to
|
||||
// multi-attach. We trust in the individual volume implementations to not allow unsupported access modes
|
||||
if volumeSpec.PersistentVolume != nil {
|
||||
// Check for persistent volume types which do not fail when trying to multi-attach
|
||||
@ -269,12 +271,8 @@ func (rc *reconciler) attachDesiredVolumes() {
|
||||
nodes := rc.actualStateOfWorld.GetNodesForVolume(volumeToAttach.VolumeName)
|
||||
if len(nodes) > 0 {
|
||||
if !volumeToAttach.MultiAttachErrorReported {
|
||||
simpleMsg, detailedMsg := volumeToAttach.GenerateMsg("Multi-Attach error", "Volume is already exclusively attached to one node and can't be attached to another")
|
||||
for _, pod := range volumeToAttach.ScheduledPods {
|
||||
rc.recorder.Eventf(pod, v1.EventTypeWarning, kevents.FailedAttachVolume, simpleMsg)
|
||||
}
|
||||
rc.reportMultiAttachError(volumeToAttach, nodes)
|
||||
rc.desiredStateOfWorld.SetMultiAttachError(volumeToAttach.VolumeName, volumeToAttach.NodeName)
|
||||
glog.Warningf(detailedMsg)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -292,5 +290,78 @@ func (rc *reconciler) attachDesiredVolumes() {
|
||||
glog.Errorf(volumeToAttach.GenerateErrorDetailed("attacherDetacher.AttachVolume failed to start", err).Error())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// reportMultiAttachError sends events and logs situation that a volume that
|
||||
// should be attached to a node is already attached to different node(s).
|
||||
func (rc *reconciler) reportMultiAttachError(volumeToAttach cache.VolumeToAttach, nodes []types.NodeName) {
|
||||
// Filter out the current node from list of nodes where the volume is
|
||||
// attached.
|
||||
// Some methods need []string, some other needs []NodeName, collect both.
|
||||
// In theory, these arrays should have always only one element - the
|
||||
// controller does not allow more than one attachment. But use array just
|
||||
// in case...
|
||||
otherNodes := []types.NodeName{}
|
||||
otherNodesStr := []string{}
|
||||
for _, node := range nodes {
|
||||
if node != volumeToAttach.NodeName {
|
||||
otherNodes = append(otherNodes, node)
|
||||
otherNodesStr = append(otherNodesStr, string(node))
|
||||
}
|
||||
}
|
||||
|
||||
// Get list of pods that use the volume on the other nodes.
|
||||
pods := rc.desiredStateOfWorld.GetVolumePodsOnNodes(otherNodes, volumeToAttach.VolumeName)
|
||||
|
||||
if len(pods) == 0 {
|
||||
// We did not find any pods that requests the volume. The pod must have been deleted already.
|
||||
simpleMsg, _ := volumeToAttach.GenerateMsg("Multi-Attach error", "Volume is already exclusively attached to one node and can't be attached to another")
|
||||
for _, pod := range volumeToAttach.ScheduledPods {
|
||||
rc.recorder.Eventf(pod, v1.EventTypeWarning, kevents.FailedAttachVolume, simpleMsg)
|
||||
}
|
||||
// Log detailed message to system admin
|
||||
nodeList := strings.Join(otherNodesStr, ", ")
|
||||
detailedMsg := volumeToAttach.GenerateMsgDetailed("Multi-Attach error", fmt.Sprintf("Volume is already exclusively attached to node %s and can't be attached to another", nodeList))
|
||||
glog.Warningf(detailedMsg)
|
||||
return
|
||||
}
|
||||
|
||||
// There are pods that require the volume and run on another node. Typically
|
||||
// it's user error, e.g. a ReplicaSet uses a PVC and has >1 replicas. Let
|
||||
// the user know what pods are blocking the volume.
|
||||
for _, scheduledPod := range volumeToAttach.ScheduledPods {
|
||||
// Each scheduledPod must get a custom message. They can run in
|
||||
// different namespaces and user of a namespace should not see names of
|
||||
// pods in other namespaces.
|
||||
localPodNames := []string{} // Names of pods in scheduledPods's namespace
|
||||
otherPods := 0 // Count of pods in other namespaces
|
||||
for _, pod := range pods {
|
||||
if pod.Namespace == scheduledPod.Namespace {
|
||||
localPodNames = append(localPodNames, pod.Name)
|
||||
} else {
|
||||
otherPods++
|
||||
}
|
||||
}
|
||||
|
||||
var msg string
|
||||
if len(localPodNames) > 0 {
|
||||
msg = fmt.Sprintf("Volume is already used by pod(s) %s", strings.Join(localPodNames, ", "))
|
||||
if otherPods > 0 {
|
||||
msg = fmt.Sprintf("%s and %d pod(s) in different namespaces", msg, otherPods)
|
||||
}
|
||||
} else {
|
||||
// No local pods, there are pods only in different namespaces.
|
||||
msg = fmt.Sprintf("Volume is already used by %d pod(s) in different namespaces", otherPods)
|
||||
}
|
||||
simpleMsg, _ := volumeToAttach.GenerateMsg("Multi-Attach error", msg)
|
||||
rc.recorder.Eventf(scheduledPod, v1.EventTypeWarning, kevents.FailedAttachVolume, simpleMsg)
|
||||
}
|
||||
|
||||
// Log all pods for system admin
|
||||
podNames := []string{}
|
||||
for _, pod := range pods {
|
||||
podNames = append(podNames, pod.Namespace+"/"+pod.Name)
|
||||
}
|
||||
detailedMsg := volumeToAttach.GenerateMsgDetailed("Multi-Attach error", fmt.Sprintf("Volume is already used by pods %s on node %s", strings.Join(podNames, ", "), strings.Join(otherNodesStr, ", ")))
|
||||
glog.Warningf(detailedMsg)
|
||||
}
|
||||
|
110
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/reconciler/reconciler_test.go
generated
vendored
110
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/reconciler/reconciler_test.go
generated
vendored
@ -29,6 +29,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/attachdetach/statusupdater"
|
||||
controllervolumetesting "k8s.io/kubernetes/pkg/controller/volume/attachdetach/testing"
|
||||
stringutil "k8s.io/kubernetes/pkg/util/strings"
|
||||
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
|
||||
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
|
||||
"k8s.io/kubernetes/pkg/volume/util/types"
|
||||
@ -531,6 +532,115 @@ func Test_Run_OneVolumeAttachAndDetachMultipleNodesWithReadWriteOnce(t *testing.
|
||||
waitForTotalAttachCallCount(t, 2 /* expectedAttachCallCount */, fakePlugin)
|
||||
}
|
||||
|
||||
func Test_ReportMultiAttachError(t *testing.T) {
|
||||
type nodeWithPods struct {
|
||||
name k8stypes.NodeName
|
||||
podNames []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
nodes []nodeWithPods
|
||||
expectedEvents []string
|
||||
}{
|
||||
{
|
||||
"no pods use the volume",
|
||||
[]nodeWithPods{
|
||||
{"node1", []string{"ns1/pod1"}},
|
||||
},
|
||||
[]string{"Warning FailedAttachVolume Multi-Attach error for volume \"volume-name\" Volume is already exclusively attached to one node and can't be attached to another"},
|
||||
},
|
||||
{
|
||||
"pods in the same namespace use the volume",
|
||||
[]nodeWithPods{
|
||||
{"node1", []string{"ns1/pod1"}},
|
||||
{"node2", []string{"ns1/pod2"}},
|
||||
},
|
||||
[]string{"Warning FailedAttachVolume Multi-Attach error for volume \"volume-name\" Volume is already used by pod(s) pod2"},
|
||||
},
|
||||
{
|
||||
"pods in anotother namespace use the volume",
|
||||
[]nodeWithPods{
|
||||
{"node1", []string{"ns1/pod1"}},
|
||||
{"node2", []string{"ns2/pod2"}},
|
||||
},
|
||||
[]string{"Warning FailedAttachVolume Multi-Attach error for volume \"volume-name\" Volume is already used by 1 pod(s) in different namespaces"},
|
||||
},
|
||||
{
|
||||
"pods both in the same and anotother namespace use the volume",
|
||||
[]nodeWithPods{
|
||||
{"node1", []string{"ns1/pod1"}},
|
||||
{"node2", []string{"ns2/pod2"}},
|
||||
{"node3", []string{"ns1/pod3"}},
|
||||
},
|
||||
[]string{"Warning FailedAttachVolume Multi-Attach error for volume \"volume-name\" Volume is already used by pod(s) pod3 and 1 pod(s) in different namespaces"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Arrange
|
||||
t.Logf("Test %q starting", test.name)
|
||||
volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t)
|
||||
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
|
||||
asw := cache.NewActualStateOfWorld(volumePluginMgr)
|
||||
fakeKubeClient := controllervolumetesting.CreateTestClient()
|
||||
fakeRecorder := record.NewFakeRecorder(100)
|
||||
fakeHandler := volumetesting.NewBlockVolumePathHandler()
|
||||
ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
|
||||
fakeKubeClient,
|
||||
volumePluginMgr,
|
||||
fakeRecorder,
|
||||
false, /* checkNodeCapabilitiesBeforeMount */
|
||||
fakeHandler))
|
||||
nsu := statusupdater.NewFakeNodeStatusUpdater(false /* returnError */)
|
||||
rc := NewReconciler(
|
||||
reconcilerLoopPeriod, maxWaitForUnmountDuration, syncLoopPeriod, false, dsw, asw, ad, nsu, fakeRecorder)
|
||||
|
||||
nodes := []k8stypes.NodeName{}
|
||||
for _, n := range test.nodes {
|
||||
dsw.AddNode(n.name, false /*keepTerminatedPodVolumes*/)
|
||||
nodes = append(nodes, n.name)
|
||||
for _, podName := range n.podNames {
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName)
|
||||
volumeSpec.PersistentVolume.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
|
||||
uid := string(n.name) + "-" + podName // unique UID
|
||||
namespace, name := stringutil.SplitQualifiedName(podName)
|
||||
pod := controllervolumetesting.NewPod(uid, name)
|
||||
pod.Namespace = namespace
|
||||
_, err := dsw.AddPod(types.UniquePodName(uid), pod, volumeSpec, n.name)
|
||||
if err != nil {
|
||||
t.Fatalf("Error adding pod %s to DSW: %s", podName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Act
|
||||
volumes := dsw.GetVolumesToAttach()
|
||||
for _, vol := range volumes {
|
||||
if vol.NodeName == "node1" {
|
||||
rc.(*reconciler).reportMultiAttachError(vol, nodes)
|
||||
}
|
||||
}
|
||||
|
||||
// Assert
|
||||
close(fakeRecorder.Events)
|
||||
index := 0
|
||||
for event := range fakeRecorder.Events {
|
||||
if len(test.expectedEvents) < index {
|
||||
t.Errorf("Test %q: unexpected event received: %s", test.name, event)
|
||||
} else {
|
||||
expectedEvent := test.expectedEvents[index]
|
||||
if expectedEvent != event {
|
||||
t.Errorf("Test %q: event %d: expected %q, got %q", test.name, index, expectedEvent, event)
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
for i := index; i < len(test.expectedEvents); i++ {
|
||||
t.Errorf("Test %q: event %d: expected %q, got none", test.name, i, test.expectedEvents[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitForMultiAttachErrorOnNode(
|
||||
t *testing.T,
|
||||
attachedNode k8stypes.NodeName,
|
||||
|
2
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/statusupdater/BUILD
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/statusupdater/BUILD
generated
vendored
@ -14,11 +14,11 @@ go_library(
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach/statusupdater",
|
||||
deps = [
|
||||
"//pkg/controller/volume/attachdetach/cache:go_default_library",
|
||||
"//pkg/util/node:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
|
||||
],
|
||||
|
@ -19,18 +19,15 @@ limitations under the License.
|
||||
package statusupdater
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache"
|
||||
nodeutil "k8s.io/kubernetes/pkg/util/node"
|
||||
)
|
||||
|
||||
// NodeStatusUpdater defines a set of operations for updating the
|
||||
@ -100,47 +97,12 @@ func (nsu *nodeStatusUpdater) UpdateNodeStatuses() error {
|
||||
|
||||
func (nsu *nodeStatusUpdater) updateNodeStatus(nodeName types.NodeName, nodeObj *v1.Node, attachedVolumes []v1.AttachedVolume) error {
|
||||
node := nodeObj.DeepCopy()
|
||||
|
||||
// TODO: Change to pkg/util/node.UpdateNodeStatus.
|
||||
oldData, err := json.Marshal(node)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to Marshal oldData for node %q. %v",
|
||||
nodeName,
|
||||
err)
|
||||
}
|
||||
|
||||
node.Status.VolumesAttached = attachedVolumes
|
||||
|
||||
newData, err := json.Marshal(node)
|
||||
_, patchBytes, err := nodeutil.PatchNodeStatus(nsu.kubeClient.CoreV1(), nodeName, nodeObj, node)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to Marshal newData for node %q. %v",
|
||||
nodeName,
|
||||
err)
|
||||
return err
|
||||
}
|
||||
|
||||
patchBytes, err :=
|
||||
strategicpatch.CreateTwoWayMergePatch(oldData, newData, node)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to CreateTwoWayMergePatch for node %q. %v",
|
||||
nodeName,
|
||||
err)
|
||||
}
|
||||
|
||||
_, err = nsu.kubeClient.CoreV1().Nodes().PatchStatus(string(nodeName), patchBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to kubeClient.CoreV1().Nodes().Patch for node %q. %v",
|
||||
nodeName,
|
||||
err)
|
||||
}
|
||||
glog.V(4).Infof(
|
||||
"Updating status for node %q succeeded. patchBytes: %q VolumesAttached: %v",
|
||||
nodeName,
|
||||
string(patchBytes),
|
||||
node.Status.VolumesAttached)
|
||||
|
||||
glog.V(4).Infof("Updating status %q for node %q succeeded. VolumesAttached: %v", patchBytes, nodeName, attachedVolumes)
|
||||
return nil
|
||||
}
|
||||
|
2
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/testing/BUILD
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/testing/BUILD
generated
vendored
@ -11,7 +11,7 @@ go_library(
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach/testing",
|
||||
deps = [
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util/volumehelper:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
|
@ -30,7 +30,7 @@ import (
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
const TestPluginName = "kubernetes.io/testPlugin"
|
||||
@ -142,7 +142,7 @@ func CreateTestClient() *fake.Clientset {
|
||||
"name": nodeName,
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
volumehelper.ControllerManagedAttachAnnotation: "true",
|
||||
util.ControllerManagedAttachAnnotation: "true",
|
||||
},
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
|
2
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/util/BUILD
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/util/BUILD
generated
vendored
@ -12,7 +12,7 @@ go_library(
|
||||
deps = [
|
||||
"//pkg/controller/volume/attachdetach/cache:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util/volumehelper:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
|
8
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/util/util.go
generated
vendored
8
vendor/k8s.io/kubernetes/pkg/controller/volume/attachdetach/util/util.go
generated
vendored
@ -25,7 +25,7 @@ import (
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
// CreateVolumeSpec creates and returns a mutatable volume.Spec object for the
|
||||
@ -150,7 +150,7 @@ func DetermineVolumeAction(pod *v1.Pod, desiredStateOfWorld cache.DesiredStateOf
|
||||
nodeName := types.NodeName(pod.Spec.NodeName)
|
||||
keepTerminatedPodVolume := desiredStateOfWorld.GetKeepTerminatedPodVolumesForNode(nodeName)
|
||||
|
||||
if volumehelper.IsPodTerminated(pod, pod.Status) {
|
||||
if util.IsPodTerminated(pod, pod.Status) {
|
||||
// if pod is terminate we let kubelet policy dictate if volume
|
||||
// should be detached or not
|
||||
return keepTerminatedPodVolume
|
||||
@ -216,7 +216,7 @@ func ProcessPodVolumes(pod *v1.Pod, addVolumes bool, desiredStateOfWorld cache.D
|
||||
continue
|
||||
}
|
||||
|
||||
uniquePodName := volumehelper.GetUniquePodName(pod)
|
||||
uniquePodName := util.GetUniquePodName(pod)
|
||||
if addVolumes {
|
||||
// Add volume to desired state of world
|
||||
_, err := desiredStateOfWorld.AddPod(
|
||||
@ -232,7 +232,7 @@ func ProcessPodVolumes(pod *v1.Pod, addVolumes bool, desiredStateOfWorld cache.D
|
||||
|
||||
} else {
|
||||
// Remove volume from desired state of world
|
||||
uniqueVolumeName, err := volumehelper.GetUniqueVolumeNameFromSpec(
|
||||
uniqueVolumeName, err := util.GetUniqueVolumeNameFromSpec(
|
||||
attachableVolumePlugin, volumeSpec)
|
||||
if err != nil {
|
||||
glog.V(10).Infof(
|
||||
|
Reference in New Issue
Block a user