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.
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