pv_controller_base.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package persistentvolume
  14. import (
  15. "fmt"
  16. "strconv"
  17. "time"
  18. v1 "k8s.io/api/core/v1"
  19. "k8s.io/apimachinery/pkg/api/errors"
  20. "k8s.io/apimachinery/pkg/api/meta"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/labels"
  23. utilruntime "k8s.io/apimachinery/pkg/util/runtime"
  24. "k8s.io/apimachinery/pkg/util/wait"
  25. coreinformers "k8s.io/client-go/informers/core/v1"
  26. storageinformers "k8s.io/client-go/informers/storage/v1"
  27. clientset "k8s.io/client-go/kubernetes"
  28. "k8s.io/client-go/kubernetes/scheme"
  29. v1core "k8s.io/client-go/kubernetes/typed/core/v1"
  30. corelisters "k8s.io/client-go/listers/core/v1"
  31. "k8s.io/client-go/tools/cache"
  32. "k8s.io/client-go/tools/record"
  33. "k8s.io/client-go/util/workqueue"
  34. cloudprovider "k8s.io/cloud-provider"
  35. "k8s.io/kubernetes/pkg/controller"
  36. "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/metrics"
  37. pvutil "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/util"
  38. "k8s.io/kubernetes/pkg/util/goroutinemap"
  39. vol "k8s.io/kubernetes/pkg/volume"
  40. "k8s.io/klog"
  41. )
  42. // This file contains the controller base functionality, i.e. framework to
  43. // process PV/PVC added/updated/deleted events. The real binding, provisioning,
  44. // recycling and deleting is done in pv_controller.go
  45. // ControllerParameters contains arguments for creation of a new
  46. // PersistentVolume controller.
  47. type ControllerParameters struct {
  48. KubeClient clientset.Interface
  49. SyncPeriod time.Duration
  50. VolumePlugins []vol.VolumePlugin
  51. Cloud cloudprovider.Interface
  52. ClusterName string
  53. VolumeInformer coreinformers.PersistentVolumeInformer
  54. ClaimInformer coreinformers.PersistentVolumeClaimInformer
  55. ClassInformer storageinformers.StorageClassInformer
  56. PodInformer coreinformers.PodInformer
  57. NodeInformer coreinformers.NodeInformer
  58. EventRecorder record.EventRecorder
  59. EnableDynamicProvisioning bool
  60. }
  61. // NewController creates a new PersistentVolume controller
  62. func NewController(p ControllerParameters) (*PersistentVolumeController, error) {
  63. eventRecorder := p.EventRecorder
  64. if eventRecorder == nil {
  65. broadcaster := record.NewBroadcaster()
  66. broadcaster.StartLogging(klog.Infof)
  67. broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: p.KubeClient.CoreV1().Events("")})
  68. eventRecorder = broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "persistentvolume-controller"})
  69. }
  70. controller := &PersistentVolumeController{
  71. volumes: newPersistentVolumeOrderedIndex(),
  72. claims: cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc),
  73. kubeClient: p.KubeClient,
  74. eventRecorder: eventRecorder,
  75. runningOperations: goroutinemap.NewGoRoutineMap(true /* exponentialBackOffOnError */),
  76. cloud: p.Cloud,
  77. enableDynamicProvisioning: p.EnableDynamicProvisioning,
  78. clusterName: p.ClusterName,
  79. createProvisionedPVRetryCount: createProvisionedPVRetryCount,
  80. createProvisionedPVInterval: createProvisionedPVInterval,
  81. claimQueue: workqueue.NewNamed("claims"),
  82. volumeQueue: workqueue.NewNamed("volumes"),
  83. resyncPeriod: p.SyncPeriod,
  84. operationTimestamps: metrics.NewOperationStartTimeCache(),
  85. }
  86. // Prober is nil because PV is not aware of Flexvolume.
  87. if err := controller.volumePluginMgr.InitPlugins(p.VolumePlugins, nil /* prober */, controller); err != nil {
  88. return nil, fmt.Errorf("Could not initialize volume plugins for PersistentVolume Controller: %v", err)
  89. }
  90. p.VolumeInformer.Informer().AddEventHandler(
  91. cache.ResourceEventHandlerFuncs{
  92. AddFunc: func(obj interface{}) { controller.enqueueWork(controller.volumeQueue, obj) },
  93. UpdateFunc: func(oldObj, newObj interface{}) { controller.enqueueWork(controller.volumeQueue, newObj) },
  94. DeleteFunc: func(obj interface{}) { controller.enqueueWork(controller.volumeQueue, obj) },
  95. },
  96. )
  97. controller.volumeLister = p.VolumeInformer.Lister()
  98. controller.volumeListerSynced = p.VolumeInformer.Informer().HasSynced
  99. p.ClaimInformer.Informer().AddEventHandler(
  100. cache.ResourceEventHandlerFuncs{
  101. AddFunc: func(obj interface{}) { controller.enqueueWork(controller.claimQueue, obj) },
  102. UpdateFunc: func(oldObj, newObj interface{}) { controller.enqueueWork(controller.claimQueue, newObj) },
  103. DeleteFunc: func(obj interface{}) { controller.enqueueWork(controller.claimQueue, obj) },
  104. },
  105. )
  106. controller.claimLister = p.ClaimInformer.Lister()
  107. controller.claimListerSynced = p.ClaimInformer.Informer().HasSynced
  108. controller.classLister = p.ClassInformer.Lister()
  109. controller.classListerSynced = p.ClassInformer.Informer().HasSynced
  110. controller.podLister = p.PodInformer.Lister()
  111. controller.podListerSynced = p.PodInformer.Informer().HasSynced
  112. controller.NodeLister = p.NodeInformer.Lister()
  113. controller.NodeListerSynced = p.NodeInformer.Informer().HasSynced
  114. return controller, nil
  115. }
  116. // initializeCaches fills all controller caches with initial data from etcd in
  117. // order to have the caches already filled when first addClaim/addVolume to
  118. // perform initial synchronization of the controller.
  119. func (ctrl *PersistentVolumeController) initializeCaches(volumeLister corelisters.PersistentVolumeLister, claimLister corelisters.PersistentVolumeClaimLister) {
  120. volumeList, err := volumeLister.List(labels.Everything())
  121. if err != nil {
  122. klog.Errorf("PersistentVolumeController can't initialize caches: %v", err)
  123. return
  124. }
  125. for _, volume := range volumeList {
  126. volumeClone := volume.DeepCopy()
  127. if _, err = ctrl.storeVolumeUpdate(volumeClone); err != nil {
  128. klog.Errorf("error updating volume cache: %v", err)
  129. }
  130. }
  131. claimList, err := claimLister.List(labels.Everything())
  132. if err != nil {
  133. klog.Errorf("PersistentVolumeController can't initialize caches: %v", err)
  134. return
  135. }
  136. for _, claim := range claimList {
  137. if _, err = ctrl.storeClaimUpdate(claim.DeepCopy()); err != nil {
  138. klog.Errorf("error updating claim cache: %v", err)
  139. }
  140. }
  141. klog.V(4).Infof("controller initialized")
  142. }
  143. // enqueueWork adds volume or claim to given work queue.
  144. func (ctrl *PersistentVolumeController) enqueueWork(queue workqueue.Interface, obj interface{}) {
  145. // Beware of "xxx deleted" events
  146. if unknown, ok := obj.(cache.DeletedFinalStateUnknown); ok && unknown.Obj != nil {
  147. obj = unknown.Obj
  148. }
  149. objName, err := controller.KeyFunc(obj)
  150. if err != nil {
  151. klog.Errorf("failed to get key from object: %v", err)
  152. return
  153. }
  154. klog.V(5).Infof("enqueued %q for sync", objName)
  155. queue.Add(objName)
  156. }
  157. func (ctrl *PersistentVolumeController) storeVolumeUpdate(volume interface{}) (bool, error) {
  158. return storeObjectUpdate(ctrl.volumes.store, volume, "volume")
  159. }
  160. func (ctrl *PersistentVolumeController) storeClaimUpdate(claim interface{}) (bool, error) {
  161. return storeObjectUpdate(ctrl.claims, claim, "claim")
  162. }
  163. // updateVolume runs in worker thread and handles "volume added",
  164. // "volume updated" and "periodic sync" events.
  165. func (ctrl *PersistentVolumeController) updateVolume(volume *v1.PersistentVolume) {
  166. // Store the new volume version in the cache and do not process it if this
  167. // is an old version.
  168. new, err := ctrl.storeVolumeUpdate(volume)
  169. if err != nil {
  170. klog.Errorf("%v", err)
  171. }
  172. if !new {
  173. return
  174. }
  175. err = ctrl.syncVolume(volume)
  176. if err != nil {
  177. if errors.IsConflict(err) {
  178. // Version conflict error happens quite often and the controller
  179. // recovers from it easily.
  180. klog.V(3).Infof("could not sync volume %q: %+v", volume.Name, err)
  181. } else {
  182. klog.Errorf("could not sync volume %q: %+v", volume.Name, err)
  183. }
  184. }
  185. }
  186. // deleteVolume runs in worker thread and handles "volume deleted" event.
  187. func (ctrl *PersistentVolumeController) deleteVolume(volume *v1.PersistentVolume) {
  188. _ = ctrl.volumes.store.Delete(volume)
  189. klog.V(4).Infof("volume %q deleted", volume.Name)
  190. // record deletion metric if a deletion start timestamp is in the cache
  191. // the following calls will be a no-op if there is nothing for this volume in the cache
  192. // end of timestamp cache entry lifecycle, "RecordMetric" will do the clean
  193. metrics.RecordMetric(volume.Name, &ctrl.operationTimestamps, nil)
  194. if volume.Spec.ClaimRef == nil {
  195. return
  196. }
  197. // sync the claim when its volume is deleted. Explicitly syncing the
  198. // claim here in response to volume deletion prevents the claim from
  199. // waiting until the next sync period for its Lost status.
  200. claimKey := claimrefToClaimKey(volume.Spec.ClaimRef)
  201. klog.V(5).Infof("deleteVolume[%s]: scheduling sync of claim %q", volume.Name, claimKey)
  202. ctrl.claimQueue.Add(claimKey)
  203. }
  204. // updateClaim runs in worker thread and handles "claim added",
  205. // "claim updated" and "periodic sync" events.
  206. func (ctrl *PersistentVolumeController) updateClaim(claim *v1.PersistentVolumeClaim) {
  207. // Store the new claim version in the cache and do not process it if this is
  208. // an old version.
  209. new, err := ctrl.storeClaimUpdate(claim)
  210. if err != nil {
  211. klog.Errorf("%v", err)
  212. }
  213. if !new {
  214. return
  215. }
  216. err = ctrl.syncClaim(claim)
  217. if err != nil {
  218. if errors.IsConflict(err) {
  219. // Version conflict error happens quite often and the controller
  220. // recovers from it easily.
  221. klog.V(3).Infof("could not sync claim %q: %+v", claimToClaimKey(claim), err)
  222. } else {
  223. klog.Errorf("could not sync volume %q: %+v", claimToClaimKey(claim), err)
  224. }
  225. }
  226. }
  227. // Unit test [5-5] [5-6] [5-7]
  228. // deleteClaim runs in worker thread and handles "claim deleted" event.
  229. func (ctrl *PersistentVolumeController) deleteClaim(claim *v1.PersistentVolumeClaim) {
  230. _ = ctrl.claims.Delete(claim)
  231. claimKey := claimToClaimKey(claim)
  232. klog.V(4).Infof("claim %q deleted", claimKey)
  233. // clean any possible unfinished provision start timestamp from cache
  234. // Unit test [5-8] [5-9]
  235. ctrl.operationTimestamps.Delete(claimKey)
  236. volumeName := claim.Spec.VolumeName
  237. if volumeName == "" {
  238. klog.V(5).Infof("deleteClaim[%q]: volume not bound", claimKey)
  239. return
  240. }
  241. // sync the volume when its claim is deleted. Explicitly sync'ing the
  242. // volume here in response to claim deletion prevents the volume from
  243. // waiting until the next sync period for its Release.
  244. klog.V(5).Infof("deleteClaim[%q]: scheduling sync of volume %s", claimKey, volumeName)
  245. ctrl.volumeQueue.Add(volumeName)
  246. }
  247. // Run starts all of this controller's control loops
  248. func (ctrl *PersistentVolumeController) Run(stopCh <-chan struct{}) {
  249. defer utilruntime.HandleCrash()
  250. defer ctrl.claimQueue.ShutDown()
  251. defer ctrl.volumeQueue.ShutDown()
  252. klog.Infof("Starting persistent volume controller")
  253. defer klog.Infof("Shutting down persistent volume controller")
  254. if !controller.WaitForCacheSync("persistent volume", stopCh, ctrl.volumeListerSynced, ctrl.claimListerSynced, ctrl.classListerSynced, ctrl.podListerSynced, ctrl.NodeListerSynced) {
  255. return
  256. }
  257. ctrl.initializeCaches(ctrl.volumeLister, ctrl.claimLister)
  258. go wait.Until(ctrl.resync, ctrl.resyncPeriod, stopCh)
  259. go wait.Until(ctrl.volumeWorker, time.Second, stopCh)
  260. go wait.Until(ctrl.claimWorker, time.Second, stopCh)
  261. metrics.Register(ctrl.volumes.store, ctrl.claims)
  262. <-stopCh
  263. }
  264. // volumeWorker processes items from volumeQueue. It must run only once,
  265. // syncVolume is not assured to be reentrant.
  266. func (ctrl *PersistentVolumeController) volumeWorker() {
  267. workFunc := func() bool {
  268. keyObj, quit := ctrl.volumeQueue.Get()
  269. if quit {
  270. return true
  271. }
  272. defer ctrl.volumeQueue.Done(keyObj)
  273. key := keyObj.(string)
  274. klog.V(5).Infof("volumeWorker[%s]", key)
  275. _, name, err := cache.SplitMetaNamespaceKey(key)
  276. if err != nil {
  277. klog.V(4).Infof("error getting name of volume %q to get volume from informer: %v", key, err)
  278. return false
  279. }
  280. volume, err := ctrl.volumeLister.Get(name)
  281. if err == nil {
  282. // The volume still exists in informer cache, the event must have
  283. // been add/update/sync
  284. ctrl.updateVolume(volume)
  285. return false
  286. }
  287. if !errors.IsNotFound(err) {
  288. klog.V(2).Infof("error getting volume %q from informer: %v", key, err)
  289. return false
  290. }
  291. // The volume is not in informer cache, the event must have been
  292. // "delete"
  293. volumeObj, found, err := ctrl.volumes.store.GetByKey(key)
  294. if err != nil {
  295. klog.V(2).Infof("error getting volume %q from cache: %v", key, err)
  296. return false
  297. }
  298. if !found {
  299. // The controller has already processed the delete event and
  300. // deleted the volume from its cache
  301. klog.V(2).Infof("deletion of volume %q was already processed", key)
  302. return false
  303. }
  304. volume, ok := volumeObj.(*v1.PersistentVolume)
  305. if !ok {
  306. klog.Errorf("expected volume, got %+v", volumeObj)
  307. return false
  308. }
  309. ctrl.deleteVolume(volume)
  310. return false
  311. }
  312. for {
  313. if quit := workFunc(); quit {
  314. klog.Infof("volume worker queue shutting down")
  315. return
  316. }
  317. }
  318. }
  319. // claimWorker processes items from claimQueue. It must run only once,
  320. // syncClaim is not reentrant.
  321. func (ctrl *PersistentVolumeController) claimWorker() {
  322. workFunc := func() bool {
  323. keyObj, quit := ctrl.claimQueue.Get()
  324. if quit {
  325. return true
  326. }
  327. defer ctrl.claimQueue.Done(keyObj)
  328. key := keyObj.(string)
  329. klog.V(5).Infof("claimWorker[%s]", key)
  330. namespace, name, err := cache.SplitMetaNamespaceKey(key)
  331. if err != nil {
  332. klog.V(4).Infof("error getting namespace & name of claim %q to get claim from informer: %v", key, err)
  333. return false
  334. }
  335. claim, err := ctrl.claimLister.PersistentVolumeClaims(namespace).Get(name)
  336. if err == nil {
  337. // The claim still exists in informer cache, the event must have
  338. // been add/update/sync
  339. ctrl.updateClaim(claim)
  340. return false
  341. }
  342. if !errors.IsNotFound(err) {
  343. klog.V(2).Infof("error getting claim %q from informer: %v", key, err)
  344. return false
  345. }
  346. // The claim is not in informer cache, the event must have been "delete"
  347. claimObj, found, err := ctrl.claims.GetByKey(key)
  348. if err != nil {
  349. klog.V(2).Infof("error getting claim %q from cache: %v", key, err)
  350. return false
  351. }
  352. if !found {
  353. // The controller has already processed the delete event and
  354. // deleted the claim from its cache
  355. klog.V(2).Infof("deletion of claim %q was already processed", key)
  356. return false
  357. }
  358. claim, ok := claimObj.(*v1.PersistentVolumeClaim)
  359. if !ok {
  360. klog.Errorf("expected claim, got %+v", claimObj)
  361. return false
  362. }
  363. ctrl.deleteClaim(claim)
  364. return false
  365. }
  366. for {
  367. if quit := workFunc(); quit {
  368. klog.Infof("claim worker queue shutting down")
  369. return
  370. }
  371. }
  372. }
  373. // resync supplements short resync period of shared informers - we don't want
  374. // all consumers of PV/PVC shared informer to have a short resync period,
  375. // therefore we do our own.
  376. func (ctrl *PersistentVolumeController) resync() {
  377. klog.V(4).Infof("resyncing PV controller")
  378. pvcs, err := ctrl.claimLister.List(labels.NewSelector())
  379. if err != nil {
  380. klog.Warningf("cannot list claims: %s", err)
  381. return
  382. }
  383. for _, pvc := range pvcs {
  384. ctrl.enqueueWork(ctrl.claimQueue, pvc)
  385. }
  386. pvs, err := ctrl.volumeLister.List(labels.NewSelector())
  387. if err != nil {
  388. klog.Warningf("cannot list persistent volumes: %s", err)
  389. return
  390. }
  391. for _, pv := range pvs {
  392. ctrl.enqueueWork(ctrl.volumeQueue, pv)
  393. }
  394. }
  395. // setClaimProvisioner saves
  396. // claim.Annotations[pvutil.AnnStorageProvisioner] = class.Provisioner
  397. func (ctrl *PersistentVolumeController) setClaimProvisioner(claim *v1.PersistentVolumeClaim, provisionerName string) (*v1.PersistentVolumeClaim, error) {
  398. if val, ok := claim.Annotations[pvutil.AnnStorageProvisioner]; ok && val == provisionerName {
  399. // annotation is already set, nothing to do
  400. return claim, nil
  401. }
  402. // The volume from method args can be pointing to watcher cache. We must not
  403. // modify these, therefore create a copy.
  404. claimClone := claim.DeepCopy()
  405. metav1.SetMetaDataAnnotation(&claimClone.ObjectMeta, pvutil.AnnStorageProvisioner, provisionerName)
  406. newClaim, err := ctrl.kubeClient.CoreV1().PersistentVolumeClaims(claim.Namespace).Update(claimClone)
  407. if err != nil {
  408. return newClaim, err
  409. }
  410. _, err = ctrl.storeClaimUpdate(newClaim)
  411. if err != nil {
  412. return newClaim, err
  413. }
  414. return newClaim, nil
  415. }
  416. // Stateless functions
  417. func getClaimStatusForLogging(claim *v1.PersistentVolumeClaim) string {
  418. bound := metav1.HasAnnotation(claim.ObjectMeta, pvutil.AnnBindCompleted)
  419. boundByController := metav1.HasAnnotation(claim.ObjectMeta, pvutil.AnnBoundByController)
  420. return fmt.Sprintf("phase: %s, bound to: %q, bindCompleted: %v, boundByController: %v", claim.Status.Phase, claim.Spec.VolumeName, bound, boundByController)
  421. }
  422. func getVolumeStatusForLogging(volume *v1.PersistentVolume) string {
  423. boundByController := metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnBoundByController)
  424. claimName := ""
  425. if volume.Spec.ClaimRef != nil {
  426. claimName = fmt.Sprintf("%s/%s (uid: %s)", volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name, volume.Spec.ClaimRef.UID)
  427. }
  428. return fmt.Sprintf("phase: %s, bound to: %q, boundByController: %v", volume.Status.Phase, claimName, boundByController)
  429. }
  430. // storeObjectUpdate updates given cache with a new object version from Informer
  431. // callback (i.e. with events from etcd) or with an object modified by the
  432. // controller itself. Returns "true", if the cache was updated, false if the
  433. // object is an old version and should be ignored.
  434. func storeObjectUpdate(store cache.Store, obj interface{}, className string) (bool, error) {
  435. objName, err := controller.KeyFunc(obj)
  436. if err != nil {
  437. return false, fmt.Errorf("Couldn't get key for object %+v: %v", obj, err)
  438. }
  439. oldObj, found, err := store.Get(obj)
  440. if err != nil {
  441. return false, fmt.Errorf("Error finding %s %q in controller cache: %v", className, objName, err)
  442. }
  443. objAccessor, err := meta.Accessor(obj)
  444. if err != nil {
  445. return false, err
  446. }
  447. if !found {
  448. // This is a new object
  449. klog.V(4).Infof("storeObjectUpdate: adding %s %q, version %s", className, objName, objAccessor.GetResourceVersion())
  450. if err = store.Add(obj); err != nil {
  451. return false, fmt.Errorf("Error adding %s %q to controller cache: %v", className, objName, err)
  452. }
  453. return true, nil
  454. }
  455. oldObjAccessor, err := meta.Accessor(oldObj)
  456. if err != nil {
  457. return false, err
  458. }
  459. objResourceVersion, err := strconv.ParseInt(objAccessor.GetResourceVersion(), 10, 64)
  460. if err != nil {
  461. return false, fmt.Errorf("Error parsing ResourceVersion %q of %s %q: %s", objAccessor.GetResourceVersion(), className, objName, err)
  462. }
  463. oldObjResourceVersion, err := strconv.ParseInt(oldObjAccessor.GetResourceVersion(), 10, 64)
  464. if err != nil {
  465. return false, fmt.Errorf("Error parsing old ResourceVersion %q of %s %q: %s", oldObjAccessor.GetResourceVersion(), className, objName, err)
  466. }
  467. // Throw away only older version, let the same version pass - we do want to
  468. // get periodic sync events.
  469. if oldObjResourceVersion > objResourceVersion {
  470. klog.V(4).Infof("storeObjectUpdate: ignoring %s %q version %s", className, objName, objAccessor.GetResourceVersion())
  471. return false, nil
  472. }
  473. klog.V(4).Infof("storeObjectUpdate updating %s %q with version %s", className, objName, objAccessor.GetResourceVersion())
  474. if err = store.Update(obj); err != nil {
  475. return false, fmt.Errorf("Error updating %s %q in controller cache: %v", className, objName, err)
  476. }
  477. return true, nil
  478. }