diff --git a/.vscode/launch.json b/.vscode/launch.json index 107e83d..a849f11 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,6 +15,22 @@ "--webhook-tls-directory=${workspaceFolder}/tmp/ssl", "--zap-log-level=debug" ] + }, + { + "name": "Launch test function", + "type": "go", + "request": "launch", + "mode": "test", + "program": "${workspaceFolder}/tests/e2e", +// "args": ["--ginkgo.focus-file", "e2e"], + "args": ["--ginkgo.focus", "service monitor and prometheus rule enabled"], +// "args": ["./...",], + "env": { + "E2E_ENABLED": "true" //, +// "E2E_HOSTNAME": "some address", + }, + "showLog": true } ] } + diff --git a/api/v1alpha1/types.go b/api/v1alpha1/types.go index 1f36504..f8c59ab 100644 --- a/api/v1alpha1/types.go +++ b/api/v1alpha1/types.go @@ -11,6 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + prometheusv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "github.com/sap/component-operator-runtime/pkg/component" componentoperatorruntimetypes "github.com/sap/component-operator-runtime/pkg/types" ) @@ -41,6 +42,25 @@ type SentinelProperties struct { type MetricsProperties struct { Enabled bool `json:"enabled,omitempty"` component.KubernetesContainerProperties `json:",inline"` + ServiceMonitor *MetricsServiceMonitorProperties `json:"monitor,omitempty"` + PrometheusRule *MetricsPrometheusRuleProperties `json:"prometheusRule,omitempty"` +} + +type MetricsServiceMonitorProperties struct { + Enabled bool `json:"enabled,omitempty"` + Interval prometheusv1.Duration `json:"interval,omitempty"` + ScrapeTimeout prometheusv1.Duration `json:"scrapeTimeout,omitempty"` + Relabellings []*prometheusv1.RelabelConfig `json:"relabellings,omitempty"` + MetricRelabellings []*prometheusv1.RelabelConfig `json:"metricRelabelings,omitempty"` + HonorLabels bool `json:"honorLabels,omitempty"` + AdditionalLabels map[string]string `json:"additionalLabels,omitempty"` + PodTargetLabels []string `json:"podTargetLabels,omitempty"` +} + +type MetricsPrometheusRuleProperties struct { + Enabled bool `json:"enabled,omitempty"` + AdditionalLabels map[string]string `json:"additionalLabels,omitempty"` + Rules []prometheusv1.Rule `json:"rules,omitempty"` } // TLSProperties models TLS settings of the redis services diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3e4caa4..61b8836 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -10,6 +10,7 @@ SPDX-License-Identifier: Apache-2.0 package v1alpha1 import ( + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -54,10 +55,49 @@ func (in *CertManagerProperties) DeepCopy() *CertManagerProperties { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MetricsPrometheusRuleProperties) DeepCopyInto(out *MetricsPrometheusRuleProperties) { + *out = *in + if in.AdditionalLabels != nil { + in, out := &in.AdditionalLabels, &out.AdditionalLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]monitoringv1.Rule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsPrometheusRuleProperties. +func (in *MetricsPrometheusRuleProperties) DeepCopy() *MetricsPrometheusRuleProperties { + if in == nil { + return nil + } + out := new(MetricsPrometheusRuleProperties) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MetricsProperties) DeepCopyInto(out *MetricsProperties) { *out = *in in.KubernetesContainerProperties.DeepCopyInto(&out.KubernetesContainerProperties) + if in.ServiceMonitor != nil { + in, out := &in.ServiceMonitor, &out.ServiceMonitor + *out = new(MetricsServiceMonitorProperties) + (*in).DeepCopyInto(*out) + } + if in.PrometheusRule != nil { + in, out := &in.PrometheusRule, &out.PrometheusRule + *out = new(MetricsPrometheusRuleProperties) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsProperties. @@ -70,6 +110,55 @@ func (in *MetricsProperties) DeepCopy() *MetricsProperties { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MetricsServiceMonitorProperties) DeepCopyInto(out *MetricsServiceMonitorProperties) { + *out = *in + if in.Relabellings != nil { + in, out := &in.Relabellings, &out.Relabellings + *out = make([]*monitoringv1.RelabelConfig, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(monitoringv1.RelabelConfig) + (*in).DeepCopyInto(*out) + } + } + } + if in.MetricRelabellings != nil { + in, out := &in.MetricRelabellings, &out.MetricRelabellings + *out = make([]*monitoringv1.RelabelConfig, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(monitoringv1.RelabelConfig) + (*in).DeepCopyInto(*out) + } + } + } + if in.AdditionalLabels != nil { + in, out := &in.AdditionalLabels, &out.AdditionalLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.PodTargetLabels != nil { + in, out := &in.PodTargetLabels, &out.PodTargetLabels + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsServiceMonitorProperties. +func (in *MetricsServiceMonitorProperties) DeepCopy() *MetricsServiceMonitorProperties { + if in == nil { + return nil + } + out := new(MetricsServiceMonitorProperties) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ObjectReference) DeepCopyInto(out *ObjectReference) { *out = *in diff --git a/crds/cache.cs.sap.com_redis.yaml b/crds/cache.cs.sap.com_redis.yaml index 5b41cec..ba78475 100644 --- a/crds/cache.cs.sap.com_redis.yaml +++ b/crds/cache.cs.sap.com_redis.yaml @@ -945,6 +945,280 @@ spec: properties: enabled: type: boolean + monitor: + properties: + additionalLabels: + additionalProperties: + type: string + type: object + enabled: + type: boolean + honorLabels: + type: boolean + interval: + description: |- + Duration is a valid time duration that can be parsed by Prometheus model.ParseDuration() function. + Supported units: y, w, d, h, m, s, ms + Examples: `30s`, `1m`, `1h20m15s`, `15d` + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + items: + description: |- + RelabelConfig allows dynamic rewriting of the label set for targets, alerts, + scraped samples and remote write samples. + + + More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config + properties: + action: + default: replace + description: |- + Action to perform based on the regex matching. + + + `Uppercase` and `Lowercase` actions require Prometheus >= v2.36.0. + `DropEqual` and `KeepEqual` actions require Prometheus >= v2.41.0. + + + Default: "Replace" + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + description: |- + Modulus to take of the hash of the source label values. + + + Only applicable when the action is `HashMod`. + format: int64 + type: integer + regex: + description: Regular expression against which the extracted + value is matched. + type: string + replacement: + description: |- + Replacement value against which a Replace action is performed if the + regular expression matches. + + + Regex capture groups are available. + type: string + separator: + description: Separator is the string between concatenated + SourceLabels. + 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. + items: + description: |- + LabelName is a valid Prometheus label name which may only contain ASCII + letters, numbers, as well as underscores. + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + description: |- + Label to which the resulting string is written in a replacement. + + + It is mandatory for `Replace`, `HashMod`, `Lowercase`, `Uppercase`, + `KeepEqual` and `DropEqual` actions. + + + Regex capture groups are available. + type: string + type: object + type: array + podTargetLabels: + items: + type: string + type: array + relabellings: + items: + description: |- + RelabelConfig allows dynamic rewriting of the label set for targets, alerts, + scraped samples and remote write samples. + + + More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config + properties: + action: + default: replace + description: |- + Action to perform based on the regex matching. + + + `Uppercase` and `Lowercase` actions require Prometheus >= v2.36.0. + `DropEqual` and `KeepEqual` actions require Prometheus >= v2.41.0. + + + Default: "Replace" + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + description: |- + Modulus to take of the hash of the source label values. + + + Only applicable when the action is `HashMod`. + format: int64 + type: integer + regex: + description: Regular expression against which the extracted + value is matched. + type: string + replacement: + description: |- + Replacement value against which a Replace action is performed if the + regular expression matches. + + + Regex capture groups are available. + type: string + separator: + description: Separator is the string between concatenated + SourceLabels. + 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. + items: + description: |- + LabelName is a valid Prometheus label name which may only contain ASCII + letters, numbers, as well as underscores. + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + description: |- + Label to which the resulting string is written in a replacement. + + + It is mandatory for `Replace`, `HashMod`, `Lowercase`, `Uppercase`, + `KeepEqual` and `DropEqual` actions. + + + Regex capture groups are available. + type: string + type: object + type: array + scrapeTimeout: + description: |- + Duration is a valid time duration that can be parsed by Prometheus model.ParseDuration() function. + Supported units: y, w, d, h, m, s, ms + Examples: `30s`, `1m`, `1h20m15s`, `15d` + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + type: object + prometheusRule: + properties: + additionalLabels: + additionalProperties: + type: string + type: object + enabled: + type: boolean + rules: + items: + description: |- + Rule describes an alerting or recording rule + See Prometheus documentation: [alerting](https://www.prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) or [recording](https://www.prometheus.io/docs/prometheus/latest/configuration/recording_rules/#recording-rules) rule + properties: + alert: + description: |- + Name of the alert. Must be a valid label value. + Only one of `record` and `alert` must be set. + type: string + annotations: + additionalProperties: + type: string + description: |- + Annotations to add to each alert. + Only valid for alerting rules. + type: object + expr: + anyOf: + - type: integer + - type: string + description: PromQL expression to evaluate. + x-kubernetes-int-or-string: true + for: + description: Alerts are considered firing once they + have been returned for this long. + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + keep_firing_for: + description: KeepFiringFor defines how long an alert + will continue firing after the condition that triggered + it has cleared. + minLength: 1 + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + labels: + additionalProperties: + type: string + description: Labels to add or overwrite. + type: object + record: + description: |- + Name of the time series to output to. Must be a valid metric name. + Only one of `record` and `alert` must be set. + type: string + required: + - expr + type: object + type: array + type: object resources: description: ResourceRequirements describes the compute resource requirements. diff --git a/go.mod b/go.mod index cb9e091..b030aa8 100644 --- a/go.mod +++ b/go.mod @@ -62,6 +62,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.73.0 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect github.com/prometheus/common v0.47.0 // indirect @@ -71,12 +72,12 @@ require ( github.com/spf13/pflag v1.0.5 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect - golang.org/x/net v0.21.0 // indirect + golang.org/x/net v0.22.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.18.0 // indirect @@ -89,7 +90,7 @@ require ( k8s.io/component-base v0.29.3 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240209001042-7a0d5b415232 // indirect - k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect + k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect sigs.k8s.io/cli-utils v0.35.0 // indirect sigs.k8s.io/gateway-api v1.0.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index 5c65d40..520d1d3 100644 --- a/go.sum +++ b/go.sum @@ -133,6 +133,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.73.0 h1:M7SQPR01PKW0vraTxrZvg0IKpVpTb3c0Rs0F753uTlI= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.73.0/go.mod h1:yJ3CawR/A5qEYFEeCOUVYLTwYxmacfHQhJS+b/2QiaM= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= @@ -194,6 +196,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -214,6 +218,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -231,11 +237,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -309,6 +319,8 @@ k8s.io/kube-openapi v0.0.0-20240209001042-7a0d5b415232 h1:MMq4iF9pHuAz/9dLnHwBQK k8s.io/kube-openapi v0.0.0-20240209001042-7a0d5b415232/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY= +k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/cli-utils v0.35.0 h1:dfSJaF1W0frW74PtjwiyoB4cwdRygbHnC7qe7HF0g/Y= sigs.k8s.io/cli-utils v0.35.0/go.mod h1:ITitykCJxP1vaj1Cew/FZEaVJ2YsTN9Q71m02jebkoE= sigs.k8s.io/controller-runtime v0.17.2 h1:FwHwD1CTUemg0pW2otk7/U5/i5m2ymzvOXdbeGOUvw0= diff --git a/pkg/operator/data/parameters.yaml b/pkg/operator/data/parameters.yaml index 660ea23..045bfdc 100644 --- a/pkg/operator/data/parameters.yaml +++ b/pkg/operator/data/parameters.yaml @@ -4,6 +4,8 @@ fullnameOverride: {{ $fullname }} {{- $sentinelEnabled := (dig "sentinel" "enabled" false .) }} {{- $metricsEnabled := (dig "metrics" "enabled" false .) }} +{{- $metricsServiceMonitorEnabled := (dig "metrics" "monitor" "enabled" false .) }} +{{- $metricsPrometheusRuleEnabled := (dig "metrics" "prometheusRule" "enabled" false .) }} {{- $tlsEnabled := (dig "tls" "enabled" false .) }} {{- $persistenceEnabled := (dig "persistence" "enabled" false .) }} @@ -175,6 +177,16 @@ metrics: containerSecurityContext: {{- toYaml . | nindent 4 }} {{- end }} + {{- if $metricsServiceMonitorEnabled }} + {{- with .metrics.monitor }} + serviceMonitor: + {{- toYaml . | nindent 4 }} + {{- end }}{{- end }} + {{- if $metricsPrometheusRuleEnabled }} + {{- with .metrics.prometheusRule }} + prometheusRule: + {{- toYaml . | nindent 4 }} + {{- end }}{{- end }} {{- end }} {{- if $tlsEnabled }} diff --git a/tests/e2e/operator_suite_test.go b/tests/e2e/operator_suite_test.go index 75cb495..8903a2e 100644 --- a/tests/e2e/operator_suite_test.go +++ b/tests/e2e/operator_suite_test.go @@ -33,6 +33,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/discovery" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -47,6 +48,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + prometheusv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "github.com/sap/component-operator-runtime/pkg/component" "github.com/sap/go-generics/slices" operatorv1alpha1 "github.com/sap/redis-operator/api/v1alpha1" @@ -133,6 +135,7 @@ var _ = BeforeSuite(func() { utilruntime.Must(apiextensionsv1.AddToScheme(scheme)) utilruntime.Must(apiregistrationv1.AddToScheme(scheme)) utilruntime.Must(certmanagerv1.AddToScheme(scheme)) + utilruntime.Must(prometheusv1.AddToScheme(scheme)) operator.InitScheme(scheme) By("initializing client") @@ -325,6 +328,77 @@ var _ = Describe("Deploy Redis", func() { createRedis(redis, true, "300s") doSomethingWithRedis(redis) }) + + It("should deploy Redis without sentinel, 1 node, with TLS disabled with metrics, service monitor and prometheus rule enabled", func() { + fmt.Printf("Test output") + var duration prometheusv1.Duration = "5m" + redis := &operatorv1alpha1.Redis{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + GenerateName: "test-", + }, + Spec: operatorv1alpha1.RedisSpec{ + Replicas: 1, + Sentinel: &operatorv1alpha1.SentinelProperties{ + Enabled: false, + }, + TLS: &operatorv1alpha1.TLSProperties{ + Enabled: false, + }, + Metrics: &operatorv1alpha1.MetricsProperties{ + Enabled: true, + ServiceMonitor: &operatorv1alpha1.MetricsServiceMonitorProperties{ + Enabled: true, + Interval: "30s", + ScrapeTimeout: "10s", + Relabellings: []*prometheusv1.RelabelConfig{ + {SourceLabels: []prometheusv1.LabelName{"__meta_kubernetes_namespace"}, TargetLabel: "namespace"}, + {SourceLabels: []prometheusv1.LabelName{"__meta_kubernetes_pod_name"}, TargetLabel: "pod"}, + }, + MetricRelabellings: []*prometheusv1.RelabelConfig{ + {SourceLabels: []prometheusv1.LabelName{"__name__"}, TargetLabel: "metric"}, + {SourceLabels: []prometheusv1.LabelName{"__meta_kubernetes_pod_name"}, TargetLabel: "pod"}, + }, + HonorLabels: true, + AdditionalLabels: map[string]string{ + "app": "redis", + }, + PodTargetLabels: []string{"app"}, + }, + PrometheusRule: &operatorv1alpha1.MetricsPrometheusRuleProperties{ + Enabled: true, + AdditionalLabels: map[string]string{ + "app": "redis", + }, + Rules: []prometheusv1.Rule{ + { + Record: "redis:metrics:exporter:scrape_duration_seconds:avg", + Expr: intstr.FromString("avg(redis:metrics:exporter:scrape_duration_seconds) by (namespace, pod)"), + }, + { + Alert: "RedisExporterDown", + Expr: intstr.FromString("up{job=\"redis-exporter\"} == 0"), + For: &duration, + Labels: map[string]string{ + "severity": "critical", + }, + Annotations: map[string]string{ + "summary": "Redis exporter is down", + }, + }, + }, + }, + }, + }, + } + defer deleteRedis(redis, true, "60s") + createRedis(redis, true, "300s") + doSomethingWithRedis(redis) + checkServiceForMetrics(redis) + checkServiceMonitor(redis) + checkPrometheusRule(redis) + }) + }) func createNamespace() string { @@ -531,3 +605,39 @@ func doSomethingWithRedis(redis *operatorv1alpha1.Redis) { } } } + +func checkServiceForMetrics(redis *operatorv1alpha1.Redis) { + service := &corev1.Service{} + serviceName := fmt.Sprintf("redis-%s-metrics", redis.Name) + err := cli.Get(ctx, types.NamespacedName{Namespace: redis.Namespace, Name: serviceName}, service) + Expect(err).NotTo(HaveOccurred()) +} + +func checkServiceMonitor(redis *operatorv1alpha1.Redis) { + serviceMonitor := &prometheusv1.ServiceMonitor{} + serviceMonitorName := fmt.Sprintf("redis-%s", redis.Name) + err := cli.Get(ctx, types.NamespacedName{Namespace: redis.Namespace, Name: serviceMonitorName}, serviceMonitor) + Expect(err).NotTo(HaveOccurred()) + + Expect(serviceMonitor.Spec.Endpoints[len(serviceMonitor.Spec.Endpoints)-1].Interval).To(Equal(redis.Spec.Metrics.ServiceMonitor.Interval)) + Expect(serviceMonitor.Spec.Endpoints[len(serviceMonitor.Spec.Endpoints)-1].ScrapeTimeout).To(Equal(redis.Spec.Metrics.ServiceMonitor.ScrapeTimeout)) + Expect(serviceMonitor.Spec.Endpoints[len(serviceMonitor.Spec.Endpoints)-1].RelabelConfigs).To(Equal(redis.Spec.Metrics.ServiceMonitor.Relabellings)) + Expect(serviceMonitor.Spec.Endpoints[len(serviceMonitor.Spec.Endpoints)-1].MetricRelabelConfigs).To(Equal(redis.Spec.Metrics.ServiceMonitor.MetricRelabellings)) + Expect(serviceMonitor.Spec.Endpoints[len(serviceMonitor.Spec.Endpoints)-1].HonorLabels).To(Equal(redis.Spec.Metrics.ServiceMonitor.HonorLabels)) + for k, v := range redis.Spec.Metrics.ServiceMonitor.AdditionalLabels { + Expect(serviceMonitor.ObjectMeta.Labels).Should(HaveKeyWithValue(k, v)) + } + Expect(serviceMonitor.Spec.PodTargetLabels).To(ContainElements(redis.Spec.Metrics.ServiceMonitor.PodTargetLabels)) +} + +func checkPrometheusRule(redis *operatorv1alpha1.Redis) { + prometheusRule := &prometheusv1.PrometheusRule{} + prometheusRuleName := fmt.Sprintf("redis-%s", redis.Name) + err := cli.Get(ctx, types.NamespacedName{Namespace: redis.Namespace, Name: prometheusRuleName}, prometheusRule) + Expect(err).NotTo(HaveOccurred()) + + for k, v := range redis.Spec.Metrics.PrometheusRule.AdditionalLabels { + Expect(prometheusRule.ObjectMeta.Labels).Should(HaveKeyWithValue(k, v)) + } + Expect(prometheusRule.Spec.Groups[0].Rules).To(Equal(redis.Spec.Metrics.PrometheusRule.Rules)) +} diff --git a/tests/e2e/util.go b/tests/e2e/util.go index 5ee7b09..7aa6ab1 100644 --- a/tests/e2e/util.go +++ b/tests/e2e/util.go @@ -37,7 +37,10 @@ import ( operatorv1alpha1 "github.com/sap/redis-operator/api/v1alpha1" ) -const CertManagerVersion = "v1.12.1" +const ( + CertManagerVersion = "v1.12.1" + PrometheusOperatorVersion = "v0.73.1" +) // run command and print stdout/stderr func run(ctx context.Context, name string, arg ...string) error { @@ -97,8 +100,20 @@ func createKindCluster(ctx context.Context, kind string, name string, kubeconfig if err != nil { return err } + err = downloadAndApplyManifests(ctx, cli, fmt.Sprintf("https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml", CertManagerVersion)) + if err != nil { + return err + } + err = downloadAndApplyManifests(ctx, cli, fmt.Sprintf("https://github.com/prometheus-operator/prometheus-operator/releases/download/%s/stripped-down-crds.yaml", PrometheusOperatorVersion)) + if err != nil { + return err + } - resp, err := http.Get(fmt.Sprintf("https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml", CertManagerVersion)) + return nil +} + +func downloadAndApplyManifests(ctx context.Context, cli client.Client, url string) error { + resp, err := http.Get(url) if err != nil { return err } @@ -107,11 +122,7 @@ func createKindCluster(ctx context.Context, kind string, name string, kubeconfig if err != nil { return err } - if err := applyManifests(ctx, cli, manifests); err != nil { - return err - } - - return nil + return applyManifests(ctx, cli, manifests) } // delete kind cluster diff --git a/tests/manual/redis.yaml b/tests/manual/redis.yaml new file mode 100644 index 0000000..f259e0a --- /dev/null +++ b/tests/manual/redis.yaml @@ -0,0 +1,76 @@ +apiVersion: cache.cs.sap.com/v1alpha1 +kind: Redis +metadata: + name: test +spec: + binding: + template: '{ credentials: { cluster_mode: false, tls: false, sentinel_nodes: [{ + hostname: {{ .sentinelHost }}, port: {{ .sentinelPort }} }], uri: redis://:{{ + .password }}@{{ .sentinelHost }}:{{ .sentinelPort }}#mymaster, password: {{ + .password }}, hostname: {{ .host }}, port: {{ .port }} }}' + metrics: + enabled: true + monitor: + enabled: true + interval: 30s + scrapeTimeout: 10s + relabellings: + - action: replace + sourceLabels: + - __meta_kubernetes_pod_node_name + targetLabel: node + - action: replace + sourceLabels: + - __meta_kubernetes_pod_name + targetLabel: pod + - action: replace + sourceLabels: + - __meta_kubernetes_namespace + targetLabel: namespace + - action: replace + sourceLabels: + - __meta_kubernetes_pod_container_name + targetLabel: container + metricRelabelings: + - action: replace + sourceLabels: + - __name__ + targetLabel: metric + honorLabels: true + additionalLabels: + label1: value1 + label2: value2 + podTargetLabels: + - "label1" + - "label2" + prometheusRule: + enabled: true + additionalLabels: + label1: value1 + label2: value2 + rules: + - alert: RedisDown + expr: redis_up{service="{{ template "common.names.fullname" . }}-metrics"} == 0 + for: 2m + labels: + severity: error + annotations: + summary: Redis® instance {{ "{{ $labels.instance }}" }} down + description: Redis® instance {{ "{{ $labels.instance }}" }} is down + - alert: RedisMemoryHigh + expr: > + redis_memory_used_bytes{service="{{ template "common.names.fullname" . }}-metrics"} * 100 + / + redis_memory_max_bytes{service="{{ template "common.names.fullname" . }}-metrics"} + > 90 + for: 2m + labels: + severity: error + annotations: + summary: Redis® instance {{ "{{ $labels.instance }}" }} is using too much memory + description: | + Redis® instance {{ "{{ $labels.instance }}" }} is using {{ "{{ $value }}" }}% of its available memory. + replicas: 3 + sentinel: + enabled: true + tls: {}