Skip to content

Commit e476f34

Browse files
authored
test/e2e: refactor sdk tests with improvements from pkg/test (#441)
* test/e2e: refactor sdk tests with improvements from pkg/test The framework in pkg/test was originally based off of the sdk's internal e2e tests. They have since been drastically improved and many changes are applicable to the old sdk tests. This commit refactors the sdk's internal tests with these improvements (specifically, using the dynamic client and the apimachinery wait util). * test/e2e/framework: add comment about createCRFromYAML use
1 parent 391fa55 commit e476f34

File tree

5 files changed

+105
-148
lines changed

5 files changed

+105
-148
lines changed

test/e2e/e2eutil/check_util.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ import (
2020

2121
apierrors "k8s.io/apimachinery/pkg/api/errors"
2222
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/util/wait"
2324
"k8s.io/client-go/kubernetes"
2425
)
2526

2627
var retryInterval = time.Second * 5
2728

2829
func DeploymentReplicaCheck(t *testing.T, kubeclient kubernetes.Interface, namespace, name string, replicas, retries int) error {
29-
err := Retry(retryInterval, retries, func() (done bool, err error) {
30+
err := wait.Poll(retryInterval, time.Duration(retries)*retryInterval, func() (done bool, err error) {
3031
deployment, err := kubeclient.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{IncludeUninitialized: true})
3132
if err != nil {
3233
if apierrors.IsNotFound(err) {

test/e2e/e2eutil/retry_util.go

Lines changed: 0 additions & 62 deletions
This file was deleted.

test/e2e/framework/framework.go

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,42 @@
1515
package framework
1616

1717
import (
18+
goctx "context"
1819
"flag"
20+
"fmt"
1921
"log"
2022
"os"
23+
"sync"
24+
"time"
2125

22-
extensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
26+
extscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
27+
"k8s.io/apimachinery/pkg/api/meta"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/runtime/serializer"
30+
"k8s.io/apimachinery/pkg/util/wait"
31+
"k8s.io/client-go/discovery"
32+
"k8s.io/client-go/discovery/cached"
2333
"k8s.io/client-go/kubernetes"
34+
cgoscheme "k8s.io/client-go/kubernetes/scheme"
2435
"k8s.io/client-go/rest"
2536
"k8s.io/client-go/tools/clientcmd"
37+
dynclient "sigs.k8s.io/controller-runtime/pkg/client"
2638
)
2739

28-
var Global *Framework
40+
var (
41+
// mutex for AddToFrameworkScheme
42+
mutex = sync.Mutex{}
43+
// Global framework struct
44+
Global *Framework
45+
)
2946

3047
type Framework struct {
31-
KubeConfig *rest.Config
32-
KubeClient kubernetes.Interface
33-
ExtensionsClient *extensions.Clientset
34-
ImageName *string
48+
KubeConfig *rest.Config
49+
KubeClient kubernetes.Interface
50+
Scheme *runtime.Scheme
51+
DynamicClient dynclient.Client
52+
DynamicDecoder runtime.Decoder
53+
ImageName *string
3554
}
3655

3756
func setup() error {
@@ -54,15 +73,49 @@ func setup() error {
5473
if err != nil {
5574
return err
5675
}
57-
extensionsClient, err := extensions.NewForConfig(kubeconfig)
76+
scheme := runtime.NewScheme()
77+
cgoscheme.AddToScheme(scheme)
78+
extscheme.AddToScheme(scheme)
79+
dynClient, err := dynclient.New(kubeconfig, dynclient.Options{Scheme: scheme})
5880
if err != nil {
59-
return err
81+
return fmt.Errorf("failed to build the dynamic client: %v", err)
6082
}
83+
dynDec := serializer.NewCodecFactory(scheme).UniversalDeserializer()
6184
Global = &Framework{
62-
KubeConfig: kubeconfig,
63-
KubeClient: kubeclient,
64-
ExtensionsClient: extensionsClient,
65-
ImageName: imageName,
85+
KubeConfig: kubeconfig,
86+
KubeClient: kubeclient,
87+
Scheme: scheme,
88+
DynamicClient: dynClient,
89+
DynamicDecoder: dynDec,
90+
ImageName: imageName,
91+
}
92+
return nil
93+
}
94+
95+
type addToSchemeFunc func(*runtime.Scheme) error
96+
97+
func AddToFrameworkScheme(addToScheme addToSchemeFunc, obj runtime.Object) error {
98+
mutex.Lock()
99+
defer mutex.Unlock()
100+
err := addToScheme(Global.Scheme)
101+
if err != nil {
102+
return err
103+
}
104+
cachedDiscoveryClient := cached.NewMemCacheClient(Global.KubeClient.Discovery())
105+
restMapper := discovery.NewDeferredDiscoveryRESTMapper(cachedDiscoveryClient, meta.InterfacesForUnstructured)
106+
restMapper.Reset()
107+
Global.DynamicClient, err = dynclient.New(Global.KubeConfig, dynclient.Options{Scheme: Global.Scheme, Mapper: restMapper})
108+
err = wait.PollImmediate(time.Second, time.Second*10, func() (done bool, err error) {
109+
err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj)
110+
if err != nil {
111+
restMapper.Reset()
112+
return false, nil
113+
}
114+
return true, nil
115+
})
116+
if err != nil {
117+
return fmt.Errorf("failed to build the dynamic client: %v", err)
66118
}
119+
Global.DynamicDecoder = serializer.NewCodecFactory(Global.Scheme).UniversalDeserializer()
67120
return nil
68121
}

test/e2e/framework/resource_creator.go

Lines changed: 36 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,13 @@
1515
package framework
1616

1717
import (
18-
"errors"
18+
"bytes"
19+
goctx "context"
1920
"strings"
2021

2122
y2j "github.com/ghodss/yaml"
2223
yaml "gopkg.in/yaml.v2"
23-
apps "k8s.io/api/apps/v1"
2424
core "k8s.io/api/core/v1"
25-
"k8s.io/api/rbac/v1beta1"
26-
crd "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
27-
extensions_scheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
28-
apierrors "k8s.io/apimachinery/pkg/api/errors"
2925
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3026
"k8s.io/apimachinery/pkg/runtime/schema"
3127
"k8s.io/apimachinery/pkg/runtime/serializer"
@@ -77,7 +73,7 @@ func (ctx *TestCtx) GetCRClient(yamlCR []byte) (*rest.RESTClient, error) {
7773
return ctx.CRClient, nil
7874
}
7975

80-
func (ctx *TestCtx) createCRFromYAML(yamlFile []byte, resourceName string) error {
76+
func (ctx *TestCtx) createCRFromYAML(yamlFile []byte) error {
8177
client, err := ctx.GetCRClient(yamlFile)
8278
if err != nil {
8379
return err
@@ -86,6 +82,13 @@ func (ctx *TestCtx) createCRFromYAML(yamlFile []byte, resourceName string) error
8682
if err != nil {
8783
return err
8884
}
85+
yamlMap := make(map[interface{}]interface{})
86+
err = yaml.Unmarshal(yamlFile, &yamlMap)
87+
if err != nil {
88+
return err
89+
}
90+
kind := yamlMap["kind"].(string)
91+
resourceName := kind + "s"
8992
jsonDat, err := y2j.YAMLToJSON(yamlFile)
9093
err = client.Post().
9194
Namespace(namespace).
@@ -104,80 +107,41 @@ func (ctx *TestCtx) createCRFromYAML(yamlFile []byte, resourceName string) error
104107
return err
105108
}
106109

107-
func (ctx *TestCtx) createCRDFromYAML(yamlFile []byte) error {
108-
decode := extensions_scheme.Codecs.UniversalDeserializer().Decode
109-
obj, _, err := decode(yamlFile, nil, nil)
110+
func setNamespaceYAML(yamlFile []byte, namespace string) ([]byte, error) {
111+
yamlMap := make(map[interface{}]interface{})
112+
err := yaml.Unmarshal(yamlFile, &yamlMap)
110113
if err != nil {
111-
return err
112-
}
113-
switch o := obj.(type) {
114-
case *crd.CustomResourceDefinition:
115-
_, err = Global.ExtensionsClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(o)
116-
ctx.AddFinalizerFn(func() error {
117-
err = Global.ExtensionsClient.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(o.Name, metav1.NewDeleteOptions(0))
118-
if err != nil && !apierrors.IsNotFound(err) {
119-
return err
120-
}
121-
return nil
122-
})
123-
if apierrors.IsAlreadyExists(err) {
124-
return nil
125-
}
126-
return err
127-
default:
128-
return errors.New("non-CRD resource in createCRDFromYAML function")
114+
return nil, err
129115
}
116+
yamlMap["metadata"].(map[interface{}]interface{})["namespace"] = namespace
117+
return yaml.Marshal(yamlMap)
130118
}
131119

132120
func (ctx *TestCtx) CreateFromYAML(yamlFile []byte) error {
133-
yamlMap := make(map[interface{}]interface{})
134-
err := yaml.Unmarshal(yamlFile, &yamlMap)
121+
namespace, err := ctx.GetNamespace()
135122
if err != nil {
136123
return err
137124
}
138-
kind := yamlMap["kind"].(string)
139-
switch kind {
140-
case "Role":
141-
case "RoleBinding":
142-
case "Deployment":
143-
case "CustomResourceDefinition":
144-
return ctx.createCRDFromYAML(yamlFile)
145-
// we assume that all custom resources are from operator-sdk and thus follow
146-
// a common naming convention
147-
default:
148-
return ctx.createCRFromYAML(yamlFile, strings.ToLower(kind)+"s")
149-
}
125+
yamlSplit := bytes.Split(yamlFile, []byte("\n---\n"))
126+
for _, yamlSpec := range yamlSplit {
127+
yamlSpec, err = setNamespaceYAML(yamlSpec, namespace)
128+
if err != nil {
129+
return err
130+
}
150131

151-
decode := scheme.Codecs.UniversalDeserializer().Decode
152-
obj, _, err := decode(yamlFile, nil, nil)
153-
if err != nil {
154-
return err
155-
}
132+
obj, _, err := Global.DynamicDecoder.Decode(yamlSpec, nil, nil)
133+
if err != nil {
134+
// DynamicClient/DynamicDecoder can only handle standard and extensions kubernetes resources.
135+
// If a resource is not recognized by the decoder, assume it's a custom resource and fall back
136+
// to createCRFromYAML.
137+
return ctx.createCRFromYAML(yamlFile)
138+
}
156139

157-
namespace, err := ctx.GetNamespace()
158-
if err != nil {
159-
return err
160-
}
161-
switch o := obj.(type) {
162-
case *v1beta1.Role:
163-
_, err = Global.KubeClient.RbacV1beta1().Roles(namespace).Create(o)
164-
ctx.AddFinalizerFn(func() error {
165-
return Global.KubeClient.RbacV1beta1().Roles(namespace).Delete(o.Name, metav1.NewDeleteOptions(0))
166-
})
167-
return err
168-
case *v1beta1.RoleBinding:
169-
_, err = Global.KubeClient.RbacV1beta1().RoleBindings(namespace).Create(o)
170-
ctx.AddFinalizerFn(func() error {
171-
return Global.KubeClient.RbacV1beta1().RoleBindings(namespace).Delete(o.Name, metav1.NewDeleteOptions(0))
172-
})
173-
return err
174-
case *apps.Deployment:
175-
_, err = Global.KubeClient.AppsV1().Deployments(namespace).Create(o)
176-
ctx.AddFinalizerFn(func() error {
177-
return Global.KubeClient.AppsV1().Deployments(namespace).Delete(o.Name, metav1.NewDeleteOptions(0))
178-
})
179-
return err
180-
default:
181-
return errors.New("unhandled resource type")
140+
err = Global.DynamicClient.Create(goctx.TODO(), obj)
141+
if err != nil {
142+
return err
143+
}
144+
ctx.AddFinalizerFn(func() error { return Global.DynamicClient.Delete(goctx.TODO(), obj) })
182145
}
146+
return nil
183147
}

test/e2e/memcached_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
framework "github.com/operator-framework/operator-sdk/test/e2e/framework"
3030

3131
"k8s.io/apimachinery/pkg/types"
32+
"k8s.io/apimachinery/pkg/util/wait"
3233
)
3334

3435
const (
@@ -185,7 +186,7 @@ func MemcachedLocal(t *testing.T) {
185186
ctx.AddFinalizerFn(func() error { return cmd.Process.Signal(os.Interrupt) })
186187

187188
// wait for operator to start (may take a minute to compile the command...)
188-
err = e2eutil.Retry(time.Second*5, 16, func() (done bool, err error) {
189+
err = wait.Poll(time.Second*5, time.Second*80, func() (done bool, err error) {
189190
file, err := ioutil.ReadFile("stderr.txt")
190191
if err != nil {
191192
return false, err

0 commit comments

Comments
 (0)