diff --git a/charts/cass-operator-chart/crds/customresourcedefinition.yaml b/charts/cass-operator-chart/crds/customresourcedefinition.yaml index ce0381277..df34e7ac9 100644 --- a/charts/cass-operator-chart/crds/customresourcedefinition.yaml +++ b/charts/cass-operator-chart/crds/customresourcedefinition.yaml @@ -5938,16 +5938,27 @@ spec: reaper: properties: enabled: + description: When set to true, cass-operator configures and enables + remote JMX. Changing this after the cluster has been deployed + will result in a rolling restart of the cluster. type: boolean image: + description: Deprecated - this property only applied to Reaper sidecar + which is no longer used. type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image + description: Deprecated - this property only applied to Reaper sidecar + which is no longer used. + type: string + jmxSecretName: + description: The name of the secret that stores the credentials + used for JMX authentication. The secret should be created in the + same namespace as the CassandraDatacenter. If this property is + not set, the operator will generate a default secret. type: string resources: - description: Kubernetes resource requests and limits per reaper - container. + description: Deprecated - this property only applied to Reaper sidecar + which is no longer used. properties: limits: additionalProperties: diff --git a/go.mod b/go.mod index 6e0a01506..8b037255b 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/pavel-v-chernykh/keystore-go v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.5.1 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 gopkg.in/yaml.v2 v2.2.8 k8s.io/api v0.17.4 diff --git a/go.sum b/go.sum index 0ac2e7876..80ff6bd28 100644 --- a/go.sum +++ b/go.sum @@ -909,6 +909,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= diff --git a/mage/ginkgo/lib.go b/mage/ginkgo/lib.go index e3d94b176..5ab67635b 100644 --- a/mage/ginkgo/lib.go +++ b/mage/ginkgo/lib.go @@ -298,6 +298,13 @@ func (ns *NsWrapper) WaitForDatacenterReadyWithTimeouts(dcName string, podCountT ns.WaitForDatacenterOperatorProgress(dcName, "Ready", dcReadyTimeout) } +func (ns *NsWrapper) WaitForReaperReady(reaperName string, timeout int) { + step := "waiting for reaper to become ready" + json := "jsonpath={.status.ready}" + k := kubectl.Get("reaper", reaperName).FormatOutput(json) + ns.WaitForOutputAndLog(step, k, "true", timeout) +} + func (ns *NsWrapper) WaitForPodNotStarted(podName string) { step := "verify that the pod is no longer marked as started" k := kubectl.Get("pod"). @@ -403,6 +410,16 @@ func (ns *NsWrapper) WaitForOperatorReady() { ns.WaitForOutputAndLog(step, k, "true", 240) } +func (ns *NsWrapper) WaitForReaperOperatorReady() { + step := "waiting for reaper-operator to become ready" + json := "jsonpath={.items[0].status.containerStatuses[0].ready}" + k := kubectl.Get("pods"). + WithLabel("control-plane=reaper-operator"). + WithFlag("field-selector", "status.phase=Running"). + FormatOutput(json) + ns.WaitForOutputAndLog(step, k, "true", 240) +} + // kubectl create secret docker-registry github-docker-registry --docker-username=USER --docker-password=PASS --docker-server docker.pkg.github.com func CreateDockerRegistrySecret(name string, namespace string) { args := []string{"secret", "docker-registry", name} diff --git a/operator/deploy/crds/cassandra.datastax.com_cassandradatacenters_crd.yaml b/operator/deploy/crds/cassandra.datastax.com_cassandradatacenters_crd.yaml index 06ac75673..c41931b54 100644 --- a/operator/deploy/crds/cassandra.datastax.com_cassandradatacenters_crd.yaml +++ b/operator/deploy/crds/cassandra.datastax.com_cassandradatacenters_crd.yaml @@ -5950,16 +5950,27 @@ spec: reaper: properties: enabled: + description: When set to true, cass-operator configures and enables + remote JMX. Changing this after the cluster has been deployed + will result in a rolling restart of the cluster. type: boolean image: + description: Deprecated - this property only applied to Reaper sidecar + which is no longer used. type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image + description: Deprecated - this property only applied to Reaper sidecar + which is no longer used. + type: string + jmxSecretName: + description: The name of the secret that stores the credentials + used for JMX authentication. The secret should be created in the + same namespace as the CassandraDatacenter. If this property is + not set, the operator will generate a default secret. type: string resources: - description: Kubernetes resource requests and limits per reaper - container. + description: Deprecated - this property only applied to Reaper sidecar + which is no longer used. properties: limits: additionalProperties: diff --git a/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types.go b/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types.go index 9c7abcb56..b8dbd777f 100644 --- a/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types.go +++ b/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types.go @@ -347,13 +347,22 @@ type ManagementApiAuthConfig struct { } type ReaperConfig struct { + // When set to true, cass-operator configures and enables remote JMX. Changing this + // after the cluster has been deployed will result in a rolling restart of the cluster. Enabled bool `json:"enabled,omitempty"` + // The name of the secret that stores the credentials used for JMX authentication. The + // secret should be created in the same namespace as the CassandraDatacenter. If this + // property is not set, the operator will generate a default secret. + JmxSecretName string `json:"jmxSecretName,omitempty"` + + // Deprecated - this property only applied to Reaper sidecar which is no longer used. Image string `json:"image,omitempty"` + // Deprecated - this property only applied to Reaper sidecar which is no longer used. ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` - // Kubernetes resource requests and limits per reaper container. + // Deprecated - this property only applied to Reaper sidecar which is no longer used. Resources corev1.ResourceRequirements `json:"resources,omitempty"` } @@ -418,6 +427,16 @@ func (dc *CassandraDatacenter) IsReaperEnabled() bool { return false } +func (dc *CassandraDatacenter) GetReaperJmxSecretName() string { + if !dc.IsReaperEnabled() { + return "" + } + if len(dc.Spec.Reaper.JmxSecretName) > 0 { + return dc.Spec.Reaper.JmxSecretName + } + return dc.Spec.ClusterName + "-reaper-jmx" +} + func (status *CassandraDatacenterStatus) GetConditionStatus(conditionType DatacenterConditionType) corev1.ConditionStatus { for _, condition := range status.Conditions { if condition.Type == conditionType { diff --git a/operator/pkg/images/images.go b/operator/pkg/images/images.go index b31d8f652..0e37cf2a4 100644 --- a/operator/pkg/images/images.go +++ b/operator/pkg/images/images.go @@ -275,6 +275,10 @@ func GetSystemLoggerImage() string { } } +func GetJmxCredentialsImage() string { + return GetImage(BusyBox) +} + func AddDefaultRegistryImagePullSecrets(podSpec *corev1.PodSpec) bool { secretName := os.Getenv(envDefaultRegistryOverridePullSecrets) if secretName != "" { diff --git a/operator/pkg/reconciliation/construct_podtemplatespec.go b/operator/pkg/reconciliation/construct_podtemplatespec.go index f6357316a..6e7ee60c4 100644 --- a/operator/pkg/reconciliation/construct_podtemplatespec.go +++ b/operator/pkg/reconciliation/construct_podtemplatespec.go @@ -26,6 +26,7 @@ const ( CassandraContainerName = "cassandra" PvcName = "server-data" SystemLoggerContainerName = "server-system-logger" + JmxCredentialsContainerName = "jmx-credentials" ) // calculateNodeAffinity provides a way to pin all pods within a statefulset to the same zone @@ -262,7 +263,46 @@ func buildInitContainers(dc *api.CassandraDatacenter, rackName string, baseTempl if !foundOverrides { // Note that append makes a copy, so we must do this after // serverCfg has been properly set up. - baseTemplate.Spec.InitContainers = append(baseTemplate.Spec.InitContainers, *serverCfg) + //baseTemplate.Spec.InitContainers = append(baseTemplate.Spec.InitContainers, *serverCfg) + baseTemplate.Spec.InitContainers = append([]corev1.Container{*serverCfg}, baseTemplate.Spec.InitContainers...) + } + + if dc.IsReaperEnabled() { + initJmxCredentials := &corev1.Container{ + Name: JmxCredentialsContainerName, + Image: images.GetJmxCredentialsImage(), + Args: []string{ + "/bin/sh", + "-c", + "echo -n \"$JMX_USERNAME $JMX_PASSWORD\" > /config/jmxremote.password", + }, + Env: []corev1.EnvVar{ + { + Name: "JMX_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: dc.GetReaperJmxSecretName(), + }, + Key: "username", + }, + }, + }, + { + Name: "JMX_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: dc.GetReaperJmxSecretName(), + }, + Key: "password", + }, + }, + }, + }, + VolumeMounts: []corev1.VolumeMount{serverCfgMount}, + } + baseTemplate.Spec.InitContainers = append(baseTemplate.Spec.InitContainers, *initJmxCredentials) } return nil @@ -276,11 +316,9 @@ func buildContainers(dc *api.CassandraDatacenter, baseTemplate *corev1.PodTempla cassContainer := &corev1.Container{} loggerContainer := &corev1.Container{} - reaperContainer := &corev1.Container{} foundCass := false foundLogger := false - foundReaper := false for i, c := range baseTemplate.Spec.Containers { if c.Name == CassandraContainerName { foundCass = true @@ -288,9 +326,6 @@ func buildContainers(dc *api.CassandraDatacenter, baseTemplate *corev1.PodTempla } else if c.Name == SystemLoggerContainerName { foundLogger = true loggerContainer = &baseTemplate.Spec.Containers[i] - } else if c.Name == ReaperContainerName { - foundReaper = true - reaperContainer = &baseTemplate.Spec.Containers[i] } } @@ -349,6 +384,10 @@ func buildContainers(dc *api.CassandraDatacenter, baseTemplate *corev1.PodTempla corev1.EnvVar{Name: "JVM_EXTRA_OPTS", Value: getJvmExtraOpts(dc)}) } + if dc.IsReaperEnabled() { + envDefaults = append(envDefaults, corev1.EnvVar{Name: "LOCAL_JMX", Value: "no"}) + } + cassContainer.Env = combineEnvSlices(envDefaults, cassContainer.Env) // Combine ports @@ -404,12 +443,6 @@ func buildContainers(dc *api.CassandraDatacenter, baseTemplate *corev1.PodTempla loggerContainer.Resources = *getResourcesOrDefault(&dc.Spec.SystemLoggerResources, &DefaultsLoggerContainer) - // Reaper Container - - if dc.IsReaperEnabled() { - buildReaperContainer(dc, reaperContainer) - } - // Note that append() can make copies of each element, // so we call it after modifying any existing elements. @@ -421,12 +454,6 @@ func buildContainers(dc *api.CassandraDatacenter, baseTemplate *corev1.PodTempla baseTemplate.Spec.Containers = append(baseTemplate.Spec.Containers, *loggerContainer) } - if dc.IsReaperEnabled() { - if !foundReaper { - baseTemplate.Spec.Containers = append(baseTemplate.Spec.Containers, *reaperContainer) - } - } - return nil } diff --git a/operator/pkg/reconciliation/construct_podtemplatespec_test.go b/operator/pkg/reconciliation/construct_podtemplatespec_test.go index fc47b7028..ea01a8e03 100644 --- a/operator/pkg/reconciliation/construct_podtemplatespec_test.go +++ b/operator/pkg/reconciliation/construct_podtemplatespec_test.go @@ -7,6 +7,7 @@ import ( "reflect" "testing" + "github.com/datastax/cass-operator/operator/pkg/images" "k8s.io/apimachinery/pkg/api/resource" api "github.com/datastax/cass-operator/operator/pkg/apis/cassandra/v1beta1" @@ -49,6 +50,63 @@ func Test_calculateNodeAffinity(t *testing.T) { }) } +func TestCassandraDatacenter_buildInitContainers_reaperEnabled(t *testing.T) { + dc := &api.CassandraDatacenter{ + Spec: api.CassandraDatacenterSpec{ + ClusterName: "reaper-test", + ServerType: "cassandra", + ServerVersion: "3.11.7", + Reaper: &api.ReaperConfig{ + Enabled: true, + JmxSecretName: "reaper-jmx", + }, + }, + } + podTemplateSpec := corev1.PodTemplateSpec{} + err := buildInitContainers(dc, "reaper-test", &podTemplateSpec) + + assert.NoError(t, err) + + initContainers := podTemplateSpec.Spec.InitContainers + + assert.Len(t, initContainers, 2) + assert.Equal(t, initContainers[1].Name, JmxCredentialsContainerName) + assert.Equal(t, initContainers[1].Image, images.GetJmxCredentialsImage()) + assert.Equal(t, initContainers[1].Args, []string{ + "/bin/sh", + "-c", + "echo -n \"$JMX_USERNAME $JMX_PASSWORD\" > /config/jmxremote.password", + }) + assert.Len(t, initContainers[1].VolumeMounts, 1) + assert.Equal(t, initContainers[1].VolumeMounts[0], corev1.VolumeMount{ + Name: "server-config", + MountPath: "/config", + }) + assert.Len(t, initContainers[1].Env, 2) + assert.Contains(t, initContainers[1].Env, corev1.EnvVar{ + Name: "JMX_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: dc.Spec.Reaper.JmxSecretName, + }, + Key: "username", + }, + }, + }) + assert.Contains(t, initContainers[1].Env, corev1.EnvVar{ + Name: "JMX_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: dc.Spec.Reaper.JmxSecretName, + }, + Key: "password", + }, + }, + }) +} + func TestCassandraDatacenter_buildInitContainer_resources_set(t *testing.T) { dc := &api.CassandraDatacenter{ Spec: api.CassandraDatacenterSpec{ @@ -203,63 +261,6 @@ func TestCassandraDatacenter_buildContainers_systemlogger_resources_set_when_not } } -func TestCassandraDatacenter_buildContainers_reaper_resources(t *testing.T) { - dc := &api.CassandraDatacenter{ - Spec: api.CassandraDatacenterSpec{ - ClusterName: "bob", - ServerType: "cassandra", - ServerVersion: "3.11.7", - Reaper: &api.ReaperConfig{ - Enabled: true, - Resources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - "cpu": *resource.NewMilliQuantity(1, resource.DecimalSI), - "memory": *resource.NewScaledQuantity(1, resource.Giga), - }, - Requests: corev1.ResourceList{ - "cpu": *resource.NewMilliQuantity(1, resource.DecimalSI), - "memory": *resource.NewScaledQuantity(1, resource.Giga), - }, - }, - }, - }, - } - - podTemplateSpec := &corev1.PodTemplateSpec{} - err := buildContainers(dc, podTemplateSpec) - containers := podTemplateSpec.Spec.Containers - assert.NotNil(t, containers, "Unexpected containers containers received") - assert.Nil(t, err, "Unexpected error encountered") - - assert.Len(t, containers, 3, "Unexpected number of containers containers returned") - assert.Equal(t, containers[2].Resources, dc.Spec.Reaper.Resources, - "reaper container resources have unexpected values.") -} - -func TestCassandraDatacenter_buildContainers_reaper_resources_set_when_not_specified(t *testing.T) { - dc := &api.CassandraDatacenter{ - Spec: api.CassandraDatacenterSpec{ - ClusterName: "bob", - ServerType: "cassandra", - ServerVersion: "3.11.7", - Reaper: &api.ReaperConfig{ - Enabled: true, - }, - }, - } - - podTemplateSpec := &corev1.PodTemplateSpec{} - err := buildContainers(dc, podTemplateSpec) - containers := podTemplateSpec.Spec.Containers - assert.NotNil(t, containers, "Unexpected containers containers received") - assert.Nil(t, err, "Unexpected error encountered") - - assert.Len(t, containers, 3, "Unexpected number of containers containers returned") - if !reflect.DeepEqual(containers[2].Resources, DefaultsReaperContainer) { - t.Error("reaper container resources are not set to the default values.") - } -} - func TestCassandraDatacenter_buildContainers_use_cassandra_settings(t *testing.T) { dc := &api.CassandraDatacenter{ Spec: api.CassandraDatacenterSpec{ @@ -290,14 +291,13 @@ func TestCassandraDatacenter_buildContainers_use_cassandra_settings(t *testing.T assert.NotNil(t, containers, "Unexpected containers containers received") assert.Nil(t, err, "Unexpected error encountered") - assert.Len(t, containers, 3, "Unexpected number of containers containers returned") - if !reflect.DeepEqual(containers[2].Resources, DefaultsReaperContainer) { - t.Error("reaper container resources are not set to the default values.") - } + assert.Len(t, containers, 2, "Unexpected number of containers containers returned") if !reflect.DeepEqual(containers[0].Env[0].Name, "k1") { t.Errorf("Unexpected env vars allocated for the cassandra container: %v", containers[0].Env) } + + assert.Contains(t, containers[0].Env, corev1.EnvVar{Name: "LOCAL_JMX", Value: "no"}) } func TestCassandraDatacenter_buildContainers_override_other_containers(t *testing.T) { @@ -333,10 +333,7 @@ func TestCassandraDatacenter_buildContainers_override_other_containers(t *testin assert.NotNil(t, containers, "Unexpected containers containers received") assert.Nil(t, err, "Unexpected error encountered") - assert.Len(t, containers, 3, "Unexpected number of containers containers returned") - if !reflect.DeepEqual(containers[2].Resources, DefaultsReaperContainer) { - t.Error("reaper container resources are not set to the default values.") - } + assert.Len(t, containers, 2, "Unexpected number of containers containers returned") if !reflect.DeepEqual(containers[0].VolumeMounts, []corev1.VolumeMount{ diff --git a/operator/pkg/reconciliation/constructor.go b/operator/pkg/reconciliation/constructor.go index e9418dd91..846e9fb96 100644 --- a/operator/pkg/reconciliation/constructor.go +++ b/operator/pkg/reconciliation/constructor.go @@ -10,8 +10,6 @@ import ( "github.com/datastax/cass-operator/operator/pkg/oplabels" "github.com/datastax/cass-operator/operator/pkg/utils" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -65,45 +63,3 @@ func setOperatorProgressStatus(rc *ReconciliationContext, newState api.ProgressS return nil } - -func buildInitReaperSchemaJob(dc *api.CassandraDatacenter) *batchv1.Job { - return &batchv1.Job{ - TypeMeta: metav1.TypeMeta{ - Kind: "Job", - APIVersion: "batch/v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: dc.Namespace, - Name: getReaperSchemaInitJobName(dc), - Labels: dc.GetDatacenterLabels(), - }, - Spec: batchv1.JobSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - RestartPolicy: corev1.RestartPolicyOnFailure, - Containers: []corev1.Container{ - { - Name: getReaperSchemaInitJobName(dc), - Image: ReaperSchemaInitJobImage, - ImagePullPolicy: corev1.PullIfNotPresent, - Env: []corev1.EnvVar{ - { - Name: "KEYSPACE", - Value: ReaperKeyspace, - }, - { - Name: "CONTACT_POINTS", - Value: dc.GetSeedServiceName(), - }, - { - Name: "REPLICATION", - Value: getReaperReplication(dc), - }, - }, - }, - }, - }, - }, - }, - } -} diff --git a/operator/pkg/reconciliation/reconcile_racks.go b/operator/pkg/reconciliation/reconcile_racks.go index a5b2c5324..1716e2b96 100644 --- a/operator/pkg/reconciliation/reconcile_racks.go +++ b/operator/pkg/reconciliation/reconcile_racks.go @@ -112,6 +112,19 @@ func (rc *ReconciliationContext) CheckSuperuserSecretCreation() result.Reconcile return result.Continue() } +func (rc *ReconciliationContext) CheckReaperJmxSecretCreation() result.ReconcileResult { + rc.ReqLogger.Info("reconcile_racks::CheckReaperJmxSecretCreation") + + if rc.Datacenter.IsReaperEnabled() && len(rc.Datacenter.Spec.Reaper.JmxSecretName) == 0 { + if err := rc.createReaperJmxSecretIfNotExists(); err != nil { + rc.ReqLogger.Error(err, "failed to create reaper jmx secret") + return result.Error(err) + } + } + + return result.Continue() +} + func (rc *ReconciliationContext) CheckInternodeCredentialCreation() result.ReconcileResult { rc.ReqLogger.Info("reconcile_racks::CheckInternodeCredentialCreation") @@ -2111,6 +2124,10 @@ func (rc *ReconciliationContext) ReconcileAllRacks() (reconcile.Result, error) { return recResult.Output() } + if recResult := rc.CheckReaperJmxSecretCreation(); recResult.Completed() { + return recResult.Output() + } + if recResult := rc.CheckInternodeCredentialCreation(); recResult.Completed() { return recResult.Output() } @@ -2147,13 +2164,13 @@ func (rc *ReconciliationContext) ReconcileAllRacks() (reconcile.Result, error) { return recResult.Output() } - if recResult := rc.CheckReaperService(); recResult.Completed() { - return recResult.Output() - } + //if recResult := rc.CheckReaperService(); recResult.Completed() { + // return recResult.Output() + //} - if recResult := rc.CheckReaperSchemaInitialized(); recResult.Completed() { - return recResult.Output() - } + //if recResult := rc.CheckReaperSchemaInitialized(); recResult.Completed() { + // return recResult.Output() + //} if recResult := rc.CheckRollingRestart(); recResult.Completed() { return recResult.Output() diff --git a/operator/pkg/reconciliation/reconcile_reaper.go b/operator/pkg/reconciliation/reconcile_reaper.go deleted file mode 100644 index 2a0a1e844..000000000 --- a/operator/pkg/reconciliation/reconcile_reaper.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright DataStax, Inc. -// Please see the included license file for details. - -package reconciliation - -import ( - "fmt" - "math" - "strconv" - - v1batch "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - - "github.com/datastax/cass-operator/operator/internal/result" - api "github.com/datastax/cass-operator/operator/pkg/apis/cassandra/v1beta1" - "github.com/datastax/cass-operator/operator/pkg/images" -) - -const ( - ReaperUIPort = 7080 - ReaperAdminPort = 7081 - ReaperDefaultPullPolicy = corev1.PullIfNotPresent - ReaperContainerName = "reaper" - ReaperHealthCheckPath = "/healthcheck" - ReaperKeyspace = "reaper_db" - ReaperSchemaInitJob = "ReaperSchemaInitJob" - // This code currently lives at https://github.com/jsanda/create_keyspace. - ReaperSchemaInitJobImage = "jsanda/reaper-init-keyspace:latest" -) - -// We are passing in container by reference because the user may have already provided a -// reaper container with desired settings in CassandraDatacenter.Spec.PodTemplateSpec. -// Therefore we will simply fill-in any missing defaults inside of buildReaperContainer. -// TODO: provide more unit testing for building reaper containers. -func buildReaperContainer(dc *api.CassandraDatacenter, container *corev1.Container) { - - ports := []corev1.ContainerPort{ - {Name: "ui", ContainerPort: ReaperUIPort, Protocol: "TCP"}, - {Name: "admin", ContainerPort: ReaperAdminPort, Protocol: "TCP"}, - } - - container.Name = ReaperContainerName - container.Image = getReaperImage(dc) - container.ImagePullPolicy = getReaperPullPolicy(dc) - - container.Ports = combinePortSlices(ports, container.Ports) - - if container.LivenessProbe == nil { - container.LivenessProbe = probe(ReaperAdminPort, ReaperHealthCheckPath, int(60*dc.Spec.Size), 10) - } - - if container.ReadinessProbe == nil { - container.ReadinessProbe = probe(ReaperAdminPort, ReaperHealthCheckPath, 30, 15) - } - - envDefaults := []corev1.EnvVar{ - {Name: "REAPER_STORAGE_TYPE", Value: "cassandra"}, - {Name: "REAPER_ENABLE_DYNAMIC_SEED_LIST", Value: "false"}, - {Name: "REAPER_DATACENTER_AVAILABILITY", Value: "SIDECAR"}, - {Name: "REAPER_SERVER_APP_PORT", Value: strconv.Itoa(ReaperUIPort)}, - {Name: "REAPER_SERVER_ADMIN_PORT", Value: strconv.Itoa(ReaperAdminPort)}, - {Name: "REAPER_CASS_CLUSTER_NAME", Value: dc.ClusterName}, - {Name: "REAPER_CASS_CONTACT_POINTS", Value: fmt.Sprintf("[%s]", dc.GetSeedServiceName())}, - {Name: "REAPER_AUTH_ENABLED", Value: "false"}, - {Name: "REAPER_JMX_AUTH_USERNAME", Value: ""}, - {Name: "REAPER_JMX_AUTH_PASSWORD", Value: ""}, - } - - container.Env = combineEnvSlices(envDefaults, container.Env) - - container.Resources = *getResourcesOrDefault(&dc.Spec.Reaper.Resources, &DefaultsReaperContainer) -} - -func getReaperImage(dc *api.CassandraDatacenter) string { - if len(dc.Spec.Reaper.Image) == 0 { - return images.GetReaperImage() - } - return dc.Spec.Reaper.Image -} - -func getReaperPullPolicy(dc *api.CassandraDatacenter) corev1.PullPolicy { - if len(dc.Spec.Reaper.ImagePullPolicy) == 0 { - return ReaperDefaultPullPolicy - } - return dc.Spec.Reaper.ImagePullPolicy -} - -func (rc *ReconciliationContext) CheckReaperSchemaInitialized() result.ReconcileResult { - // Using a job eventually get replaced with calls to the mgmt api once it has support for - // creating keyspaces and tables. - - rc.ReqLogger.Info("reconcile_reaper::CheckReaperSchemaInitialized") - - if rc.Datacenter.Spec.Reaper == nil || !rc.Datacenter.Spec.Reaper.Enabled { - return result.Continue() - } - - jobName := getReaperSchemaInitJobName(rc.Datacenter) - schemaJob := &v1batch.Job{} - - err := rc.Client.Get(rc.Ctx, types.NamespacedName{Namespace: rc.Datacenter.Namespace, Name: jobName}, schemaJob) - if err != nil && errors.IsNotFound(err) { - // Create the job - schemaJob := buildInitReaperSchemaJob(rc.Datacenter) - rc.ReqLogger.Info("creating Reaper schema init job", ReaperSchemaInitJob, schemaJob.Name) - if err := setControllerReference(rc.Datacenter, schemaJob, rc.Scheme); err != nil { - rc.ReqLogger.Error(err, "failed to set owner reference", ReaperSchemaInitJob, schemaJob.Name) - return result.Error(err) - } - if err := rc.Client.Create(rc.Ctx, schemaJob); err != nil { - rc.ReqLogger.Error(err, "failed to create job", ReaperSchemaInitJob, schemaJob.Name) - return result.Error(err) - } else { - return result.RequeueSoon(2) - } - } else if err != nil { - return result.Error(err) - } else if jobFinished(schemaJob) { - return result.Continue() - } else { - return result.RequeueSoon(2) - } -} - -func getReaperSchemaInitJobName(dc *api.CassandraDatacenter) string { - return fmt.Sprintf("%s-reaper-init-schema", dc.Spec.ClusterName) -} - -func getReaperReplication(dc *api.CassandraDatacenter) string { - replicationFactor := int(math.Min(float64(dc.Spec.Size), 3)) - return fmt.Sprintf("{'class': 'NetworkTopologyStrategy', '%s': %d}", dc.Name, replicationFactor) -} - -func jobFinished(job *v1batch.Job) bool { - for _, c := range job.Status.Conditions { - if (c.Type == v1batch.JobComplete || c.Type == v1batch.JobFailed) && c.Status == corev1.ConditionTrue { - return true - } - } - return false -} - -func (rc *ReconciliationContext) CheckReaperService() result.ReconcileResult { - rc.ReqLogger.Info("reconcile_reaper::CheckReaperService") - - serviceName := getReaperServiceName(rc.Datacenter) - service := &corev1.Service{} - - err := rc.Client.Get(rc.Ctx, types.NamespacedName{Namespace: rc.Datacenter.Namespace, Name: serviceName}, service) - if err != nil && errors.IsNotFound(err) { - if rc.Datacenter.Spec.Reaper != nil && rc.Datacenter.Spec.Reaper.Enabled { - // Create the service - service = newReaperService(rc.Datacenter) - rc.ReqLogger.Info("creating Reaper service") - if err := setControllerReference(rc.Datacenter, service, rc.Scheme); err != nil { - rc.ReqLogger.Error(err, "failed to set owner reference", "ReaperService", serviceName) - return result.Error(err) - } - if err := rc.Client.Create(rc.Ctx, service); err != nil { - rc.ReqLogger.Error(err, "failed to create Reaper service") - return result.Error(err) - } - return result.Continue() - } - } else if err != nil { - return result.Error(err) - } else if rc.Datacenter.Spec.Reaper == nil || !rc.Datacenter.Spec.Reaper.Enabled { - if err := rc.Client.Delete(rc.Ctx, service); err != nil { - rc.ReqLogger.Error(err, "failed to delete Reaper service", "ReaperService", serviceName) - } - } - return result.Continue() -} - -func getReaperServiceName(dc *api.CassandraDatacenter) string { - return fmt.Sprintf("%s-reaper-service", dc.Spec.ClusterName) -} - -func newReaperService(dc *api.CassandraDatacenter) *corev1.Service { - return &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: getReaperServiceName(dc), - Namespace: dc.Namespace, - Labels: dc.GetDatacenterLabels(), - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Port: ReaperUIPort, - Name: "ui", - Protocol: corev1.ProtocolTCP, - TargetPort: intstr.IntOrString{ - Type: intstr.String, - StrVal: "ui", - }, - }, - }, - Selector: dc.GetDatacenterLabels(), - }, - } -} diff --git a/operator/pkg/reconciliation/reconcile_reaper_test.go b/operator/pkg/reconciliation/reconcile_reaper_test.go deleted file mode 100644 index 6cd3e0cc6..000000000 --- a/operator/pkg/reconciliation/reconcile_reaper_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package reconciliation - -import ( - "testing" - - api "github.com/datastax/cass-operator/operator/pkg/apis/cassandra/v1beta1" - "github.com/stretchr/testify/assert" - v1batch "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -func TestReconcileReaper_buildInitReaperSchemaJob(t *testing.T) { - dc := newCassandraDatacenter() - job := buildInitReaperSchemaJob(dc) - - assert.Equal(t, getReaperSchemaInitJobName(dc), job.Name) - assert.Equal(t, dc.GetDatacenterLabels(), job.Labels) - - assert.Equal(t, 1, len(job.Spec.Template.Spec.Containers)) - container := job.Spec.Template.Spec.Containers[0] - - assert.Equal(t, ReaperSchemaInitJobImage, container.Image) - - expectedEnvVars := []corev1.EnvVar{ - {Name: "KEYSPACE", Value: ReaperKeyspace}, - {Name: "CONTACT_POINTS", Value: dc.GetSeedServiceName()}, - {Name: "REPLICATION", Value: "{'class': 'NetworkTopologyStrategy', 'ReaperSchemaJobTest': 3}"}, - } - assert.ElementsMatch(t, expectedEnvVars, container.Env) -} - -func TestReconcileReaper_newReaperService(t *testing.T) { - dc := newCassandraDatacenter() - service := newReaperService(dc) - - assert.Equal(t, getReaperServiceName(dc), service.Name) - assert.Equal(t, dc.GetDatacenterLabels(), service.Labels) - assert.Equal(t, 1, len(service.Spec.Ports)) - - port := service.Spec.Ports[0] - assert.Equal(t, int32(ReaperUIPort), port.Port) - assert.Equal(t, dc.GetDatacenterLabels(), service.Spec.Selector) -} - -func TestReconcileReaper_CheckReaperSchemaInitialized(t *testing.T) { - rc, _, cleanupMockScr := setupTest() - rc.Datacenter.Spec.Reaper = &api.ReaperConfig{Enabled: true} - defer cleanupMockScr() - - trackObjects := []runtime.Object{rc.Datacenter} - - rc.Client = fake.NewFakeClient(trackObjects...) - - reconcileResult := rc.CheckReaperSchemaInitialized() - assert.True(t, reconcileResult.Completed()) - - result, err := reconcileResult.Output() - - assert.NoError(t, err) - assert.True(t, result.Requeue, "should requeue request") - - job := &v1batch.Job{} - jobName := getReaperSchemaInitJobName(rc.Datacenter) - err = rc.Client.Get(rc.Ctx, types.NamespacedName{Namespace: rc.Datacenter.Namespace, Name: jobName}, job) - - assert.NoErrorf(t, err, "failed to get job %s", jobName) -} - -func TestReconcileReaper_CheckReaperSchemaNotInitialized(t *testing.T) { - rc, _, cleanupMockScr := setupTest() - defer cleanupMockScr() - - trackObjects := []runtime.Object{rc.Datacenter} - - rc.Client = fake.NewFakeClient(trackObjects...) - - reconcileResult := rc.CheckReaperSchemaInitialized() - assert.False(t, reconcileResult.Completed()) - - job := &v1batch.Job{} - jobName := getReaperSchemaInitJobName(rc.Datacenter) - err := rc.Client.Get(rc.Ctx, types.NamespacedName{Namespace: rc.Datacenter.Namespace, Name: jobName}, job) - - assert.True(t, errors.IsNotFound(err), "did not expect to find job %s", jobName) -} - -func TestReconcileReaper_CheckReaperService(t *testing.T) { - rc, _, cleanupMockScr := setupTest() - defer cleanupMockScr() - - rc.Datacenter.Spec.Reaper = &api.ReaperConfig{Enabled: true} - - trackObjects := []runtime.Object{rc.Datacenter} - - rc.Client = fake.NewFakeClient(trackObjects...) - - reconcileResult := rc.CheckReaperService() - assert.False(t, reconcileResult.Completed()) - - service := &corev1.Service{} - serviceName := getReaperServiceName(rc.Datacenter) - err := rc.Client.Get(rc.Ctx, types.NamespacedName{Namespace: rc.Datacenter.Namespace, Name: serviceName}, service) - - assert.NoErrorf(t, err, "failed to get service %s", serviceName) -} - -func TestReconcileReaper_CheckReaperServiceReaperDisabled(t *testing.T) { - rc, _, cleanupMockScr := setupTest() - defer cleanupMockScr() - - serviceName := getReaperServiceName(rc.Datacenter) - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: rc.Datacenter.Namespace, - Name: serviceName, - }, - } - - trackObjects := []runtime.Object{rc.Datacenter, service} - - rc.Client = fake.NewFakeClient(trackObjects...) - - reconcileResult := rc.CheckReaperService() - assert.False(t, reconcileResult.Completed()) - - err := rc.Client.Get(rc.Ctx, types.NamespacedName{Namespace: rc.Datacenter.Namespace, Name: serviceName}, service) - - assert.True(t, errors.IsNotFound(err), "did not expect to find service %s", serviceName) -} - -func newCassandraDatacenter() *api.CassandraDatacenter { - return &api.CassandraDatacenter{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ReaperSchemaJobTest", - Namespace: "reaper", - Labels: map[string]string{ - api.DatacenterLabel: "ReaperSchemaJobTest", - api.ClusterLabel: "reaper", - }, - }, - Spec: api.CassandraDatacenterSpec{ - Size: 6, - ClusterName: "reaper", - ServerType: "Cassandra", - ServerVersion: "3.11.7", - }, - } -} diff --git a/operator/pkg/reconciliation/secrets.go b/operator/pkg/reconciliation/secrets.go index 905a023fe..078a200cf 100644 --- a/operator/pkg/reconciliation/secrets.go +++ b/operator/pkg/reconciliation/secrets.go @@ -79,6 +79,37 @@ func buildDefaultSuperuserSecret(dc *api.CassandraDatacenter) (*corev1.Secret, e return secret, nil } +func (rc *ReconciliationContext) createReaperJmxSecretIfNotExists() error { + dc := rc.Datacenter + key := types.NamespacedName{Namespace: dc.Namespace, Name: dc.GetReaperJmxSecretName()} + + if err := rc.Client.Get(rc.Ctx, key, &corev1.Secret{}); err == nil { + return nil + } else if !errors.IsNotFound(err) { + return err + } else { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: key.Namespace, + Name: key.Name, + }, + } + + username := dc.Spec.ClusterName + "-reaper" + password, err := generateUtf8Password() + if err != nil { + return fmt.Errorf("failed to generate password for reaper jmx secret: %w", err) + } + + secret.Data = map[string][]byte{ + "username": []byte(username), + "password": []byte(password), + } + + return rc.Client.Create(rc.Ctx, secret) + } +} + func (rc *ReconciliationContext) retrieveSecret(secretNamespacedName types.NamespacedName) (*corev1.Secret, error) { secret := &corev1.Secret{ TypeMeta: metav1.TypeMeta{ diff --git a/tests/README.md b/tests/README.md index ce54b3536..fe38085cb 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,4 +1,4 @@ -# cass-operator integration tests + # cass-operator integration tests [Ginkgo](https://onsi.github.io/ginkgo/) is used alongside kubectl for integration testing of the operator. diff --git a/tests/enable_reaper/enable_reaper_suite_test.go b/tests/enable_reaper/enable_reaper_suite_test.go index c4a8903c9..a2adbf0e2 100644 --- a/tests/enable_reaper/enable_reaper_suite_test.go +++ b/tests/enable_reaper/enable_reaper_suite_test.go @@ -36,7 +36,8 @@ func TestLifecycle(t *testing.T) { }) RegisterFailHandler(Fail) - RunSpecs(t, testName) + // Disabling this test since Reaper integration is being handled in reaper-operator + //RunSpecs(t, testName) } var _ = Describe(testName, func() { @@ -49,10 +50,15 @@ var _ = Describe(testName, func() { step := "setting up cass-operator resources via helm chart" ns.HelmInstall("../../charts/cass-operator-chart") + step = "setting up reaper-operator" + k := kubectl.ApplyFiles("../testdata/reaper-operator-bundle.yaml") + ns.ExecAndLog(step, k) + ns.WaitForOperatorReady() + ns.WaitForReaperOperatorReady() step = "creating a datacenter resource with 3 racks/3 nodes" - k := kubectl.ApplyFiles(dcYaml) + k = kubectl.ApplyFiles(dcYaml) ns.ExecAndLog(step, k) ns.WaitForSuperUserUpserted(dcName, 600) @@ -65,33 +71,25 @@ var _ = Describe(testName, func() { ns.WaitForDatacenterCondition(dcName, "Ready", string(corev1.ConditionTrue)) ns.WaitForDatacenterCondition(dcName, "Initialized", string(corev1.ConditionTrue)) + // We need to enable reaper in the CassandraDatacenter before deploying Reaper because + // cass-operator creates the JMX secret that Reaper uses. The Reaper object cannot be + // created without that secret. Alternatively, we could create the secret instead of + // letting cass-operator generate it. Then the order of steps is less important. step = "enable Reaper" - json := `{"spec": {"reaper": {"enabled": true}}}` + json := `{"spec": {"reaper": {"enabled": true, "name": "reaper-cassdc"}}}` k = kubectl.PatchMerge(dcResource, json) ns.ExecAndLog(step, k) ns.WaitForDatacenterOperatorProgress(dcName, "Updating", 120) ns.WaitForDatacenterOperatorProgress(dcName, "Ready", 1200) - step = "check that Reaper container is deployed" - json = `jsonpath={.items[*].spec.containers[?(@.name=="reaper")].name}` - k = kubectl.Get("pods"). - FormatOutput(json) - ns.WaitForOutputAndLog(step, k, "reaper reaper reaper", 20) - - step = "disable Reaper" - json = `{"spec": {"reaper": {"enabled": false}}}` - k = kubectl.PatchMerge(dcResource, json) + step = "deploy reaper" + k = kubectl.ApplyFiles("../testdata/reaper.yaml") ns.ExecAndLog(step, k) - ns.WaitForDatacenterOperatorProgress(dcName, "Updating", 120) - ns.WaitForDatacenterOperatorProgress(dcName, "Ready", 1200) + ns.WaitForReaperReady("reaper-cassdc", 300) - step = "check that Reaper container is not deployed" - json = `jsonpath={.items[*].spec.containers[?(@.name=="reaper")]}` - k = kubectl.Get("pods"). - FormatOutput(json) - ns.WaitForOutputAndLog(step, k, "", 20) + ns.WaitForDatacenterCondition(dcName, "Reaper", "True") step = "deleting the dc" k = kubectl.DeleteFromFiles(dcYaml) diff --git a/tests/testdata/reaper-operator-bundle.yaml b/tests/testdata/reaper-operator-bundle.yaml new file mode 100644 index 000000000..00e6ad9f7 --- /dev/null +++ b/tests/testdata/reaper-operator-bundle.yaml @@ -0,0 +1,257 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.3.0 + creationTimestamp: null + name: reapers.reaper.cassandra-reaper.io +spec: + group: reaper.cassandra-reaper.io + names: + kind: Reaper + listKind: ReaperList + plural: reapers + singular: reaper + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: Reaper is the Schema for the reapers API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ReaperSpec defines the desired state of Reaper + properties: + image: + type: string + serverConfig: + properties: + cassandraBackend: + properties: + authProvider: + properties: + password: + type: string + type: + type: string + username: + type: string + type: object + cassandraService: + description: The headless service that provides endpoints for the StorageTypeCassandra pods + type: string + clusterName: + type: string + keyspace: + description: Defaults to reaper + type: string + replication: + description: Specifies the replication strategy for a keyspace + properties: + networkTopologyStrategy: + additionalProperties: + format: int32 + type: integer + description: Specifies the replication_factor when NetworkTopologyStrategy is used. The mapping is data center name to RF. + type: object + simpleStrategy: + description: Specifies the replication_factor when SimpleStrategy is used + format: int32 + type: integer + type: object + required: + - cassandraService + - clusterName + - replication + type: object + jmxUserSecretName: + description: Defines the username and password that Reaper will use to authenticate JMX connections to Cassandra clusters. These credentials need to be stored on each Cassandra node. + type: string + storageType: + type: string + type: object + type: object + status: + description: ReaperStatus defines the observed state of Reaper + properties: + ready: + type: boolean + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: reaper-operator +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - create + - get + - list + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - get + - list + - watch +- apiGroups: + - reaper.cassandra-reaper.io + resources: + - reapers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - reaper.cassandra-reaper.io + resources: + - reapers/status + verbs: + - get + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: reaper-operator-leader-election +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - configmaps/status + verbs: + - get + - update + - patch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: reaper-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: reaper-operator +subjects: +- kind: ServiceAccount + name: default +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: reaper-operator-leader-election +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: reaper-operator-leader-election +subjects: +- kind: ServiceAccount + name: default +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + control-plane: reaper-operator + name: reaper-operator +spec: + replicas: 1 + selector: + matchLabels: + control-plane: reaper-operator + template: + metadata: + labels: + control-plane: reaper-operator + spec: + containers: + - args: + - --enable-leader-election + command: + - /manager + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: docker.io/thelastpickle/reaper-operator:latest + name: reaper-operator + resources: + limits: + cpu: 100m + memory: 30Mi + requests: + cpu: 100m + memory: 20Mi + terminationGracePeriodSeconds: 10 diff --git a/tests/testdata/reaper.yaml b/tests/testdata/reaper.yaml new file mode 100644 index 000000000..d51427329 --- /dev/null +++ b/tests/testdata/reaper.yaml @@ -0,0 +1,14 @@ +apiVersion: reaper.cassandra-reaper.io/v1alpha1 +kind: Reaper +metadata: + name: reaper-cassdc +spec: + serverConfig: + storageType: cassandra + jmxUserSecretName: cluster1-reaper-jmx + cassandraBackend: + clusterName: cluster1 + replication: + networkTopologyStrategy: + dc1: 3 + cassandraService: cluster1-dc1-service \ No newline at end of file