k8s.io/apiserver@v0.31.1/pkg/storageversion/manager.go (about) 1 /* 2 Copyright 2020 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 storageversion 18 19 import ( 20 "fmt" 21 "sort" 22 "sync" 23 "sync/atomic" 24 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 "k8s.io/client-go/kubernetes" 29 "k8s.io/client-go/rest" 30 _ "k8s.io/component-base/metrics/prometheus/workqueue" // for workqueue metric registration 31 "k8s.io/klog/v2" 32 ) 33 34 // ResourceInfo contains the information to register the resource to the 35 // storage version API. 36 type ResourceInfo struct { 37 GroupResource schema.GroupResource 38 39 EncodingVersion string 40 // Used to calculate decodable versions. Can only be used after all 41 // equivalent versions are registered by InstallREST. 42 EquivalentResourceMapper runtime.EquivalentResourceRegistry 43 44 // DirectlyDecodableVersions is a list of versions that the converter for REST storage knows how to convert. This 45 // contains items like apiextensions.k8s.io/v1beta1 even if we don't serve that version. 46 DirectlyDecodableVersions []schema.GroupVersion 47 48 // ServedVersions holds a list of all versions of GroupResource that are served. Note that a server may be able to 49 // decode a particular version, but still not serve it. 50 ServedVersions []string 51 } 52 53 // Manager records the resources whose StorageVersions need updates, and provides a method to update those StorageVersions. 54 type Manager interface { 55 // AddResourceInfo records resources whose StorageVersions need updates 56 AddResourceInfo(resources ...*ResourceInfo) 57 // UpdateStorageVersions tries to update the StorageVersions of the recorded resources 58 UpdateStorageVersions(kubeAPIServerClientConfig *rest.Config, apiserverID string) 59 // PendingUpdate returns true if the StorageVersion of the given resource is still pending update. 60 PendingUpdate(gr schema.GroupResource) bool 61 // LastUpdateError returns the last error hit when updating the storage version of the given resource. 62 LastUpdateError(gr schema.GroupResource) error 63 // Completed returns true if updating StorageVersions of all recorded resources has completed. 64 Completed() bool 65 } 66 67 var _ Manager = &defaultManager{} 68 69 // defaultManager indicates if an apiserver has completed reporting its storage versions. 70 type defaultManager struct { 71 completed atomic.Bool 72 73 mu sync.RWMutex 74 // managedResourceInfos records the ResourceInfos whose StorageVersions will get updated in the next 75 // UpdateStorageVersions call 76 managedResourceInfos map[*ResourceInfo]struct{} 77 // managedStatus records the update status of StorageVersion for each GroupResource. Since one 78 // ResourceInfo may expand into multiple GroupResource (e.g. ingresses.networking.k8s.io and ingresses.extensions), 79 // this map allows quick status lookup for a GroupResource, during API request handling. 80 managedStatus map[schema.GroupResource]*updateStatus 81 } 82 83 type updateStatus struct { 84 done bool 85 lastErr error 86 } 87 88 // NewDefaultManager creates a new defaultManager. 89 func NewDefaultManager() Manager { 90 s := &defaultManager{} 91 s.completed.Store(false) 92 s.managedResourceInfos = make(map[*ResourceInfo]struct{}) 93 s.managedStatus = make(map[schema.GroupResource]*updateStatus) 94 return s 95 } 96 97 // AddResourceInfo adds ResourceInfo to the manager. 98 func (s *defaultManager) AddResourceInfo(resources ...*ResourceInfo) { 99 s.mu.Lock() 100 defer s.mu.Unlock() 101 for _, r := range resources { 102 s.managedResourceInfos[r] = struct{}{} 103 s.addPendingManagedStatusLocked(r) 104 } 105 } 106 107 func (s *defaultManager) addPendingManagedStatusLocked(r *ResourceInfo) { 108 gvrs := r.EquivalentResourceMapper.EquivalentResourcesFor(r.GroupResource.WithVersion(""), "") 109 for _, gvr := range gvrs { 110 gr := gvr.GroupResource() 111 if _, ok := s.managedStatus[gr]; !ok { 112 s.managedStatus[gr] = &updateStatus{} 113 } 114 } 115 } 116 117 // UpdateStorageVersions tries to update the StorageVersions of the recorded resources 118 func (s *defaultManager) UpdateStorageVersions(kubeAPIServerClientConfig *rest.Config, serverID string) { 119 clientset, err := kubernetes.NewForConfig(kubeAPIServerClientConfig) 120 if err != nil { 121 utilruntime.HandleError(fmt.Errorf("failed to get clientset: %v", err)) 122 return 123 } 124 sc := clientset.InternalV1alpha1().StorageVersions() 125 126 s.mu.RLock() 127 resources := []ResourceInfo{} 128 for resource := range s.managedResourceInfos { 129 resources = append(resources, *resource) 130 } 131 s.mu.RUnlock() 132 hasFailure := false 133 // Sorting the list to make sure we have a consistent dedup result, and 134 // therefore avoid creating unnecessarily duplicated StorageVersion objects. 135 // For example, extensions.ingresses and networking.k8s.io.ingresses share 136 // the same underlying storage. Without sorting, in an HA cluster, one 137 // apiserver may dedup and update StorageVersion for extensions.ingresses, 138 // while another apiserver may dedup and update StorageVersion for 139 // networking.k8s.io.ingresses. The storage migrator (which migrates objects 140 // per GroupResource) will migrate these resources twice, since both 141 // StorageVersion objects have CommonEncodingVersion (each with one server registered). 142 sortResourceInfosByGroupResource(resources) 143 for _, r := range dedupResourceInfos(resources) { 144 decodableVersions := decodableVersions(r.DirectlyDecodableVersions, r.EquivalentResourceMapper, r.GroupResource) 145 gr := r.GroupResource 146 // Group must be a valid subdomain in DNS (RFC 1123) 147 if len(gr.Group) == 0 { 148 gr.Group = "core" 149 } 150 151 servedVersions := r.ServedVersions 152 153 if err := updateStorageVersionFor(sc, serverID, gr, r.EncodingVersion, decodableVersions, servedVersions); err != nil { 154 utilruntime.HandleError(fmt.Errorf("failed to update storage version for %v: %v", r.GroupResource, err)) 155 s.recordStatusFailure(&r, err) 156 hasFailure = true 157 continue 158 } 159 klog.V(2).Infof("successfully updated storage version for %v", r.GroupResource) 160 s.recordStatusSuccess(&r) 161 } 162 if hasFailure { 163 return 164 } 165 klog.V(2).Infof("storage version updates complete") 166 s.setComplete() 167 } 168 169 // dedupResourceInfos dedups ResourceInfos with the same underlying storage. 170 // ResourceInfos from the same Group with different Versions share the same underlying storage. 171 // ResourceInfos from different Groups may share the same underlying storage, e.g. 172 // networking.k8s.io ingresses and extensions ingresses. The StorageVersion manager 173 // only needs to update one StorageVersion for the equivalent Groups. 174 func dedupResourceInfos(infos []ResourceInfo) []ResourceInfo { 175 var ret []ResourceInfo 176 seen := make(map[schema.GroupResource]struct{}) 177 for _, info := range infos { 178 gr := info.GroupResource 179 if _, ok := seen[gr]; ok { 180 continue 181 } 182 gvrs := info.EquivalentResourceMapper.EquivalentResourcesFor(gr.WithVersion(""), "") 183 for _, gvr := range gvrs { 184 seen[gvr.GroupResource()] = struct{}{} 185 } 186 ret = append(ret, info) 187 } 188 return ret 189 } 190 191 func sortResourceInfosByGroupResource(infos []ResourceInfo) { 192 sort.Sort(byGroupResource(infos)) 193 } 194 195 type byGroupResource []ResourceInfo 196 197 func (s byGroupResource) Len() int { return len(s) } 198 199 func (s byGroupResource) Less(i, j int) bool { 200 if s[i].GroupResource.Group == s[j].GroupResource.Group { 201 return s[i].GroupResource.Resource < s[j].GroupResource.Resource 202 } 203 return s[i].GroupResource.Group < s[j].GroupResource.Group 204 } 205 206 func (s byGroupResource) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 207 208 // recordStatusSuccess marks updated ResourceInfo as completed. 209 func (s *defaultManager) recordStatusSuccess(r *ResourceInfo) { 210 s.mu.Lock() 211 defer s.mu.Unlock() 212 s.recordStatusSuccessLocked(r) 213 } 214 215 func (s *defaultManager) recordStatusSuccessLocked(r *ResourceInfo) { 216 gvrs := r.EquivalentResourceMapper.EquivalentResourcesFor(r.GroupResource.WithVersion(""), "") 217 for _, gvr := range gvrs { 218 s.recordSuccessGroupResourceLocked(gvr.GroupResource()) 219 } 220 } 221 222 func (s *defaultManager) recordSuccessGroupResourceLocked(gr schema.GroupResource) { 223 if _, ok := s.managedStatus[gr]; !ok { 224 return 225 } 226 s.managedStatus[gr].done = true 227 s.managedStatus[gr].lastErr = nil 228 } 229 230 // recordStatusFailure records latest error updating ResourceInfo. 231 func (s *defaultManager) recordStatusFailure(r *ResourceInfo, err error) { 232 s.mu.Lock() 233 defer s.mu.Unlock() 234 s.recordStatusFailureLocked(r, err) 235 } 236 237 func (s *defaultManager) recordStatusFailureLocked(r *ResourceInfo, err error) { 238 gvrs := r.EquivalentResourceMapper.EquivalentResourcesFor(r.GroupResource.WithVersion(""), "") 239 for _, gvr := range gvrs { 240 s.recordErrorGroupResourceLocked(gvr.GroupResource(), err) 241 } 242 } 243 244 func (s *defaultManager) recordErrorGroupResourceLocked(gr schema.GroupResource, err error) { 245 if _, ok := s.managedStatus[gr]; !ok { 246 return 247 } 248 s.managedStatus[gr].lastErr = err 249 } 250 251 // PendingUpdate returns if the StorageVersion of a resource is still wait to be updated. 252 func (s *defaultManager) PendingUpdate(gr schema.GroupResource) bool { 253 s.mu.RLock() 254 defer s.mu.RUnlock() 255 if _, ok := s.managedStatus[gr]; !ok { 256 return false 257 } 258 return !s.managedStatus[gr].done 259 } 260 261 // LastUpdateError returns the last error hit when updating the storage version of the given resource. 262 func (s *defaultManager) LastUpdateError(gr schema.GroupResource) error { 263 s.mu.RLock() 264 defer s.mu.RUnlock() 265 if _, ok := s.managedStatus[gr]; !ok { 266 return fmt.Errorf("couldn't find managed status for %v", gr) 267 } 268 return s.managedStatus[gr].lastErr 269 } 270 271 // setComplete marks the completion of updating StorageVersions. No write requests need to be blocked anymore. 272 func (s *defaultManager) setComplete() { 273 s.completed.Store(true) 274 } 275 276 // Completed returns if updating StorageVersions has completed. 277 func (s *defaultManager) Completed() bool { 278 return s.completed.Load() 279 } 280 281 func decodableVersions(directlyDecodableVersions []schema.GroupVersion, e runtime.EquivalentResourceRegistry, gr schema.GroupResource) []string { 282 var versions []string 283 for _, decodableVersions := range directlyDecodableVersions { 284 versions = append(versions, decodableVersions.String()) 285 } 286 287 decodingGVRs := e.EquivalentResourcesFor(gr.WithVersion(""), "") 288 for _, v := range decodingGVRs { 289 found := false 290 for _, existingVersion := range versions { 291 if existingVersion == v.GroupVersion().String() { 292 found = true 293 } 294 } 295 if found { 296 continue 297 } 298 versions = append(versions, v.GroupVersion().String()) 299 } 300 return versions 301 }