Repository: k8ssandra/k8ssandra-operator (go)

Issue:
K8SSAND-1203 ⁃ Expose AllowMultipleWorkersPerNode from CassandraDatacenter spec
<!--

Feel free to ask questions on Discord at https://discord.com/invite/y4s64xC9 or in the forum at https://forum.k8ssandra.io/. 
-->

**What is missing?**
The `AllowMultipleWorkersPerNode` allows multiple C* pods to be co-located on the same k8s worker node.

**Why do we need it?**
There are times during dev/testing when it is convenient to be able to co-locate C* pods particularly if there are a limited number of worker nodes. Some folks may use this for production deployments as well.

**Environment**

* K8ssandra Operator version:

    `Insert image tag or Git SHA here`
    <!-- Try kubectl describe deployment k8ssandra-operator -->
    <!-- Note: please provide operator version and not a helm chart version -->**Anything else we need to know?**:



┆Issue is synchronized with this [Jira Task](https://k8ssandra.atlassian.net/browse/K8SSAND-1203) by [Unito](https://www.unito.io)
┆friendlyId: K8SSAND-1203
┆priority: Medium


Pull request description:
Expose AllowMultipleNodesPerWorker [K8SSAND-1203]
**What this PR does**:

Expose AllowMultipleNodesPerWorker property from the CassandraDatacenter CRD.

**Which issue(s) this PR fixes**:
Fixes #342

**Checklist**
- [ ] Changes manually tested
- [ ] Automated Tests added/updated
- [ ] Documentation added/updated
- [ ] CHANGELOG.md updated (not required for documentation PRs)
- [ ] CLA Signed:  [DataStax CLA](https://cla.datastax.com/)


Patch:
diff --git a/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go b/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go
index 25dd94d1..d3ae4728 100644
--- a/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go
+++ b/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go
@@ -238,6 +238,11 @@ type CassandraClusterTemplate struct {
 	// api heap.
 	// +optional
 	MgmtAPIHeap *resource.Quantity `json:"mgmtAPIHeap,omitempty"`
+
+	// SoftPodAntiAffinity sets whether multiple Cassandra instances can be scheduled on the same node.
+	// This should normally be false to ensure cluster resilience but may be set true for test/dev scenarios to minimise
+	// the number of nodes required.
+	SoftPodAntiAffinity *bool `json:"softPodAntiAffinity,omitempty"`
 }
 
 // +kubebuilder:pruning:PreserveUnknownFields
@@ -307,6 +312,11 @@ type CassandraDatacenterTemplate struct {
 	// a user-provided monitoring solution (at present, only support for Prometheus is available).
 	// +optional
 	CassandraTelemetry *telemetryapi.TelemetrySpec `json:"cassandraTelemetry,omitempty"`
+
+	// SoftPodAntiAffinity sets whether multiple Cassandra instances can be scheduled on the same node.
+	// This should normally be false to ensure cluster resilience but may be set true for test/dev scenarios to minimise
+	// the number of nodes required.
+	SoftPodAntiAffinity *bool `json:"softPodAntiAffinity,omitempty"`
 }
 
 type EmbeddedObjectMeta struct {
diff --git a/pkg/cassandra/datacenter.go b/pkg/cassandra/datacenter.go
index 51f9b5a3..faf06286 100644
--- a/pkg/cassandra/datacenter.go
+++ b/pkg/cassandra/datacenter.go
@@ -2,6 +2,7 @@ package cassandra
 
 import (
 	"fmt"
+
 	cassdcapi "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1"
 	"github.com/k8ssandra/cass-operator/pkg/reconciliation"
 	api "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1"
@@ -103,6 +104,7 @@ type DatacenterConfig struct {
 	Users                 []cassdcapi.CassandraUser
 	PodTemplateSpec       *corev1.PodTemplateSpec
 	MgmtAPIHeap           *resource.Quantity
+	SoftPodAntiAffinity   *bool
 }
 
 const (
@@ -163,6 +165,10 @@ func NewDatacenter(klusterKey types.NamespacedName, template *DatacenterConfig)
 		setMgmtAPIHeap(dc, template.MgmtAPIHeap)
 	}
 
+	if template.SoftPodAntiAffinity != nil {
+		dc.Spec.AllowMultipleNodesPerWorker = *template.SoftPodAntiAffinity
+	}
+
 	return dc, nil
 }
 
@@ -304,6 +310,12 @@ func Coalesce(clusterName string, clusterTemplate *api.CassandraClusterTemplate,
 		dcConfig.MgmtAPIHeap = dcTemplate.MgmtAPIHeap
 	}
 
+	if dcTemplate.SoftPodAntiAffinity == nil {
+		dcConfig.SoftPodAntiAffinity = clusterTemplate.SoftPodAntiAffinity
+	} else {
+		dcConfig.SoftPodAntiAffinity = dcTemplate.SoftPodAntiAffinity
+	}
+
 	return dcConfig
 }
 


Test patch:
diff --git a/pkg/cassandra/datacenter_test.go b/pkg/cassandra/datacenter_test.go
index b57ebf07..8893b552 100644
--- a/pkg/cassandra/datacenter_test.go
+++ b/pkg/cassandra/datacenter_test.go
@@ -344,6 +344,17 @@ func TestNewDatacenter_MgmtAPIHeapSize_Unset(t *testing.T) {
 	assert.Equal(t, (*corev1.PodTemplateSpec)(nil), dc.Spec.PodTemplateSpec)
 }
 
+func TestNewDatacenter_AllowMultipleCassPerNodeSet(t *testing.T) {
+	template := GetDatacenterConfig()
+	template.SoftPodAntiAffinity = pointer.Bool(true)
+	dc, err := NewDatacenter(
+		types.NamespacedName{Name: "testdc", Namespace: "test-namespace"},
+		&template,
+	)
+	assert.NoError(t, err)
+	assert.Equal(t, true, dc.Spec.AllowMultipleNodesPerWorker)
+}
+
 // TestNewDatacenter_Fail_NoStorageConfig tests that NewDatacenter fails when no storage config is provided.
 func TestNewDatacenter_Fail_NoStorageConfig(t *testing.T) {
 	template := GetDatacenterConfig()