mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 14:07:14 +00:00
allow pre-binding of persistent volumes to pvclaims
This commit is contained in:
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"
|
"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
|
||||||
|
Loading…
Reference in New Issue
Block a user