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.
@@ -1,21 +1,23 @@
1
1
  $:.push File.expand_path("../lib", __FILE__)
2
2
 
3
+ require 'active_record/connection_adapters/master_slave_adapter/version'
4
+
3
5
  Gem::Specification.new do |s|
4
6
  s.name = 'master_slave_adapter'
5
- s.version = File.read('VERSION').to_s
7
+ s.version = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::VERSION
6
8
  s.platform = Gem::Platform::RUBY
7
- s.authors = [ 'Mauricio Linhares', 'Torsten Curdt', 'Kim Altintop', 'Omid Aladini', 'SoundCloud' ]
8
- s.email = %q{kim@soundcloud.com tcurdt@soundcloud.com omid@soundcloud.com}
9
+ s.authors = [ 'Mauricio Linhares', 'Torsten Curdt', 'Kim Altintop', 'Omid Aladini', 'Tiago Loureiro', 'Tobias Schmidt', 'SoundCloud' ]
10
+ s.email = %q{kim@soundcloud.com tcurdt@soundcloud.com ts@soundcloud}
9
11
  s.homepage = 'http://github.com/soundcloud/master_slave_adapter'
10
- s.summary = %q{Replication Aware Master/Slave Database Adapter for Rails/ActiveRecord}
11
- s.description = %q{(MySQL) Replication Aware Master/Slave Database Adapter for Rails/ActiveRecord}
12
+ s.summary = %q{Replication Aware Master/Slave Database Adapter for ActiveRecord}
13
+ s.description = %q{(MySQL) Replication Aware Master/Slave Database Adapter for ActiveRecord}
12
14
 
13
15
  s.files = `git ls-files`.split("\n")
14
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
18
  s.require_path = 'lib'
17
19
 
18
- s.required_ruby_version = '>= 1.9.2'
20
+ s.required_ruby_version = '>= 1.8.7'
19
21
  s.required_rubygems_version = '>= 1.3.7'
20
22
 
21
23
  s.add_development_dependency 'rspec'
@@ -0,0 +1,52 @@
1
+ $: << File.expand_path(File.join(File.dirname( __FILE__ ), '..', 'lib'))
2
+
3
+ require 'rspec'
4
+ require 'active_record/connection_adapters/master_slave_adapter/circuit_breaker'
5
+
6
+ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::CircuitBreaker do
7
+ let(:logger) { nil }
8
+ let(:failure_threshold) { 5 }
9
+ let(:timeout) { 10 }
10
+
11
+ subject { described_class.new(logger, failure_threshold, timeout) }
12
+
13
+ it 'should not be tripped by default' do
14
+ should_not be_tripped
15
+ end
16
+
17
+ context "after single failure" do
18
+ before { subject.fail! }
19
+
20
+ it 'should remain untripped' do
21
+ should_not be_tripped
22
+ end
23
+ end
24
+
25
+ context "after failure threshold is reached" do
26
+ before { failure_threshold.times { subject.fail! } }
27
+
28
+ it { should be_tripped }
29
+
30
+ context "and timeout exceeded" do
31
+ before do
32
+ now = Time.now
33
+ Time.stub(:now).and_return(now + timeout)
34
+ subject.tripped? # side effect :/
35
+ end
36
+
37
+ it { should_not be_tripped }
38
+
39
+ context "after single failure" do
40
+ before { subject.fail! }
41
+
42
+ it { should be_tripped }
43
+ end
44
+
45
+ context "after single success" do
46
+ before { subject.success! }
47
+
48
+ it { should_not be_tripped }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,18 +1,35 @@
1
- require 'rubygems'
2
- require 'active_record'
1
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
3
  require 'rspec'
4
+ require 'active_record/connection_adapters/master_slave_adapter'
4
5
 
5
6
  ActiveRecord::Base.logger =
6
- Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG }
7
+ Logger.new($stdout).tap { |l| l.level = Logger::DEBUG }
7
8
 
8
- $LOAD_PATH << File.expand_path(File.join( File.dirname( __FILE__ ), '..', 'lib' ))
9
+ module ActiveRecord
10
+ class Base
11
+ cattr_accessor :master_mock, :slave_mock
12
+ def self.test_connection(config)
13
+ config[:database] == 'slave' ? slave_mock : master_mock
14
+ end
9
15
 
10
- require 'active_record/connection_adapters/master_slave_adapter'
16
+ def self.test_master_slave_connection(config)
17
+ TestMasterSlaveAdapter.new(config, logger)
18
+ end
19
+ end
20
+
21
+ module ConnectionAdapters
22
+ class TestMasterSlaveAdapter < MasterSlaveAdapter::Base
23
+ def master_clock
24
+ end
25
+
26
+ def slave_clock(connection)
27
+ end
11
28
 
12
- class ActiveRecord::Base
13
- cattr_accessor :master_mock, :slave_mock
14
- def self.test_connection(config)
15
- config[:database] == 'slave' ? slave_mock : master_mock
29
+ def connection_error?(exception)
30
+ true
31
+ end
32
+ end
16
33
  end
17
34
  end
18
35
 
@@ -34,6 +51,7 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
34
51
  {
35
52
  :reconnect! => true,
36
53
  :disconnect! => true,
54
+ :active? => true,
37
55
  }
38
56
  end
39
57
 
@@ -58,16 +76,10 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
58
76
  ActiveRecord::Base.connection
59
77
  end
60
78
 
61
- SchemaStatements = ActiveRecord::ConnectionAdapters::SchemaStatements.instance_methods.map(&:to_sym)
79
+ SchemaStatements = ActiveRecord::ConnectionAdapters::SchemaStatements.public_instance_methods.map(&:to_sym)
62
80
  SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ]
63
- Clock = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::Clock
64
81
 
65
82
  before do
66
- unless database_setup[:disable_connection_test] == 'true'
67
- [ master_connection, slave_connection ].each do |c|
68
- c.should_receive(:active?).exactly(2).times.and_return(true)
69
- end
70
- end
71
83
  ActiveRecord::Base.establish_connection(database_setup)
72
84
  end
73
85
 
@@ -76,12 +88,6 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
76
88
  end
77
89
 
78
90
  describe 'common configuration' do
79
- before do
80
- [ master_connection, slave_connection ].each do |c|
81
- c.stub!( :select_value ).with( "SELECT 1", "test select" ).and_return( true )
82
- end
83
- end
84
-
85
91
  it "should call 'columns' on master" do
86
92
  master_connection.should_receive(:columns)
87
93
  adapter_connection.columns
@@ -145,7 +151,7 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
145
151
  end
146
152
 
147
153
  it 'should be a master slave connection' do
148
- adapter_connection.class.should == ActiveRecord::ConnectionAdapters::MasterSlaveAdapter
154
+ adapter_connection.class.should == ActiveRecord::ConnectionAdapters::TestMasterSlaveAdapter
149
155
  end
150
156
 
151
157
  it 'should have a master connection' do
@@ -153,39 +159,36 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
153
159
  end
154
160
 
155
161
  it 'should have a slave connection' do
156
- master_connection.stub!( :open_transactions ).and_return( 0 )
157
162
  adapter_connection.slave_connection!.should == slave_connection
158
163
  end
159
164
  end
160
165
 
161
166
  describe "connection testing" do
167
+ before do
168
+ master_connection.unstub(:active?)
169
+ slave_connection.unstub(:active?)
170
+ end
171
+
162
172
  context "disabled" do
163
173
  let(:database_setup) do
164
174
  default_database_setup.merge(:disable_connection_test => 'true')
165
175
  end
166
176
 
167
- context "on master" do
168
- SchemaStatements.each do |method|
169
- it "should not perform the testing when #{method} is called" do
170
- master_connection.tap do |c|
171
- c.should_not_receive(:active?)
172
- c.should_receive(method).with('testing').and_return(true)
173
- end
174
- adapter_connection.send(method, 'testing')
175
- end
176
- end
177
+ it "should not perform the testing" do
178
+ master_connection.should_not_receive(:active?)
179
+ slave_connection.should_not_receive(:active?)
180
+
181
+ adapter_connection.active?.should == true
177
182
  end
183
+ end
178
184
 
179
- context "on slave" do
180
- SelectMethods.each do |method|
181
- it "should not perform the testing when #{method} is called" do
182
- slave_connection.tap do |c|
183
- c.should_not_receive(:active?)
184
- c.should_receive(method).with('testing').and_return(true)
185
- end
186
- adapter_connection.send(method, 'testing')
187
- end
188
- end
185
+ context "enabled" do
186
+ it "should perform the testing" do
187
+ # twice == one during connection + one on explicit #active? call
188
+ master_connection.should_receive(:active?).twice.and_return(true)
189
+ slave_connection.should_receive(:active?).twice.and_return(true)
190
+
191
+ adapter_connection.active?.should == true
189
192
  end
190
193
  end
191
194
  end
@@ -196,259 +199,11 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
196
199
  end
197
200
  end
198
201
 
199
- describe 'consistency' do
200
- before do
201
- ActiveRecord::ConnectionAdapters::MasterSlaveAdapter.reset!
202
-
203
- [ master_connection, slave_connection ].each do |c|
204
- c.stub!(:select_value).with("SELECT 1", "test select").and_return(true)
205
- end
206
- end
207
-
208
- def zero
209
- Clock.zero
210
- end
211
-
212
- def master_position(pos)
213
- Clock.new('', pos)
214
- end
215
-
216
- def slave_should_report_clock(pos)
217
- pos = Array(pos)
218
- values = pos.map { |p| { 'Relay_Master_Log_File' => '', 'Exec_Master_Log_Pos' => p } }
219
- slave_connection.
220
- should_receive('select_one').exactly(pos.length).with('SHOW SLAVE STATUS').
221
- and_return(*values)
222
- end
223
-
224
- def master_should_report_clock(pos)
225
- pos = Array(pos)
226
- values = pos.map { |p| { 'File' => '', 'Position' => p } }
227
- master_connection.
228
- should_receive('select_one').exactly(pos.length).with('SHOW MASTER STATUS').
229
- and_return(*values)
230
- end
231
-
232
- SelectMethods.each do |method|
233
- it "should raise an exception if consistency is nil" do
234
- lambda do
235
- ActiveRecord::Base.with_consistency(nil) do
236
- end
237
- end.should raise_error(ArgumentError)
238
- end
239
-
240
- it "should send the method '#{method}' to the slave if clock.zero is given" do
241
- slave_should_report_clock(0)
242
- slave_connection.should_receive(method).with('testing').and_return(true)
243
- old_clock = zero
244
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
245
- adapter_connection.send(method, 'testing')
246
- end
247
- new_clock.should be_a(zero.class)
248
- new_clock.should equal(zero)
249
- end
250
-
251
- it "should send the method '#{method}' to the master if slave hasn't cought up to required clock yet" do
252
- slave_should_report_clock(0)
253
- master_connection.should_receive(method).with('testing').and_return(true)
254
- old_clock = master_position(1)
255
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
256
- adapter_connection.send(method, 'testing' )
257
- end
258
- new_clock.should be_a(zero.class)
259
- new_clock.should equal(old_clock)
260
- end
261
-
262
- it "should send the method '#{method}' to the master connection if there are open transactions" do
263
- master_connection.stub!(:open_transactions).and_return(1)
264
- master_connection.should_receive(method).with('testing').and_return(true)
265
- old_clock = zero
266
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
267
- adapter_connection.send(method, 'testing')
268
- end
269
- new_clock.should be_a(zero.class)
270
- new_clock.should equal(zero)
271
- end
272
-
273
- it "should send the method '#{method}' to the master after a write operation" do
274
- slave_should_report_clock(0)
275
- master_should_report_clock(2)
276
- slave_connection.should_receive(method).with('testing').and_return(true)
277
- master_connection.should_receive('update').with('testing').and_return(true)
278
- master_connection.should_receive(method).with('testing').and_return(true)
279
- old_clock = zero
280
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
281
- adapter_connection.send(method, 'testing') # slave
282
- adapter_connection.send('update', 'testing') # master
283
- adapter_connection.send(method, 'testing') # master
284
- end
285
- new_clock.should be_a(zero.class)
286
- new_clock.should > old_clock
287
- end
288
- end
289
-
290
- it "should update the clock after a transaction" do
291
- slave_should_report_clock(0)
292
- master_should_report_clock([0, 1, 1])
293
-
294
- slave_connection.
295
- should_receive('select_all').exactly(1).times.with('testing').
296
- and_return(true)
297
-
298
- master_connection.
299
- should_receive('update').exactly(3).times.with('testing').
300
- and_return(true)
301
- master_connection.
302
- should_receive('select_all').exactly(5).times.with('testing').
303
- and_return(true)
304
- %w(begin_db_transaction
305
- commit_db_transaction
306
- increment_open_transactions
307
- decrement_open_transactions
308
- outside_transaction?).each do |txstmt|
309
- master_connection.should_receive(txstmt).exactly(1).times
310
- end
311
-
312
- master_connection.
313
- should_receive('open_transactions').exactly(13).times.
314
- and_return(
315
- # adapter: with_consistency, select_all, update, select_all
316
- 0, 0, 0, 0,
317
- # connection: transaction
318
- 0,
319
- # adapter: select_all, update, select_all, commit_db_transaction
320
- 1, 1, 1, 0,
321
- # connection: transaction (ensure)
322
- 0,
323
- # adapter: select_all, update, select_all
324
- 0, 0, 0
325
- )
326
-
327
- old_clock = zero
328
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
329
- adapter_connection.send('select_all', 'testing') # slave s=0 m=0
330
- adapter_connection.send('update', 'testing') # master s=0 m=1
331
- adapter_connection.send('select_all', 'testing') # master s=0 m=1
332
-
333
- ActiveRecord::Base.transaction do
334
- adapter_connection.send('select_all', 'testing') # master s=0 m=1
335
- adapter_connection.send('update', 'testing') # master s=0 m=1
336
- adapter_connection.send('select_all', 'testing') # master s=0 m=1
337
- end
338
-
339
- adapter_connection.send('select_all', 'testing') # master s=0 m=2
340
- adapter_connection.send('update', 'testing') # master s=0 m=3
341
- adapter_connection.send('select_all', 'testing') # master s=0 m=3
342
- end
343
-
344
- new_clock.should > old_clock
345
- end
346
-
347
- context "with nested with_consistency" do
348
- it "should return the same clock if not writing and no lag" do
349
- slave_should_report_clock(0) # note: tests memoizing slave clock
350
- slave_connection.
351
- should_receive('select_one').exactly(3).times.with('testing').
352
- and_return(true)
353
-
354
- old_clock = zero
355
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
356
- adapter_connection.send('select_one', 'testing')
357
- ActiveRecord::Base.with_consistency(old_clock) do
358
- adapter_connection.send('select_one', 'testing')
359
- end
360
- adapter_connection.send('select_one', 'testing')
361
- end
362
- new_clock.should equal(old_clock)
363
- end
364
-
365
- it "requesting a newer clock should return a new clock" do
366
- adapter_connection.
367
- should_receive('slave_consistent?').exactly(2).times.
368
- and_return(true, false)
369
- slave_connection.
370
- should_receive('select_all').exactly(2).times.with('testing').
371
- and_return(true)
372
- master_connection.
373
- should_receive('select_all').exactly(1).times.with('testing').
374
- and_return(true)
375
-
376
- start_clock = zero
377
- inner_clock = zero
378
- outer_clock = ActiveRecord::Base.with_consistency(start_clock) do
379
- adapter_connection.send('select_all', 'testing') # slave
380
- inner_clock = ActiveRecord::Base.with_consistency(master_position(1)) do
381
- adapter_connection.send('select_all', 'testing') # master
382
- end
383
- adapter_connection.send('select_all', 'testing') # slave
384
- end
385
-
386
- start_clock.should equal(outer_clock)
387
- inner_clock.should > start_clock
388
- end
389
- end
390
-
391
- it "should do the right thing when nested inside with_master" do
392
- slave_should_report_clock(0)
393
- slave_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
394
- master_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
395
- ActiveRecord::Base.with_master do
396
- adapter_connection.send('select_all', 'testing') # master
397
- ActiveRecord::Base.with_consistency(zero) do
398
- adapter_connection.send('select_all', 'testing') # slave
399
- end
400
- adapter_connection.send('select_all', 'testing') # master
401
- end
402
- end
403
-
404
- it "should do the right thing when nested inside with_slave" do
405
- slave_should_report_clock(0)
406
- slave_connection.should_receive('select_all').exactly(3).times.with('testing').and_return(true)
407
- ActiveRecord::Base.with_slave do
408
- adapter_connection.send('select_all', 'testing') # slave
409
- ActiveRecord::Base.with_consistency(zero) do
410
- adapter_connection.send('select_all', 'testing') # slave
411
- end
412
- adapter_connection.send('select_all', 'testing') # slave
413
- end
414
- end
415
-
416
- it "should do the right thing when wrapping with_master" do
417
- slave_should_report_clock(0)
418
- slave_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
419
- master_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
420
- ActiveRecord::Base.with_consistency(zero) do
421
- adapter_connection.send('select_all', 'testing') # slave
422
- ActiveRecord::Base.with_master do
423
- adapter_connection.send('select_all', 'testing') # master
424
- end
425
- adapter_connection.send('select_all', 'testing') # slave
426
- end
427
- end
428
-
429
- it "should do the right thing when wrapping with_slave" do
430
- slave_should_report_clock(0)
431
- slave_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
432
- master_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
433
- ActiveRecord::Base.with_consistency(master_position(1)) do
434
- adapter_connection.send('select_all', 'testing') # master
435
- ActiveRecord::Base.with_slave do
436
- adapter_connection.send('select_all', 'testing') # slave
437
- end
438
- adapter_connection.send('select_all', 'testing') # master
439
- end
440
- end
441
- end # /with_consistency
442
-
443
202
  describe "transaction callbacks" do
444
- before do
445
- ActiveRecord::ConnectionAdapters::MasterSlaveAdapter.reset!
446
- end
447
-
448
203
  def run_tx
449
204
  adapter_connection.
450
205
  should_receive('master_clock').
451
- and_return(Clock.new('', 1))
206
+ and_return(1)
452
207
  %w(begin_db_transaction
453
208
  commit_db_transaction
454
209
  increment_open_transactions