elasticsearch-transport-pixlee 1.0.13

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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE.txt +13 -0
  5. data/README.md +441 -0
  6. data/Rakefile +80 -0
  7. data/elasticsearch-transport.gemspec +74 -0
  8. data/lib/elasticsearch-transport.rb +1 -0
  9. data/lib/elasticsearch/transport.rb +30 -0
  10. data/lib/elasticsearch/transport/client.rb +195 -0
  11. data/lib/elasticsearch/transport/transport/base.rb +261 -0
  12. data/lib/elasticsearch/transport/transport/connections/collection.rb +93 -0
  13. data/lib/elasticsearch/transport/transport/connections/connection.rb +121 -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 +87 -0
  17. data/lib/elasticsearch/transport/transport/http/faraday.rb +60 -0
  18. data/lib/elasticsearch/transport/transport/http/manticore.rb +124 -0
  19. data/lib/elasticsearch/transport/transport/response.rb +21 -0
  20. data/lib/elasticsearch/transport/transport/serializer/multi_json.rb +36 -0
  21. data/lib/elasticsearch/transport/transport/sniffer.rb +46 -0
  22. data/lib/elasticsearch/transport/version.rb +5 -0
  23. data/test/integration/client_test.rb +144 -0
  24. data/test/integration/transport_test.rb +73 -0
  25. data/test/profile/client_benchmark_test.rb +125 -0
  26. data/test/test_helper.rb +76 -0
  27. data/test/unit/client_test.rb +274 -0
  28. data/test/unit/connection_collection_test.rb +88 -0
  29. data/test/unit/connection_selector_test.rb +64 -0
  30. data/test/unit/connection_test.rb +100 -0
  31. data/test/unit/response_test.rb +15 -0
  32. data/test/unit/serializer_test.rb +16 -0
  33. data/test/unit/sniffer_test.rb +145 -0
  34. data/test/unit/transport_base_test.rb +478 -0
  35. data/test/unit/transport_curb_test.rb +97 -0
  36. data/test/unit/transport_faraday_test.rb +140 -0
  37. data/test/unit/transport_manticore_test.rb +118 -0
  38. metadata +408 -0
@@ -0,0 +1,124 @@
1
+ require 'manticore'
2
+
3
+ module Elasticsearch
4
+ module Transport
5
+ module Transport
6
+ module HTTP
7
+ # Alternative HTTP transport implementation for JRuby,
8
+ # using the [_Manticore_](https://github.com/cheald/manticore) client,
9
+ #
10
+ # @example HTTP
11
+ #
12
+ # require 'elasticsearch/transport/transport/http/manticore'
13
+ #
14
+ # client = Elasticsearch::Client.new transport_class: Elasticsearch::Transport::Transport::HTTP::Manticore
15
+ #
16
+ # client.transport.connections.first.connection
17
+ # => #<Manticore::Client:0x56bf7ca6 ...>
18
+ #
19
+ # client.info['status']
20
+ # => 200
21
+ #
22
+ # @example HTTPS (All SSL settings are optional,
23
+ # see http://www.rubydoc.info/gems/manticore/Manticore/Client:initialize)
24
+ #
25
+ # require 'elasticsearch/transport/transport/http/manticore'
26
+ #
27
+ # client = Elasticsearch::Client.new \
28
+ # url: 'https://elasticsearch.example.com',
29
+ # transport_class: Elasticsearch::Transport::Transport::HTTP::Manticore,
30
+ # ssl: {
31
+ # truststore: '/tmp/truststore.jks',
32
+ # truststore_password: 'password',
33
+ # keystore: '/tmp/keystore.jks',
34
+ # keystore_password: 'secret',
35
+ # }
36
+ #
37
+ # client.transport.connections.first.connection
38
+ # => #<Manticore::Client:0xdeadbeef ...>
39
+ #
40
+ # client.info['status']
41
+ # => 200
42
+ #
43
+ # @see Transport::Base
44
+ #
45
+ class Manticore
46
+ include Base
47
+
48
+ # Performs the request by invoking {Transport::Base#perform_request} with a block.
49
+ #
50
+ # @return [Response]
51
+ # @see Transport::Base#perform_request
52
+ #
53
+ def perform_request(method, path, params={}, body=nil)
54
+ super do |connection, url|
55
+ params[:body] = __convert_to_json(body) if body
56
+ params = params.merge @request_options
57
+ case method
58
+ when "GET"
59
+ resp = connection.connection.get(url, params)
60
+ when "HEAD"
61
+ resp = connection.connection.head(url, params)
62
+ when "PUT"
63
+ resp = connection.connection.put(url, params)
64
+ when "POST"
65
+ resp = connection.connection.post(url, params)
66
+ when "DELETE"
67
+ resp = connection.connection.delete(url, params)
68
+ else
69
+ raise ArgumentError.new "Method #{method} not supported"
70
+ end
71
+ Response.new resp.code, resp.read_body, resp.headers
72
+ end
73
+ end
74
+
75
+ # Builds and returns a collection of connections.
76
+ # Each connection is a Manticore::Client
77
+ #
78
+ # @return [Connections::Collection]
79
+ #
80
+ def __build_connections
81
+ @request_options = {}
82
+
83
+ if options.key?(:headers)
84
+ @request_options[:headers] = options[:headers]
85
+ end
86
+
87
+ client_options = options[:transport_options] || {}
88
+ client_options[:ssl] = options[:ssl] || {}
89
+
90
+ Connections::Collection.new \
91
+ :connections => hosts.map { |host|
92
+ host[:protocol] = host[:scheme] || DEFAULT_PROTOCOL
93
+ host[:port] ||= DEFAULT_PORT
94
+
95
+ host.delete(:user) # auth is not supported here.
96
+ host.delete(:password) # use the headers
97
+
98
+ url = __full_url(host)
99
+
100
+ Connections::Connection.new \
101
+ :host => host,
102
+ :connection => ::Manticore::Client.new(client_options)
103
+ },
104
+ :selector_class => options[:selector_class],
105
+ :selector => options[:selector]
106
+ end
107
+
108
+ # Returns an array of implementation specific connection errors.
109
+ #
110
+ # @return [Array]
111
+ #
112
+ def host_unreachable_exceptions
113
+ [
114
+ ::Manticore::Timeout,
115
+ ::Manticore::SocketException,
116
+ ::Manticore::ClientProtocolException,
117
+ ::Manticore::ResolutionFailure
118
+ ]
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,21 @@
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
+ @body = body.force_encoding('UTF-8') if body.respond_to?(:force_encoding)
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ 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', '_nodes/http').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 = "1.0.13"
4
+ end
5
+ end
@@ -0,0 +1,144 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::ClientIntegrationTest < Elasticsearch::Test::IntegrationTestCase
4
+ startup do
5
+ Elasticsearch::Extensions::Test::Cluster.start(nodes: 2) if ENV['SERVER'] and not Elasticsearch::Extensions::Test::Cluster.running?
6
+ end
7
+
8
+ context "Elasticsearch client" do
9
+ teardown do
10
+ begin; Object.send(:remove_const, :Typhoeus); rescue NameError; end
11
+ begin; Object.send(:remove_const, :Patron); rescue NameError; end
12
+ end
13
+
14
+ setup do
15
+ @port = (ENV['TEST_CLUSTER_PORT'] || 9250).to_i
16
+ system "curl -X DELETE http://localhost:#{@port}/_all > /dev/null 2>&1"
17
+
18
+ @logger = Logger.new(STDERR)
19
+ @logger.formatter = proc do |severity, datetime, progname, msg|
20
+ color = case severity
21
+ when /INFO/ then :green
22
+ when /ERROR|WARN|FATAL/ then :red
23
+ when /DEBUG/ then :cyan
24
+ else :white
25
+ end
26
+ ANSI.ansi(severity[0] + ' ', color, :faint) + ANSI.ansi(msg, :white, :faint) + "\n"
27
+ end
28
+
29
+ @client = Elasticsearch::Client.new host: "localhost:#{@port}"
30
+ end
31
+
32
+ should "connect to the cluster" do
33
+ assert_nothing_raised do
34
+ response = @client.perform_request 'GET', '_cluster/health'
35
+ assert_equal 2, response.body['number_of_nodes']
36
+ end
37
+ end
38
+
39
+ should "handle paths and URL parameters" do
40
+ @client.perform_request 'PUT', 'myindex/mydoc/1', {routing: 'XYZ'}, {foo: 'bar'}
41
+ @client.perform_request 'GET', '_cluster/health?wait_for_status=green', {}
42
+
43
+ response = @client.perform_request 'GET', 'myindex/mydoc/1?routing=XYZ'
44
+ assert_equal 200, response.status
45
+ assert_equal 'bar', response.body['_source']['foo']
46
+
47
+ assert_raise Elasticsearch::Transport::Transport::Errors::NotFound do
48
+ @client.perform_request 'GET', 'myindex/mydoc/1?routing=ABC'
49
+ end
50
+ end
51
+
52
+ should "pass options to the transport" do
53
+ @client = Elasticsearch::Client.new \
54
+ host: "localhost:#{@port}",
55
+ logger: (ENV['QUIET'] ? nil : @logger),
56
+ transport_options: { headers: { content_type: 'application/yaml' } }
57
+
58
+ response = @client.perform_request 'GET', '_cluster/health'
59
+ assert_match /---\ncluster_name:/, response.body.to_s
60
+ end
61
+
62
+ context "with round robin selector" do
63
+ setup do
64
+ @client = Elasticsearch::Client.new \
65
+ hosts: ["localhost:#{@port}", "localhost:#{@port+1}" ],
66
+ logger: (ENV['QUIET'] ? nil : @logger)
67
+ end
68
+
69
+ should "rotate nodes" do
70
+ # Hit node 1
71
+ response = @client.perform_request 'GET', '_nodes/_local'
72
+ assert_equal 'node-1', response.body['nodes'].to_a[0][1]['name']
73
+
74
+ # Hit node 2
75
+ response = @client.perform_request 'GET', '_nodes/_local'
76
+ assert_equal 'node-2', response.body['nodes'].to_a[0][1]['name']
77
+
78
+ # Hit node 1
79
+ response = @client.perform_request 'GET', '_nodes/_local'
80
+ assert_equal 'node-1', response.body['nodes'].to_a[0][1]['name']
81
+ end
82
+ end
83
+
84
+ context "with a sick node and retry on failure" do
85
+ setup do
86
+ @port = (ENV['TEST_CLUSTER_PORT'] || 9250).to_i
87
+ @client = Elasticsearch::Client.new \
88
+ hosts: ["localhost:#{@port}", "foobar1"],
89
+ logger: (ENV['QUIET'] ? nil : @logger),
90
+ retry_on_failure: true
91
+ end
92
+
93
+ should "retry the request with next server" do
94
+ assert_nothing_raised do
95
+ 5.times { @client.perform_request 'GET', '_nodes/_local' }
96
+ end
97
+ end
98
+
99
+ should "raise exception when it cannot get any healthy server" do
100
+ @client = Elasticsearch::Client.new \
101
+ hosts: ["localhost:#{@port}", "foobar1", "foobar2", "foobar3"],
102
+ logger: (ENV['QUIET'] ? nil : @logger),
103
+ retry_on_failure: 1
104
+
105
+ assert_nothing_raised do
106
+ # First hit is OK
107
+ @client.perform_request 'GET', '_nodes/_local'
108
+ end
109
+
110
+ assert_raise Faraday::Error::ConnectionFailed do
111
+ # Second hit fails
112
+ @client.perform_request 'GET', '_nodes/_local'
113
+ end
114
+ end
115
+ end
116
+
117
+ context "with a sick node and reloading on failure" do
118
+ setup do
119
+ @client = Elasticsearch::Client.new \
120
+ hosts: ["localhost:#{@port}", "foobar1", "foobar2"],
121
+ logger: (ENV['QUIET'] ? nil : @logger),
122
+ reload_on_failure: true
123
+ end
124
+
125
+ should "reload the connections" do
126
+ assert_equal 3, @client.transport.connections.size
127
+ assert_nothing_raised do
128
+ 5.times { @client.perform_request 'GET', '_nodes/_local' }
129
+ end
130
+ assert_equal 2, @client.transport.connections.size
131
+ end
132
+ end
133
+
134
+ context "with Faraday adapters" do
135
+ should "automatically use the Patron client when loaded" do
136
+ require 'patron'
137
+ client = Elasticsearch::Transport::Client.new host: "localhost:#{@port}"
138
+
139
+ response = @client.perform_request 'GET', '_cluster/health'
140
+ assert_equal 200, response.status
141
+ end unless JRUBY
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,73 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::ClientIntegrationTest < Elasticsearch::Test::IntegrationTestCase
4
+ startup do
5
+ Elasticsearch::Extensions::Test::Cluster.start(nodes: 2) if ENV['SERVER'] and not Elasticsearch::Extensions::Test::Cluster.running?
6
+ end
7
+
8
+ context "Transport" do
9
+ setup do
10
+ @port = (ENV['TEST_CLUSTER_PORT'] || 9250).to_i
11
+ begin; Object.send(:remove_const, :Patron); rescue NameError; end
12
+ end
13
+
14
+ should "allow to customize the Faraday adapter" do
15
+ require 'typhoeus'
16
+ require 'typhoeus/adapters/faraday'
17
+
18
+ transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new \
19
+ :hosts => [ { :host => 'localhost', :port => @port } ] do |f|
20
+ f.response :logger
21
+ f.adapter :typhoeus
22
+ end
23
+
24
+ client = Elasticsearch::Transport::Client.new transport: transport
25
+ client.perform_request 'GET', ''
26
+ end
27
+
28
+ should "allow to define connection parameters and pass them" do
29
+ transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new \
30
+ :hosts => [ { :host => 'localhost', :port => @port } ],
31
+ :options => { :transport_options => {
32
+ :params => { :format => 'yaml' }
33
+ }
34
+ }
35
+
36
+ client = Elasticsearch::Transport::Client.new transport: transport
37
+ response = client.perform_request 'GET', ''
38
+
39
+ assert response.body.start_with?("---\n"), "Response body should be YAML: #{response.body.inspect}"
40
+ end
41
+
42
+ should "use the Curb client" do
43
+ require 'curb'
44
+ require 'elasticsearch/transport/transport/http/curb'
45
+
46
+ transport = Elasticsearch::Transport::Transport::HTTP::Curb.new \
47
+ :hosts => [ { :host => 'localhost', :port => @port } ] do |curl|
48
+ curl.verbose = true
49
+ end
50
+
51
+ client = Elasticsearch::Transport::Client.new transport: transport
52
+ client.perform_request 'GET', ''
53
+ end unless JRUBY
54
+
55
+ should "deserialize JSON responses in the Curb client" do
56
+ require 'curb'
57
+ require 'elasticsearch/transport/transport/http/curb'
58
+
59
+ transport = Elasticsearch::Transport::Transport::HTTP::Curb.new \
60
+ :hosts => [ { :host => 'localhost', :port => @port } ] do |curl|
61
+ curl.verbose = true
62
+ end
63
+
64
+ client = Elasticsearch::Transport::Client.new transport: transport
65
+ response = client.perform_request 'GET', ''
66
+
67
+ assert_respond_to(response.body, :to_hash)
68
+ assert_not_nil response.body['name']
69
+ assert_equal 'application/json', response.headers['content-type']
70
+ end unless JRUBY
71
+ end
72
+
73
+ end