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,40 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe 'stubbed client tests' do
4
+ before do
5
+ @stubs = Faraday::Adapter.lookup_middleware(:test)::Stubs.new
6
+ @client = Elastomer::Client.new :adapter => [:test, @stubs]
7
+ end
8
+
9
+ describe Elastomer::Client::Cluster do
10
+ it 'reroutes shards' do
11
+ @stubs.post '/_cluster/reroute?dry_run=true' do |env|
12
+ assert_match %r/^\{"commands":\[\{"move":\{[^\{\}]+\}\}\]\}$/, env[:body]
13
+ [200, {'Content-Type' => 'application/json'}, '{"acknowledged" : true}']
14
+ end
15
+
16
+ commands = { :move => { :index => 'test', :shard => 0, :from_node => 'node1', :to_node => 'node2' }}
17
+ h = @client.cluster.reroute commands, :dry_run => true
18
+ assert_acknowledged h
19
+ end
20
+
21
+ it 'performs a shutdown of the cluster' do
22
+ @stubs.post('/_shutdown') { [200, {'Content-Type' => 'application/json'}, '{"cluster_name":"elasticsearch"}'] }
23
+ h = @client.cluster.shutdown
24
+ assert_equal "elasticsearch", h['cluster_name']
25
+ end
26
+ end
27
+
28
+ describe Elastomer::Client::Nodes do
29
+ it 'performs a shutdown of the node(s)' do
30
+ @stubs.post('/_cluster/nodes/_shutdown') { [200, {'Content-Type' => 'application/json'}, '{"nodes":{"1":{"name":"Node1"}}}'] }
31
+ @stubs.post('/_cluster/nodes/node2/_shutdown') { [200, {'Content-Type' => 'application/json'}, '{"nodes":{"2":{"name":"Node2"}}}'] }
32
+
33
+ h = @client.nodes.shutdown
34
+ assert_equal "Node1", h['nodes']['1']['name']
35
+
36
+ h = @client.nodes('node2').shutdown
37
+ assert_equal 'Node2', h['nodes']['2']['name']
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Client::Cluster do
4
+
5
+ before do
6
+ @name = 'elastomer-template-test'
7
+ @template = $client.template @name
8
+ end
9
+
10
+ after do
11
+ @template.delete if @template.exists?
12
+ end
13
+
14
+ it 'creates a template' do
15
+ assert !@template.exists?, 'the template should not exist'
16
+
17
+ @template.create({
18
+ :template => 'test-elastomer*',
19
+ :settings => { :number_of_shards => 3 },
20
+ :mappings => {
21
+ :doco => { :_source => { :enabled => false }}
22
+ }
23
+ })
24
+
25
+ assert @template.exists?, ' we now have a cluster-test template'
26
+
27
+ template = @template.get
28
+ assert_equal [@name], template.keys
29
+ assert_equal 'test-elastomer*', template[@name]['template']
30
+ assert_equal '3', template[@name]['settings']['index.number_of_shards']
31
+ end
32
+
33
+ end
@@ -0,0 +1,56 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Client::Warmer do
4
+ before do
5
+ @name = 'elastomer-warmer-test'
6
+ @index = $client.index(@name)
7
+
8
+ unless @index.exists?
9
+ @index.create(
10
+ :settings => { 'index.number_of_shards' => 1, 'index.number_of_replicas' => 0 },
11
+ :mappings => {
12
+ :tweet => {
13
+ :_source => { :enabled => true }, :_all => { :enabled => false },
14
+ :properties => {
15
+ :message => { :type => 'string', :analyzer => 'standard' },
16
+ :author => { :type => 'string', :index => 'not_analyzed' }
17
+ }
18
+ }
19
+ }
20
+ )
21
+ wait_for_index(@name)
22
+ end
23
+ end
24
+
25
+ after do
26
+ @index.delete if @index.exists?
27
+ end
28
+
29
+ it 'creates warmers' do
30
+ h = @index.warmer('test1').create(:query => { :match_all => {}})
31
+ assert_acknowledged h
32
+ end
33
+
34
+ it 'deletes warmers' do
35
+ @index.warmer('test1').create(:query => { :match_all => {}})
36
+
37
+ h = @index.warmer('test1').delete
38
+ assert_acknowledged h
39
+ end
40
+
41
+ it 'gets warmers' do
42
+ body = { "query" => {"match_all" => {}}}
43
+ @index.warmer('test1').create(body)
44
+
45
+ h = @index.warmer('test1').get
46
+ assert_equal body, h[@name]["warmers"]["test1"]["source"]
47
+ end
48
+
49
+ it 'knows when warmers exist' do
50
+ assert_equal false, @index.warmer('test1').exists?
51
+ assert_equal false, @index.warmer('test1').exist?
52
+
53
+ h = @index.warmer('test1').create(:query => { :match_all => {}})
54
+ assert_equal true, @index.warmer('test1').exists?
55
+ end
56
+ end
@@ -0,0 +1,86 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Client do
4
+
5
+ it 'uses the adapter specified at creation' do
6
+ c = Elastomer::Client.new(:adapter => :test)
7
+ assert_includes c.connection.builder.handlers, Faraday::Adapter::Test
8
+ end
9
+
10
+ it "use Faraday's default adapter if none is specified" do
11
+ c = Elastomer::Client.new
12
+ adapter = Faraday::Adapter.lookup_middleware(Faraday.default_adapter)
13
+ assert_includes c.connection.builder.handlers, adapter
14
+ end
15
+
16
+ it 'uses the same connection for all requests' do
17
+ c = $client.connection
18
+ assert_same c, $client.connection
19
+ end
20
+
21
+ it 'raises an error for unknown HTTP request methods' do
22
+ assert_raises(ArgumentError) { $client.request :foo, '/', {} }
23
+ end
24
+
25
+ it 'raises an error on 4XX responses with an `error` field' do
26
+ begin
27
+ $client.get '/non-existent-index/_search?q=*:*'
28
+ assert false, 'exception was not raised when it should have been'
29
+ rescue Elastomer::Client::Error => err
30
+ assert_equal 404, err.status
31
+ assert_equal 'IndexMissingException[[non-existent-index] missing]', err.message
32
+ end
33
+ end
34
+
35
+ it 'handles path expansions' do
36
+ uri = $client.expand_path '/{foo}/{bar}', :foo => '_cluster', :bar => 'health'
37
+ assert_equal '/_cluster/health', uri
38
+
39
+ uri = $client.expand_path '{/foo}{/baz}{/bar}', :foo => '_cluster', :bar => 'state'
40
+ assert_equal '/_cluster/state', uri
41
+ end
42
+
43
+ it 'handles query parameters' do
44
+ uri = $client.expand_path '/_cluster/health', :level => 'shards'
45
+ assert_equal '/_cluster/health?level=shards', uri
46
+ end
47
+
48
+ it 'validates path expansions' do
49
+ assert_raises(ArgumentError) {
50
+ $client.expand_path '/{foo}/{bar}', :foo => '_cluster', :bar => nil
51
+ }
52
+
53
+ assert_raises(ArgumentError) {
54
+ $client.expand_path '/{foo}/{bar}', :foo => '_cluster', :bar => ''
55
+ }
56
+ end
57
+
58
+ describe 'when validating parameters' do
59
+ it 'rejects nil values' do
60
+ assert_raises(ArgumentError) { $client.assert_param_presence nil }
61
+ end
62
+
63
+ it 'rejects empty strings' do
64
+ assert_raises(ArgumentError) { $client.assert_param_presence "" }
65
+ assert_raises(ArgumentError) { $client.assert_param_presence " " }
66
+ assert_raises(ArgumentError) { $client.assert_param_presence " \t \r \n " }
67
+ end
68
+
69
+ it 'rejects empty strings and nil values found in arrays' do
70
+ assert_raises(ArgumentError) { $client.assert_param_presence ['foo', nil, 'bar'] }
71
+ assert_raises(ArgumentError) { $client.assert_param_presence ['baz', " \t \r \n "] }
72
+ end
73
+
74
+ it 'strips whitespace from strings' do
75
+ assert_equal 'foo', $client.assert_param_presence(" foo \t")
76
+ end
77
+
78
+ it 'joins array values into a string' do
79
+ assert_equal 'foo,bar', $client.assert_param_presence(%w[foo bar])
80
+ end
81
+
82
+ it 'flattens arrays' do
83
+ assert_equal 'foo,bar,baz,buz', $client.assert_param_presence([" foo \t", %w[bar baz buz]])
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,46 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+ require 'elastomer/core_ext/time'
3
+
4
+ describe 'JSON conversions for Time' do
5
+ before do
6
+ @name = 'elastomer-time-test'
7
+ @index = $client.index(@name)
8
+
9
+ unless @index.exists?
10
+ @index.create \
11
+ :settings => { 'index.number_of_shards' => 1, 'index.number_of_replicas' => 0 },
12
+ :mappings => {
13
+ :doc1 => {
14
+ :_source => { :enabled => true }, :_all => { :enabled => false },
15
+ :properties => {
16
+ :title => { :type => 'string', :index => 'not_analyzed' },
17
+ :created_at => { :type => 'date' }
18
+ }
19
+ }
20
+ }
21
+
22
+ wait_for_index(@name)
23
+ end
24
+
25
+ @docs = @index.docs
26
+ end
27
+
28
+ after do
29
+ @index.delete if @index.exists?
30
+ end
31
+
32
+ it 'generates ISO8601 formatted time strings' do
33
+ time = Time.utc(2013, 5, 3, 10, 1, 31)
34
+ assert_equal '"2013-05-03T10:01:31Z"', MultiJson.encode(time)
35
+ end
36
+
37
+ it 'indexes time fields' do
38
+ time = Time.utc(2013, 5, 3, 10, 1, 31)
39
+ h = @docs.index({:title => 'test document', :created_at => time}, :type => 'doc1')
40
+
41
+ assert_created(h)
42
+
43
+ doc = @docs.get(:type => 'doc1', :id => h['_id'])
44
+ assert_equal '2013-05-03T10:01:31Z', doc['_source']['created_at']
45
+ end
46
+ end
@@ -0,0 +1,53 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Middleware::EncodeJson do
4
+ let(:middleware) { Elastomer::Middleware::EncodeJson.new(lambda {|env| env})}
5
+
6
+ def process(body, content_type = nil)
7
+ env = { :body => body, :request_headers => Faraday::Utils::Headers.new }
8
+ env[:request_headers]['content-type'] = content_type if content_type
9
+ middleware.call(env)
10
+ end
11
+
12
+ it 'handles no body' do
13
+ result = process(nil)
14
+ assert_nil result[:body]
15
+ assert_nil result[:request_headers]['content-type']
16
+ end
17
+
18
+ it 'handles empty body' do
19
+ result = process('')
20
+ assert_empty result[:body]
21
+ assert_nil result[:request_headers]['content-type']
22
+ end
23
+
24
+ it 'handles string body' do
25
+ result = process('{"a":1}')
26
+ assert_equal '{"a":1}', result[:body]
27
+ assert_equal 'application/json', result[:request_headers]['content-type']
28
+ end
29
+
30
+ it 'handles object body' do
31
+ result = process({:a => 1})
32
+ assert_equal '{"a":1}', result[:body]
33
+ assert_equal 'application/json', result[:request_headers]['content-type']
34
+ end
35
+
36
+ it 'handles empty object body' do
37
+ result = process({})
38
+ assert_equal '{}', result[:body]
39
+ assert_equal 'application/json', result[:request_headers]['content-type']
40
+ end
41
+
42
+ it 'handles object body with json type' do
43
+ result = process({:a => 1}, 'application/json; charset=utf-8')
44
+ assert_equal '{"a":1}', result[:body]
45
+ assert_equal 'application/json; charset=utf-8', result[:request_headers]['content-type']
46
+ end
47
+
48
+ it 'handles object body with incompatible type' do
49
+ result = process({:a => 1}, 'application/xml; charset=utf-8')
50
+ assert_equal({:a => 1}, result[:body])
51
+ assert_equal 'application/xml; charset=utf-8', result[:request_headers]['content-type']
52
+ end
53
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Middleware::OpaqueId do
4
+
5
+ before do
6
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
7
+ stub.get('/_cluster/health') { |env|
8
+ [ 200,
9
+
10
+ { 'X-Opaque-Id' => env[:request_headers]['X-Opaque-Id'],
11
+ 'Content-Type' => 'application/json; charset=UTF-8',
12
+ 'Content-Length' => '49' },
13
+
14
+ %q[{"cluster_name":"elasticsearch","status":"green"}]
15
+ ]
16
+ }
17
+
18
+ stub.get('/_cluster/state') { |env|
19
+ [ 200, {'X-Opaque-Id' => "00000000-0000-0000-0000-000000000000"}, %q[{"foo":"bar"}] ]
20
+ }
21
+ end
22
+
23
+ opts = $client_params.merge \
24
+ :opaque_id => true,
25
+ :adapter => [:test, stubs]
26
+
27
+ @client = Elastomer::Client.new opts
28
+ end
29
+
30
+ it 'generates an "X-Opaque-Id" header' do
31
+ health = @client.cluster.health
32
+ assert_equal({"cluster_name" => "elasticsearch", "status" => "green"}, health)
33
+ end
34
+
35
+ it 'raises an exception on conflicting headers' do
36
+ assert_raises(Elastomer::Client::OpaqueIdError) { @client.cluster.state }
37
+ end
38
+
39
+ end
@@ -0,0 +1,54 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Middleware::ParseJson do
4
+ let(:middleware) { Elastomer::Middleware::ParseJson.new(lambda {|env| Faraday::Response.new(env)})}
5
+ let(:headers) { Hash.new }
6
+
7
+ def process(body, content_type = nil)
8
+ env = { :body => body, :response_headers => Faraday::Utils::Headers.new(headers) }
9
+ env[:response_headers]['content-type'] = content_type if content_type
10
+ middleware.call(env)
11
+ end
12
+
13
+ it "doesn't change nil body" do
14
+ response = process(nil)
15
+ assert_nil response.body
16
+ end
17
+
18
+ it 'nullifies empty body' do
19
+ response = process("")
20
+ assert_nil response.body
21
+ end
22
+
23
+ it 'nullifies blank body' do
24
+ response = process(" ")
25
+ assert_nil response.body
26
+ end
27
+
28
+ it 'parses json body with empty type' do
29
+ response = process('{"a":1}')
30
+ assert_equal({'a' => 1}, response.body)
31
+ end
32
+
33
+ it 'parses json body of correct type' do
34
+ response = process('{"a":1}', 'application/json; charset=utf-8')
35
+ assert_equal({'a' => 1}, response.body)
36
+ end
37
+
38
+ it 'ignores json body if incorrect type' do
39
+ response = process('{"a":1}', 'application/xml; charset=utf-8')
40
+ assert_equal('{"a":1}', response.body)
41
+ end
42
+
43
+ it 'chokes on invalid json' do
44
+ assert_raises(Faraday::Error::ParsingError) { process '{!' }
45
+ assert_raises(Faraday::Error::ParsingError) { process 'invalid' }
46
+
47
+ # surprisingly these are all valid according to MultiJson
48
+ #
49
+ # assert_raises(Faraday::Error::ParsingError) { process '"a"' }
50
+ # assert_raises(Faraday::Error::ParsingError) { process 'true' }
51
+ # assert_raises(Faraday::Error::ParsingError) { process 'null' }
52
+ # assert_raises(Faraday::Error::ParsingError) { process '1' }
53
+ end
54
+ end
@@ -0,0 +1,94 @@
1
+ require 'rubygems' unless defined? Gem
2
+ require 'bundler'
3
+ Bundler.require(:default, :development)
4
+
5
+ if ENV['COVERAGE'] == 'true'
6
+ require 'simplecov'
7
+ SimpleCov.start do
8
+ add_filter "/test/"
9
+ add_filter "/vendor/"
10
+ end
11
+ end
12
+
13
+ require 'minitest/spec'
14
+ require 'minitest/autorun'
15
+
16
+ # push the lib folder onto the load path
17
+ $LOAD_PATH.unshift 'lib'
18
+ require 'elastomer/client'
19
+
20
+ # we are going to use the same client instance everywhere!
21
+ # the client should always be stateless
22
+ $client_params = {
23
+ :port => ENV['BOXEN_ELASTICSEARCH_PORT'] || 9200,
24
+ :read_timeout => 2,
25
+ :open_timeout => 1,
26
+ :opaque_id => true
27
+ }
28
+ $client = Elastomer::Client.new $client_params
29
+
30
+ # ensure we have an ElasticSearch server to test with
31
+ raise "No server available at #{$client.url}" unless $client.available?
32
+
33
+ # remove any lingering test indices from the cluster
34
+ MiniTest::Unit.after_tests do
35
+ $client.cluster.indices.keys.each do |name|
36
+ next unless name =~ /^elastomer-/i
37
+ $client.index(name).delete
38
+ end
39
+
40
+ $client.cluster.templates.keys.each do |name|
41
+ next unless name =~ /^elastomer-/i
42
+ $client.template(name).delete
43
+ end
44
+ end
45
+
46
+ # add custom assertions
47
+ require File.expand_path('../assertions', __FILE__)
48
+
49
+ # require 'elastomer/notifications'
50
+ # require 'pp'
51
+
52
+ # ActiveSupport::Notifications.subscribe('request.client.elastomer') do |name, start_time, end_time, transaction_id, payload|
53
+ # $stdout.puts '-'*100
54
+ # #$stdout.puts "-- #{payload[:action].inspect}"
55
+ # pp payload #if payload[:action].nil?
56
+ # end
57
+
58
+ # Wait for an index to be created. Since index creation requests return
59
+ # before the index is actually ready to receive documents, one needs to wait
60
+ # until the cluster status recovers before proceeding.
61
+ #
62
+ # name - The index name to wait for
63
+ # status - The status to wait for. Defaults to yellow. Yellow is the
64
+ # preferred status for tests, because it waits for at least one
65
+ # shard to be active, but doesn't wait for all replicas. Single
66
+ # node clusters will never achieve green status with the default
67
+ # setting of 1 replica.
68
+ #
69
+ # Returns the cluster health response.
70
+ # Raises Elastomer::Client::TimeoutError if requested status is not achieved
71
+ # within 5 seconds.
72
+ def wait_for_index(name, status='yellow')
73
+ $client.cluster.health(
74
+ :index => name,
75
+ :wait_for_status => status,
76
+ :timeout => '5s'
77
+ )
78
+ end
79
+
80
+ # Elasticsearch 1.0 changed some request formats in a non-backward-compatible
81
+ # way. Some tests need to know what version is running to structure requests
82
+ # as expected.
83
+ #
84
+ # Returns true if Elasticsearch version is 1.x.
85
+ def es_version_1_x?
86
+ $client.semantic_version >= '1.0.0'
87
+ end
88
+
89
+ # Elasticsearch 1.2 removed support for gateway snapshots.
90
+ #
91
+ # Returns true if Elasticsearch version supports gateway snapshots.
92
+ def es_version_supports_gateway_snapshots?
93
+ $client.semantic_version <= '1.2.0'
94
+ end