Bumping k8s dependencies to 1.13

This commit is contained in:
Cheng Xing
2018-11-16 14:08:25 -08:00
parent 305407125c
commit b4c0b68ec7
8002 changed files with 884099 additions and 276228 deletions

View File

@@ -0,0 +1,155 @@
/*
Copyright 2018 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 dynamicinformer
import (
"sync"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/dynamiclister"
"k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache"
)
// NewDynamicSharedInformerFactory constructs a new instance of dynamicSharedInformerFactory for all namespaces.
func NewDynamicSharedInformerFactory(client dynamic.Interface, defaultResync time.Duration) DynamicSharedInformerFactory {
return NewFilteredDynamicSharedInformerFactory(client, defaultResync, metav1.NamespaceAll, nil)
}
// NewFilteredDynamicSharedInformerFactory constructs a new instance of dynamicSharedInformerFactory.
// Listers obtained via this factory will be subject to the same filters as specified here.
func NewFilteredDynamicSharedInformerFactory(client dynamic.Interface, defaultResync time.Duration, namespace string, tweakListOptions TweakListOptionsFunc) DynamicSharedInformerFactory {
return &dynamicSharedInformerFactory{
client: client,
defaultResync: defaultResync,
namespace: metav1.NamespaceAll,
informers: map[schema.GroupVersionResource]informers.GenericInformer{},
startedInformers: make(map[schema.GroupVersionResource]bool),
}
}
type dynamicSharedInformerFactory struct {
client dynamic.Interface
defaultResync time.Duration
namespace string
lock sync.Mutex
informers map[schema.GroupVersionResource]informers.GenericInformer
// startedInformers is used for tracking which informers have been started.
// This allows Start() to be called multiple times safely.
startedInformers map[schema.GroupVersionResource]bool
}
var _ DynamicSharedInformerFactory = &dynamicSharedInformerFactory{}
func (f *dynamicSharedInformerFactory) ForResource(gvr schema.GroupVersionResource) informers.GenericInformer {
f.lock.Lock()
defer f.lock.Unlock()
key := gvr
informer, exists := f.informers[key]
if exists {
return informer
}
informer = NewFilteredDynamicInformer(f.client, gvr, f.namespace, f.defaultResync, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, nil)
f.informers[key] = informer
return informer
}
// Start initializes all requested informers.
func (f *dynamicSharedInformerFactory) Start(stopCh <-chan struct{}) {
f.lock.Lock()
defer f.lock.Unlock()
for informerType, informer := range f.informers {
if !f.startedInformers[informerType] {
go informer.Informer().Run(stopCh)
f.startedInformers[informerType] = true
}
}
}
// WaitForCacheSync waits for all started informers' cache were synced.
func (f *dynamicSharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool {
informers := func() map[schema.GroupVersionResource]cache.SharedIndexInformer {
f.lock.Lock()
defer f.lock.Unlock()
informers := map[schema.GroupVersionResource]cache.SharedIndexInformer{}
for informerType, informer := range f.informers {
if f.startedInformers[informerType] {
informers[informerType] = informer.Informer()
}
}
return informers
}()
res := map[schema.GroupVersionResource]bool{}
for informType, informer := range informers {
res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)
}
return res
}
// NewFilteredDynamicInformer constructs a new informer for a dynamic type.
func NewFilteredDynamicInformer(client dynamic.Interface, gvr schema.GroupVersionResource, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions TweakListOptionsFunc) informers.GenericInformer {
return &dynamicInformer{
gvr: gvr,
informer: cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.Resource(gvr).Namespace(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.Resource(gvr).Namespace(namespace).Watch(options)
},
},
&unstructured.Unstructured{},
resyncPeriod,
indexers,
),
}
}
type dynamicInformer struct {
informer cache.SharedIndexInformer
gvr schema.GroupVersionResource
}
var _ informers.GenericInformer = &dynamicInformer{}
func (d *dynamicInformer) Informer() cache.SharedIndexInformer {
return d.informer
}
func (d *dynamicInformer) Lister() cache.GenericLister {
return dynamiclister.NewRuntimeObjectShim(dynamiclister.New(d.informer.GetIndexer(), d.gvr))
}

View File

@@ -0,0 +1,160 @@
/*
Copyright 2018 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 dynamicinformer_test
import (
"context"
"testing"
"time"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/tools/cache"
)
func TestDynamicSharedInformerFactory(t *testing.T) {
scenarios := []struct {
name string
existingObj *unstructured.Unstructured
gvr schema.GroupVersionResource
ns string
trigger func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured
handler func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs
}{
// scenario 1
{
name: "scenario 1: test if adding an object triggers AddFunc",
ns: "ns-foo",
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, _ *unstructured.Unstructured) *unstructured.Unstructured {
testObject := newUnstructured("extensions/v1beta1", "Deployment", "ns-foo", "name-foo")
createdObj, err := fakeClient.Resource(gvr).Namespace(ns).Create(testObject, metav1.CreateOptions{})
if err != nil {
t.Error(err)
}
return createdObj
},
handler: func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs {
return &cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
rcvCh <- obj.(*unstructured.Unstructured)
},
}
},
},
// scenario 2
{
name: "scenario 2: tests if updating an object triggers UpdateFunc",
ns: "ns-foo",
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
existingObj: newUnstructured("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"),
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured {
testObject.Object["spec"] = "updatedName"
updatedObj, err := fakeClient.Resource(gvr).Namespace(ns).Update(testObject, metav1.UpdateOptions{})
if err != nil {
t.Error(err)
}
return updatedObj
},
handler: func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs {
return &cache.ResourceEventHandlerFuncs{
UpdateFunc: func(old, updated interface{}) {
rcvCh <- updated.(*unstructured.Unstructured)
},
}
},
},
// scenario 3
{
name: "scenario 3: test if deleting an object triggers DeleteFunc",
ns: "ns-foo",
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
existingObj: newUnstructured("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"),
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured {
err := fakeClient.Resource(gvr).Namespace(ns).Delete(testObject.GetName(), &metav1.DeleteOptions{})
if err != nil {
t.Error(err)
}
return testObject
},
handler: func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs {
return &cache.ResourceEventHandlerFuncs{
DeleteFunc: func(obj interface{}) {
rcvCh <- obj.(*unstructured.Unstructured)
},
}
},
},
}
for _, ts := range scenarios {
t.Run(ts.name, func(t *testing.T) {
// test data
timeout := time.Duration(3 * time.Second)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
scheme := runtime.NewScheme()
informerReciveObjectCh := make(chan *unstructured.Unstructured, 1)
objs := []runtime.Object{}
if ts.existingObj != nil {
objs = append(objs, ts.existingObj)
}
fakeClient := fake.NewSimpleDynamicClient(scheme, objs...)
target := dynamicinformer.NewDynamicSharedInformerFactory(fakeClient, 0)
// act
informerListerForGvr := target.ForResource(ts.gvr)
informerListerForGvr.Informer().AddEventHandler(ts.handler(informerReciveObjectCh))
target.Start(ctx.Done())
if synced := target.WaitForCacheSync(ctx.Done()); !synced[ts.gvr] {
t.Errorf("informer for %s hasn't synced", ts.gvr)
}
testObject := ts.trigger(ts.gvr, ts.ns, fakeClient, ts.existingObj)
select {
case objFromInformer := <-informerReciveObjectCh:
if !equality.Semantic.DeepEqual(testObject, objFromInformer) {
t.Fatalf("%v", diff.ObjectDiff(testObject, objFromInformer))
}
case <-ctx.Done():
t.Errorf("tested informer haven't received an object, waited %v", timeout)
}
})
}
}
func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": apiVersion,
"kind": kind,
"metadata": map[string]interface{}{
"namespace": namespace,
"name": name,
},
"spec": name,
},
}
}

View File

@@ -0,0 +1,34 @@
/*
Copyright 2018 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 dynamicinformer
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/informers"
)
// DynamicSharedInformerFactory provides access to a shared informer and lister for dynamic client
type DynamicSharedInformerFactory interface {
Start(stopCh <-chan struct{})
ForResource(gvr schema.GroupVersionResource) informers.GenericInformer
WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool
}
// TweakListOptionsFunc defines the signature of a helper function
// that wants to provide more listing options to API
type TweakListOptionsFunc func(*metav1.ListOptions)

View File

@@ -0,0 +1,40 @@
/*
Copyright 2018 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 dynamiclister
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
)
// Lister helps list resources.
type Lister interface {
// List lists all resources in the indexer.
List(selector labels.Selector) (ret []*unstructured.Unstructured, err error)
// Get retrieves a resource from the indexer with the given name
Get(name string) (*unstructured.Unstructured, error)
// Namespace returns an object that can list and get resources in a given namespace.
Namespace(namespace string) NamespaceLister
}
// NamespaceLister helps list and get resources.
type NamespaceLister interface {
// List lists all resources in the indexer for a given namespace.
List(selector labels.Selector) (ret []*unstructured.Unstructured, err error)
// Get retrieves a resource from the indexer for a given namespace and name.
Get(name string) (*unstructured.Unstructured, error)
}

View File

@@ -0,0 +1,91 @@
/*
Copyright 2018 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 dynamiclister
import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
)
var _ Lister = &dynamicLister{}
var _ NamespaceLister = &dynamicNamespaceLister{}
// dynamicLister implements the Lister interface.
type dynamicLister struct {
indexer cache.Indexer
gvr schema.GroupVersionResource
}
// New returns a new Lister.
func New(indexer cache.Indexer, gvr schema.GroupVersionResource) Lister {
return &dynamicLister{indexer: indexer, gvr: gvr}
}
// List lists all resources in the indexer.
func (l *dynamicLister) List(selector labels.Selector) (ret []*unstructured.Unstructured, err error) {
err = cache.ListAll(l.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*unstructured.Unstructured))
})
return ret, err
}
// Get retrieves a resource from the indexer with the given name
func (l *dynamicLister) Get(name string) (*unstructured.Unstructured, error) {
obj, exists, err := l.indexer.GetByKey(name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(l.gvr.GroupResource(), name)
}
return obj.(*unstructured.Unstructured), nil
}
// Namespace returns an object that can list and get resources from a given namespace.
func (l *dynamicLister) Namespace(namespace string) NamespaceLister {
return &dynamicNamespaceLister{indexer: l.indexer, namespace: namespace, gvr: l.gvr}
}
// dynamicNamespaceLister implements the NamespaceLister interface.
type dynamicNamespaceLister struct {
indexer cache.Indexer
namespace string
gvr schema.GroupVersionResource
}
// List lists all resources in the indexer for a given namespace.
func (l *dynamicNamespaceLister) List(selector labels.Selector) (ret []*unstructured.Unstructured, err error) {
err = cache.ListAllByNamespace(l.indexer, l.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*unstructured.Unstructured))
})
return ret, err
}
// Get retrieves a resource from the indexer for a given namespace and name.
func (l *dynamicNamespaceLister) Get(name string) (*unstructured.Unstructured, error) {
obj, exists, err := l.indexer.GetByKey(l.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(l.gvr.GroupResource(), name)
}
return obj.(*unstructured.Unstructured), nil
}

View File

@@ -0,0 +1,257 @@
/*
Copyright 2018 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 dynamiclister_test
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/client-go/dynamic/dynamiclister"
"k8s.io/client-go/tools/cache"
)
func TestNamespaceGetMethod(t *testing.T) {
tests := []struct {
name string
existingObjects []runtime.Object
namespaceToSync string
gvrToSync schema.GroupVersionResource
objectToGet string
expectedObject *unstructured.Unstructured
expectError bool
}{
{
name: "scenario 1: gets name-foo1 resource from the indexer from ns-foo namespace",
existingObjects: []runtime.Object{
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"),
},
namespaceToSync: "ns-foo",
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
objectToGet: "name-foo1",
expectedObject: newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
},
{
name: "scenario 2: gets name-foo-non-existing resource from the indexer from ns-foo namespace",
existingObjects: []runtime.Object{
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"),
},
namespaceToSync: "ns-foo",
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
objectToGet: "name-foo-non-existing",
expectError: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// test data
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
for _, obj := range test.existingObjects {
err := indexer.Add(obj)
if err != nil {
t.Fatal(err)
}
}
// act
target := dynamiclister.New(indexer, test.gvrToSync).Namespace(test.namespaceToSync)
actualObject, err := target.Get(test.objectToGet)
// validate
if test.expectError {
if err == nil {
t.Fatal("expected to get an error but non was returned")
}
return
}
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(test.expectedObject, actualObject) {
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, diff.ObjectDiff(test.expectedObject, actualObject))
}
})
}
}
func TestNamespaceListMethod(t *testing.T) {
// test data
objs := []runtime.Object{
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"),
}
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
for _, obj := range objs {
err := indexer.Add(obj)
if err != nil {
t.Fatal(err)
}
}
expectedOutput := []*unstructured.Unstructured{
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
}
namespaceToList := "ns-foo"
// act
target := dynamiclister.New(indexer, schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"}).Namespace(namespaceToList)
actualOutput, err := target.List(labels.Everything())
// validate
if err != nil {
t.Fatal(err)
}
assertListOrDie(expectedOutput, actualOutput, t)
}
func TestListerGetMethod(t *testing.T) {
tests := []struct {
name string
existingObjects []runtime.Object
namespaceToSync string
gvrToSync schema.GroupVersionResource
objectToGet string
expectedObject *unstructured.Unstructured
expectError bool
}{
{
name: "scenario 1: gets name-foo1 resource from the indexer",
existingObjects: []runtime.Object{
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group/version", "TheKind", "", "name-foo1"),
newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"),
},
namespaceToSync: "",
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
objectToGet: "name-foo1",
expectedObject: newUnstructured("group/version", "TheKind", "", "name-foo1"),
},
{
name: "scenario 2: doesn't get name-foo resource from the indexer from ns-foo namespace",
existingObjects: []runtime.Object{
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"),
},
namespaceToSync: "ns-foo",
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
objectToGet: "name-foo",
expectError: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// test data
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
for _, obj := range test.existingObjects {
err := indexer.Add(obj)
if err != nil {
t.Fatal(err)
}
}
// act
target := dynamiclister.New(indexer, test.gvrToSync)
actualObject, err := target.Get(test.objectToGet)
// validate
if test.expectError {
if err == nil {
t.Fatal("expected to get an error but non was returned")
}
return
}
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(test.expectedObject, actualObject) {
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, diff.ObjectDiff(test.expectedObject, actualObject))
}
})
}
}
func TestListerListMethod(t *testing.T) {
// test data
objs := []runtime.Object{
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
}
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
for _, obj := range objs {
err := indexer.Add(obj)
if err != nil {
t.Fatal(err)
}
}
expectedOutput := []*unstructured.Unstructured{
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
}
// act
target := dynamiclister.New(indexer, schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"})
actualOutput, err := target.List(labels.Everything())
// validate
if err != nil {
t.Fatal(err)
}
assertListOrDie(expectedOutput, actualOutput, t)
}
func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": apiVersion,
"kind": kind,
"metadata": map[string]interface{}{
"namespace": namespace,
"name": name,
},
},
}
}
func assertListOrDie(expected, actual []*unstructured.Unstructured, t *testing.T) {
if len(actual) != len(expected) {
t.Fatalf("unexpected number of items returned, expected = %d, actual = %d", len(expected), len(actual))
}
for _, expectedObject := range expected {
found := false
for _, actualObject := range actual {
if actualObject.GetName() == expectedObject.GetName() {
if !reflect.DeepEqual(expectedObject, actualObject) {
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", expectedObject, actualObject, diff.ObjectDiff(expectedObject, actualObject))
}
found = true
}
}
if !found {
t.Fatalf("the resource with the name = %s was not found in the returned output", expectedObject.GetName())
}
}
}

87
vendor/k8s.io/client-go/dynamic/dynamiclister/shim.go generated vendored Normal file
View File

@@ -0,0 +1,87 @@
/*
Copyright 2018 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 dynamiclister
import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/cache"
)
var _ cache.GenericLister = &dynamicListerShim{}
var _ cache.GenericNamespaceLister = &dynamicNamespaceListerShim{}
// dynamicListerShim implements the cache.GenericLister interface.
type dynamicListerShim struct {
lister Lister
}
// NewRuntimeObjectShim returns a new shim for Lister.
// It wraps Lister so that it implements cache.GenericLister interface
func NewRuntimeObjectShim(lister Lister) cache.GenericLister {
return &dynamicListerShim{lister: lister}
}
// List will return all objects across namespaces
func (s *dynamicListerShim) List(selector labels.Selector) (ret []runtime.Object, err error) {
objs, err := s.lister.List(selector)
if err != nil {
return nil, err
}
ret = make([]runtime.Object, len(objs))
for index, obj := range objs {
ret[index] = obj
}
return ret, err
}
// Get will attempt to retrieve assuming that name==key
func (s *dynamicListerShim) Get(name string) (runtime.Object, error) {
return s.lister.Get(name)
}
func (s *dynamicListerShim) ByNamespace(namespace string) cache.GenericNamespaceLister {
return &dynamicNamespaceListerShim{
namespaceLister: s.lister.Namespace(namespace),
}
}
// dynamicNamespaceListerShim implements the NamespaceLister interface.
// It wraps NamespaceLister so that it implements cache.GenericNamespaceLister interface
type dynamicNamespaceListerShim struct {
namespaceLister NamespaceLister
}
// List will return all objects in this namespace
func (ns *dynamicNamespaceListerShim) List(selector labels.Selector) (ret []runtime.Object, err error) {
objs, err := ns.namespaceLister.List(selector)
if err != nil {
return nil, err
}
ret = make([]runtime.Object, len(objs))
for index, obj := range objs {
ret[index] = obj
}
return ret, err
}
// Get will attempt to retrieve by namespace and name
func (ns *dynamicNamespaceListerShim) Get(name string) (runtime.Object, error) {
return ns.namespaceLister.Get(name)
}

View File

@@ -45,7 +45,7 @@ func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *
}
}
cs := &FakeDynamicClient{}
cs := &FakeDynamicClient{scheme: scheme}
cs.AddReactor("*", "*", testing.ObjectReaction(o))
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
gvr := action.GetResource()
@@ -331,25 +331,26 @@ func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface,
panic("math broke")
}
// TODO: opts are currently ignored.
func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootPatchAction(c.resource, name, data), &metav1.Status{Status: "dynamic patch fail"})
Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})
case len(c.namespace) == 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
case len(c.namespace) > 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewPatchAction(c.resource, c.namespace, name, data), &metav1.Status{Status: "dynamic patch fail"})
Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})
case len(c.namespace) > 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
package fake
import (
"fmt"
"testing"
"k8s.io/apimachinery/pkg/api/equality"
@@ -24,9 +25,20 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/diff"
)
const (
testGroup = "testgroup"
testVersion = "testversion"
testResource = "testkinds"
testNamespace = "testns"
testName = "testname"
testKind = "TestKind"
testAPIVersion = "testgroup/testversion"
)
func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
@@ -40,6 +52,12 @@ func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Uns
}
}
func newUnstructuredWithSpec(spec map[string]interface{}) *unstructured.Unstructured {
u := newUnstructured(testAPIVersion, testKind, testNamespace, testName)
u.Object["spec"] = spec
return u
}
func TestList(t *testing.T) {
scheme := runtime.NewScheme()
@@ -64,3 +82,116 @@ func TestList(t *testing.T) {
t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))
}
}
type patchTestCase struct {
name string
object runtime.Object
patchType types.PatchType
patchBytes []byte
wantErrMsg string
expectedPatchedObject runtime.Object
}
func (tc *patchTestCase) runner(t *testing.T) {
client := NewSimpleDynamicClient(runtime.NewScheme(), tc.object)
resourceInterface := client.Resource(schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}).Namespace(testNamespace)
got, recErr := resourceInterface.Patch(testName, tc.patchType, tc.patchBytes, metav1.UpdateOptions{})
if err := tc.verifyErr(recErr); err != nil {
t.Error(err)
}
if err := tc.verifyResult(got); err != nil {
t.Error(err)
}
}
// verifyErr verifies that the given error returned from Patch is the error
// expected by the test case.
func (tc *patchTestCase) verifyErr(err error) error {
if tc.wantErrMsg != "" && err == nil {
return fmt.Errorf("want error, got nil")
}
if tc.wantErrMsg == "" && err != nil {
return fmt.Errorf("want no error, got %v", err)
}
if err != nil {
if want, got := tc.wantErrMsg, err.Error(); want != got {
return fmt.Errorf("incorrect error: want: %q got: %q", want, got)
}
}
return nil
}
func (tc *patchTestCase) verifyResult(result *unstructured.Unstructured) error {
if tc.expectedPatchedObject == nil && result == nil {
return nil
}
if !equality.Semantic.DeepEqual(result, tc.expectedPatchedObject) {
return fmt.Errorf("unexpected diff in received object: %s", diff.ObjectGoPrintDiff(tc.expectedPatchedObject, result))
}
return nil
}
func TestPatch(t *testing.T) {
testCases := []patchTestCase{
{
name: "jsonpatch fails with merge type",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.StrategicMergePatchType,
patchBytes: []byte(`[]`),
wantErrMsg: "invalid JSON document",
}, {
name: "jsonpatch works with empty patch",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.JSONPatchType,
// No-op
patchBytes: []byte(`[]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
}, {
name: "jsonpatch works with simple change patch",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.JSONPatchType,
// change spec.foo from bar to foobar
patchBytes: []byte(`[{"op": "replace", "path": "/spec/foo", "value": "foobar"}]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "foobar"}),
}, {
name: "jsonpatch works with simple addition",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.JSONPatchType,
// add spec.newvalue = dummy
patchBytes: []byte(`[{"op": "add", "path": "/spec/newvalue", "value": "dummy"}]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar", "newvalue": "dummy"}),
}, {
name: "jsonpatch works with simple deletion",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar", "toremove": "shouldnotbehere"}),
patchType: types.JSONPatchType,
// remove spec.newvalue = dummy
patchBytes: []byte(`[{"op": "remove", "path": "/spec/toremove"}]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
}, {
name: "strategic merge patch fails with JSONPatch",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.StrategicMergePatchType,
// add spec.newvalue = dummy
patchBytes: []byte(`[{"op": "add", "path": "/spec/newvalue", "value": "dummy"}]`),
wantErrMsg: "invalid JSON document",
}, {
name: "merge patch fails as unsupported",
object: newUnstructured(testAPIVersion, testKind, testNamespace, testName),
patchType: types.MergePatchType,
patchBytes: []byte(`{}`),
wantErrMsg: "PatchType is not supported",
},
// TODO: Add tests for strategic merge using v1.Pod for example to ensure the test cases
// demonstrate expected use cases.
}
for _, tc := range testCases {
t.Run(tc.name, tc.runner)
}
}