Skip to content

Commit 3521cd0

Browse files
committed
Refactor and simplify e2e2
1 parent 17ac7f2 commit 3521cd0

File tree

3 files changed

+257
-113
lines changed

3 files changed

+257
-113
lines changed

test/e2e2/flexcluster_test.go

Lines changed: 35 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ import (
2323
. "github.com/onsi/ginkgo/v2"
2424
. "github.com/onsi/gomega"
2525
corev1 "k8s.io/api/core/v1"
26-
"k8s.io/apimachinery/pkg/api/meta"
2726
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2827
"sigs.k8s.io/controller-runtime/pkg/client"
2928

3029
nextapiv1 "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/nextapi/generated/v1"
3130
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/version"
32-
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/state"
3331
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/e2e2/flexsamples"
3432
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/control"
3533
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/utils"
3634
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/kube"
3735
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/operator"
36+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/resources"
37+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/testparams"
3838
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/yml"
3939
)
4040

@@ -43,15 +43,6 @@ const (
4343
GroupCRDName = "groups.atlas.generated.mongodb.com"
4444
)
4545

46-
// yamlPlaceholders holds all placeholder values for YAML template replacement.
47-
type yamlPlaceholders struct {
48-
GroupID string
49-
OrgID string
50-
GroupName string
51-
OperatorNamespace string
52-
CredentialsSecretName string
53-
}
54-
5546
var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func() {
5647
var ctx context.Context
5748
var kubeClient client.Client
@@ -61,7 +52,7 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
6152
var testGroup *nextapiv1.Group
6253
var groupID string
6354
var orgID string
64-
var sharedPlaceholders yamlPlaceholders
55+
var sharedTestParams *testparams.TestParams
6556

6657
_ = BeforeAll(func() {
6758
if !version.IsExperimental() {
@@ -86,34 +77,32 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
8677
Name: utils.RandomName("flex-shared-grp-ns"),
8778
}}
8879
Expect(kubeClient.Create(ctx, sharedGroupNamespace)).To(Succeed())
89-
copyCredentialsToNamespace(ctx, kubeClient, sharedGroupNamespace.Name)
80+
Expect(resources.CopyCredentialsToNamespace(ctx, kubeClient, DefaultGlobalCredentials, control.MustEnvVar("OPERATOR_NAMESPACE"), sharedGroupNamespace.Name, GinkGoFieldOwner)).To(Succeed())
9081
})
9182

9283
By("Create test Group", func() {
9384
groupName := utils.RandomName("flexcluster-test-group")
94-
// Set up shared placeholders for Group YAML template
95-
sharedPlaceholders = yamlPlaceholders{
96-
GroupName: groupName,
97-
OperatorNamespace: sharedGroupNamespace.Name,
98-
CredentialsSecretName: DefaultGlobalCredentials,
99-
OrgID: orgID,
100-
}
85+
// Set up shared test params for Group YAML template
86+
sharedTestParams = testparams.New(orgID, sharedGroupNamespace.Name, DefaultGlobalCredentials).
87+
WithGroupName(groupName)
10188
// Replace placeholders in the Group YAML template
102-
groupYAML := replaceYAMLPlaceholders(string(flexsamples.TestGroup), sharedPlaceholders)
89+
groupYAML := sharedTestParams.ReplaceYAML(string(flexsamples.TestGroup))
10390
objs := yml.MustParseObjects(strings.NewReader(groupYAML))
10491
Expect(len(objs)).To(Equal(1))
10592
testGroup = objs[0].(*nextapiv1.Group)
10693
Expect(kubeClient.Create(ctx, testGroup)).To(Succeed())
10794
})
10895

10996
By("Wait for Group to be Ready and get its ID", func() {
110-
waitForResourceReady(ctx, kubeClient, testGroup)
97+
Eventually(func(g Gomega) {
98+
g.Expect(resources.CheckResourceReady(ctx, kubeClient, testGroup)).To(Succeed())
99+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).Should(Succeed())
111100
Expect(testGroup.Status.V20250312).NotTo(BeNil())
112101
Expect(testGroup.Status.V20250312.Id).NotTo(BeNil())
113102
groupID = *testGroup.Status.V20250312.Id
114103
Expect(groupID).NotTo(BeEmpty())
115-
// Update shared placeholders with groupID now that it's available
116-
sharedPlaceholders.GroupID = groupID
104+
// Update shared test params with groupID now that it's available
105+
sharedTestParams = sharedTestParams.WithGroupID(groupID)
117106
})
118107
})
119108

@@ -165,30 +154,33 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
165154
// Generate randomized group name for this test run (cluster names are unique per group)
166155
groupName := utils.RandomName("flex-grp")
167156

168-
// Set up placeholders for this test case (reuse shared values, override groupName)
169-
testPlaceholders := sharedPlaceholders
170-
testPlaceholders.GroupName = groupName
157+
// Set up test params for this test case (reuse shared values, override groupName)
158+
testParams := sharedTestParams.WithGroupName(groupName)
171159

172160
// Track created objects for cleanup
173161
var createdObjects []client.Object
174162

175163
By("Copy credentials secret to test namespace", func() {
176-
copyCredentialsToNamespace(ctx, kubeClient, testNamespace.Name)
164+
Expect(resources.CopyCredentialsToNamespace(ctx, kubeClient, DefaultGlobalCredentials, control.MustEnvVar("OPERATOR_NAMESPACE"), testNamespace.Name, GinkGoFieldOwner)).To(Succeed())
177165
})
178166

179167
By("Create resources from YAML", func() {
180-
objs := applyYAMLToNamespace(ctx, kubeClient, createYAML, testPlaceholders, testNamespace.Name)
168+
objs, err := resources.ApplyYAMLToNamespace(ctx, kubeClient, createYAML, testParams, testNamespace.Name, GinkGoFieldOwner)
169+
Expect(err).NotTo(HaveOccurred())
181170
createdObjects = append(createdObjects, objs...)
182171
})
183172

184173
By("Wait for Group to be Ready (if using groupRef)", func() {
185-
createYAMLStr := replaceYAMLPlaceholders(string(createYAML), testPlaceholders)
174+
createYAMLStr := testParams.ReplaceYAML(string(createYAML))
186175
objs := yml.MustParseObjects(strings.NewReader(createYAMLStr))
187176
for _, obj := range objs {
188177
if group, ok := obj.(*nextapiv1.Group); ok {
189-
waitForResourceReady(ctx, kubeClient, &nextapiv1.Group{
178+
groupObj := &nextapiv1.Group{
190179
ObjectMeta: metav1.ObjectMeta{Name: group.Name, Namespace: testNamespace.Name},
191-
})
180+
}
181+
Eventually(func(g Gomega) {
182+
g.Expect(resources.CheckResourceReady(ctx, kubeClient, groupObj)).To(Succeed())
183+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).Should(Succeed())
192184
}
193185
}
194186
})
@@ -198,7 +190,9 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
198190
}
199191

200192
By("Wait for FlexCluster to be Ready", func() {
201-
waitForResourceReady(ctx, kubeClient, &cluster)
193+
Eventually(func(g Gomega) {
194+
g.Expect(resources.CheckResourceReady(ctx, kubeClient, &cluster)).To(Succeed())
195+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).Should(Succeed())
202196
})
203197

204198
By("Verify cluster was created", func() {
@@ -209,13 +203,16 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
209203

210204
By("Update FlexCluster", func() {
211205
if len(updateYAML) > 0 {
212-
applyYAMLToNamespace(ctx, kubeClient, updateYAML, testPlaceholders, testNamespace.Name)
206+
_, err := resources.ApplyYAMLToNamespace(ctx, kubeClient, updateYAML, testParams, testNamespace.Name, GinkGoFieldOwner)
207+
Expect(err).NotTo(HaveOccurred())
213208
}
214209
})
215210

216211
By("Wait for FlexCluster to be Ready & updated", func() {
217212
if len(updateYAML) > 0 {
218-
waitForResourceUpdated(ctx, kubeClient, &cluster)
213+
Eventually(func(g Gomega) {
214+
g.Expect(resources.CheckResourceUpdated(ctx, kubeClient, &cluster)).To(Succeed())
215+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).Should(Succeed())
219216
}
220217
})
221218

@@ -227,7 +224,9 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
227224

228225
By("Wait for all resources to be deleted", func() {
229226
for _, obj := range createdObjects {
230-
waitForResourceDeleted(ctx, kubeClient, obj)
227+
Eventually(func(g Gomega) {
228+
g.Expect(resources.CheckResourceDeleted(ctx, kubeClient, obj)).To(Succeed())
229+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).Should(Succeed())
231230
}
232231
})
233232
},
@@ -243,80 +242,3 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
243242
),
244243
)
245244
})
246-
247-
// replaceYAMLPlaceholders replaces placeholders in YAML templates with actual values from the struct.
248-
func replaceYAMLPlaceholders(yaml string, p yamlPlaceholders) string {
249-
result := yaml
250-
result = strings.ReplaceAll(result, "__GROUP_ID__", p.GroupID)
251-
result = strings.ReplaceAll(result, "__ORG_ID__", p.OrgID)
252-
result = strings.ReplaceAll(result, "__GROUP_NAME__", p.GroupName)
253-
result = strings.ReplaceAll(result, "__OPERATOR_NAMESPACE__", p.OperatorNamespace)
254-
result = strings.ReplaceAll(result, "__CREDENTIALS_SECRET_NAME__", p.CredentialsSecretName)
255-
return result
256-
}
257-
258-
// copyCredentialsToNamespace copies the default global credentials secret to the specified namespace.
259-
func copyCredentialsToNamespace(ctx context.Context, kubeClient client.Client, namespace string) {
260-
globalCredsKey := client.ObjectKey{
261-
Name: DefaultGlobalCredentials,
262-
Namespace: control.MustEnvVar("OPERATOR_NAMESPACE"),
263-
}
264-
credentialsSecret, err := copySecretToNamespace(ctx, kubeClient, globalCredsKey, namespace)
265-
Expect(err).NotTo(HaveOccurred())
266-
Expect(
267-
kubeClient.Patch(ctx, credentialsSecret, client.Apply, client.ForceOwnership, GinkGoFieldOwner),
268-
).To(Succeed())
269-
}
270-
271-
// applyYAMLToNamespace applies YAML objects to a namespace after replacing placeholders.
272-
// Returns the list of applied objects.
273-
func applyYAMLToNamespace(ctx context.Context, kubeClient client.Client, yaml []byte, placeholders yamlPlaceholders, namespace string) []client.Object {
274-
yamlStr := replaceYAMLPlaceholders(string(yaml), placeholders)
275-
objs := yml.MustParseObjects(strings.NewReader(yamlStr))
276-
for _, obj := range objs {
277-
obj.SetNamespace(namespace)
278-
Expect(
279-
kubeClient.Patch(ctx, obj, client.Apply, client.ForceOwnership, GinkGoFieldOwner),
280-
).To(Succeed())
281-
}
282-
return objs
283-
}
284-
285-
// waitForResourceReady waits for a resource to have Ready condition set to True.
286-
func waitForResourceReady(ctx context.Context, kubeClient client.Client, obj kube.ObjectWithStatus) {
287-
Eventually(func(g Gomega) bool {
288-
g.Expect(
289-
kubeClient.Get(ctx, client.ObjectKeyFromObject(obj), obj),
290-
).To(Succeed())
291-
if condition := meta.FindStatusCondition(obj.GetConditions(), "Ready"); condition != nil {
292-
return condition.Status == metav1.ConditionTrue
293-
}
294-
return false
295-
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).To(BeTrue())
296-
}
297-
298-
// waitForResourceUpdated waits for a resource to be Ready and in Updated state.
299-
func waitForResourceUpdated(ctx context.Context, kubeClient client.Client, obj kube.ObjectWithStatus) {
300-
Eventually(func(g Gomega) bool {
301-
g.Expect(
302-
kubeClient.Get(ctx, client.ObjectKeyFromObject(obj), obj),
303-
).To(Succeed())
304-
ready := false
305-
if condition := meta.FindStatusCondition(obj.GetConditions(), "Ready"); condition != nil {
306-
ready = (condition.Status == metav1.ConditionTrue)
307-
}
308-
if ready {
309-
if condition := meta.FindStatusCondition(obj.GetConditions(), "State"); condition != nil {
310-
return state.ResourceState(condition.Reason) == state.StateUpdated
311-
}
312-
}
313-
return false
314-
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).To(BeTrue())
315-
}
316-
317-
// waitForResourceDeleted waits for a resource to be deleted from the cluster.
318-
func waitForResourceDeleted(ctx context.Context, kubeClient client.Client, obj client.Object) {
319-
Eventually(func(g Gomega) error {
320-
return kubeClient.Get(ctx, client.ObjectKeyFromObject(obj), obj)
321-
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).ShouldNot(Succeed())
322-
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright 2025 MongoDB Inc
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 resources
16+
17+
import (
18+
"context"
19+
"errors"
20+
"fmt"
21+
"strings"
22+
23+
corev1 "k8s.io/api/core/v1"
24+
"k8s.io/apimachinery/pkg/api/meta"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
28+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/state"
29+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/kube"
30+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/testparams"
31+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/yml"
32+
)
33+
34+
// CopySecretToNamespace copies a secret from one namespace to another.
35+
// Returns the copied secret ready to be applied to the target namespace.
36+
func CopySecretToNamespace(ctx context.Context, kubeClient client.Client, key client.ObjectKey, targetNamespace string) (*corev1.Secret, error) {
37+
secret := corev1.Secret{}
38+
if err := kubeClient.Get(ctx, key, &secret); err != nil {
39+
return nil, fmt.Errorf("failed to load original secret %v: %w", key, err)
40+
}
41+
return &corev1.Secret{
42+
TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"},
43+
ObjectMeta: metav1.ObjectMeta{
44+
Name: key.Name,
45+
Namespace: targetNamespace,
46+
Labels: secret.Labels,
47+
},
48+
Data: secret.Data,
49+
}, nil
50+
}
51+
52+
// CopyCredentialsToNamespace copies the credentials secret from the operator namespace
53+
// to the target namespace. The secret is applied with the specified field owner.
54+
func CopyCredentialsToNamespace(ctx context.Context, kubeClient client.Client, credentialsName, operatorNamespace, targetNamespace string, fieldOwner client.FieldOwner) error {
55+
globalCredsKey := client.ObjectKey{
56+
Name: credentialsName,
57+
Namespace: operatorNamespace,
58+
}
59+
credentialsSecret, err := CopySecretToNamespace(ctx, kubeClient, globalCredsKey, targetNamespace)
60+
if err != nil {
61+
return err
62+
}
63+
return kubeClient.Patch(ctx, credentialsSecret, client.Apply, client.ForceOwnership, fieldOwner)
64+
}
65+
66+
// ApplyYAMLToNamespace applies YAML objects to a namespace after replacing placeholders.
67+
// Returns the list of applied objects.
68+
func ApplyYAMLToNamespace(ctx context.Context, kubeClient client.Client, yaml []byte, params *testparams.TestParams, namespace string, fieldOwner client.FieldOwner) ([]client.Object, error) {
69+
yamlStr := params.ReplaceYAML(string(yaml))
70+
objs := yml.MustParseObjects(strings.NewReader(yamlStr))
71+
for _, obj := range objs {
72+
obj.SetNamespace(namespace)
73+
if err := kubeClient.Patch(ctx, obj, client.Apply, client.ForceOwnership, fieldOwner); err != nil {
74+
return nil, fmt.Errorf("failed to apply object %s/%s: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err)
75+
}
76+
}
77+
return objs, nil
78+
}
79+
80+
var (
81+
// ErrResourceNotReady indicates the resource is not in Ready state
82+
ErrResourceNotReady = errors.New("resource is not ready")
83+
// ErrResourceNotUpdated indicates the resource is not in Updated state
84+
ErrResourceNotUpdated = errors.New("resource is not updated")
85+
// ErrResourceNotDeleted indicates the resource still exists
86+
ErrResourceNotDeleted = errors.New("resource still exists")
87+
)
88+
89+
// CheckResourceReady checks if a resource has Ready condition set to True.
90+
// Returns nil if ready, ErrResourceNotReady if not ready, or an error if the resource cannot be fetched.
91+
func CheckResourceReady(ctx context.Context, kubeClient client.Client, obj kube.ObjectWithStatus) error {
92+
key := client.ObjectKeyFromObject(obj)
93+
if err := kubeClient.Get(ctx, key, obj); err != nil {
94+
return fmt.Errorf("failed to get resource %s/%s: %w", obj.GetNamespace(), obj.GetName(), err)
95+
}
96+
if condition := meta.FindStatusCondition(obj.GetConditions(), "Ready"); condition != nil {
97+
if condition.Status == metav1.ConditionTrue {
98+
return nil
99+
}
100+
}
101+
return ErrResourceNotReady
102+
}
103+
104+
// CheckResourceUpdated checks if a resource is Ready and in Updated state.
105+
// Returns nil if updated, ErrResourceNotUpdated if not updated, or an error if the resource cannot be fetched.
106+
func CheckResourceUpdated(ctx context.Context, kubeClient client.Client, obj kube.ObjectWithStatus) error {
107+
key := client.ObjectKeyFromObject(obj)
108+
if err := kubeClient.Get(ctx, key, obj); err != nil {
109+
return fmt.Errorf("failed to get resource %s/%s: %w", obj.GetNamespace(), obj.GetName(), err)
110+
}
111+
ready := false
112+
if condition := meta.FindStatusCondition(obj.GetConditions(), "Ready"); condition != nil {
113+
ready = (condition.Status == metav1.ConditionTrue)
114+
}
115+
if !ready {
116+
return ErrResourceNotUpdated
117+
}
118+
if condition := meta.FindStatusCondition(obj.GetConditions(), "State"); condition != nil {
119+
if state.ResourceState(condition.Reason) == state.StateUpdated {
120+
return nil
121+
}
122+
}
123+
return ErrResourceNotUpdated
124+
}
125+
126+
// CheckResourceDeleted checks if a resource has been deleted from the cluster.
127+
// Returns nil if deleted, ErrResourceNotDeleted if still exists, or an error if the check fails.
128+
func CheckResourceDeleted(ctx context.Context, kubeClient client.Client, obj client.Object) error {
129+
key := client.ObjectKeyFromObject(obj)
130+
if err := kubeClient.Get(ctx, key, obj); err != nil {
131+
// Resource not found means it's deleted - success!
132+
if client.IgnoreNotFound(err) == nil {
133+
return nil
134+
}
135+
// Other errors are unexpected
136+
return fmt.Errorf("failed to check if resource %s/%s is deleted: %w", obj.GetNamespace(), obj.GetName(), err)
137+
}
138+
return ErrResourceNotDeleted
139+
}

0 commit comments

Comments
 (0)