Skip to content

Commit 02f5acd

Browse files
authored
Rework existing e2e test setup (#516)
* Simplify komega usage in existing e2e tests * Split into multiple ordered containers * Retry secret creation * Use dedicated namespaces for e2e tests
1 parent 76560e1 commit 02f5acd

File tree

5 files changed

+178
-84
lines changed

5 files changed

+178
-84
lines changed

cmd/checksum-controller/main.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ This example sharded controller is also useful for developing the sharding compo
8080
type options struct {
8181
zapOptions *zap.Options
8282
controllerRingName string
83+
namespace string
8384
leaseNamespace string
8485
shardName string
8586
}
@@ -92,11 +93,13 @@ func newOptions() *options {
9293
},
9394

9495
controllerRingName: "checksum-controller",
96+
namespace: metav1.NamespaceDefault,
9597
}
9698
}
9799

98100
func (o *options) AddFlags(fs *pflag.FlagSet) {
99101
fs.StringVar(&o.controllerRingName, "controllerring", o.controllerRingName, "Name of the ControllerRing the shard belongs to.")
102+
fs.StringVar(&o.namespace, "namespace", o.namespace, "Namespace to watch objects in.")
100103
fs.StringVar(&o.leaseNamespace, "lease-namespace", o.leaseNamespace, "Namespace to use for the shard lease. Defaults to the pod's namespace if running in-cluster.")
101104
fs.StringVar(&o.shardName, "shard-name", o.shardName, "Name of the shard. Defaults to the instance's hostname.")
102105

@@ -153,8 +156,8 @@ func (o *options) run(ctx context.Context) error {
153156

154157
// FILTERED WATCH CACHE
155158
Cache: cache.Options{
156-
// This controller only acts on objects in the default namespace.
157-
DefaultNamespaces: map[string]cache.Config{metav1.NamespaceDefault: {}},
159+
// This controller only acts on objects in a single configured namespace.
160+
DefaultNamespaces: map[string]cache.Config{o.namespace: {}},
158161
// Configure cache to only watch objects that are assigned to this shard.
159162
// This controller only watches sharded objects, so we can configure the label selector on the cache's global level.
160163
// If your controller watches sharded objects as well as non-sharded objects, use cache.Options.ByObject to configure

hack/config/checksum-controller/controller/deployment.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ spec:
1313
- name: checksum-controller
1414
image: checksum-controller:latest
1515
args:
16-
- --controllerring=checksum-controller
1716
- --zap-log-level=debug
1817
env:
1918
- name: DISABLE_HTTP2

pkg/utils/test/matchers/condition.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package matchers
1818

1919
import (
2020
. "github.com/onsi/gomega"
21-
"github.com/onsi/gomega/gstruct"
2221
gomegatypes "github.com/onsi/gomega/types"
2322
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2423
)
@@ -35,28 +34,20 @@ var MatchCondition = And
3534

3635
// OfType returns a matcher for checking whether a condition has a certain type.
3736
func OfType(conditionType string) gomegatypes.GomegaMatcher {
38-
return gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
39-
"Type": Equal(conditionType),
40-
})
37+
return HaveField("Type", Equal(conditionType))
4138
}
4239

4340
// WithStatus returns a matcher for checking whether a condition has a certain status.
4441
func WithStatus(status metav1.ConditionStatus) gomegatypes.GomegaMatcher {
45-
return gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
46-
"Status": Equal(status),
47-
})
42+
return HaveField("Status", Equal(status))
4843
}
4944

5045
// WithReason returns a matcher for checking whether a condition has a certain reason.
5146
func WithReason(reason string) gomegatypes.GomegaMatcher {
52-
return gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
53-
"Reason": Equal(reason),
54-
})
47+
return HaveField("Reason", Equal(reason))
5548
}
5649

5750
// WithMessage returns a matcher for checking whether a condition has a certain message.
5851
func WithMessage(message string) gomegatypes.GomegaMatcher {
59-
return gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
60-
"Message": ContainSubstring(message),
61-
})
52+
return HaveField("Message", ContainSubstring(message))
6253
}

test/e2e/e2e_suite_test.go

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,19 @@ package e2e
1818

1919
import (
2020
"context"
21+
"fmt"
22+
"maps"
2123
"testing"
2224
"time"
2325

2426
"github.com/go-logr/logr"
2527
. "github.com/onsi/ginkgo/v2"
2628
. "github.com/onsi/gomega"
29+
appsv1 "k8s.io/api/apps/v1"
30+
corev1 "k8s.io/api/core/v1"
31+
rbacv1 "k8s.io/api/rbac/v1"
32+
"k8s.io/apimachinery/pkg/api/meta"
33+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2734
"k8s.io/apimachinery/pkg/runtime"
2835
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
2936
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -32,6 +39,8 @@ import (
3239
"sigs.k8s.io/controller-runtime/pkg/log/zap"
3340

3441
shardingv1alpha1 "github.com/timebertt/kubernetes-controller-sharding/pkg/apis/sharding/v1alpha1"
42+
"github.com/timebertt/kubernetes-controller-sharding/pkg/utils/test"
43+
. "github.com/timebertt/kubernetes-controller-sharding/pkg/utils/test/matchers"
3544
)
3645

3746
func TestE2E(t *testing.T) {
@@ -40,17 +49,23 @@ func TestE2E(t *testing.T) {
4049
}
4150

4251
const (
43-
ShortTimeout = 10 * time.Second
52+
testID = "e2e-controller-sharding"
53+
54+
ShortTimeout = 10 * time.Second
55+
MediumTimeout = time.Minute
4456
)
4557

4658
var (
4759
log logr.Logger
4860

4961
testClient client.Client
62+
63+
testRunID string
64+
testRunLabels map[string]string
5065
)
5166

5267
var _ = BeforeSuite(func() {
53-
log = zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter))
68+
log = zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)).WithName(testID)
5469

5570
restConfig, err := config.GetConfig()
5671
Expect(err).NotTo(HaveOccurred())
@@ -69,4 +84,87 @@ var _ = BeforeSuite(func() {
6984
komega.SetClient(testClient)
7085
komega.SetContext(clientContext)
7186
DeferCleanup(clientCancel)
87+
88+
testRunID = testID + "-" + test.RandomSuffix()
89+
testRunLabels = map[string]string{
90+
testID: testRunID,
91+
}
92+
log = log.WithValues("testRun", testRunID)
7293
})
94+
95+
var (
96+
controllerRing *shardingv1alpha1.ControllerRing
97+
namespace *corev1.Namespace
98+
)
99+
100+
var _ = BeforeEach(func(ctx SpecContext) {
101+
By("Set up test Namespace")
102+
namespace = &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
103+
GenerateName: testRunID + "-",
104+
Labels: maps.Clone(testRunLabels),
105+
}}
106+
107+
// We create a dedicated test namespace and clean it up for every test case to ensure a clean test environment.
108+
Expect(testClient.Create(ctx, namespace)).To(Succeed())
109+
log.Info("Created test Namespace", "namespace", namespace.Name)
110+
111+
DeferCleanup(func(ctx SpecContext) {
112+
By("Delete test Namespace")
113+
Eventually(ctx, func() error {
114+
return testClient.Delete(ctx, namespace)
115+
}).Should(Or(Succeed(), BeNotFoundError()))
116+
}, NodeTimeout(MediumTimeout))
117+
118+
By("Set up test ControllerRing")
119+
// Deploy a dedicated ControllerRing instance for this test case
120+
defaultControllerRing := &shardingv1alpha1.ControllerRing{ObjectMeta: metav1.ObjectMeta{Name: "checksum-controller"}}
121+
Expect(komega.Get(defaultControllerRing)()).To(Succeed())
122+
123+
controllerRing = defaultControllerRing.DeepCopy()
124+
controllerRing.Name = namespace.Name
125+
controllerRing.ResourceVersion = ""
126+
maps.Copy(controllerRing.Labels, testRunLabels)
127+
controllerRing.Spec.NamespaceSelector.MatchLabels[corev1.LabelMetadataName] = namespace.Name
128+
Expect(testClient.Create(ctx, controllerRing)).To(Succeed())
129+
130+
DeferCleanup(func(ctx SpecContext) {
131+
By("Delete test ControllerRing")
132+
Eventually(ctx, func() error {
133+
return testClient.Delete(ctx, controllerRing)
134+
}).Should(Or(Succeed(), BeNotFoundError()))
135+
}, NodeTimeout(ShortTimeout))
136+
137+
By("Set up test controller")
138+
// Deploy a dedicated controller instance to this test case's namespace.
139+
// Copy all relevant objects from the default namespace.
140+
for _, objList := range []client.ObjectList{
141+
&appsv1.DeploymentList{},
142+
&corev1.ServiceAccountList{},
143+
&rbacv1.RoleList{},
144+
&rbacv1.RoleBindingList{},
145+
} {
146+
Expect(testClient.List(ctx, objList, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{"app.kubernetes.io/component": "checksum-controller"})).
147+
Should(Succeed(), "should list %T in default namespace", objList)
148+
149+
Expect(meta.EachListItem(objList, func(object runtime.Object) error {
150+
obj := object.DeepCopyObject().(client.Object)
151+
obj.SetNamespace(namespace.Name)
152+
obj.SetResourceVersion("")
153+
154+
switch o := obj.(type) {
155+
case *appsv1.Deployment:
156+
o.Spec.Template.Spec.Containers[0].Args = append(o.Spec.Template.Spec.Containers[0].Args,
157+
"--controllerring="+controllerRing.Name,
158+
"--namespace="+namespace.Name,
159+
)
160+
case *rbacv1.RoleBinding:
161+
o.Subjects[0].Namespace = namespace.Name
162+
}
163+
164+
if err := testClient.Create(ctx, obj); err != nil {
165+
return fmt.Errorf("error copying object %T %q to %s namespace: %w", obj, client.ObjectKeyFromObject(obj), namespace.Name, err)
166+
}
167+
return nil
168+
})).To(Succeed(), "should copy %T", objList)
169+
}
170+
}, NodeTimeout(MediumTimeout), OncePerOrdered)

0 commit comments

Comments
 (0)