diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..aaeec1834 Binary files /dev/null and b/.DS_Store differ diff --git a/CHANGELOG/CHANGELOG-1.5.md b/CHANGELOG/CHANGELOG-1.5.md index bf73e76b9..717d097ac 100644 --- a/CHANGELOG/CHANGELOG-1.5.md +++ b/CHANGELOG/CHANGELOG-1.5.md @@ -15,6 +15,7 @@ When cutting a new release, update the `unreleased` heading to the tag being gen ## unreleased * [FEATURE] [#783](https://github.com/k8ssandra/k8ssandra-operator/issues/783) Allow disabling MCAC +* [FEATURE] [#739815](https://github.com/k8ssandra/k8ssandra-operator/issues/815) Add configuration block to CRDs for new Cassandra metrics agent. * [FEATURE] [#739](https://github.com/k8ssandra/k8ssandra-operator/issues/739) Add API for cluster-level tasks * [FEATURE] [#775](https://github.com/k8ssandra/k8ssandra-operator/issues/775) Add the ability to inject and configure a Vector agent sidecar in the Cassandra pods * [FEATURE] [#600](https://github.com/k8ssandra/k8ssandra-operator/issues/600) Disable secrets management and replication with the external secrets provider diff --git a/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go b/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go index 3552585d4..20eb0399d 100644 --- a/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go +++ b/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go @@ -364,7 +364,7 @@ type DatacenterOptions struct { // If telemetry configurations are defined, telemetry resources will be deployed to integrate with // a user-provided monitoring solution (at present, only support for Prometheus is available). // +optional - Telemetry *telemetryapi.TelemetrySpec `json:"telemetry,omitempty"` + Telemetry *telemetryapi.CassandraTelemetrySpec `json:"telemetry,omitempty"` // CDC defines the desired state for CDC integrations. It can be used to feed mutation events from Cassandra into an Apache Pulsar cluster, // from where they can be expored to external systems. diff --git a/apis/k8ssandra/v1alpha1/zz_generated.deepcopy.go b/apis/k8ssandra/v1alpha1/zz_generated.deepcopy.go index 9199b4826..ee788d749 100644 --- a/apis/k8ssandra/v1alpha1/zz_generated.deepcopy.go +++ b/apis/k8ssandra/v1alpha1/zz_generated.deepcopy.go @@ -248,7 +248,7 @@ func (in *DatacenterOptions) DeepCopyInto(out *DatacenterOptions) { } if in.Telemetry != nil { in, out := &in.Telemetry, &out.Telemetry - *out = new(telemetryv1alpha1.TelemetrySpec) + *out = new(telemetryv1alpha1.CassandraTelemetrySpec) (*in).DeepCopyInto(*out) } if in.CDC != nil { diff --git a/apis/telemetry/v1alpha1/telemetry_methods.go b/apis/telemetry/v1alpha1/telemetry_methods.go index e11202a4f..432f9ff01 100644 --- a/apis/telemetry/v1alpha1/telemetry_methods.go +++ b/apis/telemetry/v1alpha1/telemetry_methods.go @@ -7,11 +7,16 @@ func (in *TelemetrySpec) MergeWith(clusterTemplate *TelemetrySpec) *TelemetrySpe return goalesceutils.MergeCRs(clusterTemplate, in) } +// MergeWith merges the given cluster-level template into this (DC-level) template. +func (in *CassandraTelemetrySpec) MergeWith(clusterTemplate *CassandraTelemetrySpec) *CassandraTelemetrySpec { + return goalesceutils.MergeCRs(clusterTemplate, in) +} + func (in *TelemetrySpec) IsPrometheusEnabled() bool { return in != nil && in.Prometheus != nil && in.Prometheus.Enabled != nil && *in.Prometheus.Enabled } -func (in *TelemetrySpec) IsMcacEnabled() bool { +func (in *CassandraTelemetrySpec) IsMcacEnabled() bool { return in == nil || in.Mcac == nil || in.Mcac.Enabled == nil || *in.Mcac.Enabled } diff --git a/apis/telemetry/v1alpha1/telemetry_types.go b/apis/telemetry/v1alpha1/telemetry_types.go index 37f32062e..e2a924869 100644 --- a/apis/telemetry/v1alpha1/telemetry_types.go +++ b/apis/telemetry/v1alpha1/telemetry_types.go @@ -3,16 +3,22 @@ package v1alpha1 import ( + promapi "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type TelemetrySpec struct { Prometheus *PrometheusTelemetrySpec `json:"prometheus,omitempty"` - Mcac *McacTelemetrySpec `json:"mcac,omitempty"` Vector *VectorSpec `json:"vector,omitempty"` } +type CassandraTelemetrySpec struct { + *TelemetrySpec `json:",inline"` + Mcac *McacTelemetrySpec `json:"mcac,omitempty"` + Cassandra *CassandraAgentSpec `json:"cassandra,omitempty"` +} + type PrometheusTelemetrySpec struct { // Enable the creation of Prometheus serviceMonitors for this resource (Cassandra or Stargate). Enabled *bool `json:"enabled,omitempty"` @@ -126,3 +132,13 @@ type McacTelemetrySpec struct { // This is considered true by default. Enabled *bool `json:"enabled,omitempty"` } + +type CassandraAgentSpec struct { + Endpoint Endpoint `json:"endpoint,omitempty"` + Filters []promapi.RelabelConfig `json:"filters,omitempty"` +} + +type Endpoint struct { + Address string `json:"address,omitempty"` + Port string `json:"port,omitempty"` +} diff --git a/apis/telemetry/v1alpha1/zz_generated.deepcopy.go b/apis/telemetry/v1alpha1/zz_generated.deepcopy.go index f7379e686..f539db770 100644 --- a/apis/telemetry/v1alpha1/zz_generated.deepcopy.go +++ b/apis/telemetry/v1alpha1/zz_generated.deepcopy.go @@ -22,10 +22,79 @@ limitations under the License. package v1alpha1 import ( + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CassandraAgentSpec) DeepCopyInto(out *CassandraAgentSpec) { + *out = *in + out.Endpoint = in.Endpoint + if in.Filters != nil { + in, out := &in.Filters, &out.Filters + *out = make([]monitoringv1.RelabelConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CassandraAgentSpec. +func (in *CassandraAgentSpec) DeepCopy() *CassandraAgentSpec { + if in == nil { + return nil + } + out := new(CassandraAgentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CassandraTelemetrySpec) DeepCopyInto(out *CassandraTelemetrySpec) { + *out = *in + if in.TelemetrySpec != nil { + in, out := &in.TelemetrySpec, &out.TelemetrySpec + *out = new(TelemetrySpec) + (*in).DeepCopyInto(*out) + } + if in.Mcac != nil { + in, out := &in.Mcac, &out.Mcac + *out = new(McacTelemetrySpec) + (*in).DeepCopyInto(*out) + } + if in.Cassandra != nil { + in, out := &in.Cassandra, &out.Cassandra + *out = new(CassandraAgentSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CassandraTelemetrySpec. +func (in *CassandraTelemetrySpec) DeepCopy() *CassandraTelemetrySpec { + if in == nil { + return nil + } + out := new(CassandraTelemetrySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Endpoint) DeepCopyInto(out *Endpoint) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Endpoint. +func (in *Endpoint) DeepCopy() *Endpoint { + if in == nil { + return nil + } + out := new(Endpoint) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *McacTelemetrySpec) DeepCopyInto(out *McacTelemetrySpec) { *out = *in @@ -90,11 +159,6 @@ func (in *TelemetrySpec) DeepCopyInto(out *TelemetrySpec) { *out = new(PrometheusTelemetrySpec) (*in).DeepCopyInto(*out) } - if in.Mcac != nil { - in, out := &in.Mcac, &out.Mcac - *out = new(McacTelemetrySpec) - (*in).DeepCopyInto(*out) - } if in.Vector != nil { in, out := &in.Vector, &out.Vector *out = new(VectorSpec) diff --git a/config/crd/bases/k8ssandra.io_k8ssandraclusters.yaml b/config/crd/bases/k8ssandra.io_k8ssandraclusters.yaml index 194ba82d8..f62852206 100644 --- a/config/crd/bases/k8ssandra.io_k8ssandraclusters.yaml +++ b/config/crd/bases/k8ssandra.io_k8ssandraclusters.yaml @@ -10801,38 +10801,6 @@ spec: pods for all DCs in this cluster (unless overriden by DC specific settings) properties: - mcac: - properties: - enabled: - description: enabled sets whether MCAC - (legacy metrics endpoint) is enabled. - This is considered true by default. - type: boolean - metricFilters: - description: 'MetricFilters allows passing - filters to MCAC in order to reduce the - amount of extracted metrics. Not setting - this field will result in the default - filters being used: - "deny:org.apache.cassandra.metrics.Table" - - "deny:org.apache.cassandra.metrics.table" - - "allow:org.apache.cassandra.metrics.table.live_ss_table_count" - - "allow:org.apache.cassandra.metrics.Table.LiveSSTableCount" - - "allow:org.apache.cassandra.metrics.table.live_disk_space_used" - - "allow:org.apache.cassandra.metrics.table.LiveDiskSpaceUsed" - - "allow:org.apache.cassandra.metrics.Table.Pending" - - "allow:org.apache.cassandra.metrics.Table.Memtable" - - "allow:org.apache.cassandra.metrics.Table.Compaction" - - "allow:org.apache.cassandra.metrics.table.read" - - "allow:org.apache.cassandra.metrics.table.write" - - "allow:org.apache.cassandra.metrics.table.range" - - "allow:org.apache.cassandra.metrics.table.coordinator" - - "allow:org.apache.cassandra.metrics.table.dropped_mutations" - Setting it to an empty list will result - in all metrics being extracted.' - items: - type: string - type: array - type: object prometheus: properties: commonLabels: @@ -11254,37 +11222,6 @@ spec: for all DCs in this cluster (unless overriden by DC specific settings) properties: - mcac: - properties: - enabled: - description: enabled sets whether MCAC (legacy - metrics endpoint) is enabled. This is considered - true by default. - type: boolean - metricFilters: - description: 'MetricFilters allows passing filters - to MCAC in order to reduce the amount of extracted - metrics. Not setting this field will result - in the default filters being used: - "deny:org.apache.cassandra.metrics.Table" - - "deny:org.apache.cassandra.metrics.table" - - "allow:org.apache.cassandra.metrics.table.live_ss_table_count" - - "allow:org.apache.cassandra.metrics.Table.LiveSSTableCount" - - "allow:org.apache.cassandra.metrics.table.live_disk_space_used" - - "allow:org.apache.cassandra.metrics.table.LiveDiskSpaceUsed" - - "allow:org.apache.cassandra.metrics.Table.Pending" - - "allow:org.apache.cassandra.metrics.Table.Memtable" - - "allow:org.apache.cassandra.metrics.Table.Compaction" - - "allow:org.apache.cassandra.metrics.table.read" - - "allow:org.apache.cassandra.metrics.table.write" - - "allow:org.apache.cassandra.metrics.table.range" - - "allow:org.apache.cassandra.metrics.table.coordinator" - - "allow:org.apache.cassandra.metrics.table.dropped_mutations" - Setting it to an empty list will result in - all metrics being extracted.' - items: - type: string - type: array - type: object prometheus: properties: commonLabels: @@ -11908,6 +11845,212 @@ spec: with a user-provided monitoring solution (at present, only support for Prometheus is available). properties: + cassandra: + properties: + endpoint: + properties: + address: + type: string + port: + type: string + type: object + filters: + items: + description: 'RelabelConfig allows dynamic rewriting + of the label set, being applied to samples before + ingestion. It defines ``-section + of Prometheus configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' + properties: + action: + description: Action to perform based on regex + matching. Default is 'replace' + type: string + modulus: + description: Modulus to take of the hash of + the source label values. + format: int64 + type: integer + regex: + description: Regular expression against which + the extracted value is matched. Default + is '(.*)' + type: string + replacement: + description: Replacement value against which + a regex replace is performed if the regular + expression matches. Regex capture groups + are available. Default is '$1' + type: string + separator: + description: Separator placed between concatenated + source label values. default is ';'. + type: string + sourceLabels: + description: The source labels select values + from existing labels. Their content is concatenated + using the configured separator and matched + against the configured regular expression + for the replace, keep, and drop actions. + items: + type: string + type: array + targetLabel: + description: Label to which the resulting + value is written in a replace action. It + is mandatory for replace actions. Regex + capture groups are available. + type: string + type: object + type: array + type: object + inline: + properties: + prometheus: + properties: + commonLabels: + additionalProperties: + type: string + description: CommonLabels are applied to all + serviceMonitors created. + type: object + enabled: + description: Enable the creation of Prometheus + serviceMonitors for this resource (Cassandra + or Stargate). + type: boolean + type: object + vector: + properties: + components: + properties: + sinks: + description: Sinks is the list of sinks + to use for the Vector agent. + items: + properties: + config: + description: Config is the configuration + for the sink. + type: string + inputs: + description: Inputs is the list of + inputs for the transform. + items: + type: string + type: array + name: + description: Name is the name of the + sink. + type: string + type: + description: Type is the type of the + sink. + type: string + required: + - name + - type + type: object + type: array + sources: + description: Sources is the list of sources + to use for the Vector agent. + items: + properties: + config: + description: Config is the configuration + for the source. + type: string + name: + description: Name is the name of the + source. + type: string + type: + description: Type is the type of the + source. + type: string + required: + - name + - type + type: object + type: array + transforms: + description: Transforms is the list of transforms + to use for the Vector agent. + items: + properties: + config: + description: Config is the configuration + for the transform. + type: string + inputs: + description: Inputs is the list of + inputs for the transform. + items: + type: string + type: array + name: + description: Name is the name of the + transform. + type: string + type: + description: Type is the type of the + transform. + type: string + required: + - name + - type + type: object + type: array + type: object + enabled: + description: Enabled enables the Vector agent + for this resource (Cassandra, Reaper or Stargate). + Enabling the vector agent will inject a sidecar + container into the pod. + type: boolean + image: + description: Image is the name of the Vector + image to use. If not set, the default image + will be used. kube:default="timberio/vector:0.26.0-alpine" + type: string + resources: + description: Resources is the resource requirements + for the Vector agent. + properties: + 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. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + scrapeInterval: + description: ScrapeInterval is the interval + at which the Vector agent will scrape the + metrics endpoint. Use values like 30s, 1m, + 5m. kube:default=30s + type: string + type: object + type: object mcac: properties: enabled: @@ -11939,142 +12082,8 @@ spec: type: string type: array type: object - prometheus: - properties: - commonLabels: - additionalProperties: - type: string - description: CommonLabels are applied to all serviceMonitors - created. - type: object - enabled: - description: Enable the creation of Prometheus serviceMonitors - for this resource (Cassandra or Stargate). - type: boolean - type: object - vector: - properties: - components: - properties: - sinks: - description: Sinks is the list of sinks to use - for the Vector agent. - items: - properties: - config: - description: Config is the configuration - for the sink. - type: string - inputs: - description: Inputs is the list of inputs - for the transform. - items: - type: string - type: array - name: - description: Name is the name of the sink. - type: string - type: - description: Type is the type of the sink. - type: string - required: - - name - - type - type: object - type: array - sources: - description: Sources is the list of sources - to use for the Vector agent. - items: - properties: - config: - description: Config is the configuration - for the source. - type: string - name: - description: Name is the name of the source. - type: string - type: - description: Type is the type of the source. - type: string - required: - - name - - type - type: object - type: array - transforms: - description: Transforms is the list of transforms - to use for the Vector agent. - items: - properties: - config: - description: Config is the configuration - for the transform. - type: string - inputs: - description: Inputs is the list of inputs - for the transform. - items: - type: string - type: array - name: - description: Name is the name of the transform. - type: string - type: - description: Type is the type of the transform. - type: string - required: - - name - - type - type: object - type: array - type: object - enabled: - description: Enabled enables the Vector agent for - this resource (Cassandra, Reaper or Stargate). - Enabling the vector agent will inject a sidecar - container into the pod. - type: boolean - image: - description: Image is the name of the Vector image - to use. If not set, the default image will be - used. kube:default="timberio/vector:0.26.0-alpine" - type: string - resources: - description: Resources is the resource requirements - for the Vector agent. - properties: - 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. More info: - https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - scrapeInterval: - description: ScrapeInterval is the interval at which - the Vector agent will scrape the metrics endpoint. - Use values like 30s, 1m, 5m. kube:default=30s - type: string - type: object + required: + - inline type: object tolerations: description: Tolerations applied to every Cassandra pod. @@ -16132,41 +16141,237 @@ spec: contains only "value". The requirements are ANDed. type: object type: object - x-kubernetes-map-type: atomic - storageClassName: - description: 'storageClassName is the name of the StorageClass - required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is - required by the claim. Value of Filesystem is implied - when not included in claim spec. - type: string - volumeName: - description: volumeName is the binding reference to the - PersistentVolume backing this claim. - type: string + x-kubernetes-map-type: atomic + storageClassName: + description: 'storageClassName is the name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is + required by the claim. Value of Filesystem is implied + when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference to the + PersistentVolume backing this claim. + type: string + type: object + type: object + superuserSecretRef: + description: The reference to the superuser secret to use for + Cassandra. If unspecified, a default secret will be generated + with a random password; the generated secret name will be "-superuser" + where is the K8ssandraCluster CRD name. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + telemetry: + description: Telemetry defines the desired state for telemetry + resources in this datacenter. If telemetry configurations are + defined, telemetry resources will be deployed to integrate with + a user-provided monitoring solution (at present, only support + for Prometheus is available). + properties: + cassandra: + properties: + endpoint: + properties: + address: + type: string + port: + type: string + type: object + filters: + items: + description: 'RelabelConfig allows dynamic rewriting + of the label set, being applied to samples before + ingestion. It defines ``-section + of Prometheus configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' + properties: + action: + description: Action to perform based on regex matching. + Default is 'replace' + type: string + modulus: + description: Modulus to take of the hash of the + source label values. + format: int64 + type: integer + regex: + description: Regular expression against which the + extracted value is matched. Default is '(.*)' + type: string + replacement: + description: Replacement value against which a regex + replace is performed if the regular expression + matches. Regex capture groups are available. Default + is '$1' + type: string + separator: + description: Separator placed between concatenated + source label values. default is ';'. + type: string + sourceLabels: + description: The source labels select values from + existing labels. Their content is concatenated + using the configured separator and matched against + the configured regular expression for the replace, + keep, and drop actions. + items: + type: string + type: array + targetLabel: + description: Label to which the resulting value + is written in a replace action. It is mandatory + for replace actions. Regex capture groups are + available. + type: string + type: object + type: array + type: object + inline: + properties: + prometheus: + properties: + commonLabels: + additionalProperties: + type: string + description: CommonLabels are applied to all serviceMonitors + created. + type: object + enabled: + description: Enable the creation of Prometheus serviceMonitors + for this resource (Cassandra or Stargate). + type: boolean + type: object + vector: + properties: + components: + properties: + sinks: + description: Sinks is the list of sinks to use + for the Vector agent. + items: + properties: + config: + description: Config is the configuration + for the sink. + type: string + inputs: + description: Inputs is the list of inputs + for the transform. + items: + type: string + type: array + name: + description: Name is the name of the sink. + type: string + type: + description: Type is the type of the sink. + type: string + required: + - name + - type + type: object + type: array + sources: + description: Sources is the list of sources to + use for the Vector agent. + items: + properties: + config: + description: Config is the configuration + for the source. + type: string + name: + description: Name is the name of the source. + type: string + type: + description: Type is the type of the source. + type: string + required: + - name + - type + type: object + type: array + transforms: + description: Transforms is the list of transforms + to use for the Vector agent. + items: + properties: + config: + description: Config is the configuration + for the transform. + type: string + inputs: + description: Inputs is the list of inputs + for the transform. + items: + type: string + type: array + name: + description: Name is the name of the transform. + type: string + type: + description: Type is the type of the transform. + type: string + required: + - name + - type + type: object + type: array + type: object + enabled: + description: Enabled enables the Vector agent for + this resource (Cassandra, Reaper or Stargate). Enabling + the vector agent will inject a sidecar container + into the pod. + type: boolean + image: + description: Image is the name of the Vector image + to use. If not set, the default image will be used. + kube:default="timberio/vector:0.26.0-alpine" + type: string + resources: + description: Resources is the resource requirements + for the Vector agent. + properties: + 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. More info: + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + scrapeInterval: + description: ScrapeInterval is the interval at which + the Vector agent will scrape the metrics endpoint. + Use values like 30s, 1m, 5m. kube:default=30s + type: string + type: object type: object - type: object - superuserSecretRef: - description: The reference to the superuser secret to use for - Cassandra. If unspecified, a default secret will be generated - with a random password; the generated secret name will be "-superuser" - where is the K8ssandraCluster CRD name. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - telemetry: - description: Telemetry defines the desired state for telemetry - resources in this datacenter. If telemetry configurations are - defined, telemetry resources will be deployed to integrate with - a user-provided monitoring solution (at present, only support - for Prometheus is available). - properties: mcac: properties: enabled: @@ -16195,140 +16400,8 @@ spec: type: string type: array type: object - prometheus: - properties: - commonLabels: - additionalProperties: - type: string - description: CommonLabels are applied to all serviceMonitors - created. - type: object - enabled: - description: Enable the creation of Prometheus serviceMonitors - for this resource (Cassandra or Stargate). - type: boolean - type: object - vector: - properties: - components: - properties: - sinks: - description: Sinks is the list of sinks to use for - the Vector agent. - items: - properties: - config: - description: Config is the configuration for - the sink. - type: string - inputs: - description: Inputs is the list of inputs for - the transform. - items: - type: string - type: array - name: - description: Name is the name of the sink. - type: string - type: - description: Type is the type of the sink. - type: string - required: - - name - - type - type: object - type: array - sources: - description: Sources is the list of sources to use - for the Vector agent. - items: - properties: - config: - description: Config is the configuration for - the source. - type: string - name: - description: Name is the name of the source. - type: string - type: - description: Type is the type of the source. - type: string - required: - - name - - type - type: object - type: array - transforms: - description: Transforms is the list of transforms - to use for the Vector agent. - items: - properties: - config: - description: Config is the configuration for - the transform. - type: string - inputs: - description: Inputs is the list of inputs for - the transform. - items: - type: string - type: array - name: - description: Name is the name of the transform. - type: string - type: - description: Type is the type of the transform. - type: string - required: - - name - - type - type: object - type: array - type: object - enabled: - description: Enabled enables the Vector agent for this - resource (Cassandra, Reaper or Stargate). Enabling the - vector agent will inject a sidecar container into the - pod. - type: boolean - image: - description: Image is the name of the Vector image to - use. If not set, the default image will be used. kube:default="timberio/vector:0.26.0-alpine" - type: string - resources: - description: Resources is the resource requirements for - the Vector agent. - properties: - 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. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - scrapeInterval: - description: ScrapeInterval is the interval at which the - Vector agent will scrape the metrics endpoint. Use values - like 30s, 1m, 5m. kube:default=30s - type: string - type: object + required: + - inline type: object tolerations: description: Tolerations applied to every Cassandra pod. @@ -18787,34 +18860,6 @@ spec: to deploy targeting the Reaper pods for all DCs in this cluster (unless overriden by DC specific settings) properties: - mcac: - properties: - enabled: - description: enabled sets whether MCAC (legacy metrics - endpoint) is enabled. This is considered true by default. - type: boolean - metricFilters: - description: 'MetricFilters allows passing filters to - MCAC in order to reduce the amount of extracted metrics. - Not setting this field will result in the default filters - being used: - "deny:org.apache.cassandra.metrics.Table" - - "deny:org.apache.cassandra.metrics.table" - "allow:org.apache.cassandra.metrics.table.live_ss_table_count" - - "allow:org.apache.cassandra.metrics.Table.LiveSSTableCount" - - "allow:org.apache.cassandra.metrics.table.live_disk_space_used" - - "allow:org.apache.cassandra.metrics.table.LiveDiskSpaceUsed" - - "allow:org.apache.cassandra.metrics.Table.Pending" - - "allow:org.apache.cassandra.metrics.Table.Memtable" - - "allow:org.apache.cassandra.metrics.Table.Compaction" - - "allow:org.apache.cassandra.metrics.table.read" - - "allow:org.apache.cassandra.metrics.table.write" - "allow:org.apache.cassandra.metrics.table.range" - - "allow:org.apache.cassandra.metrics.table.coordinator" - - "allow:org.apache.cassandra.metrics.table.dropped_mutations" - Setting it to an empty list will result in all metrics - being extracted.' - items: - type: string - type: array - type: object prometheus: properties: commonLabels: @@ -20399,34 +20444,6 @@ spec: to deploy targeting the Stargate pods for all DCs in this cluster (unless overriden by DC specific settings) properties: - mcac: - properties: - enabled: - description: enabled sets whether MCAC (legacy metrics - endpoint) is enabled. This is considered true by default. - type: boolean - metricFilters: - description: 'MetricFilters allows passing filters to - MCAC in order to reduce the amount of extracted metrics. - Not setting this field will result in the default filters - being used: - "deny:org.apache.cassandra.metrics.Table" - - "deny:org.apache.cassandra.metrics.table" - "allow:org.apache.cassandra.metrics.table.live_ss_table_count" - - "allow:org.apache.cassandra.metrics.Table.LiveSSTableCount" - - "allow:org.apache.cassandra.metrics.table.live_disk_space_used" - - "allow:org.apache.cassandra.metrics.table.LiveDiskSpaceUsed" - - "allow:org.apache.cassandra.metrics.Table.Pending" - - "allow:org.apache.cassandra.metrics.Table.Memtable" - - "allow:org.apache.cassandra.metrics.Table.Compaction" - - "allow:org.apache.cassandra.metrics.table.read" - - "allow:org.apache.cassandra.metrics.table.write" - "allow:org.apache.cassandra.metrics.table.range" - - "allow:org.apache.cassandra.metrics.table.coordinator" - - "allow:org.apache.cassandra.metrics.table.dropped_mutations" - Setting it to an empty list will result in all metrics - being extracted.' - items: - type: string - type: array - type: object prometheus: properties: commonLabels: diff --git a/config/crd/bases/reaper.k8ssandra.io_reapers.yaml b/config/crd/bases/reaper.k8ssandra.io_reapers.yaml index 1fec57ce4..b3b700eb6 100644 --- a/config/crd/bases/reaper.k8ssandra.io_reapers.yaml +++ b/config/crd/bases/reaper.k8ssandra.io_reapers.yaml @@ -2078,32 +2078,6 @@ spec: to deploy targeting the Reaper pods for all DCs in this cluster (unless overriden by DC specific settings) properties: - mcac: - properties: - enabled: - description: enabled sets whether MCAC (legacy metrics endpoint) - is enabled. This is considered true by default. - type: boolean - metricFilters: - description: 'MetricFilters allows passing filters to MCAC - in order to reduce the amount of extracted metrics. Not - setting this field will result in the default filters being - used: - "deny:org.apache.cassandra.metrics.Table" - "deny:org.apache.cassandra.metrics.table" - - "allow:org.apache.cassandra.metrics.table.live_ss_table_count" - - "allow:org.apache.cassandra.metrics.Table.LiveSSTableCount" - - "allow:org.apache.cassandra.metrics.table.live_disk_space_used" - - "allow:org.apache.cassandra.metrics.table.LiveDiskSpaceUsed" - - "allow:org.apache.cassandra.metrics.Table.Pending" - "allow:org.apache.cassandra.metrics.Table.Memtable" - - "allow:org.apache.cassandra.metrics.Table.Compaction" - - "allow:org.apache.cassandra.metrics.table.read" - "allow:org.apache.cassandra.metrics.table.write" - - "allow:org.apache.cassandra.metrics.table.range" - "allow:org.apache.cassandra.metrics.table.coordinator" - - "allow:org.apache.cassandra.metrics.table.dropped_mutations" - Setting it to an empty list will result in all metrics being - extracted.' - items: - type: string - type: array - type: object prometheus: properties: commonLabels: diff --git a/config/crd/bases/stargate.k8ssandra.io_stargates.yaml b/config/crd/bases/stargate.k8ssandra.io_stargates.yaml index eeb8b4530..78a84da4d 100644 --- a/config/crd/bases/stargate.k8ssandra.io_stargates.yaml +++ b/config/crd/bases/stargate.k8ssandra.io_stargates.yaml @@ -2766,35 +2766,6 @@ spec: to deploy targeting the Stargate pods for all DCs in this cluster (unless overriden by DC specific settings) properties: - mcac: - properties: - enabled: - description: enabled sets whether MCAC (legacy metrics - endpoint) is enabled. This is considered true by default. - type: boolean - metricFilters: - description: 'MetricFilters allows passing filters to - MCAC in order to reduce the amount of extracted metrics. - Not setting this field will result in the default - filters being used: - "deny:org.apache.cassandra.metrics.Table" - - "deny:org.apache.cassandra.metrics.table" - "allow:org.apache.cassandra.metrics.table.live_ss_table_count" - - "allow:org.apache.cassandra.metrics.Table.LiveSSTableCount" - - "allow:org.apache.cassandra.metrics.table.live_disk_space_used" - - "allow:org.apache.cassandra.metrics.table.LiveDiskSpaceUsed" - - "allow:org.apache.cassandra.metrics.Table.Pending" - - "allow:org.apache.cassandra.metrics.Table.Memtable" - - "allow:org.apache.cassandra.metrics.Table.Compaction" - - "allow:org.apache.cassandra.metrics.table.read" - - "allow:org.apache.cassandra.metrics.table.write" - - "allow:org.apache.cassandra.metrics.table.range" - - "allow:org.apache.cassandra.metrics.table.coordinator" - - "allow:org.apache.cassandra.metrics.table.dropped_mutations" - Setting it to an empty list will result in all metrics - being extracted.' - items: - type: string - type: array - type: object prometheus: properties: commonLabels: @@ -3176,32 +3147,6 @@ spec: to deploy targeting the Stargate pods for all DCs in this cluster (unless overriden by DC specific settings) properties: - mcac: - properties: - enabled: - description: enabled sets whether MCAC (legacy metrics endpoint) - is enabled. This is considered true by default. - type: boolean - metricFilters: - description: 'MetricFilters allows passing filters to MCAC - in order to reduce the amount of extracted metrics. Not - setting this field will result in the default filters being - used: - "deny:org.apache.cassandra.metrics.Table" - "deny:org.apache.cassandra.metrics.table" - - "allow:org.apache.cassandra.metrics.table.live_ss_table_count" - - "allow:org.apache.cassandra.metrics.Table.LiveSSTableCount" - - "allow:org.apache.cassandra.metrics.table.live_disk_space_used" - - "allow:org.apache.cassandra.metrics.table.LiveDiskSpaceUsed" - - "allow:org.apache.cassandra.metrics.Table.Pending" - "allow:org.apache.cassandra.metrics.Table.Memtable" - - "allow:org.apache.cassandra.metrics.Table.Compaction" - - "allow:org.apache.cassandra.metrics.table.read" - "allow:org.apache.cassandra.metrics.table.write" - - "allow:org.apache.cassandra.metrics.table.range" - "allow:org.apache.cassandra.metrics.table.coordinator" - - "allow:org.apache.cassandra.metrics.table.dropped_mutations" - Setting it to an empty list will result in all metrics being - extracted.' - items: - type: string - type: array - type: object prometheus: properties: commonLabels: diff --git a/controllers/k8ssandra/cassandra_metrics_agent_test.go b/controllers/k8ssandra/cassandra_metrics_agent_test.go new file mode 100644 index 000000000..d8f271aa3 --- /dev/null +++ b/controllers/k8ssandra/cassandra_metrics_agent_test.go @@ -0,0 +1,117 @@ +package k8ssandra + +import ( + "context" + "testing" + + "github.com/k8ssandra/k8ssandra-operator/pkg/cassandra" + "github.com/stretchr/testify/assert" + + cassdcapi "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" + api "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1" + telemetryapi "github.com/k8ssandra/k8ssandra-operator/apis/telemetry/v1alpha1" + "github.com/k8ssandra/k8ssandra-operator/test/framework" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// createSingleDcCluster verifies that the CassandraDatacenter is created and that the +// expected status updates happen on the K8ssandraCluster. +func createSingleDcClusterWithMetricsAgent(t *testing.T, ctx context.Context, f *framework.Framework, namespace string) { + require := require.New(t) + + kc := &api.K8ssandraCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "test", + }, + Spec: api.K8ssandraClusterSpec{ + Cassandra: &api.CassandraClusterTemplate{ + DatacenterOptions: api.DatacenterOptions{ + Telemetry: &telemetryapi.CassandraTelemetrySpec{ + TelemetrySpec: &telemetryapi.TelemetrySpec{ + Vector: &telemetryapi.VectorSpec{ + Enabled: pointer.Bool(true), + }, + }, + }, + }, + Datacenters: []api.CassandraDatacenterTemplate{ + { + Meta: api.EmbeddedObjectMeta{ + Name: "dc1", + }, + K8sContext: f.DataPlaneContexts[0], + Size: 1, + DatacenterOptions: api.DatacenterOptions{ + ServerVersion: "3.11.10", + StorageConfig: &cassdcapi.StorageConfig{ + CassandraDataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{ + StorageClassName: &defaultStorageClass, + }, + }, + PodSecurityContext: &corev1.PodSecurityContext{ + RunAsUser: pointer.Int64(999), + }, + ManagementApiAuth: &cassdcapi.ManagementApiAuthConfig{ + Insecure: &cassdcapi.ManagementApiAuthInsecureConfig{}, + }, + }, + }, + }, + }, + }, + } + + err := f.Client.Create(ctx, kc) + require.NoError(err, "failed to create K8ssandraCluster") + + verifyFinalizerAdded(ctx, t, f, client.ObjectKey{Namespace: kc.Namespace, Name: kc.Name}) + + verifySuperuserSecretCreated(ctx, t, f, kc) + + verifyReplicatedSecretReconciled(ctx, t, f, kc) + + verifySystemReplicationAnnotationSet(ctx, t, f, kc) + + t.Log("check that the datacenter was created") + dcKey := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}, K8sContext: f.DataPlaneContexts[0]} + require.Eventually(f.DatacenterExists(ctx, dcKey), timeout, interval) + // Check that we have the right volumes and volume mounts. + dc := &cassdcapi.CassandraDatacenter{} + f.Get(ctx, dcKey, dc) + if err := f.Get(ctx, dcKey, dc); err != nil { + require.Fail("could not find dc") + } + _, found := cassandra.FindVolume(dc.Spec.PodTemplateSpec, "metrics-agent-config") + if !found { + require.Fail("could not find expected metrics-agent-config volume") + } + cassContainerIdx, _ := cassandra.FindContainer(dc.Spec.PodTemplateSpec, "cassandra") + volMount := cassandra.FindVolumeMount(&dc.Spec.PodTemplateSpec.Spec.Containers[cassContainerIdx], "metrics-agent-config") + if volMount == nil { + require.Fail("could not find expected metrics-agent-config volumeMount") + } + + // check that we have the right ConfigMap + agentCmKey := framework.ClusterKey{NamespacedName: types.NamespacedName{Name: "test-dc1" + "-metrics-agent-config", Namespace: namespace}, K8sContext: f.DataPlaneContexts[0]} + agentCm := corev1.ConfigMap{} + if err := f.Get(ctx, agentCmKey, &agentCm); err != nil { + assert.Fail(t, "could not find expected metrics-agent-config configmap") + } + + // Test cluster deletion, ensuring configmap deleted too. + t.Log("deleting K8ssandraCluster") + err = f.DeleteK8ssandraCluster(ctx, client.ObjectKey{Namespace: namespace, Name: kc.Name}, timeout, interval) + require.NoError(err, "failed to delete K8ssandraCluster") + f.AssertObjectDoesNotExist(ctx, t, dcKey, &cassdcapi.CassandraDatacenter{}, timeout, interval) + f.AssertObjectDoesNotExist(ctx, t, + agentCmKey, + &corev1.ConfigMap{}, + timeout, + interval) +} diff --git a/controllers/k8ssandra/cassandra_telemetry_reconciler.go b/controllers/k8ssandra/cassandra_telemetry_reconciler.go index aef72ad09..f4c403123 100644 --- a/controllers/k8ssandra/cassandra_telemetry_reconciler.go +++ b/controllers/k8ssandra/cassandra_telemetry_reconciler.go @@ -8,7 +8,6 @@ import ( "github.com/go-logr/logr" cassdcapi "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" - api "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1" k8ssandraapi "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1" "github.com/k8ssandra/k8ssandra-operator/pkg/result" "github.com/k8ssandra/k8ssandra-operator/pkg/telemetry" @@ -17,8 +16,8 @@ import ( func (r *K8ssandraClusterReconciler) reconcileCassandraDCTelemetry( ctx context.Context, - kc *api.K8ssandraCluster, - dcTemplate api.CassandraDatacenterTemplate, + kc *k8ssandraapi.K8ssandraCluster, + dcTemplate k8ssandraapi.CassandraDatacenterTemplate, actualDc *cassdcapi.CassandraDatacenter, logger logr.Logger, remoteClient client.Client, @@ -48,7 +47,12 @@ func (r *K8ssandraClusterReconciler) reconcileCassandraDCTelemetry( if err != nil { return result.Error(err) } - validConfig := telemetry.SpecIsValid(mergedSpec, promInstalled) + validConfig := false + if mergedSpec != nil && mergedSpec.TelemetrySpec != nil { + validConfig = telemetry.SpecIsValid(mergedSpec.TelemetrySpec, promInstalled) + } else { + validConfig = true + } if !validConfig { return result.Error(errors.New("telemetry spec was invalid for this cluster - is Prometheus installed if you have requested it")) } @@ -57,7 +61,7 @@ func (r *K8ssandraClusterReconciler) reconcileCassandraDCTelemetry( return result.Continue() } // Determine if we want a cleanup or a resource update. - if mergedSpec.IsPrometheusEnabled() { + if mergedSpec != nil && mergedSpec.TelemetrySpec != nil && mergedSpec.IsPrometheusEnabled() { logger.Info("Prometheus config found", "mergedSpec", mergedSpec) desiredSM, err := cfg.NewCassServiceMonitor(mergedSpec.IsMcacEnabled()) if err != nil { @@ -72,6 +76,7 @@ func (r *K8ssandraClusterReconciler) reconcileCassandraDCTelemetry( return result.Error(err) } } + return result.Continue() } diff --git a/controllers/k8ssandra/cassandra_telemetry_reconciler_test.go b/controllers/k8ssandra/cassandra_telemetry_reconciler_test.go index abb3404a7..988ff5ed0 100644 --- a/controllers/k8ssandra/cassandra_telemetry_reconciler_test.go +++ b/controllers/k8ssandra/cassandra_telemetry_reconciler_test.go @@ -51,10 +51,12 @@ func Test_reconcileCassandraDCTelemetry_TracksNamespaces(t *testing.T) { Name: cassDC.Name, }, DatacenterOptions: k8ssandraapi.DatacenterOptions{ - Telemetry: &telemetryapi.TelemetrySpec{ - Prometheus: &telemetryapi.PrometheusTelemetrySpec{ - Enabled: pointer.Bool(true), - CommonLabels: map[string]string{"test-label": "test"}, + Telemetry: &telemetryapi.CassandraTelemetrySpec{ + TelemetrySpec: &telemetryapi.TelemetrySpec{ + Prometheus: &telemetryapi.PrometheusTelemetrySpec{ + Enabled: pointer.Bool(true), + CommonLabels: map[string]string{"test-label": "test"}, + }, }, }, }, diff --git a/controllers/k8ssandra/datacenters.go b/controllers/k8ssandra/datacenters.go index ea36703e5..a46286520 100644 --- a/controllers/k8ssandra/datacenters.go +++ b/controllers/k8ssandra/datacenters.go @@ -11,10 +11,12 @@ import ( cassdcapi "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" cassctlapi "github.com/k8ssandra/cass-operator/apis/control/v1alpha1" api "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1" + telemetryapi "github.com/k8ssandra/k8ssandra-operator/apis/telemetry/v1alpha1" "github.com/k8ssandra/k8ssandra-operator/pkg/annotations" "github.com/k8ssandra/k8ssandra-operator/pkg/cassandra" "github.com/k8ssandra/k8ssandra-operator/pkg/result" "github.com/k8ssandra/k8ssandra-operator/pkg/secret" + agent "github.com/k8ssandra/k8ssandra-operator/pkg/telemetry/cassandra_agent" "github.com/k8ssandra/k8ssandra-operator/pkg/utils" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -97,6 +99,28 @@ func (r *K8ssandraClusterReconciler) reconcileDatacenters(ctx context.Context, k desiredDc.Annotations[cassdcapi.SkipUserCreationAnnotation] = "true" } + mergedTelemetrySpec := kc.Spec.Cassandra.Datacenters[idx].Telemetry.MergeWith(kc.Spec.Cassandra.Telemetry) + if mergedTelemetrySpec == nil { + mergedTelemetrySpec = &telemetryapi.CassandraTelemetrySpec{} + } + agentCfg := agent.Configurator{ + TelemetrySpec: *mergedTelemetrySpec, + RemoteClient: remoteClient, + Ctx: ctx, + Kluster: kc, + RequeueDelay: r.DefaultDelay, + DcNamespace: desiredDc.Namespace, + DcName: desiredDc.Name, + } + agentRes := agentCfg.ReconcileTelemetryAgentConfig(desiredDc) + if agentRes.IsRequeue() { + return result.RequeueSoon(r.DefaultDelay), actualDcs + } + + if agentRes.IsError() { + return agentRes, actualDcs + } + // Note: desiredDc should not be modified from now on annotations.AddHashAnnotation(desiredDc) diff --git a/controllers/k8ssandra/k8ssandracluster_controller_test.go b/controllers/k8ssandra/k8ssandracluster_controller_test.go index 0549a58c4..c24adc688 100644 --- a/controllers/k8ssandra/k8ssandracluster_controller_test.go +++ b/controllers/k8ssandra/k8ssandracluster_controller_test.go @@ -115,6 +115,7 @@ func TestK8ssandraCluster(t *testing.T) { t.Run("CreateMultiDcDseCluster", testEnv.ControllerTest(ctx, createMultiDcDseCluster)) t.Run("PerNodeConfiguration", testEnv.ControllerTest(ctx, perNodeConfiguration)) t.Run("CreateSingleDcClusterWithVector", testEnv.ControllerTest(ctx, createSingleDcClusterWithVector)) + t.Run("createSingleDcClusterWithMetricsAgent", testEnv.ControllerTest(ctx, createSingleDcClusterWithMetricsAgent)) } // createSingleDcCluster verifies that the CassandraDatacenter is created and that the @@ -268,9 +269,11 @@ func createSingleDcCluster(t *testing.T, ctx context.Context, f *framework.Frame // Test that prometheus servicemonitor comes up when it is requested in the CassandraDatacenter. kcPatch := client.MergeFrom(kc.DeepCopy()) - kc.Spec.Cassandra.Datacenters[0].DatacenterOptions.Telemetry = &telemetryapi.TelemetrySpec{ - Prometheus: &telemetryapi.PrometheusTelemetrySpec{ - Enabled: pointer.Bool(true), + kc.Spec.Cassandra.Datacenters[0].DatacenterOptions.Telemetry = &telemetryapi.CassandraTelemetrySpec{ + TelemetrySpec: &telemetryapi.TelemetrySpec{ + Prometheus: &telemetryapi.PrometheusTelemetrySpec{ + Enabled: pointer.Bool(true), + }, }, } if err := f.Patch(ctx, kc, kcPatch, kcKey); err != nil { diff --git a/controllers/k8ssandra/vector.go b/controllers/k8ssandra/vector.go index ee3283edb..bf8f886dd 100644 --- a/controllers/k8ssandra/vector.go +++ b/controllers/k8ssandra/vector.go @@ -29,9 +29,9 @@ func (r *K8ssandraClusterReconciler) reconcileVector( Namespace: namespace, Name: telemetry.VectorAgentConfigMapName(kc.SanitizedName(), dcConfig.Meta.Name), } - if kc.Spec.Cassandra.Telemetry.IsVectorEnabled() { + if kc.Spec.Cassandra.Telemetry != nil && kc.Spec.Cassandra.Telemetry.TelemetrySpec != nil && kc.Spec.Cassandra.Telemetry.IsVectorEnabled() { // Create the vector toml config content - toml, err := telemetry.CreateCassandraVectorToml(kc.Spec.Cassandra.Telemetry, dcConfig.McacEnabled) + toml, err := telemetry.CreateCassandraVectorToml(kc.Spec.Cassandra.Telemetry.TelemetrySpec, dcConfig.McacEnabled) if err != nil { return result.Error(err) } diff --git a/controllers/k8ssandra/vector_test.go b/controllers/k8ssandra/vector_test.go index 138eae1d6..e1bd405ec 100644 --- a/controllers/k8ssandra/vector_test.go +++ b/controllers/k8ssandra/vector_test.go @@ -33,9 +33,11 @@ func createSingleDcClusterWithVector(t *testing.T, ctx context.Context, f *frame Spec: api.K8ssandraClusterSpec{ Cassandra: &api.CassandraClusterTemplate{ DatacenterOptions: api.DatacenterOptions{ - Telemetry: &telemetryapi.TelemetrySpec{ - Vector: &telemetryapi.VectorSpec{ - Enabled: pointer.Bool(true), + Telemetry: &telemetryapi.CassandraTelemetrySpec{ + TelemetrySpec: &telemetryapi.TelemetrySpec{ + Vector: &telemetryapi.VectorSpec{ + Enabled: pointer.Bool(true), + }, }, }, }, @@ -139,9 +141,11 @@ func createSingleDcClusterWithVector(t *testing.T, ctx context.Context, f *frame // Test that prometheus servicemonitor comes up when it is requested in the CassandraDatacenter. kcPatch := client.MergeFrom(kc.DeepCopy()) - kc.Spec.Cassandra.Datacenters[0].DatacenterOptions.Telemetry = &telemetryapi.TelemetrySpec{ - Prometheus: &telemetryapi.PrometheusTelemetrySpec{ - Enabled: pointer.Bool(true), + kc.Spec.Cassandra.Datacenters[0].DatacenterOptions.Telemetry = &telemetryapi.CassandraTelemetrySpec{ + TelemetrySpec: &telemetryapi.TelemetrySpec{ + Prometheus: &telemetryapi.PrometheusTelemetrySpec{ + Enabled: pointer.Bool(true), + }, }, } if err := f.Patch(ctx, kc, kcPatch, kcKey); err != nil { diff --git a/pkg/cassandra/datacenter_test.go b/pkg/cassandra/datacenter_test.go index dd7bf1d5c..59fed0aeb 100644 --- a/pkg/cassandra/datacenter_test.go +++ b/pkg/cassandra/datacenter_test.go @@ -355,7 +355,7 @@ func TestCoalesce(t *testing.T) { Size: 3, DatacenterOptions: api.DatacenterOptions{ MgmtAPIHeap: &mgmtAPIHeap, - Telemetry: &v1alpha1.TelemetrySpec{ + Telemetry: &v1alpha1.CassandraTelemetrySpec{ Mcac: &v1alpha1.McacTelemetrySpec{ Enabled: pointer.Bool(false), }, diff --git a/pkg/result/result_helper.go b/pkg/result/result_helper.go index b7b1d1802..93e460aa3 100644 --- a/pkg/result/result_helper.go +++ b/pkg/result/result_helper.go @@ -1,8 +1,9 @@ package result import ( - ctrl "sigs.k8s.io/controller-runtime" "time" + + ctrl "sigs.k8s.io/controller-runtime" ) // Copyright DataStax, Inc. @@ -11,6 +12,9 @@ import ( type ReconcileResult interface { Completed() bool Output() (ctrl.Result, error) + IsError() bool + IsRequeue() bool + IsDone() bool } type continueReconcile struct{} @@ -22,6 +26,18 @@ func (c continueReconcile) Output() (ctrl.Result, error) { panic("there was no Result to return") } +func (continueReconcile) IsDone() bool { + return false +} + +func (continueReconcile) IsError() bool { + return false +} + +func (continueReconcile) IsRequeue() bool { + return false +} + type done struct{} func (d done) Completed() bool { @@ -31,6 +47,18 @@ func (d done) Output() (ctrl.Result, error) { return ctrl.Result{}, nil } +func (done) IsDone() bool { + return true +} + +func (done) IsError() bool { + return false +} + +func (done) IsRequeue() bool { + return false +} + type callBackSoon struct { after time.Duration } @@ -42,6 +70,18 @@ func (c callBackSoon) Output() (ctrl.Result, error) { return ctrl.Result{Requeue: true, RequeueAfter: c.after}, nil } +func (callBackSoon) IsDone() bool { + return false +} + +func (callBackSoon) IsError() bool { + return false +} + +func (callBackSoon) IsRequeue() bool { + return true +} + type errorOut struct { err error } @@ -53,6 +93,18 @@ func (e errorOut) Output() (ctrl.Result, error) { return ctrl.Result{}, e.err } +func (errorOut) IsDone() bool { + return false +} + +func (errorOut) IsError() bool { + return true +} + +func (errorOut) IsRequeue() bool { + return false +} + func Continue() ReconcileResult { return continueReconcile{} } diff --git a/pkg/telemetry/cassandra_agent/cassandra_agent_config.go b/pkg/telemetry/cassandra_agent/cassandra_agent_config.go new file mode 100644 index 000000000..8dc382b9c --- /dev/null +++ b/pkg/telemetry/cassandra_agent/cassandra_agent_config.go @@ -0,0 +1,155 @@ +package cassandra_agent + +import ( + "context" + "path/filepath" + "time" + + cassdcapi "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" + k8ssandraapi "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1" + telemetryapi "github.com/k8ssandra/k8ssandra-operator/apis/telemetry/v1alpha1" + "github.com/k8ssandra/k8ssandra-operator/pkg/annotations" + "github.com/k8ssandra/k8ssandra-operator/pkg/cassandra" + "github.com/k8ssandra/k8ssandra-operator/pkg/labels" + "github.com/k8ssandra/k8ssandra-operator/pkg/result" + 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" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" +) + +var ( + agentConfigLocation = "/opt/management-api/metric-collector.yaml" + defaultAgentConfig = telemetryapi.CassandraAgentSpec{ + Endpoint: telemetryapi.Endpoint{ + Port: "9000", + Address: "127.0.0.1", + }, + } +) + +type Configurator struct { + TelemetrySpec telemetryapi.CassandraTelemetrySpec + Kluster *k8ssandraapi.K8ssandraCluster + Ctx context.Context + RemoteClient client.Client + RequeueDelay time.Duration + DcNamespace string + DcName string +} + +func (c Configurator) GetTelemetryAgentConfigMap() (*corev1.ConfigMap, error) { + var yamlData []byte + var err error + if c.TelemetrySpec.Cassandra != nil { + yamlData, err = yaml.Marshal(&c.TelemetrySpec.Cassandra) + if err != nil { + return &corev1.ConfigMap{}, err + } + } else { + yamlData, err = yaml.Marshal(&defaultAgentConfig) + if err != nil { + return &corev1.ConfigMap{}, err + } + } + + cm := corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: c.DcNamespace, + Name: c.Kluster.Name + "-" + c.DcName + "-metrics-agent-config", + }, + Data: map[string]string{filepath.Base(agentConfigLocation): string(yamlData)}, + } + return &cm, nil +} + +func (c Configurator) ReconcileTelemetryAgentConfig(dc *cassdcapi.CassandraDatacenter) result.ReconcileResult { + //Reconcile the agent's ConfigMap + desiredCm, err := c.GetTelemetryAgentConfigMap() + if err != nil { + return result.Error(err) + } + cmObjectKey := types.NamespacedName{ + Name: c.Kluster.Name + "-" + c.DcName + "-metrics-agent-config", + Namespace: c.DcNamespace, + } + labels.SetManagedBy(desiredCm, cmObjectKey) + KlKey := types.NamespacedName{ + Name: c.Kluster.Name, + Namespace: c.Kluster.Namespace, + } + partOfLabels := labels.PartOfLabels(KlKey) + desiredCm.SetLabels(partOfLabels) + annotations.AddHashAnnotation(desiredCm) + + currentCm := &corev1.ConfigMap{} + + err = c.RemoteClient.Get(c.Ctx, cmObjectKey, currentCm) + + if err != nil { + if errors.IsNotFound(err) { + if err := c.RemoteClient.Create(c.Ctx, desiredCm); err != nil { + return result.Error(err) + } + return result.RequeueSoon(c.RequeueDelay) + } else { + return result.Error(err) + } + } + + if !annotations.CompareHashAnnotations(currentCm, desiredCm) { + resourceVersion := currentCm.GetResourceVersion() + desiredCm.DeepCopyInto(currentCm) + currentCm.SetResourceVersion(resourceVersion) + if err := c.RemoteClient.Update(c.Ctx, currentCm); err != nil { + return result.Error(err) + } + return result.RequeueSoon(c.RequeueDelay) + } + + c.AddStsVolumes(dc) + + return result.Done() +} + +func (c Configurator) AddStsVolumes(dc *cassdcapi.CassandraDatacenter) error { + if dc.Spec.PodTemplateSpec == nil { + dc.Spec.PodTemplateSpec = &corev1.PodTemplateSpec{} + } + _, found := cassandra.FindVolume(dc.Spec.PodTemplateSpec, "metrics-agent-config") + if !found { + v := corev1.Volume{ + Name: "metrics-agent-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + Items: []corev1.KeyToPath{ + { + Key: filepath.Base(agentConfigLocation), + Path: filepath.Base(agentConfigLocation), + }, + }, + LocalObjectReference: corev1.LocalObjectReference{ + Name: c.Kluster.Name + "-" + c.DcName + "-metrics-agent-config", + }, + }, + }, + } + // We don't check that the volume mount exists before appending because we assume that the existence of the volume + // is a sufficient signal that reconciliation has run. + dc.Spec.PodTemplateSpec.Spec.Volumes = append(dc.Spec.PodTemplateSpec.Spec.Volumes, v) + cassandra.UpdateCassandraContainer( + dc.Spec.PodTemplateSpec, + func(c *corev1.Container) { + vm := corev1.VolumeMount{ + Name: "metrics-agent-config", + MountPath: agentConfigLocation, + SubPath: filepath.Base(agentConfigLocation), + } + c.VolumeMounts = append(c.VolumeMounts, vm) + }) + + } + return nil +} diff --git a/pkg/telemetry/cassandra_agent/cassandra_agent_config_test.go b/pkg/telemetry/cassandra_agent/cassandra_agent_config_test.go new file mode 100644 index 000000000..82ec7eeb4 --- /dev/null +++ b/pkg/telemetry/cassandra_agent/cassandra_agent_config_test.go @@ -0,0 +1,183 @@ +package cassandra_agent + +import ( + "context" + "path/filepath" + "testing" + "time" + + k8ssandraapi "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1" + telemetryapi "github.com/k8ssandra/k8ssandra-operator/apis/telemetry/v1alpha1" + "github.com/k8ssandra/k8ssandra-operator/pkg/cassandra" + telemetry "github.com/k8ssandra/k8ssandra-operator/pkg/telemetry" + testutils "github.com/k8ssandra/k8ssandra-operator/pkg/test" + promapi "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var ( + testCluster k8ssandraapi.K8ssandraCluster = testutils.NewK8ssandraCluster("test-cluster", "test-namespace") + Cfg Configurator = Configurator{ + TelemetrySpec: telemetry.NewCassandraTelemetrySpec(), + Kluster: &testCluster, + Ctx: context.Background(), + RemoteClient: testutils.NewFakeClientWRestMapper(), + RequeueDelay: time.Second * 1, + DcNamespace: testCluster.Spec.Cassandra.Datacenters[0].Meta.Namespace, + DcName: testCluster.Spec.Cassandra.Datacenters[0].Meta.Name, + } + expectedYaml string = `endpoint: + address: 127.0.0.1 + port: "10000" +filters: +- action: drop + regex: (.*);(b.*) + separator: ; + sourceLabels: + - tag1 + - tag2 +` +) + +func getExpectedConfigMap() corev1.ConfigMap { + expectedCm := corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: Cfg.DcNamespace, + Name: Cfg.Kluster.Name + "-" + Cfg.DcName + "-metrics-agent-config", + }, + Data: map[string]string{filepath.Base(agentConfigLocation): expectedYaml}, + } + return expectedCm +} + +func getExampleTelemetrySpec() telemetryapi.CassandraTelemetrySpec { + tspec := &Cfg.TelemetrySpec + tspec.Cassandra.Filters = []promapi.RelabelConfig{ + { + SourceLabels: []string{"tag1", "tag2"}, + Separator: ";", + Regex: "(.*);(b.*)", + Action: "drop", + }, + } + tspec.Cassandra.Endpoint.Address = "127.0.0.1" + tspec.Cassandra.Endpoint.Port = "10000" + return *tspec +} + +func Test_GetTelemetryAgentConfigMap(t *testing.T) { + expectedCm := getExpectedConfigMap() + Cfg.RemoteClient = testutils.NewFakeClientWRestMapper() // Reset the Client + Cfg.TelemetrySpec = getExampleTelemetrySpec() + cm, err := Cfg.GetTelemetryAgentConfigMap() + assert.NoError(t, err) + assert.Equal(t, expectedCm.Data["metric-collector.yaml"], cm.Data["metric-collector.yaml"]) + assert.Equal(t, expectedCm.Name, cm.Name) + assert.Equal(t, expectedCm.Namespace, cm.Namespace) +} + +func Test_AddStsVolumes(t *testing.T) { + dc := testutils.NewCassandraDatacenter("test-dc", "test-namespace") + Cfg.RemoteClient = testutils.NewFakeClientWRestMapper() // Reset the Client + Cfg.AddStsVolumes(&dc) + expectedVol := corev1.Volume{ + Name: "metrics-agent-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + Items: []corev1.KeyToPath{ + { + Key: filepath.Base(agentConfigLocation), + Path: filepath.Base(agentConfigLocation), + }, + }, + LocalObjectReference: corev1.LocalObjectReference{ + Name: Cfg.Kluster.Name + "-" + Cfg.DcName + "-metrics-agent-config", + }, + }, + }, + } + assert.Contains(t, dc.Spec.PodTemplateSpec.Spec.Volumes, expectedVol) + cassContainer, found := cassandra.FindContainer(dc.Spec.PodTemplateSpec, "cassandra") + if !found { + assert.Fail(t, "no cassandra container found") + } + expectedVm := corev1.VolumeMount{ + Name: "metrics-agent-config", + MountPath: agentConfigLocation, + SubPath: filepath.Base(agentConfigLocation), + } + assert.Contains(t, dc.Spec.PodTemplateSpec.Spec.Containers[cassContainer].VolumeMounts, expectedVm) +} + +func Test_ReconcileTelemetryAgentConfig_CMCreateSuccess(t *testing.T) { + dc := testutils.NewCassandraDatacenter("test-dc", "test-namespace") + Cfg.RemoteClient = testutils.NewFakeClientWRestMapper() // Reset the Client + recRes := Cfg.ReconcileTelemetryAgentConfig(&dc) + assert.True(t, recRes.IsRequeue()) + actualCm := &corev1.ConfigMap{} + err := Cfg.RemoteClient.Get(Cfg.Ctx, types.NamespacedName{Name: Cfg.Kluster.Name + "-" + Cfg.DcName + "-metrics-agent-config", Namespace: Cfg.DcNamespace}, actualCm) + assert.NoError(t, err) +} +func Test_ReconcileTelemetryAgentConfig_CMCreateFailed(t *testing.T) { + dc := testutils.NewCassandraDatacenter("test-dc", "test-namespace") + Cfg.RemoteClient = testutils.NewCreateFailingFakeClient() // Reset the Client + recRes := Cfg.ReconcileTelemetryAgentConfig(&dc) + assert.True(t, recRes.IsError()) +} + +func Test_ReconcileTelemetryAgentConfig_CMUpdateSuccess(t *testing.T) { + dc := testutils.NewCassandraDatacenter("test-dc", "test-namespace") + Cfg.RemoteClient = testutils.NewFakeClientWRestMapper() // Reset the Client + // Create an initial ConfigMap with the same name. + initialCm, err := Cfg.GetTelemetryAgentConfigMap() + if err != nil { + assert.Fail(t, "couldn't create ConfigMap") + } + initialCm.Annotations = make(map[string]string) + initialCm.Annotations[k8ssandraapi.ResourceHashAnnotation] = "gobbledegook" + initialCm.Data = map[string]string{"gobbledegook": "gobbledegook"} + if err := Cfg.RemoteClient.Create(Cfg.Ctx, initialCm); err != nil { + assert.Fail(t, "could not create initial ConfigMap") + } + // Launch reconciliation. + Cfg.TelemetrySpec = getExampleTelemetrySpec() + recRes := Cfg.ReconcileTelemetryAgentConfig(&dc) + assert.True(t, recRes.IsRequeue()) + // After the update we should see the expected ConfigMap + afterUpdateCM := &corev1.ConfigMap{} + err = Cfg.RemoteClient.Get(Cfg.Ctx, + types.NamespacedName{ + Name: Cfg.Kluster.Name + "-" + Cfg.DcName + "-metrics-agent-config", + Namespace: Cfg.DcNamespace}, + afterUpdateCM) + assert.NoError(t, err) + + expectedCm := getExpectedConfigMap() + assert.NoError(t, err) + assert.Equal(t, expectedCm.Data["metric-collector.yaml"], afterUpdateCM.Data["metric-collector.yaml"]) + assert.Equal(t, expectedCm.Name, afterUpdateCM.Name) + assert.Equal(t, expectedCm.Namespace, afterUpdateCM.Namespace) +} + +func Test_ReconcileTelemetryAgentConfig_CMUpdateDone(t *testing.T) { + dc := testutils.NewCassandraDatacenter("test-dc", "test-namespace") + Cfg.RemoteClient = testutils.NewFakeClientWRestMapper() // Reset the Client + // Launch reconciliation. + recRes := Cfg.ReconcileTelemetryAgentConfig(&dc) + assert.True(t, recRes.IsRequeue()) + // After the update we should see the expected ConfigMap + afterUpdateCM := &corev1.ConfigMap{} + err := Cfg.RemoteClient.Get(Cfg.Ctx, + types.NamespacedName{ + Name: Cfg.Kluster.Name + "-" + Cfg.DcName + "-metrics-agent-config", + Namespace: Cfg.DcNamespace, + }, + afterUpdateCM) + assert.NoError(t, err) + // If we reconcile again, we should move into the Done state. + recRes = Cfg.ReconcileTelemetryAgentConfig(&dc) + assert.True(t, recRes.IsDone()) +} diff --git a/pkg/telemetry/cassandra_metrics_filters.go b/pkg/telemetry/cassandra_metrics_filters.go index 9b3e8f109..a475c114f 100644 --- a/pkg/telemetry/cassandra_metrics_filters.go +++ b/pkg/telemetry/cassandra_metrics_filters.go @@ -27,7 +27,7 @@ var ( // InjectCassandraTelemetryFilters adds MCAC filters to the cassandra container as an env variable. // If filter list is set to nil, the default filters are used, otherwise the provided filters are used. -func InjectCassandraTelemetryFilters(telemetrySpec *telemetry.TelemetrySpec, dcConfig *cassandra.DatacenterConfig) { +func InjectCassandraTelemetryFilters(telemetrySpec *telemetry.CassandraTelemetrySpec, dcConfig *cassandra.DatacenterConfig) { filtersEnvVar := v1.EnvVar{} if telemetrySpec == nil || telemetrySpec.Mcac == nil || telemetrySpec.Mcac.MetricFilters == nil { // Default filters are applied diff --git a/pkg/telemetry/cassandra_metrics_filters_test.go b/pkg/telemetry/cassandra_metrics_filters_test.go index d32082ca0..a8f27f7ab 100644 --- a/pkg/telemetry/cassandra_metrics_filters_test.go +++ b/pkg/telemetry/cassandra_metrics_filters_test.go @@ -1,10 +1,11 @@ package telemetry import ( - "k8s.io/utils/pointer" "strings" "testing" + "k8s.io/utils/pointer" + telemetry "github.com/k8ssandra/k8ssandra-operator/apis/telemetry/v1alpha1" "github.com/k8ssandra/k8ssandra-operator/pkg/cassandra" "github.com/stretchr/testify/assert" @@ -26,15 +27,17 @@ func Test_InjectCassandraTelemetryFilters(t *testing.T) { }, } - telemetrySpec := &telemetry.TelemetrySpec{ - Prometheus: &telemetry.PrometheusTelemetrySpec{ - Enabled: pointer.Bool(true), - }, + telemetrySpec := &telemetry.CassandraTelemetrySpec{ Mcac: &telemetry.McacTelemetrySpec{ MetricFilters: &[]string{ "deny:org.apache.cassandra.metrics.Table", "deny:org.apache.cassandra.metrics.table"}, }, + TelemetrySpec: &telemetry.TelemetrySpec{ + Prometheus: &telemetry.PrometheusTelemetrySpec{ + Enabled: pointer.Bool(true), + }, + }, } InjectCassandraTelemetryFilters(telemetrySpec, dcConfig) @@ -59,9 +62,11 @@ func Test_InjectCassandraTelemetryFiltersDefaults(t *testing.T) { }, } - telemetrySpec := &telemetry.TelemetrySpec{ - Prometheus: &telemetry.PrometheusTelemetrySpec{ - Enabled: pointer.Bool(true), + telemetrySpec := &telemetry.CassandraTelemetrySpec{ + TelemetrySpec: &telemetry.TelemetrySpec{ + Prometheus: &telemetry.PrometheusTelemetrySpec{ + Enabled: pointer.Bool(true), + }, }, } @@ -86,7 +91,7 @@ func Test_InjectCassandraTelemetryFilters_Empty(t *testing.T) { } // Test with an empty filters slice, which should result in an empty env variable to be injected - telemetrySpec := &telemetry.TelemetrySpec{ + telemetrySpec := &telemetry.CassandraTelemetrySpec{ Mcac: &telemetry.McacTelemetrySpec{ MetricFilters: &[]string{}, }, diff --git a/pkg/telemetry/test_objects.go b/pkg/telemetry/test_objects.go new file mode 100644 index 000000000..36298cfde --- /dev/null +++ b/pkg/telemetry/test_objects.go @@ -0,0 +1,17 @@ +package telemetry + +import ( + telemetryapi "github.com/k8ssandra/k8ssandra-operator/apis/telemetry/v1alpha1" +) + +func NewTelemetrySpec() telemetryapi.TelemetrySpec { + return telemetryapi.TelemetrySpec{} + +} + +func NewCassandraTelemetrySpec() telemetryapi.CassandraTelemetrySpec { + return telemetryapi.CassandraTelemetrySpec{ + Cassandra: &telemetryapi.CassandraAgentSpec{}, + } + +} diff --git a/pkg/telemetry/validation.go b/pkg/telemetry/validation.go index 88f98dee4..4f29280b4 100644 --- a/pkg/telemetry/validation.go +++ b/pkg/telemetry/validation.go @@ -2,11 +2,12 @@ package telemetry import ( "errors" + "reflect" + "github.com/go-logr/logr" telemetryapi "github.com/k8ssandra/k8ssandra-operator/apis/telemetry/v1alpha1" promapi "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "k8s.io/apimachinery/pkg/api/meta" - "reflect" "sigs.k8s.io/controller-runtime/pkg/client" ) diff --git a/pkg/telemetry/vector.go b/pkg/telemetry/vector.go index 9cea6eab2..f886885c5 100644 --- a/pkg/telemetry/vector.go +++ b/pkg/telemetry/vector.go @@ -17,8 +17,8 @@ import ( // InjectCassandraVectorAgent adds the Vector agent container to the Cassandra pods. // If the Vector agent is already present, it is not added again. -func InjectCassandraVectorAgent(telemetrySpec *telemetry.TelemetrySpec, dcConfig *cassandra.DatacenterConfig, k8cName string, logger logr.Logger) error { - if telemetrySpec.IsVectorEnabled() { +func InjectCassandraVectorAgent(telemetrySpec *telemetry.CassandraTelemetrySpec, dcConfig *cassandra.DatacenterConfig, k8cName string, logger logr.Logger) error { + if telemetrySpec != nil && telemetrySpec.TelemetrySpec != nil && telemetrySpec.IsVectorEnabled() { logger.Info("Injecting Vector agent into Cassandra pods") vectorImage := vector.DefaultVectorImage if telemetrySpec.Vector.Image != "" { @@ -38,7 +38,7 @@ func InjectCassandraVectorAgent(telemetrySpec *telemetry.TelemetrySpec, dcConfig VolumeMounts: []corev1.VolumeMount{ {Name: "vector-config", MountPath: "/etc/vector"}, }, - Resources: vector.VectorContainerResources(telemetrySpec), + Resources: vector.VectorContainerResources(telemetrySpec.TelemetrySpec), } logger.Info("Updating Vector agent in Cassandra pods") diff --git a/pkg/telemetry/vector_test.go b/pkg/telemetry/vector_test.go index 9da10f545..b6f164df7 100644 --- a/pkg/telemetry/vector_test.go +++ b/pkg/telemetry/vector_test.go @@ -18,7 +18,7 @@ import ( // InjectCassandraVectorAgent adds the Vector agent container to the Cassandra pods. // If the Vector agent is already present, it is not added again. func TestInjectCassandraVectorAgent(t *testing.T) { - telemetrySpec := &telemetry.TelemetrySpec{Vector: &telemetry.VectorSpec{Enabled: pointer.Bool(true)}} + telemetrySpec := &telemetry.CassandraTelemetrySpec{TelemetrySpec: &telemetry.TelemetrySpec{Vector: &telemetry.VectorSpec{Enabled: pointer.Bool(true)}}} dcConfig := &cassandra.DatacenterConfig{ PodTemplateSpec: corev1.PodTemplateSpec{}, } diff --git a/pkg/test/fakeclient.go b/pkg/test/fakeclient.go index 8bf3af29b..44d15fe52 100644 --- a/pkg/test/fakeclient.go +++ b/pkg/test/fakeclient.go @@ -1,6 +1,9 @@ package test import ( + "context" + "errors" + cassdcapi "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" k8ssandraapi "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1" reaperapi "github.com/k8ssandra/k8ssandra-operator/apis/reaper/v1alpha1" @@ -60,3 +63,17 @@ func NewFakeClientWRestMapper() client.Client { fakeClient, _ := NewFakeClient() return composedClient{fakeClient} } + +// CreateFailingFakeClient is a fake client. Calls to .Create on this client will fail after `createFailsAfter` invocations. +type CreateFailingFakeClient struct { + client.Client +} + +func (c CreateFailingFakeClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + return errors.New("artificial failure on create function") +} + +func NewCreateFailingFakeClient() client.Client { + fakeClient, _ := NewFakeClient() + return CreateFailingFakeClient{fakeClient} +} diff --git a/pkg/test/test_objects.go b/pkg/test/test_objects.go index 93211f1b3..8ddbe5e26 100644 --- a/pkg/test/test_objects.go +++ b/pkg/test/test_objects.go @@ -40,6 +40,14 @@ func NewK8ssandraCluster(name string, namespace string) k8ssandraapi.K8ssandraCl }, }, }, + Datacenters: []k8ssandraapi.CassandraDatacenterTemplate{ + { + Meta: k8ssandraapi.EmbeddedObjectMeta{ + Name: "dc1", + Namespace: "dc-namespace", + }, + }, + }, }, }, } diff --git a/test/kuttl/test-servicemonitors/04-assert.yaml b/test/kuttl/test-servicemonitors/04-assert.yaml new file mode 100644 index 000000000..c443afb72 --- /dev/null +++ b/test/kuttl/test-servicemonitors/04-assert.yaml @@ -0,0 +1,6 @@ +# Test for presence of expected config for cassandra metrics agent +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-dc1-metrics-agent-config + namespace: k8ssandra-operator