Skip to content

Commit 19cbe80

Browse files
committed
CLOUDP-358677: Add Flex e2e2 test
Signed-off-by: jose.vazquez <[email protected]>
1 parent 45523cd commit 19cbe80

File tree

10 files changed

+424
-3
lines changed

10 files changed

+424
-3
lines changed

Makefile

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,10 +491,13 @@ post-install-hook:
491491
x509-cert: ## Create X.509 cert at path tmp/x509/ (see docs/x509-user.md)
492492
go run scripts/create_x509.go
493493

494-
clean: ## Clean built binaries
494+
.PHONY: clean-gen-crds
495+
clean-gen-crds: ## Clean only generated CRD files
496+
rm -f config/generated/crd/bases/crds.yaml
497+
498+
clean: clean-gen-crds ## Clean built binaries
495499
rm -rf bin/*
496500
rm -rf config/manifests/bases/
497-
rm -f config/generated/crd/bases/crds.yaml
498501
rm -f config/crd/bases/*.yaml
499502
rm -f helm-charts/atlas-operator-crds/templates/*.yaml
500503
rm -f config/rbac/clusterwide/role.yaml
@@ -607,7 +610,7 @@ clear-e2e-leftovers: ## Clear the e2e test leftovers quickly
607610
install-crds: manifests ## Install CRDs in Kubernetes
608611
kubectl apply -k config/crd
609612
ifdef EXPERIMENTAL
610-
$(MAKE) clean gen-crds
613+
$(MAKE) regen-crds
611614
kubectl apply -f config/generated/crd/bases/crds.yaml
612615
endif
613616

@@ -881,6 +884,9 @@ gen-crds: tools/openapi2crd/bin/openapi2crd
881884
--output $(realpath .)/config/generated/crd/bases/crds.yaml
882885
cp $(realpath .)/config/generated/crd/bases/crds.yaml $(realpath .)/internal/generated/crds/crds.yaml
883886

887+
.PHONY: regen-crds
888+
regen-crds: clean-gen-crds gen-crds ## Clean and regenerate CRDs
889+
884890
gen-go-types:
885891
@echo "==> Generating Go models from CRDs..."
886892
$(CRD2GO) --input $(realpath .)/config/generated/crd/bases/crds.yaml \

test/e2e2/e2e2_suite_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func initTestLogging(t *testing.T) {
7575
ctrllog.SetLogger(logrLogger.WithName("test"))
7676
}
7777

78+
// nolint:unparam
7879
func runTestAKO(globalCreds, ns string, deletionprotection bool) operator.Operator {
7980
args := []string{
8081
"--log-level=-9",

test/e2e2/flexcluster_test.go

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
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 e2e2_test
16+
17+
import (
18+
"context"
19+
"os"
20+
"strings"
21+
"time"
22+
23+
. "github.com/onsi/ginkgo/v2"
24+
. "github.com/onsi/gomega"
25+
corev1 "k8s.io/api/core/v1"
26+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
27+
"k8s.io/apimachinery/pkg/api/meta"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
31+
32+
nextapiv1 "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/nextapi/generated/v1"
33+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/version"
34+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/state"
35+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/e2e2/flexsamples"
36+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/control"
37+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/utils"
38+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/kube"
39+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/operator"
40+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/yml"
41+
)
42+
43+
const (
44+
FlexClusterCRDName = "flexclusters.atlas.generated.mongodb.com"
45+
GroupCRDName = "groups.atlas.generated.mongodb.com"
46+
)
47+
48+
var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func() {
49+
var ctx context.Context
50+
var kubeClient client.Client
51+
var ako operator.Operator
52+
var testNamespace *corev1.Namespace
53+
var testGroup *nextapiv1.Group
54+
var groupID string
55+
var orgID string
56+
57+
_ = BeforeAll(func() {
58+
if !version.IsExperimental() {
59+
Skip("FlexCluster is an experimental CRD and controller. Skipping test as experimental features are not enabled.")
60+
}
61+
62+
orgID = os.Getenv("MCLI_ORG_ID")
63+
Expect(orgID).NotTo(BeEmpty(), "MCLI_ORG_ID environment variable must be set")
64+
65+
deletionProtectionOff := false
66+
ako = runTestAKO(DefaultGlobalCredentials, control.MustEnvVar("OPERATOR_NAMESPACE"), deletionProtectionOff)
67+
ako.Start(GinkgoT())
68+
69+
ctx = context.Background()
70+
testClient, err := kube.NewTestClient()
71+
Expect(err).To(Succeed())
72+
kubeClient = testClient
73+
Expect(kube.AssertCRDs(ctx, kubeClient,
74+
&apiextensionsv1.CustomResourceDefinition{
75+
ObjectMeta: v1.ObjectMeta{Name: FlexClusterCRDName},
76+
},
77+
&apiextensionsv1.CustomResourceDefinition{
78+
ObjectMeta: v1.ObjectMeta{Name: GroupCRDName},
79+
},
80+
)).To(Succeed())
81+
82+
By("Create test Group", func() {
83+
operatorNamespace := control.MustEnvVar("OPERATOR_NAMESPACE")
84+
groupName := utils.RandomName("flexcluster-test-group")
85+
// Replace placeholders in the Group YAML template
86+
groupYAML := strings.ReplaceAll(string(flexsamples.TestGroup), "__GROUP_NAME__", groupName)
87+
groupYAML = strings.ReplaceAll(groupYAML, "__OPERATOR_NAMESPACE__", operatorNamespace)
88+
groupYAML = strings.ReplaceAll(groupYAML, "__CREDENTIALS_SECRET_NAME__", DefaultGlobalCredentials)
89+
groupYAML = strings.ReplaceAll(groupYAML, "__ORG_ID__", orgID)
90+
objs := yml.MustParseObjects(strings.NewReader(groupYAML))
91+
Expect(len(objs)).To(Equal(1))
92+
testGroup = objs[0].(*nextapiv1.Group)
93+
Expect(kubeClient.Create(ctx, testGroup)).To(Succeed())
94+
})
95+
96+
By("Wait for Group to be Ready and get its ID", func() {
97+
Eventually(func(g Gomega) bool {
98+
g.Expect(
99+
kubeClient.Get(ctx, client.ObjectKeyFromObject(testGroup), testGroup),
100+
).To(Succeed())
101+
if condition := meta.FindStatusCondition(testGroup.GetConditions(), "Ready"); condition != nil {
102+
if condition.Status == metav1.ConditionTrue {
103+
if testGroup.Status.V20250312 != nil && testGroup.Status.V20250312.Id != nil {
104+
groupID = *testGroup.Status.V20250312.Id
105+
return true
106+
}
107+
}
108+
}
109+
return false
110+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).To(BeTrue())
111+
Expect(groupID).NotTo(BeEmpty())
112+
})
113+
})
114+
115+
_ = AfterAll(func() {
116+
if kubeClient != nil && testGroup != nil {
117+
By("Clean up test Group", func() {
118+
Expect(kubeClient.Delete(ctx, testGroup)).To(Succeed())
119+
Eventually(func(g Gomega) error {
120+
err := kubeClient.Get(ctx, client.ObjectKeyFromObject(testGroup), testGroup)
121+
return err
122+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).NotTo(Succeed())
123+
})
124+
}
125+
if ako != nil {
126+
ako.Stop(GinkgoT())
127+
}
128+
})
129+
130+
_ = BeforeEach(func() {
131+
testNamespace = &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
132+
Name: utils.RandomName("flexcluster-ctlr-ns"),
133+
}}
134+
Expect(kubeClient.Create(ctx, testNamespace)).To(Succeed())
135+
Expect(ako.Running()).To(BeTrue(), "Operator must be running")
136+
})
137+
138+
_ = AfterEach(func() {
139+
if kubeClient == nil {
140+
return
141+
}
142+
Expect(
143+
kubeClient.Delete(ctx, testNamespace),
144+
).To(Succeed())
145+
Eventually(func(g Gomega) bool {
146+
return kubeClient.Get(ctx, client.ObjectKeyFromObject(testNamespace), testNamespace) == nil
147+
}).WithTimeout(time.Minute).WithPolling(time.Second).To(BeFalse())
148+
})
149+
150+
DescribeTable("FlexCluster CRUD lifecycle",
151+
func(createYAML, updateYAML []byte, clusterName string) {
152+
By("Copy credentials secret to test namespace", func() {
153+
globalCredsKey := client.ObjectKey{
154+
Name: DefaultGlobalCredentials,
155+
Namespace: control.MustEnvVar("OPERATOR_NAMESPACE"),
156+
}
157+
credentialsSecret, err := copySecretToNamespace(ctx, kubeClient, globalCredsKey, testNamespace.Name)
158+
Expect(err).NotTo(HaveOccurred())
159+
Expect(
160+
kubeClient.Patch(ctx, credentialsSecret, client.Apply, client.ForceOwnership, GinkGoFieldOwner),
161+
).To(Succeed())
162+
})
163+
164+
By("Create resources from YAML", func() {
165+
// Replace placeholders with actual values
166+
createYAMLStr := strings.ReplaceAll(string(createYAML), "__GROUP_ID__", groupID)
167+
createYAMLStr = strings.ReplaceAll(createYAMLStr, "__ORG_ID__", orgID)
168+
objs := yml.MustParseObjects(strings.NewReader(createYAMLStr))
169+
for _, obj := range objs {
170+
objToApply := kube.WithRenamedNamespace(obj, testNamespace.Name)
171+
Expect(
172+
kubeClient.Patch(ctx, objToApply, client.Apply, client.ForceOwnership, GinkGoFieldOwner),
173+
).To(Succeed())
174+
}
175+
})
176+
177+
By("Wait for Group to be Ready (if using groupRef)", func() {
178+
createYAMLStr := strings.ReplaceAll(string(createYAML), "__GROUP_ID__", groupID)
179+
createYAMLStr = strings.ReplaceAll(createYAMLStr, "__ORG_ID__", orgID)
180+
objs := yml.MustParseObjects(strings.NewReader(createYAMLStr))
181+
for _, obj := range objs {
182+
if group, ok := obj.(*nextapiv1.Group); ok {
183+
groupInKube := nextapiv1.Group{
184+
ObjectMeta: metav1.ObjectMeta{Name: group.Name, Namespace: testNamespace.Name},
185+
}
186+
Eventually(func(g Gomega) bool {
187+
g.Expect(
188+
kubeClient.Get(ctx, client.ObjectKeyFromObject(&groupInKube), &groupInKube),
189+
).To(Succeed())
190+
if condition := meta.FindStatusCondition(groupInKube.GetConditions(), "Ready"); condition != nil {
191+
return condition.Status == metav1.ConditionTrue
192+
}
193+
return false
194+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).To(BeTrue())
195+
}
196+
}
197+
})
198+
199+
cluster := nextapiv1.FlexCluster{
200+
ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: testNamespace.Name},
201+
}
202+
203+
By("Wait for FlexCluster to be Ready", func() {
204+
Eventually(func(g Gomega) bool {
205+
g.Expect(
206+
kubeClient.Get(ctx, client.ObjectKeyFromObject(&cluster), &cluster),
207+
).To(Succeed())
208+
if condition := meta.FindStatusCondition(cluster.GetConditions(), "Ready"); condition != nil {
209+
return condition.Status == metav1.ConditionTrue
210+
}
211+
return false
212+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).To(BeTrue())
213+
})
214+
215+
By("Verify cluster was created", func() {
216+
Expect(cluster.Status.V20250312).NotTo(BeNil())
217+
Expect(cluster.Status.V20250312.Id).NotTo(BeNil())
218+
Expect(*cluster.Status.V20250312.Id).NotTo(BeEmpty())
219+
})
220+
221+
By("Update FlexCluster", func() {
222+
if len(updateYAML) > 0 {
223+
// Replace placeholders with actual values
224+
updateYAMLStr := strings.ReplaceAll(string(updateYAML), "__GROUP_ID__", groupID)
225+
updateYAMLStr = strings.ReplaceAll(updateYAMLStr, "__ORG_ID__", orgID)
226+
updateObjs := yml.MustParseObjects(strings.NewReader(updateYAMLStr))
227+
for _, obj := range updateObjs {
228+
objToPatch := kube.WithRenamedNamespace(obj, testNamespace.Name)
229+
Expect(
230+
kubeClient.Patch(ctx, objToPatch, client.Apply, client.ForceOwnership, GinkGoFieldOwner),
231+
).To(Succeed())
232+
}
233+
}
234+
})
235+
236+
By("Wait for FlexCluster to be Ready & updated", func() {
237+
if len(updateYAML) > 0 {
238+
Eventually(func(g Gomega) bool {
239+
g.Expect(
240+
kubeClient.Get(ctx, client.ObjectKeyFromObject(&cluster), &cluster),
241+
).To(Succeed())
242+
ready := false
243+
if condition := meta.FindStatusCondition(cluster.GetConditions(), "Ready"); condition != nil {
244+
ready = (condition.Status == metav1.ConditionTrue)
245+
}
246+
if ready {
247+
if condition := meta.FindStatusCondition(cluster.GetConditions(), "State"); condition != nil {
248+
return state.ResourceState(condition.Reason) == state.StateUpdated
249+
}
250+
}
251+
return false
252+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).To(BeTrue())
253+
}
254+
})
255+
256+
By("Delete FlexCluster", func() {
257+
Expect(kubeClient.Delete(ctx, &cluster)).To(Succeed())
258+
})
259+
260+
By("Wait for FlexCluster to be deleted", func() {
261+
Eventually(func(g Gomega) error {
262+
err := kubeClient.Get(ctx, client.ObjectKeyFromObject(&cluster), &cluster)
263+
return err
264+
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).NotTo(Succeed())
265+
})
266+
},
267+
Entry("With direct groupId",
268+
flexsamples.WithGroupIdCreate,
269+
flexsamples.WithGroupIdUpdate,
270+
"flexy",
271+
),
272+
Entry("With groupRef",
273+
flexsamples.WithGroupRefCreate,
274+
flexsamples.WithGroupRefUpdate,
275+
"flexy",
276+
),
277+
)
278+
})

test/e2e2/flexsamples/samples.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 flexsamples
16+
17+
import _ "embed"
18+
19+
//go:embed with_groupid_create.yaml
20+
var WithGroupIdCreate []byte
21+
22+
//go:embed with_groupid_update.yaml
23+
var WithGroupIdUpdate []byte
24+
25+
//go:embed with_groupref_create.yaml
26+
var WithGroupRefCreate []byte
27+
28+
//go:embed with_groupref_update.yaml
29+
var WithGroupRefUpdate []byte
30+
31+
//go:embed test_group.yaml
32+
var TestGroup []byte
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: atlas.generated.mongodb.com/v1
2+
kind: Group
3+
metadata:
4+
name: __GROUP_NAME__
5+
namespace: __OPERATOR_NAMESPACE__
6+
spec:
7+
connectionSecretRef:
8+
name: __CREDENTIALS_SECRET_NAME__
9+
v20250312:
10+
entry:
11+
orgId: __ORG_ID__
12+
name: __GROUP_NAME__
13+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: atlas.generated.mongodb.com/v1
2+
kind: FlexCluster
3+
metadata:
4+
name: flexy
5+
namespace: mongodb-atlas-system
6+
spec:
7+
connectionSecretRef:
8+
name: mongodb-atlas-operator-api-key
9+
v20250312:
10+
groupId: __GROUP_ID__
11+
entry:
12+
name: flexy
13+
terminationProtectionEnabled: true
14+
providerSettings:
15+
backingProviderName: GCP
16+
regionName: CENTRAL_US
17+

0 commit comments

Comments
 (0)