mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 12:18:16 +00:00 
			
		
		
		
	Merge pull request #44897 from msau42/local-storage-plugin
Automatic merge from submit-queue (batch tested with PRs 46076, 43879, 44897, 46556, 46654) Local storage plugin **What this PR does / why we need it**: Volume plugin implementation for local persistent volumes. Scheduler predicate will direct already-bound PVCs to the node that the local PV is at. PVC binding still happens independently. **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: Part of #43640 **Release note**: ``` Alpha feature: Local volume plugin allows local directories to be created and consumed as a Persistent Volume. These volumes have node affinity and pods will only be scheduled to the node that the volume is at. ```
This commit is contained in:
		@@ -84,6 +84,7 @@ go_library(
 | 
				
			|||||||
        "//pkg/volume/gce_pd:go_default_library",
 | 
					        "//pkg/volume/gce_pd:go_default_library",
 | 
				
			||||||
        "//pkg/volume/glusterfs:go_default_library",
 | 
					        "//pkg/volume/glusterfs:go_default_library",
 | 
				
			||||||
        "//pkg/volume/host_path:go_default_library",
 | 
					        "//pkg/volume/host_path:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/volume/local:go_default_library",
 | 
				
			||||||
        "//pkg/volume/nfs:go_default_library",
 | 
					        "//pkg/volume/nfs:go_default_library",
 | 
				
			||||||
        "//pkg/volume/photon_pd:go_default_library",
 | 
					        "//pkg/volume/photon_pd:go_default_library",
 | 
				
			||||||
        "//pkg/volume/portworx:go_default_library",
 | 
					        "//pkg/volume/portworx:go_default_library",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,6 +47,7 @@ import (
 | 
				
			|||||||
	"k8s.io/kubernetes/pkg/volume/gce_pd"
 | 
						"k8s.io/kubernetes/pkg/volume/gce_pd"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/glusterfs"
 | 
						"k8s.io/kubernetes/pkg/volume/glusterfs"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/host_path"
 | 
						"k8s.io/kubernetes/pkg/volume/host_path"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/volume/local"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/nfs"
 | 
						"k8s.io/kubernetes/pkg/volume/nfs"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/photon_pd"
 | 
						"k8s.io/kubernetes/pkg/volume/photon_pd"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/portworx"
 | 
						"k8s.io/kubernetes/pkg/volume/portworx"
 | 
				
			||||||
@@ -121,6 +122,7 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componen
 | 
				
			|||||||
	allPlugins = append(allPlugins, flocker.ProbeVolumePlugins()...)
 | 
						allPlugins = append(allPlugins, flocker.ProbeVolumePlugins()...)
 | 
				
			||||||
	allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
 | 
						allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
 | 
				
			||||||
	allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
 | 
						allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
 | 
				
			||||||
 | 
						allPlugins = append(allPlugins, local.ProbeVolumePlugins()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if cloud != nil {
 | 
						if cloud != nil {
 | 
				
			||||||
		switch {
 | 
							switch {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,6 +92,7 @@ go_library(
 | 
				
			|||||||
        "//pkg/volume/glusterfs:go_default_library",
 | 
					        "//pkg/volume/glusterfs:go_default_library",
 | 
				
			||||||
        "//pkg/volume/host_path:go_default_library",
 | 
					        "//pkg/volume/host_path:go_default_library",
 | 
				
			||||||
        "//pkg/volume/iscsi:go_default_library",
 | 
					        "//pkg/volume/iscsi:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/volume/local:go_default_library",
 | 
				
			||||||
        "//pkg/volume/nfs:go_default_library",
 | 
					        "//pkg/volume/nfs:go_default_library",
 | 
				
			||||||
        "//pkg/volume/photon_pd:go_default_library",
 | 
					        "//pkg/volume/photon_pd:go_default_library",
 | 
				
			||||||
        "//pkg/volume/portworx:go_default_library",
 | 
					        "//pkg/volume/portworx:go_default_library",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,6 +45,7 @@ import (
 | 
				
			|||||||
	"k8s.io/kubernetes/pkg/volume/glusterfs"
 | 
						"k8s.io/kubernetes/pkg/volume/glusterfs"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/host_path"
 | 
						"k8s.io/kubernetes/pkg/volume/host_path"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/iscsi"
 | 
						"k8s.io/kubernetes/pkg/volume/iscsi"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/volume/local"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/nfs"
 | 
						"k8s.io/kubernetes/pkg/volume/nfs"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/photon_pd"
 | 
						"k8s.io/kubernetes/pkg/volume/photon_pd"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/portworx"
 | 
						"k8s.io/kubernetes/pkg/volume/portworx"
 | 
				
			||||||
@@ -95,6 +96,7 @@ func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin {
 | 
				
			|||||||
	allPlugins = append(allPlugins, projected.ProbeVolumePlugins()...)
 | 
						allPlugins = append(allPlugins, projected.ProbeVolumePlugins()...)
 | 
				
			||||||
	allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
 | 
						allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
 | 
				
			||||||
	allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
 | 
						allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
 | 
				
			||||||
 | 
						allPlugins = append(allPlugins, local.ProbeVolumePlugins()...)
 | 
				
			||||||
	return allPlugins
 | 
						return allPlugins
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -590,6 +590,10 @@ func GetStorageNodeAffinityFromAnnotation(annotations map[string]string) (*api.N
 | 
				
			|||||||
// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes
 | 
					// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes
 | 
				
			||||||
// TODO: update when storage node affinity graduates to beta
 | 
					// TODO: update when storage node affinity graduates to beta
 | 
				
			||||||
func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *api.NodeAffinity) error {
 | 
					func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *api.NodeAffinity) error {
 | 
				
			||||||
 | 
						if affinity == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	json, err := json.Marshal(*affinity)
 | 
						json, err := json.Marshal(*affinity)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -517,6 +517,10 @@ func GetStorageNodeAffinityFromAnnotation(annotations map[string]string) (*v1.No
 | 
				
			|||||||
// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes
 | 
					// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes
 | 
				
			||||||
// TODO: update when storage node affinity graduates to beta
 | 
					// TODO: update when storage node affinity graduates to beta
 | 
				
			||||||
func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *v1.NodeAffinity) error {
 | 
					func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *v1.NodeAffinity) error {
 | 
				
			||||||
 | 
						if affinity == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	json, err := json.Marshal(*affinity)
 | 
						json, err := json.Marshal(*affinity)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -565,3 +565,7 @@ func (adc *attachDetachController) addNodeToDswp(node *v1.Node, nodeName types.N
 | 
				
			|||||||
		adc.desiredStateOfWorld.AddNode(nodeName, keepTerminatedPodVolumes)
 | 
							adc.desiredStateOfWorld.AddNode(nodeName, keepTerminatedPodVolumes)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (adc *attachDetachController) GetNodeLabels() (map[string]string, error) {
 | 
				
			||||||
 | 
						return nil, fmt.Errorf("GetNodeLabels() unsupported in Attach/Detach controller")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -86,3 +86,7 @@ func (adc *PersistentVolumeController) GetSecretFunc() func(namespace, name stri
 | 
				
			|||||||
		return nil, fmt.Errorf("GetSecret unsupported in PersistentVolumeController")
 | 
							return nil, fmt.Errorf("GetSecret unsupported in PersistentVolumeController")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ctrl *PersistentVolumeController) GetNodeLabels() (map[string]string, error) {
 | 
				
			||||||
 | 
						return nil, fmt.Errorf("GetNodeLabels() unsupported in PersistentVolumeController")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -140,3 +140,11 @@ func (kvh *kubeletVolumeHost) GetNodeAllocatable() (v1.ResourceList, error) {
 | 
				
			|||||||
func (kvh *kubeletVolumeHost) GetSecretFunc() func(namespace, name string) (*v1.Secret, error) {
 | 
					func (kvh *kubeletVolumeHost) GetSecretFunc() func(namespace, name string) (*v1.Secret, error) {
 | 
				
			||||||
	return kvh.secretManager.GetSecret
 | 
						return kvh.secretManager.GetSecret
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (kvh *kubeletVolumeHost) GetNodeLabels() (map[string]string, error) {
 | 
				
			||||||
 | 
						node, err := kvh.kubelet.GetNode()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("error retrieving node: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return node.Labels, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,6 +106,7 @@ filegroup(
 | 
				
			|||||||
        "//pkg/volume/glusterfs:all-srcs",
 | 
					        "//pkg/volume/glusterfs:all-srcs",
 | 
				
			||||||
        "//pkg/volume/host_path:all-srcs",
 | 
					        "//pkg/volume/host_path:all-srcs",
 | 
				
			||||||
        "//pkg/volume/iscsi:all-srcs",
 | 
					        "//pkg/volume/iscsi:all-srcs",
 | 
				
			||||||
 | 
					        "//pkg/volume/local:all-srcs",
 | 
				
			||||||
        "//pkg/volume/nfs:all-srcs",
 | 
					        "//pkg/volume/nfs:all-srcs",
 | 
				
			||||||
        "//pkg/volume/photon_pd:all-srcs",
 | 
					        "//pkg/volume/photon_pd:all-srcs",
 | 
				
			||||||
        "//pkg/volume/portworx:all-srcs",
 | 
					        "//pkg/volume/portworx:all-srcs",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										56
									
								
								pkg/volume/local/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								pkg/volume/local/BUILD
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					package(default_visibility = ["//visibility:public"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					licenses(["notice"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					load(
 | 
				
			||||||
 | 
					    "@io_bazel_rules_go//go:def.bzl",
 | 
				
			||||||
 | 
					    "go_library",
 | 
				
			||||||
 | 
					    "go_test",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go_library(
 | 
				
			||||||
 | 
					    name = "go_default_library",
 | 
				
			||||||
 | 
					    srcs = [
 | 
				
			||||||
 | 
					        "doc.go",
 | 
				
			||||||
 | 
					        "local.go",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
 | 
					    deps = [
 | 
				
			||||||
 | 
					        "//pkg/api/v1:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/util/mount:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/util/strings:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/volume:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/volume/util:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/github.com/golang/glog:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go_test(
 | 
				
			||||||
 | 
					    name = "go_default_test",
 | 
				
			||||||
 | 
					    srcs = ["local_test.go"],
 | 
				
			||||||
 | 
					    library = ":go_default_library",
 | 
				
			||||||
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
 | 
					    deps = [
 | 
				
			||||||
 | 
					        "//pkg/api/v1:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/volume:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/volume/testing:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/client-go/util/testing:go_default_library",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					filegroup(
 | 
				
			||||||
 | 
					    name = "package-srcs",
 | 
				
			||||||
 | 
					    srcs = glob(["**"]),
 | 
				
			||||||
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
 | 
					    visibility = ["//visibility:private"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					filegroup(
 | 
				
			||||||
 | 
					    name = "all-srcs",
 | 
				
			||||||
 | 
					    srcs = [":package-srcs"],
 | 
				
			||||||
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										21
									
								
								pkg/volume/local/OWNERS
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								pkg/volume/local/OWNERS
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					approvers:
 | 
				
			||||||
 | 
					- saad-ali
 | 
				
			||||||
 | 
					- thockin
 | 
				
			||||||
 | 
					- vishh
 | 
				
			||||||
 | 
					- msau42
 | 
				
			||||||
 | 
					- jingxu97
 | 
				
			||||||
 | 
					- jsafrane
 | 
				
			||||||
 | 
					reviewers:
 | 
				
			||||||
 | 
					- thockin
 | 
				
			||||||
 | 
					- smarterclayton
 | 
				
			||||||
 | 
					- deads2k
 | 
				
			||||||
 | 
					- brendandburns
 | 
				
			||||||
 | 
					- derekwaynecarr
 | 
				
			||||||
 | 
					- pmorie
 | 
				
			||||||
 | 
					- saad-ali
 | 
				
			||||||
 | 
					- justinsb
 | 
				
			||||||
 | 
					- jsafrane
 | 
				
			||||||
 | 
					- rootfs
 | 
				
			||||||
 | 
					- jingxu97
 | 
				
			||||||
 | 
					- msau42
 | 
				
			||||||
 | 
					- vishh
 | 
				
			||||||
							
								
								
									
										18
									
								
								pkg/volume/local/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								pkg/volume/local/doc.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2017 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 local contains the internal representation of local volumes
 | 
				
			||||||
 | 
					package local // import "k8s.io/kubernetes/pkg/volume/local"
 | 
				
			||||||
							
								
								
									
										267
									
								
								pkg/volume/local/local.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								pkg/volume/local/local.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,267 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2017 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 local
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/golang/glog"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api/v1"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/util/mount"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/util/strings"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/volume"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/volume/util"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This is the primary entrypoint for volume plugins.
 | 
				
			||||||
 | 
					func ProbeVolumePlugins() []volume.VolumePlugin {
 | 
				
			||||||
 | 
						return []volume.VolumePlugin{&localVolumePlugin{}}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type localVolumePlugin struct {
 | 
				
			||||||
 | 
						host volume.VolumeHost
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ volume.VolumePlugin = &localVolumePlugin{}
 | 
				
			||||||
 | 
					var _ volume.PersistentVolumePlugin = &localVolumePlugin{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						localVolumePluginName = "kubernetes.io/local-volume"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) Init(host volume.VolumeHost) error {
 | 
				
			||||||
 | 
						plugin.host = host
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) GetPluginName() string {
 | 
				
			||||||
 | 
						return localVolumePluginName
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
 | 
				
			||||||
 | 
						// This volume is only supported as a PersistentVolumeSource, so the PV name is unique
 | 
				
			||||||
 | 
						return spec.Name(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) CanSupport(spec *volume.Spec) bool {
 | 
				
			||||||
 | 
						// This volume is only supported as a PersistentVolumeSource
 | 
				
			||||||
 | 
						return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Local != nil)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) RequiresRemount() bool {
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) SupportsMountOption() bool {
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) SupportsBulkVolumeVerification() bool {
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
 | 
				
			||||||
 | 
						// The current meaning of AccessMode is how many nodes can attach to it, not how many pods can mount it
 | 
				
			||||||
 | 
						return []v1.PersistentVolumeAccessMode{
 | 
				
			||||||
 | 
							v1.ReadWriteOnce,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getVolumeSource(spec *volume.Spec) (*v1.LocalVolumeSource, bool, error) {
 | 
				
			||||||
 | 
						if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Local != nil {
 | 
				
			||||||
 | 
							return spec.PersistentVolume.Spec.Local, spec.ReadOnly, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil, false, fmt.Errorf("Spec does not reference a Local volume type")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
 | 
				
			||||||
 | 
						volumeSource, readOnly, err := getVolumeSource(spec)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &localVolumeMounter{
 | 
				
			||||||
 | 
							localVolume: &localVolume{
 | 
				
			||||||
 | 
								podUID:     pod.UID,
 | 
				
			||||||
 | 
								volName:    spec.Name(),
 | 
				
			||||||
 | 
								mounter:    plugin.host.GetMounter(),
 | 
				
			||||||
 | 
								plugin:     plugin,
 | 
				
			||||||
 | 
								globalPath: volumeSource.Path,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							readOnly: readOnly,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
 | 
				
			||||||
 | 
						return &localVolumeUnmounter{
 | 
				
			||||||
 | 
							localVolume: &localVolume{
 | 
				
			||||||
 | 
								podUID:  podUID,
 | 
				
			||||||
 | 
								volName: volName,
 | 
				
			||||||
 | 
								mounter: plugin.host.GetMounter(),
 | 
				
			||||||
 | 
								plugin:  plugin,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: check if no path and no topology constraints are ok
 | 
				
			||||||
 | 
					func (plugin *localVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
 | 
				
			||||||
 | 
						localVolume := &v1.PersistentVolume{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: volumeName,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: v1.PersistentVolumeSpec{
 | 
				
			||||||
 | 
								PersistentVolumeSource: v1.PersistentVolumeSource{
 | 
				
			||||||
 | 
									Local: &v1.LocalVolumeSource{
 | 
				
			||||||
 | 
										Path: "",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return volume.NewSpecFromPersistentVolume(localVolume, false), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Local volumes represent a local directory on a node.
 | 
				
			||||||
 | 
					// The directory at the globalPath will be bind-mounted to the pod's directory
 | 
				
			||||||
 | 
					type localVolume struct {
 | 
				
			||||||
 | 
						volName string
 | 
				
			||||||
 | 
						podUID  types.UID
 | 
				
			||||||
 | 
						// Global path to the volume
 | 
				
			||||||
 | 
						globalPath string
 | 
				
			||||||
 | 
						// Mounter interface that provides system calls to mount the global path to the pod local path.
 | 
				
			||||||
 | 
						mounter mount.Interface
 | 
				
			||||||
 | 
						plugin  *localVolumePlugin
 | 
				
			||||||
 | 
						// TODO: add metrics
 | 
				
			||||||
 | 
						volume.MetricsNil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (l *localVolume) GetPath() string {
 | 
				
			||||||
 | 
						return l.plugin.host.GetPodVolumeDir(l.podUID, strings.EscapeQualifiedNameForDisk(localVolumePluginName), l.volName)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type localVolumeMounter struct {
 | 
				
			||||||
 | 
						*localVolume
 | 
				
			||||||
 | 
						readOnly bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ volume.Mounter = &localVolumeMounter{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *localVolumeMounter) GetAttributes() volume.Attributes {
 | 
				
			||||||
 | 
						return volume.Attributes{
 | 
				
			||||||
 | 
							ReadOnly:        m.readOnly,
 | 
				
			||||||
 | 
							Managed:         !m.readOnly,
 | 
				
			||||||
 | 
							SupportsSELinux: true,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CanMount checks prior to mount operations to verify that the required components (binaries, etc.)
 | 
				
			||||||
 | 
					// to mount the volume are available on the underlying node.
 | 
				
			||||||
 | 
					// If not, it returns an error
 | 
				
			||||||
 | 
					func (m *localVolumeMounter) CanMount() error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetUp bind mounts the directory to the volume path
 | 
				
			||||||
 | 
					func (m *localVolumeMounter) SetUp(fsGroup *types.UnixGroupID) error {
 | 
				
			||||||
 | 
						return m.SetUpAt(m.GetPath(), fsGroup)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetUpAt bind mounts the directory to the volume path and sets up volume ownership
 | 
				
			||||||
 | 
					func (m *localVolumeMounter) SetUpAt(dir string, fsGroup *types.UnixGroupID) error {
 | 
				
			||||||
 | 
						if m.globalPath == "" {
 | 
				
			||||||
 | 
							err := fmt.Errorf("LocalVolume volume %q path is empty", m.volName)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						notMnt, err := m.mounter.IsLikelyNotMountPoint(dir)
 | 
				
			||||||
 | 
						glog.V(4).Infof("LocalVolume mount setup: PodDir(%s) VolDir(%s) Mounted(%t) Error(%v), ReadOnly(%t)", dir, m.globalPath, !notMnt, err, m.readOnly)
 | 
				
			||||||
 | 
						if err != nil && !os.IsNotExist(err) {
 | 
				
			||||||
 | 
							glog.Errorf("cannot validate mount point: %s %v", dir, err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !notMnt {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := os.MkdirAll(dir, 0750); err != nil {
 | 
				
			||||||
 | 
							glog.Errorf("mkdir failed on disk %s (%v)", dir, err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Perform a bind mount to the full path to allow duplicate mounts of the same volume.
 | 
				
			||||||
 | 
						options := []string{"bind"}
 | 
				
			||||||
 | 
						if m.readOnly {
 | 
				
			||||||
 | 
							options = append(options, "ro")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						glog.V(4).Infof("attempting to mount %s", dir)
 | 
				
			||||||
 | 
						err = m.mounter.Mount(m.globalPath, dir, "", options)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							glog.Errorf("Mount of volume %s failed: %v", dir, err)
 | 
				
			||||||
 | 
							notMnt, mntErr := m.mounter.IsLikelyNotMountPoint(dir)
 | 
				
			||||||
 | 
							if mntErr != nil {
 | 
				
			||||||
 | 
								glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !notMnt {
 | 
				
			||||||
 | 
								if mntErr = m.mounter.Unmount(dir); mntErr != nil {
 | 
				
			||||||
 | 
									glog.Errorf("Failed to unmount: %v", mntErr)
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								notMnt, mntErr = m.mounter.IsLikelyNotMountPoint(dir)
 | 
				
			||||||
 | 
								if mntErr != nil {
 | 
				
			||||||
 | 
									glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !notMnt {
 | 
				
			||||||
 | 
									// This is very odd, we don't expect it.  We'll try again next sync loop.
 | 
				
			||||||
 | 
									glog.Errorf("%s is still mounted, despite call to unmount().  Will try again next sync loop.", dir)
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							os.Remove(dir)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !m.readOnly {
 | 
				
			||||||
 | 
							// TODO: how to prevent multiple mounts with conflicting fsGroup?
 | 
				
			||||||
 | 
							return volume.SetVolumeOwnership(m, fsGroup)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type localVolumeUnmounter struct {
 | 
				
			||||||
 | 
						*localVolume
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ volume.Unmounter = &localVolumeUnmounter{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TearDown unmounts the bind mount
 | 
				
			||||||
 | 
					func (u *localVolumeUnmounter) TearDown() error {
 | 
				
			||||||
 | 
						return u.TearDownAt(u.GetPath())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TearDownAt unmounts the bind mount
 | 
				
			||||||
 | 
					func (u *localVolumeUnmounter) TearDownAt(dir string) error {
 | 
				
			||||||
 | 
						glog.V(4).Infof("Unmounting volume %q at path %q\n", u.volName, dir)
 | 
				
			||||||
 | 
						return util.UnmountPath(dir, u.mounter)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										288
									
								
								pkg/volume/local/local_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								pkg/volume/local/local_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,288 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2017 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 local
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
 | 
						utiltesting "k8s.io/client-go/util/testing"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api/v1"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/volume"
 | 
				
			||||||
 | 
						volumetest "k8s.io/kubernetes/pkg/volume/testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						testPVName    = "pvA"
 | 
				
			||||||
 | 
						testMountPath = "pods/poduid/volumes/kubernetes.io~local-volume/pvA"
 | 
				
			||||||
 | 
						testNodeName  = "fakeNodeName"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getPlugin(t *testing.T) (string, volume.VolumePlugin) {
 | 
				
			||||||
 | 
						tmpDir, err := utiltesting.MkTmpdir("localVolumeTest")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("can't make a temp dir: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						plugMgr := volume.VolumePluginMgr{}
 | 
				
			||||||
 | 
						plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						plug, err := plugMgr.FindPluginByName(localVolumePluginName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							os.RemoveAll(tmpDir)
 | 
				
			||||||
 | 
							t.Fatalf("Can't find the plugin by name")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if plug.GetPluginName() != localVolumePluginName {
 | 
				
			||||||
 | 
							t.Errorf("Wrong name: %s", plug.GetPluginName())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return tmpDir, plug
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getPersistentPlugin(t *testing.T) (string, volume.PersistentVolumePlugin) {
 | 
				
			||||||
 | 
						tmpDir, err := utiltesting.MkTmpdir("localVolumeTest")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("can't make a temp dir: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						plugMgr := volume.VolumePluginMgr{}
 | 
				
			||||||
 | 
						plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						plug, err := plugMgr.FindPersistentPluginByName(localVolumePluginName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							os.RemoveAll(tmpDir)
 | 
				
			||||||
 | 
							t.Fatalf("Can't find the plugin by name")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if plug.GetPluginName() != localVolumePluginName {
 | 
				
			||||||
 | 
							t.Errorf("Wrong name: %s", plug.GetPluginName())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return tmpDir, plug
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getTestVolume(readOnly bool) *volume.Spec {
 | 
				
			||||||
 | 
						pv := &v1.PersistentVolume{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: testPVName,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: v1.PersistentVolumeSpec{
 | 
				
			||||||
 | 
								PersistentVolumeSource: v1.PersistentVolumeSource{
 | 
				
			||||||
 | 
									Local: &v1.LocalVolumeSource{
 | 
				
			||||||
 | 
										Path: "/test-vol",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return volume.NewSpecFromPersistentVolume(pv, readOnly)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
 | 
				
			||||||
 | 
						for _, m := range modes {
 | 
				
			||||||
 | 
							if m == mode {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCanSupport(t *testing.T) {
 | 
				
			||||||
 | 
						tmpDir, plug := getPlugin(t)
 | 
				
			||||||
 | 
						defer os.RemoveAll(tmpDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !plug.CanSupport(getTestVolume(false)) {
 | 
				
			||||||
 | 
							t.Errorf("Expected true")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGetAccessModes(t *testing.T) {
 | 
				
			||||||
 | 
						tmpDir, plug := getPersistentPlugin(t)
 | 
				
			||||||
 | 
						defer os.RemoveAll(tmpDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						modes := plug.GetAccessModes()
 | 
				
			||||||
 | 
						if !contains(modes, v1.ReadWriteOnce) {
 | 
				
			||||||
 | 
							t.Errorf("Expected AccessModeType %q", v1.ReadWriteOnce)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if contains(modes, v1.ReadWriteMany) {
 | 
				
			||||||
 | 
							t.Errorf("Found AccessModeType %q, expected not", v1.ReadWriteMany)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if contains(modes, v1.ReadOnlyMany) {
 | 
				
			||||||
 | 
							t.Errorf("Found AccessModeType %q, expected not", v1.ReadOnlyMany)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGetVolumeName(t *testing.T) {
 | 
				
			||||||
 | 
						tmpDir, plug := getPersistentPlugin(t)
 | 
				
			||||||
 | 
						defer os.RemoveAll(tmpDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						volName, err := plug.GetVolumeName(getTestVolume(false))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Failed to get volume name: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if volName != testPVName {
 | 
				
			||||||
 | 
							t.Errorf("Expected volume name %q, got %q", testPVName, volName)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestMountUnmount(t *testing.T) {
 | 
				
			||||||
 | 
						tmpDir, plug := getPlugin(t)
 | 
				
			||||||
 | 
						defer os.RemoveAll(tmpDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
 | 
				
			||||||
 | 
						mounter, err := plug.NewMounter(getTestVolume(false), pod, volume.VolumeOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Failed to make a new Mounter: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if mounter == nil {
 | 
				
			||||||
 | 
							t.Fatalf("Got a nil Mounter")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						volPath := path.Join(tmpDir, testMountPath)
 | 
				
			||||||
 | 
						path := mounter.GetPath()
 | 
				
			||||||
 | 
						if path != volPath {
 | 
				
			||||||
 | 
							t.Errorf("Got unexpected path: %s", path)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := mounter.SetUp(nil); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Expected success, got: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, err := os.Stat(path); err != nil {
 | 
				
			||||||
 | 
							if os.IsNotExist(err) {
 | 
				
			||||||
 | 
								t.Errorf("SetUp() failed, volume path not created: %s", path)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								t.Errorf("SetUp() failed: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						unmounter, err := plug.NewUnmounter(testPVName, pod.UID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Failed to make a new Unmounter: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if unmounter == nil {
 | 
				
			||||||
 | 
							t.Fatalf("Got a nil Unmounter")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := unmounter.TearDown(); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Expected success, got: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, err := os.Stat(path); err == nil {
 | 
				
			||||||
 | 
							t.Errorf("TearDown() failed, volume path still exists: %s", path)
 | 
				
			||||||
 | 
						} else if !os.IsNotExist(err) {
 | 
				
			||||||
 | 
							t.Errorf("SetUp() failed: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestConstructVolumeSpec(t *testing.T) {
 | 
				
			||||||
 | 
						tmpDir, plug := getPlugin(t)
 | 
				
			||||||
 | 
						defer os.RemoveAll(tmpDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						volPath := path.Join(tmpDir, testMountPath)
 | 
				
			||||||
 | 
						spec, err := plug.ConstructVolumeSpec(testPVName, volPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("ConstructVolumeSpec() failed: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if spec == nil {
 | 
				
			||||||
 | 
							t.Fatalf("ConstructVolumeSpec() returned nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						volName := spec.Name()
 | 
				
			||||||
 | 
						if volName != testPVName {
 | 
				
			||||||
 | 
							t.Errorf("Expected volume name %q, got %q", testPVName, volName)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if spec.Volume != nil {
 | 
				
			||||||
 | 
							t.Errorf("Volume object returned, expected nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pv := spec.PersistentVolume
 | 
				
			||||||
 | 
						if pv == nil {
 | 
				
			||||||
 | 
							t.Fatalf("PersistentVolume object nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ls := pv.Spec.PersistentVolumeSource.Local
 | 
				
			||||||
 | 
						if ls == nil {
 | 
				
			||||||
 | 
							t.Fatalf("LocalVolumeSource object nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestPersistentClaimReadOnlyFlag(t *testing.T) {
 | 
				
			||||||
 | 
						tmpDir, plug := getPlugin(t)
 | 
				
			||||||
 | 
						defer os.RemoveAll(tmpDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Read only == true
 | 
				
			||||||
 | 
						pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
 | 
				
			||||||
 | 
						mounter, err := plug.NewMounter(getTestVolume(true), pod, volume.VolumeOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Failed to make a new Mounter: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if mounter == nil {
 | 
				
			||||||
 | 
							t.Fatalf("Got a nil Mounter")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !mounter.GetAttributes().ReadOnly {
 | 
				
			||||||
 | 
							t.Errorf("Expected true for mounter.IsReadOnly")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Read only == false
 | 
				
			||||||
 | 
						mounter, err = plug.NewMounter(getTestVolume(false), pod, volume.VolumeOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Failed to make a new Mounter: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if mounter == nil {
 | 
				
			||||||
 | 
							t.Fatalf("Got a nil Mounter")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if mounter.GetAttributes().ReadOnly {
 | 
				
			||||||
 | 
							t.Errorf("Expected false for mounter.IsReadOnly")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestUnsupportedPlugins(t *testing.T) {
 | 
				
			||||||
 | 
						tmpDir, err := utiltesting.MkTmpdir("localVolumeTest")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("can't make a temp dir: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer os.RemoveAll(tmpDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						plugMgr := volume.VolumePluginMgr{}
 | 
				
			||||||
 | 
						plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
 | 
				
			||||||
 | 
						spec := getTestVolume(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						recyclePlug, err := plugMgr.FindRecyclablePluginBySpec(spec)
 | 
				
			||||||
 | 
						if err == nil && recyclePlug != nil {
 | 
				
			||||||
 | 
							t.Errorf("Recyclable plugin found, expected none")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						deletePlug, err := plugMgr.FindDeletablePluginByName(localVolumePluginName)
 | 
				
			||||||
 | 
						if err == nil && deletePlug != nil {
 | 
				
			||||||
 | 
							t.Errorf("Deletable plugin found, expected none")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						attachPlug, err := plugMgr.FindAttachablePluginByName(localVolumePluginName)
 | 
				
			||||||
 | 
						if err == nil && attachPlug != nil {
 | 
				
			||||||
 | 
							t.Errorf("Attachable plugin found, expected none")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						createPlug, err := plugMgr.FindCreatablePluginBySpec(spec)
 | 
				
			||||||
 | 
						if err == nil && createPlug != nil {
 | 
				
			||||||
 | 
							t.Errorf("Creatable plugin found, expected none")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						provisionPlug, err := plugMgr.FindProvisionablePluginByName(localVolumePluginName)
 | 
				
			||||||
 | 
						if err == nil && provisionPlug != nil {
 | 
				
			||||||
 | 
							t.Errorf("Provisionable plugin found, expected none")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -232,12 +232,16 @@ type VolumeHost interface {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Returns a function that returns a secret.
 | 
						// Returns a function that returns a secret.
 | 
				
			||||||
	GetSecretFunc() func(namespace, name string) (*v1.Secret, error)
 | 
						GetSecretFunc() func(namespace, name string) (*v1.Secret, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Returns the labels on the node
 | 
				
			||||||
 | 
						GetNodeLabels() (map[string]string, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// VolumePluginMgr tracks registered plugins.
 | 
					// VolumePluginMgr tracks registered plugins.
 | 
				
			||||||
type VolumePluginMgr struct {
 | 
					type VolumePluginMgr struct {
 | 
				
			||||||
	mutex   sync.Mutex
 | 
						mutex   sync.Mutex
 | 
				
			||||||
	plugins map[string]VolumePlugin
 | 
						plugins map[string]VolumePlugin
 | 
				
			||||||
 | 
						Host    VolumeHost
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Spec is an internal representation of a volume.  All API volume types translate to Spec.
 | 
					// Spec is an internal representation of a volume.  All API volume types translate to Spec.
 | 
				
			||||||
@@ -336,6 +340,7 @@ func (pm *VolumePluginMgr) InitPlugins(plugins []VolumePlugin, host VolumeHost)
 | 
				
			|||||||
	pm.mutex.Lock()
 | 
						pm.mutex.Lock()
 | 
				
			||||||
	defer pm.mutex.Unlock()
 | 
						defer pm.mutex.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pm.Host = host
 | 
				
			||||||
	if pm.plugins == nil {
 | 
						if pm.plugins == nil {
 | 
				
			||||||
		pm.plugins = map[string]VolumePlugin{}
 | 
							pm.plugins = map[string]VolumePlugin{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -134,6 +134,10 @@ func (f *fakeVolumeHost) GetSecretFunc() func(namespace, name string) (*v1.Secre
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f *fakeVolumeHost) GetNodeLabels() (map[string]string, error) {
 | 
				
			||||||
 | 
						return map[string]string{"test-label": "test-value"}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ProbeVolumePlugins(config VolumeConfig) []VolumePlugin {
 | 
					func ProbeVolumePlugins(config VolumeConfig) []VolumePlugin {
 | 
				
			||||||
	if _, ok := config.OtherAttributes["fake-property"]; ok {
 | 
						if _, ok := config.OtherAttributes["fake-property"]; ok {
 | 
				
			||||||
		return []VolumePlugin{
 | 
							return []VolumePlugin{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,6 +29,7 @@ go_library(
 | 
				
			|||||||
        "//vendor/github.com/golang/glog:go_default_library",
 | 
					        "//vendor/github.com/golang/glog:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -38,10 +39,14 @@ go_test(
 | 
				
			|||||||
    srcs = [
 | 
					    srcs = [
 | 
				
			||||||
        "atomic_writer_test.go",
 | 
					        "atomic_writer_test.go",
 | 
				
			||||||
        "device_util_linux_test.go",
 | 
					        "device_util_linux_test.go",
 | 
				
			||||||
 | 
					        "util_test.go",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    library = ":go_default_library",
 | 
					    library = ":go_default_library",
 | 
				
			||||||
    tags = ["automanaged"],
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
    deps = [
 | 
					    deps = [
 | 
				
			||||||
 | 
					        "//pkg/api/v1:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/api/v1/helper:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/client-go/util/testing:go_default_library",
 | 
					        "//vendor/k8s.io/client-go/util/testing:go_default_library",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,9 +18,11 @@ go_library(
 | 
				
			|||||||
    deps = [
 | 
					    deps = [
 | 
				
			||||||
        "//pkg/api/v1:go_default_library",
 | 
					        "//pkg/api/v1:go_default_library",
 | 
				
			||||||
        "//pkg/client/clientset_generated/clientset:go_default_library",
 | 
					        "//pkg/client/clientset_generated/clientset:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/features:go_default_library",
 | 
				
			||||||
        "//pkg/kubelet/events:go_default_library",
 | 
					        "//pkg/kubelet/events:go_default_library",
 | 
				
			||||||
        "//pkg/util/mount:go_default_library",
 | 
					        "//pkg/util/mount:go_default_library",
 | 
				
			||||||
        "//pkg/volume:go_default_library",
 | 
					        "//pkg/volume:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/volume/util:go_default_library",
 | 
				
			||||||
        "//pkg/volume/util/nestedpendingoperations:go_default_library",
 | 
					        "//pkg/volume/util/nestedpendingoperations:go_default_library",
 | 
				
			||||||
        "//pkg/volume/util/types:go_default_library",
 | 
					        "//pkg/volume/util/types:go_default_library",
 | 
				
			||||||
        "//pkg/volume/util/volumehelper:go_default_library",
 | 
					        "//pkg/volume/util/volumehelper:go_default_library",
 | 
				
			||||||
@@ -28,6 +30,7 @@ go_library(
 | 
				
			|||||||
        "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/client-go/tools/record:go_default_library",
 | 
					        "//vendor/k8s.io/client-go/tools/record:go_default_library",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,12 +24,15 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/api/errors"
 | 
						"k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/types"
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
	"k8s.io/client-go/tools/record"
 | 
						"k8s.io/client-go/tools/record"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api/v1"
 | 
						"k8s.io/kubernetes/pkg/api/v1"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
 | 
						"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/features"
 | 
				
			||||||
	kevents "k8s.io/kubernetes/pkg/kubelet/events"
 | 
						kevents "k8s.io/kubernetes/pkg/kubelet/events"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
						"k8s.io/kubernetes/pkg/util/mount"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume"
 | 
						"k8s.io/kubernetes/pkg/volume"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/volume/util"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/util/volumehelper"
 | 
						"k8s.io/kubernetes/pkg/volume/util/volumehelper"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -363,6 +366,11 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
 | 
				
			|||||||
		return nil, volumeToMount.GenerateErrorDetailed("MountVolume.FindPluginBySpec failed", err)
 | 
							return nil, volumeToMount.GenerateErrorDetailed("MountVolume.FindPluginBySpec failed", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						affinityErr := checkNodeAffinity(og, volumeToMount, volumePlugin)
 | 
				
			||||||
 | 
						if affinityErr != nil {
 | 
				
			||||||
 | 
							return nil, affinityErr
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	volumeMounter, newMounterErr := volumePlugin.NewMounter(
 | 
						volumeMounter, newMounterErr := volumePlugin.NewMounter(
 | 
				
			||||||
		volumeToMount.VolumeSpec,
 | 
							volumeToMount.VolumeSpec,
 | 
				
			||||||
		volumeToMount.Pod,
 | 
							volumeToMount.Pod,
 | 
				
			||||||
@@ -713,3 +721,27 @@ func checkMountOptionSupport(og *operationGenerator, volumeToMount VolumeToMount
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// checkNodeAffinity looks at the PV node affinity, and checks if the node has the same corresponding labels
 | 
				
			||||||
 | 
					// This ensures that we don't mount a volume that doesn't belong to this node
 | 
				
			||||||
 | 
					func checkNodeAffinity(og *operationGenerator, volumeToMount VolumeToMount, plugin volume.VolumePlugin) error {
 | 
				
			||||||
 | 
						if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pv := volumeToMount.VolumeSpec.PersistentVolume
 | 
				
			||||||
 | 
						if pv != nil {
 | 
				
			||||||
 | 
							nodeLabels, err := og.volumePluginMgr.Host.GetNodeLabels()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return volumeToMount.GenerateErrorDetailed("Error getting node labels", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = util.CheckNodeAffinity(pv, nodeLabels)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								eventErr, detailedErr := volumeToMount.GenerateError("Storage node affinity check failed", err)
 | 
				
			||||||
 | 
								og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error())
 | 
				
			||||||
 | 
								return detailedErr
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/golang/glog"
 | 
						"github.com/golang/glog"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api/v1"
 | 
						"k8s.io/kubernetes/pkg/api/v1"
 | 
				
			||||||
	v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
 | 
						v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
 | 
				
			||||||
	storage "k8s.io/kubernetes/pkg/apis/storage/v1"
 | 
						storage "k8s.io/kubernetes/pkg/apis/storage/v1"
 | 
				
			||||||
@@ -164,3 +165,30 @@ func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return class, nil
 | 
						return class, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckNodeAffinity looks at the PV node affinity, and checks if the node has the same corresponding labels
 | 
				
			||||||
 | 
					// This ensures that we don't mount a volume that doesn't belong to this node
 | 
				
			||||||
 | 
					func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
 | 
				
			||||||
 | 
						affinity, err := v1helper.GetStorageNodeAffinityFromAnnotation(pv.Annotations)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Error getting storage node affinity: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if affinity == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if affinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
 | 
				
			||||||
 | 
							terms := affinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms
 | 
				
			||||||
 | 
							glog.V(10).Infof("Match for RequiredDuringSchedulingIgnoredDuringExecution node selector terms %+v", terms)
 | 
				
			||||||
 | 
							for _, term := range terms {
 | 
				
			||||||
 | 
								selector, err := v1helper.NodeSelectorRequirementsAsSelector(term.MatchExpressions)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("Failed to parse MatchExpressions: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !selector.Matches(labels.Set(nodeLabels)) {
 | 
				
			||||||
 | 
									return fmt.Errorf("NodeSelectorTerm %+v does not match node labels", term.MatchExpressions)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										142
									
								
								pkg/volume/util/util_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								pkg/volume/util/util_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2017 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 util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api/v1"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api/v1/helper"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var nodeLabels map[string]string = map[string]string{
 | 
				
			||||||
 | 
						"test-key1": "test-value1",
 | 
				
			||||||
 | 
						"test-key2": "test-value2",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCheckNodeAffinity(t *testing.T) {
 | 
				
			||||||
 | 
						type affinityTest struct {
 | 
				
			||||||
 | 
							name          string
 | 
				
			||||||
 | 
							expectSuccess bool
 | 
				
			||||||
 | 
							pv            *v1.PersistentVolume
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cases := []affinityTest{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:          "valid-no-constraints",
 | 
				
			||||||
 | 
								expectSuccess: true,
 | 
				
			||||||
 | 
								pv:            testVolumeWithNodeAffinity(t, &v1.NodeAffinity{}),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:          "valid-constraints",
 | 
				
			||||||
 | 
								expectSuccess: true,
 | 
				
			||||||
 | 
								pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{
 | 
				
			||||||
 | 
									RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
 | 
				
			||||||
 | 
										NodeSelectorTerms: []v1.NodeSelectorTerm{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												MatchExpressions: []v1.NodeSelectorRequirement{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Key:      "test-key1",
 | 
				
			||||||
 | 
														Operator: v1.NodeSelectorOpIn,
 | 
				
			||||||
 | 
														Values:   []string{"test-value1", "test-value3"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Key:      "test-key2",
 | 
				
			||||||
 | 
														Operator: v1.NodeSelectorOpIn,
 | 
				
			||||||
 | 
														Values:   []string{"test-value0", "test-value2"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:          "invalid-key",
 | 
				
			||||||
 | 
								expectSuccess: false,
 | 
				
			||||||
 | 
								pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{
 | 
				
			||||||
 | 
									RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
 | 
				
			||||||
 | 
										NodeSelectorTerms: []v1.NodeSelectorTerm{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												MatchExpressions: []v1.NodeSelectorRequirement{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Key:      "test-key1",
 | 
				
			||||||
 | 
														Operator: v1.NodeSelectorOpIn,
 | 
				
			||||||
 | 
														Values:   []string{"test-value1", "test-value3"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Key:      "test-key3",
 | 
				
			||||||
 | 
														Operator: v1.NodeSelectorOpIn,
 | 
				
			||||||
 | 
														Values:   []string{"test-value0", "test-value2"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:          "invalid-values",
 | 
				
			||||||
 | 
								expectSuccess: false,
 | 
				
			||||||
 | 
								pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{
 | 
				
			||||||
 | 
									RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
 | 
				
			||||||
 | 
										NodeSelectorTerms: []v1.NodeSelectorTerm{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												MatchExpressions: []v1.NodeSelectorRequirement{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Key:      "test-key1",
 | 
				
			||||||
 | 
														Operator: v1.NodeSelectorOpIn,
 | 
				
			||||||
 | 
														Values:   []string{"test-value3", "test-value4"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Key:      "test-key2",
 | 
				
			||||||
 | 
														Operator: v1.NodeSelectorOpIn,
 | 
				
			||||||
 | 
														Values:   []string{"test-value0", "test-value2"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, c := range cases {
 | 
				
			||||||
 | 
							err := CheckNodeAffinity(c.pv, nodeLabels)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil && c.expectSuccess {
 | 
				
			||||||
 | 
								t.Errorf("CheckTopology %v returned error: %v", c.name, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err == nil && !c.expectSuccess {
 | 
				
			||||||
 | 
								t.Errorf("CheckTopology %v returned success, expected error", c.name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.PersistentVolume {
 | 
				
			||||||
 | 
						objMeta := metav1.ObjectMeta{Name: "test-constraints"}
 | 
				
			||||||
 | 
						objMeta.Annotations = map[string]string{}
 | 
				
			||||||
 | 
						err := helper.StorageNodeAffinityToAlphaAnnotation(objMeta.Annotations, affinity)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("Failed to get node affinity annotation: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &v1.PersistentVolume{
 | 
				
			||||||
 | 
							ObjectMeta: objMeta,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -22,6 +22,8 @@ go_library(
 | 
				
			|||||||
        "//pkg/api/v1/helper:go_default_library",
 | 
					        "//pkg/api/v1/helper:go_default_library",
 | 
				
			||||||
        "//pkg/api/v1/helper/qos:go_default_library",
 | 
					        "//pkg/api/v1/helper/qos:go_default_library",
 | 
				
			||||||
        "//pkg/client/listers/core/v1:go_default_library",
 | 
					        "//pkg/client/listers/core/v1:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/features:go_default_library",
 | 
				
			||||||
 | 
					        "//pkg/volume/util:go_default_library",
 | 
				
			||||||
        "//plugin/pkg/scheduler/algorithm:go_default_library",
 | 
					        "//plugin/pkg/scheduler/algorithm:go_default_library",
 | 
				
			||||||
        "//plugin/pkg/scheduler/algorithm/priorities/util:go_default_library",
 | 
					        "//plugin/pkg/scheduler/algorithm/priorities/util:go_default_library",
 | 
				
			||||||
        "//plugin/pkg/scheduler/schedulercache:go_default_library",
 | 
					        "//plugin/pkg/scheduler/schedulercache:go_default_library",
 | 
				
			||||||
@@ -30,7 +32,9 @@ go_library(
 | 
				
			|||||||
        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/client-go/util/workqueue:go_default_library",
 | 
					        "//vendor/k8s.io/client-go/util/workqueue:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset:go_default_library",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,6 +37,7 @@ var (
 | 
				
			|||||||
	ErrMaxVolumeCountExceeded    = newPredicateFailureError("MaxVolumeCount")
 | 
						ErrMaxVolumeCountExceeded    = newPredicateFailureError("MaxVolumeCount")
 | 
				
			||||||
	ErrNodeUnderMemoryPressure   = newPredicateFailureError("NodeUnderMemoryPressure")
 | 
						ErrNodeUnderMemoryPressure   = newPredicateFailureError("NodeUnderMemoryPressure")
 | 
				
			||||||
	ErrNodeUnderDiskPressure     = newPredicateFailureError("NodeUnderDiskPressure")
 | 
						ErrNodeUnderDiskPressure     = newPredicateFailureError("NodeUnderDiskPressure")
 | 
				
			||||||
 | 
						ErrVolumeNodeConflict        = newPredicateFailureError("NoVolumeNodeConflict")
 | 
				
			||||||
	// ErrFakePredicate is used for test only. The fake predicates returning false also returns error
 | 
						// ErrFakePredicate is used for test only. The fake predicates returning false also returns error
 | 
				
			||||||
	// as ErrFakePredicate.
 | 
						// as ErrFakePredicate.
 | 
				
			||||||
	ErrFakePredicate = newPredicateFailureError("FakePredicateError")
 | 
						ErrFakePredicate = newPredicateFailureError("FakePredicateError")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,14 +29,18 @@ import (
 | 
				
			|||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/labels"
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
						utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
	"k8s.io/client-go/util/workqueue"
 | 
						"k8s.io/client-go/util/workqueue"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api/v1"
 | 
						"k8s.io/kubernetes/pkg/api/v1"
 | 
				
			||||||
	v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
 | 
						v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
 | 
				
			||||||
	v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos"
 | 
						v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos"
 | 
				
			||||||
	corelisters "k8s.io/kubernetes/pkg/client/listers/core/v1"
 | 
						corelisters "k8s.io/kubernetes/pkg/client/listers/core/v1"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/features"
 | 
				
			||||||
 | 
						volumeutil "k8s.io/kubernetes/pkg/volume/util"
 | 
				
			||||||
	"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
 | 
						"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
 | 
				
			||||||
	priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util"
 | 
						priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util"
 | 
				
			||||||
	"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache"
 | 
						"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache"
 | 
				
			||||||
 | 
						"k8s.io/metrics/pkg/client/clientset_generated/clientset"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// predicatePrecomputations: Helper types/variables...
 | 
					// predicatePrecomputations: Helper types/variables...
 | 
				
			||||||
@@ -1264,3 +1268,81 @@ func CheckNodeDiskPressurePredicate(pod *v1.Pod, meta interface{}, nodeInfo *sch
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return true, nil, nil
 | 
						return true, nil, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type VolumeNodeChecker struct {
 | 
				
			||||||
 | 
						pvInfo  PersistentVolumeInfo
 | 
				
			||||||
 | 
						pvcInfo PersistentVolumeClaimInfo
 | 
				
			||||||
 | 
						client  clientset.Interface
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VolumeNodeChecker evaluates if a pod can fit due to the volumes it requests, given
 | 
				
			||||||
 | 
					// that some volumes have node topology constraints, particularly when using Local PVs.
 | 
				
			||||||
 | 
					// The requirement is that any pod that uses a PVC that is bound to a PV with topology constraints
 | 
				
			||||||
 | 
					// must be scheduled to a node that satisfies the PV's topology labels.
 | 
				
			||||||
 | 
					func NewVolumeNodePredicate(pvInfo PersistentVolumeInfo, pvcInfo PersistentVolumeClaimInfo, client clientset.Interface) algorithm.FitPredicate {
 | 
				
			||||||
 | 
						c := &VolumeNodeChecker{
 | 
				
			||||||
 | 
							pvInfo:  pvInfo,
 | 
				
			||||||
 | 
							pvcInfo: pvcInfo,
 | 
				
			||||||
 | 
							client:  client,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return c.predicate
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *VolumeNodeChecker) predicate(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (bool, []algorithm.PredicateFailureReason, error) {
 | 
				
			||||||
 | 
						if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) {
 | 
				
			||||||
 | 
							return true, nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If a pod doesn't have any volume attached to it, the predicate will always be true.
 | 
				
			||||||
 | 
						// Thus we make a fast path for it, to avoid unnecessary computations in this case.
 | 
				
			||||||
 | 
						if len(pod.Spec.Volumes) == 0 {
 | 
				
			||||||
 | 
							return true, nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node := nodeInfo.Node()
 | 
				
			||||||
 | 
						if node == nil {
 | 
				
			||||||
 | 
							return false, nil, fmt.Errorf("node not found")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						glog.V(2).Infof("Checking for prebound volumes with node affinity")
 | 
				
			||||||
 | 
						namespace := pod.Namespace
 | 
				
			||||||
 | 
						manifest := &(pod.Spec)
 | 
				
			||||||
 | 
						for i := range manifest.Volumes {
 | 
				
			||||||
 | 
							volume := &manifest.Volumes[i]
 | 
				
			||||||
 | 
							if volume.PersistentVolumeClaim == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							pvcName := volume.PersistentVolumeClaim.ClaimName
 | 
				
			||||||
 | 
							if pvcName == "" {
 | 
				
			||||||
 | 
								return false, nil, fmt.Errorf("PersistentVolumeClaim had no name")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							pvc, err := c.pvcInfo.GetPersistentVolumeClaimInfo(namespace, pvcName)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if pvc == nil {
 | 
				
			||||||
 | 
								return false, nil, fmt.Errorf("PersistentVolumeClaim was not found: %q", pvcName)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							pvName := pvc.Spec.VolumeName
 | 
				
			||||||
 | 
							if pvName == "" {
 | 
				
			||||||
 | 
								return false, nil, fmt.Errorf("PersistentVolumeClaim is not bound: %q", pvcName)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pv, err := c.pvInfo.GetPersistentVolumeInfo(pvName)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if pv == nil {
 | 
				
			||||||
 | 
								return false, nil, fmt.Errorf("PersistentVolume not found: %q", pvName)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = volumeutil.CheckNodeAffinity(pv, node.Labels)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								glog.V(2).Infof("Won't schedule pod %q onto node %q due to volume %q node mismatch: %v", pod.Name, node.Name, pvName, err.Error())
 | 
				
			||||||
 | 
								return false, []algorithm.PredicateFailureReason{ErrVolumeNodeConflict}, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							glog.V(4).Infof("VolumeNode predicate allows node %q for pod %q due to volume %q", node.Name, pod.Name, pvName)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true, nil, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,6 +313,77 @@ func TestCompatibility_v1_Scheduler(t *testing.T) {
 | 
				
			|||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							// Do not change this JSON after the corresponding release has been tagged.
 | 
				
			||||||
 | 
							// A failure indicates backwards compatibility with the specified release was broken.
 | 
				
			||||||
 | 
							"1.7": {
 | 
				
			||||||
 | 
								JSON: `{
 | 
				
			||||||
 | 
							  "kind": "Policy",
 | 
				
			||||||
 | 
							  "apiVersion": "v1",
 | 
				
			||||||
 | 
							  "predicates": [
 | 
				
			||||||
 | 
								{"name": "MatchNodeSelector"},
 | 
				
			||||||
 | 
								{"name": "PodFitsResources"},
 | 
				
			||||||
 | 
								{"name": "PodFitsHostPorts"},
 | 
				
			||||||
 | 
								{"name": "HostName"},
 | 
				
			||||||
 | 
								{"name": "NoDiskConflict"},
 | 
				
			||||||
 | 
								{"name": "NoVolumeZoneConflict"},
 | 
				
			||||||
 | 
								{"name": "PodToleratesNodeTaints"},
 | 
				
			||||||
 | 
								{"name": "CheckNodeMemoryPressure"},
 | 
				
			||||||
 | 
								{"name": "CheckNodeDiskPressure"},
 | 
				
			||||||
 | 
								{"name": "MaxEBSVolumeCount"},
 | 
				
			||||||
 | 
								{"name": "MaxGCEPDVolumeCount"},
 | 
				
			||||||
 | 
								{"name": "MaxAzureDiskVolumeCount"},
 | 
				
			||||||
 | 
								{"name": "MatchInterPodAffinity"},
 | 
				
			||||||
 | 
								{"name": "GeneralPredicates"},
 | 
				
			||||||
 | 
								{"name": "TestServiceAffinity", "argument": {"serviceAffinity" : {"labels" : ["region"]}}},
 | 
				
			||||||
 | 
								{"name": "TestLabelsPresence",  "argument": {"labelsPresence"  : {"labels" : ["foo"], "presence":true}}},
 | 
				
			||||||
 | 
								{"name": "NoVolumeNodeConflict"}
 | 
				
			||||||
 | 
							  ],"priorities": [
 | 
				
			||||||
 | 
								{"name": "EqualPriority",   "weight": 2},
 | 
				
			||||||
 | 
								{"name": "ImageLocalityPriority",   "weight": 2},
 | 
				
			||||||
 | 
								{"name": "LeastRequestedPriority",   "weight": 2},
 | 
				
			||||||
 | 
								{"name": "BalancedResourceAllocation",   "weight": 2},
 | 
				
			||||||
 | 
								{"name": "SelectorSpreadPriority",   "weight": 2},
 | 
				
			||||||
 | 
								{"name": "NodePreferAvoidPodsPriority",   "weight": 2},
 | 
				
			||||||
 | 
								{"name": "NodeAffinityPriority",   "weight": 2},
 | 
				
			||||||
 | 
								{"name": "TaintTolerationPriority",   "weight": 2},
 | 
				
			||||||
 | 
								{"name": "InterPodAffinityPriority",   "weight": 2},
 | 
				
			||||||
 | 
								{"name": "MostRequestedPriority",   "weight": 2}
 | 
				
			||||||
 | 
							  ]
 | 
				
			||||||
 | 
							}`,
 | 
				
			||||||
 | 
								ExpectedPolicy: schedulerapi.Policy{
 | 
				
			||||||
 | 
									Predicates: []schedulerapi.PredicatePolicy{
 | 
				
			||||||
 | 
										{Name: "MatchNodeSelector"},
 | 
				
			||||||
 | 
										{Name: "PodFitsResources"},
 | 
				
			||||||
 | 
										{Name: "PodFitsHostPorts"},
 | 
				
			||||||
 | 
										{Name: "HostName"},
 | 
				
			||||||
 | 
										{Name: "NoDiskConflict"},
 | 
				
			||||||
 | 
										{Name: "NoVolumeZoneConflict"},
 | 
				
			||||||
 | 
										{Name: "PodToleratesNodeTaints"},
 | 
				
			||||||
 | 
										{Name: "CheckNodeMemoryPressure"},
 | 
				
			||||||
 | 
										{Name: "CheckNodeDiskPressure"},
 | 
				
			||||||
 | 
										{Name: "MaxEBSVolumeCount"},
 | 
				
			||||||
 | 
										{Name: "MaxGCEPDVolumeCount"},
 | 
				
			||||||
 | 
										{Name: "MaxAzureDiskVolumeCount"},
 | 
				
			||||||
 | 
										{Name: "MatchInterPodAffinity"},
 | 
				
			||||||
 | 
										{Name: "GeneralPredicates"},
 | 
				
			||||||
 | 
										{Name: "TestServiceAffinity", Argument: &schedulerapi.PredicateArgument{ServiceAffinity: &schedulerapi.ServiceAffinity{Labels: []string{"region"}}}},
 | 
				
			||||||
 | 
										{Name: "TestLabelsPresence", Argument: &schedulerapi.PredicateArgument{LabelsPresence: &schedulerapi.LabelsPresence{Labels: []string{"foo"}, Presence: true}}},
 | 
				
			||||||
 | 
										{Name: "NoVolumeNodeConflict"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Priorities: []schedulerapi.PriorityPolicy{
 | 
				
			||||||
 | 
										{Name: "EqualPriority", Weight: 2},
 | 
				
			||||||
 | 
										{Name: "ImageLocalityPriority", Weight: 2},
 | 
				
			||||||
 | 
										{Name: "LeastRequestedPriority", Weight: 2},
 | 
				
			||||||
 | 
										{Name: "BalancedResourceAllocation", Weight: 2},
 | 
				
			||||||
 | 
										{Name: "SelectorSpreadPriority", Weight: 2},
 | 
				
			||||||
 | 
										{Name: "NodePreferAvoidPodsPriority", Weight: 2},
 | 
				
			||||||
 | 
										{Name: "NodeAffinityPriority", Weight: 2},
 | 
				
			||||||
 | 
										{Name: "TaintTolerationPriority", Weight: 2},
 | 
				
			||||||
 | 
										{Name: "InterPodAffinityPriority", Weight: 2},
 | 
				
			||||||
 | 
										{Name: "MostRequestedPriority", Weight: 2},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	registeredPredicates := sets.NewString(factory.ListRegisteredFitPredicates()...)
 | 
						registeredPredicates := sets.NewString(factory.ListRegisteredFitPredicates()...)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -176,6 +176,14 @@ func defaultPredicates() sets.String {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Fit is determined by node disk pressure condition.
 | 
							// Fit is determined by node disk pressure condition.
 | 
				
			||||||
		factory.RegisterFitPredicate("CheckNodeDiskPressure", predicates.CheckNodeDiskPressurePredicate),
 | 
							factory.RegisterFitPredicate("CheckNodeDiskPressure", predicates.CheckNodeDiskPressurePredicate),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Fit is determined by volume zone requirements.
 | 
				
			||||||
 | 
							factory.RegisterFitPredicateFactory(
 | 
				
			||||||
 | 
								"NoVolumeNodeConflict",
 | 
				
			||||||
 | 
								func(args factory.PluginFactoryArgs) algorithm.FitPredicate {
 | 
				
			||||||
 | 
									return predicates.NewVolumeNodePredicate(args.PVInfo, args.PVCInfo, nil)
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -590,6 +590,10 @@ func GetStorageNodeAffinityFromAnnotation(annotations map[string]string) (*api.N
 | 
				
			|||||||
// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes
 | 
					// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes
 | 
				
			||||||
// TODO: update when storage node affinity graduates to beta
 | 
					// TODO: update when storage node affinity graduates to beta
 | 
				
			||||||
func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *api.NodeAffinity) error {
 | 
					func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *api.NodeAffinity) error {
 | 
				
			||||||
 | 
						if affinity == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	json, err := json.Marshal(*affinity)
 | 
						json, err := json.Marshal(*affinity)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,7 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/util/uuid"
 | 
						"k8s.io/apimachinery/pkg/util/uuid"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api"
 | 
						"k8s.io/kubernetes/pkg/api"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api/v1"
 | 
						"k8s.io/kubernetes/pkg/api/v1"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api/v1/helper"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
 | 
						"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
 | 
				
			||||||
	awscloud "k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
 | 
						awscloud "k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
 | 
				
			||||||
	gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
 | 
						gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
 | 
				
			||||||
@@ -73,11 +74,13 @@ type PVCMap map[types.NamespacedName]pvcval
 | 
				
			|||||||
//	 	},
 | 
					//	 	},
 | 
				
			||||||
//	 }
 | 
					//	 }
 | 
				
			||||||
type PersistentVolumeConfig struct {
 | 
					type PersistentVolumeConfig struct {
 | 
				
			||||||
	PVSource      v1.PersistentVolumeSource
 | 
						PVSource         v1.PersistentVolumeSource
 | 
				
			||||||
	Prebind       *v1.PersistentVolumeClaim
 | 
						Prebind          *v1.PersistentVolumeClaim
 | 
				
			||||||
	ReclaimPolicy v1.PersistentVolumeReclaimPolicy
 | 
						ReclaimPolicy    v1.PersistentVolumeReclaimPolicy
 | 
				
			||||||
	NamePrefix    string
 | 
						NamePrefix       string
 | 
				
			||||||
	Labels        labels.Set
 | 
						Labels           labels.Set
 | 
				
			||||||
 | 
						StorageClassName string
 | 
				
			||||||
 | 
						NodeAffinity     *v1.NodeAffinity
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PersistentVolumeClaimConfig is consumed by MakePersistentVolumeClaim() to generate a PVC object.
 | 
					// PersistentVolumeClaimConfig is consumed by MakePersistentVolumeClaim() to generate a PVC object.
 | 
				
			||||||
@@ -85,9 +88,10 @@ type PersistentVolumeConfig struct {
 | 
				
			|||||||
// (+optional) Annotations defines the PVC's annotations
 | 
					// (+optional) Annotations defines the PVC's annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type PersistentVolumeClaimConfig struct {
 | 
					type PersistentVolumeClaimConfig struct {
 | 
				
			||||||
	AccessModes []v1.PersistentVolumeAccessMode
 | 
						AccessModes      []v1.PersistentVolumeAccessMode
 | 
				
			||||||
	Annotations map[string]string
 | 
						Annotations      map[string]string
 | 
				
			||||||
	Selector    *metav1.LabelSelector
 | 
						Selector         *metav1.LabelSelector
 | 
				
			||||||
 | 
						StorageClassName *string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Clean up a pv and pvc in a single pv/pvc test case.
 | 
					// Clean up a pv and pvc in a single pv/pvc test case.
 | 
				
			||||||
@@ -561,7 +565,7 @@ func makePvcKey(ns, name string) types.NamespacedName {
 | 
				
			|||||||
// is assigned, assumes "Retain". Specs are expected to match the test's PVC.
 | 
					// is assigned, assumes "Retain". Specs are expected to match the test's PVC.
 | 
				
			||||||
// Note: the passed-in claim does not have a name until it is created and thus the PV's
 | 
					// Note: the passed-in claim does not have a name until it is created and thus the PV's
 | 
				
			||||||
//   ClaimRef cannot be completely filled-in in this func. Therefore, the ClaimRef's name
 | 
					//   ClaimRef cannot be completely filled-in in this func. Therefore, the ClaimRef's name
 | 
				
			||||||
//   is added later in createPVCPV.
 | 
					//   is added later in CreatePVCPV.
 | 
				
			||||||
func MakePersistentVolume(pvConfig PersistentVolumeConfig) *v1.PersistentVolume {
 | 
					func MakePersistentVolume(pvConfig PersistentVolumeConfig) *v1.PersistentVolume {
 | 
				
			||||||
	var claimRef *v1.ObjectReference
 | 
						var claimRef *v1.ObjectReference
 | 
				
			||||||
	// If the reclaimPolicy is not provided, assume Retain
 | 
						// If the reclaimPolicy is not provided, assume Retain
 | 
				
			||||||
@@ -575,7 +579,7 @@ func MakePersistentVolume(pvConfig PersistentVolumeConfig) *v1.PersistentVolume
 | 
				
			|||||||
			Namespace: pvConfig.Prebind.Namespace,
 | 
								Namespace: pvConfig.Prebind.Namespace,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &v1.PersistentVolume{
 | 
						pv := &v1.PersistentVolume{
 | 
				
			||||||
		ObjectMeta: metav1.ObjectMeta{
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
			GenerateName: pvConfig.NamePrefix,
 | 
								GenerateName: pvConfig.NamePrefix,
 | 
				
			||||||
			Labels:       pvConfig.Labels,
 | 
								Labels:       pvConfig.Labels,
 | 
				
			||||||
@@ -594,9 +598,16 @@ func MakePersistentVolume(pvConfig PersistentVolumeConfig) *v1.PersistentVolume
 | 
				
			|||||||
				v1.ReadOnlyMany,
 | 
									v1.ReadOnlyMany,
 | 
				
			||||||
				v1.ReadWriteMany,
 | 
									v1.ReadWriteMany,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			ClaimRef: claimRef,
 | 
								ClaimRef:         claimRef,
 | 
				
			||||||
 | 
								StorageClassName: pvConfig.StorageClassName,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						err := helper.StorageNodeAffinityToAlphaAnnotation(pv.Annotations, pvConfig.NodeAffinity)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							Logf("Setting storage node affinity failed: %v", err)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return pv
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Returns a PVC definition based on the namespace.
 | 
					// Returns a PVC definition based on the namespace.
 | 
				
			||||||
@@ -625,6 +636,7 @@ func MakePersistentVolumeClaim(cfg PersistentVolumeClaimConfig, ns string) *v1.P
 | 
				
			|||||||
					v1.ResourceName(v1.ResourceStorage): resource.MustParse("1Gi"),
 | 
										v1.ResourceName(v1.ResourceStorage): resource.MustParse("1Gi"),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								StorageClassName: cfg.StorageClassName,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,11 +76,18 @@ type VolumeTestConfig struct {
 | 
				
			|||||||
	ServerImage string
 | 
						ServerImage string
 | 
				
			||||||
	// Ports to export from the server pod. TCP only.
 | 
						// Ports to export from the server pod. TCP only.
 | 
				
			||||||
	ServerPorts []int
 | 
						ServerPorts []int
 | 
				
			||||||
 | 
						// Commands to run in the container image.
 | 
				
			||||||
 | 
						ServerCmds []string
 | 
				
			||||||
	// Arguments to pass to the container image.
 | 
						// Arguments to pass to the container image.
 | 
				
			||||||
	ServerArgs []string
 | 
						ServerArgs []string
 | 
				
			||||||
	// Volumes needed to be mounted to the server container from the host
 | 
						// Volumes needed to be mounted to the server container from the host
 | 
				
			||||||
	// map <host (source) path> -> <container (dst.) path>
 | 
						// map <host (source) path> -> <container (dst.) path>
 | 
				
			||||||
	ServerVolumes map[string]string
 | 
						ServerVolumes map[string]string
 | 
				
			||||||
 | 
						// Wait for the pod to terminate successfully
 | 
				
			||||||
 | 
						// False indicates that the pod is long running
 | 
				
			||||||
 | 
						WaitForCompletion bool
 | 
				
			||||||
 | 
						// NodeName to run pod on.  Default is any node.
 | 
				
			||||||
 | 
						NodeName string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// VolumeTest contains a volume to mount into a client pod and its
 | 
					// VolumeTest contains a volume to mount into a client pod and its
 | 
				
			||||||
@@ -133,6 +140,11 @@ func StartVolumeServer(client clientset.Interface, config VolumeTestConfig) *v1.
 | 
				
			|||||||
	By(fmt.Sprint("creating ", serverPodName, " pod"))
 | 
						By(fmt.Sprint("creating ", serverPodName, " pod"))
 | 
				
			||||||
	privileged := new(bool)
 | 
						privileged := new(bool)
 | 
				
			||||||
	*privileged = true
 | 
						*privileged = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						restartPolicy := v1.RestartPolicyAlways
 | 
				
			||||||
 | 
						if config.WaitForCompletion {
 | 
				
			||||||
 | 
							restartPolicy = v1.RestartPolicyNever
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	serverPod := &v1.Pod{
 | 
						serverPod := &v1.Pod{
 | 
				
			||||||
		TypeMeta: metav1.TypeMeta{
 | 
							TypeMeta: metav1.TypeMeta{
 | 
				
			||||||
			Kind:       "Pod",
 | 
								Kind:       "Pod",
 | 
				
			||||||
@@ -153,12 +165,15 @@ func StartVolumeServer(client clientset.Interface, config VolumeTestConfig) *v1.
 | 
				
			|||||||
					SecurityContext: &v1.SecurityContext{
 | 
										SecurityContext: &v1.SecurityContext{
 | 
				
			||||||
						Privileged: privileged,
 | 
											Privileged: privileged,
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
 | 
										Command:      config.ServerCmds,
 | 
				
			||||||
					Args:         config.ServerArgs,
 | 
										Args:         config.ServerArgs,
 | 
				
			||||||
					Ports:        serverPodPorts,
 | 
										Ports:        serverPodPorts,
 | 
				
			||||||
					VolumeMounts: mounts,
 | 
										VolumeMounts: mounts,
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			Volumes: volumes,
 | 
								Volumes:       volumes,
 | 
				
			||||||
 | 
								RestartPolicy: restartPolicy,
 | 
				
			||||||
 | 
								NodeName:      config.NodeName,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -176,12 +191,16 @@ func StartVolumeServer(client clientset.Interface, config VolumeTestConfig) *v1.
 | 
				
			|||||||
			ExpectNoError(err, "Failed to create %q pod: %v", serverPodName, err)
 | 
								ExpectNoError(err, "Failed to create %q pod: %v", serverPodName, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ExpectNoError(WaitForPodRunningInNamespace(client, serverPod))
 | 
						if config.WaitForCompletion {
 | 
				
			||||||
 | 
							ExpectNoError(WaitForPodSuccessInNamespace(client, serverPod.Name, serverPod.Namespace))
 | 
				
			||||||
	if pod == nil {
 | 
							ExpectNoError(podClient.Delete(serverPod.Name, nil))
 | 
				
			||||||
		By(fmt.Sprintf("locating the %q server pod", serverPodName))
 | 
						} else {
 | 
				
			||||||
		pod, err = podClient.Get(serverPodName, metav1.GetOptions{})
 | 
							ExpectNoError(WaitForPodRunningInNamespace(client, serverPod))
 | 
				
			||||||
		ExpectNoError(err, "Cannot locate the server pod %q: %v", serverPodName, err)
 | 
							if pod == nil {
 | 
				
			||||||
 | 
								By(fmt.Sprintf("locating the %q server pod", serverPodName))
 | 
				
			||||||
 | 
								pod, err = podClient.Get(serverPodName, metav1.GetOptions{})
 | 
				
			||||||
 | 
								ExpectNoError(err, "Cannot locate the server pod %q: %v", serverPodName, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return pod
 | 
						return pod
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ go_library(
 | 
				
			|||||||
        "persistent_volumes.go",
 | 
					        "persistent_volumes.go",
 | 
				
			||||||
        "persistent_volumes-disruptive.go",
 | 
					        "persistent_volumes-disruptive.go",
 | 
				
			||||||
        "persistent_volumes-gce.go",
 | 
					        "persistent_volumes-gce.go",
 | 
				
			||||||
 | 
					        "persistent_volumes-local.go",
 | 
				
			||||||
        "persistent_volumes-vsphere.go",
 | 
					        "persistent_volumes-vsphere.go",
 | 
				
			||||||
        "pv_reclaimpolicy.go",
 | 
					        "pv_reclaimpolicy.go",
 | 
				
			||||||
        "pvc_label_selector.go",
 | 
					        "pvc_label_selector.go",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										245
									
								
								test/e2e/storage/persistent_volumes-local.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								test/e2e/storage/persistent_volumes-local.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,245 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2017 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 storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						. "github.com/onsi/ginkgo"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/uuid"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api/v1"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/test/e2e/framework"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type localTestConfig struct {
 | 
				
			||||||
 | 
						ns     string
 | 
				
			||||||
 | 
						nodes  []v1.Node
 | 
				
			||||||
 | 
						client clientset.Interface
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type localTestVolume struct {
 | 
				
			||||||
 | 
						// Node that the volume is on
 | 
				
			||||||
 | 
						node *v1.Node
 | 
				
			||||||
 | 
						// Path to the volume on the host node
 | 
				
			||||||
 | 
						hostDir string
 | 
				
			||||||
 | 
						// Path to the volume in the local util container
 | 
				
			||||||
 | 
						containerDir string
 | 
				
			||||||
 | 
						// PVC for this volume
 | 
				
			||||||
 | 
						pvc *v1.PersistentVolumeClaim
 | 
				
			||||||
 | 
						// PV for this volume
 | 
				
			||||||
 | 
						pv *v1.PersistentVolume
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// TODO: This may not be available/writable on all images.
 | 
				
			||||||
 | 
						hostBase      = "/tmp"
 | 
				
			||||||
 | 
						containerBase = "/myvol"
 | 
				
			||||||
 | 
						testFile      = "test-file"
 | 
				
			||||||
 | 
						testContents  = "testdata"
 | 
				
			||||||
 | 
						testSC        = "local-test-storagclass"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ = framework.KubeDescribe("[Volume] PersistentVolumes-local [Feature:LocalPersistentVolumes] [Serial]", func() {
 | 
				
			||||||
 | 
						f := framework.NewDefaultFramework("persistent-local-volumes-test")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							config *localTestConfig
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						BeforeEach(func() {
 | 
				
			||||||
 | 
							config = &localTestConfig{
 | 
				
			||||||
 | 
								ns:     f.Namespace.Name,
 | 
				
			||||||
 | 
								client: f.ClientSet,
 | 
				
			||||||
 | 
								nodes:  []v1.Node{},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get all the schedulable nodes
 | 
				
			||||||
 | 
							nodes, err := config.client.CoreV1().Nodes().List(metav1.ListOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								framework.Failf("Failed to get nodes: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, node := range nodes.Items {
 | 
				
			||||||
 | 
								if !node.Spec.Unschedulable {
 | 
				
			||||||
 | 
									// TODO: does this need to be a deep copy
 | 
				
			||||||
 | 
									config.nodes = append(config.nodes, node)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(config.nodes) == 0 {
 | 
				
			||||||
 | 
								framework.Failf("No available nodes for scheduling")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Context("when one pod requests one prebound PVC", func() {
 | 
				
			||||||
 | 
							var (
 | 
				
			||||||
 | 
								testVol *localTestVolume
 | 
				
			||||||
 | 
								node    *v1.Node
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							BeforeEach(func() {
 | 
				
			||||||
 | 
								// Choose the first node
 | 
				
			||||||
 | 
								node = &config.nodes[0]
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							AfterEach(func() {
 | 
				
			||||||
 | 
								cleanupLocalVolume(config, testVol)
 | 
				
			||||||
 | 
								testVol = nil
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							It("should be able to mount and read from the volume", func() {
 | 
				
			||||||
 | 
								By("Initializing test volume")
 | 
				
			||||||
 | 
								testVol = setupLocalVolume(config, node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								By("Creating local PVC and PV")
 | 
				
			||||||
 | 
								createLocalPVCPV(config, testVol)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								By("Creating a pod to consume the PV")
 | 
				
			||||||
 | 
								readCmd := fmt.Sprintf("cat /mnt/volume1/%s", testFile)
 | 
				
			||||||
 | 
								podSpec := createLocalPod(config, testVol, readCmd)
 | 
				
			||||||
 | 
								f.TestContainerOutput("pod consumes PV", podSpec, 0, []string{testContents})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							It("should be able to mount and write to the volume", func() {
 | 
				
			||||||
 | 
								By("Initializing test volume")
 | 
				
			||||||
 | 
								testVol = setupLocalVolume(config, node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								By("Creating local PVC and PV")
 | 
				
			||||||
 | 
								createLocalPVCPV(config, testVol)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								By("Creating a pod to write to the PV")
 | 
				
			||||||
 | 
								testFilePath := filepath.Join("/mnt/volume1", testFile)
 | 
				
			||||||
 | 
								cmd := fmt.Sprintf("echo %s > %s; cat %s", testVol.hostDir, testFilePath, testFilePath)
 | 
				
			||||||
 | 
								podSpec := createLocalPod(config, testVol, cmd)
 | 
				
			||||||
 | 
								f.TestContainerOutput("pod writes to PV", podSpec, 0, []string{testVol.hostDir})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Launches a pod with hostpath volume on a specific node to setup a directory to use
 | 
				
			||||||
 | 
					// for the local PV
 | 
				
			||||||
 | 
					func setupLocalVolume(config *localTestConfig, node *v1.Node) *localTestVolume {
 | 
				
			||||||
 | 
						testDirName := "local-volume-test-" + string(uuid.NewUUID())
 | 
				
			||||||
 | 
						testDir := filepath.Join(containerBase, testDirName)
 | 
				
			||||||
 | 
						hostDir := filepath.Join(hostBase, testDirName)
 | 
				
			||||||
 | 
						testFilePath := filepath.Join(testDir, testFile)
 | 
				
			||||||
 | 
						writeCmd := fmt.Sprintf("mkdir %s; echo %s > %s", testDir, testContents, testFilePath)
 | 
				
			||||||
 | 
						framework.Logf("Creating local volume on node %q at path %q", node.Name, hostDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						runLocalUtil(config, node.Name, writeCmd)
 | 
				
			||||||
 | 
						return &localTestVolume{
 | 
				
			||||||
 | 
							node:         node,
 | 
				
			||||||
 | 
							hostDir:      hostDir,
 | 
				
			||||||
 | 
							containerDir: testDir,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Deletes the PVC/PV, and launches a pod with hostpath volume to remove the test directory
 | 
				
			||||||
 | 
					func cleanupLocalVolume(config *localTestConfig, volume *localTestVolume) {
 | 
				
			||||||
 | 
						if volume == nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						By("Cleaning up PVC and PV")
 | 
				
			||||||
 | 
						errs := framework.PVPVCCleanup(config.client, config.ns, volume.pv, volume.pvc)
 | 
				
			||||||
 | 
						if len(errs) > 0 {
 | 
				
			||||||
 | 
							framework.Logf("AfterEach: Failed to delete PV and/or PVC: %v", utilerrors.NewAggregate(errs))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						By("Removing the test directory")
 | 
				
			||||||
 | 
						removeCmd := fmt.Sprintf("rm -r %s", volume.containerDir)
 | 
				
			||||||
 | 
						runLocalUtil(config, volume.node.Name, removeCmd)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func runLocalUtil(config *localTestConfig, nodeName, cmd string) {
 | 
				
			||||||
 | 
						framework.StartVolumeServer(config.client, framework.VolumeTestConfig{
 | 
				
			||||||
 | 
							Namespace:   config.ns,
 | 
				
			||||||
 | 
							Prefix:      "local-volume-init",
 | 
				
			||||||
 | 
							ServerImage: "gcr.io/google_containers/busybox:1.24",
 | 
				
			||||||
 | 
							ServerCmds:  []string{"/bin/sh"},
 | 
				
			||||||
 | 
							ServerArgs:  []string{"-c", cmd},
 | 
				
			||||||
 | 
							ServerVolumes: map[string]string{
 | 
				
			||||||
 | 
								hostBase: containerBase,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							WaitForCompletion: true,
 | 
				
			||||||
 | 
							NodeName:          nodeName,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func makeLocalPVCConfig() framework.PersistentVolumeClaimConfig {
 | 
				
			||||||
 | 
						sc := testSC
 | 
				
			||||||
 | 
						return framework.PersistentVolumeClaimConfig{
 | 
				
			||||||
 | 
							AccessModes:      []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
 | 
				
			||||||
 | 
							StorageClassName: &sc,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func makeLocalPVConfig(volume *localTestVolume) framework.PersistentVolumeConfig {
 | 
				
			||||||
 | 
						// TODO: hostname may not be the best option
 | 
				
			||||||
 | 
						nodeKey := "kubernetes.io/hostname"
 | 
				
			||||||
 | 
						if volume.node.Labels == nil {
 | 
				
			||||||
 | 
							framework.Failf("Node does not have labels")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						nodeValue, found := volume.node.Labels[nodeKey]
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							framework.Failf("Node does not have required label %q", nodeKey)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return framework.PersistentVolumeConfig{
 | 
				
			||||||
 | 
							PVSource: v1.PersistentVolumeSource{
 | 
				
			||||||
 | 
								Local: &v1.LocalVolumeSource{
 | 
				
			||||||
 | 
									Path: volume.hostDir,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							NamePrefix:       "local-pv",
 | 
				
			||||||
 | 
							StorageClassName: testSC,
 | 
				
			||||||
 | 
							NodeAffinity: &v1.NodeAffinity{
 | 
				
			||||||
 | 
								RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
 | 
				
			||||||
 | 
									NodeSelectorTerms: []v1.NodeSelectorTerm{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											MatchExpressions: []v1.NodeSelectorRequirement{
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:      nodeKey,
 | 
				
			||||||
 | 
													Operator: v1.NodeSelectorOpIn,
 | 
				
			||||||
 | 
													Values:   []string{nodeValue},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Creates a PVC and PV with prebinding
 | 
				
			||||||
 | 
					func createLocalPVCPV(config *localTestConfig, volume *localTestVolume) {
 | 
				
			||||||
 | 
						pvcConfig := makeLocalPVCConfig()
 | 
				
			||||||
 | 
						pvConfig := makeLocalPVConfig(volume)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						volume.pv, volume.pvc, err = framework.CreatePVPVC(config.client, pvConfig, pvcConfig, config.ns, true)
 | 
				
			||||||
 | 
						framework.ExpectNoError(err)
 | 
				
			||||||
 | 
						framework.WaitOnPVandPVC(config.client, config.ns, volume.pv, volume.pvc)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func createLocalPod(config *localTestConfig, volume *localTestVolume, cmd string) *v1.Pod {
 | 
				
			||||||
 | 
						return framework.MakePod(config.ns, []*v1.PersistentVolumeClaim{volume.pvc}, false, cmd)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user