ar_mysql_flexmaster 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -6,11 +6,16 @@ require 'yaggy'
6
6
 
7
7
  Yaggy.gem(File.expand_path("ar_mysql_flexmaster.gemspec", File.dirname(__FILE__)), :push_gem => true)
8
8
 
9
- Rake::TestTask.new(:test) do |test|
9
+ Rake::TestTask.new(:test_units) do |test|
10
10
  test.libs << 'lib' << 'test'
11
11
  test.pattern = 'test/*_test.rb'
12
- #test.test_files = ['test/integration/run_integration_tests']
13
12
  test.verbose = true
14
13
  end
15
14
 
15
+ task :test do
16
+ retval = true
17
+ retval &= Rake::Task[:test_units].invoke
18
+ retval &= system(File.dirname(__FILE__) + "/test/integration/run_integration_tests")
19
+ end
20
+
16
21
  task :default => :test
@@ -12,7 +12,7 @@ Gem::Specification.new do |gem|
12
12
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
13
  gem.name = "ar_mysql_flexmaster"
14
14
  gem.require_paths = ["lib"]
15
- gem.version = "0.1.3"
15
+ gem.version = "0.2.0"
16
16
 
17
17
  gem.add_runtime_dependency("mysql2")
18
18
  gem.add_runtime_dependency("activerecord")
@@ -94,23 +94,30 @@ module ActiveRecord
94
94
  def find_correct_host
95
95
  cxs = hosts_and_ports.map do |host, port|
96
96
  initialize_connection(host, port)
97
- end
97
+ end.compact
98
98
 
99
- correct_cxs = cxs.select { |cx| cx && cx_correct?(cx) }
99
+ correct_cxs = cxs.select { |cx| cx_correct?(cx) }
100
100
 
101
+ chosen_cx = nil
101
102
  if @is_master
102
103
  # for master connections, we make damn sure that we have just one master
103
104
  if correct_cxs.size == 1
104
- return correct_cxs.first
105
+ chosen_cx = correct_cxs.first
105
106
  else
106
107
  # nothing read-write, or too many read-write
107
108
  # (should we manually close the connections?)
108
- return nil
109
+ chosen_cx = nil
109
110
  end
110
111
  else
111
- # for slave connections, we just return a random RO candidate
112
- return correct_cxs.shuffle.first
112
+ # for slave connections, we just return a random RO candidate or the master if none are available
113
+ if correct_cxs.empty?
114
+ chosen_cx = cxs.first
115
+ else
116
+ chosen_cx = correct_cxs.shuffle.first
117
+ end
113
118
  end
119
+ cxs.each { |cx| cx.close unless chosen_cx == cx }
120
+ chosen_cx
114
121
  end
115
122
 
116
123
  def initialize_connection(host, port)
@@ -3,6 +3,7 @@ require 'ar_mysql_flexmaster'
3
3
  require 'active_record'
4
4
  require_relative 'boot_mysql_env'
5
5
  require 'test/unit'
6
+ require 'debugger'
6
7
 
7
8
  File.open(File.dirname(File.expand_path(__FILE__)) + "/database.yml", "w+") do |f|
8
9
  f.write <<-EOL
@@ -17,7 +18,7 @@ test_slave:
17
18
  adapter: mysql_flexmaster
18
19
  username: flex
19
20
  slave: true
20
- hosts: ["127.0.0.1:#{$mysql_slave.port}", "127.0.0.1:#{$mysql_slave_2.port}"]
21
+ hosts: ["127.0.0.1:#{$mysql_master.port}", "127.0.0.1:#{$mysql_slave.port}", "127.0.0.1:#{$mysql_slave_2.port}"]
21
22
  password:
22
23
  database: flexmaster_test
23
24
  EOL
@@ -57,7 +58,7 @@ class TestArFlexmaster < Test::Unit::TestCase
57
58
  end
58
59
 
59
60
  def test_should_select_the_master_on_boot
60
- assert main_connection_is_master?
61
+ assert main_connection_is_original_master?
61
62
  end
62
63
 
63
64
  def test_should_hold_txs_until_timeout_then_abort
@@ -80,7 +81,7 @@ class TestArFlexmaster < Test::Unit::TestCase
80
81
  $mysql_slave.set_rw(true)
81
82
  end
82
83
  User.create(:name => "foo")
83
- assert !main_connection_is_master?
84
+ assert !main_connection_is_original_master?
84
85
  assert User.first(:conditions => {:name => "foo"})
85
86
  end
86
87
 
@@ -92,7 +93,7 @@ class TestArFlexmaster < Test::Unit::TestCase
92
93
  $mysql_slave.set_rw(true)
93
94
  end
94
95
  User.update_all(:name => "bar")
95
- assert !main_connection_is_master?
96
+ assert !main_connection_is_original_master?
96
97
  assert_equal "bar", User.first.name
97
98
  end
98
99
 
@@ -109,11 +110,11 @@ class TestArFlexmaster < Test::Unit::TestCase
109
110
  ActiveRecord::Base.connection
110
111
  $mysql_master.set_rw(false)
111
112
  $mysql_slave.set_rw(true)
112
- assert main_connection_is_master?
113
+ assert main_connection_is_original_master?
113
114
  100.times do
114
115
  u = User.first
115
116
  end
116
- assert !main_connection_is_master?
117
+ assert !main_connection_is_original_master?
117
118
  end
118
119
 
119
120
  def test_should_choose_a_random_slave_connection
@@ -131,10 +132,11 @@ class TestArFlexmaster < Test::Unit::TestCase
131
132
  User.create!
132
133
  $mysql_master.set_rw(false)
133
134
  $mysql_slave.set_rw(true)
134
- 11.times do
135
- UserSlave.first
135
+ 20.times do
136
+ UserSlave.connection.execute("select 1")
136
137
  end
137
- assert_equal $mysql_slave_2.port, port_for_class(UserSlave)
138
+ connected_port = port_for_class(UserSlave)
139
+ assert [$mysql_slave_2.port, $mysql_master.port].include?(connected_port)
138
140
  end
139
141
 
140
142
  def test_xxx_non_responsive_master
@@ -149,20 +151,31 @@ class TestArFlexmaster < Test::Unit::TestCase
149
151
  def test_yyy_shooting_the_master_in_the_head
150
152
  User.create!
151
153
  Process.kill("TERM", $mysql_master.pid)
154
+ sleep 1
152
155
  $mysql_slave.set_rw(true)
153
156
  User.connection.reconnect!
154
157
  User.create!
155
158
  UserSlave.first
156
- assert !main_connection_is_master?
159
+ assert !main_connection_is_original_master?
157
160
  end
158
161
 
162
+ # test that when nothing else is available we can fall back to the master in a slave role
163
+ # note that by the time this test runs, the 'yyy' test has already killed the master
164
+ def test_zzz_shooting_the_other_slave_in_the_head
165
+ $mysql_slave.set_rw(true)
166
+ $mysql_slave_2.kill!
167
+ UserSlave.connection.reconnect!
168
+ assert port_for_class(UserSlave) == $mysql_slave.port
169
+ end
170
+
171
+
159
172
  private
160
173
 
161
174
  def port_for_class(klass)
162
175
  klass.connection.execute("show global variables like 'port'").first.last.to_i
163
176
  end
164
177
 
165
- def main_connection_is_master?
178
+ def main_connection_is_original_master?
166
179
  port = port_for_class(ActiveRecord::Base)
167
180
  port == $mysql_master.port
168
181
  end
@@ -2,33 +2,39 @@
2
2
 
3
3
  require_relative "mysql_isolated_server"
4
4
 
5
- mysql_master = MysqlIsolatedServer.new(allow_output: false)
6
- mysql_master.boot!
7
- mysql_master.connection.query("set global server_id=1")
5
+ threads = []
6
+ threads << Thread.new do
7
+ $mysql_master = MysqlIsolatedServer.new(allow_output: false)
8
+ $mysql_master.boot!
9
+ $mysql_master.connection.query("set global server_id=1")
8
10
 
9
- puts "mysql master booted on port #{mysql_master.port} -- access with mysql -uroot -h127.0.0.1 --port=#{mysql_master.port} mysql"
11
+ puts "mysql master booted on port #{$mysql_master.port} -- access with mysql -uroot -h127.0.0.1 --port=#{$mysql_master.port} mysql"
12
+ end
10
13
 
11
- mysql_slave = MysqlIsolatedServer.new
12
- mysql_slave.boot!
13
- mysql_slave.connection.query("set global server_id=2")
14
+ threads << Thread.new do
15
+ $mysql_slave = MysqlIsolatedServer.new
16
+ $mysql_slave.boot!
17
+ $mysql_slave.connection.query("set global server_id=2")
14
18
 
15
- puts "mysql slave booted on port #{mysql_slave.port} -- access with mysql -uroot -h127.0.0.1 --port=#{mysql_slave.port} mysql"
19
+ puts "mysql slave booted on port #{$mysql_slave.port} -- access with mysql -uroot -h127.0.0.1 --port=#{$mysql_slave.port} mysql"
20
+ end
16
21
 
17
- mysql_slave_2 = MysqlIsolatedServer.new
18
- mysql_slave_2.boot!
19
- mysql_slave_2.connection.query("set global server_id=3")
22
+ threads << Thread.new do
23
+ $mysql_slave_2 = MysqlIsolatedServer.new
24
+ $mysql_slave_2.boot!
25
+ $mysql_slave_2.connection.query("set global server_id=3")
20
26
 
21
- puts "mysql chained slave booted on port #{mysql_slave_2.port} -- access with mysql -uroot -h127.0.0.1 --port=#{mysql_slave_2.port} mysql"
27
+ puts "mysql chained slave booted on port #{$mysql_slave_2.port} -- access with mysql -uroot -h127.0.0.1 --port=#{$mysql_slave_2.port} mysql"
28
+ end
22
29
 
23
- mysql_master.connection.query("CHANGE MASTER TO master_host='127.0.0.1', master_user='root', master_password=''")
24
- mysql_slave.make_slave_of(mysql_master)
25
- mysql_slave_2.make_slave_of(mysql_slave)
30
+ threads.each(&:join)
26
31
 
27
- mysql_master.connection.query("GRANT ALL ON flexmaster_test.* to flex@localhost")
28
- mysql_master.connection.query("CREATE DATABASE flexmaster_test")
29
- mysql_master.connection.query("CREATE TABLE flexmaster_test.users (id INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, name varchar(20))")
30
- mysql_master.connection.query("INSERT INTO flexmaster_test.users set name='foo'")
32
+ $mysql_master.connection.query("CHANGE MASTER TO master_host='127.0.0.1', master_user='root', master_password=''")
33
+ $mysql_slave.make_slave_of($mysql_master)
34
+ $mysql_slave_2.make_slave_of($mysql_slave)
35
+
36
+ $mysql_master.connection.query("GRANT ALL ON flexmaster_test.* to flex@localhost")
37
+ $mysql_master.connection.query("CREATE DATABASE flexmaster_test")
38
+ $mysql_master.connection.query("CREATE TABLE flexmaster_test.users (id INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, name varchar(20))")
39
+ $mysql_master.connection.query("INSERT INTO flexmaster_test.users set name='foo'")
31
40
 
32
- $mysql_master = mysql_master
33
- $mysql_slave = mysql_slave
34
- $mysql_slave_2 = mysql_slave_2
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ retval = true
4
+ Dir.glob(File.dirname(__FILE__) + '/*_test.rb').each do |f|
5
+ retval &= system("ruby #{f}")
6
+ end
7
+ exit retval
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar_mysql_flexmaster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-27 00:00:00.000000000 Z
12
+ date: 2013-03-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mysql2
@@ -120,6 +120,7 @@ files:
120
120
  - test/boot_mysql_env.rb
121
121
  - test/boot_slave
122
122
  - test/integration/no_traffic_test.rb
123
+ - test/integration/run_integration_tests
123
124
  - test/integration/there_and_back_again_test.rb
124
125
  - test/integration/with_queries_to_be_killed_test.rb
125
126
  - test/integration/wrong_setup_test.rb
@@ -141,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
142
  version: '0'
142
143
  segments:
143
144
  - 0
144
- hash: 1413669962655768174
145
+ hash: 3291849220981304751
145
146
  required_rubygems_version: !ruby/object:Gem::Requirement
146
147
  none: false
147
148
  requirements:
@@ -150,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
151
  version: '0'
151
152
  segments:
152
153
  - 0
153
- hash: 1413669962655768174
154
+ hash: 3291849220981304751
154
155
  requirements: []
155
156
  rubyforge_project:
156
157
  rubygems_version: 1.8.24
@@ -162,6 +163,7 @@ test_files:
162
163
  - test/boot_mysql_env.rb
163
164
  - test/boot_slave
164
165
  - test/integration/no_traffic_test.rb
166
+ - test/integration/run_integration_tests
165
167
  - test/integration/there_and_back_again_test.rb
166
168
  - test/integration/with_queries_to_be_killed_test.rb
167
169
  - test/integration/wrong_setup_test.rb