Skip to content

Commit c6c9bdf

Browse files
authored
CLOUDP-125096: Unset conditions for unused resources (#566)
* Unset conditions if resource is no longer in use * Move condition setting inside for each project service type * Refactor IP Access List and Private Endpoints ensure functions
1 parent 43cd5a3 commit c6c9bdf

File tree

10 files changed

+201
-124
lines changed

10 files changed

+201
-124
lines changed

pkg/api/v1/status/condition.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,16 @@ func EnsureConditionExists(condition Condition, source []Condition) []Condition
111111
return target
112112
}
113113

114+
func RemoveConditionIfExists(conditionType ConditionType, source []Condition) []Condition {
115+
updatedConditions := []Condition{}
116+
for _, cond := range source {
117+
if cond.Type != conditionType {
118+
updatedConditions = append(updatedConditions, cond)
119+
}
120+
}
121+
return updatedConditions
122+
}
123+
114124
func (c Condition) WithReason(reason string) Condition {
115125
c.Reason = reason
116126
return c

pkg/controller/atlasproject/atlasproject_controller.go

Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"sigs.k8s.io/controller-runtime/pkg/controller"
3535
"sigs.k8s.io/controller-runtime/pkg/handler"
3636
"sigs.k8s.io/controller-runtime/pkg/predicate"
37-
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3837
"sigs.k8s.io/controller-runtime/pkg/source"
3938

4039
mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1"
@@ -182,67 +181,34 @@ func (r *AtlasProjectReconciler) Reconcile(context context.Context, req ctrl.Req
182181
ctx.SetConditionTrue(status.ProjectReadyType)
183182
r.EventRecorder.Event(project, "Normal", string(status.ProjectReadyType), "")
184183

185-
if result = ensureIPAccessList(ctx, projectID, project); !result.IsOk() {
186-
setCondition(ctx, status.IPAccessListReadyType, result)
184+
if result := ensureIPAccessList(ctx, projectID, project); !result.IsOk() {
185+
logIfWarning(ctx, result)
187186
return result.ReconcileResult(), nil
188187
}
188+
r.EventRecorder.Event(project, "Normal", string(status.IPAccessListReadyType), "")
189189

190-
allReady, result := r.allIPAccessListsAreReady(context, ctx, projectID)
191-
if !result.IsOk() {
192-
ctx.SetConditionFalse(status.IPAccessListReadyType)
190+
if result = ensurePrivateEndpoint(ctx, projectID, project); !result.IsOk() {
191+
logIfWarning(ctx, result)
193192
return result.ReconcileResult(), nil
194193
}
194+
r.EventRecorder.Event(project, "Normal", string(status.PrivateEndpointReadyType), "")
195195

196-
if allReady {
197-
ctx.SetConditionTrue(status.IPAccessListReadyType)
198-
r.EventRecorder.Event(project, "Normal", string(status.IPAccessListReadyType), "")
199-
} else {
200-
ctx.SetConditionFalse(status.IPAccessListReadyType)
201-
return reconcile.Result{Requeue: true}, nil
196+
if result = r.ensureIntegration(ctx, projectID, project); !result.IsOk() {
197+
logIfWarning(ctx, result)
198+
return result.ReconcileResult(), nil
202199
}
200+
r.EventRecorder.Event(project, "Normal", string(status.IntegrationReadyType), "")
203201

204202
if result = ensureMaintenanceWindow(ctx, projectID, project); !result.IsOk() {
205-
setCondition(ctx, status.MaintenanceWindowReadyType, result)
206-
r.Log.Warnf("Maintenance window reconciliation failed with error : %s", result.GetMessage())
203+
logIfWarning(ctx, result)
207204
return result.ReconcileResult(), nil
208205
}
209206
r.EventRecorder.Event(project, "Normal", string(status.MaintenanceWindowReadyType), "")
210-
ctx.SetConditionTrue(status.MaintenanceWindowReadyType)
211-
212-
if result = r.ensurePrivateEndpoint(ctx, projectID, project); !result.IsOk() {
213-
return result.ReconcileResult(), nil
214-
}
215-
r.EventRecorder.Event(project, "Normal", string(status.PrivateEndpointReadyType), "")
216-
217-
if result = r.ensureIntegration(ctx, projectID, project); !result.IsOk() {
218-
setCondition(ctx, status.IntegrationReadyType, result)
219-
return result.ReconcileResult(), nil
220-
}
221-
r.EventRecorder.Event(project, "Normal", string(status.IntegrationReadyType), "")
222207

223208
ctx.SetConditionTrue(status.ReadyType)
224209
return workflow.OK().ReconcileResult(), nil
225210
}
226211

227-
// allIPAccessListsAreReady returns true if all ipAccessLists are in the ACTIVE state.
228-
func (r *AtlasProjectReconciler) allIPAccessListsAreReady(context context.Context, ctx *workflow.Context, projectID string) (bool, workflow.Result) {
229-
atlasAccess, _, err := ctx.Client.ProjectIPAccessList.List(context, projectID, &mongodbatlas.ListOptions{})
230-
if err != nil {
231-
return false, workflow.Terminate(workflow.Internal, err.Error())
232-
}
233-
for _, ipAccessList := range atlasAccess.Results {
234-
ipStatus, err := GetIPAccessListStatus(ctx.Client, ipAccessList)
235-
if err != nil {
236-
return false, workflow.Terminate(workflow.Internal, err.Error())
237-
}
238-
if ipStatus.Status != string(IPAccessListActive) {
239-
r.Log.Infof("IP Access List %v is not active", ipAccessList)
240-
return false, workflow.InProgress(workflow.ProjectIPAccessListNotActive, fmt.Sprintf("%s IP Access List is not yet active, current state: %s", getAccessListEntry(ipAccessList), ipStatus.Status))
241-
}
242-
}
243-
return true, workflow.OK()
244-
}
245-
246212
func (r *AtlasProjectReconciler) deleteAtlasProject(ctx context.Context, atlasClient mongodbatlas.Client, project *mdbv1.AtlasProject) (err error) {
247213
log := r.Log.With("atlasproject", kube.ObjectKeyFromObject(project))
248214
log.Infow("-> Starting AtlasProject deletion", "spec", project.Spec)
@@ -319,6 +285,10 @@ func removeString(slice []string, s string) (result []string) {
319285
// setCondition sets the condition from the result and logs the warnings
320286
func setCondition(ctx *workflow.Context, condition status.ConditionType, result workflow.Result) {
321287
ctx.SetConditionFromResult(condition, result)
288+
logIfWarning(ctx, result)
289+
}
290+
291+
func logIfWarning(ctx *workflow.Context, result workflow.Result) {
322292
if result.IsWarning() {
323293
ctx.Log.Warnw(result.GetMessage())
324294
}

pkg/controller/atlasproject/integrations.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,22 @@ import (
1717
)
1818

1919
func (r *AtlasProjectReconciler) ensureIntegration(ctx *workflow.Context, projectID string, project *mdbv1.AtlasProject) workflow.Result {
20+
result := r.createOrDeleteIntegrations(ctx, projectID, project)
21+
if !result.IsOk() {
22+
ctx.SetConditionFromResult(status.IntegrationReadyType, result)
23+
return result
24+
}
25+
26+
if len(project.Spec.Integrations) == 0 {
27+
ctx.UnsetCondition(status.IntegrationReadyType)
28+
return workflow.OK()
29+
}
30+
31+
ctx.SetConditionTrue(status.IntegrationReadyType)
32+
return workflow.OK()
33+
}
34+
35+
func (r *AtlasProjectReconciler) createOrDeleteIntegrations(ctx *workflow.Context, projectID string, project *mdbv1.AtlasProject) workflow.Result {
2036
integrationsInAtlas, err := fetchIntegrations(ctx, projectID)
2137
if err != nil {
2238
return workflow.Terminate(workflow.ProjectIntegrationInternal, err.Error())
@@ -45,9 +61,7 @@ func (r *AtlasProjectReconciler) ensureIntegration(ctx *workflow.Context, projec
4561
if ready := r.checkIntegrationsReady(ctx, project.Namespace, integrationsToUpdate, project.Spec.Integrations); !ready {
4662
return workflow.InProgress(workflow.ProjectIntegrationReady, "in progress")
4763
}
48-
if len(project.Spec.Integrations) > 0 {
49-
ctx.SetConditionTrue(status.IntegrationReadyType)
50-
}
64+
5165
return workflow.OK()
5266
}
5367

pkg/controller/atlasproject/ipaccess_list.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ func (i atlasProjectIPAccessList) Identifier() interface{} {
4040
// state of the IP Access list specified in the project CR. Any Access Lists which exist
4141
// in Atlas but are not specified in the CR are deleted.
4242
func ensureIPAccessList(ctx *workflow.Context, projectID string, project *mdbv1.AtlasProject) workflow.Result {
43+
result := syncIPAccessListWithAtlas(ctx, projectID, project)
44+
if !result.IsOk() {
45+
ctx.SetConditionFromResult(status.IPAccessListReadyType, result)
46+
return result
47+
}
48+
49+
if len(project.Spec.ProjectIPAccessList) == 0 {
50+
ctx.UnsetCondition(status.IPAccessListReadyType)
51+
return workflow.OK()
52+
}
53+
54+
ctx.SetConditionTrue(status.IPAccessListReadyType)
55+
return result
56+
}
57+
58+
func syncIPAccessListWithAtlas(ctx *workflow.Context, projectID string, project *mdbv1.AtlasProject) workflow.Result {
4359
if err := validateIPAccessLists(project.Spec.ProjectIPAccessList); err != nil {
4460
return workflow.Terminate(workflow.ProjectIPAccessInvalid, err.Error())
4561
}
@@ -49,9 +65,38 @@ func ensureIPAccessList(ctx *workflow.Context, projectID string, project *mdbv1.
4965
return result
5066
}
5167
ctx.EnsureStatusOption(status.AtlasProjectExpiredIPAccessOption(expired))
68+
69+
allReady, result := allIPAccessListsAreReady(context.Background(), ctx, projectID)
70+
if !result.IsOk() {
71+
return result
72+
}
73+
74+
if !allReady {
75+
return workflow.InProgress(workflow.ProjectIPAccessListNotActive, "IP Access List not ready")
76+
}
77+
5278
return workflow.OK()
5379
}
5480

81+
// allIPAccessListsAreReady returns true if all ipAccessLists are in the ACTIVE state.
82+
func allIPAccessListsAreReady(context context.Context, ctx *workflow.Context, projectID string) (bool, workflow.Result) {
83+
atlasAccess, _, err := ctx.Client.ProjectIPAccessList.List(context, projectID, &mongodbatlas.ListOptions{})
84+
if err != nil {
85+
return false, workflow.Terminate(workflow.Internal, err.Error())
86+
}
87+
for _, ipAccessList := range atlasAccess.Results {
88+
ipStatus, err := GetIPAccessListStatus(ctx.Client, ipAccessList)
89+
if err != nil {
90+
return false, workflow.Terminate(workflow.Internal, err.Error())
91+
}
92+
if ipStatus.Status != string(IPAccessListActive) {
93+
ctx.Log.Infof("IP Access List %v is not active", ipAccessList)
94+
return false, workflow.InProgress(workflow.ProjectIPAccessListNotActive, fmt.Sprintf("%s IP Access List is not yet active, current state: %s", getAccessListEntry(ipAccessList), ipStatus.Status))
95+
}
96+
}
97+
return true, workflow.OK()
98+
}
99+
55100
func validateIPAccessLists(ipAccessList []project.IPAccessList) error {
56101
for _, list := range ipAccessList {
57102
if err := validateSingleIPAccessList(list); err != nil {

pkg/controller/atlasproject/maintenancewindow.go

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,47 +8,60 @@ import (
88

99
mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1"
1010
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/project"
11+
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/status"
1112
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/workflow"
1213
)
1314

1415
// ensureMaintenanceWindow ensures that the state of the Atlas Maintenance Window matches the
1516
// state of the Maintenance Window specified in the project CR. If a Maintenance Window exists
1617
// in Atlas but is not specified in the CR, it is deleted.
1718
func ensureMaintenanceWindow(ctx *workflow.Context, projectID string, atlasProject *mdbv1.AtlasProject) workflow.Result {
18-
windowSpec := atlasProject.Spec.MaintenanceWindow
19+
if isEmptyWindow(atlasProject.Spec.MaintenanceWindow) {
20+
if condition, found := ctx.GetCondition(status.MaintenanceWindowReadyType); found {
21+
ctx.Log.Debugw("Window is empty, deleting in Atlas")
22+
if result := deleteInAtlas(ctx.Client, projectID); !result.IsOk() {
23+
ctx.SetConditionFromResult(condition.Type, result)
24+
return result
25+
}
26+
ctx.UnsetCondition(condition.Type)
27+
}
28+
29+
return workflow.OK()
30+
}
31+
32+
if result := syncAtlasWithSpec(ctx, projectID, atlasProject.Spec.MaintenanceWindow); !result.IsOk() {
33+
ctx.SetConditionFromResult(status.MaintenanceWindowReadyType, result)
34+
return result
35+
}
36+
37+
ctx.SetConditionTrue(status.MaintenanceWindowReadyType)
38+
return workflow.OK()
39+
}
40+
41+
func syncAtlasWithSpec(ctx *workflow.Context, projectID string, windowSpec project.MaintenanceWindow) workflow.Result {
42+
ctx.Log.Debugw("Validate the maintenance window")
1943
if err := validateMaintenanceWindow(windowSpec); err != nil {
2044
return workflow.Terminate(workflow.ProjectWindowInvalid, err.Error())
2145
}
2246

23-
if isEmptyWindow(windowSpec) {
24-
ctx.Log.Debugw("Window empty or undefined, deleting in Atlas")
25-
if result := deleteInAtlas(ctx.Client, projectID); !result.IsOk() {
26-
return result
27-
}
28-
return workflow.OK()
47+
ctx.Log.Debugw("Checking if window needs update")
48+
windowInAtlas, result := getInAtlas(ctx.Client, projectID)
49+
if !result.IsOk() {
50+
return result
2951
}
3052

31-
if windowSpecified(windowSpec) {
32-
ctx.Log.Debugw("Checking if window needs update")
33-
windowInAtlas, result := getInAtlas(ctx.Client, projectID)
34-
if !result.IsOk() {
53+
if daysOrHoursAreDifferent(windowInAtlas, windowSpec) {
54+
ctx.Log.Debugw("Creating or updating window")
55+
// We set startASAP to false because the operator takes care of calling the API a second time if both
56+
// startASAP and the new maintenance timeslots are defined
57+
if result := createOrUpdateInAtlas(ctx.Client, projectID, windowSpec.WithStartASAP(false)); !result.IsOk() {
3558
return result
3659
}
37-
38-
if windowInAtlas.DayOfWeek == 0 || windowInAtlas.HourOfDay == nil ||
39-
*windowInAtlas.HourOfDay != windowSpec.HourOfDay || windowInAtlas.DayOfWeek != windowSpec.DayOfWeek {
40-
ctx.Log.Debugw("Creating or updating window")
41-
// We set startASAP to false because the operator takes care of calling the API a second time if both
42-
// startASAP and the new maintenance timeslots are defined
43-
if result := createOrUpdateInAtlas(ctx.Client, projectID, windowSpec.WithStartASAP(false)); !result.IsOk() {
44-
return result
45-
}
46-
} else if *windowInAtlas.AutoDeferOnceEnabled != windowSpec.AutoDefer {
47-
// If autoDefer flag is different in Atlas, and we haven't updated the window previously, we toggle the flag
48-
ctx.Log.Debugw("Toggling autoDefer")
49-
if result := toggleAutoDeferInAtlas(ctx.Client, projectID); !result.IsOk() {
50-
return result
51-
}
60+
} else if *windowInAtlas.AutoDeferOnceEnabled != windowSpec.AutoDefer {
61+
// If autoDefer flag is different in Atlas, and we haven't updated the window previously, we toggle the flag
62+
ctx.Log.Debugw("Toggling autoDefer")
63+
if result := toggleAutoDeferInAtlas(ctx.Client, projectID); !result.IsOk() {
64+
return result
5265
}
5366
}
5467

@@ -91,10 +104,15 @@ func maxOneFlag(window project.MaintenanceWindow) bool {
91104
return !(window.StartASAP && window.Defer)
92105
}
93106

107+
func daysOrHoursAreDifferent(windowInAtlas *mongodbatlas.MaintenanceWindow, windowSpec project.MaintenanceWindow) bool {
108+
return windowInAtlas.DayOfWeek == 0 || windowInAtlas.HourOfDay == nil ||
109+
*windowInAtlas.HourOfDay != windowSpec.HourOfDay || windowInAtlas.DayOfWeek != windowSpec.DayOfWeek
110+
}
111+
94112
// validateMaintenanceWindow performs validation of the Maintenance Window. Note, that we intentionally don't validate
95113
// that hour of day and day of week are in the bounds - this will be done by Atlas.
96114
func validateMaintenanceWindow(window project.MaintenanceWindow) error {
97-
if isEmptyWindow(window) || (windowSpecified(window) && maxOneFlag(window)) {
115+
if windowSpecified(window) && maxOneFlag(window) {
98116
return nil
99117
}
100118
errorString := "projectMaintenanceWindow must respect the following constraints, or be empty : " +

pkg/controller/atlasproject/maintenancewindow_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func TestValidateMaintenanceWindow(t *testing.T) {
2222
Defer: false,
2323
AutoDefer: false,
2424
},
25-
valid: true,
25+
valid: false,
2626
},
2727

2828
// Only dayOfWeek specified, valid

0 commit comments

Comments
 (0)