lhm 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +5 -0
- data/.travis.yml +3 -1
- data/README.md +14 -6
- data/Rakefile +4 -2
- data/bin/lhm-spec-clobber.sh +4 -3
- data/bin/lhm-spec-setup-cluster.sh +1 -2
- data/lhm.gemspec +1 -2
- data/lib/lhm.rb +39 -10
- data/lib/lhm/chunker.rb +42 -38
- data/lib/lhm/command.rb +3 -1
- data/lib/lhm/invoker.rb +30 -9
- data/lib/lhm/printer.rb +56 -0
- data/lib/lhm/throttler.rb +32 -0
- data/lib/lhm/throttler/time.rb +29 -0
- data/lib/lhm/version.rb +1 -1
- data/spec/.lhm.example +1 -1
- data/spec/README.md +20 -13
- data/spec/fixtures/lines.ddl +7 -0
- data/spec/integration/chunker_spec.rb +9 -3
- data/spec/integration/cleanup_spec.rb +19 -5
- data/spec/integration/integration_helper.rb +26 -18
- data/spec/integration/lhm_spec.rb +22 -2
- data/spec/integration/table_spec.rb +0 -2
- data/spec/{bootstrap.rb → test_helper.rb} +17 -2
- data/spec/unit/chunker_spec.rb +86 -77
- data/spec/unit/datamapper_connection_spec.rb +1 -0
- data/spec/unit/lhm_spec.rb +29 -0
- data/spec/unit/printer_spec.rb +79 -0
- data/spec/unit/throttler_spec.rb +73 -0
- data/spec/unit/unit_helper.rb +1 -13
- metadata +22 -17
data/lib/lhm/printer.rb
ADDED
@@ -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
|
data/lib/lhm/version.rb
CHANGED
data/spec/.lhm.example
CHANGED
data/spec/README.md
CHANGED
@@ -1,47 +1,54 @@
|
|
1
|
-
Preparing for master slave integration tests
|
2
|
-
--------------------------------------------
|
1
|
+
# Preparing for master slave integration tests
|
3
2
|
|
4
|
-
|
3
|
+
## Configuration
|
5
4
|
|
6
5
|
create ~/.lhm:
|
7
6
|
|
8
7
|
mysqldir=/usr/local/mysql
|
9
|
-
basedir
|
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
|
-
|
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
|
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
|
-
|
26
|
+
## Manual setup
|
25
27
|
|
26
|
-
|
28
|
+
### set up instances
|
27
29
|
|
28
30
|
bin/lhm-spec-setup-cluster.sh
|
29
31
|
|
30
|
-
|
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
|
-
|
38
|
+
### run the grants
|
37
39
|
|
38
40
|
bin/lhm-spec-grants.sh
|
39
41
|
|
40
42
|
## run specs
|
41
43
|
|
42
|
-
|
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
|
|
@@ -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
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
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
|
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 }'") }
|
@@ -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
|
-
|
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
|