updated vendor
This commit is contained in:
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package testing provides a fake Kubernetes client suitable for use in unit
|
||||
// tests. The fake client simulates interactions with a kube-apiserver without
|
||||
// requiring one to be running, making tests fast and self-contained.
|
||||
//
|
||||
// # Scope and Limitations
|
||||
//
|
||||
// This fake client is intentionally simplified. It does not, and will never,
|
||||
// fully replicate the behavior of a real kube-apiserver. Many server-side
|
||||
// behaviors such as field defaulting, validation, status management,
|
||||
// strategic merge patch, server-side apply semantics, and advanced
|
||||
// field selectors are not supported by this client and there are no plans
|
||||
// to add them.
|
||||
//
|
||||
// This is by design. Maintaining a high-fidelity mock of the entire
|
||||
// kube-apiserver API surface would introduce significant complexity that is
|
||||
// difficult to justify for a test utility.
|
||||
//
|
||||
// # When to Use This Package
|
||||
//
|
||||
// The fake client works well for unit tests that need to verify how your code
|
||||
// interacts with the Kubernetes API at a structural level, for example:
|
||||
//
|
||||
// - Verifying that the correct API calls are made.
|
||||
// - Supplying canned responses to drive specific code paths.
|
||||
//
|
||||
// # When Not to Use This Package
|
||||
//
|
||||
// If your tests depend on the kube-apiserver behaving correctly (e.g.,
|
||||
// enforcing validation, persisting resources accurately, handling apply
|
||||
// semantics, or producing realistic watch events), you should write
|
||||
// integration tests against a real kube-apiserver instead.
|
||||
//
|
||||
// # Contributing
|
||||
//
|
||||
// Issues requesting that the fake client more closely match kube-apiserver
|
||||
// behavior should be limiting to bugs in how the fake behaves for unit test
|
||||
// scenarios it is clearly intended to support. Pull requests that improve the fake
|
||||
// client will only be accepted when they meet all of the following criteria:
|
||||
//
|
||||
// - The change makes the fake client easier to use for common unit testing
|
||||
// patterns.
|
||||
// - The change does not introduce significant complexity to the fake client.
|
||||
// - The use cases motivating the change are clearly better served by a fake
|
||||
// client than by integration tests against a real kube-apiserver.
|
||||
//
|
||||
// We hold a high bar for these changes. If the test scenarios in question can
|
||||
// be reasonably addressed through integration testing, we will prefer that
|
||||
// path over expanding the fake client.
|
||||
//
|
||||
// We understand this stance may be inconvenient, and we appreciate your
|
||||
// understanding. Our goal is to keep this package simple, maintainable, and
|
||||
// honest about what it provides so that it remains a reliable tool for the
|
||||
// cases it is designed to handle.
|
||||
package testing
|
||||
+108
-14
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -288,13 +289,46 @@ type tracker struct {
|
||||
scheme ObjectScheme
|
||||
decoder runtime.Decoder
|
||||
lock sync.RWMutex
|
||||
objects map[schema.GroupVersionResource]map[types.NamespacedName]runtime.Object
|
||||
objects map[schema.GroupVersionResource]map[types.NamespacedName]versionedObject
|
||||
// The value type of watchers is a map of which the key is either a namespace or
|
||||
// all/non namespace aka "" and its value is list of fake watchers.
|
||||
// Manipulations on resources will broadcast the notification events into the
|
||||
// watchers' channel. Note that too many unhandled events (currently 100,
|
||||
// see apimachinery/pkg/watch.DefaultChanSize) will cause a panic.
|
||||
watchers map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher
|
||||
// resourceVersions is the highest resource version of any tracked object with
|
||||
// a certain gvr. Conceptually it starts at 1 when no objects are stored (0 is
|
||||
// special in queries) but the map contains no entries in that case.
|
||||
// The resource version for that set of objects gets bumped before
|
||||
// storing a new or modified object.
|
||||
//
|
||||
// Object content does not get changed to preserve the traditional behavior
|
||||
// (hence also the versionedObject type instead of storing a runtime.Object
|
||||
// with modified ResourceVersion).
|
||||
//
|
||||
// Resource version support (https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions)
|
||||
// is very limited. It only supports one particular use case:
|
||||
// List (no resource version check, returned ListMeta has ResourceVersion set) +
|
||||
// Watch (Exact match for the ResourceVersion returned by List).
|
||||
//
|
||||
// This is sufficient for Reflector.ListAndWatch (https://github.com/kubernetes/kubernetes/blob/b53b9fb5573323484af9a19cf3f5bfe80760abba/staging/src/k8s.io/client-go/tools/cache/reflector.go#L401)
|
||||
// when setting up informers in an informer factory.
|
||||
//
|
||||
// Strictly speaking, this should be by GroupVersion. But objects are
|
||||
// also tracked by GroupVersionResource instead of GroupVersion, so the
|
||||
// same is done here to match how List is implemented.
|
||||
resourceVersions map[schema.GroupVersionResource]int64
|
||||
}
|
||||
|
||||
// versionedObject stores an object together with the resource version that was
|
||||
// assigned to it by the tracker. The version could be stored inline in the object,
|
||||
// but this is not how fake client-go has traditionally worked and starting to do
|
||||
// that now might break tests.
|
||||
type versionedObject struct {
|
||||
// resourceVersion is always > 1 for a stored object because 1
|
||||
// is the initial value for an empty set of objects.
|
||||
resourceVersion int64
|
||||
runtime.Object
|
||||
}
|
||||
|
||||
var _ ObjectTracker = &tracker{}
|
||||
@@ -303,10 +337,11 @@ var _ ObjectTracker = &tracker{}
|
||||
// of objects for the fake clientset. Mostly useful for unit tests.
|
||||
func NewObjectTracker(scheme ObjectScheme, decoder runtime.Decoder) ObjectTracker {
|
||||
return &tracker{
|
||||
scheme: scheme,
|
||||
decoder: decoder,
|
||||
objects: make(map[schema.GroupVersionResource]map[types.NamespacedName]runtime.Object),
|
||||
watchers: make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher),
|
||||
scheme: scheme,
|
||||
decoder: decoder,
|
||||
objects: make(map[schema.GroupVersionResource]map[types.NamespacedName]versionedObject),
|
||||
watchers: make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher),
|
||||
resourceVersions: make(map[schema.GroupVersionResource]int64),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,15 +373,27 @@ func (t *tracker) List(gvr schema.GroupVersionResource, gvk schema.GroupVersionK
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
if listMeta, err := meta.ListAccessor(list); err == nil {
|
||||
resourceVersion, ok := t.resourceVersions[gvr]
|
||||
if !ok {
|
||||
resourceVersion = 1
|
||||
}
|
||||
listMeta.SetResourceVersion(fmt.Sprintf("%d", resourceVersion))
|
||||
}
|
||||
|
||||
objs, ok := t.objects[gvr]
|
||||
if !ok {
|
||||
return list, nil
|
||||
}
|
||||
|
||||
matchingObjs, err := filterByNamespace(objs, ns)
|
||||
matchingVersionedObjs, err := filterByNamespace(objs, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchingObjs := make([]runtime.Object, len(matchingVersionedObjs))
|
||||
for i, obj := range matchingVersionedObjs {
|
||||
matchingObjs[i] = obj.Object
|
||||
}
|
||||
if err := meta.SetList(list, matchingObjs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -359,6 +406,27 @@ func (t *tracker) Watch(gvr schema.GroupVersionResource, ns string, opts ...meta
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// By default, emulate the traditional behavior of the tracker and don't deliver
|
||||
// *any* existing objects unless list options are provided.
|
||||
addExisting := false
|
||||
addFromRV := int64(0)
|
||||
if len(opts) > 0 {
|
||||
// Providing options, as the generated client-go fake does, enables support
|
||||
// for existing objects depending on the resource version.
|
||||
//
|
||||
// The default if ResourceVersion is empty is "start at most recent",
|
||||
// which includes delivering all existing objects. addFromRV == 0
|
||||
// matches all objects below because all stored objects have addFromRV > 0.
|
||||
addExisting = true
|
||||
if opts[0].ResourceVersion != "" {
|
||||
rv, err := strconv.ParseInt(opts[0].ResourceVersion, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ResourceVersion %q in ListOptions, must be int64: %w", opts[0].ResourceVersion, err)
|
||||
}
|
||||
addFromRV = rv
|
||||
}
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
@@ -368,6 +436,22 @@ func (t *tracker) Watch(gvr schema.GroupVersionResource, ns string, opts ...meta
|
||||
t.watchers[gvr] = make(map[string][]*watch.RaceFreeFakeWatcher)
|
||||
}
|
||||
t.watchers[gvr][ns] = append(t.watchers[gvr][ns], fakewatcher)
|
||||
|
||||
// Deliver all objects that match the list options, for example
|
||||
// between the initial List and the following Watch.
|
||||
if addExisting {
|
||||
objs := t.objects[gvr]
|
||||
matchingObjs, err := filterByNamespace(objs, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, obj := range matchingObjs {
|
||||
if addFromRV < obj.resourceVersion {
|
||||
fakewatcher.Add(obj.Object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fakewatcher, nil
|
||||
}
|
||||
|
||||
@@ -565,17 +649,26 @@ func (t *tracker) add(gvr schema.GroupVersionResource, obj runtime.Object, ns st
|
||||
|
||||
_, ok := t.objects[gvr]
|
||||
if !ok {
|
||||
t.objects[gvr] = make(map[types.NamespacedName]runtime.Object)
|
||||
t.objects[gvr] = make(map[types.NamespacedName]versionedObject)
|
||||
}
|
||||
|
||||
// Determine resource version for the new or updated object.
|
||||
resourceVersion, ok := t.resourceVersions[gvr]
|
||||
if !ok {
|
||||
resourceVersion = 1
|
||||
}
|
||||
resourceVersion++
|
||||
|
||||
namespacedName := types.NamespacedName{Namespace: newMeta.GetNamespace(), Name: newMeta.GetName()}
|
||||
if _, ok = t.objects[gvr][namespacedName]; ok {
|
||||
if replaceExisting {
|
||||
t.resourceVersions[gvr] = resourceVersion
|
||||
t.objects[gvr][namespacedName] = versionedObject{resourceVersion, obj}
|
||||
|
||||
for _, w := range t.getWatches(gvr, ns) {
|
||||
// To avoid the object from being accidentally modified by watcher
|
||||
w.Modify(obj.DeepCopyObject())
|
||||
}
|
||||
t.objects[gvr][namespacedName] = obj
|
||||
return nil
|
||||
}
|
||||
return apierrors.NewAlreadyExists(gr, newMeta.GetName())
|
||||
@@ -586,7 +679,8 @@ func (t *tracker) add(gvr schema.GroupVersionResource, obj runtime.Object, ns st
|
||||
return apierrors.NewNotFound(gr, newMeta.GetName())
|
||||
}
|
||||
|
||||
t.objects[gvr][namespacedName] = obj
|
||||
t.resourceVersions[gvr] = resourceVersion
|
||||
t.objects[gvr][namespacedName] = versionedObject{resourceVersion, obj}
|
||||
|
||||
for _, w := range t.getWatches(gvr, ns) {
|
||||
// To avoid the object from being accidentally modified by watcher
|
||||
@@ -841,11 +935,11 @@ func (d *objectDefaulter) Default(_ runtime.Object) {}
|
||||
// filterByNamespace returns all objects in the collection that
|
||||
// match provided namespace. Empty namespace matches
|
||||
// non-namespaced objects.
|
||||
func filterByNamespace(objs map[types.NamespacedName]runtime.Object, ns string) ([]runtime.Object, error) {
|
||||
var res []runtime.Object
|
||||
func filterByNamespace(objs map[types.NamespacedName]versionedObject, ns string) ([]versionedObject, error) {
|
||||
var res []versionedObject
|
||||
|
||||
for _, obj := range objs {
|
||||
acc, err := meta.Accessor(obj)
|
||||
acc, err := meta.Accessor(obj.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -857,8 +951,8 @@ func filterByNamespace(objs map[types.NamespacedName]runtime.Object, ns string)
|
||||
|
||||
// Sort res to get deterministic order.
|
||||
sort.Slice(res, func(i, j int) bool {
|
||||
acc1, _ := meta.Accessor(res[i])
|
||||
acc2, _ := meta.Accessor(res[j])
|
||||
acc1, _ := meta.Accessor(res[i].Object)
|
||||
acc2, _ := meta.Accessor(res[j].Object)
|
||||
if acc1.GetNamespace() != acc2.GetNamespace() {
|
||||
return acc1.GetNamespace() < acc2.GetNamespace()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user