Skip to content

Commit

Permalink
new API to allow services to generate MariaDBAccount
Browse files Browse the repository at this point in the history
  • Loading branch information
zzzeek committed Feb 22, 2024
1 parent b37bc25 commit 647dde5
Show file tree
Hide file tree
Showing 10 changed files with 937 additions and 141 deletions.
32 changes: 32 additions & 0 deletions api/test/helpers/crd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ package helpers

import (
"context"
"fmt"
"strings"
"time"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -239,3 +241,33 @@ func (tc *TestHelper) SimulateMariaDBAccountCompleted(name types.NamespacedName)

tc.Logger.Info("Simulated DB Account completed", "on", name)
}

// CreateMariaDBAccount creates and persists a MariaDBAccount resource and
// associated Secret in the Kubernetes cluster
func (tc *TestHelper) CreateMariaDBAccount(name types.NamespacedName) (*mariadbv1.MariaDBAccount, *corev1.Secret) {
secret := tc.CreateSecret(
types.NamespacedName{Namespace: name.Namespace, Name: fmt.Sprintf("%s-db-secret", name.Name)},
map[string][]byte{
"DatabasePassword": []byte(fmt.Sprintf("%s123", name.Name)),
},
)

instance := &mariadbv1.MariaDBAccount{
ObjectMeta: metav1.ObjectMeta{
Name: name.Name,
Namespace: name.Namespace,
},
Spec: mariadbv1.MariaDBAccountSpec{
UserName: fmt.Sprintf("%s_account", strings.Replace(name.Name, "-", "_", -1)),
Secret: fmt.Sprintf("%s-db-secret", name.Name),
},
}

gomega.Eventually(func(g gomega.Gomega) {
g.Expect(tc.K8sClient.Create(tc.Ctx, instance)).Should(gomega.Succeed())
}, tc.Timeout, tc.Interval).Should(gomega.Succeed())

tc.Logger.Info(fmt.Sprintf("Created MariaDBAccount %s, username %s, secret %s", name.Name, instance.Spec.UserName, instance.Spec.Secret))

return instance, secret
}
329 changes: 329 additions & 0 deletions api/test/helpers/harnesses.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
/*
Copyright 2023 Red Hat
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package helpers

import (
"fmt"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/types"
)

// populateHarness describes a function that will insert suite-appropriate
// data into a MariaDBTestHarness instance
type populateHarness func(*MariaDBTestHarness)

// establishesCR describes a test function that can fully set up a particular
// controller's "Reconciliation Successful" state for a given kind of CR.
type establishesCR func(types.NamespacedName)

// updatesAccountName describes a test function that can change the
// "databaseAccount" or similar member of an already-reconciled CR to a new
// one, which is expected to kick off a username/password rotation sequence.
type updatesAccountName func(types.NamespacedName)

// deletesCr describes a test function that will delete the CR that was
// created by an establishesCR function
type deletesCR func()

// MariaDBTestHarness describes the parameters for running a series
// of Ginkgo tests which exercise a controller's ability to correctly
// work with MariaDBDatabase / MariaDBAccount APIs.
type MariaDBTestHarness struct {
Namespace string
DatabaseName string
FinalizerName string
MariaDBHelper *TestHelper
Timeout time.Duration
Interval time.Duration
}

// PopulateHarness receives named arguments to place within a
// MariaDBTestHarness instance.
func (harness *MariaDBTestHarness) PopulateHarness(
namespace string,
databaseName string,
finalizerName string,
mariadb *TestHelper,
timeout time.Duration,
interval time.Duration,
) {
harness.Namespace = namespace
harness.DatabaseName = databaseName
harness.FinalizerName = finalizerName
harness.MariaDBHelper = mariadb
harness.Timeout = timeout
harness.Interval = interval
}

// MariaDBAccountSuiteTests runs MariaDBAccount suite tests. these are
// pre-packaged ginkgo tests that exercise standard account create / update
// patterns that should be common to all controllers that work with
// MariaDBDatabase and MariaDBAccount CRs.
func MariaDBAccountSuiteTests(
description string,
provideHarness populateHarness,
crSetup establishesCR,
updateAccount updatesAccountName,
crDelete deletesCR,
) {

var harness *MariaDBTestHarness

BeforeEach(func() {
harness = &MariaDBTestHarness{}
provideHarness(harness)
})

When(fmt.Sprintf("The %s service is being configured to run", description), func() {
It("Uses a pre-existing MariaDBAccount and sets a finalizer", func() {

mariadb := harness.MariaDBHelper
k8sClient := mariadb.K8sClient

accountName := types.NamespacedName{
Name: "some-mariadb-account",
Namespace: harness.Namespace,
}

// create MariaDBAccount first
acc, accSecret := mariadb.CreateMariaDBAccount(accountName)
DeferCleanup(k8sClient.Delete, mariadb.Ctx, accSecret)
DeferCleanup(k8sClient.Delete, mariadb.Ctx, acc)

// then create the CR
crSetup(accountName)

mariadb.Logger.Info(fmt.Sprintf("Service should fully configure on MariaDBAccount %s", accountName))

// now wait for the account to have the finalizer and the
// database name

Eventually(func() []string {
mariadbAccount := mariadb.GetMariaDBAccount(accountName)
return mariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).Should(ContainElement(harness.FinalizerName))

// CreateOrPatchDBByName will add a label referring to the database
Eventually(func() string {
mariadbAccount := mariadb.GetMariaDBAccount(accountName)
return mariadbAccount.Labels["mariaDBDatabaseName"]
}, harness.Timeout, harness.Interval).Should(Equal(harness.DatabaseName))

})

It("Ensures a MariaDBAccount is created if not present and sets a finalizer", func() {
mariadb := harness.MariaDBHelper

accountName := types.NamespacedName{
Name: "some-mariadb-account",
Namespace: harness.Namespace,
}

// here, dont create a mariadbaccount. right now CRs should
// generate this if not exists using EnsureMariaDBAccount

// then create the CR
crSetup(accountName)

mariadb.Logger.Info(fmt.Sprintf("Service should fully configure on MariaDBAccount %s", accountName))

// now wait for the account to have the finalizer and the
// database name

Eventually(func() []string {
mariadbAccount := mariadb.GetMariaDBAccount(accountName)
return mariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).Should(ContainElement(harness.FinalizerName))

// CreateOrPatchDBByName will add a label referring to the database
Eventually(func() string {
mariadbAccount := mariadb.GetMariaDBAccount(accountName)
return mariadbAccount.Labels["mariaDBDatabaseName"]
}, harness.Timeout, harness.Interval).Should(Equal(harness.DatabaseName))

})
})

When(fmt.Sprintf("The %s service is fully running", description), func() {
// get service fully complete with a mariadbaccount
BeforeEach(func() {
mariadb := harness.MariaDBHelper

oldAccountName := types.NamespacedName{
Name: "some-old-account",
Namespace: harness.Namespace,
}

// create the CR with old account
crSetup(oldAccountName)

// also simulate that it got completed
mariadb.SimulateMariaDBAccountCompleted(oldAccountName)

mariadb.Logger.Info(fmt.Sprintf("Service should fully configure on MariaDBAccount %s", oldAccountName))

// finalizer is attached to old account
Eventually(func() []string {
oldMariadbAccount := mariadb.GetMariaDBAccount(oldAccountName)
return oldMariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).Should(ContainElement(harness.FinalizerName))

})
It("should ensure a new MariaDBAccount exists when accountname is changed", func() {
mariadb := harness.MariaDBHelper

oldAccountName := types.NamespacedName{
Name: "some-old-account",
Namespace: harness.Namespace,
}

newAccountName := types.NamespacedName{
Name: "some-new-account",
Namespace: harness.Namespace,
}

mariadb.Logger.Info("About to update account from some-old-account to some-new-account")

updateAccount(newAccountName)

// new account is (eventually) created
_ = mariadb.GetMariaDBAccount(newAccountName)

// dont simuluate MariaDBAccount being created. it's not done yet

mariadb.Logger.Info(
fmt.Sprintf("Service should have ensured MariaDBAccount %s exists but should remain running on %s",
newAccountName, oldAccountName),
)

// finalizer is attached to new account
Eventually(func() []string {
newMariadbAccount := mariadb.GetMariaDBAccount(newAccountName)
return newMariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).Should(ContainElement(harness.FinalizerName))

// old account retains the finalizer because we did not yet
// complete the new MariaDBAccount
Eventually(func() []string {
oldMariadbAccount := mariadb.GetMariaDBAccount(oldAccountName)
return oldMariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).Should(ContainElement(harness.FinalizerName))
})

It("should move the finalizer to a new MariaDBAccount when create is complete", func() {
mariadb := harness.MariaDBHelper

oldAccountName := types.NamespacedName{
Name: "some-old-account",
Namespace: harness.Namespace,
}

newAccountName := types.NamespacedName{
Name: "some-new-account",
Namespace: harness.Namespace,
}

updateAccount(newAccountName)

// new account is (eventually) created
_ = mariadb.GetMariaDBAccount(newAccountName)

// also simulate that it got completed
mariadb.SimulateMariaDBAccountCompleted(newAccountName)

mariadb.Logger.Info(
fmt.Sprintf("Service should move to run fully off MariaDBAccount %s and remove finalizer from %s",
newAccountName, oldAccountName),
)

// finalizer is attached to new account
Eventually(func() []string {
newMariadbAccount := mariadb.GetMariaDBAccount(newAccountName)
return newMariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).Should(ContainElement(harness.FinalizerName))

// CreateOrPatchDBByName will add a label referring to the database
Eventually(func() string {
mariadbAccount := mariadb.GetMariaDBAccount(newAccountName)
return mariadbAccount.Labels["mariaDBDatabaseName"]
}, harness.Timeout, harness.Interval).Should(Equal(harness.DatabaseName))

// finalizer removed from old account
Eventually(func() []string {
oldMariadbAccount := mariadb.GetMariaDBAccount(oldAccountName)
return oldMariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).ShouldNot(ContainElement(harness.FinalizerName))
})

It("should remove the finalizer from all associated MariaDBAccount objects regardless of status when deleted", func() {
mariadb := harness.MariaDBHelper

oldAccountName := types.NamespacedName{
Name: "some-old-account",
Namespace: harness.Namespace,
}

newAccountName := types.NamespacedName{
Name: "some-new-account",
Namespace: harness.Namespace,
}

mariadb.Logger.Info("About to update account from some-old-account to some-new-account")

updateAccount(newAccountName)

// new account is (eventually) created
_ = mariadb.GetMariaDBAccount(newAccountName)

// dont simuluate MariaDBAccount being created, so that finalizer is
// on both

mariadb.Logger.Info(
fmt.Sprintf("Service should have ensured MariaDBAccount %s exists but should remain running on %s",
newAccountName, oldAccountName),
)

// as before, both accounts have a finalizer
Eventually(func() []string {
newMariadbAccount := mariadb.GetMariaDBAccount(newAccountName)
return newMariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).Should(ContainElement(harness.FinalizerName))

Eventually(func() []string {
oldMariadbAccount := mariadb.GetMariaDBAccount(oldAccountName)
return oldMariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).Should(ContainElement(harness.FinalizerName))

// now delete the CR
crDelete()

// finalizer is removed from both as part of the delete
// process
Eventually(func() []string {
newMariadbAccount := mariadb.GetMariaDBAccount(newAccountName)
return newMariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).ShouldNot(ContainElement(harness.FinalizerName))

Eventually(func() []string {
oldMariadbAccount := mariadb.GetMariaDBAccount(oldAccountName)
return oldMariadbAccount.Finalizers
}, harness.Timeout, harness.Interval).ShouldNot(ContainElement(harness.FinalizerName))

})
})

}
6 changes: 6 additions & 0 deletions api/v1beta1/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,15 @@ const (

MariaDBAccountReadyMessage = "MariaDBAccount creation complete"

MariaDBAccountNotReadyMessage = "MariaDBAccount is not present: %s"

MariaDBAccountSecretNotReadyMessage = "MariaDBAccount secret is missing or incomplete: %s"

MariaDBErrorRetrievingMariaDBDatabaseMessage = "Error retrieving MariaDBDatabase instance %s"

MariaDBErrorRetrievingMariaDBGaleraMessage = "Error retrieving MariaDB/Galera instance %s"

MariaDBAccountFinalizersRemainMessage = "Waiting for finalizers %s to be removed before dropping username"

MariaDBAccountReadyForDeleteMessage = "MariaDBAccount ready for delete"
)
Loading

0 comments on commit 647dde5

Please sign in to comment.