fixture.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  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 testing
  14. import (
  15. "fmt"
  16. "sync"
  17. jsonpatch "github.com/evanphx/json-patch"
  18. "k8s.io/apimachinery/pkg/api/errors"
  19. "k8s.io/apimachinery/pkg/api/meta"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/apimachinery/pkg/runtime/schema"
  23. "k8s.io/apimachinery/pkg/types"
  24. "k8s.io/apimachinery/pkg/util/json"
  25. "k8s.io/apimachinery/pkg/util/strategicpatch"
  26. "k8s.io/apimachinery/pkg/watch"
  27. restclient "k8s.io/client-go/rest"
  28. )
  29. // ObjectTracker keeps track of objects. It is intended to be used to
  30. // fake calls to a server by returning objects based on their kind,
  31. // namespace and name.
  32. type ObjectTracker interface {
  33. // Add adds an object to the tracker. If object being added
  34. // is a list, its items are added separately.
  35. Add(obj runtime.Object) error
  36. // Get retrieves the object by its kind, namespace and name.
  37. Get(gvr schema.GroupVersionResource, ns, name string) (runtime.Object, error)
  38. // Create adds an object to the tracker in the specified namespace.
  39. Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error
  40. // Update updates an existing object in the tracker in the specified namespace.
  41. Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error
  42. // List retrieves all objects of a given kind in the given
  43. // namespace. Only non-List kinds are accepted.
  44. List(gvr schema.GroupVersionResource, gvk schema.GroupVersionKind, ns string) (runtime.Object, error)
  45. // Delete deletes an existing object from the tracker. If object
  46. // didn't exist in the tracker prior to deletion, Delete returns
  47. // no error.
  48. Delete(gvr schema.GroupVersionResource, ns, name string) error
  49. // Watch watches objects from the tracker. Watch returns a channel
  50. // which will push added / modified / deleted object.
  51. Watch(gvr schema.GroupVersionResource, ns string) (watch.Interface, error)
  52. }
  53. // ObjectScheme abstracts the implementation of common operations on objects.
  54. type ObjectScheme interface {
  55. runtime.ObjectCreater
  56. runtime.ObjectTyper
  57. }
  58. // ObjectReaction returns a ReactionFunc that applies core.Action to
  59. // the given tracker.
  60. func ObjectReaction(tracker ObjectTracker) ReactionFunc {
  61. return func(action Action) (bool, runtime.Object, error) {
  62. ns := action.GetNamespace()
  63. gvr := action.GetResource()
  64. // Here and below we need to switch on implementation types,
  65. // not on interfaces, as some interfaces are identical
  66. // (e.g. UpdateAction and CreateAction), so if we use them,
  67. // updates and creates end up matching the same case branch.
  68. switch action := action.(type) {
  69. case ListActionImpl:
  70. obj, err := tracker.List(gvr, action.GetKind(), ns)
  71. return true, obj, err
  72. case GetActionImpl:
  73. obj, err := tracker.Get(gvr, ns, action.GetName())
  74. return true, obj, err
  75. case CreateActionImpl:
  76. objMeta, err := meta.Accessor(action.GetObject())
  77. if err != nil {
  78. return true, nil, err
  79. }
  80. if action.GetSubresource() == "" {
  81. err = tracker.Create(gvr, action.GetObject(), ns)
  82. } else {
  83. // TODO: Currently we're handling subresource creation as an update
  84. // on the enclosing resource. This works for some subresources but
  85. // might not be generic enough.
  86. err = tracker.Update(gvr, action.GetObject(), ns)
  87. }
  88. if err != nil {
  89. return true, nil, err
  90. }
  91. obj, err := tracker.Get(gvr, ns, objMeta.GetName())
  92. return true, obj, err
  93. case UpdateActionImpl:
  94. objMeta, err := meta.Accessor(action.GetObject())
  95. if err != nil {
  96. return true, nil, err
  97. }
  98. err = tracker.Update(gvr, action.GetObject(), ns)
  99. if err != nil {
  100. return true, nil, err
  101. }
  102. obj, err := tracker.Get(gvr, ns, objMeta.GetName())
  103. return true, obj, err
  104. case DeleteActionImpl:
  105. err := tracker.Delete(gvr, ns, action.GetName())
  106. if err != nil {
  107. return true, nil, err
  108. }
  109. return true, nil, nil
  110. case PatchActionImpl:
  111. obj, err := tracker.Get(gvr, ns, action.GetName())
  112. if err != nil {
  113. return true, nil, err
  114. }
  115. old, err := json.Marshal(obj)
  116. if err != nil {
  117. return true, nil, err
  118. }
  119. switch action.GetPatchType() {
  120. case types.JSONPatchType:
  121. patch, err := jsonpatch.DecodePatch(action.GetPatch())
  122. if err != nil {
  123. return true, nil, err
  124. }
  125. modified, err := patch.Apply(old)
  126. if err != nil {
  127. return true, nil, err
  128. }
  129. if err = json.Unmarshal(modified, obj); err != nil {
  130. return true, nil, err
  131. }
  132. case types.MergePatchType:
  133. modified, err := jsonpatch.MergePatch(old, action.GetPatch())
  134. if err != nil {
  135. return true, nil, err
  136. }
  137. if err := json.Unmarshal(modified, obj); err != nil {
  138. return true, nil, err
  139. }
  140. case types.StrategicMergePatchType:
  141. mergedByte, err := strategicpatch.StrategicMergePatch(old, action.GetPatch(), obj)
  142. if err != nil {
  143. return true, nil, err
  144. }
  145. if err = json.Unmarshal(mergedByte, obj); err != nil {
  146. return true, nil, err
  147. }
  148. default:
  149. return true, nil, fmt.Errorf("PatchType is not supported")
  150. }
  151. if err = tracker.Update(gvr, obj, ns); err != nil {
  152. return true, nil, err
  153. }
  154. return true, obj, nil
  155. default:
  156. return false, nil, fmt.Errorf("no reaction implemented for %s", action)
  157. }
  158. }
  159. }
  160. type tracker struct {
  161. scheme ObjectScheme
  162. decoder runtime.Decoder
  163. lock sync.RWMutex
  164. objects map[schema.GroupVersionResource][]runtime.Object
  165. // The value type of watchers is a map of which the key is either a namespace or
  166. // all/non namespace aka "" and its value is list of fake watchers.
  167. // Manipulations on resources will broadcast the notification events into the
  168. // watchers' channel. Note that too many unhandled events (currently 100,
  169. // see apimachinery/pkg/watch.DefaultChanSize) will cause a panic.
  170. watchers map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher
  171. }
  172. var _ ObjectTracker = &tracker{}
  173. // NewObjectTracker returns an ObjectTracker that can be used to keep track
  174. // of objects for the fake clientset. Mostly useful for unit tests.
  175. func NewObjectTracker(scheme ObjectScheme, decoder runtime.Decoder) ObjectTracker {
  176. return &tracker{
  177. scheme: scheme,
  178. decoder: decoder,
  179. objects: make(map[schema.GroupVersionResource][]runtime.Object),
  180. watchers: make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher),
  181. }
  182. }
  183. func (t *tracker) List(gvr schema.GroupVersionResource, gvk schema.GroupVersionKind, ns string) (runtime.Object, error) {
  184. // Heuristic for list kind: original kind + List suffix. Might
  185. // not always be true but this tracker has a pretty limited
  186. // understanding of the actual API model.
  187. listGVK := gvk
  188. listGVK.Kind = listGVK.Kind + "List"
  189. // GVK does have the concept of "internal version". The scheme recognizes
  190. // the runtime.APIVersionInternal, but not the empty string.
  191. if listGVK.Version == "" {
  192. listGVK.Version = runtime.APIVersionInternal
  193. }
  194. list, err := t.scheme.New(listGVK)
  195. if err != nil {
  196. return nil, err
  197. }
  198. if !meta.IsListType(list) {
  199. return nil, fmt.Errorf("%q is not a list type", listGVK.Kind)
  200. }
  201. t.lock.RLock()
  202. defer t.lock.RUnlock()
  203. objs, ok := t.objects[gvr]
  204. if !ok {
  205. return list, nil
  206. }
  207. matchingObjs, err := filterByNamespaceAndName(objs, ns, "")
  208. if err != nil {
  209. return nil, err
  210. }
  211. if err := meta.SetList(list, matchingObjs); err != nil {
  212. return nil, err
  213. }
  214. return list.DeepCopyObject(), nil
  215. }
  216. func (t *tracker) Watch(gvr schema.GroupVersionResource, ns string) (watch.Interface, error) {
  217. t.lock.Lock()
  218. defer t.lock.Unlock()
  219. fakewatcher := watch.NewRaceFreeFake()
  220. if _, exists := t.watchers[gvr]; !exists {
  221. t.watchers[gvr] = make(map[string][]*watch.RaceFreeFakeWatcher)
  222. }
  223. t.watchers[gvr][ns] = append(t.watchers[gvr][ns], fakewatcher)
  224. return fakewatcher, nil
  225. }
  226. func (t *tracker) Get(gvr schema.GroupVersionResource, ns, name string) (runtime.Object, error) {
  227. errNotFound := errors.NewNotFound(gvr.GroupResource(), name)
  228. t.lock.RLock()
  229. defer t.lock.RUnlock()
  230. objs, ok := t.objects[gvr]
  231. if !ok {
  232. return nil, errNotFound
  233. }
  234. matchingObjs, err := filterByNamespaceAndName(objs, ns, name)
  235. if err != nil {
  236. return nil, err
  237. }
  238. if len(matchingObjs) == 0 {
  239. return nil, errNotFound
  240. }
  241. if len(matchingObjs) > 1 {
  242. return nil, fmt.Errorf("more than one object matched gvr %s, ns: %q name: %q", gvr, ns, name)
  243. }
  244. // Only one object should match in the tracker if it works
  245. // correctly, as Add/Update methods enforce kind/namespace/name
  246. // uniqueness.
  247. obj := matchingObjs[0].DeepCopyObject()
  248. if status, ok := obj.(*metav1.Status); ok {
  249. if status.Status != metav1.StatusSuccess {
  250. return nil, &errors.StatusError{ErrStatus: *status}
  251. }
  252. }
  253. return obj, nil
  254. }
  255. func (t *tracker) Add(obj runtime.Object) error {
  256. if meta.IsListType(obj) {
  257. return t.addList(obj, false)
  258. }
  259. objMeta, err := meta.Accessor(obj)
  260. if err != nil {
  261. return err
  262. }
  263. gvks, _, err := t.scheme.ObjectKinds(obj)
  264. if err != nil {
  265. return err
  266. }
  267. if len(gvks) == 0 {
  268. return fmt.Errorf("no registered kinds for %v", obj)
  269. }
  270. for _, gvk := range gvks {
  271. // NOTE: UnsafeGuessKindToResource is a heuristic and default match. The
  272. // actual registration in apiserver can specify arbitrary route for a
  273. // gvk. If a test uses such objects, it cannot preset the tracker with
  274. // objects via Add(). Instead, it should trigger the Create() function
  275. // of the tracker, where an arbitrary gvr can be specified.
  276. gvr, _ := meta.UnsafeGuessKindToResource(gvk)
  277. // Resource doesn't have the concept of "__internal" version, just set it to "".
  278. if gvr.Version == runtime.APIVersionInternal {
  279. gvr.Version = ""
  280. }
  281. err := t.add(gvr, obj, objMeta.GetNamespace(), false)
  282. if err != nil {
  283. return err
  284. }
  285. }
  286. return nil
  287. }
  288. func (t *tracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
  289. return t.add(gvr, obj, ns, false)
  290. }
  291. func (t *tracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
  292. return t.add(gvr, obj, ns, true)
  293. }
  294. func (t *tracker) getWatches(gvr schema.GroupVersionResource, ns string) []*watch.RaceFreeFakeWatcher {
  295. watches := []*watch.RaceFreeFakeWatcher{}
  296. if t.watchers[gvr] != nil {
  297. if w := t.watchers[gvr][ns]; w != nil {
  298. watches = append(watches, w...)
  299. }
  300. if ns != metav1.NamespaceAll {
  301. if w := t.watchers[gvr][metav1.NamespaceAll]; w != nil {
  302. watches = append(watches, w...)
  303. }
  304. }
  305. }
  306. return watches
  307. }
  308. func (t *tracker) add(gvr schema.GroupVersionResource, obj runtime.Object, ns string, replaceExisting bool) error {
  309. t.lock.Lock()
  310. defer t.lock.Unlock()
  311. gr := gvr.GroupResource()
  312. // To avoid the object from being accidentally modified by caller
  313. // after it's been added to the tracker, we always store the deep
  314. // copy.
  315. obj = obj.DeepCopyObject()
  316. newMeta, err := meta.Accessor(obj)
  317. if err != nil {
  318. return err
  319. }
  320. // Propagate namespace to the new object if hasn't already been set.
  321. if len(newMeta.GetNamespace()) == 0 {
  322. newMeta.SetNamespace(ns)
  323. }
  324. if ns != newMeta.GetNamespace() {
  325. msg := fmt.Sprintf("request namespace does not match object namespace, request: %q object: %q", ns, newMeta.GetNamespace())
  326. return errors.NewBadRequest(msg)
  327. }
  328. for i, existingObj := range t.objects[gvr] {
  329. oldMeta, err := meta.Accessor(existingObj)
  330. if err != nil {
  331. return err
  332. }
  333. if oldMeta.GetNamespace() == newMeta.GetNamespace() && oldMeta.GetName() == newMeta.GetName() {
  334. if replaceExisting {
  335. for _, w := range t.getWatches(gvr, ns) {
  336. w.Modify(obj)
  337. }
  338. t.objects[gvr][i] = obj
  339. return nil
  340. }
  341. return errors.NewAlreadyExists(gr, newMeta.GetName())
  342. }
  343. }
  344. if replaceExisting {
  345. // Tried to update but no matching object was found.
  346. return errors.NewNotFound(gr, newMeta.GetName())
  347. }
  348. t.objects[gvr] = append(t.objects[gvr], obj)
  349. for _, w := range t.getWatches(gvr, ns) {
  350. w.Add(obj)
  351. }
  352. return nil
  353. }
  354. func (t *tracker) addList(obj runtime.Object, replaceExisting bool) error {
  355. list, err := meta.ExtractList(obj)
  356. if err != nil {
  357. return err
  358. }
  359. errs := runtime.DecodeList(list, t.decoder)
  360. if len(errs) > 0 {
  361. return errs[0]
  362. }
  363. for _, obj := range list {
  364. if err := t.Add(obj); err != nil {
  365. return err
  366. }
  367. }
  368. return nil
  369. }
  370. func (t *tracker) Delete(gvr schema.GroupVersionResource, ns, name string) error {
  371. t.lock.Lock()
  372. defer t.lock.Unlock()
  373. found := false
  374. for i, existingObj := range t.objects[gvr] {
  375. objMeta, err := meta.Accessor(existingObj)
  376. if err != nil {
  377. return err
  378. }
  379. if objMeta.GetNamespace() == ns && objMeta.GetName() == name {
  380. obj := t.objects[gvr][i]
  381. t.objects[gvr] = append(t.objects[gvr][:i], t.objects[gvr][i+1:]...)
  382. for _, w := range t.getWatches(gvr, ns) {
  383. w.Delete(obj)
  384. }
  385. found = true
  386. break
  387. }
  388. }
  389. if found {
  390. return nil
  391. }
  392. return errors.NewNotFound(gvr.GroupResource(), name)
  393. }
  394. // filterByNamespaceAndName returns all objects in the collection that
  395. // match provided namespace and name. Empty namespace matches
  396. // non-namespaced objects.
  397. func filterByNamespaceAndName(objs []runtime.Object, ns, name string) ([]runtime.Object, error) {
  398. var res []runtime.Object
  399. for _, obj := range objs {
  400. acc, err := meta.Accessor(obj)
  401. if err != nil {
  402. return nil, err
  403. }
  404. if ns != "" && acc.GetNamespace() != ns {
  405. continue
  406. }
  407. if name != "" && acc.GetName() != name {
  408. continue
  409. }
  410. res = append(res, obj)
  411. }
  412. return res, nil
  413. }
  414. func DefaultWatchReactor(watchInterface watch.Interface, err error) WatchReactionFunc {
  415. return func(action Action) (bool, watch.Interface, error) {
  416. return true, watchInterface, err
  417. }
  418. }
  419. // SimpleReactor is a Reactor. Each reaction function is attached to a given verb,resource tuple. "*" in either field matches everything for that value.
  420. // For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions
  421. type SimpleReactor struct {
  422. Verb string
  423. Resource string
  424. Reaction ReactionFunc
  425. }
  426. func (r *SimpleReactor) Handles(action Action) bool {
  427. verbCovers := r.Verb == "*" || r.Verb == action.GetVerb()
  428. if !verbCovers {
  429. return false
  430. }
  431. resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
  432. if !resourceCovers {
  433. return false
  434. }
  435. return true
  436. }
  437. func (r *SimpleReactor) React(action Action) (bool, runtime.Object, error) {
  438. return r.Reaction(action)
  439. }
  440. // SimpleWatchReactor is a WatchReactor. Each reaction function is attached to a given resource. "*" matches everything for that value.
  441. // For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions
  442. type SimpleWatchReactor struct {
  443. Resource string
  444. Reaction WatchReactionFunc
  445. }
  446. func (r *SimpleWatchReactor) Handles(action Action) bool {
  447. resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
  448. if !resourceCovers {
  449. return false
  450. }
  451. return true
  452. }
  453. func (r *SimpleWatchReactor) React(action Action) (bool, watch.Interface, error) {
  454. return r.Reaction(action)
  455. }
  456. // SimpleProxyReactor is a ProxyReactor. Each reaction function is attached to a given resource. "*" matches everything for that value.
  457. // For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions.
  458. type SimpleProxyReactor struct {
  459. Resource string
  460. Reaction ProxyReactionFunc
  461. }
  462. func (r *SimpleProxyReactor) Handles(action Action) bool {
  463. resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
  464. if !resourceCovers {
  465. return false
  466. }
  467. return true
  468. }
  469. func (r *SimpleProxyReactor) React(action Action) (bool, restclient.ResponseWrapper, error) {
  470. return r.Reaction(action)
  471. }