Skip to content

Commit 7520284

Browse files
committed
Mongodb garbage collector
1 parent ee6d518 commit 7520284

File tree

9 files changed

+211
-33
lines changed

9 files changed

+211
-33
lines changed

api/v1beta1/mongodb_types.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,16 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"errors"
2021
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2122
)
2223

24+
// defaults
25+
const (
26+
DEFAULT_MONGODB_ROOT_USER = "root"
27+
DEFAULT_MONGODB_ROOT_AUTHENTICATION_DATABASE = "admin"
28+
)
29+
2330
type MongoDBRootSecretLookup struct {
2431
Name string `json:"name"`
2532
Namespace string `json:"namespace"`
@@ -74,6 +81,56 @@ type MongoDBList struct {
7481
Items []MongoDB `json:"items"`
7582
}
7683

84+
/*
85+
Alignes credentials status with spec by removing unneeded statuses. Mutates the original.
86+
Returns removed statuses.
87+
*/
88+
func (mongodb *MongoDB) RemoveUnneededCredentialsStatus() *CredentialsStatus {
89+
removedStatuses := make(CredentialsStatus, 0)
90+
statuses := &mongodb.Status.CredentialsStatus
91+
for i := 0; i < len(*statuses); i++ {
92+
status := (*statuses)[i]
93+
found := false
94+
if status != nil {
95+
for _, credential := range mongodb.Spec.Credentials {
96+
if credential.UserName == status.Username {
97+
found = true
98+
}
99+
}
100+
}
101+
if !found {
102+
removedStatuses = append(removedStatuses, status)
103+
s := append((*statuses)[:i], (*statuses)[i+1:]...)
104+
statuses = &s
105+
i--
106+
}
107+
}
108+
mongodb.Status.CredentialsStatus = *statuses
109+
return &removedStatuses
110+
}
111+
112+
func (this *MongoDB) SetDefaults() error {
113+
if this.Spec.RootUsername == "" {
114+
this.Spec.RootUsername = DEFAULT_MONGODB_ROOT_USER
115+
}
116+
if this.Spec.RootAuthenticationDatabase == "" {
117+
this.Spec.RootAuthenticationDatabase = DEFAULT_MONGODB_ROOT_AUTHENTICATION_DATABASE
118+
}
119+
if this.Spec.RootSecretLookup.Name == "" {
120+
return errors.New("must specify root secret")
121+
}
122+
if this.Spec.RootSecretLookup.Field == "" {
123+
return errors.New("must specify root secret field")
124+
}
125+
if this.Spec.RootSecretLookup.Namespace == "" {
126+
this.Spec.RootSecretLookup.Namespace = this.ObjectMeta.Namespace
127+
}
128+
if this.Status.CredentialsStatus == nil || len(this.Status.CredentialsStatus) == 0 {
129+
this.Status.CredentialsStatus = make([]*CredentialStatus, 0)
130+
}
131+
return nil
132+
}
133+
77134
func init() {
78135
SchemeBuilder.Register(&MongoDB{}, &MongoDBList{})
79136
}

api/v1beta1/postgresql_types.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import (
2323

2424
// defaults
2525
const (
26-
DEFAULT_POSTGRESQL_ROOT_USER = "postgres"
27-
DEFAULT_ROOT_AUTHENTICATION_DATABASE = "postgres"
26+
DEFAULT_POSTGRESQL_ROOT_USER = "postgres"
27+
DEFAULT_POSTGRESQL_ROOT_AUTHENTICATION_DATABASE = "postgres"
2828
)
2929

3030
type PostgreSQLRootSecretLookup struct {
@@ -114,7 +114,7 @@ func (this *PostgreSQL) SetDefaults() error {
114114
this.Spec.RootUsername = DEFAULT_POSTGRESQL_ROOT_USER
115115
}
116116
if this.Spec.RootAuthenticationDatabase == "" {
117-
this.Spec.RootAuthenticationDatabase = DEFAULT_ROOT_AUTHENTICATION_DATABASE
117+
this.Spec.RootAuthenticationDatabase = DEFAULT_POSTGRESQL_ROOT_AUTHENTICATION_DATABASE
118118
}
119119
if this.Spec.RootSecretLookup.Name == "" {
120120
return errors.New("must specify root secret")

api/v1beta1/zz_generated.deepcopy.go

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

common/db/mongodb/repository.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ func (m *MongoDBServer) updateUserPasswordAndRoles(database string, username str
132132
return nil
133133
}
134134

135+
func (m *MongoDBServer) DropUser(database string, username string) error {
136+
command := &bson.D{primitive.E{Key: "dropUser", Value: username}}
137+
r := m.runCommand(database, command)
138+
if _, err := r.DecodeBytes(); err != nil {
139+
return err
140+
}
141+
return nil
142+
}
143+
135144
func (m *MongoDBServer) runCommand(database string, command *bson.D) *mongo.SingleResult {
136145
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
137146
defer cancel()

common/db/postgresql/repository.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,21 @@ func (s *PostgreSQLServer) CreateDatabaseIfNotExists(database string) error {
6363
}
6464
}
6565

66-
func (s *PostgreSQLServer) SetupUser(user string, password string, database string) error {
66+
func (s *PostgreSQLServer) SetupUser(database string, user string, password string) error {
6767
if err := s.createUserIfNotExists(user); err != nil {
6868
return err
6969
}
7070
if err := s.setPasswordForUser(user, password); err != nil {
7171
return err
7272
}
73-
if err := s.grantAllPrivileges(user, database); err != nil {
73+
if err := s.grantAllPrivileges(database, user); err != nil {
7474
return err
7575
}
7676
return nil
7777
}
7878

79-
func (s *PostgreSQLServer) DropUser(user string, database string) error {
80-
if err := s.revokeAllPrivileges(user, database); err != nil {
79+
func (s *PostgreSQLServer) DropUser(database string, user string) error {
80+
if err := s.revokeAllPrivileges(database, user); err != nil {
8181
return err
8282
}
8383
if err := s.dropUserIfNotExist(user); err != nil {
@@ -139,14 +139,14 @@ func (s *PostgreSQLServer) setPasswordForUser(user string, password string) erro
139139
return nil
140140
}
141141

142-
func (s *PostgreSQLServer) grantAllPrivileges(user string, database string) error {
142+
func (s *PostgreSQLServer) grantAllPrivileges(database string, user string) error {
143143
if _, err := s.dbpool.Exec(context.Background(), fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE \"%s\" TO \"%s\";", database, user)); err != nil {
144144
return err
145145
}
146146
return nil
147147
}
148148

149-
func (s *PostgreSQLServer) revokeAllPrivileges(user string, database string) error {
149+
func (s *PostgreSQLServer) revokeAllPrivileges(database string, user string) error {
150150
if _, err := s.dbpool.Exec(context.Background(), fmt.Sprintf("REVOKE ALL PRIVILEGES ON DATABASE \"%s\" FROM \"%s\";", database, user)); err != nil {
151151
return err
152152
}

controllers/mongodb_controller.go

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,15 @@ package controllers
1818

1919
import (
2020
"context"
21+
infrav1beta1 "github.com/doodlescheduling/kubedb/api/v1beta1"
22+
mongodbAPI "github.com/doodlescheduling/kubedb/common/db/mongodb"
2123
vaultAPI "github.com/doodlescheduling/kubedb/common/vault"
22-
"github.com/pkg/errors"
24+
"github.com/go-logr/logr"
2325
apierrors "k8s.io/apimachinery/pkg/api/errors"
2426
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25-
26-
"github.com/go-logr/logr"
2727
"k8s.io/apimachinery/pkg/runtime"
2828
ctrl "sigs.k8s.io/controller-runtime"
2929
"sigs.k8s.io/controller-runtime/pkg/client"
30-
31-
infrav1beta1 "github.com/doodlescheduling/kubedb/api/v1beta1"
32-
mongodbAPI "github.com/doodlescheduling/kubedb/common/db/mongodb"
3330
)
3431

3532
// MongoDBReconciler reconciles a MongoDB object
@@ -43,50 +40,71 @@ type MongoDBReconciler struct {
4340

4441
// +kubebuilder:rbac:groups=infra.doodle.com,resources=mongodbs,verbs=get;list;watch;create;update;patch;delete
4542
// +kubebuilder:rbac:groups=infra.doodle.com,resources=mongodbs/status,verbs=get;update;patch
43+
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
4644

4745
func (r *MongoDBReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
4846
ctx := context.Background()
4947
log := r.Log.WithValues("mongodb", req.NamespacedName)
5048

5149
// common controller functions
5250
cw := NewControllerWrapper(*r, &ctx)
51+
// garbage collector
52+
gc := NewMongoDBGarbageCollector(r, cw, &log)
5353

54+
// get mongodb resource by namespaced name
5455
var mongodb infrav1beta1.MongoDB
5556
if err := r.Get(ctx, req.NamespacedName, &mongodb); err != nil {
5657
if apierrors.IsNotFound(err) {
58+
// resource no longer present. Consider dropping a database? What about data, it will be lost.. Probably acceptable for devboxes
59+
// How to do it, though? Resource doesn't exist anymore, so we need to list all databases and all manifests and compare?
5760
return ctrl.Result{}, nil
5861
}
59-
return ctrl.Result{}, errors.Wrap(err, "unable to fetch Mongodb")
62+
return ctrl.Result{}, err
6063
}
6164

62-
s := make(infrav1beta1.CredentialsStatus, 0)
63-
mongodb.Status.CredentialsStatus = s
65+
// set spec defaults. Does not mutate the spec, since we are not updating resource
66+
if err := mongodb.SetDefaults(); err != nil {
67+
mongodb.Status.DatabaseStatus.SetDatabaseStatus(infrav1beta1.Unavailable, err.Error(), "", "")
68+
mongodb.Status.CredentialsStatus = make(infrav1beta1.CredentialsStatus, 0)
69+
return r.updateAndReturn(&ctx, &mongodb, &log)
70+
}
6471

65-
// root password
72+
// Garbage Collection. If errors occur, log and proceed with reconciliation.
73+
if err := gc.Clean(&mongodb); err != nil {
74+
log.Info("Error while cleaning garbage", "error", err)
75+
}
76+
77+
// get root database password from k8s secret
6678
rootPassword, err := cw.GetRootPassword(mongodb.Spec.RootSecretLookup.Name, mongodb.Spec.RootSecretLookup.Namespace, mongodb.Spec.RootSecretLookup.Field)
6779
if err != nil {
6880
mongodb.Status.DatabaseStatus.SetDatabaseStatus(infrav1beta1.Unavailable, err.Error(), "", "")
6981
mongodb.Status.CredentialsStatus = make(infrav1beta1.CredentialsStatus, 0)
7082
return r.updateAndReturn(&ctx, &mongodb, &log)
7183
}
7284

73-
// mongoDB server
85+
// mongoDB connection to spec host, cached
7486
mongoDBServer, err := r.ServerCache.Get(mongodb.Spec.HostName, mongodb.Spec.RootUsername, rootPassword, mongodb.Spec.RootAuthenticationDatabase)
7587
if err != nil {
76-
log.Error(err, "Error while connecting to mongodb")
77-
mongodb.Status.DatabaseStatus.Status = infrav1beta1.Unavailable
78-
mongodb.Status.DatabaseStatus.Message = err.Error()
88+
mongodb.Status.DatabaseStatus.SetDatabaseStatus(infrav1beta1.Unavailable, err.Error(), "", mongodb.Spec.HostName).
89+
WithUsername(mongodb.Spec.RootUsername).
90+
WithAuthDatabase(mongodb.Spec.RootAuthenticationDatabase).
91+
WithRootSecretLookup(mongodb.Spec.RootSecretLookup.Name, mongodb.Spec.RootSecretLookup.Namespace, mongodb.Spec.RootSecretLookup.Field)
92+
mongodb.Status.CredentialsStatus = make(infrav1beta1.CredentialsStatus, 0)
7993
return r.updateAndReturn(&ctx, &mongodb, &log)
8094
}
8195

8296
// vault connection, cached
8397
vault, err := r.VaultCache.Get(mongodb.Spec.RootSecretLookup.Name)
8498
if err != nil {
85-
mongodb.Status.DatabaseStatus.SetDatabaseStatus(infrav1beta1.Unavailable, err.Error(), "", mongodb.Spec.HostName)
99+
mongodb.Status.DatabaseStatus.SetDatabaseStatus(infrav1beta1.Unavailable, err.Error(), "", mongodb.Spec.HostName).
100+
WithUsername(mongodb.Spec.RootUsername).
101+
WithAuthDatabase(mongodb.Spec.RootAuthenticationDatabase).
102+
WithRootSecretLookup(mongodb.Spec.RootSecretLookup.Name, mongodb.Spec.RootSecretLookup.Namespace, mongodb.Spec.RootSecretLookup.Field)
86103
mongodb.Status.CredentialsStatus = make(infrav1beta1.CredentialsStatus, 0)
87104
return r.updateAndReturn(&ctx, &mongodb, &log)
88105
}
89106

107+
// setup credentials as per spec
90108
for _, credential := range mongodb.Spec.Credentials {
91109
username := credential.UserName
92110
mongodbCredentialStatus := mongodb.Status.CredentialsStatus.FindOrCreate(username, func(status *infrav1beta1.CredentialStatus) bool {
@@ -105,8 +123,12 @@ func (r *MongoDBReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
105123
mongodbCredentialStatus.SetCredentialsStatus(infrav1beta1.Available, "Credentials up.")
106124
}
107125
}
108-
mongodb.Status.DatabaseStatus.Status = infrav1beta1.Available
109-
mongodb.Status.DatabaseStatus.Message = "Database up."
126+
127+
// setup database status - database is automatically set up with credentials
128+
mongodb.Status.DatabaseStatus.SetDatabaseStatus(infrav1beta1.Available, "Database up.", mongodb.Spec.DatabaseName, mongodb.Spec.HostName).
129+
WithUsername(mongodb.Spec.RootUsername).
130+
WithAuthDatabase(mongodb.Spec.RootAuthenticationDatabase).
131+
WithRootSecretLookup(mongodb.Spec.RootSecretLookup.Name, mongodb.Spec.RootSecretLookup.Namespace, mongodb.Spec.RootSecretLookup.Field)
110132

111133
return r.updateAndReturn(&ctx, &mongodb, &log)
112134
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package controllers
2+
3+
import (
4+
infrav1beta1 "github.com/doodlescheduling/kubedb/api/v1beta1"
5+
"github.com/doodlescheduling/kubedb/common/db/mongodb"
6+
"github.com/go-logr/logr"
7+
)
8+
9+
type MongoDBGarbageCollector struct {
10+
r *MongoDBReconciler
11+
cw *ControllerWrapper
12+
log *logr.Logger
13+
}
14+
15+
func NewMongoDBGarbageCollector(r *MongoDBReconciler, cw *ControllerWrapper, log *logr.Logger) *MongoDBGarbageCollector {
16+
return &MongoDBGarbageCollector{
17+
r: r,
18+
cw: cw,
19+
log: log,
20+
}
21+
}
22+
23+
/* For now, Garbage Collector does not drop databases, because we haven't decided if we want to delete all data.
24+
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.
25+
*/
26+
func (g *MongoDBGarbageCollector) Clean(mongodb *infrav1beta1.MongoDB) error {
27+
rootPassword, err := g.cw.GetRootPassword(mongodb.Status.DatabaseStatus.RootSecretLookup.Name, mongodb.Status.DatabaseStatus.RootSecretLookup.Namespace,
28+
mongodb.Status.DatabaseStatus.RootSecretLookup.Field)
29+
if err != nil {
30+
// no point in proceeding. In future could also try with Spec credential
31+
return err
32+
}
33+
mongoDBServer, err := g.r.ServerCache.Get(mongodb.Status.DatabaseStatus.Host, mongodb.Status.DatabaseStatus.RootUsername, rootPassword,
34+
mongodb.Status.DatabaseStatus.RootAuthenticationDatabase)
35+
if err != nil {
36+
// no point in proceeding. In future could also try with Spec credential
37+
return err
38+
}
39+
// 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.
40+
var errToReturn error
41+
errToReturn = g.HandleHostOrDatabaseChange(mongoDBServer, mongodb)
42+
errToReturn = g.HandleUnneededCredentials(mongoDBServer, mongodb)
43+
return errToReturn
44+
}
45+
46+
// if host or database changed in spec, try to clean on old host/database
47+
func (g *MongoDBGarbageCollector) HandleHostOrDatabaseChange(mongoDBServer *mongodb.MongoDBServer, mongodb *infrav1beta1.MongoDB) error {
48+
var errToReturn error
49+
if (mongodb.Status.DatabaseStatus.Host != "" && mongodb.Spec.HostName != mongodb.Status.DatabaseStatus.Host) ||
50+
(mongodb.Status.DatabaseStatus.Name != "" && mongodb.Spec.DatabaseName != mongodb.Status.DatabaseStatus.Name) {
51+
mongodb.Status.CredentialsStatus.ForEach(func(status *infrav1beta1.CredentialStatus) {
52+
err := mongoDBServer.DropUser(mongodb.Status.DatabaseStatus.Name, status.Username)
53+
if err != nil {
54+
errToReturn = err
55+
} else {
56+
(*g.log).Info("Deleted user on database", "user", status.Username, "database", mongodb.Status.DatabaseStatus.Name)
57+
}
58+
})
59+
}
60+
return errToReturn
61+
}
62+
63+
func (g *MongoDBGarbageCollector) HandleUnneededCredentials(mongoDBServer *mongodb.MongoDBServer, mongodb *infrav1beta1.MongoDB) error {
64+
var errToReturn error
65+
// - garbage collection
66+
// remove all statuses for credentials that are no longer required by spec, and delete users in database
67+
mongodb.RemoveUnneededCredentialsStatus().ForEach(func(status *infrav1beta1.CredentialStatus) {
68+
errToReturn = mongoDBServer.DropUser(mongodb.Status.DatabaseStatus.Name, status.Username)
69+
})
70+
return errToReturn
71+
}

controllers/postgresql_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func (r *PostgreSQLReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
9292
postgresql.Status.CredentialsStatus = make(infrav1beta1.CredentialsStatus, 0)
9393
return r.updateAndReturn(&ctx, &postgresql, &log)
9494
}
95+
9596
// vault connection, cached
9697
vault, err := r.VaultCache.Get(postgresql.Spec.RootSecretLookup.Name)
9798
if err != nil {
@@ -133,7 +134,7 @@ func (r *PostgreSQLReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
133134
password := vaultResponse.Secret
134135

135136
// setup user credentials and privileges
136-
if err := postgreSQLServer.SetupUser(username, password, postgresql.Spec.DatabaseName); err != nil {
137+
if err := postgreSQLServer.SetupUser(postgresql.Spec.DatabaseName, username, password); err != nil {
137138
postgreSQLCredentialStatus.SetCredentialsStatus(infrav1beta1.Unavailable, err.Error())
138139
} else {
139140
postgreSQLCredentialStatus.SetCredentialsStatus(infrav1beta1.Available, "Credentials up.")

0 commit comments

Comments
 (0)