elastomer-client 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile +5 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +108 -0
  8. data/Rakefile +9 -0
  9. data/docs/notifications.md +71 -0
  10. data/elastomer-client.gemspec +30 -0
  11. data/lib/elastomer/client.rb +307 -0
  12. data/lib/elastomer/client/bulk.rb +257 -0
  13. data/lib/elastomer/client/cluster.rb +208 -0
  14. data/lib/elastomer/client/docs.rb +432 -0
  15. data/lib/elastomer/client/errors.rb +51 -0
  16. data/lib/elastomer/client/index.rb +407 -0
  17. data/lib/elastomer/client/multi_search.rb +115 -0
  18. data/lib/elastomer/client/nodes.rb +87 -0
  19. data/lib/elastomer/client/scan.rb +161 -0
  20. data/lib/elastomer/client/template.rb +85 -0
  21. data/lib/elastomer/client/warmer.rb +96 -0
  22. data/lib/elastomer/core_ext/time.rb +7 -0
  23. data/lib/elastomer/middleware/encode_json.rb +51 -0
  24. data/lib/elastomer/middleware/opaque_id.rb +69 -0
  25. data/lib/elastomer/middleware/parse_json.rb +39 -0
  26. data/lib/elastomer/notifications.rb +83 -0
  27. data/lib/elastomer/version.rb +7 -0
  28. data/script/bootstrap +16 -0
  29. data/script/cibuild +28 -0
  30. data/script/console +9 -0
  31. data/script/testsuite +10 -0
  32. data/test/assertions.rb +74 -0
  33. data/test/client/bulk_test.rb +226 -0
  34. data/test/client/cluster_test.rb +113 -0
  35. data/test/client/docs_test.rb +394 -0
  36. data/test/client/index_test.rb +244 -0
  37. data/test/client/multi_search_test.rb +129 -0
  38. data/test/client/nodes_test.rb +35 -0
  39. data/test/client/scan_test.rb +84 -0
  40. data/test/client/stubbed_client_tests.rb +40 -0
  41. data/test/client/template_test.rb +33 -0
  42. data/test/client/warmer_test.rb +56 -0
  43. data/test/client_test.rb +86 -0
  44. data/test/core_ext/time_test.rb +46 -0
  45. data/test/middleware/encode_json_test.rb +53 -0
  46. data/test/middleware/opaque_id_test.rb +39 -0
  47. data/test/middleware/parse_json_test.rb +54 -0
  48. data/test/test_helper.rb +94 -0
  49. 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