master_slave_adapter 0.2.0 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,320 @@
1
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'rspec'
4
+
5
+ class Mysql
6
+ class Error
7
+ CR_CONNECTION_ERROR = 1
8
+ CR_CONN_HOST_ERROR = 2
9
+ CR_SERVER_GONE_ERROR = 4
10
+ CR_SERVER_LOST = 8
11
+ end
12
+ end
13
+
14
+ require 'active_record/connection_adapters/mysql_master_slave_adapter'
15
+
16
+ ActiveRecord::Base.logger =
17
+ Logger.new($stdout).tap { |l| l.level = Logger::DEBUG }
18
+
19
+ module ActiveRecord
20
+ class Base
21
+ cattr_accessor :master_mock, :slave_mock
22
+ def self.mysql_connection(config)
23
+ config[:database] == 'slave' ? slave_mock : master_mock
24
+ end
25
+ end
26
+ end
27
+
28
+ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
29
+ let(:default_database_setup) do
30
+ {
31
+ :adapter => 'master_slave',
32
+ :username => 'root',
33
+ :database => 'slave',
34
+ :connection_adapter => 'mysql',
35
+ :master => { :username => 'root', :database => 'master' },
36
+ :slaves => [{ :database => 'slave' }],
37
+ }
38
+ end
39
+
40
+ let(:database_setup) { default_database_setup }
41
+
42
+ let(:mocked_methods) do
43
+ {
44
+ :reconnect! => true,
45
+ :disconnect! => true,
46
+ :active? => true,
47
+ }
48
+ end
49
+
50
+ let!(:master_connection) do
51
+ mock(
52
+ 'master connection',
53
+ mocked_methods.merge(:open_transactions => 0)
54
+ ).tap do |conn|
55
+ conn.stub!(:uncached).and_yield
56
+ ActiveRecord::Base.master_mock = conn
57
+ end
58
+ end
59
+
60
+ let!(:slave_connection) do
61
+ mock('slave connection', mocked_methods).tap do |conn|
62
+ conn.stub!(:uncached).and_yield
63
+ ActiveRecord::Base.slave_mock = conn
64
+ end
65
+ end
66
+
67
+ def adapter_connection
68
+ ActiveRecord::Base.connection
69
+ end
70
+
71
+ SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ]
72
+ Clock = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::Clock
73
+
74
+ before do
75
+ ActiveRecord::Base.establish_connection(database_setup)
76
+ end
77
+
78
+ after do
79
+ ActiveRecord::Base.connection_handler.clear_all_connections!
80
+ end
81
+
82
+ describe 'consistency' do
83
+ def zero
84
+ Clock.zero
85
+ end
86
+
87
+ def master_position(pos)
88
+ Clock.new('', pos)
89
+ end
90
+
91
+ def slave_should_report_clock(pos)
92
+ pos = Array(pos)
93
+ values = pos.map { |p| { 'Relay_Master_Log_File' => '', 'Exec_Master_Log_Pos' => p } }
94
+ slave_connection.
95
+ should_receive('select_one').exactly(pos.length).with('SHOW SLAVE STATUS').
96
+ and_return(*values)
97
+ end
98
+
99
+ def master_should_report_clock(pos)
100
+ pos = Array(pos)
101
+ values = pos.map { |p| { 'File' => '', 'Position' => p } }
102
+ master_connection.
103
+ should_receive('select_one').exactly(pos.length).with('SHOW MASTER STATUS').
104
+ and_return(*values)
105
+ end
106
+
107
+ SelectMethods.each do |method|
108
+ it "should send the method '#{method}' to the slave if nil is given" do
109
+ slave_should_report_clock(0)
110
+ slave_connection.should_receive(method).with('testing').and_return(true)
111
+ new_clock = ActiveRecord::Base.with_consistency(nil) do
112
+ adapter_connection.send(method, 'testing')
113
+ end
114
+ new_clock.should be_a(Clock)
115
+ new_clock.should equal(zero)
116
+ end
117
+
118
+ it "should send the method '#{method}' to the slave if clock.zero is given" do
119
+ slave_should_report_clock(0)
120
+ slave_connection.should_receive(method).with('testing').and_return(true)
121
+ old_clock = zero
122
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
123
+ adapter_connection.send(method, 'testing')
124
+ end
125
+ new_clock.should be_a(Clock)
126
+ new_clock.should equal(old_clock)
127
+ end
128
+
129
+ it "should send the method '#{method}' to the master if slave hasn't cought up to required clock yet" do
130
+ slave_should_report_clock(0)
131
+ master_connection.should_receive(method).with('testing').and_return(true)
132
+ old_clock = master_position(1)
133
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
134
+ adapter_connection.send(method, 'testing' )
135
+ end
136
+ new_clock.should be_a(Clock)
137
+ new_clock.should equal(old_clock)
138
+ end
139
+
140
+ it "should send the method '#{method}' to the master connection if there are open transactions" do
141
+ master_connection.stub!(:open_transactions).and_return(1)
142
+ master_connection.should_receive(method).with('testing').and_return(true)
143
+ old_clock = zero
144
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
145
+ adapter_connection.send(method, 'testing')
146
+ end
147
+ new_clock.should be_a(Clock)
148
+ new_clock.should equal(zero)
149
+ end
150
+
151
+ it "should send the method '#{method}' to the master after a write operation" do
152
+ slave_should_report_clock(0)
153
+ master_should_report_clock(2)
154
+ slave_connection.should_receive(method).with('testing').and_return(true)
155
+ master_connection.should_receive('update').with('testing').and_return(true)
156
+ master_connection.should_receive(method).with('testing').and_return(true)
157
+ old_clock = zero
158
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
159
+ adapter_connection.send(method, 'testing') # slave
160
+ adapter_connection.send('update', 'testing') # master
161
+ adapter_connection.send(method, 'testing') # master
162
+ end
163
+ new_clock.should be_a(Clock)
164
+ new_clock.should > old_clock
165
+ end
166
+ end
167
+
168
+ it "should update the clock after a transaction" do
169
+ slave_should_report_clock(0)
170
+ master_should_report_clock([0, 1, 1])
171
+
172
+ slave_connection.
173
+ should_receive('select_all').exactly(1).times.with('testing').
174
+ and_return(true)
175
+
176
+ master_connection.
177
+ should_receive('update').exactly(3).times.with('testing').
178
+ and_return(true)
179
+ master_connection.
180
+ should_receive('select_all').exactly(5).times.with('testing').
181
+ and_return(true)
182
+ %w(begin_db_transaction
183
+ commit_db_transaction
184
+ increment_open_transactions
185
+ decrement_open_transactions
186
+ outside_transaction?).each do |txstmt|
187
+ master_connection.should_receive(txstmt).exactly(1).times
188
+ end
189
+
190
+ master_connection.
191
+ should_receive('open_transactions').exactly(13).times.
192
+ and_return(
193
+ # adapter: with_consistency, select_all, update, select_all
194
+ 0, 0, 0, 0,
195
+ # connection: transaction
196
+ 0,
197
+ # adapter: select_all, update, select_all, commit_db_transaction
198
+ 1, 1, 1, 0,
199
+ # connection: transaction (ensure)
200
+ 0,
201
+ # adapter: select_all, update, select_all
202
+ 0, 0, 0
203
+ )
204
+
205
+ old_clock = zero
206
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
207
+ adapter_connection.send('select_all', 'testing') # slave s=0 m=0
208
+ adapter_connection.send('update', 'testing') # master s=0 m=1
209
+ adapter_connection.send('select_all', 'testing') # master s=0 m=1
210
+
211
+ ActiveRecord::Base.transaction do
212
+ adapter_connection.send('select_all', 'testing') # master s=0 m=1
213
+ adapter_connection.send('update', 'testing') # master s=0 m=1
214
+ adapter_connection.send('select_all', 'testing') # master s=0 m=1
215
+ end
216
+
217
+ adapter_connection.send('select_all', 'testing') # master s=0 m=2
218
+ adapter_connection.send('update', 'testing') # master s=0 m=3
219
+ adapter_connection.send('select_all', 'testing') # master s=0 m=3
220
+ end
221
+
222
+ new_clock.should > old_clock
223
+ end
224
+
225
+ context "with nested with_consistency" do
226
+ it "should return the same clock if not writing and no lag" do
227
+ slave_should_report_clock(0) # note: tests memoizing slave clock
228
+ slave_connection.
229
+ should_receive('select_one').exactly(3).times.with('testing').
230
+ and_return(true)
231
+
232
+ old_clock = zero
233
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
234
+ adapter_connection.send('select_one', 'testing')
235
+ ActiveRecord::Base.with_consistency(old_clock) do
236
+ adapter_connection.send('select_one', 'testing')
237
+ end
238
+ adapter_connection.send('select_one', 'testing')
239
+ end
240
+ new_clock.should equal(old_clock)
241
+ end
242
+
243
+ it "requesting a newer clock should return a new clock" do
244
+ adapter_connection.
245
+ should_receive('slave_consistent?').exactly(2).times.
246
+ and_return(true, false)
247
+ slave_connection.
248
+ should_receive('select_all').exactly(2).times.with('testing').
249
+ and_return(true)
250
+ master_connection.
251
+ should_receive('select_all').exactly(1).times.with('testing').
252
+ and_return(true)
253
+
254
+ start_clock = zero
255
+ inner_clock = zero
256
+ outer_clock = ActiveRecord::Base.with_consistency(start_clock) do
257
+ adapter_connection.send('select_all', 'testing') # slave
258
+ inner_clock = ActiveRecord::Base.with_consistency(master_position(1)) do
259
+ adapter_connection.send('select_all', 'testing') # master
260
+ end
261
+ adapter_connection.send('select_all', 'testing') # slave
262
+ end
263
+
264
+ start_clock.should equal(outer_clock)
265
+ inner_clock.should > start_clock
266
+ end
267
+ end
268
+
269
+ it "should do the right thing when nested inside with_master" do
270
+ slave_should_report_clock(0)
271
+ slave_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
272
+ master_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
273
+ ActiveRecord::Base.with_master do
274
+ adapter_connection.send('select_all', 'testing') # master
275
+ ActiveRecord::Base.with_consistency(zero) do
276
+ adapter_connection.send('select_all', 'testing') # slave
277
+ end
278
+ adapter_connection.send('select_all', 'testing') # master
279
+ end
280
+ end
281
+
282
+ it "should do the right thing when nested inside with_slave" do
283
+ slave_should_report_clock(0)
284
+ slave_connection.should_receive('select_all').exactly(3).times.with('testing').and_return(true)
285
+ ActiveRecord::Base.with_slave do
286
+ adapter_connection.send('select_all', 'testing') # slave
287
+ ActiveRecord::Base.with_consistency(zero) do
288
+ adapter_connection.send('select_all', 'testing') # slave
289
+ end
290
+ adapter_connection.send('select_all', 'testing') # slave
291
+ end
292
+ end
293
+
294
+ it "should do the right thing when wrapping with_master" do
295
+ slave_should_report_clock(0)
296
+ slave_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
297
+ master_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
298
+ ActiveRecord::Base.with_consistency(zero) do
299
+ adapter_connection.send('select_all', 'testing') # slave
300
+ ActiveRecord::Base.with_master do
301
+ adapter_connection.send('select_all', 'testing') # master
302
+ end
303
+ adapter_connection.send('select_all', 'testing') # slave
304
+ end
305
+ end
306
+
307
+ it "should do the right thing when wrapping with_slave" do
308
+ slave_should_report_clock(0)
309
+ slave_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
310
+ master_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
311
+ ActiveRecord::Base.with_consistency(master_position(1)) do
312
+ adapter_connection.send('select_all', 'testing') # master
313
+ ActiveRecord::Base.with_slave do
314
+ adapter_connection.send('select_all', 'testing') # slave
315
+ end
316
+ adapter_connection.send('select_all', 'testing') # master
317
+ end
318
+ end
319
+ end # /with_consistency
320
+ end
metadata CHANGED
@@ -1,19 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: master_slave_adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
5
- prerelease:
4
+ version: 1.0.0.beta1
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Mauricio Linhares
9
9
  - Torsten Curdt
10
10
  - Kim Altintop
11
11
  - Omid Aladini
12
+ - Tiago Loureiro
13
+ - Tobias Schmidt
12
14
  - SoundCloud
13
15
  autorequire:
14
16
  bindir: bin
15
17
  cert_chain: []
16
- date: 2012-05-21 00:00:00.000000000 Z
18
+ date: 2012-05-24 00:00:00.000000000 Z
17
19
  dependencies:
18
20
  - !ruby/object:Gem::Dependency
19
21
  name: rspec
@@ -63,8 +65,8 @@ dependencies:
63
65
  - - ~>
64
66
  - !ruby/object:Gem::Version
65
67
  version: 2.3.9
66
- description: (MySQL) Replication Aware Master/Slave Database Adapter for Rails/ActiveRecord
67
- email: kim@soundcloud.com tcurdt@soundcloud.com omid@soundcloud.com
68
+ description: (MySQL) Replication Aware Master/Slave Database Adapter for ActiveRecord
69
+ email: kim@soundcloud.com tcurdt@soundcloud.com ts@soundcloud
68
70
  executables: []
69
71
  extensions: []
70
72
  extra_rdoc_files: []
@@ -77,12 +79,16 @@ files:
77
79
  - Rakefile
78
80
  - Readme.md
79
81
  - TODO.txt
80
- - VERSION
81
82
  - lib/active_record/connection_adapters/master_slave_adapter.rb
83
+ - lib/active_record/connection_adapters/master_slave_adapter/circuit_breaker.rb
84
+ - lib/active_record/connection_adapters/master_slave_adapter/clock.rb
85
+ - lib/active_record/connection_adapters/master_slave_adapter/version.rb
82
86
  - lib/active_record/connection_adapters/mysql_master_slave_adapter.rb
83
87
  - lib/master_slave_adapter.rb
84
88
  - master_slave_adapter.gemspec
89
+ - spec/circuit_breaker_spec.rb
85
90
  - spec/master_slave_adapter_spec.rb
91
+ - spec/mysql_master_slave_adapter_spec.rb
86
92
  homepage: http://github.com/soundcloud/master_slave_adapter
87
93
  licenses: []
88
94
  post_install_message:
@@ -94,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
100
  requirements:
95
101
  - - ! '>='
96
102
  - !ruby/object:Gem::Version
97
- version: 1.9.2
103
+ version: 1.8.7
98
104
  required_rubygems_version: !ruby/object:Gem::Requirement
99
105
  none: false
100
106
  requirements:
@@ -106,6 +112,8 @@ rubyforge_project:
106
112
  rubygems_version: 1.8.21
107
113
  signing_key:
108
114
  specification_version: 3
109
- summary: Replication Aware Master/Slave Database Adapter for Rails/ActiveRecord
115
+ summary: Replication Aware Master/Slave Database Adapter for ActiveRecord
110
116
  test_files:
117
+ - spec/circuit_breaker_spec.rb
111
118
  - spec/master_slave_adapter_spec.rb
119
+ - spec/mysql_master_slave_adapter_spec.rb
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.2.0