fixture.go 16 KB

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