mirror of https://github.com/k3s-io/k3s
allow pre-binding of persistent volumes to pvclaims
parent
efedcb6ca1
commit
09600095c4
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue