elasticsearch-transport 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +13 -0
  4. data/README.md +276 -0
  5. data/Rakefile +67 -0
  6. data/elasticsearch-transport.gemspec +52 -0
  7. data/lib/elasticsearch-transport.rb +1 -0
  8. data/lib/elasticsearch/transport.rb +29 -0
  9. data/lib/elasticsearch/transport/client.rb +123 -0
  10. data/lib/elasticsearch/transport/extensions/test_cluster.rb +163 -0
  11. data/lib/elasticsearch/transport/transport/base.rb +236 -0
  12. data/lib/elasticsearch/transport/transport/connections/collection.rb +93 -0
  13. data/lib/elasticsearch/transport/transport/connections/connection.rb +117 -0
  14. data/lib/elasticsearch/transport/transport/connections/selector.rb +63 -0
  15. data/lib/elasticsearch/transport/transport/errors.rb +73 -0
  16. data/lib/elasticsearch/transport/transport/http/curb.rb +70 -0
  17. data/lib/elasticsearch/transport/transport/http/faraday.rb +59 -0
  18. data/lib/elasticsearch/transport/transport/response.rb +20 -0
  19. data/lib/elasticsearch/transport/transport/serializer/multi_json.rb +36 -0
  20. data/lib/elasticsearch/transport/transport/sniffer.rb +46 -0
  21. data/lib/elasticsearch/transport/version.rb +5 -0
  22. data/test/integration/client_test.rb +117 -0
  23. data/test/integration/transport_test.rb +37 -0
  24. data/test/profile/client_benchmark_test.rb +107 -0
  25. data/test/test_extensions.rb +139 -0
  26. data/test/test_helper.rb +58 -0
  27. data/test/unit/client_test.rb +109 -0
  28. data/test/unit/connection_collection_test.rb +83 -0
  29. data/test/unit/connection_selector_test.rb +64 -0
  30. data/test/unit/connection_test.rb +90 -0
  31. data/test/unit/serializer_test.rb +16 -0
  32. data/test/unit/sniffer_test.rb +146 -0
  33. data/test/unit/transport_base_test.rb +402 -0
  34. data/test/unit/transport_curb_test.rb +59 -0
  35. data/test/unit/transport_faraday_test.rb +73 -0
  36. metadata +342 -0
@@ -0,0 +1,20 @@
1
+ module Elasticsearch
2
+ module Transport
3
+ module Transport
4
+
5
+ # Wraps the response from Elasticsearch.
6
+ #
7
+ class Response
8
+ attr_reader :status, :body, :headers
9
+
10
+ # @param status [Integer] Response status code
11
+ # @param body [String] Response body
12
+ # @param headers [Hash] Response headers
13
+ def initialize(status, body, headers={})
14
+ @status, @body, @headers = status, body, headers
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ module Elasticsearch
2
+ module Transport
3
+ module Transport
4
+ module Serializer
5
+
6
+ # An abstract class for implementing serializer implementations
7
+ #
8
+ module Base
9
+ # @param transport [Object] The instance of transport which uses this serializer
10
+ #
11
+ def initialize(transport=nil)
12
+ @transport = transport
13
+ end
14
+ end
15
+
16
+ # A default JSON serializer (using [MultiJSON](http://rubygems.org/gems/multi_json))
17
+ #
18
+ class MultiJson
19
+ include Base
20
+
21
+ # De-serialize a Hash from JSON string
22
+ #
23
+ def load(string, options={})
24
+ ::MultiJson.load(string, options)
25
+ end
26
+
27
+ # Serialize a Hash to JSON string
28
+ #
29
+ def dump(object, options={})
30
+ ::MultiJson.dump(object, options)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ module Elasticsearch
2
+ module Transport
3
+ module Transport
4
+
5
+ # Handles node discovery ("sniffing").
6
+ #
7
+ class Sniffer
8
+ RE_URL = /\/([^:]*):([0-9]+)\]/ # Use named groups on Ruby 1.9: /\/(?<host>[^:]*):(?<port>[0-9]+)\]/
9
+
10
+ attr_reader :transport
11
+ attr_accessor :timeout
12
+
13
+ # @param transport [Object] A transport instance.
14
+ #
15
+ def initialize(transport)
16
+ @transport = transport
17
+ @timeout = transport.options[:sniffer_timeout] || 1
18
+ end
19
+
20
+ # Retrieves the node list from the Elasticsearch's
21
+ # [_Nodes Info API_](http://www.elasticsearch.org/guide/reference/api/admin-cluster-nodes-info/)
22
+ # and returns a normalized Array of information suitable for passing to transport.
23
+ #
24
+ # Shuffles the collection before returning it when the `randomize_hosts` option is set for transport.
25
+ #
26
+ # @return [Array<Hash>]
27
+ # @raise [SnifferTimeoutError]
28
+ #
29
+ def hosts
30
+ Timeout::timeout(timeout, SnifferTimeoutError) do
31
+ nodes = transport.perform_request('GET', '_cluster/nodes').body
32
+ hosts = nodes['nodes'].map do |id,info|
33
+ if matches = info["#{transport.protocol}_address"].to_s.match(RE_URL)
34
+ # TODO: Implement lightweight "indifferent access" here
35
+ info.merge :host => matches[1], :port => matches[2], :id => id
36
+ end
37
+ end.compact
38
+
39
+ hosts.shuffle! if transport.options[:randomize_hosts]
40
+ hosts
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ module Elasticsearch
2
+ module Transport
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
@@ -0,0 +1,117 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::ClientIntegrationTest < Elasticsearch::Test::IntegrationTestCase
4
+ startup do
5
+ Elasticsearch::TestCluster.start if ENV['SERVER'] and not Elasticsearch::TestCluster.running?
6
+ end
7
+
8
+ context "Elasticsearch client" do
9
+ setup do
10
+ system "curl -X DELETE http://localhost:9250/_all > /dev/null 2>&1"
11
+
12
+ @logger = Logger.new(STDERR)
13
+ @logger.formatter = proc do |severity, datetime, progname, msg|
14
+ color = case severity
15
+ when /INFO/ then :green
16
+ when /ERROR|WARN|FATAL/ then :red
17
+ when /DEBUG/ then :cyan
18
+ else :white
19
+ end
20
+ ANSI.ansi(severity[0] + ' ', color, :faint) + ANSI.ansi(msg, :white, :faint) + "\n"
21
+ end
22
+
23
+ @client = Elasticsearch::Client.new host: 'localhost:9250'
24
+ end
25
+
26
+ should "connect to the cluster" do
27
+ assert_nothing_raised do
28
+ response = @client.perform_request 'GET', '_cluster/health'
29
+ assert_equal 2, response.body['number_of_nodes']
30
+ end
31
+ end
32
+
33
+ should "handle paths and URL parameters" do
34
+ @client.perform_request 'PUT', 'myindex/mydoc/1', {routing: 'XYZ'}, {foo: 'bar'}
35
+ response = @client.perform_request 'GET', 'myindex/mydoc/1?routing=XYZ'
36
+ assert_equal true, response.body['exists']
37
+ assert_equal 'bar', response.body['_source']['foo']
38
+ assert_raise Elasticsearch::Transport::Transport::Errors::NotFound do
39
+ response = @client.perform_request 'GET', 'myindex/mydoc/1?routing=ABC'
40
+ assert_nil response.body['_source']
41
+ assert_equal false, response.body['exists']
42
+ end
43
+ end
44
+
45
+ context "with round robin selector" do
46
+ setup do
47
+ @client = Elasticsearch::Client.new \
48
+ hosts: %w| localhost:9250 localhost:9251 |,
49
+ logger: @logger
50
+ end
51
+
52
+ should "rotate nodes" do
53
+ # Hit node 1
54
+ response = @client.perform_request 'GET', '_cluster/nodes/_local'
55
+ assert_equal 'node-1', response.body['nodes'].to_a[0][1]['name']
56
+
57
+ # Hit node 2
58
+ response = @client.perform_request 'GET', '_cluster/nodes/_local'
59
+ assert_equal 'node-2', response.body['nodes'].to_a[0][1]['name']
60
+
61
+ # Hit node 1
62
+ response = @client.perform_request 'GET', '_cluster/nodes/_local'
63
+ assert_equal 'node-1', response.body['nodes'].to_a[0][1]['name']
64
+ end
65
+ end
66
+
67
+ context "with a sick node and retry on failure" do
68
+ setup do
69
+ @client = Elasticsearch::Client.new \
70
+ hosts: %w| localhost:9250 foobar1 |,
71
+ logger: @logger,
72
+ retry_on_failure: true
73
+ end
74
+
75
+ should "retry the request with next server" do
76
+ assert_nothing_raised do
77
+ 5.times { @client.perform_request 'GET', '_cluster/nodes/_local' }
78
+ end
79
+ end
80
+
81
+ should "raise exception when it cannot get any healthy server" do
82
+ @client = Elasticsearch::Client.new \
83
+ hosts: %w| localhost:9250 foobar1 foobar2 foobar3 |,
84
+ logger: @logger,
85
+ retry_on_failure: 1
86
+
87
+ assert_nothing_raised do
88
+ # First hit is OK
89
+ @client.perform_request 'GET', '_cluster/nodes/_local'
90
+ end
91
+
92
+ assert_raise Faraday::Error::ConnectionFailed do
93
+ # Second hit fails
94
+ @client.perform_request 'GET', '_cluster/nodes/_local'
95
+ end
96
+ end
97
+ end
98
+
99
+ context "with a sick node and reloading on failure" do
100
+ setup do
101
+ @client = Elasticsearch::Client.new \
102
+ hosts: %w| localhost:9250 foobar1 foobar2 |,
103
+ logger: @logger,
104
+ reload_on_failure: true
105
+ end
106
+
107
+ should "reload the connections" do
108
+ assert_equal 3, @client.transport.connections.size
109
+ assert_nothing_raised do
110
+ 5.times { @client.perform_request 'GET', '_cluster/nodes/_local' }
111
+ end
112
+ assert_equal 2, @client.transport.connections.size
113
+ end
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::ClientIntegrationTest < Elasticsearch::Test::IntegrationTestCase
4
+ startup do
5
+ Elasticsearch::TestCluster.start if ENV['SERVER'] and not Elasticsearch::TestCluster.running?
6
+ end
7
+
8
+ context "Transport" do
9
+ should "allow to customize the Faraday adapter" do
10
+ require 'typhoeus'
11
+ require 'typhoeus/adapters/faraday'
12
+
13
+ transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new \
14
+ :hosts => [ { :host => 'localhost', :port => '9250' } ] do |f|
15
+ f.response :logger
16
+ f.adapter :typhoeus
17
+ end
18
+
19
+ client = Elasticsearch::Transport::Client.new transport: transport
20
+ client.perform_request 'GET', ''
21
+ end
22
+
23
+ should "use the Curb client" do
24
+ require 'curb'
25
+ require 'elasticsearch/transport/transport/http/curb'
26
+
27
+ transport = Elasticsearch::Transport::Transport::HTTP::Curb.new \
28
+ :hosts => [ { :host => 'localhost', :port => '9250' } ] do |curl|
29
+ curl.verbose = true
30
+ end
31
+
32
+ client = Elasticsearch::Transport::Client.new transport: transport
33
+ client.perform_request 'GET', ''
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,107 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::ClientProfilingTest < Elasticsearch::Test::ProfilingTest
4
+ startup do
5
+ Elasticsearch::TestCluster.start if ENV['SERVER'] and not Elasticsearch::TestCluster.running?
6
+ end
7
+
8
+ context "Elasticsearch client benchmark" do
9
+ setup do
10
+ client = Elasticsearch::Client.new host: 'localhost:9250'
11
+ client.perform_request 'DELETE', '/ruby_test_benchmark/' rescue nil
12
+ client.perform_request 'POST', '/ruby_test_benchmark/', {index: {number_of_shards: 1, number_of_replicas: 0}}
13
+ 100.times do client.perform_request 'POST', '/ruby_test_benchmark_search/test/', {}, {foo: 'bar'}; end
14
+ client.perform_request 'POST', '/ruby_test_benchmark_search/_refresh'
15
+ end
16
+ teardown do
17
+ client = Elasticsearch::Client.new host: 'localhost:9250'
18
+ client.perform_request 'DELETE', '/ruby_test_benchmark/'
19
+ client.perform_request 'DELETE', '/ruby_test_benchmark_search/'
20
+ end
21
+
22
+ context "with a single-node cluster" do
23
+ setup do
24
+ @client = Elasticsearch::Client.new hosts: 'localhost:9250'
25
+ end
26
+
27
+ measure "get the cluster info", count: 1_000 do
28
+ @client.perform_request 'GET', ''
29
+ end
30
+
31
+ measure "index a document" do
32
+ @client.perform_request 'POST', '/ruby_test_benchmark/test/', {}, {foo: 'bar'}
33
+ end
34
+
35
+ measure "search" do
36
+ @client.perform_request 'POST', '/ruby_test_benchmark_search/test/_search', {}, {query: {match: {foo: 'bar'}}}
37
+ end
38
+ end
39
+
40
+ context "with a two-node cluster" do
41
+ setup do
42
+ @client = Elasticsearch::Client.new hosts: ['localhost:9250', 'localhost:9251']
43
+ end
44
+
45
+ measure "get the cluster info", count: 1_000 do
46
+ @client.perform_request 'GET', ''
47
+ end
48
+
49
+ measure "index a document"do
50
+ @client.perform_request 'POST', '/ruby_test_benchmark/test/', {}, {foo: 'bar'}
51
+ end
52
+
53
+ measure "search" do
54
+ @client.perform_request 'POST', '/ruby_test_benchmark_search/test/_search', {}, {query: {match: {foo: 'bar'}}}
55
+ end
56
+ end
57
+
58
+ context "with a single-node cluster and the Curb client" do
59
+ setup do
60
+ require 'curb'
61
+ require 'elasticsearch/transport/transport/http/curb'
62
+ @client = Elasticsearch::Client.new host: 'localhost:9250',
63
+ transport_class: Elasticsearch::Transport::Transport::HTTP::Curb
64
+ end
65
+
66
+ measure "get the cluster info", count: 1_000 do
67
+ @client.perform_request 'GET', ''
68
+ end
69
+
70
+ measure "index a document" do
71
+ @client.perform_request 'POST', '/ruby_test_benchmark/test/', {}, {foo: 'bar'}
72
+ end
73
+
74
+ measure "search" do
75
+ @client.perform_request 'POST', '/ruby_test_benchmark_search/test/_search', {}, {query: {match: {foo: 'bar'}}}
76
+ end
77
+ end
78
+
79
+ context "with a single-node cluster and the Typhoeus client" do
80
+ setup do
81
+ require 'typhoeus'
82
+ require 'typhoeus/adapters/faraday'
83
+
84
+ transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new \
85
+ :hosts => [ { :host => 'localhost', :port => '9250' } ] do |f|
86
+ f.adapter :typhoeus
87
+ end
88
+
89
+ @client = Elasticsearch::Client.new transport: transport
90
+ end
91
+
92
+ measure "get the cluster info", count: 1_000 do
93
+ @client.perform_request 'GET', ''
94
+ end
95
+
96
+ measure "index a document" do
97
+ @client.perform_request 'POST', '/ruby_test_benchmark/test/', {}, {foo: 'bar'}
98
+ end
99
+
100
+ measure "search" do
101
+ @client.perform_request 'POST', '/ruby_test_benchmark_search/test/_search', {}, {query: {match: {foo: 'bar'}}}
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ end
@@ -0,0 +1,139 @@
1
+ require 'benchmark'
2
+ require 'ruby-prof'
3
+ require 'ansi/code'
4
+ require 'ansi/terminal'
5
+
6
+ module Elasticsearch
7
+ module Test
8
+
9
+ # Startup/shutdown support for test suites
10
+ #
11
+ # Example:
12
+ #
13
+ # class MyTest < Test::Unit::TestCase
14
+ # extend IntegrationTestStartupShutdown
15
+ #
16
+ # startup { puts "Suite starting up..." }
17
+ # shutdown { puts "Suite shutting down..." }
18
+ # end
19
+ #
20
+ # *** IMPORTANT NOTE: **********************************************************
21
+ #
22
+ # You have to register the handler for shutdown before requiring 'test/unit':
23
+ #
24
+ # # File: test_helper.rb
25
+ # at_exit { MyTest.__run_at_exit_hooks }
26
+ # require 'test/unit'
27
+ #
28
+ # The API follows Test::Unit 2.0
29
+ # <https://github.com/test-unit/test-unit/blob/master/lib/test/unit/testcase.rb>
30
+ #
31
+ module IntegrationTestStartupShutdown
32
+ @@started = false
33
+ @@shutdown_blocks ||= []
34
+
35
+ def startup &block
36
+ return if started?
37
+ @@started = true
38
+ yield block if block_given?
39
+ end
40
+
41
+ def shutdown &block
42
+ @@shutdown_blocks << block if block_given?
43
+ end
44
+
45
+ def started?
46
+ !! @@started
47
+ end
48
+
49
+ def __run_at_exit_hooks
50
+ return unless started?
51
+ STDERR.puts ANSI.faint("Running at_exit hooks...")
52
+ puts ANSI.faint('-'*80)
53
+ @@shutdown_blocks.each { |b| b.call }
54
+ puts ANSI.faint('-'*80)
55
+ end
56
+ end
57
+
58
+ # Profiling support for tests with [ruby-prof](https://github.com/ruby-prof/ruby-prof)
59
+ #
60
+ # Example:
61
+ #
62
+ # measure "divide numbers", count: 10_000 do
63
+ # assert_nothing_raised { 1/2 }
64
+ # end
65
+ #
66
+ # Will print out something like this along your test output:
67
+ #
68
+ # ---------------------------------------------------------------------
69
+ # Context: My benchmark should divide numbers (10000x)
70
+ # mean: 0.01ms | avg: 0.01ms | max: 6.19ms
71
+ # ---------------------------------------------------------------------
72
+ # ...
73
+ # Total: 0.313283
74
+ #
75
+ # %self total self wait child calls name
76
+ # 25.38 0.313 0.079 0.000 0.234 1 <Object::MyTets>#__bind_1368638677_723101
77
+ # 14.42 0.118 0.045 0.000 0.073 20000 <Class::Time>#now
78
+ # 7.57 0.088 0.033 0.000 0.055 10000 Time#-
79
+ # ...
80
+ #
81
+ # PASS (0:00:00.322) test: My benchmark should divide numbers (10000x).
82
+ #
83
+ #
84
+ module ProfilingTestSupport
85
+
86
+ # Profiles the passed block of code.
87
+ #
88
+ # measure "divide numbers", count: 10_000 do
89
+ # assert_nothing_raised { 1/2 }
90
+ # end
91
+ #
92
+ # @todo Try to make progress bar not interfere with tests
93
+ #
94
+ def measure(name, options={}, &block)
95
+ # require 'pry'; binding.pry
96
+ ___ = '-'*ANSI::Terminal.terminal_width
97
+ test_name = self.name.split('::').last
98
+ context_name = self.context(nil) {}.first.parent.name
99
+ count = Integer(ENV['COUNT'] || options[:count] || 1_000)
100
+ ticks = []
101
+ # progress = ANSI::Progressbar.new("#{name} (#{count}x)", count)
102
+
103
+ should "#{name} (#{count}x)" do
104
+ RubyProf.start
105
+
106
+ count.times do
107
+ ticks << Benchmark.realtime { self.instance_eval(&block) }
108
+ # RubyProf.pause
109
+ # progress.inc
110
+ # RubyProf.resume
111
+ end
112
+
113
+ result = RubyProf.stop
114
+ # progress.finish
115
+
116
+ total = result.threads.reduce(0) { |total,info| total += info.total_time; total }
117
+ mean = (ticks.sort[(ticks.size/2).round-1])*1000
118
+ avg = (ticks.inject {|sum,el| sum += el; sum}.to_f/ticks.size)*1000
119
+ max = ticks.max*1000
120
+
121
+
122
+ result.eliminate_methods!([/Integer#times|Benchmark.realtime|ANSI::Code#.*|ANSI::ProgressBar#.*/])
123
+ printer = RubyProf::FlatPrinter.new(result)
124
+ # printer = RubyProf::GraphPrinter.new(result)
125
+
126
+ puts "\n",
127
+ ___,
128
+ 'Context: ' + ANSI.bold(context_name) + ' should ' + ANSI.bold(name) + " (#{count}x)",
129
+ "mean: #{sprintf('%.2f', mean)}ms | " +
130
+ "avg: #{sprintf('%.2f', avg)}ms | " +
131
+ "max: #{sprintf('%.2f', max)}ms",
132
+ ___
133
+ printer.print(STDOUT, {}) unless ENV['QUIET'] || options[:quiet]
134
+ end
135
+ end
136
+ end
137
+
138
+ end
139
+ end