Skip to content

Commit b2ff680

Browse files
committed
Finalizers.
1 parent 73e929c commit b2ff680

8 files changed

+211
-10
lines changed

api/v1beta1/mongodbdatabase_types.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package v1beta1
1818

1919
import (
2020
"errors"
21+
"github.com/doodlescheduling/kubedb/common/stringutils"
2122
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2223
)
2324

@@ -27,6 +28,11 @@ const (
2728
DEFAULT_MONGODB_ROOT_AUTHENTICATION_DATABASE = "admin"
2829
)
2930

31+
// Finalizer
32+
const (
33+
MongoSQLDatabaseControllerFinalizer = "infra.finalizers.doodle.com"
34+
)
35+
3036
type MongoDBDatabaseRootSecretLookup struct {
3137
Name string `json:"name"`
3238
Namespace string `json:"namespace"`
@@ -57,7 +63,7 @@ type MongoDBDatabaseSpec struct {
5763
type MongoDBDatabaseStatus struct {
5864
DatabaseStatus DatabaseStatus `json:"database"`
5965
CredentialsStatus CredentialsStatus `json:"credentials"`
60-
LastUpdateTime metav1.Time `json:"lastUpdateTime"`
66+
LastUpdateTime *metav1.Time `json:"lastUpdateTime"`
6167
}
6268

6369
// +kubebuilder:object:root=true
@@ -109,6 +115,38 @@ func (d *MongoDBDatabase) RemoveUnneededCredentialsStatus() *CredentialsStatus {
109115
return &removedStatuses
110116
}
111117

118+
/*
119+
If object doesn't contain finalizer, set it and call update function 'updateF'.
120+
Only do this if object is not being deleted (judged by DeletionTimestamp being zero)
121+
*/
122+
func (d *MongoDBDatabase) SetFinalizer(updateF func() error) error {
123+
if !d.ObjectMeta.DeletionTimestamp.IsZero() {
124+
return nil
125+
}
126+
if !stringutils.ContainsString(d.ObjectMeta.Finalizers, MongoSQLDatabaseControllerFinalizer) {
127+
d.ObjectMeta.Finalizers = append(d.ObjectMeta.Finalizers, MongoSQLDatabaseControllerFinalizer)
128+
return updateF()
129+
}
130+
return nil
131+
}
132+
133+
/*
134+
Finalize object if deletion timestamp is not zero (i.e. object is being deleted).
135+
Call finalize function 'finalizeF', which should handle finalization logic.
136+
Remove finalizer from the object (so that object can be deleted), and update by calling update function 'updateF'.
137+
*/
138+
func (d *MongoDBDatabase) Finalize(updateF func() error, finalizeF func() error) (bool, error) {
139+
if d.ObjectMeta.DeletionTimestamp.IsZero() {
140+
return false, nil
141+
}
142+
if stringutils.ContainsString(d.ObjectMeta.Finalizers, MongoSQLDatabaseControllerFinalizer) {
143+
_ = finalizeF()
144+
d.ObjectMeta.Finalizers = stringutils.RemoveString(d.ObjectMeta.Finalizers, MongoSQLDatabaseControllerFinalizer)
145+
return true, updateF()
146+
}
147+
return true, nil
148+
}
149+
112150
func (d *MongoDBDatabase) SetDefaults() error {
113151
if d.Spec.RootUsername == "" {
114152
d.Spec.RootUsername = DEFAULT_MONGODB_ROOT_USER

api/v1beta1/postgresqldatabase_types.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package v1beta1
1818

1919
import (
2020
"errors"
21+
"github.com/doodlescheduling/kubedb/common/stringutils"
2122
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2223
)
2324

@@ -27,6 +28,11 @@ const (
2728
DEFAULT_POSTGRESQL_ROOT_AUTHENTICATION_DATABASE = "postgres"
2829
)
2930

31+
// Finalizer
32+
const (
33+
PostgreSQLDatabaseControllerFinalizer = "infra.finalizers.doodle.com"
34+
)
35+
3036
type PostgreSQLDatabaseRootSecretLookup struct {
3137
Name string `json:"name"`
3238
Namespace string `json:"namespace"`
@@ -57,7 +63,7 @@ type PostgreSQLDatabaseSpec struct {
5763
type PostgreSQLDatabaseStatus struct {
5864
DatabaseStatus DatabaseStatus `json:"database"`
5965
CredentialsStatus CredentialsStatus `json:"credentials"`
60-
LastUpdateTime metav1.Time `json:"lastUpdateTime"`
66+
LastUpdateTime *metav1.Time `json:"lastUpdateTime"`
6167
}
6268

6369
// +kubebuilder:object:root=true
@@ -109,6 +115,38 @@ func (d *PostgreSQLDatabase) RemoveUnneededCredentialsStatus() *CredentialsStatu
109115
return &removedStatuses
110116
}
111117

118+
/*
119+
If object doesn't contain finalizer, set it and call update function 'updateF'.
120+
Only do this if object is not being deleted (judged by DeletionTimestamp being zero)
121+
*/
122+
func (d *PostgreSQLDatabase) SetFinalizer(updateF func() error) error {
123+
if !d.ObjectMeta.DeletionTimestamp.IsZero() {
124+
return nil
125+
}
126+
if !stringutils.ContainsString(d.ObjectMeta.Finalizers, PostgreSQLDatabaseControllerFinalizer) {
127+
d.ObjectMeta.Finalizers = append(d.ObjectMeta.Finalizers, PostgreSQLDatabaseControllerFinalizer)
128+
return updateF()
129+
}
130+
return nil
131+
}
132+
133+
/*
134+
Finalize object if deletion timestamp is not zero (i.e. object is being deleted).
135+
Call finalize function 'finalizeF', which should handle finalization logic.
136+
Remove finalizer from the object (so that object can be deleted), and update by calling update function 'updateF'.
137+
*/
138+
func (d *PostgreSQLDatabase) Finalize(updateF func() error, finalizeF func() error) (bool, error) {
139+
if d.ObjectMeta.DeletionTimestamp.IsZero() {
140+
return false, nil
141+
}
142+
if stringutils.ContainsString(d.ObjectMeta.Finalizers, PostgreSQLDatabaseControllerFinalizer) {
143+
_ = finalizeF()
144+
d.ObjectMeta.Finalizers = stringutils.RemoveString(d.ObjectMeta.Finalizers, PostgreSQLDatabaseControllerFinalizer)
145+
return true, updateF()
146+
}
147+
return true, nil
148+
}
149+
112150
func (d *PostgreSQLDatabase) SetDefaults() error {
113151
if d.Spec.RootUsername == "" {
114152
d.Spec.RootUsername = DEFAULT_POSTGRESQL_ROOT_USER

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/stringutils/utils.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package stringutils
2+
3+
// Helper functions to check and remove string from a slice of strings.
4+
func ContainsString(slice []string, s string) bool {
5+
for _, item := range slice {
6+
if item == s {
7+
return true
8+
}
9+
}
10+
return false
11+
}
12+
13+
func RemoveString(slice []string, s string) (result []string) {
14+
for _, item := range slice {
15+
if item == s {
16+
continue
17+
}
18+
result = append(result, item)
19+
}
20+
return
21+
}

controllers/mongodbdatabase_controller.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
ctrl "sigs.k8s.io/controller-runtime"
2929
"sigs.k8s.io/controller-runtime/pkg/client"
3030
"sigs.k8s.io/controller-runtime/pkg/controller"
31+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3132
)
3233

3334
// MongoDBDatabaseReconciler reconciles a MongoDBDatabase object
@@ -63,6 +64,24 @@ func (r *MongoDBDatabaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er
6364
return ctrl.Result{}, err
6465
}
6566

67+
// set finalizer
68+
if err := database.SetFinalizer(func() error {
69+
return r.Update(ctx, &database)
70+
}); err != nil {
71+
return reconcile.Result{}, err
72+
}
73+
74+
// finalize
75+
if finalized, err := database.Finalize(func() error {
76+
return r.Update(ctx, &database)
77+
}, func() error {
78+
return gc.CleanFromSpec(&database)
79+
}); err != nil {
80+
return reconcile.Result{}, err
81+
} else if finalized {
82+
return reconcile.Result{}, nil
83+
}
84+
6685
// set spec defaults. Does not mutate the spec, since we are not updating resource
6786
if err := database.SetDefaults(); err != nil {
6887
database.Status.DatabaseStatus.SetDatabaseStatus(infrav1beta1.Unavailable, err.Error(), "", "")
@@ -71,7 +90,7 @@ func (r *MongoDBDatabaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er
7190
}
7291

7392
// Garbage Collection. If errors occur, log and proceed with reconciliation.
74-
if err := gc.Clean(&database); err != nil {
93+
if err := gc.CleanFromStatus(&database); err != nil {
7594
log.Info("Error while cleaning garbage", "error", err)
7695
}
7796

@@ -143,7 +162,8 @@ func (r *MongoDBDatabaseReconciler) SetupWithManager(mgr ctrl.Manager, maxConcur
143162
}
144163

145164
func (r *MongoDBDatabaseReconciler) updateAndReturn(ctx *context.Context, database *infrav1beta1.MongoDBDatabase, log *logr.Logger) (ctrl.Result, error) {
146-
database.Status.LastUpdateTime = metav1.Now()
165+
now := metav1.Now()
166+
database.Status.LastUpdateTime = &now
147167
if err := r.Status().Update(*ctx, database); err != nil {
148168
(*log).Error(err, "unable to update MongoDBDatabase status")
149169
return ctrl.Result{}, err

controllers/mongodbdatabase_garbage_collector.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ func NewMongoDBGarbageCollector(r *MongoDBDatabaseReconciler, cw *ControllerWrap
2323
/* For now, Garbage Collector does not drop databases, because we haven't decided if we want to delete all data.
2424
Possible avenue to proceed is to have a separate option flag/struct (to be set in Spec), that will force deletion of all garbage, including data.
2525
*/
26-
func (g *MongoDBDatabaseGarbageCollector) Clean(database *infrav1beta1.MongoDBDatabase) error {
26+
func (g *MongoDBDatabaseGarbageCollector) CleanFromStatus(database *infrav1beta1.MongoDBDatabase) error {
27+
// first time, nothing to clear
28+
if database.Status.LastUpdateTime == nil {
29+
return nil
30+
}
2731
rootPassword, err := g.cw.GetRootPassword(database.Status.DatabaseStatus.RootSecretLookup.Name, database.Status.DatabaseStatus.RootSecretLookup.Namespace,
2832
database.Status.DatabaseStatus.RootSecretLookup.Field)
2933
if err != nil {
@@ -43,6 +47,26 @@ func (g *MongoDBDatabaseGarbageCollector) Clean(database *infrav1beta1.MongoDBDa
4347
return errToReturn
4448
}
4549

50+
func (g *MongoDBDatabaseGarbageCollector) CleanFromSpec(database *infrav1beta1.MongoDBDatabase) error {
51+
rootPassword, err := g.cw.GetRootPassword(database.Spec.RootSecretLookup.Name, database.Spec.RootSecretLookup.Namespace,
52+
database.Spec.RootSecretLookup.Field)
53+
if err != nil {
54+
// no point in proceeding. In future could also try with Spec credential
55+
return err
56+
}
57+
mongoDBServer, err := g.r.ServerCache.Get(database.Spec.HostName, database.Spec.RootUsername, rootPassword,
58+
database.Spec.RootAuthenticationDatabase)
59+
if err != nil {
60+
// no point in proceeding. In future could also try with Spec credential
61+
return err
62+
}
63+
var errToReturn error
64+
for _, credential := range database.Spec.Credentials {
65+
errToReturn = mongoDBServer.DropUser(database.Status.DatabaseStatus.Name, credential.UserName)
66+
}
67+
return errToReturn
68+
}
69+
4670
// if host or database changed in spec, try to clean on old host/database
4771
func (g *MongoDBDatabaseGarbageCollector) handleHostOrDatabaseChange(mongoDBServer *mongodb.MongoDBServer, database *infrav1beta1.MongoDBDatabase) error {
4872
var errToReturn error

controllers/postgresqldatabase_controller.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ import (
2828
ctrl "sigs.k8s.io/controller-runtime"
2929
"sigs.k8s.io/controller-runtime/pkg/client"
3030
"sigs.k8s.io/controller-runtime/pkg/controller"
31+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
32+
)
33+
34+
const (
35+
PostgreSQLDatabaseControllerFinalizer = "infra.finalizers.doodle.com"
3136
)
3237

3338
// PostgreSQLDatabaseReconciler reconciles a PostgreSQLDatabase object
@@ -63,6 +68,24 @@ func (r *PostgreSQLDatabaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result,
6368
return ctrl.Result{}, err
6469
}
6570

71+
// set finalizer
72+
if err := database.SetFinalizer(func() error {
73+
return r.Update(ctx, &database)
74+
}); err != nil {
75+
return reconcile.Result{}, err
76+
}
77+
78+
// finalize
79+
if finalized, err := database.Finalize(func() error {
80+
return r.Update(ctx, &database)
81+
}, func() error {
82+
return gc.CleanFromSpec(&database)
83+
}); err != nil {
84+
return reconcile.Result{}, err
85+
} else if finalized {
86+
return reconcile.Result{}, nil
87+
}
88+
6689
// set spec defaults. Does not mutate the spec, since we are not updating resource
6790
if err := database.SetDefaults(); err != nil {
6891
database.Status.DatabaseStatus.SetDatabaseStatus(infrav1beta1.Unavailable, err.Error(), "", "")
@@ -71,7 +94,7 @@ func (r *PostgreSQLDatabaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result,
7194
}
7295

7396
// Garbage Collection. If errors occur, log and proceed with reconciliation.
74-
if err := gc.Clean(&database); err != nil {
97+
if err := gc.CleanFromStatus(&database); err != nil {
7598
log.Info("Error while cleaning garbage", "error", err)
7699
}
77100

@@ -154,8 +177,14 @@ func (r *PostgreSQLDatabaseReconciler) SetupWithManager(mgr ctrl.Manager, maxCon
154177
}
155178

156179
func (r *PostgreSQLDatabaseReconciler) updateAndReturn(ctx *context.Context, database *infrav1beta1.PostgreSQLDatabase, log *logr.Logger) (ctrl.Result, error) {
157-
database.Status.LastUpdateTime = metav1.Now()
180+
now := metav1.Now()
181+
database.Status.LastUpdateTime = &now
158182
if err := r.Status().Update(*ctx, database); err != nil {
183+
if apierrors.IsConflict(err) {
184+
return ctrl.Result{
185+
Requeue: true,
186+
}, nil
187+
}
159188
(*log).Error(err, "unable to update PostgreSQLDatabase status")
160189
return ctrl.Result{}, err
161190
}

controllers/postgresqldatabase_garbage_collector.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ func NewPostgreSQLGarbageCollector(r *PostgreSQLDatabaseReconciler, cw *Controll
2323
/* For now, Garbage Collector does not drop databases, because we haven't decided if we want to delete all data.
2424
Possible avenue to proceed is to have a separate option flag/struct (to be set in Spec), that will force deletion of all garbage, including data.
2525
*/
26-
func (g *PostgreSQLGarbageCollector) Clean(database *infrav1beta1.PostgreSQLDatabase) error {
26+
func (g *PostgreSQLGarbageCollector) CleanFromStatus(database *infrav1beta1.PostgreSQLDatabase) error {
27+
// first time, nothing to clear
28+
if database.Status.LastUpdateTime == nil {
29+
return nil
30+
}
2731
rootPassword, err := g.cw.GetRootPassword(database.Status.DatabaseStatus.RootSecretLookup.Name, database.Status.DatabaseStatus.RootSecretLookup.Namespace,
2832
database.Status.DatabaseStatus.RootSecretLookup.Field)
2933
if err != nil {
@@ -43,6 +47,27 @@ func (g *PostgreSQLGarbageCollector) Clean(database *infrav1beta1.PostgreSQLData
4347
return errToReturn
4448
}
4549

50+
func (g *PostgreSQLGarbageCollector) CleanFromSpec(database *infrav1beta1.PostgreSQLDatabase) error {
51+
rootPassword, err := g.cw.GetRootPassword(database.Spec.RootSecretLookup.Name, database.Spec.RootSecretLookup.Namespace,
52+
database.Spec.RootSecretLookup.Field)
53+
if err != nil {
54+
// no point in proceeding. In future could also try with Spec credential
55+
return err
56+
}
57+
postgreSQLServer, err := g.r.ServerCache.Get(database.Spec.HostName, database.Spec.RootUsername, rootPassword,
58+
database.Status.DatabaseStatus.RootAuthenticationDatabase)
59+
if err != nil {
60+
// no point in proceeding. In future could also try with Spec credential
61+
return err
62+
}
63+
// if an error happens, just collect it and try to clean as much as possible. Return it at the end. This is a pattern in for most of this Garbage Collector.
64+
var errToReturn error
65+
for _, credential := range database.Spec.Credentials {
66+
errToReturn = postgreSQLServer.DropUser(database.Spec.DatabaseName, credential.UserName)
67+
}
68+
return errToReturn
69+
}
70+
4671
// if host or database changed in spec, try to clean on old host/database
4772
func (g *PostgreSQLGarbageCollector) handleHostOrDatabaseChange(postgreSQLServer *postgresqlAPI.PostgreSQLServer, database *infrav1beta1.PostgreSQLDatabase) error {
4873
var errToReturn error

0 commit comments

Comments
 (0)