Skip to content

Commit 778eb3b

Browse files
authored
CLOUDP-80607: Implement project delete logic (#88)
1 parent 33c0748 commit 778eb3b

File tree

6 files changed

+126
-70
lines changed

6 files changed

+126
-70
lines changed

pkg/controller/atlascluster/atlascluster_controller.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ import (
2424
"go.uber.org/zap"
2525
"k8s.io/apimachinery/pkg/runtime"
2626
ctrl "sigs.k8s.io/controller-runtime"
27-
"sigs.k8s.io/controller-runtime/pkg/builder"
2827
"sigs.k8s.io/controller-runtime/pkg/client"
28+
"sigs.k8s.io/controller-runtime/pkg/controller"
29+
"sigs.k8s.io/controller-runtime/pkg/event"
2930
"sigs.k8s.io/controller-runtime/pkg/source"
3031

3132
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/watch"
@@ -104,22 +105,25 @@ func (r *AtlasClusterReconciler) readProjectResource(cluster *mdbv1.AtlasCluster
104105
}
105106

106107
func (r *AtlasClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
107-
return ctrl.NewControllerManagedBy(mgr).
108-
For(&mdbv1.AtlasCluster{}).
109-
WithEventFilter(watch.CommonPredicates()).
110-
Watches(
111-
&source.Kind{Type: &mdbv1.AtlasCluster{}},
112-
&watch.AtlasResourceEventHandler{Controller: r},
113-
builder.WithPredicates(watch.DeleteOnly()),
114-
).
115-
Complete(r)
108+
c, err := controller.New("AtlasCluster", mgr, controller.Options{Reconciler: r})
109+
if err != nil {
110+
return err
111+
}
112+
113+
// Watch for changes to primary resource AtlasCluster & handle delete separately
114+
err = c.Watch(&source.Kind{Type: &mdbv1.AtlasCluster{}}, &watch.EventHandlerWithDelete{Controller: r}, watch.CommonPredicates())
115+
if err != nil {
116+
return err
117+
}
118+
119+
return nil
116120
}
117121

118122
// Delete implements a handler for the Delete event.
119-
func (r *AtlasClusterReconciler) Delete(obj runtime.Object) error {
120-
cluster, ok := obj.(*mdbv1.AtlasCluster)
123+
func (r *AtlasClusterReconciler) Delete(e event.DeleteEvent) error {
124+
cluster, ok := e.Object.(*mdbv1.AtlasCluster)
121125
if !ok {
122-
r.Log.Errorf("Ignoring malformed Delete() call (expected type %T, got %T)", &mdbv1.AtlasCluster{}, obj)
126+
r.Log.Errorf("Ignoring malformed Delete() call (expected type %T, got %T)", &mdbv1.AtlasCluster{}, e.Object)
123127
return nil
124128
}
125129

pkg/controller/atlasproject/atlasproject_controller.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ limitations under the License.
1717
package atlasproject
1818

1919
import (
20+
"context"
21+
"errors"
22+
"fmt"
23+
2024
"go.uber.org/zap"
2125
corev1 "k8s.io/api/core/v1"
2226
"k8s.io/apimachinery/pkg/runtime"
2327
ctrl "sigs.k8s.io/controller-runtime"
2428
"sigs.k8s.io/controller-runtime/pkg/client"
2529
"sigs.k8s.io/controller-runtime/pkg/controller"
30+
"sigs.k8s.io/controller-runtime/pkg/event"
2631
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2732
"sigs.k8s.io/controller-runtime/pkg/source"
2833

@@ -32,6 +37,7 @@ import (
3237
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/customresource"
3338
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/statushandler"
3439
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/watch"
40+
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/kube"
3541
)
3642

3743
// AtlasProjectReconciler reconciles a AtlasProject object
@@ -96,8 +102,34 @@ func (r *AtlasProjectReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error
96102
return ctrl.Result{}, nil
97103
}
98104

99-
func (r *AtlasProjectReconciler) Delete(obj runtime.Object) error {
100-
// TODO CLOUDP-80607
105+
func (r *AtlasProjectReconciler) Delete(e event.DeleteEvent) error {
106+
project, ok := e.Object.(*mdbv1.AtlasProject)
107+
if !ok {
108+
r.Log.Errorf("Ignoring malformed Delete() call (expected type %T, got %T)", &mdbv1.AtlasProject{}, e.Object)
109+
return nil
110+
}
111+
112+
log := r.Log.With("atlasproject", kube.ObjectKeyFromObject(project))
113+
114+
log.Infow("-> Starting AtlasProject deletion", "spec", project.Spec)
115+
116+
connection, result := atlas.ReadConnection(log, r.Client, "TODO!", project.ConnectionSecretObjectKey())
117+
if !result.IsOk() {
118+
return errors.New("cannot read Atlas connection")
119+
}
120+
121+
atlasClient, err := atlas.Client(r.AtlasDomain, connection, log)
122+
if err != nil {
123+
return fmt.Errorf("cannot build Atlas client: %w", err)
124+
}
125+
126+
_, err = atlasClient.Projects.Delete(context.Background(), project.Status.ID)
127+
if err != nil {
128+
return fmt.Errorf("cannot delete Atlas project: %w", err)
129+
}
130+
131+
log.Infow("Successfully deleted Atlas project", "projectID", project.Status.ID)
132+
101133
return nil
102134
}
103135

@@ -107,8 +139,8 @@ func (r *AtlasProjectReconciler) SetupWithManager(mgr ctrl.Manager) error {
107139
return err
108140
}
109141

110-
// Watch for changes to primary resource MongoDbReplicaSet
111-
err = c.Watch(&source.Kind{Type: &mdbv1.AtlasProject{}}, &watch.AtlasResourceEventHandler{Controller: r}, watch.CommonPredicates())
142+
// Watch for changes to primary resource AtlasProject & handle delete separately
143+
err = c.Watch(&source.Kind{Type: &mdbv1.AtlasProject{}}, &watch.EventHandlerWithDelete{Controller: r}, watch.CommonPredicates())
112144
if err != nil {
113145
return err
114146
}

pkg/controller/watch/delete_handler.go

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package watch
2+
3+
import (
4+
"go.uber.org/zap"
5+
"k8s.io/client-go/util/workqueue"
6+
"sigs.k8s.io/controller-runtime/pkg/event"
7+
"sigs.k8s.io/controller-runtime/pkg/handler"
8+
9+
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/kube"
10+
)
11+
12+
// EventHandlerWithDelete is an extension of EnqueueRequestForObject that will _not_ trigger a reconciliation for a Delete event.
13+
// Instead, it will call an external controller's Delete() method and pass the event argument unchanged.
14+
type EventHandlerWithDelete struct {
15+
handler.EnqueueRequestForObject
16+
Controller interface {
17+
Delete(e event.DeleteEvent) error
18+
}
19+
}
20+
21+
func (d *EventHandlerWithDelete) Delete(e event.DeleteEvent, _ workqueue.RateLimitingInterface) {
22+
objectKey := kube.ObjectKeyFromObject(e.Meta)
23+
log := zap.S().With("resource", objectKey)
24+
25+
if err := d.Controller.Delete(e); err != nil {
26+
log.Errorf("Object (%s) removed from Kubernetes, but controller could not delete it: %s", e.Object.GetObjectKind(), err)
27+
return
28+
}
29+
}

test/int/cluster_test.go

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package int
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"net/http"
87
"time"
@@ -15,7 +14,6 @@ import (
1514

1615
mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1"
1716
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/status"
18-
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/atlas"
1917
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/workflow"
2018
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/kube"
2119
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/testutil"
@@ -52,17 +50,22 @@ var _ = Describe("AtlasCluster", func() {
5250
By("Creating the project " + createdProject.Name)
5351
Expect(k8sClient.Create(context.Background(), createdProject)).To(Succeed())
5452
Eventually(testutil.WaitFor(k8sClient, createdProject, status.TrueCondition(status.ReadyType)),
55-
10, interval).Should(BeTrue())
53+
20, interval).Should(BeTrue())
5654
})
5755

5856
AfterEach(func() {
5957
if createdProject != nil && createdProject.Status.ID != "" {
6058
if createdCluster != nil {
6159
By("Removing Atlas Cluster " + createdCluster.Name)
6260
Expect(k8sClient.Delete(context.Background(), createdCluster)).To(Succeed())
63-
6461
Eventually(checkAtlasClusterRemoved(createdProject.Status.ID, createdCluster.Name), 600, interval).Should(BeTrue())
6562
}
63+
64+
// TODO: CLOUDP-82115
65+
// By("Removing Atlas Project " + createdProject.Status.ID)
66+
// Expect(k8sClient.Delete(context.Background(), createdProject)).To(Succeed())
67+
// Eventually(checkAtlasProjectRemoved(createdProject.Status.ID), 20, interval).Should(BeTrue())
68+
6669
By("Removing Atlas Project " + createdProject.Status.ID)
6770
// This is a bit strange but the delete request right after the cluster is removed may fail with "Still active cluster" error
6871
// UI shows the cluster being deleted though. Seems to be the issue only if removal is done using API,
@@ -237,20 +240,8 @@ func checkAtlasClusterRemoved(projectID string, clusterName string) func() bool
237240
return true
238241
}
239242
}
240-
return false
241-
}
242-
}
243243

244-
func removeAtlasProject(projectID string) func() bool {
245-
return func() bool {
246-
_, err := atlasClient.Projects.Delete(context.Background(), projectID)
247-
if err != nil {
248-
var apiError *mongodbatlas.ErrorResponse
249-
Expect(errors.As(err, &apiError)).To(BeTrue())
250-
Expect(apiError.ErrorCode).To(Equal(atlas.CannotCloseGroupActiveAtlasCluster))
251-
return false
252-
}
253-
return true
244+
return false
254245
}
255246
}
256247

test/int/project_test.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"net/http"
78
"time"
89

910
. "github.com/onsi/ginkgo"
@@ -47,8 +48,8 @@ var _ = Describe("AtlasProject", func() {
4748
AfterEach(func() {
4849
if createdProject != nil && createdProject.Status.ID != "" {
4950
By("Removing Atlas Project " + createdProject.Status.ID)
50-
_, err := atlasClient.Projects.Delete(context.Background(), createdProject.Status.ID)
51-
Expect(err).ToNot(HaveOccurred())
51+
Expect(k8sClient.Delete(context.Background(), createdProject)).To(Succeed())
52+
Eventually(checkAtlasProjectRemoved(createdProject.Status.ID), 20, interval).Should(BeTrue())
5253
}
5354
removeControllersAndNamespace()
5455
})
@@ -108,7 +109,7 @@ var _ = Describe("AtlasProject", func() {
108109

109110
expectedCondition := status.FalseCondition(status.ProjectReadyType).WithReason(string(workflow.AtlasCredentialsNotProvided))
110111
Eventually(testutil.WaitFor(k8sClient, createdProject, expectedCondition),
111-
10, interval).Should(BeTrue())
112+
20, interval).Should(BeTrue())
112113

113114
Expect(createdProject.Status.ObservedGeneration).To(Equal(createdProject.Generation))
114115
expectedConditionsMatchers := testutil.MatchConditions(
@@ -147,7 +148,7 @@ var _ = Describe("AtlasProject", func() {
147148
Expect(k8sClient.Update(context.Background(), createdProject)).To(Succeed())
148149

149150
Eventually(testutil.WaitFor(k8sClient, createdProject, status.TrueCondition(status.ReadyType)),
150-
10, interval).Should(BeTrue())
151+
20, interval).Should(BeTrue())
151152

152153
Expect(testutil.ReadAtlasResource(k8sClient, createdProject)).To(BeTrue())
153154
Expect(createdProject.Status.Conditions).To(ContainElement(testutil.MatchCondition(status.TrueCondition(status.ProjectReadyType))))
@@ -163,9 +164,9 @@ var _ = Describe("AtlasProject", func() {
163164
var secondProject *mdbv1.AtlasProject
164165
AfterEach(func() {
165166
if secondProject != nil && secondProject.Status.ID != "" {
166-
By("Removing (second) Atlas Project " + secondProject.ID())
167-
_, err := atlasClient.Projects.Delete(context.Background(), secondProject.ID())
168-
Expect(err).ToNot(HaveOccurred())
167+
By("Removing (second) Atlas Project " + secondProject.Status.ID)
168+
Expect(k8sClient.Delete(context.Background(), secondProject)).To(Succeed())
169+
Eventually(checkAtlasProjectRemoved(secondProject.Status.ID), 20, interval).Should(BeTrue())
169170
}
170171
})
171172
It("Should Succeed", func() {
@@ -280,6 +281,7 @@ var _ = Describe("AtlasProject", func() {
280281
checkExpiredAccessLists([]mdbv1.ProjectIPAccessList{})
281282
})
282283
})
284+
283285
Describe("Updating the project IP access list", func() {
284286
It("Should Succeed (single)", func() {
285287
By("Creating the project first", func() {
@@ -305,6 +307,7 @@ var _ = Describe("AtlasProject", func() {
305307
checkIPAccessListInAtlas()
306308
})
307309
})
310+
308311
It("Should Succeed (multiple)", func() {
309312
By("Creating the project first", func() {
310313
createdProject = testAtlasProject(namespace.Name, "test-project", namespace.Name, connectionSecret.Name)
@@ -336,7 +339,6 @@ var _ = Describe("AtlasProject", func() {
336339
})
337340
})
338341
})
339-
340342
})
341343

342344
// TODO builders
@@ -353,6 +355,32 @@ func testAtlasProject(namespace, name, atlasName, connectionSecretName string) *
353355
}
354356
}
355357

358+
func removeAtlasProject(projectID string) func() bool {
359+
return func() bool {
360+
_, err := atlasClient.Projects.Delete(context.Background(), projectID)
361+
if err != nil {
362+
var apiError *mongodbatlas.ErrorResponse
363+
Expect(errors.As(err, &apiError)).To(BeTrue())
364+
Expect(apiError.ErrorCode).To(Equal(atlas.CannotCloseGroupActiveAtlasCluster))
365+
return false
366+
}
367+
return true
368+
}
369+
}
370+
371+
// checkAtlasProjectRemoved returns true if the Atlas Project is removed from Atlas.
372+
func checkAtlasProjectRemoved(projectID string) func() bool {
373+
return func() bool {
374+
_, r, err := atlasClient.Projects.GetOneProject(context.Background(), projectID)
375+
if err != nil {
376+
if r != nil && r.StatusCode == http.StatusNotFound {
377+
return true
378+
}
379+
}
380+
return false
381+
}
382+
}
383+
356384
// validateNoErrorsIPAccessListDuringCreate performs check that no problems happen to IP Access list during the create.
357385
// This allows the test to fail fast instead by timeout if there are any troubles.
358386
func validateNoErrorsIPAccessListDuringCreate(a mdbv1.AtlasCustomResource) {

0 commit comments

Comments
 (0)