diff --git a/api/v1beta1/axonopscassandra_types.go b/api/v1beta1/axonopscassandra_types.go index 6ae2338..7397838 100644 --- a/api/v1beta1/axonopscassandra_types.go +++ b/api/v1beta1/axonopscassandra_types.go @@ -62,6 +62,7 @@ type AxonOpsCassandraCluster struct { Image ContainerImage `json:"image,omitempty"` Replicas int `json:"replicas,omitempty"` ClusterName string `json:"clusterName,omitempty"` + DC string `json:"dc,omitempty"` PersistentVolume PersistentVolumeSpec `json:"persistentVolume,omitempty"` JavaOpts string `json:"javaOpts,omitempty"` HeapSize string `json:"heapSize,omitempty"` @@ -89,12 +90,14 @@ type AxonOpsDashboard struct { // AxonOpsServer defines the dashboard type AxonOpsServer struct { // Container image definition with repository and tag - Image ContainerImage `json:"image,omitempty"` - Annotations map[string]string `json:"annotations,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Env []EnvVars `json:"env,omitempty"` - Resources corev1.ResourceRequirements `json:"resources,omitempty"` - PullPolicy string `json:"pullPolicy,omitempty"` + Image ContainerImage `json:"image,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Env []EnvVars `json:"env,omitempty"` + Resources corev1.ResourceRequirements `json:"resources,omitempty"` + PullPolicy string `json:"pullPolicy,omitempty"` + CassandraMetricsEnabled bool `json:"cassandraMetricsEnabled,omitempty"` + CassandraMetricsCluster AxonOpsCassandraCluster `json:"cassandraMetricsCluster,omitempty"` } // AxonOpsServer defines the dashboard diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index c7eb3e5..3df9c2e 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -240,6 +240,7 @@ func (in *AxonOpsServer) DeepCopyInto(out *AxonOpsServer) { copy(*out, *in) } in.Resources.DeepCopyInto(&out.Resources) + in.CassandraMetricsCluster.DeepCopyInto(&out.CassandraMetricsCluster) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AxonOpsServer. diff --git a/apps/axon-server.go b/apps/axon-server.go index 1259c11..d1d51ef 100644 --- a/apps/axon-server.go +++ b/apps/axon-server.go @@ -155,6 +155,16 @@ type ServerConfig struct { } func GenerateServerConfig(cfg cassandraaxonopscomv1beta1.AxonOpsCassandra) (*appsv1.StatefulSet, error) { + if cfg.Spec.AxonOps.Server.CassandraMetricsEnabled { + cfg.Spec.AxonOps.Server.Env = append(cfg.Spec.AxonOps.Server.Env, cassandraaxonopscomv1beta1.EnvVars{ + Name: "CQL_HOSTS", + Value: "ca-metrics-" + cfg.GetName(), + }) + cfg.Spec.AxonOps.Server.Env = append(cfg.Spec.AxonOps.Server.Env, cassandraaxonopscomv1beta1.EnvVars{ + Name: "CQL_LOCAL_DC", + Value: "axonops1", + }) + } config := ServerConfig{ Name: cfg.GetName(), Namespace: cfg.GetNamespace(), diff --git a/apps/cassandra.go b/apps/cassandra.go index 333205e..4582377 100644 --- a/apps/cassandra.go +++ b/apps/cassandra.go @@ -163,7 +163,7 @@ spec: - name: CASSANDRA_ENDPOINT_SNITCH value: GossipingPropertyFileSnitch - name: CASSANDRA_DC - value: dc1 + value: {{ .DC }} - name: CASSANDRA_RACK value: rack1 - name: CASSANDRA_BROADCAST_RPC_ADDRESS @@ -279,6 +279,7 @@ type CassandraConfig struct { Replicas int Image string ClusterName string + DC string JavaOpts string HeapSize string StorageSize string @@ -293,28 +294,29 @@ type CassandraConfig struct { PullPolicy string } -func GenerateCassandraConfig(cfg cassandraaxonopscomv1beta1.AxonOpsCassandra) (*appsv1.StatefulSet, error) { +func GenerateCassandraConfig(name string, namespace string, storageSize string, storageClass string, cfg cassandraaxonopscomv1beta1.AxonOpsCassandraCluster) (*appsv1.StatefulSet, error) { config := CassandraConfig{ - Name: cfg.GetName(), - Namespace: cfg.GetNamespace(), - Replicas: utils.ValueOrDefaultInt(cfg.Spec.Cassandra.Replicas, 1), + Name: name, + Namespace: namespace, + Replicas: utils.ValueOrDefaultInt(cfg.Replicas, 1), Image: fmt.Sprintf("%s:%s", - utils.ValueOrDefault(cfg.Spec.Cassandra.Image.Repository, defaultCassandraImage), - utils.ValueOrDefault(cfg.Spec.Cassandra.Image.Tag, defaultCassandraTag), + utils.ValueOrDefault(cfg.Image.Repository, defaultCassandraImage), + utils.ValueOrDefault(cfg.Image.Tag, defaultCassandraTag), ), - ClusterName: utils.ValueOrDefault(cfg.Spec.Cassandra.ClusterName, cfg.GetName()), - JavaOpts: utils.ValueOrDefault(cfg.Spec.Cassandra.JavaOpts, "-Xms512m -Xmx512m"), - StorageSize: utils.ValueOrDefault(cfg.Spec.AxonOps.Elasticsearch.PersistentVolume.Size, ""), - StorageClass: utils.ValueOrDefault(cfg.Spec.AxonOps.Elasticsearch.PersistentVolume.StorageClass, ""), - HeapSize: utils.ValueOrDefault(cfg.Spec.Cassandra.HeapSize, "512M"), - Labels: cfg.Spec.Cassandra.Labels, - Annotations: cfg.Spec.Cassandra.Annotations, - Env: cfg.Spec.Cassandra.Env, - CpuRequest: utils.ValueOrDefault(cfg.Spec.Cassandra.Resources.Requests.Cpu().String(), "500m"), - MemoryRequest: utils.ValueOrDefault(cfg.Spec.Cassandra.Resources.Requests.Memory().String(), "1Gi"), - CpuLimit: utils.ValueOrDefault(cfg.Spec.Cassandra.Resources.Limits.Cpu().String(), "1000m"), - MemoryLimit: utils.ValueOrDefault(cfg.Spec.Cassandra.Resources.Limits.Memory().String(), "2Gi"), - PullPolicy: utils.ValueOrDefault(cfg.Spec.Cassandra.PullPolicy, "IfNotPresent"), + ClusterName: utils.ValueOrDefault(cfg.ClusterName, name), + DC: utils.ValueOrDefault(cfg.DC, "dc1"), + JavaOpts: utils.ValueOrDefault(cfg.JavaOpts, "-Xms512m -Xmx512m"), + StorageSize: utils.ValueOrDefault(storageSize, ""), + StorageClass: utils.ValueOrDefault(storageClass, ""), + HeapSize: utils.ValueOrDefault(cfg.HeapSize, "512M"), + Labels: cfg.Labels, + Annotations: cfg.Annotations, + Env: cfg.Env, + CpuRequest: utils.ValueOrDefault(cfg.Resources.Requests.Cpu().String(), "500m"), + MemoryRequest: utils.ValueOrDefault(cfg.Resources.Requests.Memory().String(), "1Gi"), + CpuLimit: utils.ValueOrDefault(cfg.Resources.Limits.Cpu().String(), "1000m"), + MemoryLimit: utils.ValueOrDefault(cfg.Resources.Limits.Memory().String(), "2Gi"), + PullPolicy: utils.ValueOrDefault(cfg.PullPolicy, "IfNotPresent"), } statefulSet := &appsv1.StatefulSet{} @@ -342,12 +344,12 @@ func GenerateCassandraConfig(cfg cassandraaxonopscomv1beta1.AxonOpsCassandra) (* return statefulSet, nil } -func GenerateCassandraServiceConfig(cfg cassandraaxonopscomv1beta1.AxonOpsCassandra) (*corev1.Service, error) { +func GenerateCassandraServiceConfig(name string, namespace string, labels map[string]string, annonations map[string]string) (*corev1.Service, error) { config := CassandraServiceConfig{ - Name: cfg.GetName(), - Namespace: cfg.GetNamespace(), - Labels: cfg.Spec.Cassandra.Labels, - Annotations: cfg.Spec.Cassandra.Annotations, + Name: name, + Namespace: namespace, + Labels: labels, + Annotations: annonations, } svc := &corev1.Service{} @@ -380,8 +382,8 @@ func GenerateCassandraHeadlessServiceConfig(cfg cassandraaxonopscomv1beta1.AxonO config := CassandraServiceConfig{ Name: cfg.GetName(), Namespace: cfg.GetNamespace(), - Labels: cfg.Spec.Cassandra.Labels, - Annotations: cfg.Spec.Cassandra.Annotations, + Labels: cfg.Labels, + Annotations: cfg.Annotations, } svc := &corev1.Service{} diff --git a/config/crd/bases/axonops.com_axonopscassandras.yaml b/config/crd/bases/axonops.com_axonopscassandras.yaml index 622ade5..fee62a4 100644 --- a/config/crd/bases/axonops.com_axonopscassandras.yaml +++ b/config/crd/bases/axonops.com_axonopscassandras.yaml @@ -300,6 +300,121 @@ spec: additionalProperties: type: string type: object + cassandraMetricsCluster: + description: AxonOpsCassandraCluster defines the Apache Cassandra + cluster to install + properties: + annotations: + additionalProperties: + type: string + type: object + clusterName: + type: string + dc: + type: string + env: + items: + description: EnvVars lists the environmetn variables + to add to the deployment or statefulset + properties: + name: + description: Environment variable name + type: string + value: + description: Environment variable value + type: string + type: object + type: array + heapSize: + type: string + image: + properties: + repository: + type: string + tag: + type: string + type: object + javaOpts: + type: string + labels: + additionalProperties: + type: string + type: object + persistentVolume: + description: PersistentVolumeSpec defines the persistent + volume specification + properties: + size: + description: Storage size + type: string + storageClass: + description: Optional Storage Class name + type: string + type: object + pullPolicy: + type: string + replicas: + type: integer + resources: + description: ResourceRequirements describes the compute + resource requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + type: object + cassandraMetricsEnabled: + type: boolean env: items: description: EnvVars lists the environmetn variables to @@ -399,6 +514,8 @@ spec: type: object clusterName: type: string + dc: + type: string env: items: description: EnvVars lists the environmetn variables to add diff --git a/config/samples/cassandra-metrics.yaml b/config/samples/cassandra-metrics.yaml new file mode 100644 index 0000000..a6d4864 --- /dev/null +++ b/config/samples/cassandra-metrics.yaml @@ -0,0 +1,25 @@ +apiVersion: axonops.com/v1beta1 +kind: AxonOpsCassandra +metadata: + labels: + app.kubernetes.io/name: axonops-developer-operator + app.kubernetes.io/managed-by: kustomize + name: axonopscassandra-sample + namespace: axonops-dev +spec: + cassandra: + replicas: 1 + clusterName: "my-dev-env" + axonops: + elasticsearch: + persistentVolume: + size: 2Gi + storageClass: local-path + server: + cassandraMetricsEnabled: true + cassandraMetricsCluster: + replicas: 2 + dc: axonops2 + persistentVolume: + size: 2Gi + storageClass: local-path diff --git a/internal/controller/axonopscassandra_controller.go b/internal/controller/axonopscassandra_controller.go index 0c92b1b..68e260b 100644 --- a/internal/controller/axonopscassandra_controller.go +++ b/internal/controller/axonopscassandra_controller.go @@ -90,6 +90,10 @@ func (r *AxonOpsCassandraReconciler) Reconcile(ctx context.Context, req ctrl.Req "ds-" + thisClusterName, } + if axonopsCassCluster.Spec.AxonOps.Server.CassandraMetricsEnabled { + statefulSetList = append(statefulSetList, "ca-mt-"+thisClusterName) + } + // The object is being deleted if utils.ContainsString(axonopsCassCluster.GetFinalizers(), axonopsFinalizerName) { // our finalizer is present, so lets handle any external dependency @@ -268,6 +272,80 @@ func (r *AxonOpsCassandraReconciler) Reconcile(ctx context.Context, req ctrl.Req /* STEP 3: + Create the Cassandra STS for metrics if enabled + */ + + if axonopsCassCluster.Spec.AxonOps.Server.CassandraMetricsEnabled { + var cassandraMetricsStatefulSetCurrent *appsv1.StatefulSet + var cassandraMetricsStatefulSet *appsv1.StatefulSet + cassandraMetricsStatefulSetCurrent, err = r.getSts("ca-mt-"+thisClusterName, thisClusterNamespace) + + if client.IgnoreNotFound(err) != nil { + return ctrl.Result{}, err + } + + /* Create the cassandra search STS */ + if axonopsCassCluster.Spec.Cassandra.DC == "" { + axonopsCassCluster.Spec.Cassandra.DC = "axonops1" + } + cassandraMetricsStatefulSet, err = apps.GenerateCassandraConfig( + "metrics-"+axonopsCassCluster.GetName(), + axonopsCassCluster.GetNamespace(), + axonopsCassCluster.Spec.AxonOps.Server.CassandraMetricsCluster.PersistentVolume.Size, + axonopsCassCluster.Spec.AxonOps.Server.CassandraMetricsCluster.PersistentVolume.StorageClass, + axonopsCassCluster.Spec.AxonOps.Server.CassandraMetricsCluster) + if err != nil { + r.Recorder.Event(&axonopsCassCluster, corev1.EventTypeNormal, "Failed", "Failed to parse the Cassandra configuration for the metrics storage: "+err.Error()) + return ctrl.Result{}, err + } + + if cassandraMetricsStatefulSetCurrent == nil { + err = r.Create(ctx, cassandraMetricsStatefulSet) + if err != nil { + //r.Recorder.Event(&axonopsCassCluster, corev1.EventTypeNormal, "Failed", "Failed to create the Cassandra Statefulset: "+err.Error()) + return ctrl.Result{}, err + } + r.Recorder.Event(&axonopsCassCluster, corev1.EventTypeNormal, "Created", "Cassandra sts created successfully") + } else { + /* Update the cassandra search STS */ + err = r.Update(ctx, cassandraMetricsStatefulSet) + if err != nil { + r.Recorder.Event(&axonopsCassCluster, corev1.EventTypeNormal, "Failed", "Failed to update the Cassandra Statefulset: "+err.Error()) + return ctrl.Result{}, err + } + } + var cassandraSvc *corev1.Service + var cassandraSvcCurrent *corev1.Service + cassandraSvcCurrent, err = r.getService("ca-"+thisClusterName, thisClusterNamespace) + if client.IgnoreNotFound(err) != nil { + return ctrl.Result{}, err + } + /* Create the cassandra service */ + cassandraSvc, err = apps.GenerateCassandraServiceConfig("metrics-"+axonopsCassCluster.GetName(), + axonopsCassCluster.GetNamespace(), + axonopsCassCluster.Spec.Cassandra.Labels, + axonopsCassCluster.Spec.Cassandra.Annotations) + if err != nil { + r.Recorder.Event(&axonopsCassCluster, corev1.EventTypeNormal, "Failed", "Failed to create the Cassandra service: "+err.Error()) + return ctrl.Result{}, err + } + if cassandraSvcCurrent == nil { + err = r.Create(ctx, cassandraSvc) + if err != nil { + return ctrl.Result{}, err + } + } else { + /* Update the cassandra search service */ + err = r.Update(ctx, cassandraSvc) + if err != nil { + r.Recorder.Event(&axonopsCassCluster, corev1.EventTypeNormal, "Failed", "Failed to update the Cassandra service: "+err.Error()) + return ctrl.Result{}, err + } + //r.Recorder.Event(&axonopsCassCluster, corev1.EventTypeNormal, "Created", "Cassandra service updated successfully") + } + } + /* + STEP 4: Create the AxonServer Config */ @@ -326,7 +404,7 @@ func (r *AxonOpsCassandraReconciler) Reconcile(ctx context.Context, req ctrl.Req } /* - STEP 4: + STEP 5: Create the Cassandra STS */ @@ -339,7 +417,12 @@ func (r *AxonOpsCassandraReconciler) Reconcile(ctx context.Context, req ctrl.Req } /* Create the cassandra search STS */ - cassandraStatefulSet, err = apps.GenerateCassandraConfig(axonopsCassCluster) + cassandraStatefulSet, err = apps.GenerateCassandraConfig( + axonopsCassCluster.GetName(), + axonopsCassCluster.GetNamespace(), + axonopsCassCluster.Spec.AxonOps.Server.CassandraMetricsCluster.PersistentVolume.Size, + axonopsCassCluster.Spec.AxonOps.Server.CassandraMetricsCluster.PersistentVolume.StorageClass, + axonopsCassCluster.Spec.Cassandra) if err != nil { r.Recorder.Event(&axonopsCassCluster, corev1.EventTypeNormal, "Failed", "Failed to parse the Cassandra configuration: "+err.Error()) return ctrl.Result{}, err @@ -368,8 +451,10 @@ func (r *AxonOpsCassandraReconciler) Reconcile(ctx context.Context, req ctrl.Req if client.IgnoreNotFound(err) != nil { return ctrl.Result{}, err } - /* Create the cassandra search service */ - cassandraSvc, err = apps.GenerateCassandraServiceConfig(axonopsCassCluster) + /* Create the cassandra service */ + cassandraSvc, err = apps.GenerateCassandraServiceConfig(axonopsCassCluster.GetName(), axonopsCassCluster.GetNamespace(), + axonopsCassCluster.Spec.Cassandra.Labels, + axonopsCassCluster.Spec.Cassandra.Annotations) if err != nil { r.Recorder.Event(&axonopsCassCluster, corev1.EventTypeNormal, "Failed", "Failed to create the Cassandra service: "+err.Error()) return ctrl.Result{}, err