Skip to content

Commit 1945c37

Browse files
committed
Replicate medusa bucket secrets to each Cass DC
1 parent 75cc260 commit 1945c37

File tree

14 files changed

+243
-19
lines changed

14 files changed

+243
-19
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
## unreleased
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("BucketSecretsGetReplicated", testEnv.ControllerTest(ctx, bucketSecretGetsReplicated))
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

+73-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

@@ -36,7 +36,7 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
3636
if medusaSpec != nil {
3737
logger.Info("Medusa is enabled")
3838

39-
mergeResult := mergeStorageProperties(ctx, remoteClient, namespace, medusaSpec, logger, kc)
39+
mergeResult := r.mergeStorageProperties(ctx, remoteClient, namespace, medusaSpec, logger, kc)
4040
if mergeResult.IsError() {
4141
return result.Error(mergeResult.GetError())
4242
}
@@ -49,6 +49,11 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
4949
return result.Error(fmt.Errorf("medusa encryption certificates were not provided despite client encryption being enabled"))
5050
}
5151
}
52+
53+
recRes := r.reconcileBucketSecrets(ctx, remoteClient, kc, logger, namespace)
54+
if recRes.IsError() {
55+
return recRes
56+
}
5257
if medusaSpec.StorageProperties.StorageSecretRef.Name == "" {
5358
return result.Error(fmt.Errorf("medusa storage secret is not defined for storage provider %s", medusaSpec.StorageProperties.StorageProvider))
5459
}
@@ -92,7 +97,7 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
9297
// Reconcile the Medusa standalone deployment
9398
kcKey := utils.GetKey(kc)
9499
desiredMedusaStandalone.SetLabels(labels.CleanedUpByLabels(kcKey))
95-
recRes := reconciliation.ReconcileObject(ctx, remoteClient, r.DefaultDelay, *desiredMedusaStandalone)
100+
recRes = reconciliation.ReconcileObject(ctx, remoteClient, r.DefaultDelay, *desiredMedusaStandalone)
96101
switch {
97102
case recRes.IsError():
98103
return recRes
@@ -213,7 +218,7 @@ func (r *K8ssandraClusterReconciler) reconcileMedusaConfigMap(
213218
return result.Continue()
214219
}
215220

216-
func mergeStorageProperties(
221+
func (r *K8ssandraClusterReconciler) mergeStorageProperties(
217222
ctx context.Context,
218223
remoteClient client.Client,
219224
namespace string,
@@ -229,7 +234,7 @@ func mergeStorageProperties(
229234
configKey := types.NamespacedName{Namespace: namespace, Name: medusaSpec.MedusaConfigurationRef.Name}
230235
if err := remoteClient.Get(ctx, configKey, storageProperties); err != nil {
231236
logger.Error(err, fmt.Sprintf("failed to get MedusaConfiguration %s", configKey))
232-
return result.Error(err)
237+
return result.RequeueSoon(r.DefaultDelay)
233238
}
234239
// check if the StorageProperties from the cluster have the prefix field set
235240
// it is required to be present because that's the single thing that differentiates backups of two different clusters
@@ -247,3 +252,64 @@ func mergeStorageProperties(
247252
mergedProperties.DeepCopyInto(&desiredKc.Spec.Medusa.StorageProperties)
248253
return result.Continue()
249254
}
255+
256+
func (r *K8ssandraClusterReconciler) reconcileBucketSecrets(ctx context.Context, remoteClient client.Client, kc *api.K8ssandraCluster, logger logr.Logger, namespace string) result.ReconcileResult {
257+
logger.Info("Reconciling Medusa bucket secrets")
258+
medusaSpec := kc.Spec.Medusa
259+
// there is nothing to reconcile if we're not using Medusa configuration reference
260+
if medusaSpec.MedusaConfigurationRef.Name == "" {
261+
logger.Info("MedusaConfigurationRef is not set, skipping bucket secret reconciliation")
262+
return result.Continue()
263+
}
264+
265+
// fetch the referenced configuration
266+
medusaConfigNamespace := utils.FirstNonEmptyString(medusaSpec.MedusaConfigurationRef.Namespace, namespace)
267+
medusaConfigName := medusaSpec.MedusaConfigurationRef.Name
268+
medusaConfig := &medusaapi.MedusaConfiguration{}
269+
medusaConfigKey := types.NamespacedName{Namespace: medusaConfigNamespace, Name: medusaConfigName}
270+
if err := remoteClient.Get(ctx, medusaConfigKey, medusaConfig); err != nil {
271+
logger.Error(err, "failed to get MedusaConfiguration", "key", medusaConfigKey)
272+
return result.Error(err)
273+
}
274+
275+
// fetch the referenced medusa configuration's bucket secret
276+
bucketSecretName := medusaConfig.Spec.StorageProperties.StorageSecretRef.Name
277+
bucketSecret := &corev1.Secret{}
278+
bucketSecretKey := types.NamespacedName{Namespace: medusaConfigNamespace, Name: bucketSecretName}
279+
err := remoteClient.Get(ctx, bucketSecretKey, bucketSecret)
280+
if err != nil {
281+
logger.Error(err, "failed to get bucket secret", "key", bucketSecretKey)
282+
return result.Error(err)
283+
}
284+
285+
// write the secret into the namespace of the K8ssandraCluster
286+
clusterBucketSecret := bucketSecret.DeepCopy()
287+
clusterBucketSecret.ResourceVersion = ""
288+
clusterBucketSecret.Name = fmt.Sprintf("%s-%s", kc.Name, bucketSecret.Name)
289+
clusterBucketSecret.Namespace = kc.Namespace
290+
labels.SetReplicatedBy(clusterBucketSecret, utils.GetKey(kc))
291+
err = remoteClient.Create(ctx, clusterBucketSecret)
292+
if err != nil {
293+
if !errors.IsAlreadyExists(err) {
294+
logger.Error(err, fmt.Sprintf("failed to create cluster bucket secret %s", clusterBucketSecret))
295+
return result.Error(err)
296+
}
297+
// we already have the bucket secret, so continue to updating the cluster (it might have failed before)
298+
}
299+
300+
// finally, update the storage properties to point to a newly created secret
301+
//if kc.Spec.Medusa.StorageProperties.StorageSecretRef.Name == clusterBucketSecret.Name {
302+
// logger.Info("The cluster bucket secret is already set")
303+
// return result.Continue()
304+
//} else {
305+
// newSecretRef := &corev1.LocalObjectReference{Name: clusterBucketSecret.Name}
306+
// kc.Spec.Medusa.StorageProperties.StorageSecretRef = *newSecretRef
307+
// if err = remoteClient.Update(ctx, kc); err != nil {
308+
// logger.Error(err, "failed to update K8ssandraCluster")
309+
// return result.Error(err)
310+
// }
311+
// logger.Info("Medusa bucket secrets successfully updated", "cluster", kc.Name, "namespace", kc.Namespace)
312+
// return result.Continue()
313+
//}
314+
return result.Continue()
315+
}

controllers/k8ssandra/medusa_reconciler_test.go

+74
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,77 @@ func creatingSingleDcClusterWithoutPrefixInClusterSpecFails(t *testing.T, ctx co
538538
// verify the cluster still doesn't get created
539539
require.Never(f.DatacenterExists(ctx, dc1Key), timeout, interval)
540540
}
541+
542+
func keyFor(f *framework.Framework, object metav1.Object, contextIndex int) framework.ClusterKey {
543+
return framework.ClusterKey{NamespacedName: utils.GetKey(object), K8sContext: f.DataPlaneContexts[contextIndex]}
544+
}
545+
546+
func bucketSecretGetsReplicated(t *testing.T, ctx context.Context, f *framework.Framework, namespace string) {
547+
require := require.New(t)
548+
549+
// create a storage secret, then a MedusaConfiguration that points to it
550+
medusaSecret := &corev1.Secret{
551+
ObjectMeta: metav1.ObjectMeta{
552+
Name: storageSecret,
553+
Namespace: namespace,
554+
},
555+
StringData: map[string]string{
556+
"credentials": "some-credentials",
557+
},
558+
}
559+
err := f.Create(ctx, keyFor(f, medusaSecret, 0), medusaSecret)
560+
require.NoError(err, "failed to create secret")
561+
562+
medusaConfig := MedusaConfig(namespace)
563+
medusaConfig.Spec.StorageProperties.StorageSecretRef = corev1.LocalObjectReference{
564+
Name: medusaSecret.Name,
565+
}
566+
err = f.Create(ctx, keyFor(f, medusaConfig, 0), medusaConfig)
567+
require.NoError(err, "failed to create MedusaConfiguration")
568+
569+
// create a 2-dc K8ssandraCluster with Medusa featuring the reference to the above MedusaConfiguration
570+
kc := &api.K8ssandraCluster{
571+
ObjectMeta: metav1.ObjectMeta{
572+
Name: k8ssandraClusterName,
573+
Namespace: namespace,
574+
},
575+
Spec: api.K8ssandraClusterSpec{
576+
Cassandra: &api.CassandraClusterTemplate{
577+
Datacenters: []api.CassandraDatacenterTemplate{
578+
dcTemplate("dc1", f.DataPlaneContexts[0]),
579+
dcTemplate("dc2", f.DataPlaneContexts[2]),
580+
},
581+
},
582+
Medusa: &medusaapi.MedusaClusterTemplate{
583+
MedusaConfigurationRef: corev1.ObjectReference{
584+
Namespace: namespace,
585+
Name: medusaConfig.Name,
586+
},
587+
StorageProperties: medusaapi.Storage{
588+
Prefix: "some-prefix",
589+
StorageSecretRef: corev1.LocalObjectReference{
590+
Name: fmt.Sprintf("%s-%s", k8ssandraClusterName, medusaConfig.Name),
591+
},
592+
},
593+
},
594+
},
595+
}
596+
597+
//key := framework.ClusterKey{NamespacedName: utils.GetKey(kc), K8sContext: f.DataPlaneContexts[1]}
598+
//err = f.Create(ctx, key, kc)
599+
err = f.Client.Create(ctx, kc)
600+
601+
require.NoError(err, "failed to create K8ssandraCluster")
602+
603+
verifySuperuserSecretCreated(ctx, t, f, kc)
604+
verifyReplicatedSecretReconciled(ctx, t, f, kc)
605+
606+
dc1Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}, K8sContext: f.DataPlaneContexts[0]}
607+
require.Eventually(f.DatacenterExists(ctx, dc1Key), timeout, interval)
608+
t.Log("update dc1 status to ready")
609+
err = f.SetDatacenterStatusReady(ctx, dc1Key)
610+
require.NoError(err, "failed to update dc1 status to ready")
611+
dc2Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc2"}, K8sContext: f.DataPlaneContexts[2]}
612+
require.Eventually(f.DatacenterExists(ctx, dc2Key), timeout, interval)
613+
614+
}

docs/content/en/tasks/backup-restore/_index.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ spec:
8888
# size: 100Mi
8989
```
9090

91-
The definition above requires a secret named `medusa-bucket-key` to be created in the target namespace before the `K8ssandraCluster` object gets created. Use the following format for this secret:
91+
The definition above requires a secret named `medusa-bucket-key` to be present in the target namespace before the `K8ssandraCluster` object gets created. Use the following format for this secret:
9292

9393
```yaml
9494
apiVersion: v1
@@ -106,9 +106,11 @@ stringData:
106106
107107
The file should always specify `credentials` as shown in the example above; in that section, provide the expected format and credential values that are expected by Medusa for the chosen storage backend. For more, refer to the [Medusa documentation](https://github.com/thelastpickle/cassandra-medusa/blob/master/docs/Installation.md) to know which file format should used for each supported storage backend.
108108

109-
A successful deployment should inject a new init container named `medusa-restore` and a new container named `medusa` in the Cassandra StatefulSet pods.
109+
If using a shared Medusa configuration (see below), this secret can be created in the same namespace as the `MedusaConfiguration` object. The K8ssandra operator will then make sure the secret is replicated to the namespaces hosting the Cassandra clusters.
110110

111-
## Using shared medusa configuration properties
111+
A successful deployment should inject a new init container named `medusa-restore` and a new container named `medusa` in the Cassandra StatefulSet pods.
112+
113+
## Using shared Medusa configuration properties
112114

113115
Medusa configuration properties can be shared across multiple K8ssandraClusters by creating a `MedusaConfiguration` custom resource in the Control Plane K8ssandra cluster.
114116
Example:

test/e2e/cluster_scope_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func multiDcMultiCluster(t *testing.T, ctx context.Context, klusterNamespace str
2323

2424
dc1Key := framework.ClusterKey{K8sContext: f.DataPlaneContexts[0], NamespacedName: types.NamespacedName{Namespace: dc1Namespace, Name: "dc1"}}
2525
checkDatacenterReady(t, ctx, dc1Key, f)
26+
checkBucketKeyPresent(t, f, ctx, dc1Namespace, dc1Key.K8sContext, k8ssandra)
2627

2728
t.Log("check k8ssandra cluster status")
2829
require.Eventually(func() bool {
@@ -41,6 +42,7 @@ func multiDcMultiCluster(t *testing.T, ctx context.Context, klusterNamespace str
4142

4243
dc2Key := framework.ClusterKey{K8sContext: f.DataPlaneContexts[1], NamespacedName: types.NamespacedName{Namespace: dc2Namespace, Name: "dc2"}}
4344
checkDatacenterReady(t, ctx, dc2Key, f)
45+
checkBucketKeyPresent(t, f, ctx, dc2Namespace, dc2Key.K8sContext, k8ssandra)
4446

4547
t.Log("check k8ssandra cluster status")
4648
require.Eventually(func() bool {

test/e2e/medusa_test.go

+24-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import (
2222
)
2323

2424
const (
25-
backupName = "backup1"
26-
clusterName = "test"
25+
backupName = "backup1"
26+
clusterName = "test"
27+
globalBucketSecretName = "global-bucket-key"
2728
)
2829

2930
func createSingleMedusaJob(t *testing.T, ctx context.Context, namespace string, f *framework.E2eFramework) {
@@ -69,6 +70,11 @@ func createMultiMedusaJob(t *testing.T, ctx context.Context, namespace string, f
6970
dc1Key := framework.ClusterKey{K8sContext: "kind-k8ssandra-0", NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}}
7071
dc2Key := framework.ClusterKey{K8sContext: "kind-k8ssandra-1", NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc2"}}
7172

73+
// Check that Medusa's bucket key has been replicated to the current namespace
74+
for _, dcKey := range []framework.ClusterKey{dc1Key, dc2Key} {
75+
checkBucketKeyPresent(t, f, ctx, namespace, dcKey.K8sContext, kc)
76+
}
77+
7278
// Check that both DCs are ready and have Medusa containers
7379
for _, dcKey := range []framework.ClusterKey{dc1Key, dc2Key} {
7480
checkDatacenterReady(t, ctx, dcKey, f)
@@ -112,6 +118,22 @@ func createMultiDcSingleMedusaJob(t *testing.T, ctx context.Context, namespace s
112118
verifyBackupJobFinished(t, ctx, f, dcKey, backupKey)
113119
}
114120

121+
func checkBucketKeyPresent(t *testing.T, f *framework.E2eFramework, ctx context.Context, namespace string, k8sContext string, kc *k8ssandraapi.K8ssandraCluster) {
122+
require := require.New(t)
123+
124+
// work out the name of the replicated bucket key. should be "clusterName-<original-bucket-key-name>"
125+
localBucketKeyName := fmt.Sprintf("%s-%s", kc.Name, globalBucketSecretName)
126+
127+
// Check that the bucket key has been replicated to the current namespace
128+
bucketKey := &corev1.Secret{}
129+
require.Eventually(func() bool {
130+
err := f.Get(ctx, framework.NewClusterKey(k8sContext, namespace, localBucketKeyName), bucketKey)
131+
return err == nil
132+
}, polling.medusaConfigurationReady.timeout, polling.medusaConfigurationReady.interval,
133+
fmt.Sprintf("Error getting the Medusa bucket key secret. Context: %s, ClusterName: %s, Namespace: %s", k8sContext, kc.SanitizedName(), namespace),
134+
)
135+
}
136+
115137
func checkMedusaContainersExist(t *testing.T, ctx context.Context, namespace string, dcKey framework.ClusterKey, f *framework.E2eFramework, kc *api.K8ssandraCluster) {
116138
require := require.New(t)
117139
// Get the Cassandra pod

test/e2e/suite_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ func TestOperator(t *testing.T) {
277277
clusterScoped: true,
278278
sutNamespace: "test-0",
279279
additionalNamespaces: []string{"test-1", "test-2"},
280+
installMinio: true,
280281
}))
281282
})
282283
t.Run("CreateSingleMedusaJob", e2eTest(ctx, &e2eTestOpts{

test/testdata/fixtures/multi-dc-cluster-scope/k8ssandra.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ metadata:
44
name: test
55
namespace: test-0
66
spec:
7+
medusa:
8+
medusaConfigurationRef:
9+
name: global-medusa-config
10+
namespace: test-0
11+
storageProperties:
12+
prefix: test
713
cassandra:
814
serverVersion: "3.11.14"
915
storageConfig:
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
apiVersion: kustomize.config.k8s.io/v1beta1
22
kind: Kustomization
33
resources:
4+
- medusa-config.yaml
45
- k8ssandra.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: global-bucket-key
5+
namespace: test-1
6+
type: Opaque
7+
stringData:
8+
# Note that this currently has to be set to credentials!
9+
credentials: |-
10+
[default]
11+
aws_access_key_id = k8ssandra
12+
aws_secret_access_key = k8ssandra
13+
---
14+
apiVersion: medusa.k8ssandra.io/v1alpha1
15+
kind: MedusaConfiguration
16+
metadata:
17+
name: global-medusa-config
18+
namespace: test-1
19+
spec:
20+
storageProperties:
21+
storageProvider: s3_compatible
22+
bucketName: k8ssandra-medusa
23+
storageSecretRef:
24+
name: global-bucket-key
25+
host: minio-service.minio.svc.cluster.local
26+
port: 9000
27+
secure: false

test/testdata/fixtures/multi-dc-encryption-medusa/k8ssandra.yaml

+2-7
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,10 @@ metadata:
2121
name: test
2222
spec:
2323
medusa:
24+
medusaConfigurationRef:
25+
name: global-medusa-config
2426
storageProperties:
25-
storageProvider: s3_compatible
26-
bucketName: k8ssandra-medusa
2727
prefix: test
28-
storageSecretRef:
29-
name: medusa-bucket-key
30-
host: minio-service.minio.svc.cluster.local
31-
port: 9000
32-
secure: false
3328
certificatesSecretRef:
3429
name: client-certificates
3530
cassandra:
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
apiVersion: kustomize.config.k8s.io/v1beta1
22
kind: Kustomization
33
resources:
4+
- medusa-config.yaml
45
- k8ssandra.yaml

0 commit comments

Comments
 (0)