Skip to content

Commit a61fab2

Browse files
committed
Fix e2e2 test
1 parent 8316373 commit a61fab2

File tree

7 files changed

+163
-75
lines changed

7 files changed

+163
-75
lines changed

config/samples/atlas_generated_v1_flexcluster_with_groupref.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
apiVersion: atlas.generated.mongodb.com/v1
22
kind: Group
33
metadata:
4-
name: my-group
4+
name: my-group-for-flexcluster
55
spec:
66
connectionSecretRef:
77
name: mongodb-atlas-operator-api-key
88
v20250312:
99
entry:
1010
orgId: "60f1b3c4e4b0e8b8c8b8c8b"
11-
name: my-group
11+
name: my-group-for-flexcluster
1212
---
1313
apiVersion: atlas.generated.mongodb.com/v1
1414
kind: FlexCluster
1515
metadata:
16-
name: flexy
16+
name: flexy-with-groupref
1717
annotations:
1818
some-tag: tag
1919
spec:
2020
v20250312:
2121
groupRef:
22-
name: my-group
22+
name: my-group-for-flexcluster
2323
entry:
24-
name: flexy
24+
name: flexy-with-groupref
2525
terminationProtectionEnabled: true
2626
providerSettings:
2727
backingProviderName: GCP

internal/generated/controller/flexcluster/handler_v20250312.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121

2222
v20250312sdk "go.mongodb.org/atlas-sdk/v20250312009/admin"
23+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2324
controllerruntime "sigs.k8s.io/controller-runtime"
2425
builder "sigs.k8s.io/controller-runtime/pkg/builder"
2526
client "sigs.k8s.io/controller-runtime/pkg/client"
@@ -71,6 +72,26 @@ func (h *Handlerv20250312) getDependencies(ctx context.Context, flexcluster *ako
7172
return result, nil
7273
}
7374

75+
// getMinimalGroupFromStatusOrSpec creates a minimal Group object with group ID from status (preferred) or spec (fallback).
76+
// Returns nil if no group ID is available. This allows deletion to proceed even if the Group CR is gone from Kubernetes.
77+
func (h *Handlerv20250312) getMinimalGroupFromStatusOrSpec(flexcluster *akov2generated.FlexCluster) *akov2generated.Group {
78+
var groupID *string
79+
if flexcluster.Status.V20250312 != nil {
80+
groupID = flexcluster.Status.V20250312.GroupId
81+
}
82+
if groupID == nil && flexcluster.Spec.V20250312 != nil {
83+
groupID = flexcluster.Spec.V20250312.GroupId
84+
}
85+
if groupID == nil || *groupID == "" {
86+
return nil
87+
}
88+
return &akov2generated.Group{
89+
Status: akov2generated.GroupStatus{
90+
V20250312: &akov2generated.GroupStatusV20250312{Id: groupID},
91+
},
92+
}
93+
}
94+
7495
// HandleInitial handles the initial state for version v20250312
7596
func (h *Handlerv20250312) HandleInitial(ctx context.Context, flexcluster *akov2generated.FlexCluster) (ctrlstate.Result, error) {
7697
deps, err := h.getDependencies(ctx, flexcluster)
@@ -165,7 +186,18 @@ func (h *Handlerv20250312) HandleDeletionRequested(ctx context.Context, flexclus
165186

166187
deps, err := h.getDependencies(ctx, flexcluster)
167188
if err != nil {
168-
return result.Error(state.StateDeletionRequested, fmt.Errorf("failed to get dependencies: %w", err))
189+
// Race condition: Group CR may be deleted from K8s before FlexCluster finishes deletion.
190+
// If Group is not found but we have group ID in status, use it to proceed with deletion.
191+
var statusErr *apierrors.StatusError
192+
if errors.As(err, &statusErr) && apierrors.IsNotFound(statusErr) {
193+
if group := h.getMinimalGroupFromStatusOrSpec(flexcluster); group != nil {
194+
deps = []client.Object{group}
195+
} else {
196+
return result.Error(state.StateDeletionRequested, fmt.Errorf("failed to get dependencies: %w", err))
197+
}
198+
} else {
199+
return result.Error(state.StateDeletionRequested, fmt.Errorf("failed to get dependencies: %w", err))
200+
}
169201
}
170202

171203
params := &v20250312sdk.DeleteFlexClusterApiParams{}
@@ -189,7 +221,18 @@ func (h *Handlerv20250312) HandleDeletionRequested(ctx context.Context, flexclus
189221
func (h *Handlerv20250312) HandleDeleting(ctx context.Context, flexcluster *akov2generated.FlexCluster) (ctrlstate.Result, error) {
190222
deps, err := h.getDependencies(ctx, flexcluster)
191223
if err != nil {
192-
return result.Error(state.StateDeleting, fmt.Errorf("failed to get dependencies: %w", err))
224+
// Race condition: Group CR may be deleted from K8s before FlexCluster finishes deletion.
225+
// If Group is not found but we have group ID in status, use it to proceed with deletion.
226+
var statusErr *apierrors.StatusError
227+
if errors.As(err, &statusErr) && apierrors.IsNotFound(statusErr) {
228+
if group := h.getMinimalGroupFromStatusOrSpec(flexcluster); group != nil {
229+
deps = []client.Object{group}
230+
} else {
231+
return result.Error(state.StateDeleting, fmt.Errorf("failed to get dependencies: %w", err))
232+
}
233+
} else {
234+
return result.Error(state.StateDeleting, fmt.Errorf("failed to get dependencies: %w", err))
235+
}
193236
}
194237

195238
params := &v20250312sdk.GetFlexClusterApiParams{}

test/e2e2/e2e2_suite_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
package e2e2_test
1616

1717
import (
18+
"context"
1819
"fmt"
1920
"os"
21+
"os/signal"
22+
"syscall"
2023
"testing"
2124
"time"
2225

@@ -86,3 +89,35 @@ func runTestAKO(globalCreds, ns string, deletionprotection bool) operator.Operat
8689
args = append(args, fmt.Sprintf("--object-deletion-protection=%v", deletionprotection))
8790
return operator.NewOperator(operator.AllNamespacesOperatorEnv(ns), os.Stdout, os.Stderr, args...)
8891
}
92+
93+
// SetupTerminationHandling sets up signal handling and context cancellation for an operator.
94+
// It handles SIGINT/SIGTERM signals and context cancellation to properly stop the operator.
95+
// Returns a cleanup function that should be called with DeferCleanup.
96+
func SetupTerminationHandling(ctx context.Context, op operator.Operator, cancel context.CancelFunc) func() {
97+
// Wire up signal handling to cancel context and stop operator on SIGINT/SIGTERM
98+
sigChan := make(chan os.Signal, 1)
99+
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
100+
go func() {
101+
defer GinkgoRecover()
102+
<-sigChan
103+
cancel()
104+
// Stop the operator immediately when Ctrl+C is pressed
105+
if op != nil {
106+
op.Stop(GinkgoT())
107+
}
108+
}()
109+
110+
// Also stop operator when context is cancelled (e.g., from cleanup)
111+
go func() {
112+
defer GinkgoRecover()
113+
<-ctx.Done()
114+
if op != nil {
115+
op.Stop(GinkgoT())
116+
}
117+
}()
118+
119+
// Return cleanup function to stop signal handling
120+
return func() {
121+
signal.Stop(sigChan)
122+
}
123+
}

0 commit comments

Comments
 (0)