fixture.go 16 KB

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