Skip to content

Commit a02dfb2

Browse files
committed
Initial changes for elastic search
1 parent 9d026b8 commit a02dfb2

File tree

6 files changed

+199
-2
lines changed

6 files changed

+199
-2
lines changed

metrics-operator/api/v1/keptnmetricsprovider_types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ import (
2626
// KeptnMetricsProviderSpec defines the desired state of KeptnMetricsProvider
2727
type KeptnMetricsProviderSpec struct {
2828
// +kubebuilder:validation:Optional
29-
// +kubebuilder:validation:Pattern:=cortex|datadog|dql|dynatrace|prometheus|thanos
30-
// Type represents the provider type. This can be one of cortex, datadog, dql, dynatrace, prometheus or thanos.
29+
// +kubebuilder:validation:Pattern:=cortex|datadog|dql|dynatrace|prometheus|thanos|elastic
30+
// Type represents the provider type. This can be one of cortex, datadog, dql, dynatrace, prometheus, elastic or thanos.
3131
Type string `json:"type"`
3232
// TargetServer defines URL (including port and protocol) at which the metrics provider is reachable.
3333
TargetServer string `json:"targetServer"`

metrics-operator/controllers/common/providers/common.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const PrometheusProviderType = "prometheus"
66
const ThanosProviderType = "thanos"
77
const CortexProviderType = "cortex"
88
const DataDogProviderType = "datadog"
9+
const ElasticProviderType = "elastic"
910

1011
var SupportedProviders = []string{
1112
DynatraceProviderType,
@@ -14,4 +15,5 @@ var SupportedProviders = []string{
1415
DataDogProviderType,
1516
CortexProviderType,
1617
ThanosProviderType,
18+
ElasticProviderType,
1719
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package elastic
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"encoding/json"
7+
"fmt"
8+
"net/http"
9+
"strings"
10+
"time"
11+
12+
elastic "github.com/elastic/go-elasticsearch/v8"
13+
"github.com/go-logr/logr"
14+
metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
)
17+
18+
const (
19+
warningLogStringElastic = "%s API returned warnings: %s"
20+
defaultTimeRange = 30 * time.Minute
21+
)
22+
23+
type KeptnElasticProvider struct {
24+
Log logr.Logger
25+
K8sClient client.Client
26+
Elastic *elastic.Client
27+
}
28+
29+
type ElasticsearchResponse struct {
30+
Hits struct {
31+
Total struct {
32+
Value int `json:"value"`
33+
} `json:"total"`
34+
} `json:"hits"`
35+
}
36+
37+
func NewElasticProvider(log logr.Logger, k8sClient client.Client, elasticURL string) (*KeptnElasticProvider, error) {
38+
log.Info("Initializing Elasticsearch client with TLS disabled...")
39+
40+
// Custom Transport to Skip TLS Verification
41+
transport := &http.Transport{
42+
TLSClientConfig: &tls.Config{
43+
InsecureSkipVerify: true,
44+
},
45+
}
46+
47+
// Elasticsearch Client Config
48+
cfg := elastic.Config{
49+
Addresses: []string{"https://quickstart-es-http:9200"},
50+
Username: "elastic",
51+
Password: "073wS1l5ct65LdktJ03M9oqa",
52+
Transport: transport,
53+
}
54+
55+
// Create Elasticsearch Client
56+
es, err := elastic.NewClient(cfg)
57+
if err != nil {
58+
log.Error(err, "Failed to create Elasticsearch client")
59+
return nil, fmt.Errorf("failed to create Elasticsearch client: %w", err)
60+
}
61+
62+
log.Info("Successfully initialized Elasticsearch client with TLS disabled")
63+
return &KeptnElasticProvider{
64+
Log: log,
65+
K8sClient: k8sClient,
66+
Elastic: es,
67+
}, nil
68+
}
69+
70+
func (r *KeptnElasticProvider) FetchAnalysisValue(ctx context.Context, query string, analysis metricsapi.Analysis, provider *metricsapi.KeptnMetricsProvider) (string, error) {
71+
r.Log.Info("Fetching analysis value from Elasticsearch", "query", query)
72+
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
73+
defer cancel()
74+
75+
result, err := r.runElasticQuery(ctx, query, analysis.GetFrom(), analysis.GetTo())
76+
if err != nil {
77+
r.Log.Error(err, "Failed to fetch analysis value")
78+
return "", err
79+
}
80+
81+
r.Log.Info("Elasticsearch query result", "result", result)
82+
return r.extractMetric(result)
83+
}
84+
85+
func (r *KeptnElasticProvider) EvaluateQuery(ctx context.Context, metric metricsapi.KeptnMetric, provider metricsapi.KeptnMetricsProvider) (string, []byte, error) {
86+
r.Log.Info("Evaluating query for KeptnMetric", "metric", metric.Name)
87+
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
88+
defer cancel()
89+
90+
timeRange := getTimeRangeFromSpec(metric.Spec.Range)
91+
92+
result, err := r.runElasticQuery(ctx, metric.Spec.Query, time.Now().Add(-timeRange), time.Now())
93+
if err != nil {
94+
r.Log.Error(err, "Failed to evaluate query")
95+
return "", nil, err
96+
}
97+
98+
metricValue, err := r.extractMetric(result)
99+
if err != nil {
100+
return "", nil, err
101+
}
102+
103+
r.Log.Info("Successfully evaluated metric", "value", metricValue)
104+
return metricValue, []byte{}, nil
105+
}
106+
107+
func (r *KeptnElasticProvider) EvaluateQueryForStep(ctx context.Context, metric metricsapi.KeptnMetric, provider metricsapi.KeptnMetricsProvider) ([]string, []byte, error) {
108+
r.Log.Info("EvaluateQueryForStep called but not implemented")
109+
return nil, nil, nil
110+
}
111+
112+
func getTimeRangeFromSpec(rangeSpec *metricsapi.RangeSpec) time.Duration {
113+
if rangeSpec == nil || rangeSpec.Interval == "" {
114+
return defaultTimeRange
115+
}
116+
duration, err := time.ParseDuration(rangeSpec.Interval)
117+
if err != nil {
118+
return defaultTimeRange
119+
}
120+
return duration
121+
}
122+
123+
func (r *KeptnElasticProvider) runElasticQuery(ctx context.Context, query string, from, to time.Time) (map[string]interface{}, error) {
124+
r.Log.Info("Running Elasticsearch query", "query", query, "from", from, "to", to)
125+
126+
queryBody := fmt.Sprintf(`
127+
{
128+
"query": {
129+
"bool": {
130+
"must": [
131+
%s,
132+
{
133+
"range": {
134+
"@timestamp": {
135+
"gte": "%s",
136+
"lte": "%s"
137+
}
138+
}
139+
}
140+
]
141+
}
142+
}
143+
}`, query, from.Format(time.RFC3339), to.Format(time.RFC3339))
144+
145+
res, err := r.Elastic.Search(
146+
r.Elastic.Search.WithContext(ctx),
147+
r.Elastic.Search.WithBody(strings.NewReader(queryBody)),
148+
)
149+
if err != nil {
150+
r.Log.Error(err, "Failed to execute Elasticsearch query")
151+
return nil, fmt.Errorf("failed to execute Elasticsearch query: %w", err)
152+
}
153+
defer res.Body.Close()
154+
155+
if warnings, ok := res.Header["Warning"]; ok {
156+
r.Log.Info(fmt.Sprintf(warningLogStringElastic, "Elasticsearch", warnings))
157+
}
158+
159+
var result map[string]interface{}
160+
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
161+
r.Log.Error(err, "Failed to parse Elasticsearch response")
162+
return nil, fmt.Errorf("failed to parse Elasticsearch response: %w", err)
163+
}
164+
165+
r.Log.Info("Successfully executed query", "result", result)
166+
return result, nil
167+
}
168+
169+
func (r *KeptnElasticProvider) extractMetric(result map[string]interface{}) (string, error) {
170+
r.Log.Info("Extracting metric from Elasticsearch response")
171+
var response ElasticsearchResponse
172+
jsonData, err := json.Marshal(result)
173+
if err != nil {
174+
r.Log.Error(err, "Failed to marshal Elasticsearch result")
175+
return "", fmt.Errorf("failed to marshal result: %w", err)
176+
}
177+
178+
if err := json.Unmarshal(jsonData, &response); err != nil {
179+
r.Log.Error(err, "Failed to unmarshal result into struct")
180+
return "", fmt.Errorf("failed to unmarshal result into struct: %w", err)
181+
}
182+
183+
value := fmt.Sprintf("%d", response.Hits.Total.Value)
184+
r.Log.Info("Extracted metric successfully", "value", value)
185+
return value, nil
186+
}

metrics-operator/controllers/common/providers/provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1"
1212
"github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/providers/datadog"
1313
"github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/providers/dynatrace"
14+
"github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/providers/elastic"
1415
"github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/providers/prometheus"
1516
"sigs.k8s.io/controller-runtime/pkg/client"
1617
)
@@ -62,6 +63,8 @@ func NewProvider(provider *metricsapi.KeptnMetricsProvider, log logr.Logger, k8s
6263
},
6364
K8sClient: k8sClient,
6465
}, nil
66+
case ElasticProviderType:
67+
return elastic.NewElasticProvider(log, k8sClient, "")
6568
default:
6669
return nil, fmt.Errorf("provider %s not supported", provider.Spec.Type)
6770
}

metrics-operator/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23
55
require (
66
github.com/DataDog/datadog-api-client-go/v2 v2.32.0
77
github.com/benbjohnson/clock v1.3.5
8+
github.com/elastic/go-elasticsearch/v8 v8.17.1
89
github.com/go-logr/logr v1.4.2
910
github.com/gorilla/mux v1.8.1
1011
github.com/kelseyhightower/envconfig v1.4.0
@@ -43,6 +44,7 @@ require (
4344
github.com/coreos/go-semver v0.3.1 // indirect
4445
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
4546
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
47+
github.com/elastic/elastic-transport-go/v8 v8.6.1 // indirect
4648
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
4749
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
4850
github.com/evanphx/json-patch/v5 v5.9.0 // indirect

metrics-operator/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
2929
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3030
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
3131
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
32+
github.com/elastic/elastic-transport-go/v8 v8.6.1 h1:h2jQRqH6eLGiBSN4eZbQnJLtL4bC5b4lfVFRjw2R4e4=
33+
github.com/elastic/elastic-transport-go/v8 v8.6.1/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
34+
github.com/elastic/go-elasticsearch/v8 v8.17.1 h1:bOXChDoCMB4TIwwGqKd031U8OXssmWLT3UrAr9EGs3Q=
35+
github.com/elastic/go-elasticsearch/v8 v8.17.1/go.mod h1:MVJCtL+gJJ7x5jFeUmA20O7rvipX8GcQmo5iBcmaJn4=
3236
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
3337
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
3438
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=

0 commit comments

Comments
 (0)