watch_cache.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /*
  2. Copyright 2015 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 storage
  14. import (
  15. "fmt"
  16. "sort"
  17. "strconv"
  18. "sync"
  19. "time"
  20. "k8s.io/kubernetes/pkg/api/errors"
  21. "k8s.io/kubernetes/pkg/api/meta"
  22. "k8s.io/kubernetes/pkg/client/cache"
  23. "k8s.io/kubernetes/pkg/runtime"
  24. "k8s.io/kubernetes/pkg/util/clock"
  25. "k8s.io/kubernetes/pkg/watch"
  26. )
  27. const (
  28. // MaximumListWait determines how long we're willing to wait for a
  29. // list if a client specified a resource version in the future.
  30. MaximumListWait = 60 * time.Second
  31. )
  32. // watchCacheEvent is a single "watch event" that is send to users of
  33. // watchCache. Additionally to a typical "watch.Event" it contains
  34. // the previous value of the object to enable proper filtering in the
  35. // upper layers.
  36. type watchCacheEvent struct {
  37. Type watch.EventType
  38. Object runtime.Object
  39. PrevObject runtime.Object
  40. ResourceVersion uint64
  41. }
  42. // watchCacheElement is a single "watch event" stored in a cache.
  43. // It contains the resource version of the object and the object
  44. // itself.
  45. type watchCacheElement struct {
  46. resourceVersion uint64
  47. watchCacheEvent watchCacheEvent
  48. }
  49. // watchCache implements a Store interface.
  50. // However, it depends on the elements implementing runtime.Object interface.
  51. //
  52. // watchCache is a "sliding window" (with a limited capacity) of objects
  53. // observed from a watch.
  54. type watchCache struct {
  55. sync.RWMutex
  56. // Condition on which lists are waiting for the fresh enough
  57. // resource version.
  58. cond *sync.Cond
  59. // Maximum size of history window.
  60. capacity int
  61. // cache is used a cyclic buffer - its first element (with the smallest
  62. // resourceVersion) is defined by startIndex, its last element is defined
  63. // by endIndex (if cache is full it will be startIndex + capacity).
  64. // Both startIndex and endIndex can be greater than buffer capacity -
  65. // you should always apply modulo capacity to get an index in cache array.
  66. cache []watchCacheElement
  67. startIndex int
  68. endIndex int
  69. // store will effectively support LIST operation from the "end of cache
  70. // history" i.e. from the moment just after the newest cached watched event.
  71. // It is necessary to effectively allow clients to start watching at now.
  72. store cache.Store
  73. // ResourceVersion up to which the watchCache is propagated.
  74. resourceVersion uint64
  75. // This handler is run at the end of every successful Replace() method.
  76. onReplace func()
  77. // This handler is run at the end of every Add/Update/Delete method
  78. // and additionally gets the previous value of the object.
  79. onEvent func(watchCacheEvent)
  80. // for testing timeouts.
  81. clock clock.Clock
  82. }
  83. func newWatchCache(capacity int) *watchCache {
  84. wc := &watchCache{
  85. capacity: capacity,
  86. cache: make([]watchCacheElement, capacity),
  87. startIndex: 0,
  88. endIndex: 0,
  89. store: cache.NewStore(cache.MetaNamespaceKeyFunc),
  90. resourceVersion: 0,
  91. clock: clock.RealClock{},
  92. }
  93. wc.cond = sync.NewCond(wc.RLocker())
  94. return wc
  95. }
  96. func (w *watchCache) Add(obj interface{}) error {
  97. object, resourceVersion, err := objectToVersionedRuntimeObject(obj)
  98. if err != nil {
  99. return err
  100. }
  101. event := watch.Event{Type: watch.Added, Object: object}
  102. f := func(obj runtime.Object) error { return w.store.Add(obj) }
  103. return w.processEvent(event, resourceVersion, f)
  104. }
  105. func (w *watchCache) Update(obj interface{}) error {
  106. object, resourceVersion, err := objectToVersionedRuntimeObject(obj)
  107. if err != nil {
  108. return err
  109. }
  110. event := watch.Event{Type: watch.Modified, Object: object}
  111. f := func(obj runtime.Object) error { return w.store.Update(obj) }
  112. return w.processEvent(event, resourceVersion, f)
  113. }
  114. func (w *watchCache) Delete(obj interface{}) error {
  115. object, resourceVersion, err := objectToVersionedRuntimeObject(obj)
  116. if err != nil {
  117. return err
  118. }
  119. event := watch.Event{Type: watch.Deleted, Object: object}
  120. f := func(obj runtime.Object) error { return w.store.Delete(obj) }
  121. return w.processEvent(event, resourceVersion, f)
  122. }
  123. func objectToVersionedRuntimeObject(obj interface{}) (runtime.Object, uint64, error) {
  124. object, ok := obj.(runtime.Object)
  125. if !ok {
  126. return nil, 0, fmt.Errorf("obj does not implement runtime.Object interface: %v", obj)
  127. }
  128. meta, err := meta.Accessor(object)
  129. if err != nil {
  130. return nil, 0, err
  131. }
  132. resourceVersion, err := parseResourceVersion(meta.GetResourceVersion())
  133. if err != nil {
  134. return nil, 0, err
  135. }
  136. return object, resourceVersion, nil
  137. }
  138. func parseResourceVersion(resourceVersion string) (uint64, error) {
  139. if resourceVersion == "" {
  140. return 0, nil
  141. }
  142. return strconv.ParseUint(resourceVersion, 10, 64)
  143. }
  144. func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, updateFunc func(runtime.Object) error) error {
  145. w.Lock()
  146. defer w.Unlock()
  147. previous, exists, err := w.store.Get(event.Object)
  148. if err != nil {
  149. return err
  150. }
  151. var prevObject runtime.Object
  152. if exists {
  153. prevObject = previous.(runtime.Object)
  154. }
  155. watchCacheEvent := watchCacheEvent{event.Type, event.Object, prevObject, resourceVersion}
  156. if w.onEvent != nil {
  157. w.onEvent(watchCacheEvent)
  158. }
  159. w.updateCache(resourceVersion, watchCacheEvent)
  160. w.resourceVersion = resourceVersion
  161. w.cond.Broadcast()
  162. return updateFunc(event.Object)
  163. }
  164. // Assumes that lock is already held for write.
  165. func (w *watchCache) updateCache(resourceVersion uint64, event watchCacheEvent) {
  166. if w.endIndex == w.startIndex+w.capacity {
  167. // Cache is full - remove the oldest element.
  168. w.startIndex++
  169. }
  170. w.cache[w.endIndex%w.capacity] = watchCacheElement{resourceVersion, event}
  171. w.endIndex++
  172. }
  173. func (w *watchCache) List() []interface{} {
  174. w.RLock()
  175. defer w.RUnlock()
  176. return w.store.List()
  177. }
  178. func (w *watchCache) WaitUntilFreshAndList(resourceVersion uint64) ([]interface{}, uint64, error) {
  179. startTime := w.clock.Now()
  180. go func() {
  181. // Wake us up when the time limit has expired. The docs
  182. // promise that time.After (well, NewTimer, which it calls)
  183. // will wait *at least* the duration given. Since this go
  184. // routine starts sometime after we record the start time, and
  185. // it will wake up the loop below sometime after the broadcast,
  186. // we don't need to worry about waking it up before the time
  187. // has expired accidentally.
  188. <-w.clock.After(MaximumListWait)
  189. w.cond.Broadcast()
  190. }()
  191. w.RLock()
  192. defer w.RUnlock()
  193. for w.resourceVersion < resourceVersion {
  194. if w.clock.Since(startTime) >= MaximumListWait {
  195. return nil, 0, fmt.Errorf("time limit exceeded while waiting for resource version %v (current value: %v)", resourceVersion, w.resourceVersion)
  196. }
  197. w.cond.Wait()
  198. }
  199. return w.store.List(), w.resourceVersion, nil
  200. }
  201. func (w *watchCache) ListKeys() []string {
  202. w.RLock()
  203. defer w.RUnlock()
  204. return w.store.ListKeys()
  205. }
  206. func (w *watchCache) Get(obj interface{}) (interface{}, bool, error) {
  207. w.RLock()
  208. defer w.RUnlock()
  209. return w.store.Get(obj)
  210. }
  211. func (w *watchCache) GetByKey(key string) (interface{}, bool, error) {
  212. w.RLock()
  213. defer w.RUnlock()
  214. return w.store.GetByKey(key)
  215. }
  216. func (w *watchCache) Replace(objs []interface{}, resourceVersion string) error {
  217. version, err := parseResourceVersion(resourceVersion)
  218. if err != nil {
  219. return err
  220. }
  221. w.Lock()
  222. defer w.Unlock()
  223. w.startIndex = 0
  224. w.endIndex = 0
  225. if err := w.store.Replace(objs, resourceVersion); err != nil {
  226. return err
  227. }
  228. w.resourceVersion = version
  229. if w.onReplace != nil {
  230. w.onReplace()
  231. }
  232. w.cond.Broadcast()
  233. return nil
  234. }
  235. func (w *watchCache) SetOnReplace(onReplace func()) {
  236. w.Lock()
  237. defer w.Unlock()
  238. w.onReplace = onReplace
  239. }
  240. func (w *watchCache) SetOnEvent(onEvent func(watchCacheEvent)) {
  241. w.Lock()
  242. defer w.Unlock()
  243. w.onEvent = onEvent
  244. }
  245. func (w *watchCache) GetAllEventsSinceThreadUnsafe(resourceVersion uint64) ([]watchCacheEvent, error) {
  246. size := w.endIndex - w.startIndex
  247. oldest := w.resourceVersion
  248. if size > 0 {
  249. oldest = w.cache[w.startIndex%w.capacity].resourceVersion
  250. }
  251. if resourceVersion == 0 {
  252. // resourceVersion = 0 means that we don't require any specific starting point
  253. // and we would like to start watching from ~now.
  254. // However, to keep backward compatibility, we additionally need to return the
  255. // current state and only then start watching from that point.
  256. //
  257. // TODO: In v2 api, we should stop returning the current state - #13969.
  258. allItems := w.store.List()
  259. result := make([]watchCacheEvent, len(allItems))
  260. for i, item := range allItems {
  261. result[i] = watchCacheEvent{Type: watch.Added, Object: item.(runtime.Object)}
  262. }
  263. return result, nil
  264. }
  265. if resourceVersion < oldest-1 {
  266. return nil, errors.NewGone(fmt.Sprintf("too old resource version: %d (%d)", resourceVersion, oldest-1))
  267. }
  268. // Binary search the smallest index at which resourceVersion is greater than the given one.
  269. f := func(i int) bool {
  270. return w.cache[(w.startIndex+i)%w.capacity].resourceVersion > resourceVersion
  271. }
  272. first := sort.Search(size, f)
  273. result := make([]watchCacheEvent, size-first)
  274. for i := 0; i < size-first; i++ {
  275. result[i] = w.cache[(w.startIndex+first+i)%w.capacity].watchCacheEvent
  276. }
  277. return result, nil
  278. }
  279. func (w *watchCache) GetAllEventsSince(resourceVersion uint64) ([]watchCacheEvent, error) {
  280. w.RLock()
  281. defer w.RUnlock()
  282. return w.GetAllEventsSinceThreadUnsafe(resourceVersion)
  283. }
  284. func (w *watchCache) Resync() error {
  285. // Nothing to do
  286. return nil
  287. }