Skip to content

Commit

Permalink
Make Medusa's config map be a merge of MedusaConfig object and cluste…
Browse files Browse the repository at this point in the history
…r spec (#1187)
  • Loading branch information
rzvoncek authored Jan 29, 2024
1 parent 651e3f7 commit a6271b4
Show file tree
Hide file tree
Showing 12 changed files with 417 additions and 77 deletions.
1 change: 1 addition & 0 deletions CHANGELOG/CHANGELOG-1.12.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ When cutting a new release, update the `unreleased` heading to the tag being gen

## unreleased

* [CHANGE] [#1158](https://github.com/k8ssandra/k8ssandra-operator/issues/1158) Use the MedusaConfiguration API when creating Medusa configuration
* [CHANGE] [#1050](https://github.com/k8ssandra/k8ssandra-operator/issues/1050) Remove unnecessary requeues in the Medusa controllers
* [CHANGE] [#1165](https://github.com/k8ssandra/k8ssandra-operator/issues/1165) Upgrade to Medusa v0.17.1
* [FEATURE] [#1157](https://github.com/k8ssandra/k8ssandra-operator/issues/1157) Add the MedusaConfiguration API
Expand Down
11 changes: 11 additions & 0 deletions apis/k8ssandra/v1alpha1/k8ssandracluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
ErrNoStorageConfig = fmt.Errorf("storageConfig must be defined at cluster level or dc level")
ErrNoResourcesSet = fmt.Errorf("softPodAntiAffinity requires Resources to be set")
ErrClusterName = fmt.Errorf("cluster name can not be changed")
ErrNoStoragePrefix = fmt.Errorf("medusa storage prefix must be set when a medusaConfigurationRef is used")
)

// log is for logging in this package.
Expand Down Expand Up @@ -88,6 +89,16 @@ func (r *K8ssandraCluster) validateK8ssandraCluster() error {
}
}

// Verify the Medusa storage prefix is explicitly set
// only relevant if Medusa is enabled and the MedusaConfiguration object is referenced
if r.Spec.Medusa != nil {
if r.Spec.Medusa.MedusaConfigurationRef.Name != "" {
if r.Spec.Medusa.StorageProperties.Prefix == "" {
return ErrNoStoragePrefix
}
}
}

if err := r.validateStatefulsetNameSize(); err != nil {
return err
}
Expand Down
44 changes: 44 additions & 0 deletions apis/k8ssandra/v1alpha1/k8ssandracluster_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"

medusaapi "github.com/k8ssandra/k8ssandra-operator/apis/medusa/v1alpha1"
reaperapi "github.com/k8ssandra/k8ssandra-operator/apis/reaper/v1alpha1"
)

Expand Down Expand Up @@ -145,6 +146,7 @@ func TestWebhook(t *testing.T) {
t.Run("NumTokensValidation", testNumTokens)
t.Run("NumTokensValidationInUpdate", testNumTokensInUpdate)
t.Run("StsNameTooLong", testStsNameTooLong)
t.Run("MedusaPrefixMissing", testMedusaPrefixMissing)
}

func testContextValidation(t *testing.T) {
Expand Down Expand Up @@ -407,3 +409,45 @@ func createMinimalClusterObj(name, namespace string) *K8ssandraCluster {
},
}
}

func testMedusaPrefixMissing(t *testing.T) {
required := require.New(t)
createNamespace(required, "short-namespace")

clusterWithoutMedusa := createMinimalClusterObj("without-medusa", "short-namespace")
err := k8sClient.Create(ctx, clusterWithoutMedusa)
required.NoError(err)

clusterWithMedusa := createMinimalClusterObj("with-medusa", "short-namespace")
clusterWithMedusa.Spec.Medusa = &medusaapi.MedusaClusterTemplate{
StorageProperties: medusaapi.Storage{
Prefix: "",
},
}
err = k8sClient.Create(ctx, clusterWithMedusa)
required.NoError(err)

clusterWithoutPrefix := createMinimalClusterObj("without-prefix", "short-namespace")
clusterWithoutPrefix.Spec.Medusa = &medusaapi.MedusaClusterTemplate{
MedusaConfigurationRef: corev1.ObjectReference{
Name: "medusa-config",
},
StorageProperties: medusaapi.Storage{
Prefix: "",
},
}
err = k8sClient.Create(ctx, clusterWithoutPrefix)
required.Error(err)

clusterWithPrefix := createMinimalClusterObj("with-prefix", "short-namespace")
clusterWithPrefix.Spec.Medusa = &medusaapi.MedusaClusterTemplate{
MedusaConfigurationRef: corev1.ObjectReference{
Name: "medusa-config",
},
StorageProperties: medusaapi.Storage{
Prefix: "some-prefix",
},
}
err = k8sClient.Create(ctx, clusterWithPrefix)
required.NoError(err)
}
6 changes: 6 additions & 0 deletions apis/medusa/v1alpha1/medusa_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ type PodStorageSettings struct {
}

type MedusaClusterTemplate struct {
// MedusaConfigurationRef points to an existing MedusaConfiguration object.
// The purpose is to allow shared default settings across several clusters.
// StorageProperties override the settings from MedusaConfiguration object to allow customization.
// +optional
MedusaConfigurationRef corev1.ObjectReference `json:"medusaConfigurationRef,omitempty"`

// MedusaContainerImage is the image characteristics to use for Medusa containers. Leave nil
// to use a default image.
// +optional
Expand Down
1 change: 1 addition & 0 deletions apis/medusa/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions config/crd/bases/k8ssandra.io_k8ssandraclusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27021,6 +27021,46 @@ spec:
format: int32
type: integer
type: object
medusaConfigurationRef:
description: MedusaConfigurationRef points to an existing MedusaConfiguration
object. The purpose is to allow shared default settings across
several clusters. StorageProperties override the settings from
MedusaConfiguration object to allow customization.
properties:
apiVersion:
description: API version of the referent.
type: string
fieldPath:
description: 'If referring to a piece of an object instead
of an entire object, this string should contain a valid
JSON/Go field access statement, such as desiredState.manifest.containers[2].
For example, if the object reference is to a container within
a pod, this would take on a value like: "spec.containers{name}"
(where "name" refers to the name of the container that triggered
the event) or if no container name is specified "spec.containers[2]"
(container with index 2 in this pod). This syntax is chosen
only to have some well-defined way of referencing a part
of an object. TODO: this design is not final and this field
is subject to change in the future.'
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
resourceVersion:
description: 'Specific resourceVersion to which this reference
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
type: string
uid:
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
type: string
type: object
x-kubernetes-map-type: atomic
readinessProbe:
description: Define the readiness probe settings to use for the
Medusa containers.
Expand Down
2 changes: 2 additions & 0 deletions controllers/k8ssandra/k8ssandracluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ func TestK8ssandraCluster(t *testing.T) {
t.Run("CreateMultiDcClusterWithStargate", testEnv.ControllerTest(ctx, createMultiDcClusterWithStargate))
t.Run("CreateMultiDcClusterWithReaper", testEnv.ControllerTest(ctx, createMultiDcClusterWithReaper))
t.Run("CreateMultiDcClusterWithMedusa", testEnv.ControllerTest(ctx, createMultiDcClusterWithMedusa))
t.Run("CreateSingleDcClusterWithMedusaConfigRef", testEnv.ControllerTest(ctx, createSingleDcClusterWithMedusaConfigRef))
t.Run("CreatingSingleDcClusterWithoutPrefixInClusterSpecFail", testEnv.ControllerTest(ctx, creatingSingleDcClusterWithoutPrefixInClusterSpecFails))
t.Run("CreateSingleDcClusterNoAuth", testEnv.ControllerTest(ctx, createSingleDcClusterNoAuth))
t.Run("CreateSingleDcClusterAuth", testEnv.ControllerTest(ctx, createSingleDcClusterAuth))
t.Run("CreateSingleDcClusterAuthExternalSecrets", testEnv.ControllerTest(ctx, createSingleDcClusterAuthExternalSecrets))
Expand Down
46 changes: 45 additions & 1 deletion controllers/k8ssandra/medusa_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package k8ssandra
import (
"context"
"fmt"
"github.com/adutra/goalesce"
medusaapi "github.com/k8ssandra/k8ssandra-operator/apis/medusa/v1alpha1"
"k8s.io/apimachinery/pkg/types"

"github.com/go-logr/logr"
api "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1"
Expand All @@ -21,17 +24,23 @@ import (
// Create all things Medusa related in the cassdc podTemplateSpec
func (r *K8ssandraClusterReconciler) reconcileMedusa(
ctx context.Context,
kc *api.K8ssandraCluster,
desiredKc *api.K8ssandraCluster,
dcConfig *cassandra.DatacenterConfig,
remoteClient client.Client,
logger logr.Logger,
) result.ReconcileResult {
kc := desiredKc.DeepCopy()
namespace := utils.FirstNonEmptyString(dcConfig.Meta.Namespace, kc.Namespace)
logger.Info("Medusa reconcile for " + dcConfig.CassDcName() + " on namespace " + namespace)
medusaSpec := kc.Spec.Medusa
if medusaSpec != nil {
logger.Info("Medusa is enabled")

mergeResult := mergeStorageProperties(ctx, remoteClient, namespace, medusaSpec, logger, kc)
if mergeResult.IsError() {
return result.Error(mergeResult.GetError())
}

// Check that certificates are provided if client encryption is enabled
if cassandra.ClientEncryptionEnabled(dcConfig) {
if kc.Spec.UseExternalSecrets() {
Expand Down Expand Up @@ -203,3 +212,38 @@ func (r *K8ssandraClusterReconciler) reconcileMedusaConfigMap(
logger.Info("Medusa ConfigMap successfully reconciled")
return result.Continue()
}

func mergeStorageProperties(
ctx context.Context,
remoteClient client.Client,
namespace string,
medusaSpec *medusaapi.MedusaClusterTemplate,
logger logr.Logger,
desiredKc *api.K8ssandraCluster,
) result.ReconcileResult {
// check if the StorageProperties are defined in the K8ssandraCluster
if medusaSpec.MedusaConfigurationRef.Name == "" {
return result.Continue()
}
storageProperties := &medusaapi.MedusaConfiguration{}
configKey := types.NamespacedName{Namespace: namespace, Name: medusaSpec.MedusaConfigurationRef.Name}
if err := remoteClient.Get(ctx, configKey, storageProperties); err != nil {
logger.Error(err, fmt.Sprintf("failed to get MedusaConfiguration %s", configKey))
return result.Error(err)
}
// check if the StorageProperties from the cluster have the prefix field set
// it is required to be present because that's the single thing that differentiates backups of two different clusters
if desiredKc.Spec.Medusa.StorageProperties.Prefix == "" {
return result.Error(fmt.Errorf("StorageProperties.Prefix is not set in K8ssandraCluster %s", utils.GetKey(desiredKc)))
}
// try to merge the storage properties. goalesce gives priority to the 2nd argument,
// so stuff in the cluster overrides stuff in the config object
mergedProperties, err := goalesce.DeepMerge(storageProperties.Spec.StorageProperties, desiredKc.Spec.Medusa.StorageProperties)
if err != nil {
logger.Error(err, "failed to merge MedusaConfiguration StorageProperties")
return result.Error(err)
}
// copy the merged properties back into the cluster
mergedProperties.DeepCopyInto(&desiredKc.Spec.Medusa.StorageProperties)
return result.Continue()
}
Loading

0 comments on commit a6271b4

Please sign in to comment.