diff --git a/Dockerfile b/Dockerfile index d06966bc..730d7fe8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.22 AS builder +FROM golang:1.23 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/Makefile b/Makefile index 9f8a367e..b11663b0 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,7 @@ test: manifests generate fmt vet lint envtest ## Run tests. # Old unit tests first - these use mocked client / fakeclient go test ./pkg/... -coverprofile cover-pkg.out # Then the envtest ones - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test -v ./apis/... ./internal/controllers/... -coverprofile cover.out + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test -v ./apis/... ./internal/... -coverprofile cover.out .PHONY: integ-test integ-test: kustomize cert-manager helm ## Run integration tests from directory M_INTEG_DIR or set M_INTEG_DIR=all to run all the integration tests. diff --git a/apis/cassandra/v1beta1/zz_generated.deepcopy.go b/apis/cassandra/v1beta1/zz_generated.deepcopy.go index 3eb2a944..752b690d 100644 --- a/apis/cassandra/v1beta1/zz_generated.deepcopy.go +++ b/apis/cassandra/v1beta1/zz_generated.deepcopy.go @@ -23,7 +23,7 @@ package v1beta1 import ( "encoding/json" "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/cmd/main.go b/cmd/main.go index 41f24b5f..4a4f71bd 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -45,6 +45,7 @@ import ( controlv1alpha1 "github.com/k8ssandra/cass-operator/apis/control/v1alpha1" controllers "github.com/k8ssandra/cass-operator/internal/controllers/cassandra" controlcontrollers "github.com/k8ssandra/cass-operator/internal/controllers/control" + apiwebhook "github.com/k8ssandra/cass-operator/internal/webhooks/cassandra/v1beta1" "github.com/k8ssandra/cass-operator/pkg/images" "github.com/k8ssandra/cass-operator/pkg/utils" ) @@ -189,7 +190,7 @@ func main() { } if !operConfig.DisableWebhooks { - if err = (&api.CassandraDatacenter{}).SetupWebhookWithManager(mgr); err != nil { + if err = apiwebhook.SetupCassandraDatacenterWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "CassandraDatacenter") os.Exit(1) } diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 8a34529f..99172d68 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -1,12 +1,37 @@ --- apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-cassandra-datastax-com-v1beta1-cassandradatacenter + failurePolicy: Fail + name: mcassandradatacenter.kb.io + rules: + - apiGroups: + - cassandra.datastax.com + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - cassandradatacenters + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration webhooks: - admissionReviewVersions: - v1 - - v1beta1 clientConfig: service: name: webhook-service diff --git a/go.mod b/go.mod index 040a2dc7..996bff04 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/k8ssandra/cass-operator -go 1.22.0 +go 1.23.0 toolchain go1.23.4 diff --git a/apis/cassandra/v1beta1/cassandradatacenter_webhook.go b/internal/webhooks/cassandra/v1beta1/cassandradatacenter_webhook.go similarity index 68% rename from apis/cassandra/v1beta1/cassandradatacenter_webhook.go rename to internal/webhooks/cassandra/v1beta1/cassandradatacenter_webhook.go index 1ff2ff8a..8fa299dd 100644 --- a/apis/cassandra/v1beta1/cassandradatacenter_webhook.go +++ b/internal/webhooks/cassandra/v1beta1/cassandradatacenter_webhook.go @@ -1,5 +1,5 @@ /* -Copyright 2021. +Copyright 2024 Just Me. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,12 +19,9 @@ package v1beta1 import ( "context" "encoding/json" - "errors" "fmt" "strings" - "github.com/google/go-cmp/cmp" - "github.com/k8ssandra/cass-operator/pkg/images" apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -32,56 +29,96 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/google/go-cmp/cmp" + api "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" + "github.com/k8ssandra/cass-operator/pkg/images" ) +// log is for logging in this package. const ( datastaxPrefix string = "cassandra.datastax.com" ) var log = logf.Log.WithName("api") -func (dc *CassandraDatacenter) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - WithValidator(dc). - WithDefaulter(dc). - For(dc). +// SetupCassandraDatacenterWebhookWithManager registers the webhook for CassandraDatacenter in the manager. +func SetupCassandraDatacenterWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&api.CassandraDatacenter{}). + WithValidator(&CassandraDatacenterCustomValidator{}). + WithDefaulter(&CassandraDatacenterCustomDefaulter{}). Complete() } -// kubebuilder:webhook:path=/mutate-cassandra-datastax-com-v1beta1-cassandradatacenter,mutating=true,failurePolicy=fail,sideEffects=None,groups=cassandra.datastax.com,resources=cassandradatacenters,verbs=create;update,versions=v1beta1,name=mcassandradatacenter.kb.io,admissionReviewVersions={v1,v1beta1} -// +kubebuilder:webhook:path=/validate-cassandra-datastax-com-v1beta1-cassandradatacenter,mutating=false,failurePolicy=fail,sideEffects=None,groups=cassandra.datastax.com,resources=cassandradatacenters,verbs=create;update,versions=v1beta1,name=vcassandradatacenter.kb.io,admissionReviewVersions={v1,v1beta1} +// +kubebuilder:webhook:path=/mutate-cassandra-datastax-com-v1beta1-cassandradatacenter,mutating=true,failurePolicy=fail,sideEffects=None,groups=cassandra.datastax.com,resources=cassandradatacenters,verbs=create;update,versions=v1beta1,name=mcassandradatacenter.kb.io,admissionReviewVersions=v1 +// +kubebuilder:webhook:path=/validate-cassandra-datastax-com-v1beta1-cassandradatacenter,mutating=false,failurePolicy=fail,sideEffects=None,groups=cassandra.datastax.com,resources=cassandradatacenters,verbs=create;update,versions=v1beta1,name=vcassandradatacenter.kb.io,admissionReviewVersions=v1 -var _ webhook.CustomDefaulter = &CassandraDatacenter{} +// CassandraDatacenterCustomDefaulter struct is responsible for setting default values on the custom resource of the +// Kind CassandraDatacenter when those are created or updated. +type CassandraDatacenterCustomDefaulter struct { +} -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (dc *CassandraDatacenter) Default(ctx context.Context, obj runtime.Object) error { - // No mutations at this point +var _ webhook.CustomDefaulter = &CassandraDatacenterCustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CassandraDatacenter. +func (d *CassandraDatacenterCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { return nil } -func attemptedTo(action string, actionStrArgs ...interface{}) error { - var msg string - if actionStrArgs != nil { - msg = fmt.Sprintf(action, actionStrArgs...) - } else { - msg = action +// CassandraDatacenterCustomValidator struct is responsible for validating the CassandraDatacenter resource +// when it is created, updated, or deleted. +type CassandraDatacenterCustomValidator struct { +} + +var _ webhook.CustomValidator = &CassandraDatacenterCustomValidator{} + +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type CassandraDatacenter. +func (v *CassandraDatacenterCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + dc, ok := obj.(*api.CassandraDatacenter) + if !ok { + return nil, fmt.Errorf("expected a CassandraDatacenter object but got %T", obj) } - return fmt.Errorf("CassandraDatacenter write rejected, attempted to %s", msg) + log.Info("Validation for CassandraDatacenter upon creation", "name", dc.GetName()) + + if err := ValidateSingleDatacenter(dc); err != nil { + return admission.Warnings{}, err + } + + return ValidateDeprecatedFieldUsage(dc), nil } -func deprecatedWarning(field, instead, extra string) string { - warning := "CassandraDatacenter is using deprecated field '%s'" - if instead != "" { - warning += fmt.Sprintf(", use '%s' instead", instead) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type CassandraDatacenter. +func (v *CassandraDatacenterCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + dc, ok := newObj.(*api.CassandraDatacenter) + if !ok { + return nil, fmt.Errorf("expected a CassandraDatacenter object for the newObj but got %T", newObj) } - if extra != "" { - warning += ". %s" + + oldDc, ok := oldObj.(*api.CassandraDatacenter) + if !ok { + return nil, fmt.Errorf("expected a CassandraDatacenter object for the oldObj but got %T", oldObj) } - return warning + + log.Info("Validation for CassandraDatacenter upon update", "name", dc.GetName()) + + if err := ValidateSingleDatacenter(dc); err != nil { + return nil, err + } + + if err := ValidateDatacenterFieldChanges(oldDc, dc); err != nil { + return nil, err + } + + return ValidateDeprecatedFieldUsage(dc), nil +} + +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type CassandraDatacenter. +func (v *CassandraDatacenterCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + return nil, nil } // ValidateSingleDatacenter checks that no values are improperly set on a CassandraDatacenter -func ValidateSingleDatacenter(dc CassandraDatacenter) error { +func ValidateSingleDatacenter(dc *api.CassandraDatacenter) error { // Ensure serverVersion and serverType are compatible if dc.Spec.ServerType == "dse" { @@ -154,7 +191,7 @@ func ValidateSingleDatacenter(dc CassandraDatacenter) error { // ValidateDatacenterFieldChanges checks that no values are improperly changing while updating // a CassandraDatacenter -func ValidateDatacenterFieldChanges(oldDc CassandraDatacenter, newDc CassandraDatacenter) error { +func ValidateDatacenterFieldChanges(oldDc *api.CassandraDatacenter, newDc *api.CassandraDatacenter) error { if oldDc.Spec.ClusterName != newDc.Spec.ClusterName { return attemptedTo("change clusterName") @@ -180,7 +217,7 @@ func ValidateDatacenterFieldChanges(oldDc CassandraDatacenter, newDc CassandraDa newClaimSpec := newDc.Spec.StorageConfig.CassandraDataVolumeClaimSpec.DeepCopy() // CassandraDataVolumeClaimSpec changes are disallowed - if metav1.HasAnnotation(newDc.ObjectMeta, AllowStorageChangesAnnotation) && newDc.Annotations[AllowStorageChangesAnnotation] == "true" { + if metav1.HasAnnotation(newDc.ObjectMeta, api.AllowStorageChangesAnnotation) && newDc.Annotations[api.AllowStorageChangesAnnotation] == "true" { // If the AllowStorageChangesAnnotation is set, we allow changes to the CassandraDataVolumeClaimSpec sizes, but not other fields oldClaimSpec.Resources.Requests = newClaimSpec.Resources.Requests } @@ -206,7 +243,7 @@ func ValidateDatacenterFieldChanges(oldDc CassandraDatacenter, newDc CassandraDa newRackCount := len(newRacks) - len(oldRacks) if newRackCount > 0 { newSizeDifference := newDc.Spec.Size - oldDc.Spec.Size - oldRackNodeSplit := SplitRacks(int(oldDc.Spec.Size), len(oldRacks)) + oldRackNodeSplit := api.SplitRacks(int(oldDc.Spec.Size), len(oldRacks)) minNodesFromOldRacks := oldRackNodeSplit[len(oldRackNodeSplit)-1] minSizeAdjustment := minNodesFromOldRacks * newRackCount @@ -243,7 +280,7 @@ func ValidateDatacenterFieldChanges(oldDc CassandraDatacenter, newDc CassandraDa } // ValidateDeprecatedFieldUsage prevents adding fields that are deprecated -func ValidateDeprecatedFieldUsage(dc CassandraDatacenter) admission.Warnings { +func ValidateDeprecatedFieldUsage(dc *api.CassandraDatacenter) admission.Warnings { warnings := admission.Warnings{} for _, rack := range dc.GetRacks() { if rack.DeprecatedZone != "" { @@ -258,7 +295,7 @@ func ValidateDeprecatedFieldUsage(dc CassandraDatacenter) admission.Warnings { return warnings } -func ValidateAdditionalVolumes(dc CassandraDatacenter) error { +func ValidateAdditionalVolumes(dc *api.CassandraDatacenter) error { for _, volume := range dc.Spec.StorageConfig.AdditionalVolumes { if volume.PVCSpec != nil && volume.VolumeSource != nil { return attemptedTo("create a volume with both PVCSpec and VolumeSource") @@ -272,46 +309,11 @@ func ValidateAdditionalVolumes(dc CassandraDatacenter) error { return nil } -// +kubebuilder:webhook:path=/validate-cassandra-datastax-com-v1beta1-cassandradatacenter,mutating=false,failurePolicy=ignore,sideEffects=None,groups=cassandra.datastax.com,resources=cassandradatacenters,verbs=create;update,versions=v1beta1,name=vcassandradatacenter.kb.io,admissionReviewVersions={v1,v1beta1} -// +kubebuilder:webhook:path=/validate-cassandradatacenter,mutating=false,failurePolicy=ignore,groups=cassandra.datastax.com,resources=cassandradatacenters,verbs=create;update,versions=v1beta1,name=validate-cassandradatacenter-webhook -var _ webhook.CustomValidator = &CassandraDatacenter{} - -func (dc *CassandraDatacenter) ValidateCreate(ctx context.Context, object runtime.Object) (admission.Warnings, error) { - log.Info("Validating webhook called for create") - if err := ValidateSingleDatacenter(*dc); err != nil { - return admission.Warnings{}, err - } - - return ValidateDeprecatedFieldUsage(*dc), nil -} - -func (dc *CassandraDatacenter) ValidateUpdate(ctx context.Context, old runtime.Object, new runtime.Object) (admission.Warnings, error) { - log.Info("Validating webhook called for update") - oldDc, ok := old.(*CassandraDatacenter) - if !ok { - return nil, errors.New("old object in ValidateUpdate cannot be cast to CassandraDatacenter") - } - - if err := ValidateSingleDatacenter(*dc); err != nil { - return nil, err - } - - if err := ValidateDatacenterFieldChanges(*oldDc, *dc); err != nil { - return nil, err - } - - return ValidateDeprecatedFieldUsage(*dc), nil -} - -func (dc *CassandraDatacenter) ValidateDelete(ctx context.Context, object runtime.Object) (admission.Warnings, error) { - return nil, nil -} - var ( ErrFQLNotSupported = fmt.Errorf("full query logging is only supported on OSS Cassandra 4.0+") ) -func ValidateFQLConfig(dc CassandraDatacenter) error { +func ValidateFQLConfig(dc *api.CassandraDatacenter) error { if dc.Spec.Config != nil { enabled, err := dc.FullQueryEnabled() if err != nil { @@ -326,7 +328,7 @@ func ValidateFQLConfig(dc CassandraDatacenter) error { return nil } -func ValidateServiceLabelsAndAnnotations(dc CassandraDatacenter) error { +func ValidateServiceLabelsAndAnnotations(dc *api.CassandraDatacenter) error { // check each service addSeedSvc := dc.Spec.AdditionalServiceConfig.AdditionalSeedService allPodsSvc := dc.Spec.AdditionalServiceConfig.AllPodsService @@ -334,7 +336,7 @@ func ValidateServiceLabelsAndAnnotations(dc CassandraDatacenter) error { nodePortSvc := dc.Spec.AdditionalServiceConfig.NodePortService seedSvc := dc.Spec.AdditionalServiceConfig.SeedService - services := map[string]ServiceConfigAdditions{ + services := map[string]api.ServiceConfigAdditions{ "AdditionalSeedService": addSeedSvc, "AllPOdsService": allPodsSvc, "DatacenterService": dcSvc, @@ -348,21 +350,21 @@ func ValidateServiceLabelsAndAnnotations(dc CassandraDatacenter) error { } } - if metav1.HasAnnotation(dc.ObjectMeta, UpdateAllowedAnnotation) { - updateType := AllowUpdateType(dc.Annotations[UpdateAllowedAnnotation]) - if updateType != AllowUpdateAlways && updateType != AllowUpdateOnce { - return attemptedTo("use %s annotation with value other than 'once' or 'always'", UpdateAllowedAnnotation) + if metav1.HasAnnotation(dc.ObjectMeta, api.UpdateAllowedAnnotation) { + updateType := api.AllowUpdateType(dc.Annotations[api.UpdateAllowedAnnotation]) + if updateType != api.AllowUpdateAlways && updateType != api.AllowUpdateOnce { + return attemptedTo("use %s annotation with value other than 'once' or 'always'", api.UpdateAllowedAnnotation) } } return nil } -func containsReservedAnnotations(config ServiceConfigAdditions) bool { +func containsReservedAnnotations(config api.ServiceConfigAdditions) bool { return containsReservedPrefixes(config.Annotations) } -func containsReservedLabels(config ServiceConfigAdditions) bool { +func containsReservedLabels(config api.ServiceConfigAdditions) bool { return containsReservedPrefixes(config.Labels) } @@ -375,3 +377,24 @@ func containsReservedPrefixes(config map[string]string) bool { } return false } + +func attemptedTo(action string, actionStrArgs ...interface{}) error { + var msg string + if actionStrArgs != nil { + msg = fmt.Sprintf(action, actionStrArgs...) + } else { + msg = action + } + return fmt.Errorf("CassandraDatacenter write rejected, attempted to %s", msg) +} + +func deprecatedWarning(field, instead, extra string) string { + warning := "CassandraDatacenter is using deprecated field '%s'" + if instead != "" { + warning += fmt.Sprintf(", use '%s' instead", instead) + } + if extra != "" { + warning += ". %s" + } + return warning +} diff --git a/apis/cassandra/v1beta1/webhook_test.go b/internal/webhooks/cassandra/v1beta1/cassandradatacenter_webhook_test.go similarity index 79% rename from apis/cassandra/v1beta1/webhook_test.go rename to internal/webhooks/cassandra/v1beta1/cassandradatacenter_webhook_test.go index 68bc2fe0..c0f2778b 100644 --- a/apis/cassandra/v1beta1/webhook_test.go +++ b/internal/webhooks/cassandra/v1beta1/cassandradatacenter_webhook_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + api "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/utils/ptr" @@ -19,16 +20,16 @@ import ( func Test_ValidateSingleDatacenter(t *testing.T) { tests := []struct { name string - dc *CassandraDatacenter + dc *api.CassandraDatacenter errString string }{ { name: "DSE Valid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "6.8.0", }, @@ -37,11 +38,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "DSE 6.8.4 Valid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "6.8.4", }, @@ -50,11 +51,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "DSE 6.9.1 Valid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "6.9.1", }, @@ -63,11 +64,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "HCD 1.0.0 Valid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "hcd", ServerVersion: "1.0.0", }, @@ -76,11 +77,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "DSE 7.0.0 invalid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "7.0.0", }, @@ -89,11 +90,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "DSE Invalid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "4.8.0", }, @@ -102,11 +103,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "DSE 5 Invalid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "5.0.0", }, @@ -115,11 +116,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Cassandra valid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "3.11.7", }, @@ -128,11 +129,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Cassandra 4.0.x must be valid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "4.0.3", }, @@ -141,11 +142,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Cassandra 4.1 must be valid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "4.1.0", }, @@ -154,11 +155,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Cassandra 5.0.0 must be valid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "5.0.0", }, @@ -167,11 +168,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Cassandra Invalid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "6.8.0", }, @@ -180,11 +181,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Cassandra Invalid too", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "7.0.0", }, @@ -193,14 +194,14 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Dse Workloads in Cassandra Invalid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "6.8.0", - DseWorkloads: &DseWorkloads{ + DseWorkloads: &api.DseWorkloads{ AnalyticsEnabled: true, }, }, @@ -209,14 +210,14 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Dse Workloads in Dse valid", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "6.8.4", - DseWorkloads: &DseWorkloads{ + DseWorkloads: &api.DseWorkloads{ AnalyticsEnabled: true, }, }, @@ -225,11 +226,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Cassandra 3.11 invalid config file dse-yaml", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "3.11.7", Config: json.RawMessage(` @@ -246,11 +247,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Cassandra 3.11 invalid config file jvm-server-options", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "3.11.7", Config: json.RawMessage(` @@ -267,11 +268,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "DSE 6.8 invalid config file jvm-options", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "6.8.4", Config: json.RawMessage(` @@ -288,11 +289,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Allow multiple nodes per worker requires resource requests", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "6.8.4", Config: json.RawMessage(`{}`), @@ -313,11 +314,11 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Allow multiple nodes per worker requires resource requests", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "6.8.4", Config: json.RawMessage(`{}`), @@ -328,15 +329,15 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Prevent user specified cassandra.datastax.com Service labels and annotations", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "4.0.4", - AdditionalServiceConfig: ServiceConfig{ - DatacenterService: ServiceConfigAdditions{ + AdditionalServiceConfig: api.ServiceConfig{ + DatacenterService: api.ServiceConfigAdditions{ Labels: map[string]string{"cassandra.datastax.com/key1": "val1"}, Annotations: map[string]string{"cassandra.datastax.com/key2": "val2"}, }, @@ -347,15 +348,15 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Allow user specified k8ssandra.io Service labels and annotations", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "cassandra", ServerVersion: "4.0.4", - AdditionalServiceConfig: ServiceConfig{ - DatacenterService: ServiceConfigAdditions{ + AdditionalServiceConfig: api.ServiceConfig{ + DatacenterService: api.ServiceConfigAdditions{ Labels: map[string]string{"k8ssandra.io/key1": "val1"}, Annotations: map[string]string{"k8ssandra.io/key2": "val2"}, }, @@ -366,14 +367,14 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Allow upgrade should not accept invalid values", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", Annotations: map[string]string{ "cassandra.datastax.com/autoupdate-spec": "invalid", }, }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "6.8.42", }, @@ -382,14 +383,14 @@ func Test_ValidateSingleDatacenter(t *testing.T) { }, { name: "Allow upgrade should accept once value", - dc: &CassandraDatacenter{ + dc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", Annotations: map[string]string{ "cassandra.datastax.com/autoupdate-spec": "once", }, }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: "dse", ServerVersion: "6.8.42", }, @@ -400,7 +401,7 @@ func Test_ValidateSingleDatacenter(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateSingleDatacenter(*tt.dc) + err := ValidateSingleDatacenter(tt.dc) if err == nil { if tt.errString != "" { t.Errorf("ValidateSingleDatacenter() err = %v, want %v", err, tt.errString) @@ -422,22 +423,22 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { tests := []struct { name string - oldDc *CassandraDatacenter - newDc *CassandraDatacenter + oldDc *api.CassandraDatacenter + newDc *api.CassandraDatacenter errString string }{ { name: "No significant changes", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ClusterName: "oldname", AllowMultipleNodesPerWorker: false, SuperuserSecretName: "hush", DeprecatedServiceAccount: "admin", - StorageConfig: StorageConfig{ + StorageConfig: api.StorageConfig{ CassandraDataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{ StorageClassName: storageName, AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, @@ -446,7 +447,7 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, }, }, - Racks: []Rack{{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", @@ -455,16 +456,16 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }}, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ClusterName: "oldname", AllowMultipleNodesPerWorker: false, SuperuserSecretName: "hush", DeprecatedServiceAccount: "admin", - StorageConfig: StorageConfig{ + StorageConfig: api.StorageConfig{ CassandraDataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{ StorageClassName: storageName, AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, @@ -473,7 +474,7 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, }, }, - Racks: []Rack{{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", @@ -486,19 +487,19 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "Clustername changed", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ClusterName: "oldname", }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ClusterName: "newname", }, }, @@ -506,19 +507,19 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "DatacenterName changed", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ DatacenterName: "oldname", }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ DatacenterName: "newname", }, }, @@ -526,19 +527,19 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "AllowMultipleNodesPerWorker changed", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ AllowMultipleNodesPerWorker: false, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ AllowMultipleNodesPerWorker: true, }, }, @@ -546,19 +547,19 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "SuperuserSecretName changed", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ SuperuserSecretName: "hush", }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ SuperuserSecretName: "newsecret", }, }, @@ -566,19 +567,19 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "ServiceAccount changed", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ DeprecatedServiceAccount: "admin", }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ DeprecatedServiceAccount: "newadmin", }, }, @@ -586,12 +587,12 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "StorageConfig changes", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - StorageConfig: StorageConfig{ + Spec: api.CassandraDatacenterSpec{ + StorageConfig: api.StorageConfig{ CassandraDataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{ StorageClassName: storageName, AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, @@ -602,12 +603,12 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - StorageConfig: StorageConfig{ + Spec: api.CassandraDatacenterSpec{ + StorageConfig: api.StorageConfig{ CassandraDataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{ StorageClassName: storageName, AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteMany"}, @@ -622,12 +623,12 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "StorageClassName changes with storageConfig changes allowed", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - StorageConfig: StorageConfig{ + Spec: api.CassandraDatacenterSpec{ + StorageConfig: api.StorageConfig{ CassandraDataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{ StorageClassName: storageName, AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, @@ -638,15 +639,15 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", Annotations: map[string]string{ - AllowStorageChangesAnnotation: "true", + api.AllowStorageChangesAnnotation: "true", }, }, - Spec: CassandraDatacenterSpec{ - StorageConfig: StorageConfig{ + Spec: api.CassandraDatacenterSpec{ + StorageConfig: api.StorageConfig{ CassandraDataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{ StorageClassName: ptr.To[string]("new-server-data"), AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, @@ -661,12 +662,12 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "storage requests size changes with storageConfig changes allowed", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - StorageConfig: StorageConfig{ + Spec: api.CassandraDatacenterSpec{ + StorageConfig: api.StorageConfig{ CassandraDataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{ StorageClassName: storageName, AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, @@ -677,15 +678,15 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", Annotations: map[string]string{ - AllowStorageChangesAnnotation: "true", + api.AllowStorageChangesAnnotation: "true", }, }, - Spec: CassandraDatacenterSpec{ - StorageConfig: StorageConfig{ + Spec: api.CassandraDatacenterSpec{ + StorageConfig: api.StorageConfig{ CassandraDataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{ StorageClassName: storageName, AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, @@ -700,12 +701,12 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "Removing a rack", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - Racks: []Rack{{ + Spec: api.CassandraDatacenterSpec{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", @@ -714,12 +715,12 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }}, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - Racks: []Rack{{ + Spec: api.CassandraDatacenterSpec{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack2", @@ -730,23 +731,23 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "Scaling down", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - Racks: []Rack{{ + Spec: api.CassandraDatacenterSpec{ + Racks: []api.Rack{{ Name: "rack0", }}, Size: 6, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - Racks: []Rack{{ + Spec: api.CassandraDatacenterSpec{ + Racks: []api.Rack{{ Name: "rack0", }}, Size: 3, @@ -756,12 +757,12 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "Changed a rack name", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - Racks: []Rack{{ + Spec: api.CassandraDatacenterSpec{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", @@ -770,12 +771,12 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }}, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - Racks: []Rack{{ + Spec: api.CassandraDatacenterSpec{ + Racks: []api.Rack{{ Name: "rack0-changed", }, { Name: "rack1", @@ -788,13 +789,13 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "Adding a rack is allowed if size increases", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ Size: 3, - Racks: []Rack{{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", @@ -803,13 +804,13 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }}, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ Size: 4, - Racks: []Rack{{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", @@ -824,12 +825,12 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "Adding a rack is not allowed if size doesn't increase", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - Racks: []Rack{{ + Spec: api.CassandraDatacenterSpec{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", @@ -838,12 +839,12 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }}, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ - Racks: []Rack{{ + Spec: api.CassandraDatacenterSpec{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", @@ -858,26 +859,26 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "Adding a rack is not allowed if size doesn't increase enough to prevent moving nodes from existing racks", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ Size: 9, - Racks: []Rack{{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", }}, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ Size: 11, - Racks: []Rack{{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", @@ -890,26 +891,26 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { }, { name: "Adding multiple racks is not allowed if size doesn't increase enough to prevent moving nodes from existing racks", - oldDc: &CassandraDatacenter{ + oldDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ Size: 9, - Racks: []Rack{{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", }}, }, }, - newDc: &CassandraDatacenter{ + newDc: &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ Size: 16, - Racks: []Rack{{ + Racks: []api.Rack{{ Name: "rack0", }, { Name: "rack1", @@ -926,7 +927,7 @@ func Test_ValidateDatacenterFieldChanges(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateDatacenterFieldChanges(*tt.oldDc, *tt.newDc) + err := ValidateDatacenterFieldChanges(tt.oldDc, tt.newDc) if err == nil { if tt.errString != "" { t.Errorf("ValidateDatacenterFieldChanges() err = %v, want %v", err, tt.errString) @@ -950,12 +951,12 @@ var fqlEnabledConfig string = `{"cassandra-yaml": { } ` -func CreateCassDc(serverType string) CassandraDatacenter { - dc := CassandraDatacenter{ +func CreateCassDc(serverType string) *api.CassandraDatacenter { + dc := &api.CassandraDatacenter{ ObjectMeta: metav1.ObjectMeta{ Name: "exampleDC", }, - Spec: CassandraDatacenterSpec{ + Spec: api.CassandraDatacenterSpec{ ServerType: serverType, }, } diff --git a/apis/cassandra/v1beta1/webhook_suite_test.go b/internal/webhooks/cassandra/v1beta1/webhook_suite_test.go similarity index 61% rename from apis/cassandra/v1beta1/webhook_suite_test.go rename to internal/webhooks/cassandra/v1beta1/webhook_suite_test.go index 5689f9ea..59991254 100644 --- a/apis/cassandra/v1beta1/webhook_suite_test.go +++ b/internal/webhooks/cassandra/v1beta1/webhook_suite_test.go @@ -1,5 +1,5 @@ /* -Copyright 2021. +Copyright 2024 Just Me. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,33 +22,43 @@ import ( "fmt" "net" "path/filepath" + "runtime" "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - //+kubebuilder:scaffold:imports - "k8s.io/apimachinery/pkg/runtime" + admissionv1 "k8s.io/api/admission/v1" + + api "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" + + // +kubebuilder:scaffold:imports + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - webhookserver "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -var k8sClient client.Client -var testEnv *envtest.Environment -var ctx context.Context -var cancel context.CancelFunc +var ( + cancel context.CancelFunc + cfg *rest.Config + ctx context.Context + k8sClient client.Client + testEnv *envtest.Environment +) func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) + RunSpecs(t, "Webhook Suite") } @@ -61,53 +71,65 @@ var _ = BeforeSuite(func() { testEnv = &envtest.Environment{ CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, ErrorIfCRDPathMissing: false, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "..", "bin", "k8s", + fmt.Sprintf("1.31.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + WebhookInstallOptions: envtest.WebhookInstallOptions{ Paths: []string{filepath.Join("..", "..", "..", "config", "webhook")}, }, } - cfg, err := testEnv.Start() + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) - scheme := runtime.NewScheme() - err = AddToScheme(scheme) + scheme := apimachineryruntime.NewScheme() + err = api.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - //+kubebuilder:scaffold:scheme + // +kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) - // start webhook server using Manager + // start webhook server using Manager. webhookInstallOptions := &testEnv.WebhookInstallOptions mgr, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme, - WebhookServer: webhookserver.NewServer(webhookserver.Options{ - Port: testEnv.WebhookInstallOptions.LocalServingPort, - Host: testEnv.WebhookInstallOptions.LocalServingHost, - CertDir: testEnv.WebhookInstallOptions.LocalServingCertDir, - TLSOpts: []func(*tls.Config){func(config *tls.Config) {}}, + WebhookServer: webhook.NewServer(webhook.Options{ + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, }), LeaderElection: false, Metrics: metricsserver.Options{BindAddress: "0"}, }) Expect(err).NotTo(HaveOccurred()) - err = (&CassandraDatacenter{}).SetupWebhookWithManager(mgr) + err = SetupCassandraDatacenterWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) - //+kubebuilder:scaffold:webhook + // +kubebuilder:scaffold:webhook go func() { + defer GinkgoRecover() err = mgr.Start(ctx) - if err != nil { - Expect(err).NotTo(HaveOccurred()) - } + Expect(err).NotTo(HaveOccurred()) }() - // wait for the webhook server to get ready + // wait for the webhook server to get ready. dialer := &net.Dialer{Timeout: time.Second} addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) Eventually(func() error { @@ -115,15 +137,14 @@ var _ = BeforeSuite(func() { if err != nil { return err } - conn.Close() - return nil - }).Should(Succeed()) + return conn.Close() + }).Should(Succeed()) }) var _ = AfterSuite(func() { - cancel() By("tearing down the test environment") + cancel() err := testEnv.Stop() Expect(err).NotTo(HaveOccurred()) }) diff --git a/pkg/reconciliation/handler.go b/pkg/reconciliation/handler.go index 772c78dd..113b2648 100644 --- a/pkg/reconciliation/handler.go +++ b/pkg/reconciliation/handler.go @@ -14,6 +14,7 @@ import ( api "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" "github.com/k8ssandra/cass-operator/internal/result" + apiwebhook "github.com/k8ssandra/cass-operator/internal/webhooks/cassandra/v1beta1" "github.com/k8ssandra/cass-operator/pkg/httphelper" ) @@ -103,10 +104,10 @@ func (rc *ReconciliationContext) IsValid(dc *api.CassandraDatacenter) error { errs = append(errs, rc.validateCassandraUserSecrets()...) // Validate FQL config - errs = append(errs, api.ValidateFQLConfig(*dc)) + errs = append(errs, apiwebhook.ValidateFQLConfig(dc)) // Validate Service labels and annotations - errs = append(errs, api.ValidateServiceLabelsAndAnnotations(*dc)) + errs = append(errs, apiwebhook.ValidateServiceLabelsAndAnnotations(dc)) // Validate Management API config errs = append(errs, httphelper.ValidateManagementApiConfig(dc, rc.Client, rc.Ctx)...)