elastomer-client 6.3.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yaml +2 -0
- data/.github/workflows/main.yml +7 -4
- data/.github/workflows/rubocop.yml +2 -2
- data/CHANGELOG.md +4 -0
- data/README.md +12 -1
- data/docker/compose.yaml +34 -0
- data/lib/elastomer_client/client/rest_api_spec/api_spec_v9_5.rb +9308 -0
- data/lib/elastomer_client/client.rb +7 -0
- data/lib/elastomer_client/version.rb +1 -1
- data/lib/elastomer_client/version_support.rb +8 -1
- data/script/generate-rest-api-spec +3 -0
- data/test/client/native_delete_by_query_test.rb +9 -8
- data/test/client/rest_api_spec/api_spec_test.rb +32 -0
- data/test/client/update_by_query_test.rb +9 -8
- data/test/client_test.rb +23 -0
- data/test/test_helper.rb +12 -0
- data/test/version_support_test.rb +9 -2
- metadata +3 -2
|
@@ -414,6 +414,13 @@ module ElastomerClient
|
|
|
414
414
|
|
|
415
415
|
if response.body.is_a?(Hash) && (error = response.body["error"])
|
|
416
416
|
root_cause = Array(error["root_cause"]).first || error
|
|
417
|
+
# ES9 returns these timeouts as 429 rather than retryable 5xx ServerError responses.
|
|
418
|
+
# Retry only these timeout types so unrelated 429s retain their existing handling.
|
|
419
|
+
if response.status == 429 &&
|
|
420
|
+
%w[timeout_exception process_cluster_event_timeout_exception search_timeout_exception].include?(root_cause["type"])
|
|
421
|
+
raise TimeoutError, response
|
|
422
|
+
end
|
|
423
|
+
|
|
417
424
|
case root_cause["type"]
|
|
418
425
|
when "index_not_found_exception"; raise IndexNotFoundError, response
|
|
419
426
|
when "illegal_argument_exception"; raise IllegalArgument, response
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rubygems/version"
|
|
4
|
+
|
|
3
5
|
module ElastomerClient
|
|
4
6
|
class VersionSupport
|
|
5
7
|
|
|
@@ -9,7 +11,8 @@ module ElastomerClient
|
|
|
9
11
|
#
|
|
10
12
|
# Raises ArgumentError if version is unsupported.
|
|
11
13
|
def initialize(version)
|
|
12
|
-
|
|
14
|
+
comparable_version = Gem::Version.new(version)
|
|
15
|
+
if comparable_version < Gem::Version.new("5.0") || comparable_version >= Gem::Version.new("10.0")
|
|
13
16
|
raise ArgumentError, "Elasticsearch version #{version} is not supported by elastomer-client"
|
|
14
17
|
end
|
|
15
18
|
|
|
@@ -20,5 +23,9 @@ module ElastomerClient
|
|
|
20
23
|
def es_version_8_plus?
|
|
21
24
|
version >= "8.0.0"
|
|
22
25
|
end
|
|
26
|
+
|
|
27
|
+
def es_version_9_plus?
|
|
28
|
+
Gem::Version.new(version) >= Gem::Version.new("9.0")
|
|
29
|
+
end
|
|
23
30
|
end
|
|
24
31
|
end
|
|
@@ -62,6 +62,9 @@ class RestApiSpecGenerator
|
|
|
62
62
|
hash = MultiJson.load(File.read(filename))
|
|
63
63
|
key = hash.keys.first
|
|
64
64
|
value = hash.values.first
|
|
65
|
+
# ES9 exposes legacy knn_search only in ES8 compatibility mode, not native ES9.
|
|
66
|
+
next if @version_support.es_version_9_plus? && key == "knn_search"
|
|
67
|
+
|
|
65
68
|
yield(key, value)
|
|
66
69
|
end
|
|
67
70
|
end
|
|
@@ -93,12 +93,13 @@ describe ElastomerClient::Client::NativeDeleteByQuery do
|
|
|
93
93
|
_routing: { required: true }
|
|
94
94
|
})
|
|
95
95
|
})
|
|
96
|
-
wait_for_index(
|
|
96
|
+
wait_for_index(index.name)
|
|
97
97
|
docs = index.docs(type)
|
|
98
|
+
routing, other_routing = routing_values_for_distinct_shards(docs)
|
|
98
99
|
|
|
99
|
-
docs.index({ _id: 0, _routing:
|
|
100
|
-
docs.index({ _id: 1, _routing:
|
|
101
|
-
docs.index({ _id: 2, _routing:
|
|
100
|
+
docs.index({ _id: 0, _routing: routing, name: "mittens" })
|
|
101
|
+
docs.index({ _id: 1, _routing: routing, name: "luna" })
|
|
102
|
+
docs.index({ _id: 2, _routing: other_routing, name: "mittens" })
|
|
102
103
|
|
|
103
104
|
query = {
|
|
104
105
|
query: {
|
|
@@ -109,15 +110,15 @@ describe ElastomerClient::Client::NativeDeleteByQuery do
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
index.refresh
|
|
112
|
-
response = index.native_delete_by_query(query, routing:
|
|
113
|
+
response = index.native_delete_by_query(query, routing:)
|
|
113
114
|
|
|
114
115
|
assert_equal(1, response["deleted"])
|
|
115
116
|
|
|
116
117
|
response = docs.multi_get({
|
|
117
118
|
docs: [
|
|
118
|
-
{ _id: 0, routing:
|
|
119
|
-
{ _id: 1, routing:
|
|
120
|
-
{ _id: 2, routing:
|
|
119
|
+
{ _id: 0, routing: },
|
|
120
|
+
{ _id: 1, routing: },
|
|
121
|
+
{ _id: 2, routing: other_routing },
|
|
121
122
|
]
|
|
122
123
|
})
|
|
123
124
|
|
|
@@ -54,4 +54,36 @@ describe ElastomerClient::Client::RestApiSpec::ApiSpec do
|
|
|
54
54
|
@api_spec.validate_params!(api: "search", params:)
|
|
55
55
|
}
|
|
56
56
|
end
|
|
57
|
+
|
|
58
|
+
it "selects the REST API spec for each supported Elasticsearch major version" do
|
|
59
|
+
{"8.18.0" => "8_18", "8.19.3" => "8_19", "9.5.6" => "9_5"}.each do |version, class_version|
|
|
60
|
+
client = ElastomerClient::Client.new(es_version: version)
|
|
61
|
+
|
|
62
|
+
assert_equal "ElastomerClient::Client::RestApiSpec::ApiSpecV#{class_version}", client.api_spec.class.name
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "keeps removed local parameters in ES8 but excludes them from ES9" do
|
|
67
|
+
eight = ElastomerClient::Client::RestApiSpec.api_spec("8.19.3")
|
|
68
|
+
nine = ElastomerClient::Client::RestApiSpec.api_spec("9.5.6")
|
|
69
|
+
|
|
70
|
+
%w[indices.get_alias indices.exists_alias cat.aliases indices.get_field_mapping].each do |api|
|
|
71
|
+
assert eight.valid_param?(api:, param: :local)
|
|
72
|
+
refute nine.valid_param?(api:, param: :local)
|
|
73
|
+
assert_empty(nine.select_params(api:, from: {local: true}))
|
|
74
|
+
assert_raises(ElastomerClient::Client::IllegalArgument) {
|
|
75
|
+
nine.validate_params!(api:, params: {local: true})
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "keeps removed endpoints in ES8 but excludes them from ES9" do
|
|
81
|
+
eight = ElastomerClient::Client::RestApiSpec.api_spec("8.19.3")
|
|
82
|
+
nine = ElastomerClient::Client::RestApiSpec.api_spec("9.5.6")
|
|
83
|
+
|
|
84
|
+
%w[indices.unfreeze knn_search].each do |api|
|
|
85
|
+
refute_nil eight.get(api)
|
|
86
|
+
assert_nil nine.get(api)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
57
89
|
end
|
|
@@ -96,12 +96,13 @@ describe ElastomerClient::Client::UpdateByQuery do
|
|
|
96
96
|
_routing: { required: true }
|
|
97
97
|
})
|
|
98
98
|
})
|
|
99
|
-
wait_for_index(
|
|
99
|
+
wait_for_index(index.name)
|
|
100
100
|
docs = index.docs(type)
|
|
101
|
+
routing, other_routing = routing_values_for_distinct_shards(docs)
|
|
101
102
|
|
|
102
|
-
docs.index({ _id: 0, _routing:
|
|
103
|
-
docs.index({ _id: 1, _routing:
|
|
104
|
-
docs.index({ _id: 2, _routing:
|
|
103
|
+
docs.index({ _id: 0, _routing: routing, name: "mittens" })
|
|
104
|
+
docs.index({ _id: 1, _routing: routing, name: "luna" })
|
|
105
|
+
docs.index({ _id: 2, _routing: other_routing, name: "mittens" })
|
|
105
106
|
|
|
106
107
|
query = {
|
|
107
108
|
query: {
|
|
@@ -115,15 +116,15 @@ describe ElastomerClient::Client::UpdateByQuery do
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
index.refresh
|
|
118
|
-
response = index.update_by_query(query, routing:
|
|
119
|
+
response = index.update_by_query(query, routing:)
|
|
119
120
|
|
|
120
121
|
assert_equal(1, response["updated"])
|
|
121
122
|
|
|
122
123
|
response = docs.multi_get({
|
|
123
124
|
docs: [
|
|
124
|
-
{ _id: 0, routing:
|
|
125
|
-
{ _id: 1, routing:
|
|
126
|
-
{ _id: 2, routing:
|
|
125
|
+
{ _id: 0, routing: },
|
|
126
|
+
{ _id: 1, routing: },
|
|
127
|
+
{ _id: 2, routing: other_routing },
|
|
127
128
|
]
|
|
128
129
|
})
|
|
129
130
|
|
data/test/client_test.rb
CHANGED
|
@@ -85,6 +85,29 @@ describe ElastomerClient::Client do
|
|
|
85
85
|
assert_equal "it took too long :: GET /_cat/indices", wrapped.message
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
it "keeps Elasticsearch timeout errors retryable when ES9 returns 429" do
|
|
89
|
+
%w[timeout_exception process_cluster_event_timeout_exception search_timeout_exception].each do |type|
|
|
90
|
+
error = {"type" => type}
|
|
91
|
+
[error, {"type" => "search_phase_execution_exception", "root_cause" => [error]}].each do |body|
|
|
92
|
+
response = Faraday::Response.new(status: 429, body: {"error" => body})
|
|
93
|
+
exception = assert_raises(ElastomerClient::Client::TimeoutError) { $client.handle_errors(response) }
|
|
94
|
+
assert_predicate exception, :retry?
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "does not make other 429 errors retryable" do
|
|
100
|
+
response = Faraday::Response.new(status: 429, body: {"error" => {"type" => "circuit_breaking_exception"}})
|
|
101
|
+
exception = assert_raises(ElastomerClient::Client::RequestError) { $client.handle_errors(response) }
|
|
102
|
+
refute_predicate exception, :retry?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "preserves ES8 server timeout error handling" do
|
|
106
|
+
response = Faraday::Response.new(status: 503, body: {"error" => {"type" => "timeout_exception"}})
|
|
107
|
+
exception = assert_raises(ElastomerClient::Client::ServerError) { $client.handle_errors(response) }
|
|
108
|
+
assert_predicate exception, :retry?
|
|
109
|
+
end
|
|
110
|
+
|
|
88
111
|
it "handles path expansions" do
|
|
89
112
|
uri = $client.expand_path "/{foo}/{bar}", foo: "_cluster", bar: "health"
|
|
90
113
|
|
data/test/test_helper.rb
CHANGED
|
@@ -110,6 +110,18 @@ def default_index_settings
|
|
|
110
110
|
{settings: {index: {number_of_shards: 1, number_of_replicas: 0}}}
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
+
def routing_values_for_distinct_shards(docs)
|
|
114
|
+
routing_by_shard = {}
|
|
115
|
+
100.times do
|
|
116
|
+
routing = SecureRandom.uuid
|
|
117
|
+
shard = docs.search_shards(routing:).fetch("shards").fetch(0).fetch(0).fetch("shard")
|
|
118
|
+
routing_by_shard[shard] ||= routing
|
|
119
|
+
return routing_by_shard.values if routing_by_shard.size == 2
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
flunk "Could not find routing values for distinct shards after 100 UUID attempts"
|
|
123
|
+
end
|
|
124
|
+
|
|
113
125
|
def run_snapshot_tests?
|
|
114
126
|
unless defined? $run_snapshot_tests
|
|
115
127
|
begin
|
|
@@ -4,9 +4,10 @@ require_relative "test_helper"
|
|
|
4
4
|
|
|
5
5
|
describe ElastomerClient::VersionSupport do
|
|
6
6
|
describe "supported versions" do
|
|
7
|
-
it "allows 5.0.0 to
|
|
7
|
+
it "allows 5.0.0 to 9.x" do
|
|
8
8
|
five_series = ["5.0.0", "5.0.9", "5.1.0", "5.9.0", "5.99.100"]
|
|
9
9
|
eight_series = ["8.0.0", "8.0.9", "8.1.0", "8.9.0", "8.99.100"]
|
|
10
|
+
nine_series = ["9.0.0", "9.0.9", "9.1.0", "9.9.0", "9.99.100"]
|
|
10
11
|
|
|
11
12
|
five_series.each do |version|
|
|
12
13
|
assert ElastomerClient::VersionSupport.new(version)
|
|
@@ -14,6 +15,12 @@ describe ElastomerClient::VersionSupport do
|
|
|
14
15
|
|
|
15
16
|
eight_series.each do |version|
|
|
16
17
|
assert_predicate ElastomerClient::VersionSupport.new(version), :es_version_8_plus?
|
|
18
|
+
refute_predicate ElastomerClient::VersionSupport.new(version), :es_version_9_plus?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
nine_series.each do |version|
|
|
22
|
+
assert_predicate ElastomerClient::VersionSupport.new(version), :es_version_8_plus?
|
|
23
|
+
assert_predicate ElastomerClient::VersionSupport.new(version), :es_version_9_plus?
|
|
17
24
|
end
|
|
18
25
|
end
|
|
19
26
|
end
|
|
@@ -21,7 +28,7 @@ describe ElastomerClient::VersionSupport do
|
|
|
21
28
|
describe "unsupported versions" do
|
|
22
29
|
it "blow up" do
|
|
23
30
|
too_low = ["0.90", "1.0.1", "2.0.0", "2.2.0"]
|
|
24
|
-
too_high = ["
|
|
31
|
+
too_high = ["10.0.0", "11.0.0", "20.0.0"]
|
|
25
32
|
|
|
26
33
|
(too_low + too_high).each do |version|
|
|
27
34
|
exception = assert_raises(ArgumentError, "expected #{version} to not be supported") do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: elastomer-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 6.
|
|
4
|
+
version: 6.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tim Pease
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-
|
|
12
|
+
date: 2026-11-05 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: addressable
|
|
@@ -151,6 +151,7 @@ files:
|
|
|
151
151
|
- lib/elastomer_client/client/rest_api_spec/api_spec_v8_18.rb
|
|
152
152
|
- lib/elastomer_client/client/rest_api_spec/api_spec_v8_19.rb
|
|
153
153
|
- lib/elastomer_client/client/rest_api_spec/api_spec_v8_7.rb
|
|
154
|
+
- lib/elastomer_client/client/rest_api_spec/api_spec_v9_5.rb
|
|
154
155
|
- lib/elastomer_client/client/rest_api_spec/rest_api.rb
|
|
155
156
|
- lib/elastomer_client/client/scroller.rb
|
|
156
157
|
- lib/elastomer_client/client/snapshot.rb
|