master_slave_adapter 0.2.0 → 1.0.0.beta1

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.
@@ -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