@@ -23,10 +23,8 @@ import (
2323 . "github.com/onsi/ginkgo/v2"
2424 . "github.com/onsi/gomega"
2525 corev1 "k8s.io/api/core/v1"
26- apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2726 "k8s.io/apimachinery/pkg/api/meta"
2827 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29- v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3028 "sigs.k8s.io/controller-runtime/pkg/client"
3129
3230 nextapiv1 "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/nextapi/generated/v1"
@@ -45,14 +43,25 @@ const (
4543 GroupCRDName = "groups.atlas.generated.mongodb.com"
4644)
4745
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+
4855var _ = Describe ("FlexCluster CRUD" , Ordered , Label ("flexcluster-ctlr" ), func () {
4956 var ctx context.Context
5057 var kubeClient client.Client
5158 var ako operator.Operator
5259 var testNamespace * corev1.Namespace
60+ var sharedGroupNamespace * corev1.Namespace
5361 var testGroup * nextapiv1.Group
5462 var groupID string
5563 var orgID string
64+ var sharedPlaceholders yamlPlaceholders
5665
5766 _ = BeforeAll (func () {
5867 if ! version .IsExperimental () {
@@ -70,45 +79,41 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
7079 testClient , err := kube .NewTestClient ()
7180 Expect (err ).To (Succeed ())
7281 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 ())
82+ Expect (kube .AssertCRDNames (ctx , kubeClient , FlexClusterCRDName , GroupCRDName )).To (Succeed ())
83+
84+ By ("Create namespace and credentials for shared test Group" , func () {
85+ sharedGroupNamespace = & corev1.Namespace {ObjectMeta : metav1.ObjectMeta {
86+ Name : utils .RandomName ("flex-shared-grp-ns" ),
87+ }}
88+ Expect (kubeClient .Create (ctx , sharedGroupNamespace )).To (Succeed ())
89+ copyCredentialsToNamespace (ctx , kubeClient , sharedGroupNamespace .Name )
90+ })
8191
8292 By ("Create test Group" , func () {
83- operatorNamespace := control .MustEnvVar ("OPERATOR_NAMESPACE" )
8493 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+ }
85101 // 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 )
102+ groupYAML := replaceYAMLPlaceholders (string (flexsamples .TestGroup ), sharedPlaceholders )
90103 objs := yml .MustParseObjects (strings .NewReader (groupYAML ))
91104 Expect (len (objs )).To (Equal (1 ))
92105 testGroup = objs [0 ].(* nextapiv1.Group )
93106 Expect (kubeClient .Create (ctx , testGroup )).To (Succeed ())
94107 })
95108
96109 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 ())
110+ waitForResourceReady (ctx , kubeClient , testGroup )
111+ Expect (testGroup .Status .V20250312 ).NotTo (BeNil ())
112+ Expect (testGroup .Status .V20250312 .Id ).NotTo (BeNil ())
113+ groupID = * testGroup .Status .V20250312 .Id
111114 Expect (groupID ).NotTo (BeEmpty ())
115+ // Update shared placeholders with groupID now that it's available
116+ sharedPlaceholders .GroupID = groupID
112117 })
113118 })
114119
@@ -122,6 +127,14 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
122127 }).WithTimeout (5 * time .Minute ).WithPolling (5 * time .Second ).NotTo (Succeed ())
123128 })
124129 }
130+ if kubeClient != nil && sharedGroupNamespace != nil {
131+ By ("Clean up shared group namespace" , func () {
132+ Expect (kubeClient .Delete (ctx , sharedGroupNamespace )).To (Succeed ())
133+ Eventually (func (g Gomega ) bool {
134+ return kubeClient .Get (ctx , client .ObjectKeyFromObject (sharedGroupNamespace ), sharedGroupNamespace ) == nil
135+ }).WithTimeout (time .Minute ).WithPolling (time .Second ).To (BeFalse ())
136+ })
137+ }
125138 if ako != nil {
126139 ako .Stop (GinkgoT ())
127140 }
@@ -149,49 +162,33 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
149162
150163 DescribeTable ("FlexCluster CRUD lifecycle" ,
151164 func (createYAML , updateYAML []byte , clusterName string ) {
165+ // Generate randomized group name for this test run (cluster names are unique per group)
166+ groupName := utils .RandomName ("flex-grp" )
167+
168+ // Set up placeholders for this test case (reuse shared values, override groupName)
169+ testPlaceholders := sharedPlaceholders
170+ testPlaceholders .GroupName = groupName
171+
172+ // Track created objects for cleanup
173+ var createdObjects []client.Object
174+
152175 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 ())
176+ copyCredentialsToNamespace (ctx , kubeClient , testNamespace .Name )
162177 })
163178
164179 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- }
180+ objs := applyYAMLToNamespace (ctx , kubeClient , createYAML , testPlaceholders , testNamespace .Name )
181+ createdObjects = append (createdObjects , objs ... )
175182 })
176183
177184 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 )
185+ createYAMLStr := replaceYAMLPlaceholders (string (createYAML ), testPlaceholders )
180186 objs := yml .MustParseObjects (strings .NewReader (createYAMLStr ))
181187 for _ , obj := range objs {
182188 if group , ok := obj .(* nextapiv1.Group ); ok {
183- groupInKube := nextapiv1.Group {
189+ waitForResourceReady ( ctx , kubeClient , & nextapiv1.Group {
184190 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 ())
191+ })
195192 }
196193 }
197194 })
@@ -201,15 +198,7 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
201198 }
202199
203200 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 ())
201+ waitForResourceReady (ctx , kubeClient , & cluster )
213202 })
214203
215204 By ("Verify cluster was created" , func () {
@@ -220,48 +209,26 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
220209
221210 By ("Update FlexCluster" , func () {
222211 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- }
212+ applyYAMLToNamespace (ctx , kubeClient , updateYAML , testPlaceholders , testNamespace .Name )
233213 }
234214 })
235215
236216 By ("Wait for FlexCluster to be Ready & updated" , func () {
237217 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 ())
218+ waitForResourceUpdated (ctx , kubeClient , & cluster )
253219 }
254220 })
255221
256- By ("Delete FlexCluster" , func () {
257- Expect (kubeClient .Delete (ctx , & cluster )).To (Succeed ())
222+ By ("Delete all created resources" , func () {
223+ for _ , obj := range createdObjects {
224+ _ = kubeClient .Delete (ctx , obj )
225+ }
258226 })
259227
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 ())
228+ By ("Wait for all resources to be deleted" , func () {
229+ for _ , obj := range createdObjects {
230+ waitForResourceDeleted (ctx , kubeClient , obj )
231+ }
265232 })
266233 },
267234 Entry ("With direct groupId" ,
@@ -276,3 +243,80 @@ var _ = Describe("FlexCluster CRUD", Ordered, Label("flexcluster-ctlr"), func()
276243 ),
277244 )
278245})
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+ }
0 commit comments