lhm 2.0.0 → 2.1.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.
- 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
|