mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			275 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			275 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2014 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package persistentvolume
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"sort"
 | 
						|
 | 
						|
	"k8s.io/kubernetes/pkg/api"
 | 
						|
	"k8s.io/kubernetes/pkg/api/unversioned"
 | 
						|
	"k8s.io/kubernetes/pkg/client/cache"
 | 
						|
	"k8s.io/kubernetes/pkg/labels"
 | 
						|
)
 | 
						|
 | 
						|
// persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes
 | 
						|
// indexed by AccessModes and ordered by storage capacity.
 | 
						|
type persistentVolumeOrderedIndex struct {
 | 
						|
	store cache.Indexer
 | 
						|
}
 | 
						|
 | 
						|
func newPersistentVolumeOrderedIndex() persistentVolumeOrderedIndex {
 | 
						|
	return persistentVolumeOrderedIndex{cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"accessmodes": accessModesIndexFunc})}
 | 
						|
}
 | 
						|
 | 
						|
// accessModesIndexFunc is an indexing function that returns a persistent
 | 
						|
// volume's AccessModes as a string
 | 
						|
func accessModesIndexFunc(obj interface{}) ([]string, error) {
 | 
						|
	if pv, ok := obj.(*api.PersistentVolume); ok {
 | 
						|
		modes := api.GetAccessModesAsString(pv.Spec.AccessModes)
 | 
						|
		return []string{modes}, nil
 | 
						|
	}
 | 
						|
	return []string{""}, fmt.Errorf("object is not a persistent volume: %v", obj)
 | 
						|
}
 | 
						|
 | 
						|
// listByAccessModes returns all volumes with the given set of
 | 
						|
// AccessModeTypes. The list is unsorted!
 | 
						|
func (pvIndex *persistentVolumeOrderedIndex) listByAccessModes(modes []api.PersistentVolumeAccessMode) ([]*api.PersistentVolume, error) {
 | 
						|
	pv := &api.PersistentVolume{
 | 
						|
		Spec: api.PersistentVolumeSpec{
 | 
						|
			AccessModes: modes,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	objs, err := pvIndex.store.Index("accessmodes", pv)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	volumes := make([]*api.PersistentVolume, len(objs))
 | 
						|
	for i, obj := range objs {
 | 
						|
		volumes[i] = obj.(*api.PersistentVolume)
 | 
						|
	}
 | 
						|
 | 
						|
	return volumes, nil
 | 
						|
}
 | 
						|
 | 
						|
// matchPredicate is a function that indicates that a persistent volume matches another
 | 
						|
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) findByClaim(claim *api.PersistentVolumeClaim, matchPredicate matchPredicate) (*api.PersistentVolume, error) {
 | 
						|
	// PVs are indexed by their access modes to allow easier searching.  Each
 | 
						|
	// index is the string representation of a set of access modes. There is a
 | 
						|
	// finite number of possible sets and PVs will only be indexed in one of
 | 
						|
	// them (whichever index matches the PV's modes).
 | 
						|
	//
 | 
						|
	// A request for resources will always specify its desired access modes.
 | 
						|
	// Any matching PV must have at least that number of access modes, but it
 | 
						|
	// can have more.  For example, a user asks for ReadWriteOnce but a GCEPD
 | 
						|
	// is available, which is ReadWriteOnce+ReadOnlyMany.
 | 
						|
	//
 | 
						|
	// 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(claim.Spec.AccessModes)
 | 
						|
 | 
						|
	var smallestVolume *api.PersistentVolume
 | 
						|
	var smallestVolumeSize int64
 | 
						|
	requestedQty := claim.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)]
 | 
						|
	requestedSize := requestedQty.Value()
 | 
						|
 | 
						|
	var selector labels.Selector
 | 
						|
	if claim.Spec.Selector != nil {
 | 
						|
		internalSelector, err := unversioned.LabelSelectorAsSelector(claim.Spec.Selector)
 | 
						|
		if err != nil {
 | 
						|
			// should be unreachable code due to validation
 | 
						|
			return nil, fmt.Errorf("error creating internal label selector for claim: %v: %v", claimToClaimKey(claim), err)
 | 
						|
		}
 | 
						|
		selector = internalSelector
 | 
						|
	}
 | 
						|
 | 
						|
	for _, modes := range allPossibleModes {
 | 
						|
		volumes, err := pvIndex.listByAccessModes(modes)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		// Go through all available volumes with two goals:
 | 
						|
		// - find a volume that is either pre-bound by user or dynamically
 | 
						|
		//   provisioned for this claim. Because of this we need to loop through
 | 
						|
		//   all volumes.
 | 
						|
		// - find the smallest matching one if there is no volume pre-bound to
 | 
						|
		//   the claim.
 | 
						|
		for _, volume := range volumes {
 | 
						|
			if isVolumeBoundToClaim(volume, claim) {
 | 
						|
				// this claim and volume are bound; return it,
 | 
						|
				// whether the claim is prebound or for volumes
 | 
						|
				// intended for dynamic provisioning v1
 | 
						|
				return volume, nil
 | 
						|
			}
 | 
						|
 | 
						|
			// filter out:
 | 
						|
			// - volumes bound to another claim
 | 
						|
			// - volumes whose labels don't match the claim's selector, if specified
 | 
						|
			if volume.Spec.ClaimRef != nil {
 | 
						|
				continue
 | 
						|
			} else if selector != nil && !selector.Matches(labels.Set(volume.Labels)) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			volumeQty := volume.Spec.Capacity[api.ResourceStorage]
 | 
						|
			volumeSize := volumeQty.Value()
 | 
						|
			if volumeSize >= requestedSize {
 | 
						|
				if smallestVolume == nil || smallestVolumeSize > volumeSize {
 | 
						|
					smallestVolume = volume
 | 
						|
					smallestVolumeSize = volumeSize
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// We want to provision volumes if the annotation is set even if there
 | 
						|
		// is matching PV. Therefore, do not look for available PV and let
 | 
						|
		// a new volume to be provisioned.
 | 
						|
		//
 | 
						|
		// When provisioner creates a new PV to this claim, an exact match
 | 
						|
		// pre-bound to the claim will be found by the checks above during
 | 
						|
		// subsequent claim sync.
 | 
						|
		if hasAnnotation(claim.ObjectMeta, annClass) {
 | 
						|
			return nil, nil
 | 
						|
		}
 | 
						|
 | 
						|
		if smallestVolume != nil {
 | 
						|
			// Found a matching volume
 | 
						|
			return smallestVolume, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
// 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.findByClaim(claim, matchStorageCapacity)
 | 
						|
}
 | 
						|
 | 
						|
// matchStorageCapacity is a matchPredicate used to sort and find volumes
 | 
						|
func matchStorageCapacity(pvA, pvB *api.PersistentVolume) bool {
 | 
						|
	aQty := pvA.Spec.Capacity[api.ResourceStorage]
 | 
						|
	bQty := pvB.Spec.Capacity[api.ResourceStorage]
 | 
						|
	aSize := aQty.Value()
 | 
						|
	bSize := bQty.Value()
 | 
						|
	return aSize <= bSize
 | 
						|
}
 | 
						|
 | 
						|
// allPossibleMatchingAccessModes returns an array of AccessMode arrays that
 | 
						|
// can satisfy a user's requested modes.
 | 
						|
//
 | 
						|
// see comments in the Find func above regarding indexing.
 | 
						|
//
 | 
						|
// allPossibleMatchingAccessModes gets all stringified accessmodes from the
 | 
						|
// index and returns all those that contain at least all of the requested
 | 
						|
// mode.
 | 
						|
//
 | 
						|
// For example, assume the index contains 2 types of PVs where the stringified
 | 
						|
// accessmodes are:
 | 
						|
//
 | 
						|
// "RWO,ROX" -- some number of GCEPDs
 | 
						|
// "RWO,ROX,RWX" -- some number of NFS volumes
 | 
						|
//
 | 
						|
// A request for RWO could be satisfied by both sets of indexed volumes, so
 | 
						|
// allPossibleMatchingAccessModes returns:
 | 
						|
//
 | 
						|
// [][]api.PersistentVolumeAccessMode {
 | 
						|
//      []api.PersistentVolumeAccessMode {
 | 
						|
//			api.ReadWriteOnce, api.ReadOnlyMany,
 | 
						|
//		},
 | 
						|
//      []api.PersistentVolumeAccessMode {
 | 
						|
//			api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany,
 | 
						|
//		},
 | 
						|
// }
 | 
						|
//
 | 
						|
// A request for RWX can be satisfied by only one set of indexed volumes, so
 | 
						|
// the return is:
 | 
						|
//
 | 
						|
// [][]api.PersistentVolumeAccessMode {
 | 
						|
//      []api.PersistentVolumeAccessMode {
 | 
						|
//			api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany,
 | 
						|
//		},
 | 
						|
// }
 | 
						|
//
 | 
						|
// This func returns modes with ascending levels of modes to give the user
 | 
						|
// what is closest to what they actually asked for.
 | 
						|
func (pvIndex *persistentVolumeOrderedIndex) allPossibleMatchingAccessModes(requestedModes []api.PersistentVolumeAccessMode) [][]api.PersistentVolumeAccessMode {
 | 
						|
	matchedModes := [][]api.PersistentVolumeAccessMode{}
 | 
						|
	keys := pvIndex.store.ListIndexFuncValues("accessmodes")
 | 
						|
	for _, key := range keys {
 | 
						|
		indexedModes := api.GetAccessModesFromString(key)
 | 
						|
		if containedInAll(indexedModes, requestedModes) {
 | 
						|
			matchedModes = append(matchedModes, indexedModes)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// sort by the number of modes in each array with the fewest number of
 | 
						|
	// modes coming first. this allows searching for volumes by the minimum
 | 
						|
	// number of modes required of the possible matches.
 | 
						|
	sort.Sort(byAccessModes{matchedModes})
 | 
						|
	return matchedModes
 | 
						|
}
 | 
						|
 | 
						|
func contains(modes []api.PersistentVolumeAccessMode, mode api.PersistentVolumeAccessMode) bool {
 | 
						|
	for _, m := range modes {
 | 
						|
		if m == mode {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func containedInAll(indexedModes []api.PersistentVolumeAccessMode, requestedModes []api.PersistentVolumeAccessMode) bool {
 | 
						|
	for _, mode := range requestedModes {
 | 
						|
		if !contains(indexedModes, mode) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
// byAccessModes is used to order access modes by size, with the fewest modes first
 | 
						|
type byAccessModes struct {
 | 
						|
	modes [][]api.PersistentVolumeAccessMode
 | 
						|
}
 | 
						|
 | 
						|
func (c byAccessModes) Less(i, j int) bool {
 | 
						|
	return len(c.modes[i]) < len(c.modes[j])
 | 
						|
}
 | 
						|
 | 
						|
func (c byAccessModes) Swap(i, j int) {
 | 
						|
	c.modes[i], c.modes[j] = c.modes[j], c.modes[i]
 | 
						|
}
 | 
						|
 | 
						|
func (c byAccessModes) Len() int {
 | 
						|
	return len(c.modes)
 | 
						|
}
 | 
						|
 | 
						|
func claimToClaimKey(claim *api.PersistentVolumeClaim) string {
 | 
						|
	return fmt.Sprintf("%s/%s", claim.Namespace, claim.Name)
 | 
						|
}
 | 
						|
 | 
						|
func claimrefToClaimKey(claimref *api.ObjectReference) string {
 | 
						|
	return fmt.Sprintf("%s/%s", claimref.Namespace, claimref.Name)
 | 
						|
}
 |