Skip to content

Commit b8562e8

Browse files
committed
Replicate medusa bucket secrets to each Cass DC
1 parent ad43fce commit b8562e8

File tree

14 files changed

+342
-33
lines changed

14 files changed

+342
-33
lines changed

CHANGELOG/CHANGELOG-1.12.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ When cutting a new release, update the `unreleased` heading to the tag being gen
1515

1616
## v1.12.0 - 2024-02-02
1717

18+
* [ENHANCEMENT] [#1159](https://github.com/k8ssandra/k8ssandra-operator/issues/1159) Replicate bucket key secrets to namespaces hosting clusters
1819
* [CHANGE] Upgrade to Medusa v0.17.2
1920
* [CHANGE] [#1158](https://github.com/k8ssandra/k8ssandra-operator/issues/1158) Use the MedusaConfiguration API when creating Medusa configuration
2021
* [CHANGE] [#1050](https://github.com/k8ssandra/k8ssandra-operator/issues/1050) Remove unnecessary requeues in the Medusa controllers

controllers/k8ssandra/k8ssandracluster_controller_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ func TestK8ssandraCluster(t *testing.T) {
103103
t.Run("CreateMultiDcClusterWithMedusa", testEnv.ControllerTest(ctx, createMultiDcClusterWithMedusa))
104104
t.Run("CreateSingleDcClusterWithMedusaConfigRef", testEnv.ControllerTest(ctx, createSingleDcClusterWithMedusaConfigRef))
105105
t.Run("CreatingSingleDcClusterWithoutPrefixInClusterSpecFail", testEnv.ControllerTest(ctx, creatingSingleDcClusterWithoutPrefixInClusterSpecFails))
106+
t.Run("CreateMultiDcClusterWithReplicatedSecrets", testEnv.ControllerTest(ctx, createMultiDcClusterWithReplicatedSecrets))
106107
t.Run("CreateSingleDcClusterNoAuth", testEnv.ControllerTest(ctx, createSingleDcClusterNoAuth))
107108
t.Run("CreateSingleDcClusterAuth", testEnv.ControllerTest(ctx, createSingleDcClusterAuth))
108109
t.Run("CreateSingleDcClusterAuthExternalSecrets", testEnv.ControllerTest(ctx, createSingleDcClusterAuthExternalSecrets))

controllers/k8ssandra/medusa_reconciler.go

+71-7
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import (
44
"context"
55
"fmt"
66
"github.com/adutra/goalesce"
7-
medusaapi "github.com/k8ssandra/k8ssandra-operator/apis/medusa/v1alpha1"
8-
"k8s.io/apimachinery/pkg/types"
9-
107
"github.com/go-logr/logr"
118
api "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1"
9+
medusaapi "github.com/k8ssandra/k8ssandra-operator/apis/medusa/v1alpha1"
1210
cassandra "github.com/k8ssandra/k8ssandra-operator/pkg/cassandra"
1311
"github.com/k8ssandra/k8ssandra-operator/pkg/labels"
1412
medusa "github.com/k8ssandra/k8ssandra-operator/pkg/medusa"
@@ -18,6 +16,8 @@ import (
1816
"github.com/k8ssandra/k8ssandra-operator/pkg/utils"
1917
appsv1 "k8s.io/api/apps/v1"
2018
corev1 "k8s.io/api/core/v1"
19+
"k8s.io/apimachinery/pkg/api/errors"
20+
"k8s.io/apimachinery/pkg/types"
2121
"sigs.k8s.io/controller-runtime/pkg/client"
2222
)
2323

@@ -32,11 +32,11 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
3232
kc := desiredKc.DeepCopy()
3333
namespace := utils.FirstNonEmptyString(dcConfig.Meta.Namespace, kc.Namespace)
3434
logger.Info("Medusa reconcile for " + dcConfig.CassDcName() + " on namespace " + namespace)
35-
medusaSpec := kc.Spec.Medusa
36-
if medusaSpec != nil {
35+
if kc.Spec.Medusa != nil {
3736
logger.Info("Medusa is enabled")
3837

39-
mergeResult := mergeStorageProperties(ctx, remoteClient, namespace, medusaSpec, logger, kc)
38+
mergeResult := r.mergeStorageProperties(ctx, r.Client, namespace, kc.Spec.Medusa, logger, kc)
39+
medusaSpec := kc.Spec.Medusa
4040
if mergeResult.IsError() {
4141
return result.Error(mergeResult.GetError())
4242
}
@@ -49,6 +49,7 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
4949
return result.Error(fmt.Errorf("medusa encryption certificates were not provided despite client encryption being enabled"))
5050
}
5151
}
52+
5253
if medusaSpec.StorageProperties.StorageSecretRef.Name == "" {
5354
medusaSpec.StorageProperties.StorageSecretRef = corev1.LocalObjectReference{Name: ""}
5455
if medusaSpec.StorageProperties.CredentialsType == "role-based" && medusaSpec.StorageProperties.StorageProvider == "s3" {
@@ -187,6 +188,11 @@ func (r *K8ssandraClusterReconciler) reconcileMedusaSecrets(
187188
logger.Error(err, "Failed to reconcile Medusa CQL user secret")
188189
return result.Error(err)
189190
}
191+
192+
if err := r.reconcileBucketSecrets(ctx, r.ClientCache.GetLocalClient(), kc, logger); err != nil {
193+
logger.Error(err, "Failed to reconcile Medusa bucket secrets")
194+
return result.Error(err)
195+
}
190196
}
191197

192198
logger.Info("Medusa user secrets successfully reconciled")
@@ -219,7 +225,7 @@ func (r *K8ssandraClusterReconciler) reconcileMedusaConfigMap(
219225
return result.Continue()
220226
}
221227

222-
func mergeStorageProperties(
228+
func (r *K8ssandraClusterReconciler) mergeStorageProperties(
223229
ctx context.Context,
224230
remoteClient client.Client,
225231
namespace string,
@@ -249,7 +255,65 @@ func mergeStorageProperties(
249255
logger.Error(err, "failed to merge MedusaConfiguration StorageProperties")
250256
return result.Error(err)
251257
}
258+
// medusaapi.MedusaConfiguration comes with a storage corev1.Secret containing the credentials to access the storage
259+
// we make a copy of that secret for each cluster/dc, and then point to it with a corev1.LocalObjectReference
260+
// when we do the copy, we name the secret as <cluster-name>-<original-secret-name>
261+
// here we need to update the reference to point to that copied secret
262+
mergedProperties.StorageSecretRef.Name = fmt.Sprintf("%s-%s", desiredKc.Name, mergedProperties.StorageSecretRef.Name)
263+
252264
// copy the merged properties back into the cluster
253265
mergedProperties.DeepCopyInto(&desiredKc.Spec.Medusa.StorageProperties)
254266
return result.Continue()
255267
}
268+
269+
func (r *K8ssandraClusterReconciler) reconcileBucketSecrets(
270+
ctx context.Context,
271+
c client.Client,
272+
kc *api.K8ssandraCluster,
273+
logger logr.Logger,
274+
) error {
275+
276+
logger.Info("Reconciling Medusa bucket secrets")
277+
medusaSpec := kc.Spec.Medusa
278+
279+
// there is nothing to reconcile if we're not using Medusa configuration reference
280+
if medusaSpec == nil || medusaSpec.MedusaConfigurationRef.Name == "" {
281+
logger.Info("MedusaConfigurationRef is not set, skipping bucket secret reconciliation")
282+
return nil
283+
}
284+
285+
// fetch the referenced configuration
286+
medusaConfigName := medusaSpec.MedusaConfigurationRef.Name
287+
medusaConfigNamespace := utils.FirstNonEmptyString(medusaSpec.MedusaConfigurationRef.Namespace, kc.Namespace)
288+
medusaConfigKey := types.NamespacedName{Namespace: medusaConfigNamespace, Name: medusaConfigName}
289+
medusaConfig := &medusaapi.MedusaConfiguration{}
290+
if err := c.Get(ctx, medusaConfigKey, medusaConfig); err != nil {
291+
logger.Error(err, fmt.Sprintf("could not get MedusaConfiguration %s/%s", medusaConfigNamespace, medusaConfigName))
292+
return err
293+
}
294+
295+
// fetch the referenced medusa configuration's bucket secret
296+
bucketSecretName := medusaConfig.Spec.StorageProperties.StorageSecretRef.Name
297+
bucketSecret := &corev1.Secret{}
298+
bucketSecretKey := types.NamespacedName{Namespace: medusaConfigNamespace, Name: bucketSecretName}
299+
if err := c.Get(ctx, bucketSecretKey, bucketSecret); err != nil {
300+
logger.Error(err, "could not get bucket Secret")
301+
return err
302+
}
303+
304+
// write the secret into the namespace of the K8ssandraCluster
305+
clusterBucketSecret := bucketSecret.DeepCopy()
306+
clusterBucketSecret.ResourceVersion = ""
307+
clusterBucketSecret.Name = fmt.Sprintf("%s-%s", kc.Name, bucketSecret.Name)
308+
clusterBucketSecret.Namespace = kc.Namespace
309+
labels.SetReplicatedBy(clusterBucketSecret, utils.GetKey(kc))
310+
if err := c.Create(ctx, clusterBucketSecret); err != nil {
311+
if !errors.IsAlreadyExists(err) {
312+
logger.Error(err, fmt.Sprintf("failed to create cluster bucket secret %s", clusterBucketSecret))
313+
return err
314+
}
315+
// we already have the bucket secret, so continue to updating the cluster (it might have failed before)
316+
}
317+
318+
return nil
319+
}

controllers/k8ssandra/medusa_reconciler_test.go

+157-14
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const (
3131
cassandraUserSecret = "medusa-secret"
3232
k8ssandraClusterName = "test"
3333
medusaConfigName = "medusa-config"
34+
medusaBucketSecretName = "medusa-bucket-secret"
3435
prefixFromMedusaConfig = "prefix-from-medusa-config"
3536
prefixFromClusterSpec = "prefix-from-cluster-spec"
3637
)
@@ -53,10 +54,10 @@ func dcTemplate(dcName string, dataPlaneContext string) api.CassandraDatacenterT
5354
}
5455
}
5556

56-
func MedusaConfig(namespace string) *medusaapi.MedusaConfiguration {
57+
func MedusaConfig(name, namespace string) *medusaapi.MedusaConfiguration {
5758
return &medusaapi.MedusaConfiguration{
5859
ObjectMeta: metav1.ObjectMeta{
59-
Name: medusaConfigName,
60+
Name: name,
6061
Namespace: namespace,
6162
},
6263
Spec: medusaapi.MedusaConfigurationSpec{
@@ -71,21 +72,22 @@ func medusaTemplateWithoutConfigRef() *medusaapi.MedusaClusterTemplate {
7172
return medusaTemplate(nil)
7273
}
7374

74-
func medusaTemplateWithConfigRef(configRefName string) *medusaapi.MedusaClusterTemplate {
75+
func medusaTemplateWithConfigRef(configRefName, namespace string) *medusaapi.MedusaClusterTemplate {
7576
configRef := &corev1.ObjectReference{
76-
Name: configRefName,
77+
Name: configRefName,
78+
Namespace: namespace,
7779
}
7880
return medusaTemplate(configRef)
7981
}
8082

81-
func medusaTemplateWithConfigRefWithoutPrefix(configRefName string) *medusaapi.MedusaClusterTemplate {
82-
template := medusaTemplateWithConfigRef(configRefName)
83+
func medusaTemplateWithConfigRefWithoutPrefix(configRefName, namespace string) *medusaapi.MedusaClusterTemplate {
84+
template := medusaTemplateWithConfigRef(configRefName, namespace)
8385
template.StorageProperties.Prefix = ""
8486
return template
8587
}
8688

87-
func medusaTemplateWithConfigRefWithPrefix(configRefName string, prefix string) *medusaapi.MedusaClusterTemplate {
88-
template := medusaTemplateWithConfigRef(configRefName)
89+
func medusaTemplateWithConfigRefWithPrefix(configRefName, namespace, prefix string) *medusaapi.MedusaClusterTemplate {
90+
template := medusaTemplateWithConfigRef(configRefName, namespace)
8991
template.StorageProperties.Prefix = prefix
9092
return template
9193
}
@@ -440,11 +442,28 @@ func reconcileMedusaStandaloneDeployment(ctx context.Context, t *testing.T, f *f
440442
func createSingleDcClusterWithMedusaConfigRef(t *testing.T, ctx context.Context, f *framework.Framework, namespace string) {
441443
require := require.New(t)
442444

445+
t.Log("Creating Medusa Bucket secret")
446+
medusaSecret := &corev1.Secret{
447+
ObjectMeta: metav1.ObjectMeta{
448+
Name: medusaBucketSecretName,
449+
Namespace: namespace,
450+
},
451+
StringData: map[string]string{
452+
"credentials": "some-credentials",
453+
},
454+
}
455+
// create the secret in the control plane
456+
err := f.Create(ctx, controlPlaneContextKey(f, medusaSecret, f.ControlPlaneContext), medusaSecret)
457+
require.NoError(err, fmt.Sprintf("failed to create secret in control plane %s", f.ControlPlaneContext))
458+
443459
t.Log("Creating Medusa Configuration object")
444-
medusaConfigKey := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: medusaConfigName}, K8sContext: f.DataPlaneContexts[0]}
445-
err := f.Create(ctx, medusaConfigKey, MedusaConfig(namespace))
460+
medusaConfig := MedusaConfig(medusaConfigName, namespace)
461+
medusaConfig.Spec.StorageProperties.StorageSecretRef = corev1.LocalObjectReference{Name: medusaBucketSecretName}
462+
medusaConfigKey := controlPlaneContextKey(f, medusaConfig, f.ControlPlaneContext)
463+
err = f.Create(ctx, medusaConfigKey, medusaConfig)
464+
446465
require.NoError(err, "failed to create Medusa Configuration")
447-
require.Eventually(f.MedusaConfigExists(ctx, f.DataPlaneContexts[0], medusaConfigKey), timeout, interval)
466+
require.Eventually(f.MedusaConfigExists(ctx, f.ControlPlaneContext, medusaConfigKey), timeout, interval)
448467

449468
kc := &api.K8ssandraCluster{
450469
ObjectMeta: metav1.ObjectMeta{
@@ -457,7 +476,7 @@ func createSingleDcClusterWithMedusaConfigRef(t *testing.T, ctx context.Context,
457476
dcTemplate("dc1", f.DataPlaneContexts[0]),
458477
},
459478
},
460-
Medusa: medusaTemplateWithConfigRefWithPrefix(medusaConfigName, prefixFromClusterSpec),
479+
Medusa: medusaTemplateWithConfigRefWithPrefix(medusaConfigName, namespace, prefixFromClusterSpec),
461480
},
462481
}
463482
require.NotNil(kc.Spec.Medusa.MedusaConfigurationRef)
@@ -500,7 +519,7 @@ func creatingSingleDcClusterWithoutPrefixInClusterSpecFails(t *testing.T, ctx co
500519
dcTemplate("dc1", f.DataPlaneContexts[0]),
501520
},
502521
},
503-
Medusa: medusaTemplateWithConfigRefWithoutPrefix(medusaConfigName),
522+
Medusa: medusaTemplateWithConfigRefWithoutPrefix(medusaConfigName, namespace),
504523
},
505524
}
506525
require.NotNil(kcFirstAttempt.Spec.Medusa.MedusaConfigurationRef)
@@ -520,7 +539,7 @@ func creatingSingleDcClusterWithoutPrefixInClusterSpecFails(t *testing.T, ctx co
520539
// create the MedusaConfiguration object
521540
t.Log("Creating Medusa Configuration object")
522541
medusaConfigKey := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: medusaConfigName}, K8sContext: f.DataPlaneContexts[0]}
523-
err = f.Create(ctx, medusaConfigKey, MedusaConfig(namespace))
542+
err = f.Create(ctx, medusaConfigKey, MedusaConfig(medusaConfigName, namespace))
524543
require.NoError(err, "failed to create Medusa Configuration")
525544
require.Eventually(f.MedusaConfigExists(ctx, f.DataPlaneContexts[0], medusaConfigKey), timeout, interval)
526545

@@ -538,3 +557,127 @@ func creatingSingleDcClusterWithoutPrefixInClusterSpecFails(t *testing.T, ctx co
538557
// verify the cluster still doesn't get created
539558
require.Never(f.DatacenterExists(ctx, dc1Key), timeout, interval)
540559
}
560+
561+
func controlPlaneContextKey(f *framework.Framework, object metav1.Object, contextName string) framework.ClusterKey {
562+
return framework.ClusterKey{NamespacedName: utils.GetKey(object), K8sContext: contextName}
563+
}
564+
565+
func dataPlaneContextKey(f *framework.Framework, object metav1.Object, dataPlaneContextIndex int) framework.ClusterKey {
566+
return framework.ClusterKey{NamespacedName: utils.GetKey(object), K8sContext: f.DataPlaneContexts[dataPlaneContextIndex]}
567+
}
568+
569+
func createMultiDcClusterWithReplicatedSecrets(t *testing.T, ctx context.Context, f *framework.Framework, namespace string) {
570+
require := require.New(t)
571+
572+
clusterName := "test-cluster"
573+
originalConfigName := "test-config"
574+
originalSecretName := fmt.Sprintf("%s-bucket-key", originalConfigName)
575+
clusterSecretName := fmt.Sprintf("%s-%s", clusterName, originalSecretName)
576+
577+
// create a storage secret, then a MedusaConfiguration that points to it
578+
// the ReplicatedSecrets controller is not loaded in env tests, so we "mock" it by replicating the secrets manually
579+
medusaSecret := &corev1.Secret{
580+
ObjectMeta: metav1.ObjectMeta{
581+
Name: originalSecretName,
582+
Namespace: namespace,
583+
},
584+
StringData: map[string]string{
585+
"credentials": "some-credentials",
586+
},
587+
}
588+
// create the secret in the control plane
589+
cpMedusaSecret := medusaSecret.DeepCopy()
590+
err := f.Create(ctx, controlPlaneContextKey(f, cpMedusaSecret, f.ControlPlaneContext), cpMedusaSecret)
591+
require.NoError(err, fmt.Sprintf("failed to create secret in control plane %s", f.ControlPlaneContext))
592+
//create the secret in the data planes
593+
for i, n := range f.DataPlaneContexts {
594+
dpMedusaSecret := medusaSecret.DeepCopy()
595+
dpMedusaSecret.Name = clusterSecretName
596+
err := f.Create(ctx, dataPlaneContextKey(f, dpMedusaSecret, i), dpMedusaSecret)
597+
require.NoError(err, fmt.Sprintf("failed to create secret in context %d (%s)", i, n))
598+
}
599+
600+
// create medusa config in the control plane only
601+
medusaConfig := MedusaConfig(originalConfigName, namespace)
602+
medusaConfig.Spec.StorageProperties.StorageSecretRef = corev1.LocalObjectReference{
603+
Name: originalSecretName,
604+
}
605+
cpMedusaConfig := medusaConfig.DeepCopy()
606+
err = f.Create(ctx, controlPlaneContextKey(f, cpMedusaConfig, f.ControlPlaneContext), cpMedusaConfig)
607+
require.NoError(err, fmt.Sprintf("failed to create MedusaConfiguration in control plane %s", f.ControlPlaneContext))
608+
609+
// create a 2-dc K8ssandraCluster with Medusa featuring the reference to the above MedusaConfiguration
610+
kc := &api.K8ssandraCluster{
611+
ObjectMeta: metav1.ObjectMeta{
612+
Name: clusterName,
613+
Namespace: namespace,
614+
},
615+
Spec: api.K8ssandraClusterSpec{
616+
Cassandra: &api.CassandraClusterTemplate{
617+
Datacenters: []api.CassandraDatacenterTemplate{
618+
dcTemplate("dc1", f.DataPlaneContexts[1]),
619+
dcTemplate("dc2", f.DataPlaneContexts[2]),
620+
},
621+
},
622+
Medusa: &medusaapi.MedusaClusterTemplate{
623+
MedusaConfigurationRef: corev1.ObjectReference{
624+
Namespace: namespace,
625+
Name: originalConfigName,
626+
},
627+
StorageProperties: medusaapi.Storage{
628+
Prefix: "some-prefix",
629+
},
630+
},
631+
},
632+
}
633+
err = f.Client.Create(ctx, kc)
634+
require.NoError(err, "failed to create K8ssandraCluster")
635+
636+
verifySuperuserSecretCreated(ctx, t, f, kc)
637+
verifyReplicatedSecretReconciled(ctx, t, f, kc)
638+
639+
reconcileMedusaStandaloneDeployment(ctx, t, f, kc, "dc1", f.DataPlaneContexts[1])
640+
641+
// crate the first DC
642+
dc1Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}, K8sContext: f.DataPlaneContexts[1]}
643+
require.Eventually(f.DatacenterExists(ctx, dc1Key), timeout, interval)
644+
645+
// mark the first DC as ready
646+
t.Log("update dc1 status to ready")
647+
err = f.SetDatacenterStatusReady(ctx, dc1Key)
648+
require.NoError(err, "failed to update dc1 status to ready")
649+
650+
// create the second DC
651+
reconcileMedusaStandaloneDeployment(ctx, t, f, kc, "dc2", f.DataPlaneContexts[2])
652+
dc2Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc2"}, K8sContext: f.DataPlaneContexts[2]}
653+
require.Eventually(f.DatacenterExists(ctx, dc2Key), timeout, interval)
654+
655+
// verify the copied secret is mounted in the pods
656+
verifyBucketSecretMounted(ctx, t, f, dc1Key, clusterSecretName)
657+
verifyBucketSecretMounted(ctx, t, f, dc2Key, clusterSecretName)
658+
659+
// verify the cluster's spec still contains the correct value
660+
// which is empty because we used MedusaConfigRef
661+
// merged it at runtime but never persisted to the k8ssandraCluster object
662+
kc = &api.K8ssandraCluster{}
663+
err = f.Client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: clusterName}, kc)
664+
require.NoError(err, "failed to get K8ssandraCluster")
665+
require.Equal("", kc.Spec.Medusa.StorageProperties.StorageSecretRef.Name)
666+
}
667+
668+
func verifyBucketSecretMounted(ctx context.Context, t *testing.T, f *framework.Framework, dcKey framework.ClusterKey, clusterSecretName string) {
669+
require := require.New(t)
670+
671+
// fetch the DC spec
672+
dc := &cassdcapi.CassandraDatacenter{}
673+
err := f.Get(ctx, dcKey, dc)
674+
require.NoError(err, fmt.Sprintf("failed to get %s", dcKey.Name))
675+
676+
// fetch medusa container
677+
containerIndex, found := cassandra.FindContainer(dc.Spec.PodTemplateSpec, "medusa")
678+
require.True(found, fmt.Sprintf("%s doesn't have medusa container", dc.Name))
679+
medusaContainer := dc.Spec.PodTemplateSpec.Spec.Containers[containerIndex]
680+
681+
// check its mount
682+
assert.True(t, f.ContainerHasVolumeMount(medusaContainer, clusterSecretName, "/etc/medusa-secrets"), "Missing Volume Mount for Medusa bucket key")
683+
}

0 commit comments

Comments
 (0)