k8s.io/kubernetes@v1.29.3/pkg/controller/volume/persistentvolume/index.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package persistentvolume 18 19 import ( 20 "fmt" 21 "sort" 22 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/client-go/tools/cache" 25 "k8s.io/component-helpers/storage/volume" 26 v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" 27 "k8s.io/kubernetes/pkg/volume/util" 28 ) 29 30 // persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes 31 // indexed by AccessModes and ordered by storage capacity. 32 type persistentVolumeOrderedIndex struct { 33 store cache.Indexer 34 } 35 36 func newPersistentVolumeOrderedIndex() persistentVolumeOrderedIndex { 37 return persistentVolumeOrderedIndex{cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"accessmodes": accessModesIndexFunc})} 38 } 39 40 // accessModesIndexFunc is an indexing function that returns a persistent 41 // volume's AccessModes as a string 42 func accessModesIndexFunc(obj interface{}) ([]string, error) { 43 if pv, ok := obj.(*v1.PersistentVolume); ok { 44 modes := v1helper.GetAccessModesAsString(pv.Spec.AccessModes) 45 return []string{modes}, nil 46 } 47 return []string{""}, fmt.Errorf("object is not a persistent volume: %v", obj) 48 } 49 50 // listByAccessModes returns all volumes with the given set of 51 // AccessModeTypes. The list is unsorted! 52 func (pvIndex *persistentVolumeOrderedIndex) listByAccessModes(modes []v1.PersistentVolumeAccessMode) ([]*v1.PersistentVolume, error) { 53 pv := &v1.PersistentVolume{ 54 Spec: v1.PersistentVolumeSpec{ 55 AccessModes: modes, 56 }, 57 } 58 59 objs, err := pvIndex.store.Index("accessmodes", pv) 60 if err != nil { 61 return nil, err 62 } 63 64 volumes := make([]*v1.PersistentVolume, len(objs)) 65 for i, obj := range objs { 66 volumes[i] = obj.(*v1.PersistentVolume) 67 } 68 69 return volumes, nil 70 } 71 72 // find returns the nearest PV from the ordered list or nil if a match is not found 73 func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *v1.PersistentVolumeClaim, delayBinding bool) (*v1.PersistentVolume, error) { 74 // PVs are indexed by their access modes to allow easier searching. Each 75 // index is the string representation of a set of access modes. There is a 76 // finite number of possible sets and PVs will only be indexed in one of 77 // them (whichever index matches the PV's modes). 78 // 79 // A request for resources will always specify its desired access modes. 80 // Any matching PV must have at least that number of access modes, but it 81 // can have more. For example, a user asks for ReadWriteOnce but a GCEPD 82 // is available, which is ReadWriteOnce+ReadOnlyMany. 83 // 84 // Searches are performed against a set of access modes, so we can attempt 85 // not only the exact matching modes but also potential matches (the GCEPD 86 // example above). 87 allPossibleModes := pvIndex.allPossibleMatchingAccessModes(claim.Spec.AccessModes) 88 89 for _, modes := range allPossibleModes { 90 volumes, err := pvIndex.listByAccessModes(modes) 91 if err != nil { 92 return nil, err 93 } 94 95 bestVol, err := volume.FindMatchingVolume(claim, volumes, nil /* node for topology binding*/, nil /* exclusion map */, delayBinding) 96 if err != nil { 97 return nil, err 98 } 99 100 if bestVol != nil { 101 return bestVol, nil 102 } 103 } 104 return nil, nil 105 } 106 107 // findBestMatchForClaim is a convenience method that finds a volume by the claim's AccessModes and requests for Storage 108 func (pvIndex *persistentVolumeOrderedIndex) findBestMatchForClaim(claim *v1.PersistentVolumeClaim, delayBinding bool) (*v1.PersistentVolume, error) { 109 return pvIndex.findByClaim(claim, delayBinding) 110 } 111 112 // allPossibleMatchingAccessModes returns an array of AccessMode arrays that 113 // can satisfy a user's requested modes. 114 // 115 // see comments in the Find func above regarding indexing. 116 // 117 // allPossibleMatchingAccessModes gets all stringified accessmodes from the 118 // index and returns all those that contain at least all of the requested 119 // mode. 120 // 121 // For example, assume the index contains 2 types of PVs where the stringified 122 // accessmodes are: 123 // 124 // "RWO,ROX" -- some number of GCEPDs 125 // "RWO,ROX,RWX" -- some number of NFS volumes 126 // 127 // A request for RWO could be satisfied by both sets of indexed volumes, so 128 // allPossibleMatchingAccessModes returns: 129 // 130 // [][]v1.PersistentVolumeAccessMode { 131 // []v1.PersistentVolumeAccessMode { 132 // v1.ReadWriteOnce, v1.ReadOnlyMany, 133 // }, 134 // []v1.PersistentVolumeAccessMode { 135 // v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany, 136 // }, 137 // } 138 // 139 // A request for RWX can be satisfied by only one set of indexed volumes, so 140 // the return is: 141 // 142 // [][]v1.PersistentVolumeAccessMode { 143 // []v1.PersistentVolumeAccessMode { 144 // v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany, 145 // }, 146 // } 147 // 148 // This func returns modes with ascending levels of modes to give the user 149 // what is closest to what they actually asked for. 150 func (pvIndex *persistentVolumeOrderedIndex) allPossibleMatchingAccessModes(requestedModes []v1.PersistentVolumeAccessMode) [][]v1.PersistentVolumeAccessMode { 151 matchedModes := [][]v1.PersistentVolumeAccessMode{} 152 keys := pvIndex.store.ListIndexFuncValues("accessmodes") 153 for _, key := range keys { 154 indexedModes := v1helper.GetAccessModesFromString(key) 155 if util.ContainsAllAccessModes(indexedModes, requestedModes) { 156 matchedModes = append(matchedModes, indexedModes) 157 } 158 } 159 160 // sort by the number of modes in each array with the fewest number of 161 // modes coming first. this allows searching for volumes by the minimum 162 // number of modes required of the possible matches. 163 sort.Sort(byAccessModes{matchedModes}) 164 return matchedModes 165 } 166 167 // byAccessModes is used to order access modes by size, with the fewest modes first 168 type byAccessModes struct { 169 modes [][]v1.PersistentVolumeAccessMode 170 } 171 172 func (c byAccessModes) Less(i, j int) bool { 173 return len(c.modes[i]) < len(c.modes[j]) 174 } 175 176 func (c byAccessModes) Swap(i, j int) { 177 c.modes[i], c.modes[j] = c.modes[j], c.modes[i] 178 } 179 180 func (c byAccessModes) Len() int { 181 return len(c.modes) 182 } 183 184 func claimToClaimKey(claim *v1.PersistentVolumeClaim) string { 185 return fmt.Sprintf("%s/%s", claim.Namespace, claim.Name) 186 } 187 188 func claimrefToClaimKey(claimref *v1.ObjectReference) string { 189 return fmt.Sprintf("%s/%s", claimref.Namespace, claimref.Name) 190 }