Skip to content

Commit

Permalink
feat: Handle updates of TCPs without dataStoreSchema (+ tests)
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonKienzler committed Sep 3, 2024
1 parent 8a0d469 commit 007be8e
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 1 deletion.
18 changes: 17 additions & 1 deletion internal/resources/datastore/datastore_storage_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,25 @@ func (r *Config) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.
username = coalesceFn(tenantControlPlane.Status.Storage.Setup.User)
}

var dataStoreSchema string
switch {
case len(tenantControlPlane.Spec.DataStoreSchema) > 0:
// for new TCPs, the spec field will have been provided by the user
// or defaulted by the defaulting webhook
dataStoreSchema = tenantControlPlane.Spec.DataStoreSchema
case len(tenantControlPlane.Status.Storage.Setup.Schema) > 0:
// for existing TCPs, the dataStoreSchema will be adopted from the status,
// as the mutating webhook only takes care of TCP creations, not updates
dataStoreSchema = tenantControlPlane.Status.Storage.Setup.Schema
tenantControlPlane.Spec.DataStoreSchema = dataStoreSchema
default:
// this can only happen on TCP creations when the webhook is not installed
return fmt.Errorf("cannot build datastore storage config, schema name must either exist in Spec or Status")
}

r.resource.Data = map[string][]byte{
"DB_CONNECTION_STRING": []byte(r.ConnString),
"DB_SCHEMA": []byte(tenantControlPlane.Spec.DataStoreSchema),
"DB_SCHEMA": []byte(dataStoreSchema),
"DB_USER": username,
"DB_PASSWORD": password,
}
Expand Down
115 changes: 115 additions & 0 deletions internal/resources/datastore/datastore_storage_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0

package datastore_test

import (
"context"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/resources"
"github.com/clastix/kamaji/internal/resources/datastore"
)

var _ = Describe("DatastoreStorageConfig", func() {
var (
ctx context.Context
dsc *datastore.Config
tcp *kamajiv1alpha1.TenantControlPlane
ds *kamajiv1alpha1.DataStore
)

BeforeEach(func() {
ctx = context.Background()

tcp = &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "tcp",
Namespace: "default",
},
Spec: kamajiv1alpha1.TenantControlPlaneSpec{},
}

ds = &kamajiv1alpha1.DataStore{
ObjectMeta: metav1.ObjectMeta{
Name: "datastore",
Namespace: "default",
},
}

Expect(kamajiv1alpha1.AddToScheme(scheme)).To(Succeed())
Expect(corev1.AddToScheme(scheme)).To(Succeed())
})

JustBeforeEach(func() {
fakeClient = fake.NewClientBuilder().
WithScheme(scheme).WithObjects(tcp).WithStatusSubresource(tcp).Build()

dsc = &datastore.Config{
Client: fakeClient,
ConnString: "",
DataStore: *ds,
}
})

When("TCP has no dataStoreSchema defined", func() {
It("should return an error", func() {
_, err := resources.Handle(ctx, dsc, tcp)
Expect(err).To(HaveOccurred())
})
})

When("TCP has dataStoreSchema set in spec", func() {
BeforeEach(func() {
tcp.Spec.DataStoreSchema = "custom-prefix"
})

It("should create the datastore secret with the schema name from the spec", func() {
op, err := resources.Handle(ctx, dsc, tcp)
Expect(err).ToNot(HaveOccurred())
Expect(op).To(Equal(controllerutil.OperationResultCreated))

secrets := &corev1.SecretList{}
Expect(fakeClient.List(ctx, secrets)).To(Succeed())
Expect(secrets.Items).To(HaveLen(1))
Expect(secrets.Items[0].Data["DB_SCHEMA"]).To(Equal([]byte("custom-prefix")))
})
})

When("TCP has dataStoreSchema set in status, but not in spec", func() {
// this test case ensures that existing TCPs (created in a CRD version without
// the dataStoreSchema field) correctly adopt the spec field from the status.

It("should create the datastore secret with the correct schema name and update the TCP spec", func() {
By("updating the TCP status")
Expect(fakeClient.Get(ctx, client.ObjectKeyFromObject(tcp), tcp)).To(Succeed())
tcp.Status.Storage.Setup.Schema = "existing-schema-name"
Expect(fakeClient.Status().Update(ctx, tcp)).To(Succeed())

By("handling the resource")
op, err := resources.Handle(ctx, dsc, tcp)
Expect(err).ToNot(HaveOccurred())
Expect(op).To(Equal(controllerutil.OperationResultCreated))

By("checking the secret")
secrets := &corev1.SecretList{}
Expect(fakeClient.List(ctx, secrets)).To(Succeed())
Expect(secrets.Items).To(HaveLen(1))
Expect(secrets.Items[0].Data["DB_SCHEMA"]).To(Equal([]byte("existing-schema-name")))

By("checking the TCP spec")
// we have to check the modified struct here (instead of retrieving the object
// via the fakeClient), as the TCP resource update is not done by the resources.
// Instead, the TCP controller will handle TCP updates after handling all resources
tcp.Spec.DataStoreSchema = "existing-schema-name"
})
})
})
23 changes: 23 additions & 0 deletions internal/resources/datastore/datastore_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0

package datastore_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
fakeClient client.Client
scheme *runtime.Scheme = runtime.NewScheme()
)

func TestDatastore(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Datastore Suite")
}

0 comments on commit 007be8e

Please sign in to comment.