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