elastomer-client 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|