mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	Add dynamic provisioning process
This commit is contained in:
		| @@ -135,6 +135,14 @@ const annDynamicallyProvisioned = "pv.kubernetes.io/provisioned-by" | |||||||
| // a volume for this PVC. | // a volume for this PVC. | ||||||
| const annStorageProvisioner = "volume.beta.kubernetes.io/storage-provisioner" | const annStorageProvisioner = "volume.beta.kubernetes.io/storage-provisioner" | ||||||
|  |  | ||||||
|  | // This annotation is added to a PVC that has been triggered by scheduler to | ||||||
|  | // be dynamically provisioned. Its value is the name of the selected node. | ||||||
|  | const annSelectedNode = "volume.alpha.kubernetes.io/selected-node" | ||||||
|  |  | ||||||
|  | // If the provisioner name in a storage class is set to "kubernetes.io/no-provisioner", | ||||||
|  | // then dynamic provisioning is not supported by the storage. | ||||||
|  | const notSupportedProvisioner = "kubernetes.io/no-provisioner" | ||||||
|  |  | ||||||
| // CloudVolumeCreatedForClaimNamespaceTag is a name of a tag attached to a real volume in cloud (e.g. AWS EBS or GCE PD) | // CloudVolumeCreatedForClaimNamespaceTag is a name of a tag attached to a real volume in cloud (e.g. AWS EBS or GCE PD) | ||||||
| // with namespace of a persistent volume claim used to create this volume. | // with namespace of a persistent volume claim used to create this volume. | ||||||
| const CloudVolumeCreatedForClaimNamespaceTag = "kubernetes.io/created-for/pvc/namespace" | const CloudVolumeCreatedForClaimNamespaceTag = "kubernetes.io/created-for/pvc/namespace" | ||||||
|   | |||||||
| @@ -24,10 +24,12 @@ import ( | |||||||
|  |  | ||||||
| 	"k8s.io/api/core/v1" | 	"k8s.io/api/core/v1" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||||
| 	coreinformers "k8s.io/client-go/informers/core/v1" | 	coreinformers "k8s.io/client-go/informers/core/v1" | ||||||
| 	storageinformers "k8s.io/client-go/informers/storage/v1" | 	storageinformers "k8s.io/client-go/informers/storage/v1" | ||||||
| 	clientset "k8s.io/client-go/kubernetes" | 	clientset "k8s.io/client-go/kubernetes" | ||||||
| 	corelisters "k8s.io/client-go/listers/core/v1" | 	v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" | ||||||
|  | 	"k8s.io/kubernetes/pkg/features" | ||||||
| 	volumeutil "k8s.io/kubernetes/pkg/volume/util" | 	volumeutil "k8s.io/kubernetes/pkg/volume/util" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -58,24 +60,30 @@ type SchedulerVolumeBinder interface { | |||||||
| 	// If a PVC is bound, it checks if the PV's NodeAffinity matches the Node. | 	// If a PVC is bound, it checks if the PV's NodeAffinity matches the Node. | ||||||
| 	// Otherwise, it tries to find an available PV to bind to the PVC. | 	// Otherwise, it tries to find an available PV to bind to the PVC. | ||||||
| 	// | 	// | ||||||
| 	// It returns true if there are matching PVs that can satisfy all of the Pod's PVCs, and returns true | 	// It returns true if all of the Pod's PVCs have matching PVs or can be dynamic provisioned, | ||||||
| 	// if bound volumes satisfy the PV NodeAffinity. | 	// and returns true if bound volumes satisfy the PV NodeAffinity. | ||||||
| 	// | 	// | ||||||
| 	// This function is called by the volume binding scheduler predicate and can be called in parallel | 	// This function is called by the volume binding scheduler predicate and can be called in parallel | ||||||
| 	FindPodVolumes(pod *v1.Pod, node *v1.Node) (unboundVolumesSatisified, boundVolumesSatisfied bool, err error) | 	FindPodVolumes(pod *v1.Pod, node *v1.Node) (unboundVolumesSatisified, boundVolumesSatisfied bool, err error) | ||||||
|  |  | ||||||
| 	// AssumePodVolumes will take the PV matches for unbound PVCs and update the PV cache assuming | 	// AssumePodVolumes will: | ||||||
|  | 	// 1. Take the PV matches for unbound PVCs and update the PV cache assuming | ||||||
| 	// that the PV is prebound to the PVC. | 	// that the PV is prebound to the PVC. | ||||||
|  | 	// 2. Take the PVCs that need provisioning and update the PVC cache with related | ||||||
|  | 	// annotations set. | ||||||
| 	// | 	// | ||||||
| 	// It returns true if all volumes are fully bound, and returns true if any volume binding API operation needs | 	// It returns true if all volumes are fully bound, and returns true if any volume binding/provisioning | ||||||
| 	// to be done afterwards. | 	// API operation needs to be done afterwards. | ||||||
| 	// | 	// | ||||||
| 	// This function will modify assumedPod with the node name. | 	// This function will modify assumedPod with the node name. | ||||||
| 	// This function is called serially. | 	// This function is called serially. | ||||||
| 	AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (allFullyBound bool, bindingRequired bool, err error) | 	AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (allFullyBound bool, bindingRequired bool, err error) | ||||||
|  |  | ||||||
| 	// BindPodVolumes will initiate the volume binding by making the API call to prebind the PV | 	// BindPodVolumes will: | ||||||
|  | 	// 1. Initiate the volume binding by making the API call to prebind the PV | ||||||
| 	// to its matching PVC. | 	// to its matching PVC. | ||||||
|  | 	// 2. Trigger the volume provisioning by making the API call to set related | ||||||
|  | 	// annotations on the PVC | ||||||
| 	// | 	// | ||||||
| 	// This function can be called in parallel. | 	// This function can be called in parallel. | ||||||
| 	BindPodVolumes(assumedPod *v1.Pod) error | 	BindPodVolumes(assumedPod *v1.Pod) error | ||||||
| @@ -87,8 +95,7 @@ type SchedulerVolumeBinder interface { | |||||||
| type volumeBinder struct { | type volumeBinder struct { | ||||||
| 	ctrl *PersistentVolumeController | 	ctrl *PersistentVolumeController | ||||||
|  |  | ||||||
| 	// TODO: Need AssumeCache for PVC for dynamic provisioning | 	pvcCache PVCAssumeCache | ||||||
| 	pvcCache corelisters.PersistentVolumeClaimLister |  | ||||||
| 	pvCache  PVAssumeCache | 	pvCache  PVAssumeCache | ||||||
|  |  | ||||||
| 	// Stores binding decisions that were made in FindPodVolumes for use in AssumePodVolumes. | 	// Stores binding decisions that were made in FindPodVolumes for use in AssumePodVolumes. | ||||||
| @@ -111,7 +118,7 @@ func NewVolumeBinder( | |||||||
|  |  | ||||||
| 	b := &volumeBinder{ | 	b := &volumeBinder{ | ||||||
| 		ctrl:            ctrl, | 		ctrl:            ctrl, | ||||||
| 		pvcCache:        pvcInformer.Lister(), | 		pvcCache:        NewPVCAssumeCache(pvcInformer.Informer()), | ||||||
| 		pvCache:         NewPVAssumeCache(pvInformer.Informer()), | 		pvCache:         NewPVAssumeCache(pvInformer.Informer()), | ||||||
| 		podBindingCache: NewPodBindingCache(), | 		podBindingCache: NewPodBindingCache(), | ||||||
| 	} | 	} | ||||||
| @@ -123,7 +130,7 @@ func (b *volumeBinder) GetBindingsCache() PodBindingCache { | |||||||
| 	return b.podBindingCache | 	return b.podBindingCache | ||||||
| } | } | ||||||
|  |  | ||||||
| // FindPodVolumes caches the matching PVs per node in podBindingCache | // FindPodVolumes caches the matching PVs and PVCs to provision per node in podBindingCache | ||||||
| func (b *volumeBinder) FindPodVolumes(pod *v1.Pod, node *v1.Node) (unboundVolumesSatisfied, boundVolumesSatisfied bool, err error) { | func (b *volumeBinder) FindPodVolumes(pod *v1.Pod, node *v1.Node) (unboundVolumesSatisfied, boundVolumesSatisfied bool, err error) { | ||||||
| 	podName := getPodName(pod) | 	podName := getPodName(pod) | ||||||
|  |  | ||||||
| @@ -135,8 +142,8 @@ func (b *volumeBinder) FindPodVolumes(pod *v1.Pod, node *v1.Node) (unboundVolume | |||||||
| 	boundVolumesSatisfied = true | 	boundVolumesSatisfied = true | ||||||
|  |  | ||||||
| 	// The pod's volumes need to be processed in one call to avoid the race condition where | 	// The pod's volumes need to be processed in one call to avoid the race condition where | ||||||
| 	// volumes can get bound in between calls. | 	// volumes can get bound/provisioned in between calls. | ||||||
| 	boundClaims, unboundClaims, unboundClaimsImmediate, err := b.getPodVolumes(pod) | 	boundClaims, claimsToBind, unboundClaimsImmediate, err := b.getPodVolumes(pod) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, false, err | 		return false, false, err | ||||||
| 	} | 	} | ||||||
| @@ -154,20 +161,32 @@ func (b *volumeBinder) FindPodVolumes(pod *v1.Pod, node *v1.Node) (unboundVolume | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Find PVs for unbound volumes | 	if len(claimsToBind) > 0 { | ||||||
| 	if len(unboundClaims) > 0 { | 		var claimsToProvision []*v1.PersistentVolumeClaim | ||||||
| 		unboundVolumesSatisfied, err = b.findMatchingVolumes(pod, unboundClaims, node) | 		unboundVolumesSatisfied, claimsToProvision, err = b.findMatchingVolumes(pod, claimsToBind, node) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return false, false, err | 			return false, false, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if utilfeature.DefaultFeatureGate.Enabled(features.DynamicProvisioningScheduling) { | ||||||
|  | 			// Try to provision for unbound volumes | ||||||
|  | 			if !unboundVolumesSatisfied { | ||||||
|  | 				unboundVolumesSatisfied, err = b.checkVolumeProvisions(pod, claimsToProvision, node) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return false, false, err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return unboundVolumesSatisfied, boundVolumesSatisfied, nil | 	return unboundVolumesSatisfied, boundVolumesSatisfied, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // AssumePodVolumes will take the cached matching PVs in podBindingCache for the chosen node | // AssumePodVolumes will take the cached matching PVs and PVCs to provision | ||||||
| // and update the pvCache with the new prebound PV.  It will update podBindingCache again | // in podBindingCache for the chosen node, and: | ||||||
| // with the PVs that need an API update. | // 1. Update the pvCache with the new prebound PV. | ||||||
|  | // 2. Update the pvcCache with the new PVCs with annotations set | ||||||
|  | // It will update podBindingCache again with the PVs and PVCs that need an API update. | ||||||
| func (b *volumeBinder) AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (allFullyBound, bindingRequired bool, err error) { | func (b *volumeBinder) AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (allFullyBound, bindingRequired bool, err error) { | ||||||
| 	podName := getPodName(assumedPod) | 	podName := getPodName(assumedPod) | ||||||
|  |  | ||||||
| @@ -179,6 +198,7 @@ func (b *volumeBinder) AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (al | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	assumedPod.Spec.NodeName = nodeName | 	assumedPod.Spec.NodeName = nodeName | ||||||
|  | 	// Assume PV | ||||||
| 	claimsToBind := b.podBindingCache.GetBindings(assumedPod, nodeName) | 	claimsToBind := b.podBindingCache.GetBindings(assumedPod, nodeName) | ||||||
| 	newBindings := []*bindingInfo{} | 	newBindings := []*bindingInfo{} | ||||||
|  |  | ||||||
| @@ -206,23 +226,48 @@ func (b *volumeBinder) AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (al | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(newBindings) == 0 { | 	// Don't update cached bindings if no API updates are needed.  This can happen if we | ||||||
| 		// Don't update cached bindings if no API updates are needed.  This can happen if we | 	// previously updated the PV object and are waiting for the PV controller to finish binding. | ||||||
| 		// previously updated the PV object and are waiting for the PV controller to finish binding. | 	if len(newBindings) != 0 { | ||||||
| 		glog.V(4).Infof("AssumePodVolumes for pod %q, node %q: PVs already assumed", podName, nodeName) | 		bindingRequired = true | ||||||
| 		return false, false, nil | 		b.podBindingCache.UpdateBindings(assumedPod, nodeName, newBindings) | ||||||
| 	} | 	} | ||||||
| 	b.podBindingCache.UpdateBindings(assumedPod, nodeName, newBindings) |  | ||||||
|  |  | ||||||
| 	return false, true, nil | 	// Assume PVCs | ||||||
|  | 	claimsToProvision := b.podBindingCache.GetProvisionedPVCs(assumedPod, nodeName) | ||||||
|  |  | ||||||
|  | 	newProvisionedPVCs := []*v1.PersistentVolumeClaim{} | ||||||
|  | 	for _, claim := range claimsToProvision { | ||||||
|  | 		// The claims from method args can be pointing to watcher cache. We must not | ||||||
|  | 		// modify these, therefore create a copy. | ||||||
|  | 		claimClone := claim.DeepCopy() | ||||||
|  | 		metav1.SetMetaDataAnnotation(&claimClone.ObjectMeta, annSelectedNode, nodeName) | ||||||
|  | 		err = b.pvcCache.Assume(claimClone) | ||||||
|  | 		if err != nil { | ||||||
|  | 			b.revertAssumedPVs(newBindings) | ||||||
|  | 			b.revertAssumedPVCs(newProvisionedPVCs) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		newProvisionedPVCs = append(newProvisionedPVCs, claimClone) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(newProvisionedPVCs) != 0 { | ||||||
|  | 		bindingRequired = true | ||||||
|  | 		b.podBindingCache.UpdateProvisionedPVCs(assumedPod, nodeName, newProvisionedPVCs) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| // BindPodVolumes gets the cached bindings in podBindingCache and makes the API update for those PVs. | // BindPodVolumes gets the cached bindings and PVCs to provision in podBindingCache | ||||||
|  | // and makes the API update for those PVs/PVCs. | ||||||
| func (b *volumeBinder) BindPodVolumes(assumedPod *v1.Pod) error { | func (b *volumeBinder) BindPodVolumes(assumedPod *v1.Pod) error { | ||||||
| 	podName := getPodName(assumedPod) | 	podName := getPodName(assumedPod) | ||||||
| 	glog.V(4).Infof("BindPodVolumes for pod %q", podName) | 	glog.V(4).Infof("BindPodVolumes for pod %q", podName) | ||||||
|  |  | ||||||
| 	bindings := b.podBindingCache.GetBindings(assumedPod, assumedPod.Spec.NodeName) | 	bindings := b.podBindingCache.GetBindings(assumedPod, assumedPod.Spec.NodeName) | ||||||
|  | 	claimsToProvision := b.podBindingCache.GetProvisionedPVCs(assumedPod, assumedPod.Spec.NodeName) | ||||||
|  |  | ||||||
| 	// Do the actual prebinding. Let the PV controller take care of the rest | 	// Do the actual prebinding. Let the PV controller take care of the rest | ||||||
| 	// There is no API rollback if the actual binding fails | 	// There is no API rollback if the actual binding fails | ||||||
| @@ -232,6 +277,20 @@ func (b *volumeBinder) BindPodVolumes(assumedPod *v1.Pod) error { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			// only revert assumed cached updates for volumes we haven't successfully bound | 			// only revert assumed cached updates for volumes we haven't successfully bound | ||||||
| 			b.revertAssumedPVs(bindings[i:]) | 			b.revertAssumedPVs(bindings[i:]) | ||||||
|  | 			// Revert all of the assumed cached updates for claims, | ||||||
|  | 			// since no actual API update will be done | ||||||
|  | 			b.revertAssumedPVCs(claimsToProvision) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Update claims objects to trigger volume provisioning. Let the PV controller take care of the rest | ||||||
|  | 	// PV controller is expect to signal back by removing related annotations if actual provisioning fails | ||||||
|  | 	for i, claim := range claimsToProvision { | ||||||
|  | 		if _, err := b.ctrl.kubeClient.CoreV1().PersistentVolumeClaims(claim.Namespace).Update(claim); err != nil { | ||||||
|  | 			glog.V(4).Infof("updating PersistentVolumeClaim[%s] failed: %v", getPVCName(claim), err) | ||||||
|  | 			// only revert assumed cached updates for claims we haven't successfully updated | ||||||
|  | 			b.revertAssumedPVCs(claimsToProvision[i:]) | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -253,7 +312,13 @@ func (b *volumeBinder) isVolumeBound(namespace string, vol *v1.Volume, checkFull | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pvcName := vol.PersistentVolumeClaim.ClaimName | 	pvcName := vol.PersistentVolumeClaim.ClaimName | ||||||
| 	pvc, err := b.pvcCache.PersistentVolumeClaims(namespace).Get(pvcName) | 	claim := &v1.PersistentVolumeClaim{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name:      pvcName, | ||||||
|  | 			Namespace: namespace, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	pvc, err := b.pvcCache.GetPVC(getPVCName(claim)) | ||||||
| 	if err != nil || pvc == nil { | 	if err != nil || pvc == nil { | ||||||
| 		return false, nil, fmt.Errorf("error getting PVC %q: %v", pvcName, err) | 		return false, nil, fmt.Errorf("error getting PVC %q: %v", pvcName, err) | ||||||
| 	} | 	} | ||||||
| @@ -342,14 +407,18 @@ func (b *volumeBinder) checkBoundClaims(claims []*v1.PersistentVolumeClaim, node | |||||||
| 	return true, nil | 	return true, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *volumeBinder) findMatchingVolumes(pod *v1.Pod, claimsToBind []*bindingInfo, node *v1.Node) (foundMatches bool, err error) { | // findMatchingVolumes tries to find matching volumes for given claims, | ||||||
|  | // and return unbound claims for further provision. | ||||||
|  | func (b *volumeBinder) findMatchingVolumes(pod *v1.Pod, claimsToBind []*bindingInfo, node *v1.Node) (foundMatches bool, unboundClaims []*v1.PersistentVolumeClaim, err error) { | ||||||
| 	podName := getPodName(pod) | 	podName := getPodName(pod) | ||||||
|  |  | ||||||
| 	// Sort all the claims by increasing size request to get the smallest fits | 	// Sort all the claims by increasing size request to get the smallest fits | ||||||
| 	sort.Sort(byPVCSize(claimsToBind)) | 	sort.Sort(byPVCSize(claimsToBind)) | ||||||
|  |  | ||||||
| 	chosenPVs := map[string]*v1.PersistentVolume{} | 	chosenPVs := map[string]*v1.PersistentVolume{} | ||||||
|  |  | ||||||
|  | 	foundMatches = true | ||||||
|  | 	matchedClaims := []*bindingInfo{} | ||||||
|  |  | ||||||
| 	for _, bindingInfo := range claimsToBind { | 	for _, bindingInfo := range claimsToBind { | ||||||
| 		// Get storage class name from each PVC | 		// Get storage class name from each PVC | ||||||
| 		storageClassName := "" | 		storageClassName := "" | ||||||
| @@ -362,21 +431,68 @@ func (b *volumeBinder) findMatchingVolumes(pod *v1.Pod, claimsToBind []*bindingI | |||||||
| 		// Find a matching PV | 		// Find a matching PV | ||||||
| 		bindingInfo.pv, err = findMatchingVolume(bindingInfo.pvc, allPVs, node, chosenPVs, true) | 		bindingInfo.pv, err = findMatchingVolume(bindingInfo.pvc, allPVs, node, chosenPVs, true) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return false, err | 			return false, nil, err | ||||||
| 		} | 		} | ||||||
| 		if bindingInfo.pv == nil { | 		if bindingInfo.pv == nil { | ||||||
| 			glog.V(4).Infof("No matching volumes for Pod %q, PVC %q on node %q", podName, getPVCName(bindingInfo.pvc), node.Name) | 			glog.V(4).Infof("No matching volumes for Pod %q, PVC %q on node %q", podName, getPVCName(bindingInfo.pvc), node.Name) | ||||||
| 			return false, nil | 			unboundClaims = append(unboundClaims, bindingInfo.pvc) | ||||||
|  | 			foundMatches = false | ||||||
|  | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// matching PV needs to be excluded so we don't select it again | 		// matching PV needs to be excluded so we don't select it again | ||||||
| 		chosenPVs[bindingInfo.pv.Name] = bindingInfo.pv | 		chosenPVs[bindingInfo.pv.Name] = bindingInfo.pv | ||||||
|  | 		matchedClaims = append(matchedClaims, bindingInfo) | ||||||
| 		glog.V(5).Infof("Found matching PV %q for PVC %q on node %q for pod %q", bindingInfo.pv.Name, getPVCName(bindingInfo.pvc), node.Name, podName) | 		glog.V(5).Infof("Found matching PV %q for PVC %q on node %q for pod %q", bindingInfo.pv.Name, getPVCName(bindingInfo.pvc), node.Name, podName) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Mark cache with all the matches for each PVC for this node | 	// Mark cache with all the matches for each PVC for this node | ||||||
| 	b.podBindingCache.UpdateBindings(pod, node.Name, claimsToBind) | 	if len(matchedClaims) > 0 { | ||||||
| 	glog.V(4).Infof("Found matching volumes for pod %q on node %q", podName, node.Name) | 		b.podBindingCache.UpdateBindings(pod, node.Name, matchedClaims) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if foundMatches { | ||||||
|  | 		glog.V(4).Infof("Found matching volumes for pod %q on node %q", podName, node.Name) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // checkVolumeProvisions checks given unbound claims (the claims have gone through func | ||||||
|  | // findMatchingVolumes, and do not have matching volumes for binding), and return true | ||||||
|  | // if all of the claims are eligible for dynamic provision. | ||||||
|  | func (b *volumeBinder) checkVolumeProvisions(pod *v1.Pod, claimsToProvision []*v1.PersistentVolumeClaim, node *v1.Node) (provisionSatisfied bool, err error) { | ||||||
|  | 	podName := getPodName(pod) | ||||||
|  | 	provisionedClaims := []*v1.PersistentVolumeClaim{} | ||||||
|  |  | ||||||
|  | 	for _, claim := range claimsToProvision { | ||||||
|  | 		className := v1helper.GetPersistentVolumeClaimClass(claim) | ||||||
|  | 		if className == "" { | ||||||
|  | 			return false, fmt.Errorf("no class for claim %q", getPVCName(claim)) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		class, err := b.ctrl.classLister.Get(className) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return false, fmt.Errorf("failed to find storage class %q", className) | ||||||
|  | 		} | ||||||
|  | 		provisioner := class.Provisioner | ||||||
|  | 		if provisioner == "" || provisioner == notSupportedProvisioner { | ||||||
|  | 			glog.V(4).Infof("storage class %q of claim %q does not support dynamic provisioning", className, getPVCName(claim)) | ||||||
|  | 			return false, nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// TODO: Check if the node can satisfy the topology requirement in the class | ||||||
|  |  | ||||||
|  | 		// TODO: Check if capacity of the node domain in the storage class | ||||||
|  | 		// can satisfy resource requirement of given claim | ||||||
|  |  | ||||||
|  | 		provisionedClaims = append(provisionedClaims, claim) | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | 	glog.V(4).Infof("Provisioning for claims of pod %q that has no matching volumes on node %q ...", podName, node.Name) | ||||||
|  |  | ||||||
|  | 	// Mark cache with all the PVCs that need provisioning for this node | ||||||
|  | 	b.podBindingCache.UpdateProvisionedPVCs(pod, node.Name, provisionedClaims) | ||||||
|  |  | ||||||
| 	return true, nil | 	return true, nil | ||||||
| } | } | ||||||
| @@ -387,6 +503,12 @@ func (b *volumeBinder) revertAssumedPVs(bindings []*bindingInfo) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (b *volumeBinder) revertAssumedPVCs(claims []*v1.PersistentVolumeClaim) { | ||||||
|  | 	for _, claim := range claims { | ||||||
|  | 		b.pvcCache.Restore(getPVCName(claim)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| type bindingInfo struct { | type bindingInfo struct { | ||||||
| 	// Claim that needs to be bound | 	// Claim that needs to be bound | ||||||
| 	pvc *v1.PersistentVolumeClaim | 	pvc *v1.PersistentVolumeClaim | ||||||
|   | |||||||
| @@ -33,20 +33,23 @@ import ( | |||||||
| 	"k8s.io/client-go/informers" | 	"k8s.io/client-go/informers" | ||||||
| 	clientset "k8s.io/client-go/kubernetes" | 	clientset "k8s.io/client-go/kubernetes" | ||||||
| 	"k8s.io/client-go/kubernetes/fake" | 	"k8s.io/client-go/kubernetes/fake" | ||||||
| 	"k8s.io/client-go/tools/cache" |  | ||||||
| 	"k8s.io/kubernetes/pkg/api/testapi" | 	"k8s.io/kubernetes/pkg/api/testapi" | ||||||
| 	"k8s.io/kubernetes/pkg/controller" | 	"k8s.io/kubernetes/pkg/controller" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	unboundPVC          = makeTestPVC("unbound-pvc", "1G", pvcUnbound, "", &waitClass) | 	unboundPVC                  = makeTestPVC("unbound-pvc", "1G", pvcUnbound, "", "1", &waitClass) | ||||||
| 	unboundPVC2         = makeTestPVC("unbound-pvc2", "5G", pvcUnbound, "", &waitClass) | 	unboundPVC2                 = makeTestPVC("unbound-pvc2", "5G", pvcUnbound, "", "1", &waitClass) | ||||||
| 	preboundPVC         = makeTestPVC("prebound-pvc", "1G", pvcPrebound, "pv-node1a", &waitClass) | 	preboundPVC                 = makeTestPVC("prebound-pvc", "1G", pvcPrebound, "pv-node1a", "1", &waitClass) | ||||||
| 	boundPVC            = makeTestPVC("bound-pvc", "1G", pvcBound, "pv-bound", &waitClass) | 	boundPVC                    = makeTestPVC("bound-pvc", "1G", pvcBound, "pv-bound", "1", &waitClass) | ||||||
| 	boundPVC2           = makeTestPVC("bound-pvc2", "1G", pvcBound, "pv-bound2", &waitClass) | 	boundPVC2                   = makeTestPVC("bound-pvc2", "1G", pvcBound, "pv-bound2", "1", &waitClass) | ||||||
| 	badPVC              = makeBadPVC() | 	badPVC                      = makeBadPVC() | ||||||
| 	immediateUnboundPVC = makeTestPVC("immediate-unbound-pvc", "1G", pvcUnbound, "", &immediateClass) | 	immediateUnboundPVC         = makeTestPVC("immediate-unbound-pvc", "1G", pvcUnbound, "", "1", &immediateClass) | ||||||
| 	immediateBoundPVC   = makeTestPVC("immediate-bound-pvc", "1G", pvcBound, "pv-bound-immediate", &immediateClass) | 	immediateBoundPVC           = makeTestPVC("immediate-bound-pvc", "1G", pvcBound, "pv-bound-immediate", "1", &immediateClass) | ||||||
|  | 	provisionedPVC              = makeTestPVC("provisioned-pvc", "1Gi", pvcUnbound, "", "1", &waitClass) | ||||||
|  | 	provisionedPVC2             = makeTestPVC("provisioned-pvc2", "1Gi", pvcUnbound, "", "1", &waitClass) | ||||||
|  | 	provisionedPVCHigherVersion = makeTestPVC("provisioned-pvc2", "1Gi", pvcUnbound, "", "2", &waitClass) | ||||||
|  | 	noProvisionerPVC            = makeTestPVC("no-provisioner-pvc", "1Gi", pvcUnbound, "", "1", &provisionNotSupportClass) | ||||||
|  |  | ||||||
| 	pvNoNode                   = makeTestPV("pv-no-node", "", "1G", "1", nil, waitClass) | 	pvNoNode                   = makeTestPV("pv-no-node", "", "1G", "1", nil, waitClass) | ||||||
| 	pvNode1a                   = makeTestPV("pv-node1a", "node1", "5G", "1", nil, waitClass) | 	pvNode1a                   = makeTestPV("pv-node1a", "node1", "5G", "1", nil, waitClass) | ||||||
| @@ -68,10 +71,12 @@ var ( | |||||||
| 	binding1aBound = makeBinding(unboundPVC, pvNode1aBound) | 	binding1aBound = makeBinding(unboundPVC, pvNode1aBound) | ||||||
| 	binding1bBound = makeBinding(unboundPVC2, pvNode1bBound) | 	binding1bBound = makeBinding(unboundPVC2, pvNode1bBound) | ||||||
|  |  | ||||||
| 	waitClass      = "waitClass" | 	waitClass                = "waitClass" | ||||||
| 	immediateClass = "immediateClass" | 	immediateClass           = "immediateClass" | ||||||
|  | 	provisionNotSupportClass = "provisionNotSupportedClass" | ||||||
|  |  | ||||||
| 	nodeLabelKey = "nodeKey" | 	nodeLabelKey   = "nodeKey" | ||||||
|  | 	nodeLabelValue = "node1" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type testEnv struct { | type testEnv struct { | ||||||
| @@ -80,7 +85,7 @@ type testEnv struct { | |||||||
| 	binder           SchedulerVolumeBinder | 	binder           SchedulerVolumeBinder | ||||||
| 	internalBinder   *volumeBinder | 	internalBinder   *volumeBinder | ||||||
| 	internalPVCache  *pvAssumeCache | 	internalPVCache  *pvAssumeCache | ||||||
| 	internalPVCCache cache.Indexer | 	internalPVCCache *pvcAssumeCache | ||||||
| } | } | ||||||
|  |  | ||||||
| func newTestBinder(t *testing.T) *testEnv { | func newTestBinder(t *testing.T) *testEnv { | ||||||
| @@ -106,6 +111,7 @@ func newTestBinder(t *testing.T) *testEnv { | |||||||
| 				Name: waitClass, | 				Name: waitClass, | ||||||
| 			}, | 			}, | ||||||
| 			VolumeBindingMode: &waitMode, | 			VolumeBindingMode: &waitMode, | ||||||
|  | 			Provisioner:       "test-provisioner", | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			ObjectMeta: metav1.ObjectMeta{ | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
| @@ -113,6 +119,13 @@ func newTestBinder(t *testing.T) *testEnv { | |||||||
| 			}, | 			}, | ||||||
| 			VolumeBindingMode: &immediateMode, | 			VolumeBindingMode: &immediateMode, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: provisionNotSupportClass, | ||||||
|  | 			}, | ||||||
|  | 			VolumeBindingMode: &waitMode, | ||||||
|  | 			Provisioner:       "kubernetes.io/no-provisioner", | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 	for _, class := range classes { | 	for _, class := range classes { | ||||||
| 		if err := classInformer.Informer().GetIndexer().Add(class); err != nil { | 		if err := classInformer.Informer().GetIndexer().Add(class); err != nil { | ||||||
| @@ -132,22 +145,31 @@ func newTestBinder(t *testing.T) *testEnv { | |||||||
| 		t.Fatalf("Failed to convert to internal PV cache") | 		t.Fatalf("Failed to convert to internal PV cache") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	pvcCache := internalBinder.pvcCache | ||||||
|  | 	internalPVCCache, ok := pvcCache.(*pvcAssumeCache) | ||||||
|  | 	if !ok { | ||||||
|  | 		t.Fatalf("Failed to convert to internal PVC cache") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return &testEnv{ | 	return &testEnv{ | ||||||
| 		client:           client, | 		client:           client, | ||||||
| 		reactor:          reactor, | 		reactor:          reactor, | ||||||
| 		binder:           binder, | 		binder:           binder, | ||||||
| 		internalBinder:   internalBinder, | 		internalBinder:   internalBinder, | ||||||
| 		internalPVCache:  internalPVCache, | 		internalPVCache:  internalPVCache, | ||||||
| 		internalPVCCache: pvcInformer.Informer().GetIndexer(), | 		internalPVCCache: internalPVCCache, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (env *testEnv) initClaims(t *testing.T, pvcs []*v1.PersistentVolumeClaim) { | func (env *testEnv) initClaims(cachedPVCs []*v1.PersistentVolumeClaim, apiPVCs []*v1.PersistentVolumeClaim) { | ||||||
| 	for _, pvc := range pvcs { | 	internalPVCCache := env.internalPVCCache | ||||||
| 		err := env.internalPVCCache.Add(pvc) | 	for _, pvc := range cachedPVCs { | ||||||
| 		if err != nil { | 		internalPVCCache.add(pvc) | ||||||
| 			t.Fatalf("Failed to add PVC %q to internal cache: %v", pvc.Name, err) | 		if apiPVCs == nil { | ||||||
|  | 			env.reactor.claims[pvc.Name] = pvc | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, pvc := range apiPVCs { | ||||||
| 		env.reactor.claims[pvc.Name] = pvc | 		env.reactor.claims[pvc.Name] = pvc | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -166,7 +188,7 @@ func (env *testEnv) initVolumes(cachedPVs []*v1.PersistentVolume, apiPVs []*v1.P | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (env *testEnv) assumeVolumes(t *testing.T, name, node string, pod *v1.Pod, bindings []*bindingInfo) { | func (env *testEnv) assumeVolumes(t *testing.T, name, node string, pod *v1.Pod, bindings []*bindingInfo, provisionings []*v1.PersistentVolumeClaim) { | ||||||
| 	pvCache := env.internalBinder.pvCache | 	pvCache := env.internalBinder.pvCache | ||||||
| 	for _, binding := range bindings { | 	for _, binding := range bindings { | ||||||
| 		if err := pvCache.Assume(binding.pv); err != nil { | 		if err := pvCache.Assume(binding.pv); err != nil { | ||||||
| @@ -175,20 +197,38 @@ func (env *testEnv) assumeVolumes(t *testing.T, name, node string, pod *v1.Pod, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	env.internalBinder.podBindingCache.UpdateBindings(pod, node, bindings) | 	env.internalBinder.podBindingCache.UpdateBindings(pod, node, bindings) | ||||||
|  |  | ||||||
|  | 	pvcCache := env.internalBinder.pvcCache | ||||||
|  | 	for _, pvc := range provisionings { | ||||||
|  | 		if err := pvcCache.Assume(pvc); err != nil { | ||||||
|  | 			t.Fatalf("Failed to setup test %q: error: %v", name, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	env.internalBinder.podBindingCache.UpdateProvisionedPVCs(pod, node, provisionings) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (env *testEnv) initPodCache(pod *v1.Pod, node string, bindings []*bindingInfo) { | func (env *testEnv) initPodCache(pod *v1.Pod, node string, bindings []*bindingInfo, provisionings []*v1.PersistentVolumeClaim) { | ||||||
| 	cache := env.internalBinder.podBindingCache | 	cache := env.internalBinder.podBindingCache | ||||||
| 	cache.UpdateBindings(pod, node, bindings) | 	cache.UpdateBindings(pod, node, bindings) | ||||||
|  |  | ||||||
|  | 	cache.UpdateProvisionedPVCs(pod, node, provisionings) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (env *testEnv) validatePodCache(t *testing.T, name, node string, pod *v1.Pod, expectedBindings []*bindingInfo) { | func (env *testEnv) validatePodCache(t *testing.T, name, node string, pod *v1.Pod, expectedBindings []*bindingInfo, expectedProvisionings []*v1.PersistentVolumeClaim) { | ||||||
| 	cache := env.internalBinder.podBindingCache | 	cache := env.internalBinder.podBindingCache | ||||||
| 	bindings := cache.GetBindings(pod, node) | 	bindings := cache.GetBindings(pod, node) | ||||||
|  |  | ||||||
| 	if !reflect.DeepEqual(expectedBindings, bindings) { | 	if !reflect.DeepEqual(expectedBindings, bindings) { | ||||||
| 		t.Errorf("Test %q failed: Expected bindings %+v, got %+v", name, expectedBindings, bindings) | 		t.Errorf("Test %q failed: Expected bindings %+v, got %+v", name, expectedBindings, bindings) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	provisionedClaims := cache.GetProvisionedPVCs(pod, node) | ||||||
|  |  | ||||||
|  | 	if !reflect.DeepEqual(expectedProvisionings, provisionedClaims) { | ||||||
|  | 		t.Errorf("Test %q failed: Expected provisionings %+v, got %+v", name, expectedProvisionings, provisionedClaims) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (env *testEnv) getPodBindings(t *testing.T, name, node string, pod *v1.Pod) []*bindingInfo { | func (env *testEnv) getPodBindings(t *testing.T, name, node string, pod *v1.Pod) []*bindingInfo { | ||||||
| @@ -196,7 +236,7 @@ func (env *testEnv) getPodBindings(t *testing.T, name, node string, pod *v1.Pod) | |||||||
| 	return cache.GetBindings(pod, node) | 	return cache.GetBindings(pod, node) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (env *testEnv) validateAssume(t *testing.T, name string, pod *v1.Pod, bindings []*bindingInfo) { | func (env *testEnv) validateAssume(t *testing.T, name string, pod *v1.Pod, bindings []*bindingInfo, provisionings []*v1.PersistentVolumeClaim) { | ||||||
| 	// TODO: Check binding cache | 	// TODO: Check binding cache | ||||||
|  |  | ||||||
| 	// Check pv cache | 	// Check pv cache | ||||||
| @@ -218,9 +258,23 @@ func (env *testEnv) validateAssume(t *testing.T, name string, pod *v1.Pod, bindi | |||||||
| 			t.Errorf("Test %q failed: expected PV.ClaimRef.Namespace %q, got %q", name, b.pvc.Namespace, pv.Spec.ClaimRef.Namespace) | 			t.Errorf("Test %q failed: expected PV.ClaimRef.Namespace %q, got %q", name, b.pvc.Namespace, pv.Spec.ClaimRef.Namespace) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Check pvc cache | ||||||
|  | 	pvcCache := env.internalBinder.pvcCache | ||||||
|  | 	for _, p := range provisionings { | ||||||
|  | 		pvcKey := getPVCName(p) | ||||||
|  | 		pvc, err := pvcCache.GetPVC(pvcKey) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("Test %q failed: GetPVC %q returned error: %v", name, pvcKey, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if pvc.Annotations[annSelectedNode] != nodeLabelValue { | ||||||
|  | 			t.Errorf("Test %q failed: expected annSelectedNode of pvc %q to be %q, but got %q", name, pvcKey, nodeLabelValue, pvc.Annotations[annSelectedNode]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (env *testEnv) validateFailedAssume(t *testing.T, name string, pod *v1.Pod, bindings []*bindingInfo) { | func (env *testEnv) validateFailedAssume(t *testing.T, name string, pod *v1.Pod, bindings []*bindingInfo, provisionings []*v1.PersistentVolumeClaim) { | ||||||
| 	// All PVs have been unmodified in cache | 	// All PVs have been unmodified in cache | ||||||
| 	pvCache := env.internalBinder.pvCache | 	pvCache := env.internalBinder.pvCache | ||||||
| 	for _, b := range bindings { | 	for _, b := range bindings { | ||||||
| @@ -230,6 +284,20 @@ func (env *testEnv) validateFailedAssume(t *testing.T, name string, pod *v1.Pod, | |||||||
| 			t.Errorf("Test %q failed: PV %q was modified in cache", name, b.pv.Name) | 			t.Errorf("Test %q failed: PV %q was modified in cache", name, b.pv.Name) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Check pvc cache | ||||||
|  | 	pvcCache := env.internalBinder.pvcCache | ||||||
|  | 	for _, p := range provisionings { | ||||||
|  | 		pvcKey := getPVCName(p) | ||||||
|  | 		pvc, err := pvcCache.GetPVC(pvcKey) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("Test %q failed: GetPVC %q returned error: %v", name, pvcKey, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if pvc.Annotations[annSelectedNode] != "" { | ||||||
|  | 			t.Errorf("Test %q failed: expected annSelectedNode of pvc %q empty, but got %q", name, pvcKey, pvc.Annotations[annSelectedNode]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (env *testEnv) validateBind( | func (env *testEnv) validateBind( | ||||||
| @@ -257,20 +325,46 @@ func (env *testEnv) validateBind( | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (env *testEnv) validateProvision( | ||||||
|  | 	t *testing.T, | ||||||
|  | 	name string, | ||||||
|  | 	pod *v1.Pod, | ||||||
|  | 	expectedPVCs []*v1.PersistentVolumeClaim, | ||||||
|  | 	expectedAPIPVCs []*v1.PersistentVolumeClaim) { | ||||||
|  |  | ||||||
|  | 	// Check pvc cache | ||||||
|  | 	pvcCache := env.internalBinder.pvcCache | ||||||
|  | 	for _, pvc := range expectedPVCs { | ||||||
|  | 		cachedPVC, err := pvcCache.GetPVC(getPVCName(pvc)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("Test %q failed: GetPVC %q returned error: %v", name, getPVCName(pvc), err) | ||||||
|  | 		} | ||||||
|  | 		if !reflect.DeepEqual(cachedPVC, pvc) { | ||||||
|  | 			t.Errorf("Test %q failed: cached PVC check failed [A-expected, B-got]:\n%s", name, diff.ObjectDiff(pvc, cachedPVC)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Check reactor for API updates | ||||||
|  | 	if err := env.reactor.checkClaims(expectedAPIPVCs); err != nil { | ||||||
|  | 		t.Errorf("Test %q failed: API reactor validation failed: %v", name, err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	pvcUnbound = iota | 	pvcUnbound = iota | ||||||
| 	pvcPrebound | 	pvcPrebound | ||||||
| 	pvcBound | 	pvcBound | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func makeTestPVC(name, size string, pvcBoundState int, pvName string, className *string) *v1.PersistentVolumeClaim { | func makeTestPVC(name, size string, pvcBoundState int, pvName, resourceVersion string, className *string) *v1.PersistentVolumeClaim { | ||||||
| 	pvc := &v1.PersistentVolumeClaim{ | 	pvc := &v1.PersistentVolumeClaim{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name:            name, | 			Name:            name, | ||||||
| 			Namespace:       "testns", | 			Namespace:       "testns", | ||||||
| 			UID:             types.UID("pvc-uid"), | 			UID:             types.UID("pvc-uid"), | ||||||
| 			ResourceVersion: "1", | 			ResourceVersion: resourceVersion, | ||||||
| 			SelfLink:        testapi.Default.SelfLink("pvc", name), | 			SelfLink:        testapi.Default.SelfLink("pvc", name), | ||||||
|  | 			Annotations:     map[string]string{}, | ||||||
| 		}, | 		}, | ||||||
| 		Spec: v1.PersistentVolumeClaimSpec{ | 		Spec: v1.PersistentVolumeClaimSpec{ | ||||||
| 			Resources: v1.ResourceRequirements{ | 			Resources: v1.ResourceRequirements{ | ||||||
| @@ -389,7 +483,15 @@ func makeBinding(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) *bindin | |||||||
| 	return &bindingInfo{pvc: pvc, pv: pv} | 	return &bindingInfo{pvc: pvc, pv: pv} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestFindPodVolumes(t *testing.T) { | func addProvisionAnn(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim { | ||||||
|  | 	res := pvc.DeepCopy() | ||||||
|  | 	// Add provision related annotations | ||||||
|  | 	res.Annotations[annSelectedNode] = nodeLabelValue | ||||||
|  |  | ||||||
|  | 	return res | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestFindPodVolumesWithoutProvisioning(t *testing.T) { | ||||||
| 	scenarios := map[string]struct { | 	scenarios := map[string]struct { | ||||||
| 		// Inputs | 		// Inputs | ||||||
| 		pvs     []*v1.PersistentVolume | 		pvs     []*v1.PersistentVolume | ||||||
| @@ -470,10 +572,11 @@ func TestFindPodVolumes(t *testing.T) { | |||||||
| 			expectedBound:    true, | 			expectedBound:    true, | ||||||
| 		}, | 		}, | ||||||
| 		"two-unbound-pvcs,partial-match": { | 		"two-unbound-pvcs,partial-match": { | ||||||
| 			podPVCs:         []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2}, | 			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2}, | ||||||
| 			pvs:             []*v1.PersistentVolume{pvNode1a}, | 			pvs:              []*v1.PersistentVolume{pvNode1a}, | ||||||
| 			expectedUnbound: false, | 			expectedBindings: []*bindingInfo{binding1a}, | ||||||
| 			expectedBound:   true, | 			expectedUnbound:  false, | ||||||
|  | 			expectedBound:    true, | ||||||
| 		}, | 		}, | ||||||
| 		"one-bound,one-unbound": { | 		"one-bound,one-unbound": { | ||||||
| 			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC, boundPVC}, | 			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC, boundPVC}, | ||||||
| @@ -552,7 +655,7 @@ func TestFindPodVolumes(t *testing.T) { | |||||||
| 		if scenario.cachePVCs == nil { | 		if scenario.cachePVCs == nil { | ||||||
| 			scenario.cachePVCs = scenario.podPVCs | 			scenario.cachePVCs = scenario.podPVCs | ||||||
| 		} | 		} | ||||||
| 		testEnv.initClaims(t, scenario.cachePVCs) | 		testEnv.initClaims(scenario.cachePVCs, scenario.cachePVCs) | ||||||
|  |  | ||||||
| 		// b. Generate pod with given claims | 		// b. Generate pod with given claims | ||||||
| 		if scenario.pod == nil { | 		if scenario.pod == nil { | ||||||
| @@ -575,16 +678,126 @@ func TestFindPodVolumes(t *testing.T) { | |||||||
| 		if unboundSatisfied != scenario.expectedUnbound { | 		if unboundSatisfied != scenario.expectedUnbound { | ||||||
| 			t.Errorf("Test %q failed: expected unboundSatsified %v, got %v", name, scenario.expectedUnbound, unboundSatisfied) | 			t.Errorf("Test %q failed: expected unboundSatsified %v, got %v", name, scenario.expectedUnbound, unboundSatisfied) | ||||||
| 		} | 		} | ||||||
| 		testEnv.validatePodCache(t, name, testNode.Name, scenario.pod, scenario.expectedBindings) | 		testEnv.validatePodCache(t, name, testNode.Name, scenario.pod, scenario.expectedBindings, nil) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestFindPodVolumesWithProvisioning(t *testing.T) { | ||||||
|  | 	scenarios := map[string]struct { | ||||||
|  | 		// Inputs | ||||||
|  | 		pvs     []*v1.PersistentVolume | ||||||
|  | 		podPVCs []*v1.PersistentVolumeClaim | ||||||
|  | 		// If nil, use pod PVCs | ||||||
|  | 		cachePVCs []*v1.PersistentVolumeClaim | ||||||
|  | 		// If nil, makePod with podPVCs | ||||||
|  | 		pod *v1.Pod | ||||||
|  |  | ||||||
|  | 		// Expected podBindingCache fields | ||||||
|  | 		expectedBindings   []*bindingInfo | ||||||
|  | 		expectedProvisions []*v1.PersistentVolumeClaim | ||||||
|  |  | ||||||
|  | 		// Expected return values | ||||||
|  | 		expectedUnbound bool | ||||||
|  | 		expectedBound   bool | ||||||
|  | 		shouldFail      bool | ||||||
|  | 	}{ | ||||||
|  | 		"one-provisioned": { | ||||||
|  | 			podPVCs:            []*v1.PersistentVolumeClaim{provisionedPVC}, | ||||||
|  | 			expectedProvisions: []*v1.PersistentVolumeClaim{provisionedPVC}, | ||||||
|  | 			expectedUnbound:    true, | ||||||
|  | 			expectedBound:      true, | ||||||
|  | 		}, | ||||||
|  | 		"two-unbound-pvcs,one-matched,one-provisioned": { | ||||||
|  | 			podPVCs:            []*v1.PersistentVolumeClaim{unboundPVC, provisionedPVC}, | ||||||
|  | 			pvs:                []*v1.PersistentVolume{pvNode1a}, | ||||||
|  | 			expectedBindings:   []*bindingInfo{binding1a}, | ||||||
|  | 			expectedProvisions: []*v1.PersistentVolumeClaim{provisionedPVC}, | ||||||
|  | 			expectedUnbound:    true, | ||||||
|  | 			expectedBound:      true, | ||||||
|  | 		}, | ||||||
|  | 		"one-bound,one-provisioned": { | ||||||
|  | 			podPVCs:            []*v1.PersistentVolumeClaim{boundPVC, provisionedPVC}, | ||||||
|  | 			pvs:                []*v1.PersistentVolume{pvBound}, | ||||||
|  | 			expectedProvisions: []*v1.PersistentVolumeClaim{provisionedPVC}, | ||||||
|  | 			expectedUnbound:    true, | ||||||
|  | 			expectedBound:      true, | ||||||
|  | 		}, | ||||||
|  | 		"immediate-unbound-pvc": { | ||||||
|  | 			podPVCs:         []*v1.PersistentVolumeClaim{immediateUnboundPVC}, | ||||||
|  | 			expectedUnbound: false, | ||||||
|  | 			expectedBound:   false, | ||||||
|  | 			shouldFail:      true, | ||||||
|  | 		}, | ||||||
|  | 		"one-immediate-bound,one-provisioned": { | ||||||
|  | 			podPVCs:            []*v1.PersistentVolumeClaim{immediateBoundPVC, provisionedPVC}, | ||||||
|  | 			pvs:                []*v1.PersistentVolume{pvBoundImmediate}, | ||||||
|  | 			expectedProvisions: []*v1.PersistentVolumeClaim{provisionedPVC}, | ||||||
|  | 			expectedUnbound:    true, | ||||||
|  | 			expectedBound:      true, | ||||||
|  | 		}, | ||||||
|  | 		"invalid-provisioner": { | ||||||
|  | 			podPVCs:         []*v1.PersistentVolumeClaim{noProvisionerPVC}, | ||||||
|  | 			expectedUnbound: false, | ||||||
|  | 			expectedBound:   true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Set VolumeScheduling and DynamicProvisioningScheduling feature gate | ||||||
|  | 	utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true,DynamicProvisioningScheduling=true") | ||||||
|  | 	defer utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false,DynamicProvisioningScheduling=false") | ||||||
|  |  | ||||||
|  | 	testNode := &v1.Node{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "node1", | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				nodeLabelKey: "node1", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for name, scenario := range scenarios { | ||||||
|  | 		// Setup | ||||||
|  | 		testEnv := newTestBinder(t) | ||||||
|  | 		testEnv.initVolumes(scenario.pvs, scenario.pvs) | ||||||
|  |  | ||||||
|  | 		// a. Init pvc cache | ||||||
|  | 		if scenario.cachePVCs == nil { | ||||||
|  | 			scenario.cachePVCs = scenario.podPVCs | ||||||
|  | 		} | ||||||
|  | 		testEnv.initClaims(scenario.cachePVCs, scenario.cachePVCs) | ||||||
|  |  | ||||||
|  | 		// b. Generate pod with given claims | ||||||
|  | 		if scenario.pod == nil { | ||||||
|  | 			scenario.pod = makePod(scenario.podPVCs) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Execute | ||||||
|  | 		unboundSatisfied, boundSatisfied, err := testEnv.binder.FindPodVolumes(scenario.pod, testNode) | ||||||
|  |  | ||||||
|  | 		// Validate | ||||||
|  | 		if !scenario.shouldFail && err != nil { | ||||||
|  | 			t.Errorf("Test %q failed: returned error: %v", name, err) | ||||||
|  | 		} | ||||||
|  | 		if scenario.shouldFail && err == nil { | ||||||
|  | 			t.Errorf("Test %q failed: returned success but expected error", name) | ||||||
|  | 		} | ||||||
|  | 		if boundSatisfied != scenario.expectedBound { | ||||||
|  | 			t.Errorf("Test %q failed: expected boundSatsified %v, got %v", name, scenario.expectedBound, boundSatisfied) | ||||||
|  | 		} | ||||||
|  | 		if unboundSatisfied != scenario.expectedUnbound { | ||||||
|  | 			t.Errorf("Test %q failed: expected unboundSatsified %v, got %v", name, scenario.expectedUnbound, unboundSatisfied) | ||||||
|  | 		} | ||||||
|  | 		testEnv.validatePodCache(t, name, testNode.Name, scenario.pod, scenario.expectedBindings, scenario.expectedProvisions) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestAssumePodVolumes(t *testing.T) { | func TestAssumePodVolumes(t *testing.T) { | ||||||
| 	scenarios := map[string]struct { | 	scenarios := map[string]struct { | ||||||
| 		// Inputs | 		// Inputs | ||||||
| 		podPVCs  []*v1.PersistentVolumeClaim | 		podPVCs         []*v1.PersistentVolumeClaim | ||||||
| 		pvs      []*v1.PersistentVolume | 		pvs             []*v1.PersistentVolume | ||||||
| 		bindings []*bindingInfo | 		bindings        []*bindingInfo | ||||||
|  | 		provisionedPVCs []*v1.PersistentVolumeClaim | ||||||
|  |  | ||||||
| 		// Expected return values | 		// Expected return values | ||||||
| 		shouldFail              bool | 		shouldFail              bool | ||||||
| @@ -636,6 +849,21 @@ func TestAssumePodVolumes(t *testing.T) { | |||||||
| 			shouldFail:              true, | 			shouldFail:              true, | ||||||
| 			expectedBindingRequired: true, | 			expectedBindingRequired: true, | ||||||
| 		}, | 		}, | ||||||
|  | 		"one-binding, one-pvc-provisioned": { | ||||||
|  | 			podPVCs:                 []*v1.PersistentVolumeClaim{unboundPVC, provisionedPVC}, | ||||||
|  | 			bindings:                []*bindingInfo{binding1a}, | ||||||
|  | 			pvs:                     []*v1.PersistentVolume{pvNode1a}, | ||||||
|  | 			provisionedPVCs:         []*v1.PersistentVolumeClaim{provisionedPVC}, | ||||||
|  | 			expectedBindingRequired: true, | ||||||
|  | 		}, | ||||||
|  | 		"one-binding, one-provision-tmpupdate-failed": { | ||||||
|  | 			podPVCs:                 []*v1.PersistentVolumeClaim{unboundPVC, provisionedPVCHigherVersion}, | ||||||
|  | 			bindings:                []*bindingInfo{binding1a}, | ||||||
|  | 			pvs:                     []*v1.PersistentVolume{pvNode1a}, | ||||||
|  | 			provisionedPVCs:         []*v1.PersistentVolumeClaim{provisionedPVC2}, | ||||||
|  | 			shouldFail:              true, | ||||||
|  | 			expectedBindingRequired: true, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for name, scenario := range scenarios { | 	for name, scenario := range scenarios { | ||||||
| @@ -643,9 +871,9 @@ func TestAssumePodVolumes(t *testing.T) { | |||||||
|  |  | ||||||
| 		// Setup | 		// Setup | ||||||
| 		testEnv := newTestBinder(t) | 		testEnv := newTestBinder(t) | ||||||
| 		testEnv.initClaims(t, scenario.podPVCs) | 		testEnv.initClaims(scenario.podPVCs, scenario.podPVCs) | ||||||
| 		pod := makePod(scenario.podPVCs) | 		pod := makePod(scenario.podPVCs) | ||||||
| 		testEnv.initPodCache(pod, "node1", scenario.bindings) | 		testEnv.initPodCache(pod, "node1", scenario.bindings, scenario.provisionedPVCs) | ||||||
| 		testEnv.initVolumes(scenario.pvs, scenario.pvs) | 		testEnv.initVolumes(scenario.pvs, scenario.pvs) | ||||||
|  |  | ||||||
| 		// Execute | 		// Execute | ||||||
| @@ -668,9 +896,9 @@ func TestAssumePodVolumes(t *testing.T) { | |||||||
| 			scenario.expectedBindings = scenario.bindings | 			scenario.expectedBindings = scenario.bindings | ||||||
| 		} | 		} | ||||||
| 		if scenario.shouldFail { | 		if scenario.shouldFail { | ||||||
| 			testEnv.validateFailedAssume(t, name, pod, scenario.expectedBindings) | 			testEnv.validateFailedAssume(t, name, pod, scenario.expectedBindings, scenario.provisionedPVCs) | ||||||
| 		} else { | 		} else { | ||||||
| 			testEnv.validateAssume(t, name, pod, scenario.expectedBindings) | 			testEnv.validateAssume(t, name, pod, scenario.expectedBindings, scenario.provisionedPVCs) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -683,11 +911,20 @@ func TestBindPodVolumes(t *testing.T) { | |||||||
| 		// if nil, use cachedPVs | 		// if nil, use cachedPVs | ||||||
| 		apiPVs []*v1.PersistentVolume | 		apiPVs []*v1.PersistentVolume | ||||||
|  |  | ||||||
|  | 		provisionedPVCs []*v1.PersistentVolumeClaim | ||||||
|  | 		cachedPVCs      []*v1.PersistentVolumeClaim | ||||||
|  | 		// if nil, use cachedPVCs | ||||||
|  | 		apiPVCs []*v1.PersistentVolumeClaim | ||||||
|  |  | ||||||
| 		// Expected return values | 		// Expected return values | ||||||
| 		shouldFail  bool | 		shouldFail  bool | ||||||
| 		expectedPVs []*v1.PersistentVolume | 		expectedPVs []*v1.PersistentVolume | ||||||
| 		// if nil, use expectedPVs | 		// if nil, use expectedPVs | ||||||
| 		expectedAPIPVs []*v1.PersistentVolume | 		expectedAPIPVs []*v1.PersistentVolume | ||||||
|  |  | ||||||
|  | 		expectedPVCs []*v1.PersistentVolumeClaim | ||||||
|  | 		// if nil, use expectedPVCs | ||||||
|  | 		expectedAPIPVCs []*v1.PersistentVolumeClaim | ||||||
| 	}{ | 	}{ | ||||||
| 		"all-bound": {}, | 		"all-bound": {}, | ||||||
| 		"not-fully-bound": { | 		"not-fully-bound": { | ||||||
| @@ -711,6 +948,30 @@ func TestBindPodVolumes(t *testing.T) { | |||||||
| 			expectedAPIPVs: []*v1.PersistentVolume{pvNode1aBound, pvNode1bBoundHigherVersion}, | 			expectedAPIPVs: []*v1.PersistentVolume{pvNode1aBound, pvNode1bBoundHigherVersion}, | ||||||
| 			shouldFail:     true, | 			shouldFail:     true, | ||||||
| 		}, | 		}, | ||||||
|  | 		"one-provisioned-pvc": { | ||||||
|  | 			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)}, | ||||||
|  | 			cachedPVCs:      []*v1.PersistentVolumeClaim{provisionedPVC}, | ||||||
|  | 			expectedPVCs:    []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)}, | ||||||
|  | 		}, | ||||||
|  | 		"provision-api-update-failed": { | ||||||
|  | 			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), addProvisionAnn(provisionedPVC2)}, | ||||||
|  | 			cachedPVCs:      []*v1.PersistentVolumeClaim{provisionedPVC, provisionedPVC2}, | ||||||
|  | 			apiPVCs:         []*v1.PersistentVolumeClaim{provisionedPVC, provisionedPVCHigherVersion}, | ||||||
|  | 			expectedPVCs:    []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), provisionedPVC2}, | ||||||
|  | 			expectedAPIPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), provisionedPVCHigherVersion}, | ||||||
|  | 			shouldFail:      true, | ||||||
|  | 		}, | ||||||
|  | 		"bingding-succeed, provision-api-update-failed": { | ||||||
|  | 			bindings:        []*bindingInfo{binding1aBound}, | ||||||
|  | 			cachedPVs:       []*v1.PersistentVolume{pvNode1a}, | ||||||
|  | 			expectedPVs:     []*v1.PersistentVolume{pvNode1aBound}, | ||||||
|  | 			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), addProvisionAnn(provisionedPVC2)}, | ||||||
|  | 			cachedPVCs:      []*v1.PersistentVolumeClaim{provisionedPVC, provisionedPVC2}, | ||||||
|  | 			apiPVCs:         []*v1.PersistentVolumeClaim{provisionedPVC, provisionedPVCHigherVersion}, | ||||||
|  | 			expectedPVCs:    []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), provisionedPVC2}, | ||||||
|  | 			expectedAPIPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), provisionedPVCHigherVersion}, | ||||||
|  | 			shouldFail:      true, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 	for name, scenario := range scenarios { | 	for name, scenario := range scenarios { | ||||||
| 		glog.V(5).Infof("Running test case %q", name) | 		glog.V(5).Infof("Running test case %q", name) | ||||||
| @@ -721,8 +982,12 @@ func TestBindPodVolumes(t *testing.T) { | |||||||
| 		if scenario.apiPVs == nil { | 		if scenario.apiPVs == nil { | ||||||
| 			scenario.apiPVs = scenario.cachedPVs | 			scenario.apiPVs = scenario.cachedPVs | ||||||
| 		} | 		} | ||||||
|  | 		if scenario.apiPVCs == nil { | ||||||
|  | 			scenario.apiPVCs = scenario.cachedPVCs | ||||||
|  | 		} | ||||||
| 		testEnv.initVolumes(scenario.cachedPVs, scenario.apiPVs) | 		testEnv.initVolumes(scenario.cachedPVs, scenario.apiPVs) | ||||||
| 		testEnv.assumeVolumes(t, name, "node1", pod, scenario.bindings) | 		testEnv.initClaims(scenario.cachedPVCs, scenario.apiPVCs) | ||||||
|  | 		testEnv.assumeVolumes(t, name, "node1", pod, scenario.bindings, scenario.provisionedPVCs) | ||||||
|  |  | ||||||
| 		// Execute | 		// Execute | ||||||
| 		err := testEnv.binder.BindPodVolumes(pod) | 		err := testEnv.binder.BindPodVolumes(pod) | ||||||
| @@ -737,7 +1002,11 @@ func TestBindPodVolumes(t *testing.T) { | |||||||
| 		if scenario.expectedAPIPVs == nil { | 		if scenario.expectedAPIPVs == nil { | ||||||
| 			scenario.expectedAPIPVs = scenario.expectedPVs | 			scenario.expectedAPIPVs = scenario.expectedPVs | ||||||
| 		} | 		} | ||||||
|  | 		if scenario.expectedAPIPVCs == nil { | ||||||
|  | 			scenario.expectedAPIPVCs = scenario.expectedPVCs | ||||||
|  | 		} | ||||||
| 		testEnv.validateBind(t, name, pod, scenario.expectedPVs, scenario.expectedAPIPVs) | 		testEnv.validateBind(t, name, pod, scenario.expectedPVs, scenario.expectedAPIPVs) | ||||||
|  | 		testEnv.validateProvision(t, name, pod, scenario.expectedPVCs, scenario.expectedAPIPVCs) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -753,7 +1022,7 @@ func TestFindAssumeVolumes(t *testing.T) { | |||||||
| 	// Setup | 	// Setup | ||||||
| 	testEnv := newTestBinder(t) | 	testEnv := newTestBinder(t) | ||||||
| 	testEnv.initVolumes(pvs, pvs) | 	testEnv.initVolumes(pvs, pvs) | ||||||
| 	testEnv.initClaims(t, podPVCs) | 	testEnv.initClaims(podPVCs, podPVCs) | ||||||
| 	pod := makePod(podPVCs) | 	pod := makePod(podPVCs) | ||||||
|  |  | ||||||
| 	testNode := &v1.Node{ | 	testNode := &v1.Node{ | ||||||
| @@ -787,7 +1056,7 @@ func TestFindAssumeVolumes(t *testing.T) { | |||||||
| 	if !bindingRequired { | 	if !bindingRequired { | ||||||
| 		t.Errorf("Test failed: binding not required") | 		t.Errorf("Test failed: binding not required") | ||||||
| 	} | 	} | ||||||
| 	testEnv.validateAssume(t, "assume", pod, expectedBindings) | 	testEnv.validateAssume(t, "assume", pod, expectedBindings, nil) | ||||||
| 	// After assume, claimref should be set on pv | 	// After assume, claimref should be set on pv | ||||||
| 	expectedBindings = testEnv.getPodBindings(t, "after-assume", testNode.Name, pod) | 	expectedBindings = testEnv.getPodBindings(t, "after-assume", testNode.Name, pod) | ||||||
|  |  | ||||||
| @@ -803,6 +1072,6 @@ func TestFindAssumeVolumes(t *testing.T) { | |||||||
| 		if !unboundSatisfied { | 		if !unboundSatisfied { | ||||||
| 			t.Errorf("Test failed: couldn't find PVs for all PVCs") | 			t.Errorf("Test failed: couldn't find PVs for all PVCs") | ||||||
| 		} | 		} | ||||||
| 		testEnv.validatePodCache(t, "after-assume", testNode.Name, pod, expectedBindings) | 		testEnv.validatePodCache(t, "after-assume", testNode.Name, pod, expectedBindings, nil) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -279,6 +279,12 @@ const ( | |||||||
| 	// A node which has closer cpu,memory utilization and volume count is favoured by scheduler | 	// A node which has closer cpu,memory utilization and volume count is favoured by scheduler | ||||||
| 	// while making decisions. | 	// while making decisions. | ||||||
| 	BalanceAttachedNodeVolumes utilfeature.Feature = "BalanceAttachedNodeVolumes" | 	BalanceAttachedNodeVolumes utilfeature.Feature = "BalanceAttachedNodeVolumes" | ||||||
|  |  | ||||||
|  | 	// owner: @lichuqiang | ||||||
|  | 	// alpha: v1.11 | ||||||
|  | 	// | ||||||
|  | 	// Extend the default scheduler to be aware of volume topology and handle PV provisioning | ||||||
|  | 	DynamicProvisioningScheduling utilfeature.Feature = "DynamicProvisioningScheduling" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func init() { | func init() { | ||||||
| @@ -327,6 +333,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS | |||||||
| 	RunAsGroup:                                  {Default: false, PreRelease: utilfeature.Alpha}, | 	RunAsGroup:                                  {Default: false, PreRelease: utilfeature.Alpha}, | ||||||
| 	VolumeSubpath:                               {Default: true, PreRelease: utilfeature.GA}, | 	VolumeSubpath:                               {Default: true, PreRelease: utilfeature.GA}, | ||||||
| 	BalanceAttachedNodeVolumes:                  {Default: false, PreRelease: utilfeature.Alpha}, | 	BalanceAttachedNodeVolumes:                  {Default: false, PreRelease: utilfeature.Alpha}, | ||||||
|  | 	DynamicProvisioningScheduling:               {Default: false, PreRelease: utilfeature.Alpha}, | ||||||
|  |  | ||||||
| 	// inherited features from generic apiserver, relisted here to get a conflict if it is changed | 	// inherited features from generic apiserver, relisted here to get a conflict if it is changed | ||||||
| 	// unintentionally on either side: | 	// unintentionally on either side: | ||||||
|   | |||||||
| @@ -506,12 +506,16 @@ func ClusterRoles() []rbacv1.ClusterRole { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { | 	if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { | ||||||
|  | 		rules := []rbacv1.PolicyRule{ | ||||||
|  | 			rbacv1helpers.NewRule(ReadUpdate...).Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie(), | ||||||
|  | 			rbacv1helpers.NewRule(Read...).Groups(storageGroup).Resources("storageclasses").RuleOrDie(), | ||||||
|  | 		} | ||||||
|  | 		if utilfeature.DefaultFeatureGate.Enabled(features.DynamicProvisioningScheduling) { | ||||||
|  | 			rules = append(rules, rbacv1helpers.NewRule(ReadUpdate...).Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie()) | ||||||
|  | 		} | ||||||
| 		roles = append(roles, rbacv1.ClusterRole{ | 		roles = append(roles, rbacv1.ClusterRole{ | ||||||
| 			ObjectMeta: metav1.ObjectMeta{Name: "system:volume-scheduler"}, | 			ObjectMeta: metav1.ObjectMeta{Name: "system:volume-scheduler"}, | ||||||
| 			Rules: []rbacv1.PolicyRule{ | 			Rules:      rules, | ||||||
| 				rbacv1helpers.NewRule(ReadUpdate...).Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie(), |  | ||||||
| 				rbacv1helpers.NewRule(Read...).Groups(storageGroup).Resources("storageclasses").RuleOrDie(), |  | ||||||
| 			}, |  | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user