elasticsearch-transport 0.0.2

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