diff --git a/api/bases/mariadb.openstack.org_galeras.yaml b/api/bases/mariadb.openstack.org_galeras.yaml index 7715bf46..fae0aba0 100644 --- a/api/bases/mariadb.openstack.org_galeras.yaml +++ b/api/bases/mariadb.openstack.org_galeras.yaml @@ -78,8 +78,18 @@ spec: maximum: 3 minimum: 0 type: integer + rootDatabaseAccount: + description: |- + RootDatabaseAccount - name of MariaDBAccount which will be used to + generate root account / password. + this account is generated if not exists, and a name is chosen based + on a naming convention if not present + type: string secret: - description: Name of the secret to look for password keys + description: |- + Name of the legacy secret to locate the initial galera root + password + this field will be removed once scripts can adjust to using root_auth.sh type: string storageClass: description: Storage class to host the mariadb databases @@ -118,7 +128,6 @@ spec: required: - containerImage - replicas - - secret - storageClass - storageRequest type: object @@ -237,6 +246,9 @@ spec: the opentack-operator in the top-level CR (e.g. the ContainerImage) format: int64 type: integer + rootDatabaseSecret: + description: name of the Secret that is being used for the root password + type: string safeToBootstrap: description: Name of the node that can safely bootstrap a cluster type: string diff --git a/api/bases/mariadb.openstack.org_mariadbaccounts.yaml b/api/bases/mariadb.openstack.org_mariadbaccounts.yaml index 1bd1f029..84acbd21 100644 --- a/api/bases/mariadb.openstack.org_mariadbaccounts.yaml +++ b/api/bases/mariadb.openstack.org_mariadbaccounts.yaml @@ -48,6 +48,12 @@ spec: spec: description: MariaDBAccountSpec defines the desired state of MariaDBAccount properties: + accountType: + default: User + enum: + - User + - System + type: string requireTLS: default: false description: Account must use TLS to connect to the database @@ -108,6 +114,14 @@ spec: - type type: object type: array + currentSecret: + description: |- + the Secret that's currently in use for the account. + keeping a handle to this secret allows us to remove its finalizer + when it's replaced with a new one. It also is useful for storing + the current "root" secret separate from a newly proposed one which is + needed when changing the database root password. + type: string hash: additionalProperties: type: string diff --git a/api/v1beta1/conditions.go b/api/v1beta1/conditions.go index 5565a2af..c1c9cfba 100644 --- a/api/v1beta1/conditions.go +++ b/api/v1beta1/conditions.go @@ -48,6 +48,9 @@ const ( // ReasonDBServiceNameError - error getting the DB service hostname ReasonDBServiceNameError condition.Reason = "DatabaseServiceNameError" + // ReasonDBResourceDeleted - the galera resource has been marked for deletion + ReasonDBResourceDeleted condition.Reason = "DatabaseResourceDeleted" + // ReasonDBSync - Database sync in progress ReasonDBSync condition.Reason = "DBSync" ) @@ -82,8 +85,12 @@ const ( MariaDBServerNotBootstrappedMessage = "MariaDB / Galera server not bootstrapped" + MariaDBServerDeletedMessage = "MariaDB / Galera server has been marked for deletion" + MariaDBAccountReadyInitMessage = "MariaDBAccount create / drop not started" + MariaDBSystemAccountReadyMessage = "MariaDBAccount System account '%s' creation complete" + MariaDBAccountReadyMessage = "MariaDBAccount creation complete" MariaDBAccountNotReadyMessage = "MariaDBAccount is not present: %s" diff --git a/api/v1beta1/galera_types.go b/api/v1beta1/galera_types.go index 764b6349..82b79fdc 100644 --- a/api/v1beta1/galera_types.go +++ b/api/v1beta1/galera_types.go @@ -17,12 +17,12 @@ limitations under the License. package v1beta1 import ( + topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/tls" "github.com/openstack-k8s-operators/lib-common/modules/common/util" - topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" ) const ( @@ -50,9 +50,19 @@ type GaleraSpec struct { // GaleraSpec defines the desired state of Galera type GaleraSpecCore struct { - // Name of the secret to look for password keys - // +kubebuilder:validation:Required + // Name of the legacy secret to locate the initial galera root + // password + // this field will be removed once scripts can adjust to using root_auth.sh + // +kubebuilder:validation:Optional Secret string `json:"secret"` + + // RootDatabaseAccount - name of MariaDBAccount which will be used to + // generate root account / password. + // this account is generated if not exists, and a name is chosen based + // on a naming convention if not present + // +kubebuilder:validation:Optional + RootDatabaseAccount string `json:"rootDatabaseAccount"` + // Storage class to host the mariadb databases // +kubebuilder:validation:Required StorageClass string `json:"storageClass"` @@ -109,6 +119,9 @@ type GaleraAttributes struct { type GaleraStatus struct { // A map of database node attributes for each pod Attributes map[string]GaleraAttributes `json:"attributes,omitempty"` + // name of the Secret that is being used for the root password + // +kubebuilder:validation:Optional + RootDatabaseSecret string `json:"rootDatabaseSecret"` // Name of the node that can safely bootstrap a cluster SafeToBootstrap string `json:"safeToBootstrap,omitempty"` // Is the galera cluster currently running diff --git a/api/v1beta1/mariadbaccount_types.go b/api/v1beta1/mariadbaccount_types.go index 32819281..6b0b13f4 100644 --- a/api/v1beta1/mariadbaccount_types.go +++ b/api/v1beta1/mariadbaccount_types.go @@ -28,9 +28,6 @@ const ( // AccountDeleteHash hash AccountDeleteHash = "accountdelete" - // DbRootPassword selector for galera root account - DbRootPasswordSelector = "DbRootPassword" - // DatabasePassword selector for MariaDBAccount->Secret DatabasePasswordSelector = "DatabasePassword" ) @@ -48,10 +45,28 @@ type MariaDBAccountSpec struct { // Account must use TLS to connect to the database // +kubebuilder:default=false RequireTLS bool `json:"requireTLS"` + + // +kubebuilder:validation:Enum=User;System + // +kubebuilder:default=User + AccountType AccountType `json:"accountType,omitempty"` } +type AccountType string + +const ( + User AccountType = "User" + System AccountType = "System" +) + // MariaDBAccountStatus defines the observed state of MariaDBAccount type MariaDBAccountStatus struct { + // the Secret that's currently in use for the account. + // keeping a handle to this secret allows us to remove its finalizer + // when it's replaced with a new one. It also is useful for storing + // the current "root" secret separate from a newly proposed one which is + // needed when changing the database root password. + CurrentSecret string `json:"currentSecret,omitempty"` + // Deployment Conditions Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"` @@ -85,3 +100,11 @@ type MariaDBAccountList struct { func init() { SchemeBuilder.Register(&MariaDBAccount{}, &MariaDBAccountList{}) } + +func (mariadbAccount MariaDBAccount) IsSystemAccount() bool { + return mariadbAccount.Spec.AccountType == System +} + +func (mariadbAccount MariaDBAccount) IsUserAccount() bool { + return mariadbAccount.Spec.AccountType == "" || mariadbAccount.Spec.AccountType == User +} diff --git a/api/v1beta1/mariadbdatabase_funcs.go b/api/v1beta1/mariadbdatabase_funcs.go index 70936905..7d8e16b6 100644 --- a/api/v1beta1/mariadbdatabase_funcs.go +++ b/api/v1beta1/mariadbdatabase_funcs.go @@ -449,6 +449,9 @@ func (d *Database) DeleteFinalizer( h *helper.Helper, ) error { + // LEGACY: remove finalizer from the secret in terms of the caller. + // we now don't add the caller's finalizer to the secret, we only add + // the mariadbaccount finalizer. if d.secretObj != nil && controllerutil.RemoveFinalizer(d.secretObj, h.GetFinalizer()) { err := h.GetClient().Update(ctx, d.secretObj) if err != nil && !k8s_errors.IsNotFound(err) { @@ -538,6 +541,50 @@ func DeleteDatabaseAndAccountFinalizers( namespace string, ) error { + err := DeleteAccountFinalizers( + ctx, + h, + accountName, + namespace, + ) + if err != nil { + return err + } + + // also do a delete for "unused" MariaDBAccounts, associated with + // this MariaDBDatabase. + err = DeleteUnusedMariaDBAccountFinalizers( + ctx, h, name, accountName, namespace, + ) + if err != nil && !k8s_errors.IsNotFound(err) { + return err + } + + mariaDBDatabase, err := GetDatabase(ctx, h, name, namespace) + if err != nil && !k8s_errors.IsNotFound(err) { + return err + } else if err == nil && controllerutil.RemoveFinalizer(mariaDBDatabase, h.GetFinalizer()) { + err := h.GetClient().Update(ctx, mariaDBDatabase) + if err != nil && !k8s_errors.IsNotFound(err) { + return err + } + util.LogForObject(h, fmt.Sprintf("Removed finalizer %s from MariaDBDatabase %s", h.GetFinalizer(), mariaDBDatabase.Spec.Name), mariaDBDatabase) + } + + return nil +} + +// DeleteAccountFinalizers performs just the primary account + secret finalizer +// removal part of DeleteDatabaseAndAccountFinalizers +func DeleteAccountFinalizers( + ctx context.Context, + h *helper.Helper, + accountName string, + namespace string, +) error { + if accountName == "" { + return fmt.Errorf("Account name is blank") + } databaseAccount, err := GetAccount(ctx, h, accountName, namespace) if err != nil && !k8s_errors.IsNotFound(err) { return err @@ -548,6 +595,9 @@ func DeleteDatabaseAndAccountFinalizers( return err } + // LEGACY: remove finalizer from the secret in terms of the caller. + // we now don't add the caller's finalizer to the secret, we only add + // the mariadbaccount finalizer. if err == nil && controllerutil.RemoveFinalizer(dbSecret, h.GetFinalizer()) { err := h.GetClient().Update(ctx, dbSecret) if err != nil && !k8s_errors.IsNotFound(err) { @@ -566,26 +616,6 @@ func DeleteDatabaseAndAccountFinalizers( } } - // also do a delete for "unused" MariaDBAccounts, associated with - // this MariaDBDatabase. - err = DeleteUnusedMariaDBAccountFinalizers( - ctx, h, name, accountName, namespace, - ) - if err != nil && !k8s_errors.IsNotFound(err) { - return err - } - - mariaDBDatabase, err := GetDatabase(ctx, h, name, namespace) - if err != nil && !k8s_errors.IsNotFound(err) { - return err - } else if err == nil && controllerutil.RemoveFinalizer(mariaDBDatabase, h.GetFinalizer()) { - err := h.GetClient().Update(ctx, mariaDBDatabase) - if err != nil && !k8s_errors.IsNotFound(err) { - return err - } - util.LogForObject(h, fmt.Sprintf("Removed finalizer %s from MariaDBDatabase %s", h.GetFinalizer(), mariaDBDatabase.Spec.Name), mariaDBDatabase) - } - return nil } @@ -624,6 +654,9 @@ func DeleteUnusedMariaDBAccountFinalizers( return err } + // LEGACY: remove finalizer from the secret in terms of the caller. + // we now don't add the caller's finalizer to the secret, we only add + // the mariadbaccount finalizer. if dbSecret != nil && controllerutil.RemoveFinalizer(dbSecret, h.GetFinalizer()) { err := h.GetClient().Update(ctx, dbSecret) if err != nil && !k8s_errors.IsNotFound(err) { @@ -708,7 +741,6 @@ func createOrPatchAccountAndSecret( // GetDatabaseByNameAndAccount to locate the Database which is how // they remove finalizers. this will return not found if secret // is not present, so finalizer will keep it around - controllerutil.AddFinalizer(accountSecret, h.GetFinalizer()) return nil }) @@ -803,6 +835,32 @@ func EnsureMariaDBAccount(ctx context.Context, userNamePrefix string, ) (*MariaDBAccount, *corev1.Secret, error) { + return ensureMariaDBAccount( + ctx, helper, accountName, namespace, requireTLS, + userNamePrefix, "", "", map[string]string{}) + +} + +// EnsureMariaDBSystemAccount ensures a MariaDBAccount has been created for a given +// operator calling the function, and returns the MariaDBAccount and its +// Secret for use in consumption into a configuration. +// Unlike EnsureMariaDBAccount, the function accepts an exact username that +// expected to remain constant, supporting in-place password changes for the +// account. +func EnsureMariaDBSystemAccount(ctx context.Context, + helper *helper.Helper, + accountName string, galeraInstanceName string, namespace string, requireTLS bool, + exactUserName string, exactPassword string) (*MariaDBAccount, *corev1.Secret, error) { + return ensureMariaDBAccount( + ctx, helper, accountName, namespace, requireTLS, + "", exactUserName, exactPassword, map[string]string{"dbName": galeraInstanceName}) +} + +func ensureMariaDBAccount(ctx context.Context, + helper *helper.Helper, + accountName string, namespace string, requireTLS bool, + userNamePrefix string, exactUserName string, exactPassword string, labels map[string]string, +) (*MariaDBAccount, *corev1.Secret, error) { if accountName == "" { return nil, nil, fmt.Errorf("accountName is empty") } @@ -814,9 +872,20 @@ func EnsureMariaDBAccount(ctx context.Context, return nil, nil, err } - username, err := generateUniqueUsername(userNamePrefix) - if err != nil { - return nil, nil, err + var username string + var accountType AccountType + + if exactUserName == "" { + accountType = "User" + username, err = generateUniqueUsername(userNamePrefix) + if err != nil { + return nil, nil, err + } + } else if userNamePrefix != "" { + return nil, nil, fmt.Errorf("userNamePrefix and exactUserName are mutually exclusive") + } else { + accountType = "System" + username = exactUserName } account = &MariaDBAccount{ @@ -829,9 +898,10 @@ func EnsureMariaDBAccount(ctx context.Context, // MariaDBAccount once this is filled in }, Spec: MariaDBAccountSpec{ - UserName: username, - Secret: fmt.Sprintf("%s-db-secret", accountName), - RequireTLS: requireTLS, + UserName: username, + Secret: fmt.Sprintf("%s-db-secret", accountName), + RequireTLS: requireTLS, + AccountType: accountType, }, } @@ -841,6 +911,7 @@ func EnsureMariaDBAccount(ctx context.Context, if account.Spec.Secret == "" { account.Spec.Secret = fmt.Sprintf("%s-db-secret", accountName) } + } dbSecret, _, err := secret.GetSecret(ctx, helper, account.Spec.Secret, namespace) @@ -850,9 +921,14 @@ func EnsureMariaDBAccount(ctx context.Context, return nil, nil, err } - dbPassword, err := generateDBPassword() - if err != nil { - return nil, nil, err + var dbPassword string + if exactPassword == "" { + dbPassword, err = generateDBPassword() + if err != nil { + return nil, nil, err + } + } else { + dbPassword = exactPassword } dbSecret = &corev1.Secret{ @@ -866,7 +942,7 @@ func EnsureMariaDBAccount(ctx context.Context, } } - _, err = createOrPatchAccountAndSecret(ctx, helper, account, dbSecret, map[string]string{}) + _, err = createOrPatchAccountAndSecret(ctx, helper, account, dbSecret, labels) if err != nil { return nil, nil, err } @@ -882,6 +958,7 @@ func EnsureMariaDBAccount(ctx context.Context, ) return account, dbSecret, nil + } // generateUniqueUsername creates a MySQL-compliant database username based on diff --git a/config/crd/bases/mariadb.openstack.org_galeras.yaml b/config/crd/bases/mariadb.openstack.org_galeras.yaml index 7715bf46..fae0aba0 100644 --- a/config/crd/bases/mariadb.openstack.org_galeras.yaml +++ b/config/crd/bases/mariadb.openstack.org_galeras.yaml @@ -78,8 +78,18 @@ spec: maximum: 3 minimum: 0 type: integer + rootDatabaseAccount: + description: |- + RootDatabaseAccount - name of MariaDBAccount which will be used to + generate root account / password. + this account is generated if not exists, and a name is chosen based + on a naming convention if not present + type: string secret: - description: Name of the secret to look for password keys + description: |- + Name of the legacy secret to locate the initial galera root + password + this field will be removed once scripts can adjust to using root_auth.sh type: string storageClass: description: Storage class to host the mariadb databases @@ -118,7 +128,6 @@ spec: required: - containerImage - replicas - - secret - storageClass - storageRequest type: object @@ -237,6 +246,9 @@ spec: the opentack-operator in the top-level CR (e.g. the ContainerImage) format: int64 type: integer + rootDatabaseSecret: + description: name of the Secret that is being used for the root password + type: string safeToBootstrap: description: Name of the node that can safely bootstrap a cluster type: string diff --git a/config/crd/bases/mariadb.openstack.org_mariadbaccounts.yaml b/config/crd/bases/mariadb.openstack.org_mariadbaccounts.yaml index 1bd1f029..84acbd21 100644 --- a/config/crd/bases/mariadb.openstack.org_mariadbaccounts.yaml +++ b/config/crd/bases/mariadb.openstack.org_mariadbaccounts.yaml @@ -48,6 +48,12 @@ spec: spec: description: MariaDBAccountSpec defines the desired state of MariaDBAccount properties: + accountType: + default: User + enum: + - User + - System + type: string requireTLS: default: false description: Account must use TLS to connect to the database @@ -108,6 +114,14 @@ spec: - type type: object type: array + currentSecret: + description: |- + the Secret that's currently in use for the account. + keeping a handle to this secret allows us to remove its finalizer + when it's replaced with a new one. It also is useful for storing + the current "root" secret separate from a newly proposed one which is + needed when changing the database root password. + type: string hash: additionalProperties: type: string diff --git a/controllers/galera_controller.go b/controllers/galera_controller.go index 36a642f3..8ed1af36 100644 --- a/controllers/galera_controller.go +++ b/controllers/galera_controller.go @@ -33,7 +33,7 @@ import ( helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" "github.com/openstack-k8s-operators/lib-common/modules/common/labels" common_rbac "github.com/openstack-k8s-operators/lib-common/modules/common/rbac" - secret "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" "github.com/openstack-k8s-operators/lib-common/modules/common/service" commonstatefulset "github.com/openstack-k8s-operators/lib-common/modules/common/statefulset" "github.com/openstack-k8s-operators/lib-common/modules/common/tls" @@ -481,6 +481,21 @@ func (r *GaleraReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res Resources: []string{"services"}, Verbs: []string{"get", "list", "update", "patch"}, }, + { + APIGroups: []string{"mariadb.openstack.org"}, + Resources: []string{"galeras"}, + Verbs: []string{"get", "list"}, + }, + { + APIGroups: []string{"mariadb.openstack.org"}, + Resources: []string{"mariadbaccounts"}, + Verbs: []string{"get", "list"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get"}, + }, } rbacResult, err := common_rbac.ReconcileRbac(ctx, helper, instance, rbacRules) if err != nil { @@ -543,6 +558,8 @@ func (r *GaleraReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res log.Info("", "Kind", instance.Kind, "Name", instance.Name, "database service", service.Name, "operation", string(op)) } + instance.IsReady() + instance.Status.Conditions.MarkTrue(condition.CreateServiceReadyCondition, condition.CreateServiceReadyMessage) // Map of all resources that may cause a rolling service restart @@ -552,8 +569,10 @@ func (r *GaleraReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res clusterPropertiesEnv := make(map[string]env.Setter) // Check and hash inputs - // NOTE do not hash the db root password, as its change requires - // more orchestration than a simple rolling restart + + // ******** TEMPORARY ************ + // Pull the DbRootPassword from osp-secret to allow CI / external + // scripts to work, until they can adapt to root_auth.sh _, res, err := secret.VerifySecret( ctx, types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Secret}, @@ -571,15 +590,56 @@ func (r *GaleraReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res condition.InputReadyWaitingMessage)) return res, fmt.Errorf("OpenStack secret %s not found", instance.Spec.Secret) } + return ctrl.Result{}, err + } + + legacySecret, _, err := secret.GetSecret(ctx, helper, instance.Spec.Secret, instance.Namespace) + if err != nil { + return ctrl.Result{}, err + } + legacyRootPassword := string(legacySecret.Data["DbRootPassword"]) + // ******** END TEMPORARY ************ + + databaseAccountName := r.getRootMariadbAccountName(instance) + + mariaDBAccount, rootSecret, err := mariadbv1.EnsureMariaDBSystemAccount( + ctx, helper, databaseAccountName, + instance.Name, instance.Namespace, false, "root", legacyRootPassword) + + if err != nil { instance.Status.Conditions.Set(condition.FalseCondition( - condition.InputReadyCondition, + mariadbv1.MariaDBAccountReadyCondition, condition.ErrorReason, condition.SeverityWarning, - condition.InputReadyErrorMessage, + mariadbv1.MariaDBAccountNotReadyMessage, err.Error())) + return ctrl.Result{}, err } - instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + + // the current root secret for the MariaDBAccount name is copied out to Status. + // this allows us to avoid having to change the Spec, which seems to be not + // best practice here and also seems to interfere with the openstack + // controller's ability to see the Galera status as Completed (though this + // might only be due to CRDs not being in sync between mariadb-operator and + // openstack-operator during development) + // Also by trying to always use CurrentSecret if available, we try to keep + // instance.Status.RootDatabaseSecret as the secret that should work for + // root right now, independent of changes in the mariadbaccount name + // or its secret that have not been reconciled w/ a new job hash. + if mariaDBAccount.Status.CurrentSecret != "" { + instance.Status.RootDatabaseSecret = mariaDBAccount.Status.CurrentSecret + } else { + instance.Status.RootDatabaseSecret = rootSecret.Name + } + + instance.Status.Conditions.MarkTrue( + mariadbv1.MariaDBAccountReadyCondition, + mariadbv1.MariaDBSystemAccountReadyMessage, "root") + + instance.Status.Conditions.MarkTrue( + condition.InputReadyCondition, + condition.InputReadyMessage) // // TLS input validation @@ -930,7 +990,8 @@ func (r *GaleraReconciler) generateConfigMaps( ) error { log := GetLog(ctx, "galera") templateParameters := map[string]interface{}{ - "logToDisk": instance.Spec.LogToDisk, + "logToDisk": instance.Spec.LogToDisk, + "galeraInstanceName": instance.Name, } customData := make(map[string]string) customData[mariadbv1.CustomServiceConfigFile] = instance.Spec.CustomServiceConfig @@ -938,11 +999,12 @@ func (r *GaleraReconciler) generateConfigMaps( cms := []util.Template{ // ScriptsConfigMap { - Name: configMapNameForScripts(instance), - Namespace: instance.Namespace, - Type: util.TemplateTypeScripts, - InstanceType: instance.Kind, - Labels: map[string]string{}, + Name: configMapNameForScripts(instance), + Namespace: instance.Namespace, + Type: util.TemplateTypeScripts, + InstanceType: instance.Kind, + Labels: map[string]string{}, + ConfigOptions: templateParameters, }, // ConfigMap { @@ -1028,9 +1090,8 @@ func (r *GaleraReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -// GetDatabaseObject - returns either a Galera or MariaDB object (and an associated client.Object interface). +// GetDatabaseObject - returns a Galera object. // used by both MariaDBDatabaseReconciler and MariaDBAccountReconciler -// this will later return only Galera objects, so as a lookup it's part of the galera controller func GetDatabaseObject(ctx context.Context, clientObj client.Client, name string, namespace string) (*databasev1beta1.Galera, error) { dbGalera := &databasev1beta1.Galera{ ObjectMeta: metav1.ObjectMeta{ @@ -1084,6 +1145,14 @@ func (r *GaleraReconciler) findObjectsForSrc(ctx context.Context, src client.Obj return requests } +func (r *GaleraReconciler) getRootMariadbAccountName(instance *databasev1beta1.Galera) string { + databaseAccountName := instance.Spec.RootDatabaseAccount + if databaseAccountName == "" { + databaseAccountName = instance.Name + "-mariadb-root" + } + return databaseAccountName +} + func (r *GaleraReconciler) reconcileDelete(ctx context.Context, instance *databasev1beta1.Galera, helper *helper.Helper) (ctrl.Result, error) { helper.GetLogger().Info("Reconciling Service delete") @@ -1111,6 +1180,13 @@ func (r *GaleraReconciler) reconcileDelete(ctx context.Context, instance *databa return ctrlResult, err } + // remove finalizer from the system mariadbaccount and associated secret + err = mariadbv1.DeleteAccountFinalizers( + ctx, helper, r.getRootMariadbAccountName(instance), instance.Namespace) + if err != nil { + return ctrl.Result{}, err + } + // Service is deleted so remove the finalizer. controllerutil.RemoveFinalizer(instance, helper.GetFinalizer()) helper.GetLogger().Info("Reconciled Service delete successfully") diff --git a/controllers/mariadbaccount_controller.go b/controllers/mariadbaccount_controller.go index 7624fab2..c2c343ab 100644 --- a/controllers/mariadbaccount_controller.go +++ b/controllers/mariadbaccount_controller.go @@ -27,12 +27,13 @@ import ( helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" job "github.com/openstack-k8s-operators/lib-common/modules/common/job" "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + util "github.com/openstack-k8s-operators/lib-common/modules/common/util" databasev1beta1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" mariadb "github.com/openstack-k8s-operators/mariadb-operator/pkg/mariadb" + batchv1 "k8s.io/api/batch/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -63,10 +64,8 @@ func (r *MariaDBAccountReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *MariaDBAccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, _err error) { log := GetLog(ctx, "MariaDBAccount") - var err error - instance := &databasev1beta1.MariaDBAccount{} - err = r.Client.Get(ctx, req.NamespacedName, instance) + err := r.Client.Get(ctx, req.NamespacedName, instance) if err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } @@ -109,182 +108,115 @@ func (r *MariaDBAccountReconciler) Reconcile(ctx context.Context, req ctrl.Reque } }() - // initialize conditions used later as Status=Unknown - cl := condition.CreateList( - condition.UnknownCondition(condition.ReadyCondition, condition.InitReason, condition.ReadyInitMessage), - condition.UnknownCondition(databasev1beta1.MariaDBServerReadyCondition, condition.InitReason, databasev1beta1.MariaDBServerReadyInitMessage), - condition.UnknownCondition(databasev1beta1.MariaDBDatabaseReadyCondition, condition.InitReason, databasev1beta1.MariaDBDatabaseReadyInitMessage), - condition.UnknownCondition(databasev1beta1.MariaDBAccountReadyCondition, condition.InitReason, databasev1beta1.MariaDBAccountReadyInitMessage), - ) - + var cl condition.Conditions + if instance.IsUserAccount() { + // initialize conditions used later as Status=Unknown + cl = condition.CreateList( + condition.UnknownCondition(condition.ReadyCondition, condition.InitReason, condition.ReadyInitMessage), + condition.UnknownCondition(databasev1beta1.MariaDBServerReadyCondition, condition.InitReason, databasev1beta1.MariaDBServerReadyInitMessage), + condition.UnknownCondition(databasev1beta1.MariaDBDatabaseReadyCondition, condition.InitReason, databasev1beta1.MariaDBDatabaseReadyInitMessage), + condition.UnknownCondition(databasev1beta1.MariaDBAccountReadyCondition, condition.InitReason, databasev1beta1.MariaDBAccountReadyInitMessage), + ) + } else { + // initialize conditions used later as Status=Unknown + cl = condition.CreateList( + condition.UnknownCondition(condition.ReadyCondition, condition.InitReason, condition.ReadyInitMessage), + condition.UnknownCondition(databasev1beta1.MariaDBServerReadyCondition, condition.InitReason, databasev1beta1.MariaDBServerReadyInitMessage), + condition.UnknownCondition(databasev1beta1.MariaDBAccountReadyCondition, condition.InitReason, databasev1beta1.MariaDBAccountReadyInitMessage), + ) + } instance.Status.Conditions.Init(&cl) if instance.DeletionTimestamp.IsZero() || isNewInstance { //revive:disable:indent-error-flow - return r.reconcileCreate(ctx, log, helper, instance) + return r.reconcileCreateOrUpdate(ctx, log, helper, instance) } else { return r.reconcileDelete(ctx, log, helper, instance) } } -// reconcileDelete - run reconcile for case where delete timestamp is zero -func (r *MariaDBAccountReconciler) reconcileCreate( +// reconcileCreateOrUpdate - run reconcile for case where delete timestamp is zero +func (r *MariaDBAccountReconciler) reconcileCreateOrUpdate( ctx context.Context, log logr.Logger, helper *helper.Helper, instance *databasev1beta1.MariaDBAccount) (result ctrl.Result, _err error) { - // this is following from how the MariaDBDatabase CRD works. - // the related Galera / MariaDB object is given as a label, while - // the reference to the secret itself is given in the spec - // the convention appears to be: "things we are dependent on are named in labels, - // things we are setting up are named in the spec" - mariadbDatabaseName := instance.ObjectMeta.Labels["mariaDBDatabaseName"] - if mariadbDatabaseName == "" { - - log.Info(fmt.Sprintf( - "MariaDBAccount '%s' does not have a 'mariaDBDatabaseName' label, create won't proceed", - instance.Name, - )) - - return ctrl.Result{}, nil - } - - // locate the MariaDBDatabase object that this account is associated with - mariadbDatabase, err := r.getMariaDBDatabaseObject(ctx, instance, mariadbDatabaseName) - - // not found - if err != nil && k8s_errors.IsNotFound(err) { - // for the create case, need to wait for the MariaDBDatabase to exists before we can continue; - // requeue - - instance.Status.Conditions.Set(condition.FalseCondition( - databasev1beta1.MariaDBDatabaseReadyCondition, - databasev1beta1.ReasonDBNotFound, - condition.SeverityInfo, - databasev1beta1.MariaDBDatabaseReadyInitMessage)) - - log.Info(fmt.Sprintf( - "MariaDBAccount '%s' didn't find MariaDBDatabase '%s'; requeueing", - instance.Name, instance.ObjectMeta.Labels["mariaDBDatabaseName"])) - - return ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil - } else if err == nil && !mariadbDatabase.Status.Conditions.IsTrue(databasev1beta1.MariaDBDatabaseReadyCondition) { - // found but database not ready - - // for the create case, need to wait for the MariaDBDatabase to exists before we can continue; - // requeue - - instance.Status.Conditions.Set(condition.FalseCondition( - databasev1beta1.MariaDBDatabaseReadyCondition, - databasev1beta1.ReasonDBWaitingInitialized, - condition.SeverityInfo, - databasev1beta1.MariaDBDatabaseReadyInitMessage)) - - log.Info(fmt.Sprintf( - "MariaDBAccount '%s' MariaDBDatabase '%s' not yet complete; requeueing", - instance.Name, instance.ObjectMeta.Labels["mariaDBDatabaseName"])) - - return ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil - } else if err != nil { - // unhandled error; exit - log.Error(err, "unhandled error retrieving MariaDBDatabase instance") + var mariadbDatabase *databasev1beta1.MariaDBDatabase + var err error - instance.Status.Conditions.Set(condition.FalseCondition( - databasev1beta1.MariaDBDatabaseReadyCondition, - condition.ErrorReason, - condition.SeverityError, - databasev1beta1.MariaDBErrorRetrievingMariaDBDatabaseMessage, - err)) + log.Info("Reconcile MariaDBAccount create or update") - return ctrl.Result{}, err + if instance.IsUserAccount() { + // for User account, get a handle to the current, active MariaDBDatabase. + // if not ready yet, requeue. + mariadbDatabase, result, err = r.getMariaDBDatabaseForCreate(ctx, log, instance) + if mariadbDatabase == nil { + return result, err + } } - instance.Status.Conditions.MarkTrue( - databasev1beta1.MariaDBDatabaseReadyCondition, - databasev1beta1.MariaDBDatabaseReadyMessage, - ) - - // first, add a finalizer for us, so that subsequent steps can make - // additional state changes that we'd be on the hook to clean up afterwards if controllerutil.AddFinalizer(instance, helper.GetFinalizer()) { // we need to persist this right away return ctrl.Result{}, nil } - // MariaDBdatabase exists and we are a create case. ensure finalizers set up - if controllerutil.AddFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) { - err := r.Update(ctx, mariadbDatabase) - if err != nil { - return ctrl.Result{}, err + if instance.IsUserAccount() { + // MariaDBdatabase exists and we are a create case. ensure finalizers set up + if controllerutil.AddFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) { + err = r.Update(ctx, mariadbDatabase) + if err != nil { + return ctrl.Result{}, err + } } } - // now proceed to do actual work. acquire the galera/mariadb instance - // referenced by the MariaDBDatabase which will lead us to the hostname - // and container image to target - - dbGalera, err := r.getDatabaseObject(ctx, mariadbDatabase, instance) - if err != nil { - - log.Error(err, "Error getting database object") - - instance.Status.Conditions.Set(condition.FalseCondition( - databasev1beta1.MariaDBServerReadyCondition, - condition.ErrorReason, - condition.SeverityError, - databasev1beta1.MariaDBErrorRetrievingMariaDBGaleraMessage, - err)) - - return ctrl.Result{}, err - } - - var dbAdminSecret, dbContainerImage, serviceAccountName string - - if !dbGalera.Status.Bootstrapped { - log.Info("DB bootstrap not complete. Requeue...") - return ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil - } - - dbAdminSecret = dbGalera.Spec.Secret - dbContainerImage = dbGalera.Spec.ContainerImage - serviceAccountName = dbGalera.RbacResourceName() - - dbHostname, dbHostResult, err := databasev1beta1.GetServiceHostname(ctx, helper, dbGalera.Name, dbGalera.Namespace) - - if (err != nil || dbHostResult != ctrl.Result{}) { - return dbHostResult, err + // now proceed to do actual work. acquire the Galera instance + // which will lead us to the hostname and container image to target + dbGalera, dbHostname, result, err := r.getGaleraForCreateOrDelete( + ctx, log, helper, instance, mariadbDatabase, + ) + if dbGalera == nil { + return result, err } - instance.Status.Conditions.MarkTrue( - databasev1beta1.MariaDBServerReadyCondition, - databasev1beta1.MariaDBServerReadyMessage, - ) + dbContainerImage := dbGalera.Spec.ContainerImage + serviceAccountName := dbGalera.RbacResourceName() // account create - // ensure secret is present before running a job - _, secretResult, err := secret.VerifySecret( - ctx, - types.NamespacedName{Name: instance.Spec.Secret, Namespace: instance.Namespace}, - []string{databasev1beta1.DatabasePasswordSelector}, - r.Client, - time.Duration(30)*time.Second, - ) - if (err != nil || secretResult != ctrl.Result{}) { + // ensure secret is present, add a finalizer for mariadbaccount + result, err = r.ensureAccountSecret(ctx, log, helper, instance) + if (result != ctrl.Result{} || err != nil) { + return result, err + } - instance.Status.Conditions.Set(condition.FalseCondition( - databasev1beta1.MariaDBAccountReadyCondition, - secret.ReasonSecretMissing, - condition.SeverityInfo, - databasev1beta1.MariaDBAccountSecretNotReadyMessage, err)) + var jobDef *batchv1.Job - return secretResult, err + var createOrUpdate string + if instance.Status.CurrentSecret != "" { + createOrUpdate = "update" + } else { + createOrUpdate = "create" } - log.Info(fmt.Sprintf("Running account create '%s' MariaDBDatabase '%s'", instance.Name, mariadbDatabaseName)) - - jobDef, err := mariadb.CreateDbAccountJob(instance, mariadbDatabase.Spec.Name, dbHostname, dbAdminSecret, dbContainerImage, serviceAccountName, dbGalera.Spec.NodeSelector) - if err != nil { - return ctrl.Result{}, err + if instance.IsUserAccount() { + log.Info(fmt.Sprintf("Checking %s account job '%s' MariaDBDatabase '%s'", + createOrUpdate, + instance.Name, mariadbDatabase.Spec.Name)) + jobDef, err = mariadb.CreateOrUpdateDbAccountJob( + dbGalera, instance, mariadbDatabase.Spec.Name, dbHostname, + dbContainerImage, serviceAccountName, dbGalera.Spec.NodeSelector) + if err != nil { + return ctrl.Result{}, err + } + } else { + log.Info(fmt.Sprintf("Checking %s system account job '%s'", createOrUpdate, + instance.Name)) + jobDef, err = mariadb.CreateOrUpdateDbAccountJob( + dbGalera, instance, "", dbHostname, + dbContainerImage, serviceAccountName, dbGalera.Spec.NodeSelector) + if err != nil { + return ctrl.Result{}, err + } } accountCreateHash := instance.Status.Hash[databasev1beta1.AccountCreateHash] @@ -299,19 +231,29 @@ func (r *MariaDBAccountReconciler) reconcileCreate( ctx, helper, ) - if (ctrlResult != ctrl.Result{}) { - // TODO: should this be ctrlResult, err ? - return ctrlResult, nil - } - if err != nil { - return ctrl.Result{}, err + if (ctrlResult != ctrl.Result{} || err != nil) { + return ctrlResult, err } + if accountCreateJob.HasChanged() { + if instance.Status.Hash == nil { instance.Status.Hash = make(map[string]string) } instance.Status.Hash[databasev1beta1.AccountCreateHash] = accountCreateJob.GetHash() - log.Info(fmt.Sprintf("Job %s hash added - %s", jobDef.Name, instance.Status.Hash[databasev1beta1.AccountCreateHash])) + log.Info(fmt.Sprintf("Job %s hash created or updated - %s", jobDef.Name, instance.Status.Hash[databasev1beta1.AccountCreateHash])) + + // set up new Secret and remove finalizer from old secret + if instance.Status.CurrentSecret != instance.Spec.Secret { + currentSecret := instance.Status.CurrentSecret + err = r.removeSecretFinalizer(ctx, log, helper, currentSecret, instance.Namespace) + if err == nil { + instance.Status.CurrentSecret = instance.Spec.Secret + } else { + return ctrl.Result{}, err + } + } + } // database creation finished @@ -335,74 +277,16 @@ func (r *MariaDBAccountReconciler) reconcileDelete( ctx context.Context, log logr.Logger, helper *helper.Helper, instance *databasev1beta1.MariaDBAccount) (result ctrl.Result, _err error) { - // this is following from how the MariaDBDatabase CRD works. - // the related Galera / MariaDB object is given as a label, while - // the reference to the secret itself is given in the spec - // the convention appears to be: "things we are dependent on are named in labels, - // things we are setting up are named in the spec" - mariadbDatabaseName := instance.ObjectMeta.Labels["mariaDBDatabaseName"] - if mariadbDatabaseName == "" { - - log.Info(fmt.Sprintf( - "MariaDBAccount '%s' does not have a 'mariaDBDatabaseName' label, will remove finalizers", - instance.Name, - )) - controllerutil.RemoveFinalizer(instance, helper.GetFinalizer()) - - return ctrl.Result{}, nil - } - - // locate the MariaDBDatabase object that this account is associated with - mariadbDatabase, err := r.getMariaDBDatabaseObject(ctx, instance, mariadbDatabaseName) - - // not found - if err != nil && k8s_errors.IsNotFound(err) { - // for the delete case, the database doesn't exist. so - // that means we don't, either. remove finalizer from - // our own instance and return - log.Info(fmt.Sprintf( - "MariaDBAccount '%s' Didn't find MariaDBDatabase '%s'; no account delete needed", - instance.Name, instance.ObjectMeta.Labels["mariaDBDatabaseName"])) - - controllerutil.RemoveFinalizer(instance, helper.GetFinalizer()) - - return ctrl.Result{}, nil - } else if err == nil && !mariadbDatabase.Status.Conditions.IsTrue(databasev1beta1.MariaDBDatabaseReadyCondition) { - // found but database is not ready - - // for the delete case, the database doesn't exist. so - // that means we don't, either. remove finalizer from - // our own instance and return - log.Info(fmt.Sprintf( - "MariaDBAccount '%s' MariaDBDatabase '%s' not yet complete; no account delete needed", - instance.Name, instance.ObjectMeta.Labels["mariaDBDatabaseName"])) - - // first, remove finalizer from the MariaDBDatabase instance - if controllerutil.RemoveFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) { - err = r.Update(ctx, mariadbDatabase) + var mariadbDatabase *databasev1beta1.MariaDBDatabase + var err error - if err != nil && !k8s_errors.IsNotFound(err) { - return ctrl.Result{}, err - } + log.Info("Reconcile MariaDBAccount delete") + if instance.IsUserAccount() { + mariadbDatabase, result, err = r.getMariaDBDatabaseForDelete(ctx, log, helper, instance) + if mariadbDatabase == nil { + return result, err } - - // then remove finalizer from our own instance - controllerutil.RemoveFinalizer(instance, helper.GetFinalizer()) - - return ctrl.Result{}, nil - } else if err != nil { - // unhandled error; exit - log.Error(err, "unhandled error retrieving MariaDBDatabase instance") - - instance.Status.Conditions.Set(condition.FalseCondition( - databasev1beta1.MariaDBDatabaseReadyCondition, - condition.ErrorReason, - condition.SeverityError, - "Error retrieving MariaDBDatabase instance %s", - err)) - - return ctrl.Result{}, err } // dont do actual DROP USER until finalizers from downstream controllers @@ -415,6 +299,7 @@ func (r *MariaDBAccountReconciler) reconcileDelete( finalizersWeCareAbout = append(finalizersWeCareAbout, f) } } + if len(finalizersWeCareAbout) > 0 { instance.Status.Conditions.MarkFalse( databasev1beta1.MariaDBAccountReadyCondition, @@ -423,7 +308,7 @@ func (r *MariaDBAccountReconciler) reconcileDelete( databasev1beta1.MariaDBAccountFinalizersRemainMessage, strings.Join(finalizersWeCareAbout, ", "), ) - return ctrl.Result{}, err + return ctrl.Result{}, nil } instance.Status.Conditions.MarkTrue( @@ -431,21 +316,40 @@ func (r *MariaDBAccountReconciler) reconcileDelete( databasev1beta1.MariaDBAccountReadyForDeleteMessage, ) - instance.Status.Conditions.MarkTrue( - databasev1beta1.MariaDBDatabaseReadyCondition, - databasev1beta1.MariaDBDatabaseReadyMessage, + if instance.IsUserAccount() { + instance.Status.Conditions.MarkTrue( + databasev1beta1.MariaDBDatabaseReadyCondition, + databasev1beta1.MariaDBDatabaseReadyMessage, + ) + } + + // now proceed to do actual work. acquire the Galera instance + // which will lead us to the hostname and container image to target + dbGalera, dbHostname, result, err := r.getGaleraForCreateOrDelete( + ctx, log, helper, instance, mariadbDatabase, ) - // now proceed to do actual work. acquire the galera/mariadb instance - // referenced by the MariaDBDatabase which will lead us to the hostname - // and container image to target + // if Galera CR was not found or in a deletion process, this indicates + // we won't ever have a DB server with which to run a DROP for the + // account, so remove all finalizers and exit - dbGalera, err := r.getDatabaseObject(ctx, mariadbDatabase, instance) - if err != nil { + var galeraGone bool - log.Error(err, "Error getting database object") + if k8s_errors.IsNotFound(err) { + log.Info("Galera instance not found, so we will remove finalizers and skip account delete") + galeraGone = true + } else if err != nil { + // unexpected error code + return result, err + } else if dbGalera == nil || !dbGalera.DeletionTimestamp.IsZero() { + log.Info("Galera deleted or deletion timestamp is non-zero, so we will remove finalizers and skip account delete") + galeraGone = true + } else { + galeraGone = false + } - if k8s_errors.IsNotFound(err) { + if galeraGone { + if instance.IsUserAccount() { // remove finalizer from the MariaDBDatabase instance if controllerutil.RemoveFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) { err = r.Update(ctx, mariadbDatabase) @@ -453,65 +357,35 @@ func (r *MariaDBAccountReconciler) reconcileDelete( if err != nil && !k8s_errors.IsNotFound(err) { return ctrl.Result{}, err } - } - - // remove local finalizer - controllerutil.RemoveFinalizer(instance, helper.GetFinalizer()) - - // galera DB does not exist, so return - return ctrl.Result{}, nil - } else { - instance.Status.Conditions.Set(condition.FalseCondition( - databasev1beta1.MariaDBServerReadyCondition, - condition.ErrorReason, - condition.SeverityError, - "Error retrieving MariaDB/Galera instance %s", - err)) - - return ctrl.Result{}, err } - } - - var dbAdminSecret, dbContainerImage, serviceAccountName string - - if !dbGalera.Status.Bootstrapped { - log.Info("DB bootstrap not complete. Requeue...") - instance.Status.Conditions.MarkFalse( - databasev1beta1.MariaDBServerReadyCondition, - databasev1beta1.ReasonDBWaitingInitialized, - condition.SeverityInfo, - databasev1beta1.MariaDBServerNotBootstrappedMessage, - ) - - return ctrl.Result{RequeueAfter: time.Second * 10}, nil + // remove local finalizer + err := r.removeAccountAndSecretFinalizer(ctx, log, helper, instance) + return ctrl.Result{}, err } - dbAdminSecret = dbGalera.Spec.Secret - dbContainerImage = dbGalera.Spec.ContainerImage - serviceAccountName = dbGalera.RbacResourceName() - - dbHostname, dbHostResult, err := databasev1beta1.GetServiceHostname(ctx, helper, dbGalera.Name, dbGalera.Namespace) + dbContainerImage := dbGalera.Spec.ContainerImage + serviceAccountName := dbGalera.RbacResourceName() - if (err != nil || dbHostResult != ctrl.Result{}) { - return dbHostResult, err - } + var jobDef *batchv1.Job - instance.Status.Conditions.MarkTrue( - databasev1beta1.MariaDBServerReadyCondition, - databasev1beta1.MariaDBServerReadyMessage, - ) + if instance.IsUserAccount() { - // account delete + log.Info(fmt.Sprintf("Running account delete '%s' MariaDBDatabase '%s'", instance.Name, mariadbDatabase.Spec.Name)) - log.Info(fmt.Sprintf("Running account delete '%s' MariaDBDatabase '%s'", instance.Name, mariadbDatabaseName)) + jobDef, err = mariadb.DeleteDbAccountJob(dbGalera, instance, mariadbDatabase.Spec.Name, dbHostname, dbContainerImage, serviceAccountName, dbGalera.Spec.NodeSelector) + if err != nil { + return ctrl.Result{}, err + } + } else { + log.Info(fmt.Sprintf("Running system account delete '%s'", instance.Name)) - jobDef, err := mariadb.DeleteDbAccountJob(instance, mariadbDatabase.Spec.Name, dbHostname, dbAdminSecret, dbContainerImage, serviceAccountName, dbGalera.Spec.NodeSelector) - if err != nil { - return ctrl.Result{}, err + jobDef, err = mariadb.DeleteDbAccountJob(dbGalera, instance, "", dbHostname, dbContainerImage, serviceAccountName, dbGalera.Spec.NodeSelector) + if err != nil { + return ctrl.Result{}, err + } } - accountDeleteHash := instance.Status.Hash[databasev1beta1.AccountDeleteHash] accountDeleteJob := job.NewJob( jobDef, @@ -524,12 +398,8 @@ func (r *MariaDBAccountReconciler) reconcileDelete( ctx, helper, ) - if (ctrlResult != ctrl.Result{}) { - // TODO: should this be ctrlResult, err ? - return ctrlResult, nil - } - if err != nil { - return ctrl.Result{}, err + if (ctrlResult != ctrl.Result{} || err != nil) { + return ctrlResult, err } if accountDeleteJob.HasChanged() { if instance.Status.Hash == nil { @@ -540,25 +410,242 @@ func (r *MariaDBAccountReconciler) reconcileDelete( } // first, remove finalizer from the MariaDBDatabase instance - if controllerutil.RemoveFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) { - err = r.Update(ctx, mariadbDatabase) + if instance.IsUserAccount() { + if controllerutil.RemoveFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) { + err = r.Update(ctx, mariadbDatabase) + if err != nil { + return ctrl.Result{}, err + } + } } // then remove finalizer from our own instance - controllerutil.RemoveFinalizer(instance, helper.GetFinalizer()) - + err = r.removeAccountAndSecretFinalizer(ctx, log, helper, instance) return ctrl.Result{}, err } -// getDatabaseObject - returns either a Galera or MariaDB object (and an associated client.Object interface) -func (r *MariaDBAccountReconciler) getDatabaseObject(ctx context.Context, mariaDBDatabase *databasev1beta1.MariaDBDatabase, instance *databasev1beta1.MariaDBAccount) (*databasev1beta1.Galera, error) { - dbName := mariaDBDatabase.ObjectMeta.Labels["dbName"] - return GetDatabaseObject( - ctx, r.Client, - dbName, - instance.Namespace, +// getMariaDBDatabaseForCreate - waits for a MariaDBDatabase to be available in preparation +// to create an account +func (r *MariaDBAccountReconciler) getMariaDBDatabaseForCreate(ctx context.Context, log logr.Logger, + instance *databasev1beta1.MariaDBAccount) (*databasev1beta1.MariaDBDatabase, ctrl.Result, error) { + + // the convention of using a label for "things we are dependent on" is + // taken from the same practice in MariaDBDatabase where the "dbName" label + // refers to the Galera instance. + mariadbDatabaseName := instance.ObjectMeta.Labels["mariaDBDatabaseName"] + if mariadbDatabaseName == "" { + + log.Info(fmt.Sprintf( + "MariaDBAccount '%s' does not have a 'mariaDBDatabaseName' label, create won't proceed", + instance.Name, + )) + + return nil, ctrl.Result{}, nil + } + + // locate the MariaDBDatabase object itself + mariadbDatabase, err := r.getMariaDBDatabaseObject(ctx, instance, mariadbDatabaseName) + + if err != nil { + if k8s_errors.IsNotFound(err) { + // doesnt exist yet; requeue + + instance.Status.Conditions.Set(condition.FalseCondition( + databasev1beta1.MariaDBDatabaseReadyCondition, + databasev1beta1.ReasonDBNotFound, + condition.SeverityInfo, + databasev1beta1.MariaDBDatabaseReadyInitMessage)) + + log.Info(fmt.Sprintf( + "MariaDBAccount '%s' didn't find MariaDBDatabase '%s'; requeueing", + instance.Name, mariadbDatabaseName)) + + return nil, ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil + } else { + // unhandled error; exit without requeue + log.Error(err, "unhandled error retrieving MariaDBDatabase instance") + + instance.Status.Conditions.Set(condition.FalseCondition( + databasev1beta1.MariaDBDatabaseReadyCondition, + condition.ErrorReason, + condition.SeverityError, + databasev1beta1.MariaDBErrorRetrievingMariaDBDatabaseMessage, + err)) + + return nil, ctrl.Result{}, err + } + } else if mariadbDatabase.Status.Conditions.IsFalse(databasev1beta1.MariaDBDatabaseReadyCondition) { + // found but not ready; requeue + + instance.Status.Conditions.Set(condition.FalseCondition( + databasev1beta1.MariaDBDatabaseReadyCondition, + databasev1beta1.ReasonDBWaitingInitialized, + condition.SeverityInfo, + databasev1beta1.MariaDBDatabaseReadyInitMessage)) + + log.Info(fmt.Sprintf( + "MariaDBAccount '%s' MariaDBDatabase '%s' not yet complete; requeueing", + instance.Name, mariadbDatabaseName)) + + return nil, ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil + } + + // MariaDBDabase is ready, update status + instance.Status.Conditions.MarkTrue( + databasev1beta1.MariaDBDatabaseReadyCondition, + databasev1beta1.MariaDBDatabaseReadyMessage, ) + // return MariaDBDatabase where account create flow will then continue + return mariadbDatabase, ctrl.Result{}, nil +} + +// getMariaDBDatabaseForDelete - retrieves a MariaDBDatabase to be available in preparation +// to delete an account. if MariaDBDatabase not available, deleted, or not set up, +// removes account-level finalizers and returns nil for the object +func (r *MariaDBAccountReconciler) getMariaDBDatabaseForDelete(ctx context.Context, log logr.Logger, + helper *helper.Helper, instance *databasev1beta1.MariaDBAccount) (*databasev1beta1.MariaDBDatabase, ctrl.Result, error) { + + mariadbDatabaseName := instance.ObjectMeta.Labels["mariaDBDatabaseName"] + if mariadbDatabaseName == "" { + + log.Info(fmt.Sprintf( + "MariaDBAccount '%s' does not have a 'mariaDBDatabaseName' label, will remove finalizers", + instance.Name, + )) + + // remove local finalizer + err := r.removeAccountAndSecretFinalizer(ctx, log, helper, instance) + return nil, ctrl.Result{}, err + } + + // locate the MariaDBDatabase object itself + mariadbDatabase, err := r.getMariaDBDatabaseObject(ctx, instance, mariadbDatabaseName) + + if err != nil { + if k8s_errors.IsNotFound(err) { + // not found. this implies MariaDBAccount has no database-level + // entry either. Remove MariaDBAccount / secret finalizers and return + log.Info(fmt.Sprintf( + "MariaDBAccount '%s' Didn't find MariaDBDatabase '%s'; no account delete needed", + instance.Name, mariadbDatabaseName)) + + // remove local finalizer + err = r.removeAccountAndSecretFinalizer(ctx, log, helper, instance) + return nil, ctrl.Result{}, err + } else { + // unhandled error; exit without change + log.Error(err, "unhandled error retrieving MariaDBDatabase instance") + + instance.Status.Conditions.Set(condition.FalseCondition( + databasev1beta1.MariaDBDatabaseReadyCondition, + condition.ErrorReason, + condition.SeverityError, + databasev1beta1.MariaDBErrorRetrievingMariaDBDatabaseMessage, + err)) + + return nil, ctrl.Result{}, err + + } + } else if mariadbDatabase.Status.Conditions.IsFalse(databasev1beta1.MariaDBDatabaseReadyCondition) { + // found but database is not ready. this implies MariaDBAccount has no database-level + // entry either. Remove MariaDBAccount / secret finalizers and return + log.Info(fmt.Sprintf( + "MariaDBAccount '%s' MariaDBDatabase '%s' not yet complete; no account delete needed", + instance.Name, mariadbDatabaseName)) + + if controllerutil.RemoveFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) { + err = r.Update(ctx, mariadbDatabase) + + if err != nil && !k8s_errors.IsNotFound(err) { + return nil, ctrl.Result{}, err + } + } + + // remove local finalizer + err = r.removeAccountAndSecretFinalizer(ctx, log, helper, instance) + return nil, ctrl.Result{}, err + } + + // return MariaDBDatabase where account delete flow will then continue + return mariadbDatabase, ctrl.Result{}, nil +} + +// getGaleraForCreateOrDelete - retrieves the Galera instance in use, and establishes +// that it's in a ready state. Sets appropriate statuses and returns requeue +// or error results as needed. +func (r *MariaDBAccountReconciler) getGaleraForCreateOrDelete( + ctx context.Context, log logr.Logger, + helper *helper.Helper, instance *databasev1beta1.MariaDBAccount, + mariadbDatabase *databasev1beta1.MariaDBDatabase) (*databasev1beta1.Galera, string, ctrl.Result, error) { + + var dbGalera *databasev1beta1.Galera + var err error + var dbName string + + if instance.IsUserAccount() { + dbName = mariadbDatabase.ObjectMeta.Labels["dbName"] + } else { + // note mariadbDatabase is passed as nil in this case + dbName = instance.ObjectMeta.Labels["dbName"] + } + dbGalera, err = GetDatabaseObject(ctx, r.Client, dbName, instance.Namespace) + + if err != nil { + if k8s_errors.IsNotFound(err) { + log.Info(fmt.Sprintf("Galera instance '%s' does not exist", dbName)) + } else { + log.Error(err, "Error retrieving Galera instance") + } + + instance.Status.Conditions.Set(condition.FalseCondition( + databasev1beta1.MariaDBServerReadyCondition, + condition.ErrorReason, + condition.SeverityError, + databasev1beta1.MariaDBErrorRetrievingMariaDBGaleraMessage, + err)) + + return nil, "", ctrl.Result{}, err + } + + if !dbGalera.Status.Bootstrapped { + log.Info("DB bootstrap not complete. Requeue...") + + instance.Status.Conditions.MarkFalse( + databasev1beta1.MariaDBServerReadyCondition, + databasev1beta1.ReasonDBWaitingInitialized, + condition.SeverityInfo, + databasev1beta1.MariaDBServerNotBootstrappedMessage, + ) + + return nil, "", ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil + } + + if !dbGalera.DeletionTimestamp.IsZero() { + log.Info("DB server marked for deletion, preventing account operations from proceeding. Will seek to remove finalizers and exit") + + instance.Status.Conditions.MarkFalse( + databasev1beta1.MariaDBServerReadyCondition, + databasev1beta1.ReasonDBResourceDeleted, + condition.SeverityInfo, + databasev1beta1.MariaDBServerDeletedMessage, + ) + + return dbGalera, "", ctrl.Result{}, nil + } + + dbHostname, dbHostResult, err := databasev1beta1.GetServiceHostname(ctx, helper, dbGalera.Name, dbGalera.Namespace) + + if (err != nil || dbHostResult != ctrl.Result{}) { + return nil, "", dbHostResult, err + } + + instance.Status.Conditions.MarkTrue( + databasev1beta1.MariaDBServerReadyCondition, + databasev1beta1.MariaDBServerReadyMessage, + ) + + return dbGalera, dbHostname, ctrl.Result{}, nil } // getMariaDBDatabaseObject - returns a MariaDBDatabase object @@ -578,6 +665,99 @@ func (r *MariaDBAccountReconciler) getMariaDBDatabaseObject(ctx context.Context, return nil, err } - return mariaDBDatabase, err + return mariaDBDatabase, nil + +} + +// ensureAccountSecret - ensures the Secret exists, is valid, adds a finalizer. +// includes requeue for secret does not exist +func (r *MariaDBAccountReconciler) ensureAccountSecret( + ctx context.Context, + log logr.Logger, + h *helper.Helper, + instance *databasev1beta1.MariaDBAccount, +) (ctrl.Result, error) { + + secretName := instance.Spec.Secret + secretNamespace := instance.Namespace + secretObj, _, err := secret.GetSecret(ctx, h, secretName, secretNamespace) + if err != nil { + if k8s_errors.IsNotFound(err) { + instance.Status.Conditions.Set(condition.FalseCondition( + databasev1beta1.MariaDBAccountReadyCondition, + secret.ReasonSecretMissing, + condition.SeverityInfo, + databasev1beta1.MariaDBAccountSecretNotReadyMessage, err)) + + log.Info(fmt.Sprintf( + "MariaDBAccount '%s' didn't find Secret '%s'; requeueing", + instance.Name, instance.Spec.Secret)) + + return ctrl.Result{RequeueAfter: time.Duration(30) * time.Second}, nil + + } else { + return ctrl.Result{}, err + } + } + + var expectedFields = []string{databasev1beta1.DatabasePasswordSelector} + + // collect the secret values the caller expects to exist + for _, field := range expectedFields { + _, ok := secretObj.Data[field] + if !ok { + err := fmt.Errorf("%w: field %s not found in Secret %s", util.ErrFieldNotFound, field, secretName) + return ctrl.Result{}, err + } + } + if controllerutil.AddFinalizer(secretObj, h.GetFinalizer()) { + err = r.Update(ctx, secretObj) + if err != nil { + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, err +} + +// removeAccountAndSecretFinalizer - removes finalizer from mariadbaccount as well +// as current primary secret +func (r *MariaDBAccountReconciler) removeAccountAndSecretFinalizer(ctx context.Context, + log logr.Logger, helper *helper.Helper, instance *databasev1beta1.MariaDBAccount) error { + + err := r.removeSecretFinalizer( + ctx, log, helper, instance.Spec.Secret, instance.Namespace, + ) + if err != nil && !k8s_errors.IsNotFound(err) { + return err + } + + // remove mariadbaccount finalizer which will update at end of reconcile + controllerutil.RemoveFinalizer(instance, helper.GetFinalizer()) + + return nil +} + +func (r *MariaDBAccountReconciler) removeSecretFinalizer(ctx context.Context, + log logr.Logger, helper *helper.Helper, secretName string, namespace string) error { + accountSecret, _, err := secret.GetSecret(ctx, helper, secretName, namespace) + + if err == nil { + if controllerutil.RemoveFinalizer(accountSecret, helper.GetFinalizer()) { + err = r.Update(ctx, accountSecret) + if err != nil { + log.Error( + err, + fmt.Sprintf("Error removing mariadbaccount finalizer from secret '%s', will try again", secretName)) + return err + } else { + log.Info(fmt.Sprintf("Successfully removed mariadbaccount finalizer from secret '%s'", secretName)) + } + } + } else if !k8s_errors.IsNotFound(err) { + return err + } + + return nil } diff --git a/controllers/mariadbdatabase_controller.go b/controllers/mariadbdatabase_controller.go index f7e7b345..f261a04d 100644 --- a/controllers/mariadbdatabase_controller.go +++ b/controllers/mariadbdatabase_controller.go @@ -161,7 +161,7 @@ func (r *MariaDBDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Requ // // Non-deletion (normal) flow follows // - var dbSecret, dbContainerImage, serviceAccount string + var dbContainerImage, serviceAccount string // NOTE(dciabrin) When configured to only allow TLS connections, all clients // accessing this DB must support client connection via TLS. useTLS := dbGalera.Spec.TLS.Enabled() && dbGalera.Spec.DisableNonTLSListeners @@ -179,7 +179,6 @@ func (r *MariaDBDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{RequeueAfter: time.Second * 10}, nil } - dbSecret = dbGalera.Spec.Secret dbContainerImage = dbGalera.Spec.ContainerImage serviceAccount = dbGalera.RbacResourceName() @@ -195,7 +194,7 @@ func (r *MariaDBDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Requ ) // Define a new Job object (hostname, password, containerImage) - jobDef, err := mariadb.DbDatabaseJob(instance, dbHostname, dbSecret, dbContainerImage, serviceAccount, useTLS, dbGalera.Spec.NodeSelector) + jobDef, err := mariadb.DbDatabaseJob(dbGalera, instance, dbHostname, dbContainerImage, serviceAccount, useTLS, dbGalera.Spec.NodeSelector) if err != nil { return ctrl.Result{}, err } diff --git a/pkg/mariadb/account.go b/pkg/mariadb/account.go index dc352c24..969d12c1 100644 --- a/pkg/mariadb/account.go +++ b/pkg/mariadb/account.go @@ -5,6 +5,7 @@ import ( util "github.com/openstack-k8s-operators/lib-common/modules/common/util" databasev1beta1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,7 +19,7 @@ type accountCreateOrDeleteOptions struct { RequireTLS string } -func CreateDbAccountJob(account *databasev1beta1.MariaDBAccount, databaseName string, databaseHostName string, databaseSecret string, containerImage string, serviceAccountName string, nodeSelector *map[string]string) (*batchv1.Job, error) { +func CreateOrUpdateDbAccountJob(galera *mariadbv1.Galera, account *databasev1beta1.MariaDBAccount, databaseName string, databaseHostName string, containerImage string, serviceAccountName string, nodeSelector *map[string]string) (*batchv1.Job, error) { var tlsStatement string if account.Spec.RequireTLS { tlsStatement = " REQUIRE SSL" @@ -45,7 +46,7 @@ func CreateDbAccountJob(account *databasev1beta1.MariaDBAccount, databaseName st // provided db name is used as metadata name where underscore is a not allowed // character. Lets replace all underscores with hypen. Underscores in the db name are // possible. - Name: strings.Replace(account.Spec.UserName, "_", "-", -1) + "-account-create", + Name: strings.Replace(account.Spec.UserName, "_", "-", -1) + "-account-create-update", Namespace: account.Namespace, Labels: labels, }, @@ -56,21 +57,10 @@ func CreateDbAccountJob(account *databasev1beta1.MariaDBAccount, databaseName st ServiceAccountName: serviceAccountName, Containers: []corev1.Container{ { - Name: "mariadb-account-create", + Name: "mariadb-account-create-update", Image: containerImage, Command: []string{"/bin/sh", "-c", dbCmd}, Env: []corev1.EnvVar{ - { - Name: "MYSQL_PWD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: databaseSecret, - }, - Key: databasev1beta1.DbRootPasswordSelector, - }, - }, - }, { Name: "DatabasePassword", ValueFrom: &corev1.EnvVarSource{ @@ -83,8 +73,10 @@ func CreateDbAccountJob(account *databasev1beta1.MariaDBAccount, databaseName st }, }, }, + VolumeMounts: getGaleraRootOnlyVolumeMounts(), }, }, + Volumes: getGaleraRootOnlyVolumes(galera), }, }, }, @@ -97,7 +89,7 @@ func CreateDbAccountJob(account *databasev1beta1.MariaDBAccount, databaseName st return job, nil } -func DeleteDbAccountJob(account *databasev1beta1.MariaDBAccount, databaseName string, databaseHostName string, databaseSecret string, containerImage string, serviceAccountName string, nodeSelector *map[string]string) (*batchv1.Job, error) { +func DeleteDbAccountJob(galera *mariadbv1.Galera, account *databasev1beta1.MariaDBAccount, databaseName string, databaseHostName string, containerImage string, serviceAccountName string, nodeSelector *map[string]string) (*batchv1.Job, error) { opts := accountCreateOrDeleteOptions{account.Spec.UserName, databaseName, databaseHostName, "root", ""} @@ -121,24 +113,13 @@ func DeleteDbAccountJob(account *databasev1beta1.MariaDBAccount, databaseName st ServiceAccountName: serviceAccountName, Containers: []corev1.Container{ { - Name: "mariadb-account-delete", - Image: containerImage, - Command: []string{"/bin/sh", "-c", delCmd}, - Env: []corev1.EnvVar{ - { - Name: "MYSQL_PWD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: databaseSecret, - }, - Key: databasev1beta1.DbRootPasswordSelector, - }, - }, - }, - }, + Name: "mariadb-account-delete", + Image: containerImage, + Command: []string{"/bin/sh", "-c", delCmd}, + VolumeMounts: getGaleraRootOnlyVolumeMounts(), }, }, + Volumes: getGaleraRootOnlyVolumes(galera), }, }, }, diff --git a/pkg/mariadb/database.go b/pkg/mariadb/database.go index 9654ef5c..0c3f55d3 100644 --- a/pkg/mariadb/database.go +++ b/pkg/mariadb/database.go @@ -5,6 +5,7 @@ import ( util "github.com/openstack-k8s-operators/lib-common/modules/common/util" databasev1beta1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -20,7 +21,7 @@ type dbCreateOptions struct { } // DbDatabaseJob - -func DbDatabaseJob(database *databasev1beta1.MariaDBDatabase, databaseHostName string, databaseSecret string, containerImage string, serviceAccountName string, useTLS bool, nodeSelector *map[string]string) (*batchv1.Job, error) { +func DbDatabaseJob(galera *mariadbv1.Galera, database *databasev1beta1.MariaDBDatabase, databaseHostName string, containerImage string, serviceAccountName string, useTLS bool, nodeSelector *map[string]string) (*batchv1.Job, error) { var tlsStatement string if useTLS { tlsStatement = " REQUIRE SSL" @@ -48,17 +49,6 @@ func DbDatabaseJob(database *databasev1beta1.MariaDBDatabase, databaseHostName s if database.Spec.Secret != nil { scriptEnv = []corev1.EnvVar{ - { - Name: "MYSQL_PWD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: databaseSecret, - }, - Key: "DbRootPassword", - }, - }, - }, // send deprecated Secret field but only if non-nil { Name: "DatabasePassword", @@ -73,19 +63,7 @@ func DbDatabaseJob(database *databasev1beta1.MariaDBDatabase, databaseHostName s }, } } else { - scriptEnv = []corev1.EnvVar{ - { - Name: "MYSQL_PWD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: databaseSecret, - }, - Key: "DbRootPassword", - }, - }, - }, - } + scriptEnv = []corev1.EnvVar{} } job := &batchv1.Job{ @@ -104,12 +82,14 @@ func DbDatabaseJob(database *databasev1beta1.MariaDBDatabase, databaseHostName s ServiceAccountName: serviceAccountName, Containers: []corev1.Container{ { - Name: "mariadb-database-create", - Image: containerImage, - Command: []string{"/bin/sh", "-c", dbCmd}, - Env: scriptEnv, + Name: "mariadb-database-create", + Image: containerImage, + Command: []string{"/bin/sh", "-c", dbCmd}, + Env: scriptEnv, + VolumeMounts: getGaleraRootOnlyVolumeMounts(), }, }, + Volumes: getGaleraRootOnlyVolumes(galera), }, }, }, @@ -123,7 +103,7 @@ func DbDatabaseJob(database *databasev1beta1.MariaDBDatabase, databaseHostName s } // DeleteDbDatabaseJob - -func DeleteDbDatabaseJob(database *databasev1beta1.MariaDBDatabase, databaseHostName string, databaseSecret string, containerImage string, serviceAccountName string, nodeSelector *map[string]string) (*batchv1.Job, error) { +func DeleteDbDatabaseJob(galera *mariadbv1.Galera, database *databasev1beta1.MariaDBDatabase, databaseHostName string, containerImage string, serviceAccountName string, nodeSelector *map[string]string) (*batchv1.Job, error) { opts := dbCreateOptions{ database.Spec.Name, @@ -145,17 +125,6 @@ func DeleteDbDatabaseJob(database *databasev1beta1.MariaDBDatabase, databaseHost if database.Spec.Secret != nil { scriptEnv = []corev1.EnvVar{ - { - Name: "MYSQL_PWD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: databaseSecret, - }, - Key: databasev1beta1.DbRootPasswordSelector, - }, - }, - }, // send deprecated Secret field but only if non-nil. otherwise // the script should not try to drop usernames from mysql.user { @@ -171,19 +140,7 @@ func DeleteDbDatabaseJob(database *databasev1beta1.MariaDBDatabase, databaseHost }, } } else { - scriptEnv = []corev1.EnvVar{ - { - Name: "MYSQL_PWD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: databaseSecret, - }, - Key: databasev1beta1.DbRootPasswordSelector, - }, - }, - }, - } + scriptEnv = []corev1.EnvVar{} } job := &batchv1.Job{ @@ -199,12 +156,14 @@ func DeleteDbDatabaseJob(database *databasev1beta1.MariaDBDatabase, databaseHost ServiceAccountName: serviceAccountName, Containers: []corev1.Container{ { - Name: "mariadb-database-create", - Image: containerImage, - Command: []string{"/bin/sh", "-c", delCmd}, - Env: scriptEnv, + Name: "mariadb-database-create", + Image: containerImage, + Command: []string{"/bin/sh", "-c", delCmd}, + Env: scriptEnv, + VolumeMounts: getGaleraRootOnlyVolumeMounts(), }, }, + Volumes: getGaleraRootOnlyVolumes(galera), }, }, }, diff --git a/pkg/mariadb/statefulset.go b/pkg/mariadb/statefulset.go index d3207c9a..716e10c3 100644 --- a/pkg/mariadb/statefulset.go +++ b/pkg/mariadb/statefulset.go @@ -104,16 +104,6 @@ func getGaleraInitContainers(g *mariadbv1.Galera) []corev1.Container { }, { Name: "KOLLA_CONFIG_STRATEGY", Value: "COPY_ALWAYS", - }, { - Name: "DB_ROOT_PASSWORD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: g.Spec.Secret, - }, - Key: "DbRootPassword", - }, - }, }}, VolumeMounts: getGaleraInitVolumeMounts(g), }} @@ -131,16 +121,6 @@ func getGaleraContainers(g *mariadbv1.Galera, configHash string) []corev1.Contai }, { Name: "KOLLA_CONFIG_STRATEGY", Value: "COPY_ALWAYS", - }, { - Name: "DB_ROOT_PASSWORD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: g.Spec.Secret, - }, - Key: "DbRootPassword", - }, - }, }}, Ports: []corev1.ContainerPort{{ ContainerPort: 3306, diff --git a/pkg/mariadb/volumes.go b/pkg/mariadb/volumes.go index 11ee81cc..17097c18 100644 --- a/pkg/mariadb/volumes.go +++ b/pkg/mariadb/volumes.go @@ -38,20 +38,6 @@ func getGaleraVolumes(g *mariadbv1.Galera) []corev1.Volume { } volumes := []corev1.Volume{ - { - Name: "secrets", - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: g.Spec.Secret, - Items: []corev1.KeyToPath{ - { - Key: "DbRootPassword", - Path: "dbpassword", - }, - }, - }, - }, - }, { Name: "kolla-config", VolumeSource: corev1.VolumeSource{ @@ -117,6 +103,10 @@ func getGaleraVolumes(g *mariadbv1.Galera) []corev1.Volume { Key: "mysql_wsrep_notify.sh", Path: "mysql_wsrep_notify.sh", }, + { + Key: "root_auth.sh", + Path: "root_auth.sh", + }, }, }, }, @@ -141,6 +131,29 @@ func getGaleraVolumes(g *mariadbv1.Galera) []corev1.Volume { return volumes } +func getGaleraRootOnlyVolumes(g *mariadbv1.Galera) []corev1.Volume { + volumes := []corev1.Volume{ + { + Name: "operator-scripts", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: g.Name + "-scripts", + }, + Items: []corev1.KeyToPath{ + { + Key: "root_auth.sh", + Path: "root_auth.sh", + }, + }, + }, + }, + }, + } + + return volumes +} + func getGaleraVolumeMounts(g *mariadbv1.Galera) []corev1.VolumeMount { volumeMounts := []corev1.VolumeMount{ { @@ -154,10 +167,6 @@ func getGaleraVolumeMounts(g *mariadbv1.Galera) []corev1.VolumeMount { }, { MountPath: "/var/lib/config-data/generated", Name: "config-data-generated", - }, { - MountPath: "/var/lib/secrets", - ReadOnly: true, - Name: "secrets", }, { MountPath: "/var/lib/operator-scripts", ReadOnly: true, @@ -191,6 +200,18 @@ func getGaleraVolumeMounts(g *mariadbv1.Galera) []corev1.VolumeMount { return volumeMounts } +func getGaleraRootOnlyVolumeMounts() []corev1.VolumeMount { + volumeMounts := []corev1.VolumeMount{ + { + MountPath: "/var/lib/operator-scripts", + ReadOnly: true, + Name: "operator-scripts", + }, + } + + return volumeMounts +} + func getGaleraInitVolumeMounts(g *mariadbv1.Galera) []corev1.VolumeMount { volumeMounts := []corev1.VolumeMount{ { @@ -204,10 +225,6 @@ func getGaleraInitVolumeMounts(g *mariadbv1.Galera) []corev1.VolumeMount { }, { MountPath: "/var/lib/config-data/generated", Name: "config-data-generated", - }, { - MountPath: "/var/lib/secrets", - ReadOnly: true, - Name: "secrets", }, { MountPath: "/var/lib/operator-scripts", ReadOnly: true, diff --git a/templates/account.sh b/templates/account.sh index b21da807..c5416a04 100755 --- a/templates/account.sh +++ b/templates/account.sh @@ -1,12 +1,38 @@ #!/bin/bash + +source /var/lib/operator-scripts/root_auth.sh + export DatabasePassword=${DatabasePassword:?"Please specify a DatabasePassword variable."} -mysql -h {{.DatabaseHostname}} -u {{.DatabaseAdminUsername}} -P 3306 -e "GRANT ALL PRIVILEGES ON {{.DatabaseName}}.* TO '{{.UserName}}'@'localhost' IDENTIFIED BY '$DatabasePassword'{{.RequireTLS}};GRANT ALL PRIVILEGES ON {{.DatabaseName}}.* TO '{{.UserName}}'@'%' IDENTIFIED BY '$DatabasePassword'{{.RequireTLS}};" +MYSQL_CMD="mysql -h {{.DatabaseHostname}} -u {{.DatabaseAdminUsername}} -P 3306" + +if [ -n "{{.DatabaseName}}" ]; then + GRANT_DATABASE="{{.DatabaseName}}" +else + GRANT_DATABASE="*" +fi + +# going for maximum compatibility here: +# 1. MySQL 8 no longer allows implicit create user when GRANT is used +# 2. MariaDB has "CREATE OR REPLACE", but MySQL does not +# 3. create user with CREATE but then do all password and TLS with ALTER to +# support updates + +$MYSQL_CMD <&- 7<&- diff --git a/tests/chainsaw/scripts/mysql-cli.sh b/tests/chainsaw/scripts/mysql-cli.sh index bc6367d2..45854bea 100755 --- a/tests/chainsaw/scripts/mysql-cli.sh +++ b/tests/chainsaw/scripts/mysql-cli.sh @@ -2,5 +2,7 @@ set -u ARGS=$1 SQL=$2 +ROOT_PW_AUTH="source /var/lib/operator-scripts/root_auth.sh; " CMD="mysql -uroot -p\$DB_ROOT_PASSWORD $ARGS \"$SQL\"" -oc exec -n ${NAMESPACE} -c galera openstack-galera-0 -- /bin/sh -c "$CMD" + +oc exec -n ${NAMESPACE} -c galera openstack-galera-0 -- /bin/sh -c "${ROOT_PW_AUTH} ${CMD}" diff --git a/tests/chainsaw/tests/account/chainsaw-test.yaml b/tests/chainsaw/tests/account/chainsaw-test.yaml index e7399feb..0235bb3c 100644 --- a/tests/chainsaw/tests/account/chainsaw-test.yaml +++ b/tests/chainsaw/tests/account/chainsaw-test.yaml @@ -25,6 +25,8 @@ spec: - name: create account without secret description: account CR has to wait for a secret to create account in the database + # we will delete the account manually + skipDelete: true try: - apply: file: account.yaml @@ -33,8 +35,20 @@ spec: - name: add secret and finish account creation description: make sure the account is created in the database + # we will delete the secret manually + skipDelete: true try: - apply: file: account-secret.yaml - assert: file: account-assert.yaml + finally: + - delete: + # delete account first, otherwise the secret can't be deleted b.c. + # it has a finalizer from the account + # perhaps I shouldn't have allowed MariaDBAccount to be created without + # a secret, but here we are... + file: account.yaml + - delete: + # then delete the secret + file: account-secret.yaml diff --git a/tests/kuttl/common/assert_sample_deployment.yaml b/tests/kuttl/common/assert_sample_deployment.yaml index a6080194..5c4daab4 100644 --- a/tests/kuttl/common/assert_sample_deployment.yaml +++ b/tests/kuttl/common/assert_sample_deployment.yaml @@ -32,6 +32,10 @@ status: reason: Ready status: "True" type: InputReady + - message: MariaDBAccount System account 'root' creation complete + reason: Ready + status: "True" + type: MariaDBAccountReady - message: RoleBinding created reason: Ready status: "True" diff --git a/tests/kuttl/common/scripts/check_db_account.sh b/tests/kuttl/common/scripts/check_db_account.sh index 7adfc51c..a7b0a1f5 100755 --- a/tests/kuttl/common/scripts/check_db_account.sh +++ b/tests/kuttl/common/scripts/check_db_account.sh @@ -17,7 +17,7 @@ if [ "$5" = "--reverse" ];then not_found=0 fi -found_username=$(oc rsh -n ${NAMESPACE} -c galera ${galera} /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select user from mysql.user"' | grep -o -w ${username}) +found_username=$(oc rsh -n ${NAMESPACE} -c galera ${galera} /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select user from mysql.user"' | grep -o -w ${username}) # username was not found, exit if [ -z "$found_username" ]; then @@ -26,7 +26,11 @@ fi # username was found. if we wanted it to be found, then check the login also. if [ "$found" = "0" ]; then - oc rsh -n ${NAMESPACE} -c galera ${galera} /bin/sh -c "mysql -u${username} -p${password} -Nse 'select database();' ${dbname}" || exit -1 + if [ -n "$dbname" ]; then + oc rsh -n ${NAMESPACE} -c galera ${galera} /bin/sh -c "mysql -u${username} -p${password} -Nse 'select database();' ${dbname}" || exit -1 + else + oc rsh -n ${NAMESPACE} -c galera ${galera} /bin/sh -c "mysql -u${username} -p${password} -Nse 'select 1'" || exit -1 + fi fi exit $found diff --git a/tests/kuttl/tests/account_create/01-assert.yaml b/tests/kuttl/tests/account_create/01-assert.yaml index 98df972c..547b04cf 100644 --- a/tests/kuttl/tests/account_create/01-assert.yaml +++ b/tests/kuttl/tests/account_create/01-assert.yaml @@ -34,6 +34,10 @@ status: reason: Ready status: "True" type: InputReady + - message: MariaDBAccount System account 'root' creation complete + reason: Ready + status: "True" + type: MariaDBAccountReady - message: RoleBinding created reason: Ready status: "True" diff --git a/tests/kuttl/tests/account_create/04-assert.yaml b/tests/kuttl/tests/account_create/04-assert.yaml index 7ef8ff62..0e90eaf1 100644 --- a/tests/kuttl/tests/account_create/04-assert.yaml +++ b/tests/kuttl/tests/account_create/04-assert.yaml @@ -23,3 +23,14 @@ status: reason: Ready status: "True" type: MariaDBServerReady +--- +apiVersion: v1 +data: + DatabasePassword: ZGJzZWNyZXQx +kind: Secret +metadata: + name: some-db-secret + # ensure finalizer was added + finalizers: + - openstack.org/mariadbaccount +type: Opaque diff --git a/tests/kuttl/tests/account_create/05-assert.yaml b/tests/kuttl/tests/account_create/05-assert.yaml index 8110a94f..aef8413a 100644 --- a/tests/kuttl/tests/account_create/05-assert.yaml +++ b/tests/kuttl/tests/account_create/05-assert.yaml @@ -6,12 +6,12 @@ commands: set -e ${MARIADB_KUTTL_DIR:-tests/kuttl/tests}/../common/scripts/check_db_account.sh openstack-galera-0 kuttldb_accounttest someuser dbsecret1 # ensure db users are configured without TLS connection restriction - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -e "show grants for \`someuser\`@\`%\`;"' | grep 'GRANT USAGE' | grep -v 'REQUIRE SSL' + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -e "show grants for \`someuser\`@\`%\`;"' | grep 'GRANT USAGE' | grep -v 'REQUIRE SSL' --- apiVersion: batch/v1 kind: Job metadata: - name: someuser-account-create + name: someuser-account-create-update spec: template: spec: diff --git a/tests/kuttl/tests/create_system_account/01-assert.yaml b/tests/kuttl/tests/create_system_account/01-assert.yaml new file mode 100644 index 00000000..547b04cf --- /dev/null +++ b/tests/kuttl/tests/create_system_account/01-assert.yaml @@ -0,0 +1,133 @@ +# +# Check for: +# +# - 1 MariaDB CR +# - 1 Pod for MariaDB CR +# + +apiVersion: mariadb.openstack.org/v1beta1 +kind: Galera +metadata: + name: openstack +spec: + replicas: 1 + secret: osp-secret + storageRequest: 500M + nodeSelector: + node-role.kubernetes.io/worker: "" +status: + bootstrapped: true + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Create service completed + reason: Ready + status: "True" + type: CreateServiceReady + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: MariaDBAccount System account 'root' creation complete + reason: Ready + status: "True" + type: MariaDBAccountReady + - message: RoleBinding created + reason: Ready + status: "True" + type: RoleBindingReady + - message: Role created + reason: Ready + status: "True" + type: RoleReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Service config create completed + reason: Ready + status: "True" + type: ServiceConfigReady + - message: Input data complete + reason: Ready + status: "True" + type: TLSInputReady +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: openstack-galera +spec: + replicas: 1 + selector: + matchLabels: + app: galera + cr: galera-openstack + galera/name: openstack + serviceName: openstack-galera + template: + metadata: + labels: + app: galera + cr: galera-openstack + galera/name: openstack + spec: + nodeSelector: + node-role.kubernetes.io/worker: "" + containers: + - command: + - /usr/bin/dumb-init + - -- + - /usr/local/bin/kolla_start + name: galera + ports: + - containerPort: 3306 + name: mysql + protocol: TCP + - containerPort: 4567 + name: galera + protocol: TCP + serviceAccount: galera-openstack + serviceAccountName: galera-openstack +status: + availableReplicas: 1 + readyReplicas: 1 + replicas: 1 +--- +apiVersion: v1 +kind: Pod +metadata: + name: openstack-galera-0 +spec: + nodeSelector: + node-role.kubernetes.io/worker: "" +--- +apiVersion: v1 +kind: Service +metadata: + name: openstack-galera +spec: + ports: + - name: mysql + port: 3306 + protocol: TCP + targetPort: 3306 + selector: + app: galera + cr: galera-openstack +--- +apiVersion: v1 +kind: Endpoints +metadata: + name: openstack-galera +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openstack-config-data diff --git a/tests/kuttl/tests/create_system_account/01-deploy_galera.yaml b/tests/kuttl/tests/create_system_account/01-deploy_galera.yaml new file mode 100644 index 00000000..3226c5a2 --- /dev/null +++ b/tests/kuttl/tests/create_system_account/01-deploy_galera.yaml @@ -0,0 +1,11 @@ +apiVersion: mariadb.openstack.org/v1beta1 +kind: Galera +metadata: + name: openstack +spec: + secret: osp-secret + storageClass: local-storage + storageRequest: 500M + replicas: 1 + nodeSelector: + node-role.kubernetes.io/worker: "" diff --git a/tests/kuttl/tests/create_system_account/02-assert.yaml b/tests/kuttl/tests/create_system_account/02-assert.yaml new file mode 100644 index 00000000..7a4fb914 --- /dev/null +++ b/tests/kuttl/tests/create_system_account/02-assert.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: mariadb.openstack.org/v1beta1 +kind: MariaDBAccount +metadata: + labels: + dbName: openstack + name: kuttldb-some-system-db-account +status: + conditions: + # message: 'MariaDBAccount secret is missing or incomplete: Secret $NAMESPACE/some-system-db-secret not found' + - reason: SecretMissing + severity: Info + status: "False" + type: Ready + # message: 'MariaDBAccount secret is missing or incomplete: Secret $NAMESPACE/some-system-db-secret not found' + - reason: SecretMissing + severity: Info + status: "False" + type: MariaDBAccountReady + - message: MariaDB / Galera server ready + reason: Ready + status: "True" + type: MariaDBServerReady diff --git a/tests/kuttl/tests/create_system_account/02-create-system-account.yaml b/tests/kuttl/tests/create_system_account/02-create-system-account.yaml new file mode 100644 index 00000000..caa86a3c --- /dev/null +++ b/tests/kuttl/tests/create_system_account/02-create-system-account.yaml @@ -0,0 +1,10 @@ +apiVersion: mariadb.openstack.org/v1beta1 +kind: MariaDBAccount +metadata: + labels: + dbName: openstack + name: kuttldb-some-system-db-account +spec: + userName: systemuser + secret: some-system-db-secret + accountType: System diff --git a/tests/kuttl/tests/create_system_account/03-assert.yaml b/tests/kuttl/tests/create_system_account/03-assert.yaml new file mode 100644 index 00000000..a33cd6eb --- /dev/null +++ b/tests/kuttl/tests/create_system_account/03-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: mariadb.openstack.org/v1beta1 +kind: MariaDBAccount +metadata: + labels: + dbName: openstack + name: kuttldb-some-system-db-account +status: + currentSecret: some-system-db-secret + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: MariaDBAccount creation complete + reason: Ready + status: "True" + type: MariaDBAccountReady + - message: MariaDB / Galera server ready + reason: Ready + status: "True" + type: MariaDBServerReady +--- +apiVersion: v1 +data: + DatabasePassword: ZGJzZWNyZXQx +kind: Secret +metadata: + name: some-system-db-secret + # ensure finalizer was added + finalizers: + - openstack.org/mariadbaccount +type: Opaque diff --git a/tests/kuttl/tests/create_system_account/03-create-secret.yaml b/tests/kuttl/tests/create_system_account/03-create-secret.yaml new file mode 100644 index 00000000..c97d3756 --- /dev/null +++ b/tests/kuttl/tests/create_system_account/03-create-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +data: + # dbsecret1 + DatabasePassword: ZGJzZWNyZXQx +kind: Secret +metadata: + name: some-system-db-secret +type: Opaque diff --git a/tests/kuttl/tests/create_system_account/04-assert.yaml b/tests/kuttl/tests/create_system_account/04-assert.yaml new file mode 100644 index 00000000..d76e1918 --- /dev/null +++ b/tests/kuttl/tests/create_system_account/04-assert.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: + - script: | + set -e + ${MARIADB_KUTTL_DIR:-tests/kuttl/tests}/../common/scripts/check_db_account.sh openstack-galera-0 '' systemuser dbsecret1 + # ensure db users are configured without TLS connection restriction + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -e "show grants for \`systemuser\`@\`%\`;"' | grep 'GRANT ALL' | grep -v 'REQUIRE SSL' +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: systemuser-account-create-update +spec: + template: + spec: + nodeSelector: + node-role.kubernetes.io/worker: "" +status: + succeeded: 1 diff --git a/tests/kuttl/tests/create_system_account/05-assert.yaml b/tests/kuttl/tests/create_system_account/05-assert.yaml new file mode 100644 index 00000000..7fa37eec --- /dev/null +++ b/tests/kuttl/tests/create_system_account/05-assert.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: + - script: | + set -e + ${MARIADB_KUTTL_DIR:-tests/kuttl/tests}/../common/scripts/check_db_account.sh openstack-galera-0 '' systemuser dbsecret2 +--- +apiVersion: mariadb.openstack.org/v1beta1 +kind: MariaDBAccount +metadata: + labels: + dbName: openstack + name: kuttldb-some-system-db-account +status: + currentSecret: some-new-system-db-secret + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: MariaDBAccount creation complete + reason: Ready + status: "True" + type: MariaDBAccountReady + - message: MariaDB / Galera server ready + reason: Ready + status: "True" + type: MariaDBServerReady diff --git a/tests/kuttl/tests/create_system_account/05-update-secret.yaml b/tests/kuttl/tests/create_system_account/05-update-secret.yaml new file mode 100644 index 00000000..9c124eb3 --- /dev/null +++ b/tests/kuttl/tests/create_system_account/05-update-secret.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +data: + # dbsecret2 + DatabasePassword: ZGJzZWNyZXQy +kind: Secret +metadata: + name: some-new-system-db-secret +type: Opaque +--- +apiVersion: mariadb.openstack.org/v1beta1 +kind: MariaDBAccount +metadata: + labels: + dbName: openstack + name: kuttldb-some-system-db-account +spec: + userName: systemuser + secret: some-new-system-db-secret + accountType: System diff --git a/tests/kuttl/tests/create_system_account/06-drop-account.yaml b/tests/kuttl/tests/create_system_account/06-drop-account.yaml new file mode 100644 index 00000000..b9255025 --- /dev/null +++ b/tests/kuttl/tests/create_system_account/06-drop-account.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + oc delete -n $NAMESPACE mariadbaccounts/kuttldb-some-system-db-account diff --git a/tests/kuttl/tests/create_system_account/07-assert.yaml b/tests/kuttl/tests/create_system_account/07-assert.yaml new file mode 100644 index 00000000..e03eaa05 --- /dev/null +++ b/tests/kuttl/tests/create_system_account/07-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: + - script: | + ${MARIADB_KUTTL_DIR:-tests/kuttl/tests}/../common/scripts/check_db_account.sh openstack-galera-0 "" systemuser dbsecret2 --reverse diff --git a/tests/kuttl/tests/create_system_account/08-teardown.yaml b/tests/kuttl/tests/create_system_account/08-teardown.yaml new file mode 100644 index 00000000..5c5cb2c8 --- /dev/null +++ b/tests/kuttl/tests/create_system_account/08-teardown.yaml @@ -0,0 +1,19 @@ +apiVersion: kuttl.dev/v1beta +kind: TestStep +delete: +- apiVersion: mariadb.openstack.org/v1beta1 + kind: Galera + name: openstack +- apiVersion: mariadb.openstack.org/v1beta1 + kind: MariaDBAccount + name: kuttldb-some-system-db-account +- apiVersion: v1 + kind: Secret + name: some-system-db-secret +- apiVersion: v1 + kind: Secret + name: some-new-system-db-secret +commands: + - script: | + oc delete -n $NAMESPACE pvc mysql-db-openstack-galera-0 + for i in `oc get pv | awk '/'$NAMESPACE'\/mysql-db-openstack-galera/ {print $1}'`; do oc patch pv $i -p '{"spec":{"claimRef": null}}'; done diff --git a/tests/kuttl/tests/database_create/01-assert.yaml b/tests/kuttl/tests/database_create/01-assert.yaml index 74a26acb..c79ade1a 100644 --- a/tests/kuttl/tests/database_create/01-assert.yaml +++ b/tests/kuttl/tests/database_create/01-assert.yaml @@ -32,6 +32,10 @@ status: reason: Ready status: "True" type: InputReady + - message: MariaDBAccount System account 'root' creation complete + reason: Ready + status: "True" + type: MariaDBAccountReady - message: RoleBinding created reason: Ready status: "True" diff --git a/tests/kuttl/tests/database_create/03-assert.yaml b/tests/kuttl/tests/database_create/03-assert.yaml index 56b4cd28..0c6b6a4d 100644 --- a/tests/kuttl/tests/database_create/03-assert.yaml +++ b/tests/kuttl/tests/database_create/03-assert.yaml @@ -4,12 +4,12 @@ kind: TestAssert commands: - script: | set -euxo pipefail - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@character_set_database;" kuttldb_latin1' | grep -o -w latin1 - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@collation_database;" kuttldb_latin1' | grep -o -w latin1_general_ci + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@character_set_database;" kuttldb_latin1' | grep -o -w latin1 + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@collation_database;" kuttldb_latin1' | grep -o -w latin1_general_ci - script: | set -euxo pipefail - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@character_set_database;" kuttldb_utf8' | grep -o -w utf8 - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@collation_database;" kuttldb_utf8' | grep -o -w utf8_general_ci + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@character_set_database;" kuttldb_utf8' | grep -o -w utf8 + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@collation_database;" kuttldb_utf8' | grep -o -w utf8_general_ci # for legacy secret non-present, test that a mariadb username was *not* made - script: | set -euxo pipefail diff --git a/tests/kuttl/tests/galera_cluster_restart/03-assert.yaml b/tests/kuttl/tests/galera_cluster_restart/03-assert.yaml index 59d87878..01f0b428 100644 --- a/tests/kuttl/tests/galera_cluster_restart/03-assert.yaml +++ b/tests/kuttl/tests/galera_cluster_restart/03-assert.yaml @@ -3,8 +3,8 @@ kind: TestAssert commands: - script: | # ensure galera/WSREP traffic uses encryption - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.wsrep_provider_options;"' | grep -o -w 'socket.ssl = YES' + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.wsrep_provider_options;"' | grep -o -w 'socket.ssl = YES' # ensure mysql/SQL traffic uses encryption - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.ssl_cipher;"' | grep -v '^NULL$' + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.ssl_cipher;"' | grep -v '^NULL$' # ensure the galera cluster is composed of three nodes - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "show status like \"wsrep_cluster_size\";"' | grep -w 3 + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "show status like \"wsrep_cluster_size\";"' | grep -w 3 diff --git a/tests/kuttl/tests/galera_create_user_require_tls/02-assert.yaml b/tests/kuttl/tests/galera_create_user_require_tls/02-assert.yaml index 4b78e42f..e0d8f510 100644 --- a/tests/kuttl/tests/galera_create_user_require_tls/02-assert.yaml +++ b/tests/kuttl/tests/galera_create_user_require_tls/02-assert.yaml @@ -4,8 +4,8 @@ commands: - script: | set -euxo pipefail # ensure db users are configured to TLS restriction - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -e "show grants for \`kuttldb\`@\`%\`;"' | grep 'REQUIRE SSL' + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -e "show grants for \`kuttldb\`@\`%\`;"' | grep 'REQUIRE SSL' - script: | set -euxo pipefail # ensure db users are configured to TLS restriction - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -e "show grants for \`anotheruser\`@\`%\`;"' | grep 'REQUIRE SSL' + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -e "show grants for \`anotheruser\`@\`%\`;"' | grep 'REQUIRE SSL' diff --git a/tests/kuttl/tests/galera_deploy_external_tls/01-assert.yaml b/tests/kuttl/tests/galera_deploy_external_tls/01-assert.yaml index 08b5b237..ae2af301 100644 --- a/tests/kuttl/tests/galera_deploy_external_tls/01-assert.yaml +++ b/tests/kuttl/tests/galera_deploy_external_tls/01-assert.yaml @@ -27,6 +27,10 @@ status: reason: Ready status: "True" type: InputReady + - message: MariaDBAccount System account 'root' creation complete + reason: Ready + status: "True" + type: MariaDBAccountReady - message: RoleBinding created reason: Ready status: "True" diff --git a/tests/kuttl/tests/galera_deploy_external_tls/02-assert.yaml b/tests/kuttl/tests/galera_deploy_external_tls/02-assert.yaml index e59da5b7..b3d9df4b 100644 --- a/tests/kuttl/tests/galera_deploy_external_tls/02-assert.yaml +++ b/tests/kuttl/tests/galera_deploy_external_tls/02-assert.yaml @@ -3,6 +3,6 @@ kind: TestAssert commands: - script: | # ensure galera does not encrypt WSREP traffic - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.wsrep_provider_options;"' | grep -o -w 'gmcast.listen_addr = tcp' + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.wsrep_provider_options;"' | grep -o -w 'gmcast.listen_addr = tcp' # ensure mysql/SQL traffic uses encryption - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.ssl_cipher;"' | grep -v '^NULL$' + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.ssl_cipher;"' | grep -v '^NULL$' diff --git a/tests/kuttl/tests/galera_deploy_tls/02-assert.yaml b/tests/kuttl/tests/galera_deploy_tls/02-assert.yaml index cd069330..0a6cfe26 100644 --- a/tests/kuttl/tests/galera_deploy_tls/02-assert.yaml +++ b/tests/kuttl/tests/galera_deploy_tls/02-assert.yaml @@ -3,6 +3,6 @@ kind: TestAssert commands: - script: | # ensure galera/WSREP traffic uses encryption - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.wsrep_provider_options;"' | grep -o -w 'socket.ssl = YES' + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.wsrep_provider_options;"' | grep -o -w 'socket.ssl = YES' # ensure mysql/SQL traffic uses encryption - oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.ssl_cipher;"' | grep -v '^NULL$' + oc rsh -n ${NAMESPACE} -c galera openstack-galera-0 /bin/sh -c 'source /var/lib/operator-scripts/root_auth.sh; mysql -uroot -p${DB_ROOT_PASSWORD} -Nse "select @@global.ssl_cipher;"' | grep -v '^NULL$' diff --git a/tests/kuttl/tests/galera_log_to_disk/01-assert.yaml b/tests/kuttl/tests/galera_log_to_disk/01-assert.yaml index 74a26acb..c79ade1a 100644 --- a/tests/kuttl/tests/galera_log_to_disk/01-assert.yaml +++ b/tests/kuttl/tests/galera_log_to_disk/01-assert.yaml @@ -32,6 +32,10 @@ status: reason: Ready status: "True" type: InputReady + - message: MariaDBAccount System account 'root' creation complete + reason: Ready + status: "True" + type: MariaDBAccountReady - message: RoleBinding created reason: Ready status: "True"