elastomer-client 0.3.1
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 +7 -0
- data/.gitignore +8 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +108 -0
- data/Rakefile +9 -0
- data/docs/notifications.md +71 -0
- data/elastomer-client.gemspec +30 -0
- data/lib/elastomer/client.rb +307 -0
- data/lib/elastomer/client/bulk.rb +257 -0
- data/lib/elastomer/client/cluster.rb +208 -0
- data/lib/elastomer/client/docs.rb +432 -0
- data/lib/elastomer/client/errors.rb +51 -0
- data/lib/elastomer/client/index.rb +407 -0
- data/lib/elastomer/client/multi_search.rb +115 -0
- data/lib/elastomer/client/nodes.rb +87 -0
- data/lib/elastomer/client/scan.rb +161 -0
- data/lib/elastomer/client/template.rb +85 -0
- data/lib/elastomer/client/warmer.rb +96 -0
- data/lib/elastomer/core_ext/time.rb +7 -0
- data/lib/elastomer/middleware/encode_json.rb +51 -0
- data/lib/elastomer/middleware/opaque_id.rb +69 -0
- data/lib/elastomer/middleware/parse_json.rb +39 -0
- data/lib/elastomer/notifications.rb +83 -0
- data/lib/elastomer/version.rb +7 -0
- data/script/bootstrap +16 -0
- data/script/cibuild +28 -0
- data/script/console +9 -0
- data/script/testsuite +10 -0
- data/test/assertions.rb +74 -0
- data/test/client/bulk_test.rb +226 -0
- data/test/client/cluster_test.rb +113 -0
- data/test/client/docs_test.rb +394 -0
- data/test/client/index_test.rb +244 -0
- data/test/client/multi_search_test.rb +129 -0
- data/test/client/nodes_test.rb +35 -0
- data/test/client/scan_test.rb +84 -0
- data/test/client/stubbed_client_tests.rb +40 -0
- data/test/client/template_test.rb +33 -0
- data/test/client/warmer_test.rb +56 -0
- data/test/client_test.rb +86 -0
- data/test/core_ext/time_test.rb +46 -0
- data/test/middleware/encode_json_test.rb +53 -0
- data/test/middleware/opaque_id_test.rb +39 -0
- data/test/middleware/parse_json_test.rb +54 -0
- data/test/test_helper.rb +94 -0
- metadata +210 -0
@@ -0,0 +1,257 @@
|
|
1
|
+
module Elastomer
|
2
|
+
class Client
|
3
|
+
|
4
|
+
# The `bulk` method can be used in two ways. Without a block the method
|
5
|
+
# will perform an API call, and it requires a bulk request body and
|
6
|
+
# optional request parameters. If given a block, the method will use a
|
7
|
+
# Bulk instance to assemble the operations called in the block into a
|
8
|
+
# bulk request and dispatch it at the end of the block.
|
9
|
+
#
|
10
|
+
# See http://www.elasticsearch.org/guide/reference/api/bulk/
|
11
|
+
#
|
12
|
+
# body - Request body as a String (required if a block is _not_ given)
|
13
|
+
# params - Optional request parameters as a Hash
|
14
|
+
# :request_size - Optional maximum request size in bytes
|
15
|
+
# :action_count - Optional maximum action size
|
16
|
+
# block - Passed to a Bulk instance which assembles the operations
|
17
|
+
# into one or more bulk requests.
|
18
|
+
#
|
19
|
+
# Examples
|
20
|
+
#
|
21
|
+
# bulk( request_body, :index => 'default-index' )
|
22
|
+
#
|
23
|
+
# bulk( :index => 'default-index' ) do |b|
|
24
|
+
# b.index( document1 )
|
25
|
+
# b.index( document2 )
|
26
|
+
# b.delete( document3 )
|
27
|
+
# ...
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# Returns the response body as a Hash
|
31
|
+
def bulk( body = nil, params = nil )
|
32
|
+
if block_given?
|
33
|
+
params, body = (body || {}), nil
|
34
|
+
yield bulk_obj = Bulk.new(self, params)
|
35
|
+
bulk_obj.call
|
36
|
+
|
37
|
+
else
|
38
|
+
raise 'bulk request body cannot be nil' if body.nil?
|
39
|
+
params ||= {}
|
40
|
+
|
41
|
+
response = self.post '{/index}{/type}/_bulk', params.merge(:body => body, :action => 'bulk')
|
42
|
+
response.body
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# The Bulk class provides some abstractions and helper methods for working
|
48
|
+
# with the ElasticSearch bulk API command. Instances of the Bulk class
|
49
|
+
# accumulate indexing and delete operations and then issue a single bulk
|
50
|
+
# API request to ElasticSearch. Those operations are then executed by the
|
51
|
+
# cluster.
|
52
|
+
#
|
53
|
+
# A maximum request size can be set. As soon as the size of the request
|
54
|
+
# body hits this threshold, a bulk request will be made to the search
|
55
|
+
# cluster. This happens as operations are added.
|
56
|
+
#
|
57
|
+
# Additionally, a maximum action count can be set. As soon as the number
|
58
|
+
# of actions equals the action count, a bulk request will be made.
|
59
|
+
#
|
60
|
+
# You can also use the `call` method explicitly to send a bulk request
|
61
|
+
# immediately.
|
62
|
+
#
|
63
|
+
class Bulk
|
64
|
+
|
65
|
+
# Create a new bulk client for handling some of the details of
|
66
|
+
# accumulating documents to index and then formatting them properly for
|
67
|
+
# the bulk API command.
|
68
|
+
#
|
69
|
+
# client - Elastomer::Client used for HTTP requests to the server
|
70
|
+
# params - Parameters Hash to pass to the Client#bulk method
|
71
|
+
# :request_size - the maximum request size in bytes
|
72
|
+
# :action_count - the maximum number of actions
|
73
|
+
def initialize( client, params = {} )
|
74
|
+
@client = client
|
75
|
+
@params = params
|
76
|
+
|
77
|
+
@actions = []
|
78
|
+
@current_request_size = 0
|
79
|
+
@current_action_count = 0
|
80
|
+
self.request_size = params.delete(:request_size)
|
81
|
+
self.action_count = params.delete(:action_count)
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_reader :client, :request_size, :action_count
|
85
|
+
|
86
|
+
# Set the request size in bytes. If the value is nil, then request size
|
87
|
+
# limiting will not be used and a request will only be made when the call
|
88
|
+
# method is called. It is up to the user to ensure that the request does
|
89
|
+
# not exceed ElasticSearch request size limits.
|
90
|
+
#
|
91
|
+
# If the value is a number greater than zero, then actions will be
|
92
|
+
# buffered until the request size is met or exceeded. When this happens a
|
93
|
+
# bulk request is issued, queued actions are cleared, and the response
|
94
|
+
# from ElasticSearch is returned.
|
95
|
+
def request_size=( value )
|
96
|
+
if value.nil?
|
97
|
+
@request_size = nil
|
98
|
+
else
|
99
|
+
@request_size = value.to_i <= 0 ? nil : value.to_i
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Set the action count. If the value is nil, then action count limiting
|
104
|
+
# will not be used and a request will only be made when the call method
|
105
|
+
# is called. It is up to the user to ensure that the request does not
|
106
|
+
# exceed ElasticSearch request size limits.
|
107
|
+
#
|
108
|
+
# If the value is a number greater than zero, then actions will be
|
109
|
+
# buffered until the action count is met. When this happens a bulk
|
110
|
+
# request is issued, queued actions are cleared, and the response from
|
111
|
+
# ElasticSearch is returned.
|
112
|
+
def action_count=(value)
|
113
|
+
if value.nil?
|
114
|
+
@action_count = nil
|
115
|
+
else
|
116
|
+
@action_count = value.to_i <= 0 ? nil : value.to_i
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Add an index action to the list of bulk actions to be performed when
|
121
|
+
# the bulk API call is made. The `_id` of the document cannot be `nil`
|
122
|
+
# or empty. If this is the case then we remove the `_id` and allow
|
123
|
+
# ElasticSearch to generate one.
|
124
|
+
#
|
125
|
+
# document - The document to index as a Hash or JSON encoded String
|
126
|
+
# params - Parameters for the index action (as a Hash)
|
127
|
+
#
|
128
|
+
# Returns the response from the bulk call if one was made or nil.
|
129
|
+
def index( document, params = {} )
|
130
|
+
unless String === document
|
131
|
+
overrides = from_document(document)
|
132
|
+
params = params.merge overrides
|
133
|
+
end
|
134
|
+
params.delete(:_id) if params[:_id].nil? || params[:_id].to_s.empty?
|
135
|
+
|
136
|
+
add_to_actions({:index => params}, document)
|
137
|
+
end
|
138
|
+
alias :add :index
|
139
|
+
|
140
|
+
# Add a create action to the list of bulk actions to be performed when
|
141
|
+
# the bulk API call is made. The `_id` of the document cannot be `nil`
|
142
|
+
# or empty.
|
143
|
+
#
|
144
|
+
# document - The document to create as a Hash or JSON encoded String
|
145
|
+
# params - Parameters for the create action (as a Hash)
|
146
|
+
#
|
147
|
+
# Returns the response from the bulk call if one was made or nil.
|
148
|
+
def create( document, params )
|
149
|
+
unless String === document
|
150
|
+
overrides = from_document(document)
|
151
|
+
params = params.merge overrides
|
152
|
+
end
|
153
|
+
params.delete(:_id) if params[:_id].nil? || params[:_id].to_s.empty?
|
154
|
+
|
155
|
+
add_to_actions({:create => params}, document)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Add an update action to the list of bulk actions to be performed when
|
159
|
+
# the bulk API call is made. The `_id` of the document cannot be `nil`
|
160
|
+
# or empty.
|
161
|
+
#
|
162
|
+
# document - The document to update as a Hash or JSON encoded String
|
163
|
+
# params - Parameters for the update action (as a Hash)
|
164
|
+
#
|
165
|
+
# Returns the response from the bulk call if one was made or nil.
|
166
|
+
def update( document, params )
|
167
|
+
unless String === document
|
168
|
+
overrides = from_document(document)
|
169
|
+
params = params.merge overrides
|
170
|
+
end
|
171
|
+
params.delete(:_id) if params[:_id].nil? || params[:_id].to_s.empty?
|
172
|
+
|
173
|
+
add_to_actions({:update => params}, document)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Add a delete action to the list of bulk actions to be performed when
|
177
|
+
# the bulk API call is made.
|
178
|
+
#
|
179
|
+
# params - Parameters for the delete action (as a Hash)
|
180
|
+
#
|
181
|
+
# Returns the response from the bulk call if one was made or nil.
|
182
|
+
def delete( params )
|
183
|
+
add_to_actions :delete => params
|
184
|
+
end
|
185
|
+
|
186
|
+
# Immediately execute a bulk API call with the currently accumulated
|
187
|
+
# actions. The accumulated actions list will be cleared after the call
|
188
|
+
# has been made.
|
189
|
+
#
|
190
|
+
# If the accumulated actions list is empty then no action is taken.
|
191
|
+
#
|
192
|
+
# Returns the response body Hash.
|
193
|
+
def call
|
194
|
+
return nil if @actions.empty?
|
195
|
+
|
196
|
+
body = @actions.join("\n") + "\n"
|
197
|
+
client.bulk(body, @params)
|
198
|
+
ensure
|
199
|
+
@current_request_size = 0
|
200
|
+
@current_action_count = 0
|
201
|
+
@actions.clear
|
202
|
+
end
|
203
|
+
|
204
|
+
# Internal: Extract special keys for bulk indexing from the given
|
205
|
+
# `document`. The keys and their values are returned as a Hash from this
|
206
|
+
# method. If a value is `nil` then it will be ignored.
|
207
|
+
#
|
208
|
+
# document - The document Hash
|
209
|
+
#
|
210
|
+
# Returns extracted key/value pairs as a Hash.
|
211
|
+
def from_document( document )
|
212
|
+
opts = {}
|
213
|
+
|
214
|
+
%w[_id _type _index _version _version_type _routing _parent _percolator _timestamp _ttl _retry_on_conflict].each do |field|
|
215
|
+
key = field.to_sym
|
216
|
+
opts[key] = document.delete field if document[field]
|
217
|
+
opts[key] = document.delete key if document[key]
|
218
|
+
end
|
219
|
+
|
220
|
+
opts
|
221
|
+
end
|
222
|
+
|
223
|
+
# Internal: Add the given `action` to the list of actions that will be
|
224
|
+
# performed by this bulk request. An optional `document` can also be
|
225
|
+
# given.
|
226
|
+
#
|
227
|
+
# If the total size of the accumulated actions meets our desired request
|
228
|
+
# size, then a bulk API call will be performed. After the call the
|
229
|
+
# actions list is cleared and we'll start accumulating actions again.
|
230
|
+
#
|
231
|
+
# action - The bulk action (as a Hash) to perform
|
232
|
+
# document - Optional document for the action as a Hash or JSON encoded String
|
233
|
+
#
|
234
|
+
# Returns the response from the bulk call if one was made or nil.
|
235
|
+
def add_to_actions( action, document = nil )
|
236
|
+
action = MultiJson.dump action
|
237
|
+
@actions << action
|
238
|
+
@current_request_size += action.bytesize
|
239
|
+
@current_action_count += 1
|
240
|
+
|
241
|
+
unless document.nil?
|
242
|
+
document = MultiJson.dump document unless String === document
|
243
|
+
@actions << document
|
244
|
+
@current_request_size += document.bytesize
|
245
|
+
end
|
246
|
+
|
247
|
+
if (request_size && @current_request_size >= request_size) ||
|
248
|
+
(action_count && @current_action_count >= action_count)
|
249
|
+
call
|
250
|
+
else
|
251
|
+
nil
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
end # Bulk
|
256
|
+
end # Client
|
257
|
+
end # Elastomer
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module Elastomer
|
2
|
+
class Client
|
3
|
+
|
4
|
+
# Returns a Cluster instance.
|
5
|
+
def cluster
|
6
|
+
@cluster ||= Cluster.new self
|
7
|
+
end
|
8
|
+
|
9
|
+
class Cluster
|
10
|
+
|
11
|
+
# Create a new cluster client for making API requests that pertain to
|
12
|
+
# the cluster health and management.
|
13
|
+
#
|
14
|
+
# client - Elastomer::Client used for HTTP requests to the server
|
15
|
+
#
|
16
|
+
def initialize( client )
|
17
|
+
@client = client
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :client
|
21
|
+
|
22
|
+
# Simple status on the health of the cluster.
|
23
|
+
# See http://www.elasticsearch.org/guide/reference/api/admin-cluster-health/
|
24
|
+
#
|
25
|
+
# params - Parameters Hash
|
26
|
+
#
|
27
|
+
# Returns the response as a Hash
|
28
|
+
def health( params = {} )
|
29
|
+
response = client.get '/_cluster/health{/index}', params.merge(:action => 'cluster.health')
|
30
|
+
response.body
|
31
|
+
end
|
32
|
+
|
33
|
+
# Comprehensive state information of the whole cluster.
|
34
|
+
# See http://www.elasticsearch.org/guide/reference/api/admin-cluster-state/
|
35
|
+
#
|
36
|
+
# params - Parameters Hash
|
37
|
+
#
|
38
|
+
# Returns the response as a Hash
|
39
|
+
def state( params = {} )
|
40
|
+
response = client.get '/_cluster/state', params.merge(:action => 'cluster.state')
|
41
|
+
response.body
|
42
|
+
end
|
43
|
+
|
44
|
+
# Cluster wide settings that have been modified via the update API.
|
45
|
+
# See http://www.elasticsearch.org/guide/reference/api/admin-cluster-update-settings/
|
46
|
+
#
|
47
|
+
# params - Parameters Hash
|
48
|
+
#
|
49
|
+
# Returns the response as a Hash
|
50
|
+
def get_settings( params = {} )
|
51
|
+
response = client.get '/_cluster/settings', params.merge(:action => 'cluster.get_settings')
|
52
|
+
response.body
|
53
|
+
end
|
54
|
+
alias :settings :get_settings
|
55
|
+
|
56
|
+
# Update cluster wide specific settings. Settings updated can either be
|
57
|
+
# persistent (applied cross restarts) or transient (will not survive a
|
58
|
+
# full cluster restart).
|
59
|
+
#
|
60
|
+
# See http://www.elasticsearch.org/guide/reference/api/admin-cluster-update-settings/
|
61
|
+
#
|
62
|
+
# body - The new settings as a Hash or a JSON encoded String
|
63
|
+
# params - Parameters Hash
|
64
|
+
#
|
65
|
+
# Returns the response as a Hash
|
66
|
+
def update_settings( body, params = {} )
|
67
|
+
response = client.put '/_cluster/settings', params.merge(:body => body, :action => 'cluster.update_settings')
|
68
|
+
response.body
|
69
|
+
end
|
70
|
+
|
71
|
+
# Explicitly execute a cluster reroute allocation command. For example,
|
72
|
+
# a shard can be moved from one node to another explicitly, an
|
73
|
+
# allocation can be canceled, or an unassigned shard can be explicitly
|
74
|
+
# allocated on a specific node.
|
75
|
+
#
|
76
|
+
# See http://www.elasticsearch.org/guide/reference/api/admin-cluster-reroute/
|
77
|
+
#
|
78
|
+
# commands - A command Hash or an Array of command Hashes
|
79
|
+
# params - Parameters Hash
|
80
|
+
#
|
81
|
+
# Examples
|
82
|
+
#
|
83
|
+
# reroute(:move => { :index => 'test', :shard => 0, :from_node => 'node1', :to_node => 'node2' })
|
84
|
+
#
|
85
|
+
# reroute([
|
86
|
+
# { :move => { :index => 'test', :shard => 0, :from_node => 'node1', :to_node => 'node2' }},
|
87
|
+
# { :allocate => { :index => 'test', :shard => 1, :node => 'node3' }}
|
88
|
+
# ])
|
89
|
+
#
|
90
|
+
# Returns the response as a Hash
|
91
|
+
def reroute( commands, params = {} )
|
92
|
+
commands = [commands] unless Array === commands
|
93
|
+
body = {:commands => commands}
|
94
|
+
|
95
|
+
response = client.post '/_cluster/reroute', params.merge(:body => body, :action => 'cluster.reroute')
|
96
|
+
response.body
|
97
|
+
end
|
98
|
+
|
99
|
+
# Shutdown the entire cluster.
|
100
|
+
# See http://www.elasticsearch.org/guide/reference/api/admin-cluster-nodes-shutdown/
|
101
|
+
#
|
102
|
+
# params - Parameters Hash
|
103
|
+
#
|
104
|
+
# Returns the response as a Hash
|
105
|
+
def shutdown( params = {} )
|
106
|
+
response = client.post '/_shutdown', params.merge(:action => 'cluster.shutdown')
|
107
|
+
response.body
|
108
|
+
end
|
109
|
+
|
110
|
+
# Retrieve the current aliases. An :index name can be given (or an
|
111
|
+
# array of index names) to get just the aliases for those indexes. You
|
112
|
+
# can also use the alias name here since it is acting the part of an
|
113
|
+
# index.
|
114
|
+
#
|
115
|
+
# See http://www.elasticsearch.org/guide/reference/api/admin-indices-aliases/
|
116
|
+
#
|
117
|
+
# params - Parameters Hash
|
118
|
+
#
|
119
|
+
# Examples
|
120
|
+
#
|
121
|
+
# get_aliases
|
122
|
+
# get_aliases( :index => 'users' )
|
123
|
+
#
|
124
|
+
# Returns the response body as a Hash
|
125
|
+
def get_aliases( params = {} )
|
126
|
+
response = client.get '{/index}/_aliases', params.merge(:action => 'cluster.get_aliases')
|
127
|
+
response.body
|
128
|
+
end
|
129
|
+
alias :aliases :get_aliases
|
130
|
+
|
131
|
+
# Perform an aliases action on the cluster. We are just a teensy bit
|
132
|
+
# clever here in that a single action can be given or an array of
|
133
|
+
# actions. This API method will wrap the request in the appropriate
|
134
|
+
# {:actions => [...]} body construct.
|
135
|
+
#
|
136
|
+
# See http://www.elasticsearch.org/guide/reference/api/admin-indices-aliases/
|
137
|
+
#
|
138
|
+
# actions - An action Hash or an Array of action Hashes
|
139
|
+
# params - Parameters Hash
|
140
|
+
#
|
141
|
+
# Examples
|
142
|
+
#
|
143
|
+
# update_aliases(:add => { :index => 'users-1', :alias => 'users' })
|
144
|
+
#
|
145
|
+
# update_aliases([
|
146
|
+
# { :remove => { :index => 'users-1', :alias => 'users' }},
|
147
|
+
# { :add => { :index => 'users-2', :alias => 'users' }}
|
148
|
+
# ])
|
149
|
+
#
|
150
|
+
# Returns the response body as a Hash
|
151
|
+
def update_aliases( actions, params = {} )
|
152
|
+
if actions.is_a?(Hash) && actions.key?(:actions)
|
153
|
+
body = actions
|
154
|
+
elsif actions.is_a?(Hash)
|
155
|
+
# Array() on a Hash does not do what you think it does - that is why
|
156
|
+
# we are explicitly wrapping the Hash via [actions] here.
|
157
|
+
body = {:actions => [actions]}
|
158
|
+
else
|
159
|
+
body = {:actions => Array(actions)}
|
160
|
+
end
|
161
|
+
|
162
|
+
response = client.post '/_aliases', params.merge(:body => body, :action => 'cluster.update_aliases')
|
163
|
+
response.body
|
164
|
+
end
|
165
|
+
|
166
|
+
# List all templates currently defined. This is just a convenience method
|
167
|
+
# around the `state` call that extracts and returns the templates section.
|
168
|
+
#
|
169
|
+
# Returns the template definitions as a Hash
|
170
|
+
def templates
|
171
|
+
h = state(
|
172
|
+
:filter_blocks => true,
|
173
|
+
:filter_nodes => true,
|
174
|
+
:filter_routing_table => true
|
175
|
+
)
|
176
|
+
h['metadata']['templates']
|
177
|
+
end
|
178
|
+
|
179
|
+
# List all indices currently defined. This is just a convenience method
|
180
|
+
# around the `state` call that extracts and returns the indices section.
|
181
|
+
#
|
182
|
+
# Returns the indices definitions as a Hash
|
183
|
+
def indices
|
184
|
+
h = state(
|
185
|
+
:filter_blocks => true,
|
186
|
+
:filter_nodes => true,
|
187
|
+
:filter_routing_table => true
|
188
|
+
)
|
189
|
+
h['metadata']['indices']
|
190
|
+
end
|
191
|
+
|
192
|
+
# List all nodes currently part of the cluster. This is just a convenience
|
193
|
+
# method around the `state` call that extracts and returns the nodes
|
194
|
+
# section.
|
195
|
+
#
|
196
|
+
# Returns the nodes definitions as a Hash
|
197
|
+
def nodes
|
198
|
+
h = state(
|
199
|
+
:filter_blocks => true,
|
200
|
+
:filter_metadata => true,
|
201
|
+
:filter_routing_table => true
|
202
|
+
)
|
203
|
+
h['nodes']
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|