allow pre-binding of persistent volumes to pvclaims
This commit is contained in:
		@@ -484,3 +484,78 @@ func createTestVolumes() []*api.PersistentVolume {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestFindingPreboundVolumes(t *testing.T) {
 | 
				
			||||||
 | 
						pv1 := &api.PersistentVolume{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
								Name:        "pv1",
 | 
				
			||||||
 | 
								Annotations: map[string]string{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: api.PersistentVolumeSpec{
 | 
				
			||||||
 | 
								Capacity:               api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("1Gi")},
 | 
				
			||||||
 | 
								PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{}},
 | 
				
			||||||
 | 
								AccessModes:            []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pv5 := &api.PersistentVolume{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
								Name:        "pv5",
 | 
				
			||||||
 | 
								Annotations: map[string]string{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: api.PersistentVolumeSpec{
 | 
				
			||||||
 | 
								Capacity:               api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("5Gi")},
 | 
				
			||||||
 | 
								PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{}},
 | 
				
			||||||
 | 
								AccessModes:            []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pv8 := &api.PersistentVolume{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
								Name:        "pv8",
 | 
				
			||||||
 | 
								Annotations: map[string]string{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: api.PersistentVolumeSpec{
 | 
				
			||||||
 | 
								Capacity:               api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("8Gi")},
 | 
				
			||||||
 | 
								PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{}},
 | 
				
			||||||
 | 
								AccessModes:            []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						claim := &api.PersistentVolumeClaim{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
								Name:      "claim01",
 | 
				
			||||||
 | 
								Namespace: "myns",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: api.PersistentVolumeClaimSpec{
 | 
				
			||||||
 | 
								AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
 | 
				
			||||||
 | 
								Resources:   api.ResourceRequirements{Requests: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("1Gi")}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						index := NewPersistentVolumeOrderedIndex()
 | 
				
			||||||
 | 
						index.Add(pv1)
 | 
				
			||||||
 | 
						index.Add(pv5)
 | 
				
			||||||
 | 
						index.Add(pv8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// expected exact match on size
 | 
				
			||||||
 | 
						volume, _ := index.FindBestMatchForClaim(claim)
 | 
				
			||||||
 | 
						if volume.Name != pv1.Name {
 | 
				
			||||||
 | 
							t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// pretend the exact match is pre-bound.  should get the next size up.
 | 
				
			||||||
 | 
						pv1.Annotations[createdForKey] = "some/other/claim"
 | 
				
			||||||
 | 
						volume, _ = index.FindBestMatchForClaim(claim)
 | 
				
			||||||
 | 
						if volume.Name != pv5.Name {
 | 
				
			||||||
 | 
							t.Errorf("Expected %s but got volume %s instead", pv5.Name, volume.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// pretend the exact match is available but the largest volume is pre-bound to the claim.
 | 
				
			||||||
 | 
						delete(pv1.Annotations, createdForKey)
 | 
				
			||||||
 | 
						pv8.Annotations[createdForKey] = "myns/claim01"
 | 
				
			||||||
 | 
						volume, _ = index.FindBestMatchForClaim(claim)
 | 
				
			||||||
 | 
						if volume.Name != pv8.Name {
 | 
				
			||||||
 | 
							t.Errorf("Expected %s but got volume %s instead", pv8.Name, volume.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,13 @@ import (
 | 
				
			|||||||
	"k8s.io/kubernetes/pkg/client/cache"
 | 
						"k8s.io/kubernetes/pkg/client/cache"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// A PV created specifically for one claim must contain this annotation in order to bind to the claim.
 | 
				
			||||||
 | 
						// The value must be the name of the claim being bound to.
 | 
				
			||||||
 | 
						// This is an experimental feature and likely to change in the future.
 | 
				
			||||||
 | 
						createdForKey = "persistent-volume-provisioning.experimental.kubernetes.io/created-for"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes indexed by AccessModes and ordered by storage capacity.
 | 
					// persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes indexed by AccessModes and ordered by storage capacity.
 | 
				
			||||||
type persistentVolumeOrderedIndex struct {
 | 
					type persistentVolumeOrderedIndex struct {
 | 
				
			||||||
	cache.Indexer
 | 
						cache.Indexer
 | 
				
			||||||
@@ -73,8 +80,8 @@ func (pvIndex *persistentVolumeOrderedIndex) ListByAccessModes(modes []api.Persi
 | 
				
			|||||||
type matchPredicate func(compareThis, toThis *api.PersistentVolume) bool
 | 
					type matchPredicate func(compareThis, toThis *api.PersistentVolume) bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Find returns the nearest PV from the ordered list or nil if a match is not found
 | 
					// Find returns the nearest PV from the ordered list or nil if a match is not found
 | 
				
			||||||
func (pvIndex *persistentVolumeOrderedIndex) Find(pv *api.PersistentVolume, matchPredicate matchPredicate) (*api.PersistentVolume, error) {
 | 
					func (pvIndex *persistentVolumeOrderedIndex) Find(searchPV *api.PersistentVolume, matchPredicate matchPredicate) (*api.PersistentVolume, error) {
 | 
				
			||||||
	// the 'pv' argument is a synthetic PV with capacity and accessmodes set according to the user's PersistentVolumeClaim.
 | 
						// the 'searchPV' argument is a synthetic PV with capacity and accessmodes set according to the user's PersistentVolumeClaim.
 | 
				
			||||||
	// the synthetic pv arg is, therefore, a request for a storage resource.
 | 
						// the synthetic pv arg is, therefore, a request for a storage resource.
 | 
				
			||||||
	//
 | 
						//
 | 
				
			||||||
	// PVs are indexed by their access modes to allow easier searching.  Each index is the string representation of a set of access modes.
 | 
						// PVs are indexed by their access modes to allow easier searching.  Each index is the string representation of a set of access modes.
 | 
				
			||||||
@@ -85,7 +92,7 @@ func (pvIndex *persistentVolumeOrderedIndex) Find(pv *api.PersistentVolume, matc
 | 
				
			|||||||
	//
 | 
						//
 | 
				
			||||||
	// Searches are performed against a set of access modes, so we can attempt not only the exact matching modes but also
 | 
						// Searches are performed against a set of access modes, so we can attempt not only the exact matching modes but also
 | 
				
			||||||
	// potential matches (the GCEPD example above).
 | 
						// potential matches (the GCEPD example above).
 | 
				
			||||||
	allPossibleModes := pvIndex.allPossibleMatchingAccessModes(pv.Spec.AccessModes)
 | 
						allPossibleModes := pvIndex.allPossibleMatchingAccessModes(searchPV.Spec.AccessModes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, modes := range allPossibleModes {
 | 
						for _, modes := range allPossibleModes {
 | 
				
			||||||
		volumes, err := pvIndex.ListByAccessModes(modes)
 | 
							volumes, err := pvIndex.ListByAccessModes(modes)
 | 
				
			||||||
@@ -93,16 +100,32 @@ func (pvIndex *persistentVolumeOrderedIndex) Find(pv *api.PersistentVolume, matc
 | 
				
			|||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// volumes are sorted by size but some may be bound.
 | 
							// volumes are sorted by size but some may be bound or earmarked for a specific claim.
 | 
				
			||||||
		// remove bound volumes for easy binary search by size
 | 
							// filter those volumes for easy binary search by size
 | 
				
			||||||
 | 
							// return the exact pre-binding match, if found
 | 
				
			||||||
		unboundVolumes := []*api.PersistentVolume{}
 | 
							unboundVolumes := []*api.PersistentVolume{}
 | 
				
			||||||
		for _, v := range volumes {
 | 
							for _, volume := range volumes {
 | 
				
			||||||
			if v.Spec.ClaimRef == nil {
 | 
								// check for current binding
 | 
				
			||||||
				unboundVolumes = append(unboundVolumes, v)
 | 
								if volume.Spec.ClaimRef != nil {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// check for pre-bind where the volume is intended for one specific claim
 | 
				
			||||||
 | 
								if createdFor, ok := volume.Annotations[createdForKey]; ok {
 | 
				
			||||||
 | 
									if createdFor != searchPV.Annotations[createdForKey] {
 | 
				
			||||||
 | 
										// the volume is pre-bound and does not match the search criteria.
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										// exact annotation match! No search required.
 | 
				
			||||||
 | 
										return volume, nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// volume isn't currently bound or pre-bound.
 | 
				
			||||||
 | 
								unboundVolumes = append(unboundVolumes, volume)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		i := sort.Search(len(unboundVolumes), func(i int) bool { return matchPredicate(pv, unboundVolumes[i]) })
 | 
							i := sort.Search(len(unboundVolumes), func(i int) bool { return matchPredicate(searchPV, unboundVolumes[i]) })
 | 
				
			||||||
		if i < len(unboundVolumes) {
 | 
							if i < len(unboundVolumes) {
 | 
				
			||||||
			return unboundVolumes[i], nil
 | 
								return unboundVolumes[i], nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -110,9 +133,14 @@ func (pvIndex *persistentVolumeOrderedIndex) Find(pv *api.PersistentVolume, matc
 | 
				
			|||||||
	return nil, nil
 | 
						return nil, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FindByAccessModesAndStorageCapacity is a convenience method that calls Find w/ requisite matchPredicate for storage
 | 
					// findByAccessModesAndStorageCapacity is a convenience method that calls Find w/ requisite matchPredicate for storage
 | 
				
			||||||
func (pvIndex *persistentVolumeOrderedIndex) FindByAccessModesAndStorageCapacity(modes []api.PersistentVolumeAccessMode, qty resource.Quantity) (*api.PersistentVolume, error) {
 | 
					func (pvIndex *persistentVolumeOrderedIndex) findByAccessModesAndStorageCapacity(prebindKey string, modes []api.PersistentVolumeAccessMode, qty resource.Quantity) (*api.PersistentVolume, error) {
 | 
				
			||||||
	pv := &api.PersistentVolume{
 | 
						pv := &api.PersistentVolume{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
								Annotations: map[string]string{
 | 
				
			||||||
 | 
									createdForKey: prebindKey,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		Spec: api.PersistentVolumeSpec{
 | 
							Spec: api.PersistentVolumeSpec{
 | 
				
			||||||
			AccessModes: modes,
 | 
								AccessModes: modes,
 | 
				
			||||||
			Capacity: api.ResourceList{
 | 
								Capacity: api.ResourceList{
 | 
				
			||||||
@@ -125,7 +153,7 @@ func (pvIndex *persistentVolumeOrderedIndex) FindByAccessModesAndStorageCapacity
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// FindBestMatchForClaim is a convenience method that finds a volume by the claim's AccessModes and requests for Storage
 | 
					// FindBestMatchForClaim is a convenience method that finds a volume by the claim's AccessModes and requests for Storage
 | 
				
			||||||
func (pvIndex *persistentVolumeOrderedIndex) FindBestMatchForClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolume, error) {
 | 
					func (pvIndex *persistentVolumeOrderedIndex) FindBestMatchForClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolume, error) {
 | 
				
			||||||
	return pvIndex.FindByAccessModesAndStorageCapacity(claim.Spec.AccessModes, claim.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)])
 | 
						return pvIndex.findByAccessModesAndStorageCapacity(fmt.Sprintf("%s/%s", claim.Namespace, claim.Name), claim.Spec.AccessModes, claim.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)])
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// byCapacity is used to order volumes by ascending storage size
 | 
					// byCapacity is used to order volumes by ascending storage size
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user