em-synchrony-moped 0.9.4 → 1.0.0.beta.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.
- checksums.yaml +15 -0
- data/.gitignore +4 -0
- data/Guardfile +17 -0
- data/Rakefile +6 -12
- data/em-synchrony-moped.gemspec +10 -3
- data/lib/em-synchrony/moped/cluster.rb +20 -0
- data/lib/em-synchrony/moped/connection.rb +122 -0
- data/lib/em-synchrony/moped/node.rb +48 -0
- data/lib/em-synchrony/moped.rb +5 -181
- data/spec/lib/em-synchrony/moped/cluster_spec.rb +53 -0
- data/spec/lib/em-synchrony/moped/connection_spec.rb +137 -0
- data/spec/lib/em-synchrony/moped/node_spec.rb +138 -0
- data/spec/spec_helper.rb +35 -7
- data/spec/support/contexts.rb +32 -0
- data/spec/support/simulated_mongod.rb +75 -46
- metadata +121 -25
- data/spec/moped_spec.rb +0 -124
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MWM1ZDFhNzU0ZWE3ZjM2MWQ2YTAwODlmOWMzOTliZTU5OTMwZTc2Yg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDIxYTY0M2Q1Zjg4NGRjNzBjYjMyZTlmOTEyN2FhOTlhNTk5MTQwOQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZGJhNzU3NmZlM2I3YTcxNWM5NmUzMDk4OWRiNTQwNDExMTA3M2U0NjVlYjFh
|
10
|
+
MDhkMjE5YmViNTZkNDIxY2MwMGFhNDhkZGJkMDBiNjc0ZjgwZmIyNTIzNDZi
|
11
|
+
MDY4Y2ZhZWIwNmE2YmRjZjUzMzE4MjcyMDczYzE2ZmI4MTE5MmI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MTM0OTE2NTk1MzI4NGZhYmFiMGRmNzM2ZGQ3MjlmMzI0MjlkNWJhZmRlMmI1
|
14
|
+
MTU2YmE3ZDViOTA0MTVkYmIzMDk1M2U1NWJkNzYyNWQyZWIyMDBkNTI3OGU4
|
15
|
+
ZDEzMTBlNGJmNTMyY2RkYjI0MGY5MGNiODBlNjI4ODMyNjU0Yjc=
|
data/.gitignore
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
guard 'rspec' do
|
4
|
+
watch(%r{^spec/.+_spec\.rb$})
|
5
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
6
|
+
watch('spec/spec_helper.rb') { "spec" }
|
7
|
+
end
|
8
|
+
|
9
|
+
guard 'rubocop' do
|
10
|
+
watch(%r{.+\.rb$})
|
11
|
+
watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
|
12
|
+
end
|
13
|
+
|
14
|
+
guard 'bundler' do
|
15
|
+
watch('Gemfile')
|
16
|
+
watch(/^.+\.gemspec/)
|
17
|
+
end
|
data/Rakefile
CHANGED
@@ -1,16 +1,10 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
require 'rspec/core/rake_task'
|
3
|
+
require 'rspec/core'
|
4
|
+
require 'rspec/core/rake_task'
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
task :default => [:spec]
|
7
|
+
task :test => [:spec]
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
rescue LoadError
|
15
|
-
# silent failure for when rspec is not installed (production mode)
|
16
|
-
end
|
9
|
+
desc "Run all RSpec tests"
|
10
|
+
RSpec::Core::RakeTask.new(:spec)
|
data/em-synchrony-moped.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'em-synchrony-moped'
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "1.0.0.beta.1"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Adam Lebsack"]
|
@@ -18,11 +18,18 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_runtime_dependency 'eventmachine'
|
21
|
+
s.add_runtime_dependency 'eventmachine', '~> 1.0'
|
22
22
|
s.add_runtime_dependency 'em-synchrony', '~> 1.0'
|
23
|
-
s.add_runtime_dependency 'moped', '~> 1.
|
23
|
+
s.add_runtime_dependency 'moped', '~> 1.5.1'
|
24
24
|
s.add_runtime_dependency 'em-resolv-replace', '~> 1.1.3'
|
25
25
|
|
26
26
|
s.add_development_dependency 'rspec', '~> 2.12.0'
|
27
|
+
s.add_development_dependency 'guard'
|
28
|
+
s.add_development_dependency 'guard-rspec'
|
29
|
+
s.add_development_dependency 'guard-bundler'
|
30
|
+
s.add_development_dependency 'guard-rubocop'
|
31
|
+
s.add_development_dependency 'rubocop'
|
32
|
+
s.add_development_dependency 'spork'
|
33
|
+
s.add_development_dependency 'simplecov'
|
27
34
|
|
28
35
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'moped/cluster'
|
4
|
+
|
5
|
+
module Moped
|
6
|
+
# Our patches to Moped::Cluster
|
7
|
+
module EventedCluster
|
8
|
+
def sleep(seconds)
|
9
|
+
if EventMachine.reactor_thread?
|
10
|
+
EM::Synchrony.sleep(seconds)
|
11
|
+
else
|
12
|
+
Kernel.sleep(seconds)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Cluster
|
18
|
+
include EventedCluster
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Moped
|
4
|
+
class Connection
|
5
|
+
def connect
|
6
|
+
if EventMachine.reactor_thread?
|
7
|
+
if !!options[:ssl]
|
8
|
+
@sock = Sockets::EmSSL.em_connect(host, port, timeout, options)
|
9
|
+
else
|
10
|
+
@sock = Sockets::EmTCP.em_connect(host, port, timeout, options)
|
11
|
+
end
|
12
|
+
else # use old driver
|
13
|
+
if !!options[:ssl]
|
14
|
+
@sock = Sockets::SSL.connect(host, port, timeout)
|
15
|
+
else
|
16
|
+
@sock = Sockets::TCP.connect(host, port, timeout)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end # class Cnnection
|
21
|
+
|
22
|
+
module Sockets
|
23
|
+
module Connectable
|
24
|
+
module ClassMethods
|
25
|
+
def em_connect(host, port, timeout, options)
|
26
|
+
socket = EventMachine.connect(host, port, self) do |c|
|
27
|
+
c.pending_connect_timeout = timeout
|
28
|
+
c.options = options
|
29
|
+
end
|
30
|
+
# In TCPSocket, new against a closed port raises Errno::ECONNREFUSED.
|
31
|
+
# In EM, connect against a closed port result in a call to unbind
|
32
|
+
# with a reason param of Errno::ECONNREFUSED as a class, not an
|
33
|
+
# instance.
|
34
|
+
unless socket.sync(:in) # wait for connection
|
35
|
+
fail socket.unbind_reason.new if socket.unbind_reason.is_a? Class
|
36
|
+
fail SocketError, socket.unbind_reason
|
37
|
+
end
|
38
|
+
socket
|
39
|
+
|
40
|
+
rescue Errno::ETIMEDOUT
|
41
|
+
fail Errors::ConnectionFailure,
|
42
|
+
"Timed out connection to Mongo on #{host}:#{port}"
|
43
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EPIPE,
|
44
|
+
Errno::ECONNRESET, IOError => error
|
45
|
+
fail Errors::ConnectionFailure,
|
46
|
+
"#{host}:#{port}: #{error.class.name} (#{error.errno}): " +
|
47
|
+
"#{error.message}"
|
48
|
+
rescue SocketError => error
|
49
|
+
fail Errors::ConnectionFailure,
|
50
|
+
"#{host}:#{port}: #{error.class.name}: #{error.message}"
|
51
|
+
rescue OpenSSL::SSL::SSLError => error
|
52
|
+
fail Errors::ConnectionFailure,
|
53
|
+
"#{host}:#{port}: #{error.class.name} (#{error.errno}): " +
|
54
|
+
"#{error.message}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# The EM-Synchrony flavor of Moped::Sockets::TCP
|
60
|
+
class EmTCP < EventMachine::Synchrony::TCPSocket
|
61
|
+
include Connectable
|
62
|
+
|
63
|
+
# TODO: re-evaluate the options call. Can't we pass the caller
|
64
|
+
# up to the connection or something?
|
65
|
+
attr_accessor :options
|
66
|
+
|
67
|
+
def alive?
|
68
|
+
!closed?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# The EM-Synchrony flavor of Moped::Sockets::SSL
|
73
|
+
class EmSSL < EmTCP
|
74
|
+
def connection_completed
|
75
|
+
@verified = false
|
76
|
+
if @options[:ssl].is_a?(Hash)
|
77
|
+
start_tls(@options[:ssl])
|
78
|
+
else
|
79
|
+
start_tls
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def ssl_verify_peer(pem)
|
84
|
+
unless (cert_store = @options[:ssl][:cert_store])
|
85
|
+
cert_store = OpenSSL::X509::Store.new
|
86
|
+
cert_store.add_file(@options[:ssl][:verify_cert])
|
87
|
+
end
|
88
|
+
|
89
|
+
if (cert = OpenSSL::X509::Certificate.new(pem) rescue nil)
|
90
|
+
if cert_store.verify(cert)
|
91
|
+
|
92
|
+
cert.extensions.each do |e|
|
93
|
+
if e.oid == 'basicConstraints' && e.value == 'CA:TRUE'
|
94
|
+
return true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
host = @options[:ssl][:verify_host]
|
99
|
+
if OpenSSL::SSL.verify_certificate_identity(cert, host)
|
100
|
+
@verified = true
|
101
|
+
return true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
true
|
107
|
+
rescue
|
108
|
+
unbind 'Failed to verify SSL certificate of peer'
|
109
|
+
false
|
110
|
+
end
|
111
|
+
|
112
|
+
def ssl_handshake_completed
|
113
|
+
if @options[:ssl][:verify_peer] && !@verified
|
114
|
+
unbind 'Failed to verify SSL certificate of peer'
|
115
|
+
else
|
116
|
+
@opening = false
|
117
|
+
@in_req.succeed self
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'moped/node'
|
4
|
+
require 'em-dns-resolver'
|
5
|
+
require 'fiber'
|
6
|
+
|
7
|
+
module Moped
|
8
|
+
# Our monkey patches to Moped::Node
|
9
|
+
module EventedNode
|
10
|
+
# Override to support non-blocking DNS requests
|
11
|
+
def parse_address
|
12
|
+
return super if EM.reactor_thread?
|
13
|
+
host, port = address.split(':')
|
14
|
+
@port = (port || 27_017).to_i
|
15
|
+
|
16
|
+
@ip_address = em_lookup_address(host)
|
17
|
+
fail SocketError unless @ip_address
|
18
|
+
@resolved_address = "#{@ip_address}:#{@port}"
|
19
|
+
rescue Resolv::ResolvError
|
20
|
+
raise SocketError
|
21
|
+
end
|
22
|
+
|
23
|
+
def em_lookup_address(value)
|
24
|
+
# Lookup in /etc/hosts
|
25
|
+
result = []
|
26
|
+
@hosts ||= Resolv::Hosts.new
|
27
|
+
@hosts.send(:each_address, value) { |x| result << x.to_s }
|
28
|
+
return result unless result.empty?
|
29
|
+
|
30
|
+
# Nothing, hit DNS
|
31
|
+
fiber = Fiber.current
|
32
|
+
df = EM::DnsResolver.send(:resolve, value)
|
33
|
+
df.callback do |a|
|
34
|
+
fiber.resume(a)
|
35
|
+
end
|
36
|
+
df.errback do |*a|
|
37
|
+
fiber.resume(ResolvError.new(a.inspect))
|
38
|
+
end
|
39
|
+
result = Fiber.yield
|
40
|
+
raise result if result.is_a?(StandardError)
|
41
|
+
result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Node
|
46
|
+
include EventedNode
|
47
|
+
end
|
48
|
+
end
|
data/lib/em-synchrony/moped.rb
CHANGED
@@ -1,182 +1,6 @@
|
|
1
|
-
|
2
|
-
require "moped/node"
|
3
|
-
require 'em-resolv-replace'
|
1
|
+
# encoding: utf-8
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
EM::Synchrony.sleep(seconds)
|
10
|
-
end
|
11
|
-
|
12
|
-
# MONKEY PATCH: if all available connections are down, use the seeds
|
13
|
-
# again to determine, where the application should connect. This is a
|
14
|
-
# implementation detail and unlikely to be main line. Therefore no pull
|
15
|
-
# request here. The assumtion is, that if we can't connect to any mongodb
|
16
|
-
# that they are relocated and available under a different DNS address.
|
17
|
-
def nodes(opts = {})
|
18
|
-
current_time = Time.new
|
19
|
-
down_boundary = current_time - down_interval
|
20
|
-
refresh_boundary = current_time - refresh_interval
|
21
|
-
|
22
|
-
# Find the nodes that were down but are ready to be refreshed, or those
|
23
|
-
# with stale connection information.
|
24
|
-
needs_refresh, available = @nodes.partition do |node|
|
25
|
-
node.down? ? (node.down_at < down_boundary) : node.needs_refresh?(refresh_boundary)
|
26
|
-
end
|
27
|
-
|
28
|
-
# Refresh those nodes.
|
29
|
-
available.concat refresh(needs_refresh)
|
30
|
-
|
31
|
-
# Now return all the nodes that are available and participating in the
|
32
|
-
# replica set.
|
33
|
-
avail_not_down = available.reject do |node|
|
34
|
-
node.down? || !member?(node) || (!opts[:include_arbiters] && node.arbiter?)
|
35
|
-
end
|
36
|
-
|
37
|
-
if avail_not_down.empty?
|
38
|
-
if logger = Moped.logger
|
39
|
-
logger.warn " MOPED: reinitialize cluster because all nodes " \
|
40
|
-
"are down with #{@seeds.inspect}"
|
41
|
-
end
|
42
|
-
@nodes = @seeds.map { |host| Node.new(host, @options) }
|
43
|
-
end
|
44
|
-
|
45
|
-
avail_not_down
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
class Node
|
50
|
-
# Override to support non-blocking DNS requests
|
51
|
-
def parse_address
|
52
|
-
host, port = address.split(":")
|
53
|
-
@port = (port || 27017).to_i
|
54
|
-
|
55
|
-
@resolver ||= Resolv.new([Resolv::Hosts.new, Resolv::DNS.new])
|
56
|
-
|
57
|
-
# For now, limit the IPs only to IPv4 hosts. In order to support IPv6,
|
58
|
-
# the node should be able to handle fallback connections.
|
59
|
-
@resolver.getaddresses(host).each do |ip|
|
60
|
-
if ip =~ Resolv::IPv4::Regex
|
61
|
-
@ip_address = ip
|
62
|
-
break
|
63
|
-
end
|
64
|
-
end
|
65
|
-
raise SocketError unless @ip_address
|
66
|
-
@resolved_address = "#{@ip_address}:#{@port}"
|
67
|
-
rescue Resolv::ResolvError
|
68
|
-
raise SocketError
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
class Connection
|
74
|
-
def connected?
|
75
|
-
@sock && !@sock.instance_variable_get("@remote_closed") && !@sock.closed?
|
76
|
-
end
|
77
|
-
|
78
|
-
def connect
|
79
|
-
@sock = if !!options[:ssl]
|
80
|
-
Sockets::SSL.connect(host, port, timeout, options)
|
81
|
-
else
|
82
|
-
Sockets::TCP.connect(host, port, timeout, options)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
Sockets.send(:remove_const, :TCP)
|
88
|
-
Sockets.send(:remove_const, :SSL)
|
89
|
-
module Sockets
|
90
|
-
module Connectable
|
91
|
-
attr_accessor :options
|
92
|
-
|
93
|
-
def alive?
|
94
|
-
!closed?
|
95
|
-
end
|
96
|
-
|
97
|
-
module ClassMethods
|
98
|
-
|
99
|
-
def connect(host, port, timeout, options={})
|
100
|
-
socket = EM.connect(host, port, self) do |c|
|
101
|
-
c.pending_connect_timeout = timeout
|
102
|
-
c.options = options.merge(:host => host)
|
103
|
-
end
|
104
|
-
# In TCPSocket, new against a closed port raises Errno::ECONNREFUSED.
|
105
|
-
# In EM, connect against a closed port result in a call to unbind with
|
106
|
-
# a reason param of Errno::ECONNREFUSED as a class, not an instance.
|
107
|
-
unless socket.sync(:in) # wait for connection
|
108
|
-
raise socket.unbind_reason.new if socket.unbind_reason.is_a? Class
|
109
|
-
raise SocketError, socket.unbind_reason
|
110
|
-
end
|
111
|
-
socket
|
112
|
-
|
113
|
-
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EPIPE, Errno::ECONNRESET, Errno::ETIMEDOUT, IOError => error
|
114
|
-
raise Errors::ConnectionFailure, "#{host}:#{port}: #{error.class.name} (#{error.errno}): #{error.message}"
|
115
|
-
rescue SocketError => error
|
116
|
-
raise Errors::ConnectionFailure, "#{host}:#{port}: #{error.class.name}: #{error.message}"
|
117
|
-
rescue OpenSSL::SSL::SSLError => error
|
118
|
-
raise Errors::ConnectionFailure, "#{host}:#{port}: #{error.class.name} (#{error.errno}): #{error.message}"
|
119
|
-
end
|
120
|
-
|
121
|
-
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
class TCP < EventMachine::Synchrony::TCPSocket
|
126
|
-
include Connectable
|
127
|
-
end
|
128
|
-
|
129
|
-
class SSL < EventMachine::Synchrony::TCPSocket
|
130
|
-
include Connectable
|
131
|
-
|
132
|
-
def connection_completed
|
133
|
-
@verified = false
|
134
|
-
if @options[:ssl].is_a?(Hash)
|
135
|
-
start_tls(@options[:ssl])
|
136
|
-
else
|
137
|
-
start_tls
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
def ssl_verify_peer(pem)
|
142
|
-
unless cert_store = @options[:ssl][:cert_store]
|
143
|
-
cert_store = OpenSSL::X509::Store.new
|
144
|
-
cert_store.add_file(@options[:ssl][:verify_cert])
|
145
|
-
end
|
146
|
-
|
147
|
-
if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
|
148
|
-
if cert_store.verify(cert)
|
149
|
-
|
150
|
-
cert.extensions.each do |e|
|
151
|
-
if e.oid == 'basicConstraints' && e.value == 'CA:TRUE'
|
152
|
-
return true
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
host = @options[:ssl][:verify_host]
|
157
|
-
if OpenSSL::SSL.verify_certificate_identity(cert, host)
|
158
|
-
@verified = true
|
159
|
-
return true
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
true
|
165
|
-
rescue => e
|
166
|
-
unbind "Failed to verify SSL certificate of peer"
|
167
|
-
false
|
168
|
-
end
|
169
|
-
|
170
|
-
def ssl_handshake_completed
|
171
|
-
if @options[:ssl][:verify_peer] && !@verified
|
172
|
-
unbind "Failed to verify SSL certificate of peer"
|
173
|
-
else
|
174
|
-
@opening = false
|
175
|
-
@in_req.succeed self
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'em-synchrony/moped/cluster'
|
5
|
+
require 'em-synchrony/moped/node'
|
6
|
+
require 'em-synchrony/moped/connection'
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'moped'
|
6
|
+
require 'em-synchrony/moped'
|
7
|
+
|
8
|
+
describe Moped::Cluster do
|
9
|
+
let(:mongod_options) { {} }
|
10
|
+
let(:server1) { FakeMongodServer.new(mongod_options) }
|
11
|
+
let(:server1_port) { server1.port }
|
12
|
+
|
13
|
+
let(:server2) { FakeMongodServer.new(mongod_options.merge(master: 0)) }
|
14
|
+
let(:server2_port) { server2.port }
|
15
|
+
|
16
|
+
after do
|
17
|
+
server1.stop if server1
|
18
|
+
server2.stop if server2
|
19
|
+
end
|
20
|
+
|
21
|
+
shared_context 'common cluster' do
|
22
|
+
let(:options) { {} }
|
23
|
+
let(:seeds) do
|
24
|
+
["#{server1.host}:#{server1_port}", "#{server2.host}:#{server2_port}"]
|
25
|
+
end
|
26
|
+
let(:cluster) { Moped::Cluster.new(seeds, options.merge(timeout: 1)) }
|
27
|
+
|
28
|
+
describe '#with_primary' do
|
29
|
+
it 'should get a primary node' do
|
30
|
+
cluster.with_primary do |node|
|
31
|
+
expect(node).to be_primary
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#with_secondary' do
|
37
|
+
it 'should get a secondary node' do
|
38
|
+
cluster.with_secondary do |node|
|
39
|
+
expect(node).to be_secondary
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'evented' do
|
46
|
+
include_context 'with em-synchrony'
|
47
|
+
include_context 'common cluster'
|
48
|
+
end
|
49
|
+
context 'threaded' do
|
50
|
+
include_context 'without em-synchrony'
|
51
|
+
include_context 'common cluster'
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
require 'moped'
|
5
|
+
require 'em-synchrony/moped'
|
6
|
+
|
7
|
+
# TODO: clean up a bit. we're basically testing Connection through Node.
|
8
|
+
# we should be able to test Connection by itself.
|
9
|
+
|
10
|
+
describe Moped::Connection do
|
11
|
+
it 'should have patches included' do
|
12
|
+
expect { Moped::Sockets::EmTCP }.not_to raise_error(NameError)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:mongod_options) { {} }
|
16
|
+
let(:server) { FakeMongodServer.new(mongod_options) }
|
17
|
+
let(:server_port) { server.port }
|
18
|
+
after { server.stop }
|
19
|
+
|
20
|
+
let(:node_options) { {} }
|
21
|
+
let(:node) do
|
22
|
+
options = node_options.merge(timeout: 1)
|
23
|
+
host = options.delete(:host) || 'localhost'
|
24
|
+
Moped::Node.new("#{host}:#{server_port}", options)
|
25
|
+
end
|
26
|
+
|
27
|
+
shared_context 'common connection' do
|
28
|
+
context 'with a running server' do
|
29
|
+
it 'should connect' do
|
30
|
+
node.refresh
|
31
|
+
node.should be_primary
|
32
|
+
node.should be_connected
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should detect a disconnect' do
|
36
|
+
node.refresh
|
37
|
+
node.should be_primary
|
38
|
+
node.should be_connected
|
39
|
+
server.stop
|
40
|
+
expect do
|
41
|
+
node.command('admin', ismaster: 1)
|
42
|
+
end.to raise_error(
|
43
|
+
Moped::Errors::ConnectionFailure # TODO: check the message
|
44
|
+
)
|
45
|
+
node.should_not be_connected
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'with an unpresponsive host' do
|
50
|
+
# 127.0.0.2 seems to timeout for my tests...
|
51
|
+
let(:node_options) { { host: '127.0.0.2' } }
|
52
|
+
it 'should raise a timeout error' do
|
53
|
+
expect { node.refresh }.to raise_error(
|
54
|
+
Moped::Errors::ConnectionFailure,
|
55
|
+
/^Timed out connection to Mongo on/)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'without a server' do
|
60
|
+
it 'should raise a connection error on connection refused' do
|
61
|
+
server.stop
|
62
|
+
expect { node.refresh }.to raise_error(
|
63
|
+
Moped::Errors::ConnectionFailure, /ECONNREFUSED/)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
shared_context 'common connection ssl' do
|
69
|
+
context 'with ssl server' do
|
70
|
+
let(:mongod_options) do
|
71
|
+
{
|
72
|
+
ssl: {
|
73
|
+
private_key_file: "#{SSL_DIR}/server.key",
|
74
|
+
cert_chain_file: "#{SSL_DIR}/server.crt",
|
75
|
+
verify_peer: false
|
76
|
+
}
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'without verifying peer' do
|
81
|
+
let(:node_options) { { ssl: { verify_peer: false } } }
|
82
|
+
it 'should connect' do
|
83
|
+
node.refresh
|
84
|
+
node.should be_primary
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when verifying peer' do
|
89
|
+
let(:node_options) do
|
90
|
+
{ ssl: {
|
91
|
+
verify_peer: true,
|
92
|
+
verify_cert: "#{SSL_DIR}/ca_cert.pem",
|
93
|
+
verify_host: 'localhost'
|
94
|
+
}
|
95
|
+
}
|
96
|
+
end
|
97
|
+
it 'should connect' do
|
98
|
+
node.refresh
|
99
|
+
node.should be_primary
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'with untrusted key on server' do
|
103
|
+
let(:mongod_options) do
|
104
|
+
{
|
105
|
+
ssl: {
|
106
|
+
private_key_file: "#{SSL_DIR}/untrusted.key",
|
107
|
+
cert_chain_file: "#{SSL_DIR}/untrusted.crt",
|
108
|
+
verify_peer: false
|
109
|
+
}
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should connect and fail to verify peer' do
|
114
|
+
expect do
|
115
|
+
node.refresh
|
116
|
+
node.should be_primary
|
117
|
+
end.to raise_error(
|
118
|
+
Moped::Errors::ConnectionFailure,
|
119
|
+
/Failed to verify SSL certificate of peer/
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'evented' do
|
128
|
+
include_context 'with em-synchrony'
|
129
|
+
include_context 'common connection'
|
130
|
+
include_context 'common connection ssl'
|
131
|
+
end
|
132
|
+
context 'threaded' do
|
133
|
+
include_context 'without em-synchrony'
|
134
|
+
include_context 'common connection'
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|