Skip to content

Commit 62df821

Browse files
authored
Add AddToFrameworkScheme function for the test framework (#393)
* pkg/test/framework.go: add AddToFrameworkScheme func * pkg/test/framework.go: use DeferredDiscoveryRESTMapper * pkg/test/framework.go: check for CRD to be ready * test/test-framework/memcached_test.go: update example * pkg/test: remove unused cr helper functions
1 parent 72a982c commit 62df821

File tree

7 files changed

+288
-143
lines changed

7 files changed

+288
-143
lines changed

pkg/test/context.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,12 @@ import (
2020
"strings"
2121
"testing"
2222
"time"
23-
24-
"k8s.io/client-go/rest"
2523
)
2624

2725
type TestCtx struct {
2826
ID string
2927
CleanUpFns []finalizerFn
3028
Namespace string
31-
CRClient *rest.RESTClient
3229
}
3330

3431
type finalizerFn func() error

pkg/test/framework.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@
1515
package test
1616

1717
import (
18+
goctx "context"
1819
"fmt"
20+
"time"
1921

2022
extensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
2123
extscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
24+
"k8s.io/apimachinery/pkg/api/meta"
2225
"k8s.io/apimachinery/pkg/runtime"
2326
"k8s.io/apimachinery/pkg/runtime/serializer"
27+
"k8s.io/apimachinery/pkg/util/wait"
28+
"k8s.io/client-go/discovery"
29+
"k8s.io/client-go/discovery/cached"
2430
"k8s.io/client-go/kubernetes"
2531
cgoscheme "k8s.io/client-go/kubernetes/scheme"
2632
"k8s.io/client-go/rest"
@@ -34,6 +40,8 @@ type Framework struct {
3440
KubeConfig *rest.Config
3541
KubeClient kubernetes.Interface
3642
ExtensionsClient *extensions.Clientset
43+
Scheme *runtime.Scheme
44+
RestMapper *discovery.DeferredDiscoveryRESTMapper
3745
DynamicClient dynclient.Client
3846
DynamicDecoder runtime.Decoder
3947
CrdManPath *string
@@ -66,6 +74,7 @@ func setup(kubeconfigPath, crdManPath, opManPath, rbacManPath *string) error {
6674
KubeConfig: kubeconfig,
6775
KubeClient: kubeclient,
6876
ExtensionsClient: extensionsClient,
77+
Scheme: scheme,
6978
DynamicClient: dynClient,
7079
DynamicDecoder: dynDec,
7180
CrdManPath: crdManPath,
@@ -74,3 +83,43 @@ func setup(kubeconfigPath, crdManPath, opManPath, rbacManPath *string) error {
7483
}
7584
return nil
7685
}
86+
87+
type addToSchemeFunc func(*runtime.Scheme) error
88+
89+
// AddToFrameworkScheme allows users to add the scheme for their custom resources
90+
// to the framework's scheme for use with the dynamic client. The user provides
91+
// the addToScheme function (located in the register.go file of their operator
92+
// project) and the List struct for their custom resource. For example, for a
93+
// memcached operator, the list stuct may look like:
94+
// &MemcachedList{
95+
// TypeMeta: metav1.TypeMeta{
96+
// Kind: "Memcached",
97+
// APIVersion: "cache.example.com/v1alpha1",
98+
// },
99+
// }
100+
// The List object is needed because the CRD has not always been fully registered
101+
// by the time this function is called. If the CRD takes more than 5 seconds to
102+
// become ready, this function throws an error
103+
func AddToFrameworkScheme(addToScheme addToSchemeFunc, obj runtime.Object) error {
104+
err := addToScheme(Global.Scheme)
105+
if err != nil {
106+
return err
107+
}
108+
cachedDiscoveryClient := cached.NewMemCacheClient(Global.KubeClient.Discovery())
109+
Global.RestMapper = discovery.NewDeferredDiscoveryRESTMapper(cachedDiscoveryClient, meta.InterfacesForUnstructured)
110+
Global.RestMapper.Reset()
111+
Global.DynamicClient, err = dynclient.New(Global.KubeConfig, dynclient.Options{Scheme: Global.Scheme, Mapper: Global.RestMapper})
112+
err = wait.PollImmediate(time.Second, time.Second*10, func() (done bool, err error) {
113+
err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj)
114+
if err != nil {
115+
Global.RestMapper.Reset()
116+
return false, nil
117+
}
118+
return true, nil
119+
})
120+
if err != nil {
121+
return fmt.Errorf("failed to build the dynamic client: %v", err)
122+
}
123+
Global.DynamicDecoder = serializer.NewCodecFactory(Global.Scheme).UniversalDeserializer()
124+
return nil
125+
}

pkg/test/resource_creator.go

Lines changed: 1 addition & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,13 @@ package test
1717
import (
1818
"bytes"
1919
goctx "context"
20-
"errors"
2120
"fmt"
2221
"io/ioutil"
23-
"strings"
2422

25-
y2j "github.com/ghodss/yaml"
2623
yaml "gopkg.in/yaml.v2"
2724
core "k8s.io/api/core/v1"
28-
crd "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
29-
extensions_scheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
3025
apierrors "k8s.io/apimachinery/pkg/api/errors"
3126
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32-
"k8s.io/apimachinery/pkg/runtime/schema"
33-
"k8s.io/apimachinery/pkg/runtime/serializer"
34-
"k8s.io/apimachinery/pkg/types"
35-
"k8s.io/client-go/kubernetes/scheme"
36-
"k8s.io/client-go/rest"
3727
)
3828

3929
func (ctx *TestCtx) GetNamespace() (string, error) {
@@ -55,119 +45,6 @@ func (ctx *TestCtx) GetNamespace() (string, error) {
5545
return ctx.Namespace, nil
5646
}
5747

58-
func (ctx *TestCtx) GetCRClient(yamlCR []byte) (*rest.RESTClient, error) {
59-
if ctx.CRClient != nil {
60-
return ctx.CRClient, nil
61-
}
62-
// a user may pass nil if they expect the CRClient to already exist
63-
if yamlCR == nil {
64-
return nil, errors.New("ctx.CRClient does not exist; yamlCR cannot be nil")
65-
}
66-
// get new RESTClient for custom resources
67-
crConfig := Global.KubeConfig
68-
yamlMap := make(map[interface{}]interface{})
69-
err := yaml.Unmarshal(yamlCR, &yamlMap)
70-
if err != nil {
71-
return nil, err
72-
}
73-
groupVersion := strings.Split(yamlMap["apiVersion"].(string), "/")
74-
crGV := schema.GroupVersion{Group: groupVersion[0], Version: groupVersion[1]}
75-
crConfig.GroupVersion = &crGV
76-
crConfig.APIPath = "/apis"
77-
crConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
78-
79-
if crConfig.UserAgent == "" {
80-
crConfig.UserAgent = rest.DefaultKubernetesUserAgent()
81-
}
82-
ctx.CRClient, err = rest.RESTClientFor(crConfig)
83-
return ctx.CRClient, err
84-
}
85-
86-
// TODO: Implement a way for a user to add their own scheme to us the dynamic
87-
// client to eliminate the need for the UpdateCR function
88-
89-
// UpdateCR takes the name of a resource, the resource plural name,
90-
// the path of the field that need to be updated (e.g. /spec/size),
91-
// and the new value to that field and patches the resource with
92-
// that change
93-
func (ctx *TestCtx) UpdateCR(name, resourceName, path, value string) error {
94-
crClient, err := ctx.GetCRClient(nil)
95-
if err != nil {
96-
return err
97-
}
98-
namespace, err := ctx.GetNamespace()
99-
if err != nil {
100-
return err
101-
}
102-
return crClient.Patch(types.JSONPatchType).
103-
Namespace(namespace).
104-
Resource(resourceName).
105-
Name(name).
106-
Body([]byte("[{\"op\": \"replace\", \"path\": \"" + path + "\", \"value\": " + value + "}]")).
107-
Do().
108-
Error()
109-
}
110-
111-
func (ctx *TestCtx) createCRFromYAML(yamlFile []byte, resourceName string) error {
112-
client, err := ctx.GetCRClient(yamlFile)
113-
if err != nil {
114-
return err
115-
}
116-
namespace, err := ctx.GetNamespace()
117-
if err != nil {
118-
return err
119-
}
120-
yamlMap := make(map[interface{}]interface{})
121-
err = yaml.Unmarshal(yamlFile, &yamlMap)
122-
if err != nil {
123-
return err
124-
}
125-
// TODO: handle failure of this line without segfault
126-
name := yamlMap["metadata"].(map[interface{}]interface{})["name"].(string)
127-
jsonDat, err := y2j.YAMLToJSON(yamlFile)
128-
err = client.Post().
129-
Namespace(namespace).
130-
Resource(resourceName).
131-
Body(jsonDat).
132-
Do().
133-
Error()
134-
ctx.AddFinalizerFn(func() error {
135-
return client.Delete().
136-
Namespace(namespace).
137-
Resource(resourceName).
138-
Name(name).
139-
Body(metav1.NewDeleteOptions(0)).
140-
Do().
141-
Error()
142-
})
143-
return err
144-
}
145-
146-
func (ctx *TestCtx) createCRDFromYAML(yamlFile []byte) error {
147-
decode := extensions_scheme.Codecs.UniversalDeserializer().Decode
148-
obj, _, err := decode(yamlFile, nil, nil)
149-
if err != nil {
150-
return err
151-
}
152-
switch o := obj.(type) {
153-
case *crd.CustomResourceDefinition:
154-
_, err = Global.ExtensionsClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(o)
155-
ctx.AddFinalizerFn(func() error {
156-
err = Global.ExtensionsClient.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(o.Name, metav1.NewDeleteOptions(0))
157-
if err != nil && !apierrors.IsNotFound(err) {
158-
return err
159-
}
160-
return nil
161-
})
162-
if apierrors.IsAlreadyExists(err) {
163-
return nil
164-
}
165-
return err
166-
default:
167-
return errors.New("non-CRD resource in createCRDFromYAML function")
168-
}
169-
}
170-
17148
func setNamespaceYAML(yamlFile []byte, namespace string) ([]byte, error) {
17249
yamlMap := make(map[interface{}]interface{})
17350
err := yaml.Unmarshal(yamlFile, &yamlMap)
@@ -192,17 +69,7 @@ func (ctx *TestCtx) CreateFromYAML(yamlFile []byte) error {
19269

19370
obj, _, err := Global.DynamicDecoder.Decode(yamlSpec, nil, nil)
19471
if err != nil {
195-
yamlMap := make(map[interface{}]interface{})
196-
err = yaml.Unmarshal(yamlSpec, &yamlMap)
197-
if err != nil {
198-
return err
199-
}
200-
kind := yamlMap["kind"].(string)
201-
err = ctx.createCRFromYAML(yamlSpec, strings.ToLower(kind)+"s")
202-
if err != nil {
203-
return err
204-
}
205-
continue
72+
return err
20673
}
20774

20875
err = Global.DynamicClient.Create(goctx.TODO(), obj)

test/test-framework/memcached_test.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@
1515
package e2e
1616

1717
import (
18+
goctx "context"
19+
"fmt"
1820
"testing"
1921
"time"
2022

23+
operator "github.com/operator-framework/operator-sdk/test/test-framework/pkg/apis/cache/v1alpha1"
2124
framework "github.com/operator-framework/operator-sdk/pkg/test"
2225
"github.com/operator-framework/operator-sdk/pkg/util/e2eutil"
26+
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/types"
2329
)
2430

2531
var (
@@ -28,6 +34,16 @@ var (
2834
)
2935

3036
func TestMemcached(t *testing.T) {
37+
memcachedList := &operator.MemcachedList{
38+
TypeMeta: metav1.TypeMeta{
39+
Kind: "Memcached",
40+
APIVersion: "cache.example.com/v1alpha1",
41+
},
42+
}
43+
err := framework.AddToFrameworkScheme(operator.AddToScheme, memcachedList)
44+
if err != nil {
45+
t.Fatalf("failed to add custom resource scheme to framework: %v", err)
46+
}
3147
// run subtests
3248
t.Run("memcached-group", func(t *testing.T) {
3349
t.Run("Cluster", MemcachedCluster)
@@ -36,13 +52,25 @@ func TestMemcached(t *testing.T) {
3652
}
3753

3854
func memcachedScaleTest(t *testing.T, f *framework.Framework, ctx framework.TestCtx) error {
39-
// create memcached custom resource
40-
crYAML := []byte("apiVersion: \"cache.example.com/v1alpha1\"\nkind: \"Memcached\"\nmetadata:\n name: \"example-memcached\"\nspec:\n size: 3")
41-
err := ctx.CreateFromYAML(crYAML)
55+
namespace, err := ctx.GetNamespace()
4256
if err != nil {
43-
return err
57+
return fmt.Errorf("could not get namespace: %v", err)
4458
}
45-
namespace, err := ctx.GetNamespace()
59+
// create memcached custom resource
60+
exampleMemcached := &operator.Memcached{
61+
TypeMeta: metav1.TypeMeta{
62+
Kind: "Memcached",
63+
APIVersion: "cache.example.com/v1alpha1",
64+
},
65+
ObjectMeta: metav1.ObjectMeta{
66+
Name: "example-memcached",
67+
Namespace: namespace,
68+
},
69+
Spec: operator.MemcachedSpec{
70+
Size: 3,
71+
},
72+
}
73+
err = f.DynamicClient.Create(goctx.TODO(), exampleMemcached)
4674
if err != nil {
4775
return err
4876
}
@@ -52,7 +80,12 @@ func memcachedScaleTest(t *testing.T, f *framework.Framework, ctx framework.Test
5280
return err
5381
}
5482

55-
err = ctx.UpdateCR("example-memcached", "memcacheds", "/spec/size", "4")
83+
err = f.DynamicClient.Get(goctx.TODO(), types.NamespacedName{Name: "example-memcached", Namespace: namespace}, exampleMemcached)
84+
if err != nil {
85+
return err
86+
}
87+
exampleMemcached.Spec.Size = 4
88+
err = f.DynamicClient.Update(goctx.TODO(), exampleMemcached)
5689
if err != nil {
5790
return err
5891
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2018 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1alpha1
16+
17+
import (
18+
sdkK8sutil "github.com/operator-framework/operator-sdk/pkg/util/k8sutil"
19+
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
"k8s.io/apimachinery/pkg/runtime"
22+
"k8s.io/apimachinery/pkg/runtime/schema"
23+
)
24+
25+
const (
26+
version = "v1alpha1"
27+
groupName = "cache.example.com"
28+
)
29+
30+
var (
31+
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
32+
AddToScheme = SchemeBuilder.AddToScheme
33+
// SchemeGroupVersion is the group version used to register these objects.
34+
SchemeGroupVersion = schema.GroupVersion{Group: groupName, Version: version}
35+
)
36+
37+
func init() {
38+
sdkK8sutil.AddToSDKScheme(AddToScheme)
39+
}
40+
41+
// addKnownTypes adds the set of types defined in this package to the supplied scheme.
42+
func addKnownTypes(scheme *runtime.Scheme) error {
43+
scheme.AddKnownTypes(SchemeGroupVersion,
44+
&Memcached{},
45+
&MemcachedList{},
46+
)
47+
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
48+
return nil
49+
}

0 commit comments

Comments
 (0)