lhm 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ module Lhm
2
+ module Printer
3
+
4
+ class Output
5
+ def write(message)
6
+ print message
7
+ end
8
+ end
9
+
10
+ class Base
11
+
12
+ def initialize
13
+ @output = Output.new
14
+ end
15
+ end
16
+
17
+ class Percentage < Base
18
+
19
+ def initialize
20
+ super
21
+ @max_length = 0
22
+ end
23
+
24
+ def notify(lowest, highest)
25
+ return if !highest || highest == 0
26
+ message = "%.2f%% (#{lowest}/#{highest}) complete" % (lowest.to_f / highest * 100.0)
27
+ write(message)
28
+ end
29
+
30
+ def end
31
+ write("100% complete")
32
+ @output.write "\n"
33
+ end
34
+
35
+ private
36
+ def write(message)
37
+ if (extra = @max_length - message.length) < 0
38
+ @max_length = message.length
39
+ extra = 0
40
+ end
41
+
42
+ @output.write "\r#{message}" + (" " * extra)
43
+ end
44
+ end
45
+
46
+ class Dot < Base
47
+ def notify(lowest = nil, highest = nil)
48
+ @output.write "."
49
+ end
50
+
51
+ def end
52
+ @output.write "\n"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ require 'lhm/throttler/time'
2
+
3
+ module Lhm
4
+ module Throttler
5
+ CLASSES = { :time_throttler => Throttler::Time }
6
+
7
+ def throttler
8
+ @throttler ||= Throttler::Time.new
9
+ end
10
+
11
+ def setup_throttler(type, options = {})
12
+ @throttler = Factory.create_throttler(type, options)
13
+ end
14
+
15
+ class Factory
16
+ def self.create_throttler(type, options = {})
17
+ case type
18
+ when Lhm::Command
19
+ type
20
+ when Symbol
21
+ CLASSES[type].new(options)
22
+ when String
23
+ CLASSES[type.to_sym].new(options)
24
+ when Class
25
+ type.new(options)
26
+ else
27
+ raise ArgumentError, 'type argument must be a Symbol, String or Class'
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ module Lhm
2
+ module Throttler
3
+ class Time
4
+ include Command
5
+
6
+ DEFAULT_TIMEOUT = 0.1
7
+ DEFAULT_STRIDE = 40_000
8
+
9
+ attr_accessor :timeout_seconds
10
+ attr_accessor :stride
11
+
12
+ def initialize(options = {})
13
+ @timeout_seconds = options[:delay] || DEFAULT_TIMEOUT
14
+ @stride = options[:stride] || DEFAULT_STRIDE
15
+ end
16
+
17
+ def execute
18
+ sleep timeout_seconds
19
+ end
20
+ end
21
+
22
+ class LegacyTime < Time
23
+ def initialize(timeout, stride)
24
+ @timeout_seconds = timeout / 1000.0
25
+ @stride = stride
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,5 +2,5 @@
2
2
  # Schmidt
3
3
 
4
4
  module Lhm
5
- VERSION = "2.0.0"
5
+ VERSION = "2.1.0"
6
6
  end
@@ -1,4 +1,4 @@
1
1
  mysqldir=/usr/local/mysql
2
- basedir=/opt/lhm-cluster
2
+ basedir=~/lhm-cluster
3
3
  master_port=3306
4
4
  slave_port=3307
@@ -1,47 +1,54 @@
1
- Preparing for master slave integration tests
2
- --------------------------------------------
1
+ # Preparing for master slave integration tests
3
2
 
4
- # configuration
3
+ ## Configuration
5
4
 
6
5
  create ~/.lhm:
7
6
 
8
7
  mysqldir=/usr/local/mysql
9
- basedir=/opt/lhm-cluster
8
+ basedir=~/lhm-cluster
10
9
  master_port=3306
11
10
  slave_port=3307
12
11
 
13
12
  mysqldir specifies the location of your mysql install. basedir is the
14
13
  directory master and slave databases will get installed into.
15
14
 
16
- # setup
15
+ ## Automatic setup
16
+
17
+ ### Run
18
+
19
+ bin/lhm-spec-clobber.sh
17
20
 
18
21
  You can set the integration specs up to run against a master slave setup by
19
- running the included `bin/lhm-spec-clobber.sh` script. this deletes the configured
20
- lhm master slave setup and reinstalls and configures a master slave setup.
22
+ running the included that. This deletes the configured lhm master slave setup and reinstalls and configures a master slave setup.
21
23
 
22
24
  Follow the manual instructions if you want more control over this process.
23
25
 
24
- # manual setup
26
+ ## Manual setup
25
27
 
26
- ## set up instances
28
+ ### set up instances
27
29
 
28
30
  bin/lhm-spec-setup-cluster.sh
29
31
 
30
- ## start instances
32
+ ### start instances
31
33
 
32
34
  basedir=/opt/lhm-luster
33
35
  mysqld --defaults-file="$basedir/master/my.cnf"
34
36
  mysqld --defaults-file="$basedir/slave/my.cnf"
35
37
 
36
- ## run the grants
38
+ ### run the grants
37
39
 
38
40
  bin/lhm-spec-grants.sh
39
41
 
40
42
  ## run specs
41
43
 
42
- To run specs in slave mode, set the SLAVE=1 when running tests:
44
+ Setup the dependency gems
45
+
46
+ export BUNDLE_GEMFILE=gemfiles/ar-3.2_mysql2.gemfile
47
+ bundle install
48
+
49
+ To run specs in slave mode, set the MASTER_SLAVE=1 when running tests:
43
50
 
44
- MASTER_SLAVE=1 rake specs
51
+ MASTER_SLAVE=1 bundle exec rake specs
45
52
 
46
53
  # connecting
47
54
 
@@ -0,0 +1,7 @@
1
+ CREATE TABLE `lines` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `between` varchar(10),
4
+ `lines` int(11),
5
+ `key` varchar(10),
6
+ PRIMARY KEY (`id`)
7
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -2,8 +2,6 @@
2
2
  # Schmidt
3
3
 
4
4
  require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
-
6
- require 'lhm'
7
5
  require 'lhm/table'
8
6
  require 'lhm/migration'
9
7
 
@@ -22,11 +20,19 @@ describe Lhm::Chunker do
22
20
  it "should copy 23 rows from origin to destination" do
23
21
  23.times { |n| execute("insert into origin set id = '#{ n * n + 23 }'") }
24
22
 
25
- Lhm::Chunker.new(@migration, connection, { :stride => 100 }).run
23
+ printer = MiniTest::Mock.new
24
+ 5.times { printer.expect(:notify, :return_value, [Fixnum, Fixnum]) }
25
+ printer.expect(:end, :return_value, [])
26
+
27
+ Lhm::Chunker.new(
28
+ @migration, connection, { :throttler => Lhm::Throttler::Time.new(:stride => 100), :printer => printer }
29
+ ).run
26
30
 
27
31
  slave do
28
32
  count_all(@destination.name).must_equal(23)
29
33
  end
34
+
35
+ printer.verify
30
36
  end
31
37
  end
32
38
  end
@@ -3,8 +3,6 @@
3
3
 
4
4
  require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
5
 
6
- require 'lhm'
7
-
8
6
  describe Lhm, "cleanup" do
9
7
  include IntegrationHelper
10
8
  before(:each) { connect_master! }
@@ -12,12 +10,18 @@ describe Lhm, "cleanup" do
12
10
  describe "changes" do
13
11
  before(:each) do
14
12
  table_create(:users)
15
- Lhm.change_table(:users, :atomic_switch => false) do |t|
16
- t.add_column(:logins, "INT(12) DEFAULT '0'")
17
- t.add_index(:logins)
13
+ simulate_failed_migration do
14
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
15
+ t.add_column(:logins, "INT(12) DEFAULT '0'")
16
+ t.add_index(:logins)
17
+ end
18
18
  end
19
19
  end
20
20
 
21
+ after(:each) do
22
+ Lhm.cleanup(true)
23
+ end
24
+
21
25
  it "should show temporary tables" do
22
26
  output = capture_stdout do
23
27
  Lhm.cleanup
@@ -26,6 +30,16 @@ describe Lhm, "cleanup" do
26
30
  output.must_match(/lhma_[0-9_]*_users/)
27
31
  end
28
32
 
33
+ it "should show temporary triggers" do
34
+ output = capture_stdout do
35
+ Lhm.cleanup
36
+ end
37
+ output.must_include("Existing LHM triggers")
38
+ output.must_include("lhmt_ins_users")
39
+ output.must_include("lhmt_del")
40
+ output.must_include("lhmt_upd_users")
41
+ end
42
+
29
43
  it "should delete temporary tables" do
30
44
  Lhm.cleanup(true).must_equal(true)
31
45
  Lhm.cleanup.must_equal(true)
@@ -1,19 +1,9 @@
1
1
  # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
2
  # Schmidt
3
+ require 'test_helper'
4
+ require 'yaml'
5
+ $password = YAML.load_file(File.expand_path(File.dirname(__FILE__)) + "/database.yml")["password"] rescue nil
3
6
 
4
- require File.expand_path(File.dirname(__FILE__)) + "/../bootstrap"
5
-
6
- begin
7
- require 'active_record'
8
- begin
9
- require 'mysql2'
10
- rescue LoadError
11
- require 'mysql'
12
- end
13
- rescue LoadError
14
- require 'dm-core'
15
- require 'dm-mysql-adapter'
16
- end
17
7
  require 'lhm/table'
18
8
  require 'lhm/sql_helper'
19
9
  require 'lhm/connection'
@@ -42,11 +32,12 @@ module IntegrationHelper
42
32
  :host => '127.0.0.1',
43
33
  :database => 'lhm',
44
34
  :username => 'root',
45
- :port => port
35
+ :port => port,
36
+ :password => $password
46
37
  )
47
38
  adapter = ActiveRecord::Base.connection
48
39
  elsif defined?(DataMapper)
49
- adapter = DataMapper.setup(:default, "mysql://root@localhost:#{port}/lhm")
40
+ adapter = DataMapper.setup(:default, "mysql://root:#{$password}@localhost:#{port}/lhm")
50
41
  end
51
42
 
52
43
  Lhm.setup(adapter)
@@ -131,7 +122,7 @@ module IntegrationHelper
131
122
  end
132
123
 
133
124
  def count_all(table)
134
- query = "select count(*) from #{ table }"
125
+ query = "select count(*) from `#{ table }`"
135
126
  select_value(query).to_i
136
127
  end
137
128
 
@@ -145,7 +136,7 @@ module IntegrationHelper
145
136
  non_unique = type == :non_unique ? 1 : 0
146
137
 
147
138
  !!select_one(%Q<
148
- show indexes in #{ table_name }
139
+ show indexes in `#{ table_name }`
149
140
  where key_name = '#{ key_name }'
150
141
  and non_unique = #{ non_unique }
151
142
  >)
@@ -162,7 +153,7 @@ module IntegrationHelper
162
153
  #
163
154
  # Misc
164
155
  #
165
-
156
+
166
157
  def capture_stdout
167
158
  out = StringIO.new
168
159
  $stdout = out
@@ -171,4 +162,21 @@ module IntegrationHelper
171
162
  ensure
172
163
  $stdout = ::STDOUT
173
164
  end
165
+
166
+ def simulate_failed_migration
167
+ Lhm::Entangler.class_eval do
168
+ alias_method :old_after, :after
169
+ def after
170
+ true
171
+ end
172
+ end
173
+
174
+ yield
175
+ ensure
176
+ Lhm::Entangler.class_eval do
177
+ undef_method :after
178
+ alias_method :after, :old_after
179
+ undef_method :old_after
180
+ end
181
+ end
174
182
  end
@@ -3,8 +3,6 @@
3
3
 
4
4
  require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
5
 
6
- require 'lhm'
7
-
8
6
  describe Lhm do
9
7
  include IntegrationHelper
10
8
 
@@ -205,6 +203,28 @@ describe Lhm do
205
203
  end
206
204
  end
207
205
 
206
+ it "works when mysql reserved words are used" do
207
+ table_create(:lines)
208
+ execute("insert into `lines` set id = 1, `between` = 'foo'")
209
+ execute("insert into `lines` set id = 2, `between` = 'bar'")
210
+
211
+ Lhm.change_table(:lines) do |t|
212
+ t.add_column('by', 'varchar(10)')
213
+ t.remove_column('lines')
214
+ t.add_index('by')
215
+ t.add_unique_index('between')
216
+ t.remove_index('by')
217
+ end
218
+
219
+ slave do
220
+ table_read(:lines).columns.must_include 'by'
221
+ table_read(:lines).columns.wont_include 'lines'
222
+ index_on_columns?(:lines, ['between'], :unique).must_equal true
223
+ index_on_columns?(:lines, ['by']).must_equal false
224
+ count_all(:lines).must_equal(2)
225
+ end
226
+ end
227
+
208
228
  describe "parallel" do
209
229
  it "should perserve inserts during migration" do
210
230
  50.times { |n| execute("insert into users set reference = '#{ n }'") }
@@ -2,8 +2,6 @@
2
2
  # Schmidt
3
3
 
4
4
  require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
-
6
- require 'lhm'
7
5
  require 'lhm/table'
8
6
 
9
7
  describe Lhm::Table do
@@ -1,13 +1,28 @@
1
1
  # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
2
  # Schmidt
3
3
 
4
- require 'minitest/spec'
5
4
  require 'minitest/autorun'
5
+ require 'minitest/spec'
6
6
  require 'minitest/mock'
7
7
  require "pathname"
8
+ require "lhm"
8
9
 
9
10
  $project = Pathname.new(File.dirname(__FILE__) + '/..').cleanpath
10
11
  $spec = $project.join("spec")
11
12
  $fixtures = $spec.join("fixtures")
12
13
 
13
- $: << $project.join("lib").to_s
14
+ begin
15
+ require 'active_record'
16
+ begin
17
+ require 'mysql2'
18
+ rescue LoadError
19
+ require 'mysql'
20
+ end
21
+ rescue LoadError
22
+ require 'dm-core'
23
+ require 'dm-mysql-adapter'
24
+ end
25
+
26
+ logger = Logger.new STDOUT
27
+ logger.level = Logger::WARN
28
+ Lhm.logger = logger