Skip to content

Commit b75c76f

Browse files
authored
Add e2e kube client (#2712)
Signed-off-by: perdasilva <[email protected]>
1 parent 3807cc1 commit b75c76f

File tree

4 files changed

+274
-2
lines changed

4 files changed

+274
-2
lines changed

test/e2e/ctx/ctx.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import (
77
"path/filepath"
88
"strings"
99

10-
g "github.com/onsi/ginkgo"
10+
"github.com/operator-framework/operator-lifecycle-manager/test/e2e/util"
11+
appsv1 "k8s.io/api/apps/v1"
1112
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
13+
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
14+
15+
g "github.com/onsi/ginkgo"
1216
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1317
"k8s.io/apimachinery/pkg/runtime"
1418
"k8s.io/client-go/dynamic"
@@ -35,6 +39,7 @@ type TestContext struct {
3539
operatorClient versioned.Interface
3640
dynamicClient dynamic.Interface
3741
packageClient pversioned.Interface
42+
e2eClient *util.E2EKubeClient
3843
ssaClient *controllerclient.ServerSideApplier
3944

4045
kubeconfigPath string
@@ -93,6 +98,17 @@ func (ctx TestContext) SSAClient() *controllerclient.ServerSideApplier {
9398
return ctx.ssaClient
9499
}
95100

101+
func (ctx TestContext) E2EClient() *util.E2EKubeClient {
102+
return ctx.e2eClient
103+
}
104+
105+
func (ctx TestContext) NewE2EClientSession() {
106+
if ctx.e2eClient != nil {
107+
_ = ctx.e2eClient.Reset()
108+
}
109+
ctx.e2eClient = util.NewK8sResourceManager(ctx.Client())
110+
}
111+
96112
func (ctx TestContext) DumpNamespaceArtifacts(namespace string) error {
97113
if ctx.artifactsDir == "" {
98114
ctx.Logf("$ARTIFACTS_DIR is unset -- not collecting failed test case logs")
@@ -163,12 +179,14 @@ func setDerivedFields(ctx *TestContext) error {
163179

164180
ctx.scheme = runtime.NewScheme()
165181
localSchemeBuilder := runtime.NewSchemeBuilder(
182+
apiextensions.AddToScheme,
166183
kscheme.AddToScheme,
167184
operatorsv1alpha1.AddToScheme,
168185
operatorsv1.AddToScheme,
169186
operatorsv2.AddToScheme,
170187
apiextensionsv1.AddToScheme,
171-
apiextensions.AddToScheme,
188+
appsv1.AddToScheme,
189+
apiregistrationv1.AddToScheme,
172190
)
173191
if err := localSchemeBuilder.AddToScheme(ctx.scheme); err != nil {
174192
return err
@@ -181,6 +199,7 @@ func setDerivedFields(ctx *TestContext) error {
181199
return err
182200
}
183201
ctx.client = client
202+
ctx.e2eClient = util.NewK8sResourceManager(client)
184203

185204
ctx.ssaClient, err = controllerclient.NewForConfig(ctx.restConfig, ctx.scheme, "test.olm.registry")
186205
if err != nil {

test/e2e/resource_manager_test.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
"github.com/operator-framework/operator-lifecycle-manager/test/e2e/util"
9+
corev1 "k8s.io/api/core/v1"
10+
k8serror "k8s.io/apimachinery/pkg/api/errors"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"sigs.k8s.io/controller-runtime/pkg/client"
13+
14+
"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
15+
)
16+
17+
var _ = Describe("ResourceManager", func() {
18+
19+
var generatedNamespace corev1.Namespace
20+
21+
BeforeEach(func() {
22+
ctx.Ctx().NewE2EClientSession()
23+
generatedNamespace = SetupGeneratedTestNamespace(genName("resource-manager-e2e-"))
24+
})
25+
26+
AfterEach(func() {
27+
TeardownNamespace(generatedNamespace.GetName())
28+
Expect(ctx.Ctx().E2EClient().Reset()).To(Succeed())
29+
})
30+
31+
It("should tag resources created with it", func() {
32+
// Create a namespace
33+
ns := &corev1.Namespace{
34+
ObjectMeta: metav1.ObjectMeta{
35+
Name: genName("test-"),
36+
},
37+
}
38+
Expect(ctx.Ctx().E2EClient().Create(context.TODO(), ns)).To(Succeed())
39+
40+
// Get namespace
41+
Expect(ctx.Ctx().E2EClient().Get(context.TODO(), client.ObjectKeyFromObject(ns), ns)).To(Succeed())
42+
Expect(ns.GetAnnotations()).NotTo(BeEmpty())
43+
Expect(ns.GetAnnotations()[util.E2ETestNameTag]).To(Equal("ResourceManager should tag resources created with it"))
44+
})
45+
46+
It("should delete resources on reset", func() {
47+
// Create a namespace
48+
ns := &corev1.Namespace{
49+
ObjectMeta: metav1.ObjectMeta{
50+
Name: genName("test-"),
51+
},
52+
}
53+
Expect(ctx.Ctx().E2EClient().Create(context.TODO(), ns)).To(Succeed())
54+
55+
// Add a config map
56+
configMap := &corev1.ConfigMap{
57+
ObjectMeta: metav1.ObjectMeta{
58+
Name: genName("configmap-"),
59+
60+
// creating the configmap in the generated (spec) namespace
61+
// so if the namespace (ns, above) gets deleted on reset it won't take the config map with it
62+
Namespace: generatedNamespace.GetName(),
63+
},
64+
}
65+
Expect(ctx.Ctx().E2EClient().Create(context.TODO(), configMap))
66+
67+
// Reset the client
68+
Expect(ctx.Ctx().E2EClient().Reset()).To(Succeed())
69+
70+
// And just like that resources should be gone
71+
Eventually(func() error {
72+
return ctx.Ctx().E2EClient().Get(context.TODO(), client.ObjectKeyFromObject(configMap), configMap)
73+
}).Should(WithTransform(k8serror.IsNotFound, BeTrue()))
74+
Eventually(func() error {
75+
return ctx.Ctx().E2EClient().Get(context.TODO(), client.ObjectKeyFromObject(ns), ns)
76+
}).Should(WithTransform(k8serror.IsNotFound, BeTrue()))
77+
})
78+
})

test/e2e/util/e2e_client.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package util
2+
3+
import (
4+
"context"
5+
6+
"github.com/onsi/ginkgo"
7+
k8serror "k8s.io/apimachinery/pkg/api/errors"
8+
k8scontrollerclient "sigs.k8s.io/controller-runtime/pkg/client"
9+
)
10+
11+
const (
12+
E2ETestNameTag = "e2e.testName"
13+
)
14+
15+
type E2EKubeClient struct {
16+
k8scontrollerclient.Client
17+
createdResources *ResourceQueue
18+
}
19+
20+
func NewK8sResourceManager(client k8scontrollerclient.Client) *E2EKubeClient {
21+
return &E2EKubeClient{
22+
Client: client,
23+
createdResources: NewResourceQueue(),
24+
}
25+
}
26+
27+
func (m *E2EKubeClient) Create(context context.Context, obj k8scontrollerclient.Object, options ...k8scontrollerclient.CreateOption) error {
28+
m.annotateTestResource(obj)
29+
if err := m.Client.Create(context, obj, options...); err != nil {
30+
return err
31+
}
32+
m.createdResources.EnqueueIgnoreExisting(obj)
33+
return nil
34+
}
35+
36+
func (m *E2EKubeClient) Update(context context.Context, obj k8scontrollerclient.Object, options ...k8scontrollerclient.UpdateOption) error {
37+
m.annotateTestResource(obj)
38+
if err := m.Client.Update(context, obj, options...); err != nil {
39+
return err
40+
}
41+
m.createdResources.EnqueueIgnoreExisting(obj)
42+
return nil
43+
}
44+
45+
func (m *E2EKubeClient) Delete(context context.Context, obj k8scontrollerclient.Object, options ...k8scontrollerclient.DeleteOption) error {
46+
if err := m.Client.Delete(context, obj, options...); err != nil {
47+
return err
48+
}
49+
m.createdResources.RemoveIgnoreNotFound(obj)
50+
return nil
51+
}
52+
53+
func (m *E2EKubeClient) Reset() error {
54+
for {
55+
obj, ok := m.createdResources.DequeueTail()
56+
57+
if !ok {
58+
break
59+
}
60+
61+
if err := m.Delete(context.TODO(), obj); err != nil && !k8serror.IsNotFound(err) {
62+
return err
63+
}
64+
}
65+
return nil
66+
}
67+
68+
func (m *E2EKubeClient) annotateTestResource(obj k8scontrollerclient.Object) {
69+
annotations := obj.GetAnnotations()
70+
if annotations == nil {
71+
annotations = make(map[string]string)
72+
}
73+
annotations[E2ETestNameTag] = ginkgo.CurrentGinkgoTestDescription().FullTestText
74+
obj.SetAnnotations(annotations)
75+
}

test/e2e/util/resource_queue.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package util
2+
3+
import (
4+
"fmt"
5+
k8scontrollerclient "sigs.k8s.io/controller-runtime/pkg/client"
6+
"sync"
7+
)
8+
9+
type ResourceQueue struct {
10+
queue []k8scontrollerclient.Object
11+
lookupTable map[k8scontrollerclient.Object]bool
12+
lock *sync.Mutex
13+
}
14+
15+
func NewResourceQueue() *ResourceQueue {
16+
return &ResourceQueue{
17+
queue: []k8scontrollerclient.Object{},
18+
lookupTable: map[k8scontrollerclient.Object]bool{},
19+
lock: &sync.Mutex{},
20+
}
21+
}
22+
23+
func (q *ResourceQueue) Enqueue(obj k8scontrollerclient.Object) error {
24+
q.lock.Lock()
25+
defer q.lock.Unlock()
26+
27+
if _, ok := q.lookupTable[obj]; ok {
28+
return fmt.Errorf("error inserting duplicate object: %s", obj)
29+
}
30+
q.queue = append(q.queue, obj)
31+
q.lookupTable[obj] = true
32+
return nil
33+
}
34+
35+
func (q *ResourceQueue) EnqueueIgnoreExisting(obj k8scontrollerclient.Object) {
36+
q.lock.Lock()
37+
defer q.lock.Unlock()
38+
39+
if _, ok := q.lookupTable[obj]; ok {
40+
return
41+
}
42+
q.queue = append(q.queue, obj)
43+
q.lookupTable[obj] = true
44+
}
45+
46+
func (q *ResourceQueue) Length() int {
47+
return len(q.queue)
48+
}
49+
50+
func (q *ResourceQueue) RemoveIgnoreNotFound(obj k8scontrollerclient.Object) {
51+
q.lock.Lock()
52+
defer q.lock.Unlock()
53+
54+
if _, ok := q.lookupTable[obj]; ok {
55+
for index, existingObj := range q.queue {
56+
if q.equals(existingObj, obj) {
57+
_ = q.removeItem(index)
58+
delete(q.lookupTable, obj)
59+
return
60+
}
61+
}
62+
}
63+
}
64+
65+
func (q *ResourceQueue) equals(objOne k8scontrollerclient.Object, objTwo k8scontrollerclient.Object) bool {
66+
return objOne.GetName() == objTwo.GetName() && objOne.GetNamespace() == objTwo.GetNamespace()
67+
}
68+
69+
func (q *ResourceQueue) DequeueHead() (k8scontrollerclient.Object, bool) {
70+
q.lock.Lock()
71+
defer q.lock.Unlock()
72+
73+
if q.Length() == 0 {
74+
return nil, false
75+
}
76+
return q.removeItem(0), true
77+
}
78+
79+
func (q *ResourceQueue) DequeueTail() (k8scontrollerclient.Object, bool) {
80+
q.lock.Lock()
81+
defer q.lock.Unlock()
82+
83+
if len(q.queue) == 0 {
84+
return nil, false
85+
}
86+
return q.removeItem(q.Length() - 1), true
87+
}
88+
89+
func (q *ResourceQueue) removeItem(index int) k8scontrollerclient.Object {
90+
if index < 0 || index >= q.Length() {
91+
panic("index out of bounds")
92+
}
93+
item := q.queue[index]
94+
copy(q.queue[index:], q.queue[index+1:])
95+
q.queue[len(q.queue)-1] = nil
96+
q.queue = q.queue[:len(q.queue)-1]
97+
delete(q.lookupTable, item)
98+
99+
return item
100+
}

0 commit comments

Comments
 (0)