Skip to content

Commit af80f46

Browse files
committed
add MariaDBAccount finalizer to Secret (and remove on delete)
in mariadbdatabase_funcs, the EnsureMariaDBAccount function called by external controllers adds a finalizer for that calling controller to the Secret referenced by the MariaDBAccount. This seems a little off, since the Secret is most immediately needed by the MariaDBAccount CR itself, and the controller refers to that MariaDBAccount CR also. It seems more appropriate that MariaDBAccount itself should maintain its own finalizer on that Secret, so this logic is added there. The change here causes the API function EnsureMariaDBAccount to add a finalizer to the secret that is local to the mariadbaccount, rather than the helper passed for the calling controller. Existing "remove finalizer" calls which look for the calling controller's finalizer tag in the secret are maintained however to assist with backwards compatibility. This comes up now because we are seeking to add a new class of system-level MariaDBAccount that is used only by the Galera controller itself, but also that these accounts (really all accounts, but mainly the system ones) will support in-place password changes by updating the name of the Secret to be used, implying the old one is no longer needed once the change takes place; it therefore is most appropriate that MariaDBAccount maintain its own finalizers on these secrets.
1 parent 29261cd commit af80f46

File tree

4 files changed

+178
-46
lines changed

4 files changed

+178
-46
lines changed

api/v1beta1/mariadbdatabase_funcs.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,9 @@ func (d *Database) DeleteFinalizer(
449449
h *helper.Helper,
450450
) error {
451451

452+
// LEGACY: remove finalizer from the secret in terms of the caller.
453+
// we now don't add the caller's finalizer to the secret, we only add
454+
// the mariadbaccount finalizer.
452455
if d.secretObj != nil && controllerutil.RemoveFinalizer(d.secretObj, h.GetFinalizer()) {
453456
err := h.GetClient().Update(ctx, d.secretObj)
454457
if err != nil && !k8s_errors.IsNotFound(err) {
@@ -548,6 +551,9 @@ func DeleteDatabaseAndAccountFinalizers(
548551
return err
549552
}
550553

554+
// LEGACY: remove finalizer from the secret in terms of the caller.
555+
// we now don't add the caller's finalizer to the secret, we only add
556+
// the mariadbaccount finalizer.
551557
if err == nil && controllerutil.RemoveFinalizer(dbSecret, h.GetFinalizer()) {
552558
err := h.GetClient().Update(ctx, dbSecret)
553559
if err != nil && !k8s_errors.IsNotFound(err) {
@@ -624,6 +630,9 @@ func DeleteUnusedMariaDBAccountFinalizers(
624630
return err
625631
}
626632

633+
// LEGACY: remove finalizer from the secret in terms of the caller.
634+
// we now don't add the caller's finalizer to the secret, we only add
635+
// the mariadbaccount finalizer.
627636
if dbSecret != nil && controllerutil.RemoveFinalizer(dbSecret, h.GetFinalizer()) {
628637
err := h.GetClient().Update(ctx, dbSecret)
629638
if err != nil && !k8s_errors.IsNotFound(err) {
@@ -704,12 +713,6 @@ func createOrPatchAccountAndSecret(
704713
return err
705714
}
706715

707-
// add calling CR finalizer to accountSecret first. controllers use
708-
// GetDatabaseByNameAndAccount to locate the Database which is how
709-
// they remove finalizers. this will return not found if secret
710-
// is not present, so finalizer will keep it around
711-
controllerutil.AddFinalizer(accountSecret, h.GetFinalizer())
712-
713716
return nil
714717
})
715718

controllers/mariadbaccount_controller.go

Lines changed: 144 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ import (
2727
helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper"
2828
job "github.com/openstack-k8s-operators/lib-common/modules/common/job"
2929
"github.com/openstack-k8s-operators/lib-common/modules/common/secret"
30+
util "github.com/openstack-k8s-operators/lib-common/modules/common/util"
3031
databasev1beta1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
3132
mariadb "github.com/openstack-k8s-operators/mariadb-operator/pkg/mariadb"
3233
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
3334
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3435
"k8s.io/apimachinery/pkg/runtime"
35-
"k8s.io/apimachinery/pkg/types"
3636
"k8s.io/client-go/kubernetes"
3737
ctrl "sigs.k8s.io/controller-runtime"
3838
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -168,28 +168,10 @@ func (r *MariaDBAccountReconciler) reconcileCreate(
168168

169169
// account create
170170

171-
// ensure secret is present before running a job
172-
_, secretResult, err := secret.VerifySecret(
173-
ctx,
174-
types.NamespacedName{Name: instance.Spec.Secret, Namespace: instance.Namespace},
175-
[]string{databasev1beta1.DatabasePasswordSelector},
176-
r.Client,
177-
time.Duration(30)*time.Second,
178-
)
179-
if (err != nil || secretResult != ctrl.Result{}) {
180-
// Since the account secret should have been manually created by the user and referenced in the spec,
181-
// we treat this as a warning because it means that the service will not be able to start.
182-
instance.Status.Conditions.Set(condition.FalseCondition(
183-
databasev1beta1.MariaDBAccountReadyCondition,
184-
secret.ReasonSecretMissing,
185-
condition.SeverityWarning,
186-
databasev1beta1.MariaDBAccountSecretNotReadyMessage, err))
187-
188-
log.Info(fmt.Sprintf(
189-
"MariaDBAccount '%s' didn't find Secret '%s'; requeueing",
190-
instance.Name, instance.Spec.Secret))
191-
192-
return secretResult, client.IgnoreNotFound(err)
171+
// ensure secret is present, add a finalizer for mariadbaccount
172+
result, err = r.ensureAccountSecret(ctx, log, helper, instance)
173+
if (result != ctrl.Result{} || err != nil) {
174+
return result, err
193175
}
194176

195177
log.Info(fmt.Sprintf("Running account create '%s' MariaDBDatabase '%s'",
@@ -291,7 +273,11 @@ func (r *MariaDBAccountReconciler) reconcileDelete(
291273
// implemented in the database either, so remove all finalizers and
292274
// exit
293275
if k8s_errors.IsNotFound(err) {
294-
// remove finalizer from the MariaDBDatabase instance
276+
// remove MariaDBAccount finalizer from MariaDBDatabase - this takes the
277+
// form such as openstack.org/mariadbaccount-<accountname> (this naming
278+
// scheme allows multiple MariaDBAccounts to claim the same MariaDBDatabase
279+
// as a dependency) and allows a delete of the MariaDBDatabase to proceed
280+
// assuming no other finalizers
295281
if controllerutil.RemoveFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) {
296282
err = r.Update(ctx, mariadbDatabase)
297283

@@ -301,10 +287,12 @@ func (r *MariaDBAccountReconciler) reconcileDelete(
301287

302288
}
303289

304-
// remove local finalizer
305-
controllerutil.RemoveFinalizer(instance, helper.GetFinalizer())
306-
307-
return ctrl.Result{}, nil
290+
// remove finalizer "openstack.org/mariadbaccount" from both the
291+
// MariaDBAccount as well as the Secret which is referenced from the
292+
// MariaDBAccount, allowing both to be deleted assuming no other
293+
// finalizers
294+
err = r.removeAccountAndSecretFinalizer(ctx, helper, instance)
295+
return ctrl.Result{}, err
308296
} else if dbGalera == nil {
309297
return result, err
310298
}
@@ -342,18 +330,24 @@ func (r *MariaDBAccountReconciler) reconcileDelete(
342330
log.Info(fmt.Sprintf("Job %s hash added - %s", jobDef.Name, instance.Status.Hash[databasev1beta1.AccountDeleteHash]))
343331
}
344332

345-
// first, remove finalizer from the MariaDBDatabase instance
333+
// remove MariaDBAccount finalizer from MariaDBDatabase - this takes the
334+
// form such as openstack.org/mariadbaccount-<accountname> (this naming
335+
// scheme allows multiple MariaDBAccounts to claim the same MariaDBDatabase
336+
// as a dependency) and allows a delete of the MariaDBDatabase to proceed
337+
// assuming no other finalizers
346338
if controllerutil.RemoveFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) {
347339
err = r.Update(ctx, mariadbDatabase)
348340
if err != nil {
349341
return ctrl.Result{}, err
350342
}
351343
}
352344

353-
// then remove finalizer from our own instance
354-
controllerutil.RemoveFinalizer(instance, helper.GetFinalizer())
345+
// remove finalizer "openstack.org/mariadbaccount" from
346+
// both the MariaDBAccount as well as the Secret which is referenced
347+
// from the MariaDBAccount, allowing both to be deleted
348+
err = r.removeAccountAndSecretFinalizer(ctx, helper, instance)
355349

356-
return ctrl.Result{}, nil
350+
return ctrl.Result{}, err
357351
}
358352

359353
// getMariaDBDatabaseForCreate - waits for a MariaDBDatabase to be available in preparation
@@ -446,23 +440,35 @@ func (r *MariaDBAccountReconciler) getMariaDBDatabaseForDelete(ctx context.Conte
446440
instance.Name,
447441
))
448442

449-
controllerutil.RemoveFinalizer(instance, helper.GetFinalizer())
450-
return nil, ctrl.Result{}, nil
443+
// tried to locate the MariaDBDatabase for this MariaDBAccount,
444+
// but the MariaDBAccount does not reference one. Therefore this
445+
// MariaDBAccount doesn't actually exist in any database and is
446+
// safe to be deleted.
447+
// So remove finalizer "openstack.org/mariadbaccount" from
448+
// both the MariaDBAccount as well as the Secret which is referenced
449+
// from the MariaDBAccount, allowing both to be deleted
450+
err := r.removeAccountAndSecretFinalizer(ctx, helper, instance)
451+
return nil, ctrl.Result{}, err
451452
}
452453

453454
// locate the MariaDBDatabase object itself
454455
mariadbDatabase, err := r.getMariaDBDatabaseObject(ctx, instance, mariadbDatabaseName)
455456

456457
if err != nil {
457458
if k8s_errors.IsNotFound(err) {
458-
// not found. this implies MariaDBAccount has no database-level
459-
// entry either. Remove MariaDBAccount / secret finalizers and return
460459
log.Info(fmt.Sprintf(
461460
"MariaDBAccount '%s' Didn't find MariaDBDatabase '%s'; no account delete needed",
462461
instance.Name, mariadbDatabaseName))
463462

464-
controllerutil.RemoveFinalizer(instance, helper.GetFinalizer())
465-
return nil, ctrl.Result{}, nil
463+
// tried to locate the MariaDBDatabase for this MariaDBAccount,
464+
// but it no longer exists (or never did). Therefore this
465+
// MariaDBAccount doesn't actually exist in any database and is
466+
// safe to be deleted.
467+
// So remove finalizer "openstack.org/mariadbaccount" from
468+
// both the MariaDBAccount as well as the Secret which is referenced
469+
// from the MariaDBAccount, allowing both to be deleted
470+
err = r.removeAccountAndSecretFinalizer(ctx, helper, instance)
471+
return nil, ctrl.Result{}, err
466472
} else {
467473
// unhandled error; exit without change
468474
log.Error(err, "unhandled error retrieving MariaDBDatabase instance")
@@ -484,6 +490,11 @@ func (r *MariaDBAccountReconciler) getMariaDBDatabaseForDelete(ctx context.Conte
484490
"MariaDBAccount '%s' MariaDBDatabase '%s' not yet complete; no account delete needed",
485491
instance.Name, mariadbDatabaseName))
486492

493+
// remove MariaDBAccount finalizer from MariaDBDatabase - this takes
494+
// the form such as openstack.org/mariadbaccount-<accountname> (this
495+
// naming scheme allows multiple MariaDBAccounts to claim the same
496+
// MariaDBDatabase as a dependency) and allows a delete of the
497+
// MariaDBDatabase to proceed assuming no other finalizers
487498
if controllerutil.RemoveFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) {
488499
err = r.Update(ctx, mariadbDatabase)
489500

@@ -492,8 +503,15 @@ func (r *MariaDBAccountReconciler) getMariaDBDatabaseForDelete(ctx context.Conte
492503
}
493504
}
494505

495-
controllerutil.RemoveFinalizer(instance, helper.GetFinalizer())
496-
return nil, ctrl.Result{}, nil
506+
// tried to locate the MariaDBDatabase for this MariaDBAccount, got the
507+
// MariaDBDatabase CR, and saw that it's not in a ready state.
508+
// Therefore this MariaDBAccount doesn't actually exist in any database
509+
// and is safe to be deleted.
510+
// remove finalizer "openstack.org/mariadbaccount" from
511+
// both the MariaDBAccount as well as the Secret which is referenced
512+
// from the MariaDBAccount
513+
err = r.removeAccountAndSecretFinalizer(ctx, helper, instance)
514+
return nil, ctrl.Result{}, err
497515
}
498516

499517
// return MariaDBDatabase where account delete flow will then continue
@@ -572,3 +590,89 @@ func (r *MariaDBAccountReconciler) getMariaDBDatabaseObject(ctx context.Context,
572590
return mariaDBDatabase, nil
573591

574592
}
593+
594+
// ensureAccountSecret - ensures the Secret exists, is valid, adds a finalizer.
595+
// includes requeue for secret does not exist
596+
func (r *MariaDBAccountReconciler) ensureAccountSecret(
597+
ctx context.Context,
598+
log logr.Logger,
599+
h *helper.Helper,
600+
instance *databasev1beta1.MariaDBAccount,
601+
) (ctrl.Result, error) {
602+
603+
secretName := instance.Spec.Secret
604+
secretNamespace := instance.Namespace
605+
secretObj, _, err := secret.GetSecret(ctx, h, secretName, secretNamespace)
606+
if err != nil {
607+
// Since the account secret should have been manually created by the user and referenced in the spec,
608+
// we treat this as a warning because it means that the service will not be able to start.
609+
if k8s_errors.IsNotFound(err) {
610+
instance.Status.Conditions.Set(condition.FalseCondition(
611+
databasev1beta1.MariaDBAccountReadyCondition,
612+
secret.ReasonSecretMissing,
613+
condition.SeverityWarning,
614+
databasev1beta1.MariaDBAccountSecretNotReadyMessage, err))
615+
616+
log.Info(fmt.Sprintf(
617+
"MariaDBAccount '%s' didn't find Secret '%s'; requeueing",
618+
instance.Name, instance.Spec.Secret))
619+
620+
return ctrl.Result{RequeueAfter: time.Duration(30) * time.Second}, nil
621+
622+
} else {
623+
return ctrl.Result{}, err
624+
}
625+
}
626+
627+
var expectedFields = []string{databasev1beta1.DatabasePasswordSelector}
628+
629+
// collect the secret values the caller expects to exist
630+
for _, field := range expectedFields {
631+
_, ok := secretObj.Data[field]
632+
if !ok {
633+
err := fmt.Errorf("%w: field %s not found in Secret %s", util.ErrFieldNotFound, field, secretName)
634+
return ctrl.Result{}, err
635+
}
636+
}
637+
638+
// set finalizer "openstack.org/mariadbaccount" on the Secret that the
639+
// MariaDBAccount references in its "secret" field, preventing the Secret
640+
// from being fully deleted as long as the MariaDBAccount maintains this
641+
// finalizer. As this Secret references the database password that was
642+
// used with this MariaDBAccount, the MariaDBAccount itself must have
643+
// access to this Secret as long as it corresponds to a real database
644+
// account
645+
if controllerutil.AddFinalizer(secretObj, h.GetFinalizer()) {
646+
err = r.Update(ctx, secretObj)
647+
if err != nil {
648+
return ctrl.Result{}, err
649+
}
650+
}
651+
652+
return ctrl.Result{}, err
653+
}
654+
655+
// removeAccountAndSecretFinalizer - removes finalizer from mariadbaccount as well
656+
// as current primary secret
657+
func (r *MariaDBAccountReconciler) removeAccountAndSecretFinalizer(ctx context.Context,
658+
helper *helper.Helper, instance *databasev1beta1.MariaDBAccount) error {
659+
660+
accountSecret, _, err := secret.GetSecret(ctx, helper, instance.Spec.Secret, instance.Namespace)
661+
662+
if err == nil {
663+
if controllerutil.RemoveFinalizer(accountSecret, helper.GetFinalizer()) {
664+
err = r.Update(ctx, accountSecret)
665+
if err != nil {
666+
return err
667+
}
668+
}
669+
} else if !k8s_errors.IsNotFound(err) {
670+
return err
671+
}
672+
673+
// will take effect when reconcile ends
674+
controllerutil.RemoveFinalizer(instance, helper.GetFinalizer())
675+
676+
return nil
677+
678+
}

tests/chainsaw/tests/account/chainsaw-test.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ spec:
2525

2626
- name: create account without secret
2727
description: account CR has to wait for a secret to create account in the database
28+
# we will delete the account manually
29+
skipDelete: true
2830
try:
2931
- apply:
3032
file: account.yaml
@@ -33,8 +35,20 @@ spec:
3335

3436
- name: add secret and finish account creation
3537
description: make sure the account is created in the database
38+
# we will delete the secret manually
39+
skipDelete: true
3640
try:
3741
- apply:
3842
file: account-secret.yaml
3943
- assert:
4044
file: account-assert.yaml
45+
finally:
46+
- delete:
47+
# delete account first, otherwise the secret can't be deleted b.c.
48+
# it has a finalizer from the account
49+
# perhaps I shouldn't have allowed MariaDBAccount to be created without
50+
# a secret, but here we are...
51+
file: account.yaml
52+
- delete:
53+
# then delete the secret
54+
file: account-secret.yaml

tests/kuttl/tests/account_create/04-assert.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,14 @@ status:
2323
reason: Ready
2424
status: "True"
2525
type: MariaDBServerReady
26+
---
27+
apiVersion: v1
28+
data:
29+
DatabasePassword: ZGJzZWNyZXQx
30+
kind: Secret
31+
metadata:
32+
name: some-db-secret
33+
# ensure finalizer was added
34+
finalizers:
35+
- openstack.org/mariadbaccount
36+
type: Opaque

0 commit comments

Comments
 (0)