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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
type persistentVolumeOrderedIndex struct {
 | 
			
		||||
	cache.Indexer
 | 
			
		||||
@@ -73,8 +80,8 @@ func (pvIndex *persistentVolumeOrderedIndex) ListByAccessModes(modes []api.Persi
 | 
			
		||||
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
 | 
			
		||||
func (pvIndex *persistentVolumeOrderedIndex) Find(pv *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.
 | 
			
		||||
func (pvIndex *persistentVolumeOrderedIndex) Find(searchPV *api.PersistentVolume, matchPredicate matchPredicate) (*api.PersistentVolume, error) {
 | 
			
		||||
	// 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.
 | 
			
		||||
	//
 | 
			
		||||
	// 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
 | 
			
		||||
	// potential matches (the GCEPD example above).
 | 
			
		||||
	allPossibleModes := pvIndex.allPossibleMatchingAccessModes(pv.Spec.AccessModes)
 | 
			
		||||
	allPossibleModes := pvIndex.allPossibleMatchingAccessModes(searchPV.Spec.AccessModes)
 | 
			
		||||
 | 
			
		||||
	for _, modes := range allPossibleModes {
 | 
			
		||||
		volumes, err := pvIndex.ListByAccessModes(modes)
 | 
			
		||||
@@ -93,16 +100,32 @@ func (pvIndex *persistentVolumeOrderedIndex) Find(pv *api.PersistentVolume, matc
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// volumes are sorted by size but some may be bound.
 | 
			
		||||
		// remove bound volumes for easy binary search by size
 | 
			
		||||
		// volumes are sorted by size but some may be bound or earmarked for a specific claim.
 | 
			
		||||
		// filter those volumes for easy binary search by size
 | 
			
		||||
		// return the exact pre-binding match, if found
 | 
			
		||||
		unboundVolumes := []*api.PersistentVolume{}
 | 
			
		||||
		for _, v := range volumes {
 | 
			
		||||
			if v.Spec.ClaimRef == nil {
 | 
			
		||||
				unboundVolumes = append(unboundVolumes, v)
 | 
			
		||||
		for _, volume := range volumes {
 | 
			
		||||
			// check for current binding
 | 
			
		||||
			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
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		i := sort.Search(len(unboundVolumes), func(i int) bool { return matchPredicate(pv, unboundVolumes[i]) })
 | 
			
		||||
			// volume isn't currently bound or pre-bound.
 | 
			
		||||
			unboundVolumes = append(unboundVolumes, volume)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		i := sort.Search(len(unboundVolumes), func(i int) bool { return matchPredicate(searchPV, unboundVolumes[i]) })
 | 
			
		||||
		if i < len(unboundVolumes) {
 | 
			
		||||
			return unboundVolumes[i], nil
 | 
			
		||||
		}
 | 
			
		||||
@@ -110,9 +133,14 @@ func (pvIndex *persistentVolumeOrderedIndex) Find(pv *api.PersistentVolume, matc
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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) {
 | 
			
		||||
// findByAccessModesAndStorageCapacity is a convenience method that calls Find w/ requisite matchPredicate for storage
 | 
			
		||||
func (pvIndex *persistentVolumeOrderedIndex) findByAccessModesAndStorageCapacity(prebindKey string, modes []api.PersistentVolumeAccessMode, qty resource.Quantity) (*api.PersistentVolume, error) {
 | 
			
		||||
	pv := &api.PersistentVolume{
 | 
			
		||||
		ObjectMeta: api.ObjectMeta{
 | 
			
		||||
			Annotations: map[string]string{
 | 
			
		||||
				createdForKey: prebindKey,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Spec: api.PersistentVolumeSpec{
 | 
			
		||||
			AccessModes: modes,
 | 
			
		||||
			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
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user