ar_mysql_flexmaster 0.1.3 → 0.2.0

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