Skip to content

Commit f0417ca

Browse files
shuraih775graphite-app[bot]yurishkuro
authored
Implement custom RangeQuery interface to support Elasticsearch v9 (#7358)
## Which problem is this PR solving? - Resolves #7274 - Resolves compatibility issues with Elasticsearch v9 caused by the removal of deprecated fields in the `RangeQuery` structure from the `olivere/elastic` client. - Enables Jaeger to run with Elasticsearch v9+ by replacing the use of `olivere/elastic/v7.RangeQuery` with a custom implementation. ## Description of the changes - Introduced a custom `RangeQuery` interface under `internal/storage/elasticsearch/query`. - Removed usage of deprecated fields such as `include_lower`, `include_upper`, `from`, and `to`. - Replaced all internal references to the legacy `RangeQuery` with the new implementation. - Ensured the new query format aligns with the expected request structure in Elasticsearch v9's `_search` API. ## How was this change tested? - Added and ran unit tests for the custom `RangeQuery` implementation. - Successfully built and ran Jaeger components with Elasticsearch v9 Docker image (`docker.elastic.co/elasticsearch/elasticsearch:9.0.3`). ## Checklist - [x] I have read https://github.com/jaegertracing/jaeger/blob/master/CONTRIBUTING_GUIDELINES.md - [x] I have signed all commits - [x] I have added unit tests for the new functionality - [x] I have run lint and test steps successfully - for `jaeger`: `make lint test` - for `jaeger-ui`: `npm run lint` and `npm run test` --------- Signed-off-by: Mohammed Shuraih <shuraih15@gmail.com> Signed-off-by: shuraih775 <74284337+shuraih775@users.noreply.github.com> Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> Co-authored-by: Yuri Shkuro <yurishkuro@users.noreply.github.com>
1 parent 15ac1fb commit f0417ca

File tree

9 files changed

+165
-17
lines changed

9 files changed

+165
-17
lines changed

.github/workflows/ci-e2e-elasticsearch.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ jobs:
3030
- major: 8.x
3131
distribution: elasticsearch
3232
jaeger: v2
33+
- major: 9.x
34+
distribution: elasticsearch
35+
jaeger: v2
3336
name: ${{ matrix.version.distribution }} ${{ matrix.version.major }} ${{ matrix.version.jaeger }}
3437
steps:
3538
- name: Harden Runner
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
services:
2+
elasticsearch:
3+
image: docker.elastic.co/elasticsearch/elasticsearch:9.0.4@sha256:59da431b8987b508af17a9424f1f03398ecaacc09c4218abe306749b524f1519
4+
environment:
5+
- discovery.type=single-node
6+
- http.host=0.0.0.0
7+
- transport.host=127.0.0.1
8+
- xpack.security.enabled=false # Disable security features
9+
- xpack.security.http.ssl.enabled=false # Disable HTTPS
10+
- action.destructive_requires_name=false
11+
- xpack.monitoring.collection.enabled=false # Disable monitoring features
12+
ports:
13+
- "9200:9200"
14+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) 2025 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package query
5+
6+
// Package query provides an Elasticsearch RangeQuery implementation.
7+
// This RangeQuery behaves the same as the Go Elasticsearch client (olivere/elastic),
8+
// but is rewritten to be compatible with Elasticsearch v9 and avoids deprecated parameters.
9+
//
10+
// Deprecated parameters like include_lower, include_upper, from, and to are excluded deliberately.
11+
12+
type RangeQuery struct {
13+
name string
14+
queryName string
15+
params map[string]any
16+
}
17+
18+
// NewRangeQuery creates and initializes a new RangeQuery.
19+
func NewRangeQuery(name string) *RangeQuery {
20+
return &RangeQuery{
21+
name: name,
22+
params: make(map[string]any),
23+
}
24+
}
25+
26+
// Generic setter
27+
func (q *RangeQuery) set(key string, val any) *RangeQuery {
28+
q.params[key] = val
29+
return q
30+
}
31+
32+
func (q *RangeQuery) Gt(val any) *RangeQuery { return q.set("gt", val) }
33+
func (q *RangeQuery) Gte(val any) *RangeQuery { return q.set("gte", val) }
34+
func (q *RangeQuery) Lt(val any) *RangeQuery { return q.set("lt", val) }
35+
func (q *RangeQuery) Lte(val any) *RangeQuery { return q.set("lte", val) }
36+
func (q *RangeQuery) Boost(b float64) *RangeQuery { return q.set("boost", b) }
37+
func (q *RangeQuery) TimeZone(tz string) *RangeQuery {
38+
return q.set("time_zone", tz)
39+
}
40+
41+
func (q *RangeQuery) Format(fmt string) *RangeQuery {
42+
return q.set("format", fmt)
43+
}
44+
45+
func (q *RangeQuery) Relation(r string) *RangeQuery {
46+
return q.set("relation", r)
47+
}
48+
49+
func (q *RangeQuery) QueryName(queryName string) *RangeQuery {
50+
q.queryName = queryName
51+
return q
52+
}
53+
54+
// Source builds and returns the Elasticsearch-compatible representation of the range query.
55+
56+
func (q *RangeQuery) Source() (any, error) {
57+
source := make(map[string]any)
58+
rangeQ := make(map[string]any)
59+
source["range"] = rangeQ
60+
rangeQ[q.name] = q.params
61+
62+
if q.queryName != "" {
63+
rangeQ["_name"] = q.queryName
64+
}
65+
return source, nil
66+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) 2025 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package query
5+
6+
import (
7+
"encoding/json"
8+
"testing"
9+
10+
"github.com/jaegertracing/jaeger/internal/testutils"
11+
)
12+
13+
func assertRangeQuery(t *testing.T, q *RangeQuery, expected string) {
14+
t.Helper()
15+
src, err := q.Source()
16+
if err != nil {
17+
t.Fatal(err)
18+
}
19+
data, err := json.Marshal(src)
20+
if err != nil {
21+
t.Fatalf("marshaling to JSON failed: %v", err)
22+
}
23+
got := string(data)
24+
if got != expected {
25+
t.Errorf("expected:\n%s\ngot:\n%s", expected, got)
26+
}
27+
}
28+
29+
func TestRangeQuery(t *testing.T) {
30+
q := NewRangeQuery("postDate").
31+
Gte("2010-03-01").
32+
Lte("2010-04-01").
33+
Boost(3).
34+
Relation("within").
35+
QueryName("my_query")
36+
37+
expected := `{"range":{"_name":"my_query","postDate":{"boost":3,"gte":"2010-03-01","lte":"2010-04-01","relation":"within"}}}`
38+
assertRangeQuery(t, q, expected)
39+
}
40+
41+
func TestRangeQueryWithTimeZone(t *testing.T) {
42+
q := NewRangeQuery("born").
43+
Gte("2012-01-01").
44+
Lte("now").
45+
TimeZone("+1:00")
46+
47+
expected := `{"range":{"born":{"gte":"2012-01-01","lte":"now","time_zone":"+1:00"}}}`
48+
assertRangeQuery(t, q, expected)
49+
}
50+
51+
func TestRangeQueryWithFormat(t *testing.T) {
52+
q := NewRangeQuery("born").
53+
Gt("2012/01/01").
54+
Lt("now").
55+
Format("yyyy/MM/dd")
56+
57+
expected := `{"range":{"born":{"format":"yyyy/MM/dd","gt":"2012/01/01","lt":"now"}}}`
58+
assertRangeQuery(t, q, expected)
59+
}
60+
61+
func TestMain(m *testing.M) {
62+
testutils.VerifyGoLeaks(m)
63+
}

internal/storage/metricstore/elasticsearch/query_builder.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/jaegertracing/jaeger-idl/model/v1"
1616
es "github.com/jaegertracing/jaeger/internal/storage/elasticsearch"
1717
"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config"
18+
esquery "github.com/jaegertracing/jaeger/internal/storage/elasticsearch/query"
1819
"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore"
1920
"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore"
2021
)
@@ -70,7 +71,7 @@ func (q *QueryBuilder) BuildBoolQuery(params metricstore.BaseQueryParameters, ti
7071
boolQuery.Filter(termQuery)
7172
}
7273

73-
rangeQuery := elastic.NewRangeQuery("startTimeMillis").
74+
rangeQuery := esquery.NewRangeQuery("startTimeMillis").
7475
Gte(timeRange.extendedStartTimeMillis).
7576
Lte(timeRange.endTimeMillis).
7677
Format("epoch_millis")

internal/storage/v1/elasticsearch/samplingstore/storage.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
es "github.com/jaegertracing/jaeger/internal/storage/elasticsearch"
1717
"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config"
18+
esquery "github.com/jaegertracing/jaeger/internal/storage/elasticsearch/query"
1819
"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model"
1920
"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/samplingstore/dbmodel"
2021
)
@@ -190,7 +191,7 @@ func (p *Params) PrefixedIndexName() string {
190191
}
191192

192193
func buildTSQuery(start, end time.Time) elastic.Query {
193-
return elastic.NewRangeQuery("timestamp").Gte(start).Lte(end)
194+
return esquery.NewRangeQuery("timestamp").Gte(start).Lte(end)
194195
}
195196

196197
func indexWithDate(indexNamePrefix, indexDateLayout string, date time.Time) string {

internal/storage/v1/elasticsearch/spanstore/reader.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
es "github.com/jaegertracing/jaeger/internal/storage/elasticsearch"
2525
cfg "github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config"
2626
"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel"
27+
esquery "github.com/jaegertracing/jaeger/internal/storage/elasticsearch/query"
2728
)
2829

2930
const (
@@ -635,7 +636,7 @@ func (*SpanReader) buildDurationQuery(durationMin time.Duration, durationMax tim
635636
if durationMax != 0 {
636637
maxDurationMicros = model.DurationAsMicroseconds(durationMax)
637638
}
638-
return elastic.NewRangeQuery(durationField).Gte(minDurationMicros).Lte(maxDurationMicros)
639+
return esquery.NewRangeQuery(durationField).Gte(minDurationMicros).Lte(maxDurationMicros)
639640
}
640641

641642
func (*SpanReader) buildStartTimeQuery(startTimeMin time.Time, startTimeMax time.Time) elastic.Query {
@@ -644,7 +645,7 @@ func (*SpanReader) buildStartTimeQuery(startTimeMin time.Time, startTimeMax time
644645
// startTimeMillisField is date field in ES mapping.
645646
// Using date field in range queries helps to skip search on unnecessary shards at Elasticsearch side.
646647
// https://discuss.elastic.co/t/timeline-query-on-timestamped-indices/129328/2
647-
return elastic.NewRangeQuery(startTimeMillisField).Gte(minStartTimeMicros / 1000).Lte(maxStartTimeMicros / 1000)
648+
return esquery.NewRangeQuery(startTimeMillisField).Gte(minStartTimeMicros / 1000).Lte(maxStartTimeMicros / 1000)
648649
}
649650

650651
func (*SpanReader) buildServiceNameQuery(serviceName string) elastic.Query {

internal/storage/v1/elasticsearch/spanstore/reader_test.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,10 +1093,9 @@ func TestSpanReader_buildFindTraceIDsQuery(t *testing.T) {
10931093

10941094
func TestSpanReader_buildDurationQuery(t *testing.T) {
10951095
expectedStr := `{ "range":
1096-
{ "duration": { "include_lower": true,
1097-
"include_upper": true,
1098-
"from": 1000000,
1099-
"to": 2000000 }
1096+
{ "duration": {
1097+
"gte": 1000000,
1098+
"lte": 2000000 }
11001099
}
11011100
}`
11021101
withSpanReader(t, func(r *spanReaderTest) {
@@ -1109,19 +1108,18 @@ func TestSpanReader_buildDurationQuery(t *testing.T) {
11091108
expected := make(map[string]any)
11101109
json.Unmarshal([]byte(expectedStr), &expected)
11111110
// We need to do this because we cannot process a json into uint64.
1112-
expected["range"].(map[string]any)["duration"].(map[string]any)["from"] = model.DurationAsMicroseconds(durationMin)
1113-
expected["range"].(map[string]any)["duration"].(map[string]any)["to"] = model.DurationAsMicroseconds(durationMax)
1111+
expected["range"].(map[string]any)["duration"].(map[string]any)["gte"] = model.DurationAsMicroseconds(durationMin)
1112+
expected["range"].(map[string]any)["duration"].(map[string]any)["lte"] = model.DurationAsMicroseconds(durationMax)
11141113

11151114
assert.EqualValues(t, expected, actual)
11161115
})
11171116
}
11181117

11191118
func TestSpanReader_buildStartTimeQuery(t *testing.T) {
11201119
expectedStr := `{ "range":
1121-
{ "startTimeMillis": { "include_lower": true,
1122-
"include_upper": true,
1123-
"from": 1000000,
1124-
"to": 2000000 }
1120+
{ "startTimeMillis": {
1121+
"gte": 1000000,
1122+
"lte": 2000000 }
11251123
}
11261124
}`
11271125
withSpanReader(t, func(r *spanReaderTest) {
@@ -1134,8 +1132,8 @@ func TestSpanReader_buildStartTimeQuery(t *testing.T) {
11341132
expected := make(map[string]any)
11351133
json.Unmarshal([]byte(expectedStr), &expected)
11361134
// We need to do this because we cannot process a json into uint64.
1137-
expected["range"].(map[string]any)["startTimeMillis"].(map[string]any)["from"] = model.TimeAsEpochMicroseconds(startTimeMin) / 1000
1138-
expected["range"].(map[string]any)["startTimeMillis"].(map[string]any)["to"] = model.TimeAsEpochMicroseconds(startTimeMax) / 1000
1135+
expected["range"].(map[string]any)["startTimeMillis"].(map[string]any)["gte"] = model.TimeAsEpochMicroseconds(startTimeMin) / 1000
1136+
expected["range"].(map[string]any)["startTimeMillis"].(map[string]any)["lte"] = model.TimeAsEpochMicroseconds(startTimeMax) / 1000
11391137

11401138
assert.EqualValues(t, expected, actual)
11411139
})

internal/storage/v2/elasticsearch/depstore/storage.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
es "github.com/jaegertracing/jaeger/internal/storage/elasticsearch"
1818
"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config"
19+
esquery "github.com/jaegertracing/jaeger/internal/storage/elasticsearch/query"
1920
"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore/dbmodel"
2021
)
2122

@@ -116,7 +117,7 @@ func (s *DependencyStore) GetDependencies(ctx context.Context, endTs time.Time,
116117
}
117118

118119
func buildTSQuery(endTs time.Time, lookback time.Duration) elastic.Query {
119-
return elastic.NewRangeQuery("timestamp").Gte(endTs.Add(-lookback)).Lte(endTs)
120+
return esquery.NewRangeQuery("timestamp").Gte(endTs.Add(-lookback)).Lte(endTs)
120121
}
121122

122123
func (s *DependencyStore) getReadIndices(ts time.Time, lookback time.Duration) []string {

0 commit comments

Comments
 (0)