2 Copyright 2015 The Kubernetes Authors.
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
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"
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
44 // Get retrieves the object by its kind, namespace and name.
45 Get(gvr schema.GroupVersionResource, ns, name string) (runtime.Object, error)
47 // Create adds an object to the tracker in the specified namespace.
48 Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error
50 // Update updates an existing object in the tracker in the specified namespace.
51 Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error
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)
57 // Delete deletes an existing object from the tracker. If object
58 // didn't exist in the tracker prior to deletion, Delete returns
60 Delete(gvr schema.GroupVersionResource, ns, name string) error
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)
67 // ObjectScheme abstracts the implementation of common operations on objects.
68 type ObjectScheme interface {
73 // ObjectReaction returns a ReactionFunc that applies core.Action to
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) {
86 obj, err := tracker.List(gvr, action.GetKind(), ns)
90 obj, err := tracker.Get(gvr, ns, action.GetName())
93 case CreateActionImpl:
94 objMeta, err := meta.Accessor(action.GetObject())
98 if action.GetSubresource() == "" {
99 err = tracker.Create(gvr, action.GetObject(), ns)
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)
107 return true, nil, err
109 obj, err := tracker.Get(gvr, ns, objMeta.GetName())
110 return true, obj, err
112 case UpdateActionImpl:
113 objMeta, err := meta.Accessor(action.GetObject())
115 return true, nil, err
117 err = tracker.Update(gvr, action.GetObject(), ns)
119 return true, nil, err
121 obj, err := tracker.Get(gvr, ns, objMeta.GetName())
122 return true, obj, err
124 case DeleteActionImpl:
125 err := tracker.Delete(gvr, ns, action.GetName())
127 return true, nil, err
129 return true, nil, nil
131 case PatchActionImpl:
132 obj, err := tracker.Get(gvr, ns, action.GetName())
134 // object is not registered
135 return false, nil, err
138 old, err := json.Marshal(obj)
140 return true, nil, err
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())
147 return true, nil, err
149 modified, err := patch.Apply(old)
151 return true, nil, err
153 if err = json.Unmarshal(modified, obj); err != nil {
154 return true, nil, err
156 case types.StrategicMergePatchType:
157 mergedByte, err := strategicpatch.StrategicMergePatch(old, action.GetPatch(), obj)
159 return true, nil, err
161 if err = json.Unmarshal(mergedByte, obj); err != nil {
162 return true, nil, err
165 return true, nil, fmt.Errorf("PatchType is not supported")
168 if err = tracker.Update(gvr, obj, ns); err != nil {
169 return true, nil, err
172 return true, obj, nil
175 return false, nil, fmt.Errorf("no reaction implemented for %s", action)
180 type tracker struct {
182 decoder runtime.Decoder
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
193 var _ ObjectTracker = &tracker{}
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 {
201 objects: make(map[schema.GroupVersionResource][]runtime.Object),
202 watchers: make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher),
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.
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
218 list, err := t.scheme.New(listGVK)
223 if !meta.IsListType(list) {
224 return nil, fmt.Errorf("%q is not a list type", listGVK.Kind)
228 defer t.lock.RUnlock()
230 objs, ok := t.objects[gvr]
235 matchingObjs, err := filterByNamespaceAndName(objs, ns, "")
239 if err := meta.SetList(list, matchingObjs); err != nil {
242 return list.DeepCopyObject(), nil
245 func (t *tracker) Watch(gvr schema.GroupVersionResource, ns string) (watch.Interface, error) {
247 defer t.lock.Unlock()
249 fakewatcher := watch.NewRaceFreeFake()
251 if _, exists := t.watchers[gvr]; !exists {
252 t.watchers[gvr] = make(map[string][]*watch.RaceFreeFakeWatcher)
254 t.watchers[gvr][ns] = append(t.watchers[gvr][ns], fakewatcher)
255 return fakewatcher, nil
258 func (t *tracker) Get(gvr schema.GroupVersionResource, ns, name string) (runtime.Object, error) {
259 errNotFound := errors.NewNotFound(gvr.GroupResource(), name)
262 defer t.lock.RUnlock()
264 objs, ok := t.objects[gvr]
266 return nil, errNotFound
269 matchingObjs, err := filterByNamespaceAndName(objs, ns, name)
273 if len(matchingObjs) == 0 {
274 return nil, errNotFound
276 if len(matchingObjs) > 1 {
277 return nil, fmt.Errorf("more than one object matched gvr %s, ns: %q name: %q", gvr, ns, name)
280 // Only one object should match in the tracker if it works
281 // correctly, as Add/Update methods enforce kind/namespace/name
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}
293 func (t *tracker) Add(obj runtime.Object) error {
294 if meta.IsListType(obj) {
295 return t.addList(obj, false)
297 objMeta, err := meta.Accessor(obj)
301 gvks, _, err := t.scheme.ObjectKinds(obj)
306 return fmt.Errorf("no registered kinds for %v", obj)
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 {
320 err := t.add(gvr, obj, objMeta.GetNamespace(), false)
328 func (t *tracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
329 return t.add(gvr, obj, ns, false)
332 func (t *tracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
333 return t.add(gvr, obj, ns, true)
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...)
342 if w := t.watchers[gvr][""]; w != nil {
343 watches = append(watches, w...)
349 func (t *tracker) add(gvr schema.GroupVersionResource, obj runtime.Object, ns string, replaceExisting bool) error {
351 defer t.lock.Unlock()
353 gr := gvr.GroupResource()
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
358 obj = obj.DeepCopyObject()
360 newMeta, err := meta.Accessor(obj)
365 // Propagate namespace to the new object if hasn't already been set.
366 if len(newMeta.GetNamespace()) == 0 {
367 newMeta.SetNamespace(ns)
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)
375 for i, existingObj := range t.objects[gvr] {
376 oldMeta, err := meta.Accessor(existingObj)
380 if oldMeta.GetNamespace() == newMeta.GetNamespace() && oldMeta.GetName() == newMeta.GetName() {
382 for _, w := range t.getWatches(gvr, ns) {
385 t.objects[gvr][i] = obj
388 return errors.NewAlreadyExists(gr, newMeta.GetName())
393 // Tried to update but no matching object was found.
394 return errors.NewNotFound(gr, newMeta.GetName())
397 t.objects[gvr] = append(t.objects[gvr], obj)
399 for _, w := range t.getWatches(gvr, ns) {
406 func (t *tracker) addList(obj runtime.Object, replaceExisting bool) error {
407 list, err := meta.ExtractList(obj)
411 errs := runtime.DecodeList(list, t.decoder)
415 for _, obj := range list {
416 if err := t.Add(obj); err != nil {
423 func (t *tracker) Delete(gvr schema.GroupVersionResource, ns, name string) error {
425 defer t.lock.Unlock()
429 for i, existingObj := range t.objects[gvr] {
430 objMeta, err := meta.Accessor(existingObj)
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) {
449 return errors.NewNotFound(gvr.GroupResource(), name)
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
458 for _, obj := range objs {
459 acc, err := meta.Accessor(obj)
463 if ns != "" && acc.GetNamespace() != ns {
466 if name != "" && acc.GetName() != name {
469 res = append(res, obj)
475 func DefaultWatchReactor(watchInterface watch.Interface, err error) WatchReactionFunc {
476 return func(action Action) (bool, watch.Interface, error) {
477 return true, watchInterface, err
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 {
487 Reaction ReactionFunc
490 func (r *SimpleReactor) Handles(action Action) bool {
491 verbCovers := r.Verb == "*" || r.Verb == action.GetVerb()
495 resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
503 func (r *SimpleReactor) React(action Action) (bool, runtime.Object, error) {
504 return r.Reaction(action)
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 {
512 Reaction WatchReactionFunc
515 func (r *SimpleWatchReactor) Handles(action Action) bool {
516 resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
524 func (r *SimpleWatchReactor) React(action Action) (bool, watch.Interface, error) {
525 return r.Reaction(action)
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 {
533 Reaction ProxyReactionFunc
536 func (r *SimpleProxyReactor) Handles(action Action) bool {
537 resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
545 func (r *SimpleProxyReactor) React(action Action) (bool, restclient.ResponseWrapper, error) {
546 return r.Reaction(action)