Skip to content

Commit 0f96d13

Browse files
committed
test(clientfakes): add k8s client decorator and shared options
- Add k8s fake client decorator - Add shared options for k8s and olm decorators - Add simple name generator option
1 parent a1e7603 commit 0f96d13

File tree

6 files changed

+194
-61
lines changed

6 files changed

+194
-61
lines changed

pkg/api/client/clientset/versioned/fake/decorator.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package fake
1818
import (
1919
"k8s.io/apimachinery/pkg/runtime"
2020
"k8s.io/client-go/testing"
21+
22+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clientfake"
2123
)
2224

2325
// ClientsetDecorator defines decorator methods for a Clientset.
@@ -38,14 +40,19 @@ type ReactionForwardingClientsetDecorator struct {
3840

3941
// NewReactionForwardingClientsetDecorator returns the ReactionForwardingClientsetDecorator wrapped Clientset result
4042
// of calling NewSimpleClientset with the given objects.
41-
func NewReactionForwardingClientsetDecorator(objects ...runtime.Object) *ReactionForwardingClientsetDecorator {
43+
func NewReactionForwardingClientsetDecorator(objects []runtime.Object, options ...clientfake.Option) *ReactionForwardingClientsetDecorator {
4244
decorator := &ReactionForwardingClientsetDecorator{
4345
Clientset: *NewSimpleClientset(objects...),
4446
}
4547

4648
// Swap out the embedded ReactionChain with a Reactor that reduces over the decorator's ReactionChain.
4749
decorator.ReactionChain = decorator.Clientset.ReactionChain
4850
decorator.Clientset.ReactionChain = []testing.Reactor{&testing.SimpleReactor{"*", "*", decorator.reduceReactions}}
51+
52+
// Apply options
53+
for _, option := range options {
54+
option(decorator)
55+
}
4956

5057
return decorator
5158
}

pkg/controller/operators/catalog/operator_test.go

Lines changed: 9 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@ import (
1616
rbacv1 "k8s.io/api/rbac/v1"
1717
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
1818
apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
19-
"k8s.io/apimachinery/pkg/api/meta"
2019
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2120
"k8s.io/apimachinery/pkg/runtime"
2221
"k8s.io/apimachinery/pkg/types"
2322
"k8s.io/client-go/informers"
2423
k8sfake "k8s.io/client-go/kubernetes/fake"
25-
clitesting "k8s.io/client-go/testing"
2624
"k8s.io/client-go/tools/cache"
2725
"k8s.io/client-go/util/workqueue"
2826
apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
@@ -34,6 +32,7 @@ import (
3432
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler"
3533
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
3634
"github.com/operator-framework/operator-lifecycle-manager/pkg/fakes"
35+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clientfake"
3736
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
3837
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
3938
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
@@ -379,8 +378,6 @@ func TestSyncCatalogSources(t *testing.T) {
379378
}
380379
}
381380

382-
// TODO: CatalogSource tests for RegistryServiceStatus
383-
384381
func TestCompetingCRDOwnersExist(t *testing.T) {
385382

386383
testNamespace := "default"
@@ -465,43 +462,13 @@ func fakeConfigMapData() map[string]string {
465462
return data
466463
}
467464

468-
// fakeClientOption configures a fake option
469-
type fakeClientOption func(fake.ClientsetDecorator)
470-
471-
// withSelfLinks returns a fakeClientOption that configures a ClientsetDecorator to write selfLinks to all OLM types on create.
472-
func withSelfLinks(t *testing.T) fakeClientOption {
473-
return func(c fake.ClientsetDecorator) {
474-
c.PrependReactor("create", "*", func(a clitesting.Action) (bool, runtime.Object, error) {
475-
ca, ok := a.(clitesting.CreateAction)
476-
if !ok {
477-
t.Fatalf("expected CreateAction")
478-
}
479-
480-
obj := ca.GetObject()
481-
accessor, err := meta.Accessor(obj)
482-
if err != nil {
483-
return false, nil, err
484-
}
485-
if accessor.GetSelfLink() != "" {
486-
// SelfLink is already set
487-
return false, nil, nil
488-
}
489-
490-
gvr := ca.GetResource()
491-
accessor.SetSelfLink(buildSelfLink(gvr.GroupVersion().String(), gvr.Resource, accessor.GetNamespace(), accessor.GetName()))
492-
493-
return false, obj, nil
494-
})
495-
}
496-
}
497-
498465
// fakeOperatorConfig is the configuration for a fake operator.
499466
type fakeOperatorConfig struct {
500-
clientObjs []runtime.Object
501-
k8sObjs []runtime.Object
502-
extObjs []runtime.Object
503-
regObjs []runtime.Object
504-
fakeClientOptions []fakeClientOption
467+
clientObjs []runtime.Object
468+
k8sObjs []runtime.Object
469+
extObjs []runtime.Object
470+
regObjs []runtime.Object
471+
clientOptions []clientfake.Option
505472
}
506473

507474
// fakeOperatorOption applies an option to the given fake operator configuration.
@@ -525,9 +492,9 @@ func extObjs(extObjs ...runtime.Object) fakeOperatorOption {
525492
}
526493
}
527494

528-
func withFakeClientOptions(options ...fakeClientOption) fakeOperatorOption {
495+
func withFakeClientOptions(options ...clientfake.Option) fakeOperatorOption {
529496
return func(config *fakeOperatorConfig) {
530-
config.fakeClientOptions = options
497+
config.clientOptions = options
531498
}
532499
}
533500

@@ -540,11 +507,7 @@ func NewFakeOperator(namespace string, watchedNamespaces []string, stopCh <-chan
540507
}
541508

542509
// Create client fakes
543-
clientFake := fake.NewReactionForwardingClientsetDecorator(config.clientObjs...)
544-
for _, option := range config.fakeClientOptions {
545-
option(clientFake)
546-
}
547-
510+
clientFake := fake.NewReactionForwardingClientsetDecorator(config.clientObjs, config.clientOptions...)
548511
opClientFake := operatorclient.NewClient(k8sfake.NewSimpleClientset(config.k8sObjs...), apiextensionsfake.NewSimpleClientset(config.extObjs...), apiregistrationfake.NewSimpleClientset(config.regObjs...))
549512

550513
// Create operator namespace
@@ -707,11 +670,3 @@ func toManifest(obj runtime.Object) string {
707670
raw, _ := json.Marshal(obj)
708671
return string(raw)
709672
}
710-
711-
// selfLink returns a selfLink.
712-
func buildSelfLink(groupVersion, plural, namespace, name string) string {
713-
if namespace == metav1.NamespaceAll {
714-
return fmt.Sprintf("/apis/%s/%s/%s", groupVersion, plural, name)
715-
}
716-
return fmt.Sprintf("/apis/%s/namespaces/%s/%s/%s", groupVersion, namespace, plural, name)
717-
}

pkg/controller/operators/catalog/subscriptions_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ import (
1414
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler"
1515
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
1616
"github.com/operator-framework/operator-lifecycle-manager/pkg/fakes"
17+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clientfake"
1718
)
1819

1920
func TestSyncSubscriptions(t *testing.T) {
2021
now := metav1.Date(2018, time.January, 26, 20, 40, 0, 0, time.UTC)
2122
timeNow = func() metav1.Time { return now }
22-
2323
testNamespace := "testNamespace"
24+
2425
type fields struct {
25-
fakeClientOptions []fakeClientOption
26+
clientOptions []clientfake.Option
2627
sourcesLastUpdate metav1.Time
2728
resolveSteps []*v1alpha1.Step
2829
resolveSubs []*v1alpha1.Subscription
@@ -51,7 +52,7 @@ func TestSyncSubscriptions(t *testing.T) {
5152
{
5253
name: "NoStatus/NoCurrentCSV/FoundInCatalog",
5354
fields: fields{
54-
fakeClientOptions: []fakeClientOption{withSelfLinks(t)},
55+
clientOptions: []clientfake.Option{clientfake.WithSelfLinks(t)},
5556
existingOLMObjs: []runtime.Object{
5657
&v1alpha1.Subscription{
5758
ObjectMeta: metav1.ObjectMeta{
@@ -182,7 +183,7 @@ func TestSyncSubscriptions(t *testing.T) {
182183
{
183184
name: "Status/HaveCurrentCSV/UpdateFoundInCatalog",
184185
fields: fields{
185-
fakeClientOptions: []fakeClientOption{withSelfLinks(t)},
186+
clientOptions: []clientfake.Option{clientfake.WithSelfLinks(t)},
186187
existingOLMObjs: []runtime.Object{
187188
&v1alpha1.ClusterServiceVersion{
188189
ObjectMeta: metav1.ObjectMeta{
@@ -322,7 +323,7 @@ func TestSyncSubscriptions(t *testing.T) {
322323
{
323324
name: "Status/HaveCurrentCSV/UpdateFoundInCatalog/UpdateRequiresDependency",
324325
fields: fields{
325-
fakeClientOptions: []fakeClientOption{withSelfLinks(t)},
326+
clientOptions: []clientfake.Option{clientfake.WithSelfLinks(t)},
326327
existingOLMObjs: []runtime.Object{
327328
&v1alpha1.ClusterServiceVersion{
328329
ObjectMeta: metav1.ObjectMeta{
@@ -514,7 +515,7 @@ func TestSyncSubscriptions(t *testing.T) {
514515
// Create test operator
515516
stopCh := make(chan struct{})
516517
defer func() { stopCh <- struct{}{} }()
517-
o, err := NewFakeOperator(testNamespace, []string{testNamespace}, stopCh, withClientObjs(tt.fields.existingOLMObjs...), withK8sObjs(tt.fields.existingObjects...), withFakeClientOptions(tt.fields.fakeClientOptions...))
518+
o, err := NewFakeOperator(testNamespace, []string{testNamespace}, stopCh, withClientObjs(tt.fields.existingOLMObjs...), withK8sObjs(tt.fields.existingObjects...), withFakeClientOptions(tt.fields.clientOptions...))
518519
require.NoError(t, err)
519520

520521
o.reconciler = &fakes.FakeRegistryReconcilerFactory{

pkg/lib/clientfake/client_options.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package clientfake
2+
3+
import (
4+
"testing"
5+
6+
"k8s.io/apimachinery/pkg/api/meta"
7+
"k8s.io/apimachinery/pkg/runtime"
8+
clitesting "k8s.io/client-go/testing"
9+
)
10+
11+
// Option configures a ClientsetDecorator
12+
type Option func(ClientsetDecorator)
13+
14+
// WithSelfLinks returns a fakeClientOption that configures a ClientsetDecorator to write selfLinks to all OLM types on create.
15+
func WithSelfLinks(t *testing.T) Option {
16+
return func(c ClientsetDecorator) {
17+
c.PrependReactor("create", "*", func(a clitesting.Action) (bool, runtime.Object, error) {
18+
ca, ok := a.(clitesting.CreateAction)
19+
if !ok {
20+
t.Fatalf("expected CreateAction")
21+
}
22+
23+
obj := ca.GetObject()
24+
accessor, err := meta.Accessor(obj)
25+
if err != nil {
26+
return false, nil, err
27+
}
28+
if accessor.GetSelfLink() != "" {
29+
// SelfLink is already set
30+
return false, nil, nil
31+
}
32+
33+
gvr := ca.GetResource()
34+
accessor.SetSelfLink(BuildSelfLink(gvr.GroupVersion().String(), gvr.Resource, accessor.GetNamespace(), accessor.GetName()))
35+
36+
return false, obj, nil
37+
})
38+
}
39+
}
40+
41+
// WithNameGeneration returns a fakeK8sClientOption that configures a Clientset to write generated names to all types on create.
42+
func WithNameGeneration(t *testing.T) Option {
43+
return func(c ClientsetDecorator) {
44+
c.PrependReactor("create", "*", func(a clitesting.Action) (bool, runtime.Object, error) {
45+
ca, ok := a.(clitesting.CreateAction)
46+
if !ok {
47+
t.Fatalf("expected CreateAction")
48+
}
49+
50+
return false, AddSimpleGeneratedName(ca.GetObject()), nil
51+
})
52+
}
53+
}

pkg/lib/clientfake/decorator.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package clientfake
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/runtime"
5+
fake "k8s.io/client-go/kubernetes/fake"
6+
"k8s.io/client-go/testing"
7+
)
8+
9+
// This is used to prepend reactors to the k8s fake client. should be removed when client go is updated.
10+
// TODO: see if we can merge the OLM ClientsetDecorator and this one.
11+
12+
// ClientsetDecorator defines decorator methods for a Clientset.
13+
type ClientsetDecorator interface {
14+
// PrependReactor adds a reactor to the beginning of the chain.
15+
PrependReactor(verb, resource string, reaction testing.ReactionFunc)
16+
}
17+
18+
// ReactionForwardingClientsetDecorator wraps a Clientset and "forwards" Action object mutations
19+
// from all successful non-handling Reactors along the chain to the first handling Reactor. This is
20+
// is a stopgap until we can upgrade to client-go v11.0, where the behavior is the default
21+
// (see https://github.com/kubernetes/client-go/blob/6ee68ca5fd8355d024d02f9db0b3b667e8357a0f/testing/fake.go#L130).
22+
type ReactionForwardingClientsetDecorator struct {
23+
fake.Clientset
24+
ReactionChain []testing.Reactor // shadow embedded ReactionChain
25+
actions []testing.Action // these may be castable to other types, but "Action" is the minimum
26+
}
27+
28+
// NewReactionForwardingClientsetDecorator returns the ReactionForwardingClientsetDecorator wrapped Clientset result
29+
// of calling NewSimpleClientset with the given objects.
30+
func NewReactionForwardingClientsetDecorator(objects []runtime.Object, options ...Option) *ReactionForwardingClientsetDecorator {
31+
decorator := &ReactionForwardingClientsetDecorator{
32+
Clientset: *fake.NewSimpleClientset(objects...),
33+
}
34+
35+
// Swap out the embedded ReactionChain with a Reactor that reduces over the decorator's ReactionChain.
36+
decorator.ReactionChain = decorator.Clientset.ReactionChain
37+
decorator.Clientset.ReactionChain = []testing.Reactor{&testing.SimpleReactor{"*", "*", decorator.reduceReactions}}
38+
39+
// Apply options
40+
for _, option := range options {
41+
option(decorator)
42+
}
43+
44+
return decorator
45+
}
46+
47+
// reduceReactions reduces over all reactions in the chain while "forwarding" Action object mutations
48+
// from all successful non-handling Reactors along the chain to the first handling Reactor.
49+
func (c *ReactionForwardingClientsetDecorator) reduceReactions(action testing.Action) (handled bool, ret runtime.Object, err error) {
50+
// The embedded Client set is already locked, so there's no need to lock again
51+
actionCopy := action.DeepCopy()
52+
c.actions = append(c.actions, action.DeepCopy())
53+
for _, reactor := range c.ReactionChain {
54+
if !reactor.Handles(actionCopy) {
55+
continue
56+
}
57+
58+
handled, ret, err = reactor.React(actionCopy)
59+
if !handled {
60+
continue
61+
}
62+
63+
return
64+
}
65+
66+
return
67+
}
68+
69+
// PrependReactor adds a reactor to the beginning of the chain.
70+
func (c *ReactionForwardingClientsetDecorator) PrependReactor(verb, resource string, reaction testing.ReactionFunc) {
71+
c.ReactionChain = append([]testing.Reactor{&testing.SimpleReactor{verb, resource, reaction}}, c.ReactionChain...)
72+
}

pkg/lib/clientfake/meta.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package clientfake
2+
3+
import (
4+
"fmt"
5+
6+
"k8s.io/apimachinery/pkg/api/meta"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/apimachinery/pkg/runtime"
9+
"k8s.io/apiserver/pkg/storage/names"
10+
)
11+
12+
// BuildSelfLink returns a selflink for the given group version, plural, namespace, and name.
13+
func BuildSelfLink(groupVersion, plural, namespace, name string) string {
14+
if namespace == metav1.NamespaceAll {
15+
return fmt.Sprintf("/apis/%s/%s/%s", groupVersion, plural, name)
16+
}
17+
return fmt.Sprintf("/apis/%s/namespaces/%s/%s/%s", groupVersion, namespace, plural, name)
18+
}
19+
20+
// AddSimpleGeneratedName returns the given object with a simple generated name added to its metadata.
21+
// If a name already exists, there is no GenerateName field set, or there is an issue accessing the object's metadata
22+
// the object is returned unmodified.
23+
func AddSimpleGeneratedName(obj runtime.Object) runtime.Object {
24+
accessor, err := meta.Accessor(obj)
25+
if err != nil {
26+
return obj
27+
}
28+
if accessor.GetName() == "" && accessor.GetGenerateName() != "" {
29+
// TODO: for tests, it would be nice to be able to retrieve this name later
30+
accessor.SetName(names.SimpleNameGenerator.GenerateName(accessor.GetGenerateName()))
31+
}
32+
33+
return obj
34+
}
35+
36+
// AddSimpleGeneratedNames returns the list objects with simple generated names added to their metadata.
37+
// If a name already exists, there is no GenerateName field set, or there is an issue accessing the object's metadata
38+
// the object is returned unmodified.
39+
func AddSimpleGeneratedNames(objs ...runtime.Object) []runtime.Object {
40+
for i, obj := range objs {
41+
objs[i] = AddSimpleGeneratedName(obj)
42+
}
43+
44+
return objs
45+
}

0 commit comments

Comments
 (0)