lhm-teak 3.6.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 +7 -0
- data/.github/workflows/test.yml +43 -0
- data/.gitignore +12 -0
- data/.rubocop.yml +183 -0
- data/.travis.yml +21 -0
- data/Appraisals +24 -0
- data/CHANGELOG.md +254 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +67 -0
- data/LICENSE +27 -0
- data/README.md +335 -0
- data/Rakefile +33 -0
- data/dev.yml +45 -0
- data/docker-compose.yml +60 -0
- data/gemfiles/activerecord_5.2.gemfile +9 -0
- data/gemfiles/activerecord_5.2.gemfile.lock +66 -0
- data/gemfiles/activerecord_6.0.gemfile +7 -0
- data/gemfiles/activerecord_6.0.gemfile.lock +68 -0
- data/gemfiles/activerecord_6.1.gemfile +7 -0
- data/gemfiles/activerecord_6.1.gemfile.lock +67 -0
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile +7 -0
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile.lock +65 -0
- data/lhm.gemspec +38 -0
- data/lib/lhm/atomic_switcher.rb +46 -0
- data/lib/lhm/chunk_finder.rb +62 -0
- data/lib/lhm/chunk_insert.rb +61 -0
- data/lib/lhm/chunker.rb +95 -0
- data/lib/lhm/cleanup/current.rb +71 -0
- data/lib/lhm/command.rb +48 -0
- data/lib/lhm/connection.rb +108 -0
- data/lib/lhm/entangler.rb +112 -0
- data/lib/lhm/intersection.rb +51 -0
- data/lib/lhm/invoker.rb +100 -0
- data/lib/lhm/locked_switcher.rb +76 -0
- data/lib/lhm/migration.rb +51 -0
- data/lib/lhm/migrator.rb +244 -0
- data/lib/lhm/printer.rb +63 -0
- data/lib/lhm/proxysql_helper.rb +10 -0
- data/lib/lhm/railtie.rb +9 -0
- data/lib/lhm/sql_helper.rb +77 -0
- data/lib/lhm/sql_retry.rb +180 -0
- data/lib/lhm/table.rb +121 -0
- data/lib/lhm/table_name.rb +23 -0
- data/lib/lhm/test_support.rb +35 -0
- data/lib/lhm/throttler/slave_lag.rb +162 -0
- data/lib/lhm/throttler/threads_running.rb +53 -0
- data/lib/lhm/throttler/time.rb +29 -0
- data/lib/lhm/throttler.rb +36 -0
- data/lib/lhm/timestamp.rb +11 -0
- data/lib/lhm/version.rb +6 -0
- data/lib/lhm-shopify.rb +1 -0
- data/lib/lhm.rb +156 -0
- data/scripts/helpers/wait-for-dbs.sh +21 -0
- data/scripts/mysql/reader/create_replication.sql +10 -0
- data/scripts/mysql/writer/create_test_db.sql +1 -0
- data/scripts/mysql/writer/create_users.sql +6 -0
- data/scripts/proxysql/proxysql.cnf +117 -0
- data/shipit.rubygems.yml +0 -0
- data/spec/.lhm.example +4 -0
- data/spec/README.md +58 -0
- data/spec/fixtures/bigint_table.ddl +4 -0
- data/spec/fixtures/composite_primary_key.ddl +6 -0
- data/spec/fixtures/composite_primary_key_dest.ddl +6 -0
- data/spec/fixtures/custom_primary_key.ddl +6 -0
- data/spec/fixtures/custom_primary_key_dest.ddl +6 -0
- data/spec/fixtures/destination.ddl +6 -0
- data/spec/fixtures/lines.ddl +7 -0
- data/spec/fixtures/origin.ddl +6 -0
- data/spec/fixtures/permissions.ddl +5 -0
- data/spec/fixtures/small_table.ddl +4 -0
- data/spec/fixtures/tracks.ddl +5 -0
- data/spec/fixtures/users.ddl +14 -0
- data/spec/fixtures/wo_id_int_column.ddl +6 -0
- data/spec/integration/atomic_switcher_spec.rb +129 -0
- data/spec/integration/chunk_insert_spec.rb +30 -0
- data/spec/integration/chunker_spec.rb +269 -0
- data/spec/integration/cleanup_spec.rb +147 -0
- data/spec/integration/database.yml +25 -0
- data/spec/integration/entangler_spec.rb +68 -0
- data/spec/integration/integration_helper.rb +252 -0
- data/spec/integration/invoker_spec.rb +33 -0
- data/spec/integration/lhm_spec.rb +659 -0
- data/spec/integration/lock_wait_timeout_spec.rb +30 -0
- data/spec/integration/locked_switcher_spec.rb +50 -0
- data/spec/integration/proxysql_spec.rb +34 -0
- data/spec/integration/sql_retry/db_connection_helper.rb +52 -0
- data/spec/integration/sql_retry/lock_wait_spec.rb +127 -0
- data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +114 -0
- data/spec/integration/sql_retry/proxysql_helper.rb +22 -0
- data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +109 -0
- data/spec/integration/table_spec.rb +83 -0
- data/spec/integration/toxiproxy_helper.rb +40 -0
- data/spec/test_helper.rb +69 -0
- data/spec/unit/atomic_switcher_spec.rb +29 -0
- data/spec/unit/chunk_finder_spec.rb +73 -0
- data/spec/unit/chunk_insert_spec.rb +67 -0
- data/spec/unit/chunker_spec.rb +176 -0
- data/spec/unit/connection_spec.rb +111 -0
- data/spec/unit/entangler_spec.rb +187 -0
- data/spec/unit/intersection_spec.rb +51 -0
- data/spec/unit/lhm_spec.rb +46 -0
- data/spec/unit/locked_switcher_spec.rb +46 -0
- data/spec/unit/migrator_spec.rb +144 -0
- data/spec/unit/printer_spec.rb +85 -0
- data/spec/unit/sql_helper_spec.rb +28 -0
- data/spec/unit/table_name_spec.rb +39 -0
- data/spec/unit/table_spec.rb +47 -0
- data/spec/unit/throttler/slave_lag_spec.rb +322 -0
- data/spec/unit/throttler/threads_running_spec.rb +64 -0
- data/spec/unit/throttler_spec.rb +124 -0
- data/spec/unit/unit_helper.rb +26 -0
- metadata +366 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
|
|
2
|
+
require 'lhm/migration'
|
|
3
|
+
|
|
4
|
+
describe Lhm::ChunkInsert do
|
|
5
|
+
include IntegrationHelper
|
|
6
|
+
|
|
7
|
+
describe 'insert_and_return_count_of_rows_created' do
|
|
8
|
+
before(:each) do
|
|
9
|
+
connect_master!
|
|
10
|
+
@origin = table_create(:origin)
|
|
11
|
+
@destination = table_create(:destination)
|
|
12
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
|
13
|
+
execute("insert into origin set id = 1001")
|
|
14
|
+
@connection = Lhm::Connection.new(connection: connection)
|
|
15
|
+
@instance = Lhm::ChunkInsert.new(@migration, @connection, 1001, 1001)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "returns the count" do
|
|
19
|
+
assert_equal 1, @instance.insert_and_return_count_of_rows_created
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "inserts the record into the slave" do
|
|
23
|
+
@instance.insert_and_return_count_of_rows_created
|
|
24
|
+
|
|
25
|
+
slave do
|
|
26
|
+
value(count_all(@destination.name)).must_equal(1)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
|
2
|
+
# Schmidt
|
|
3
|
+
|
|
4
|
+
require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
|
|
5
|
+
require 'lhm/table'
|
|
6
|
+
require 'lhm/migration'
|
|
7
|
+
|
|
8
|
+
describe Lhm::Chunker do
|
|
9
|
+
include IntegrationHelper
|
|
10
|
+
|
|
11
|
+
before(:each) { connect_master! }
|
|
12
|
+
|
|
13
|
+
describe 'copying' do
|
|
14
|
+
before(:each) do
|
|
15
|
+
@origin = table_create(:origin)
|
|
16
|
+
@destination = table_create(:destination)
|
|
17
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
|
18
|
+
@logs = StringIO.new
|
|
19
|
+
Lhm.logger = Logger.new(@logs)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def log_messages
|
|
23
|
+
@logs.string.split("\n")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'should copy 1 row from origin to destination even if the id of the single row does not start at 1' do
|
|
27
|
+
execute("insert into origin set id = 1001 ")
|
|
28
|
+
|
|
29
|
+
Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
|
|
30
|
+
|
|
31
|
+
slave do
|
|
32
|
+
value(count_all(@destination.name)).must_equal(1)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'should copy and ignore duplicate primary key' do
|
|
38
|
+
execute("insert into origin set id = 1001 ")
|
|
39
|
+
execute("insert into origin set id = 1002 ")
|
|
40
|
+
execute("insert into destination set id = 1002 ")
|
|
41
|
+
|
|
42
|
+
Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
|
|
43
|
+
|
|
44
|
+
slave do
|
|
45
|
+
value(count_all(@destination.name)).must_equal(2)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'should copy and ignore duplicate composite primary key' do
|
|
50
|
+
origin = table_create(:composite_primary_key)
|
|
51
|
+
destination = table_create(:composite_primary_key_dest)
|
|
52
|
+
migration = Lhm::Migration.new(origin, destination)
|
|
53
|
+
|
|
54
|
+
execute("insert into composite_primary_key set id = 1001, shop_id = 1")
|
|
55
|
+
execute("insert into composite_primary_key set id = 1002, shop_id = 1")
|
|
56
|
+
execute("insert into composite_primary_key_dest set id = 1002, shop_id = 1")
|
|
57
|
+
|
|
58
|
+
Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
|
|
59
|
+
|
|
60
|
+
slave do
|
|
61
|
+
value(count_all(destination.name)).must_equal(2)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'should copy and raise on unexpected warnings' do
|
|
66
|
+
origin = table_create(:custom_primary_key)
|
|
67
|
+
destination = table_create(:custom_primary_key_dest)
|
|
68
|
+
migration = Lhm::Migration.new(origin, destination)
|
|
69
|
+
|
|
70
|
+
execute("insert into custom_primary_key set id = 1001, pk = 1")
|
|
71
|
+
execute("insert into custom_primary_key_dest set id = 1001, pk = 2")
|
|
72
|
+
|
|
73
|
+
exception = assert_raises(Lhm::Error) do
|
|
74
|
+
Lhm::Chunker.new(migration, connection, {raise_on_warnings: true, throttler: throttler, printer: printer} ).run
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
assert_match "Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'", exception.message
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'should copy and warn on unexpected warnings by default' do
|
|
81
|
+
origin = table_create(:custom_primary_key)
|
|
82
|
+
destination = table_create(:custom_primary_key_dest)
|
|
83
|
+
migration = Lhm::Migration.new(origin, destination)
|
|
84
|
+
|
|
85
|
+
execute("insert into custom_primary_key set id = 1001, pk = 1")
|
|
86
|
+
execute("insert into custom_primary_key_dest set id = 1001, pk = 2")
|
|
87
|
+
|
|
88
|
+
Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
|
|
89
|
+
|
|
90
|
+
assert_equal 2, log_messages.length
|
|
91
|
+
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'should log two times for two unexpected warnings' do
|
|
95
|
+
origin = table_create(:custom_primary_key)
|
|
96
|
+
destination = table_create(:custom_primary_key_dest)
|
|
97
|
+
migration = Lhm::Migration.new(origin, destination)
|
|
98
|
+
|
|
99
|
+
execute("insert into custom_primary_key set id = 1001, pk = 1")
|
|
100
|
+
execute("insert into custom_primary_key set id = 1002, pk = 2")
|
|
101
|
+
execute("insert into custom_primary_key_dest set id = 1001, pk = 3")
|
|
102
|
+
execute("insert into custom_primary_key_dest set id = 1002, pk = 4")
|
|
103
|
+
|
|
104
|
+
Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
|
|
105
|
+
|
|
106
|
+
assert_equal 3, log_messages.length
|
|
107
|
+
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
|
|
108
|
+
assert log_messages[2].include?("Unexpected warning found for inserted row: Duplicate entry '1002' for key 'index_custom_primary_key_on_id'"), log_messages
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'should copy and warn on unexpected warnings' do
|
|
112
|
+
origin = table_create(:custom_primary_key)
|
|
113
|
+
destination = table_create(:custom_primary_key_dest)
|
|
114
|
+
migration = Lhm::Migration.new(origin, destination)
|
|
115
|
+
|
|
116
|
+
execute("insert into custom_primary_key set id = 1001, pk = 1")
|
|
117
|
+
execute("insert into custom_primary_key_dest set id = 1001, pk = 2")
|
|
118
|
+
|
|
119
|
+
Lhm::Chunker.new(migration, connection, {raise_on_warnings: false, throttler: throttler, printer: printer} ).run
|
|
120
|
+
|
|
121
|
+
assert_equal 2, log_messages.length
|
|
122
|
+
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'should create the modified destination, even if the source is empty' do
|
|
126
|
+
execute("truncate origin ")
|
|
127
|
+
|
|
128
|
+
Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
|
|
129
|
+
|
|
130
|
+
slave do
|
|
131
|
+
value(count_all(@destination.name)).must_equal(0)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'should copy 23 rows from origin to destination in one shot, regardless of the value of the id' do
|
|
137
|
+
23.times { |n| execute("insert into origin set id = '#{ n * n + 23 }'") }
|
|
138
|
+
|
|
139
|
+
printer = MiniTest::Mock.new
|
|
140
|
+
printer.expect(:notify, :return_value, [Integer, Integer])
|
|
141
|
+
printer.expect(:end, :return_value, [])
|
|
142
|
+
|
|
143
|
+
Lhm::Chunker.new(
|
|
144
|
+
@migration, connection, { throttler: throttler, printer: printer }
|
|
145
|
+
).run
|
|
146
|
+
|
|
147
|
+
slave do
|
|
148
|
+
value(count_all(@destination.name)).must_equal(23)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
printer.verify
|
|
152
|
+
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'should copy all the records of a table, even if the last chunk starts with the last record of it.' do
|
|
156
|
+
11.times { |n| execute("insert into origin set id = '#{ n + 1 }'") }
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
Lhm::Chunker.new(
|
|
160
|
+
@migration, connection, { throttler: Lhm::Throttler::Time.new(stride: 10), printer: printer }
|
|
161
|
+
).run
|
|
162
|
+
|
|
163
|
+
slave do
|
|
164
|
+
value(count_all(@destination.name)).must_equal(11)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it 'should copy 23 rows from origin to destination in one shot with slave lag based throttler, regardless of the value of the id' do
|
|
170
|
+
23.times { |n| execute("insert into origin set id = '#{ 100000 + n * n + 23 }'") }
|
|
171
|
+
|
|
172
|
+
printer = MiniTest::Mock.new
|
|
173
|
+
printer.expect(:notify, :return_value, [Integer, Integer])
|
|
174
|
+
printer.expect(:end, :return_value, [])
|
|
175
|
+
|
|
176
|
+
Lhm::Throttler::Slave.any_instance.stubs(:slave_hosts).returns(['127.0.0.1'])
|
|
177
|
+
Lhm::Throttler::SlaveLag.any_instance.stubs(:master_slave_hosts).returns(['127.0.0.1'])
|
|
178
|
+
|
|
179
|
+
Lhm::Chunker.new(
|
|
180
|
+
@migration, connection, { throttler: Lhm::Throttler::SlaveLag.new(stride: 100), printer: printer }
|
|
181
|
+
).run
|
|
182
|
+
|
|
183
|
+
slave do
|
|
184
|
+
value(count_all(@destination.name)).must_equal(23)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
printer.verify
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'should throttle work stride based on slave lag' do
|
|
191
|
+
15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
|
|
192
|
+
|
|
193
|
+
printer = mock()
|
|
194
|
+
printer.expects(:notify).with(instance_of(Integer), instance_of(Integer)).twice
|
|
195
|
+
printer.expects(:end)
|
|
196
|
+
|
|
197
|
+
throttler = Lhm::Throttler::SlaveLag.new(stride: 10, allowed_lag: 0)
|
|
198
|
+
def throttler.max_current_slave_lag
|
|
199
|
+
1
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
Lhm::Chunker.new(
|
|
203
|
+
@migration, connection, { throttler: throttler, printer: printer }
|
|
204
|
+
).run
|
|
205
|
+
|
|
206
|
+
assert_equal(Lhm::Throttler::SlaveLag::INITIAL_TIMEOUT * 2 * 2, throttler.timeout_seconds)
|
|
207
|
+
|
|
208
|
+
slave do
|
|
209
|
+
value(count_all(@destination.name)).must_equal(15)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'should detect a single slave with no lag in the default configuration' do
|
|
214
|
+
15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
|
|
215
|
+
|
|
216
|
+
printer = mock()
|
|
217
|
+
printer.expects(:notify).with(instance_of(Integer), instance_of(Integer)).twice
|
|
218
|
+
printer.expects(:verify)
|
|
219
|
+
printer.expects(:end)
|
|
220
|
+
|
|
221
|
+
Lhm::Throttler::Slave.any_instance.stubs(:slave_hosts).returns(['127.0.0.1'])
|
|
222
|
+
Lhm::Throttler::SlaveLag.any_instance.stubs(:master_slave_hosts).returns(['127.0.0.1'])
|
|
223
|
+
|
|
224
|
+
throttler = Lhm::Throttler::SlaveLag.new(stride: 10, allowed_lag: 0)
|
|
225
|
+
|
|
226
|
+
if master_slave_mode?
|
|
227
|
+
def throttler.slave_connection(slave)
|
|
228
|
+
config = ActiveRecord::Base.connection_pool.db_config.configuration_hash.dup
|
|
229
|
+
config[:host] = slave
|
|
230
|
+
config[:port] = 33007
|
|
231
|
+
ActiveRecord::Base.send('mysql2_connection', config)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
Lhm::Chunker.new(
|
|
236
|
+
@migration, connection, { throttler: throttler, printer: printer }
|
|
237
|
+
).run
|
|
238
|
+
|
|
239
|
+
assert_equal(Lhm::Throttler::SlaveLag::INITIAL_TIMEOUT, throttler.timeout_seconds)
|
|
240
|
+
assert_equal(0, throttler.send(:max_current_slave_lag))
|
|
241
|
+
|
|
242
|
+
slave do
|
|
243
|
+
value(count_all(@destination.name)).must_equal(15)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
printer.verify
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it 'should abort early if the triggers are removed' do
|
|
250
|
+
15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
|
|
251
|
+
|
|
252
|
+
printer = mock()
|
|
253
|
+
|
|
254
|
+
failer = Proc.new { false }
|
|
255
|
+
|
|
256
|
+
exception = assert_raises do
|
|
257
|
+
Lhm::Chunker.new(
|
|
258
|
+
@migration, connection, { verifier: failer, printer: printer, throttler: throttler }
|
|
259
|
+
).run
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
assert_match "Verification failed, aborting early", exception.message
|
|
263
|
+
|
|
264
|
+
slave do
|
|
265
|
+
value(count_all(@destination.name)).must_equal(0)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
|
2
|
+
# Schmidt
|
|
3
|
+
|
|
4
|
+
require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
|
|
5
|
+
|
|
6
|
+
describe Lhm, 'cleanup' do
|
|
7
|
+
include IntegrationHelper
|
|
8
|
+
before(:each) { connect_master! }
|
|
9
|
+
|
|
10
|
+
describe 'changes' do
|
|
11
|
+
before(:each) do
|
|
12
|
+
table_create(:users)
|
|
13
|
+
table_create(:permissions)
|
|
14
|
+
simulate_failed_migration do
|
|
15
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
16
|
+
t.add_column(:logins, "INT(12) DEFAULT '0'")
|
|
17
|
+
t.add_index(:logins)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
simulate_failed_migration do
|
|
21
|
+
Lhm.change_table(:permissions, :atomic_switch => false) do |t|
|
|
22
|
+
t.add_column(:user_id, "INT(12) DEFAULT '0'")
|
|
23
|
+
t.add_index(:user_id)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
after(:each) do
|
|
29
|
+
Lhm.cleanup(true)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe 'cleanup' do
|
|
33
|
+
it 'should show temporary tables' do
|
|
34
|
+
output = capture_stdout do |logger|
|
|
35
|
+
Lhm.logger = logger
|
|
36
|
+
Lhm.cleanup
|
|
37
|
+
end
|
|
38
|
+
value(output).must_include('Would drop LHM backup tables')
|
|
39
|
+
value(output).must_match(/lhma_[0-9_]*_users/)
|
|
40
|
+
value(output).must_match(/lhma_[0-9_]*_permissions/)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'should show temporary tables within range' do
|
|
44
|
+
table = OpenStruct.new(:name => 'users')
|
|
45
|
+
table_name = Lhm::Migration.new(table, nil, nil, {}, Time.now - 172800).archive_name
|
|
46
|
+
table_rename(:users, table_name)
|
|
47
|
+
|
|
48
|
+
table2 = OpenStruct.new(:name => 'permissions')
|
|
49
|
+
table_name2 = Lhm::Migration.new(table2, nil, nil, {}, Time.now - 172800).archive_name
|
|
50
|
+
table_rename(:permissions, table_name2)
|
|
51
|
+
|
|
52
|
+
output = capture_stdout do |logger|
|
|
53
|
+
Lhm.logger = logger
|
|
54
|
+
Lhm.cleanup false, { :until => Time.now - 86400 }
|
|
55
|
+
end
|
|
56
|
+
value(output).must_include('Would drop LHM backup tables')
|
|
57
|
+
value(output).must_match(/lhma_[0-9_]*_users/)
|
|
58
|
+
value(output).must_match(/lhma_[0-9_]*_permissions/)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'should exclude temporary tables outside range' do
|
|
62
|
+
table = OpenStruct.new(:name => 'users')
|
|
63
|
+
table_name = Lhm::Migration.new(table, nil, nil, {}, Time.now).archive_name
|
|
64
|
+
table_rename(:users, table_name)
|
|
65
|
+
|
|
66
|
+
table2 = OpenStruct.new(:name => 'permissions')
|
|
67
|
+
table_name2 = Lhm::Migration.new(table2, nil, nil, {}, Time.now).archive_name
|
|
68
|
+
table_rename(:permissions, table_name2)
|
|
69
|
+
|
|
70
|
+
output = capture_stdout do |logger|
|
|
71
|
+
Lhm.logger = logger
|
|
72
|
+
Lhm.cleanup false, { :until => Time.now - 172800 }
|
|
73
|
+
end
|
|
74
|
+
value(output).must_include('Would drop LHM backup tables')
|
|
75
|
+
value(output).wont_match(/lhma_[0-9_]*_users/)
|
|
76
|
+
value(output).wont_match(/lhma_[0-9_]*_permissions/)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'should show temporary triggers' do
|
|
80
|
+
output = capture_stdout do |logger|
|
|
81
|
+
Lhm.logger = logger
|
|
82
|
+
Lhm.cleanup
|
|
83
|
+
end
|
|
84
|
+
value(output).must_include('Would drop LHM triggers')
|
|
85
|
+
value(output).must_include('lhmt_ins_users')
|
|
86
|
+
value(output).must_include('lhmt_del_users')
|
|
87
|
+
value(output).must_include('lhmt_upd_users')
|
|
88
|
+
value(output).must_include('lhmt_ins_permissions')
|
|
89
|
+
value(output).must_include('lhmt_del_permissions')
|
|
90
|
+
value(output).must_include('lhmt_upd_permissions')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'should delete temporary tables' do
|
|
94
|
+
value(Lhm.cleanup(true)).must_equal(true)
|
|
95
|
+
value(Lhm.cleanup).must_equal(true)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'outputs deleted tables and triggers' do
|
|
99
|
+
output = capture_stdout do |logger|
|
|
100
|
+
Lhm.logger = logger
|
|
101
|
+
Lhm.cleanup(true)
|
|
102
|
+
end
|
|
103
|
+
value(output).must_include('Dropped triggers lhmt_ins_users, lhmt_upd_users, lhmt_del_users, lhmt_ins_permissions, lhmt_upd_permissions, lhmt_del_permissions')
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe 'cleanup_current_run' do
|
|
108
|
+
it 'should show lhmn table for the specified table only' do
|
|
109
|
+
table_create(:permissions)
|
|
110
|
+
table_rename(:permissions, 'lhmn_permissions')
|
|
111
|
+
output = capture_stdout do |logger|
|
|
112
|
+
Lhm.logger = logger
|
|
113
|
+
Lhm.cleanup_current_run(false, 'permissions')
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
value(output).must_include("The following DDLs would be executed:")
|
|
117
|
+
value(output).must_include("drop trigger if exists lhmt_ins_permissions")
|
|
118
|
+
value(output).must_include("drop trigger if exists lhmt_upd_permissions")
|
|
119
|
+
value(output).must_include("drop trigger if exists lhmt_del_permissions")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'should show temporary triggers for the specified table only' do
|
|
123
|
+
output = capture_stdout do |logger|
|
|
124
|
+
Lhm.logger = logger
|
|
125
|
+
Lhm.cleanup_current_run(false, 'permissions')
|
|
126
|
+
end
|
|
127
|
+
value(output).must_include("The following DDLs would be executed:")
|
|
128
|
+
value(output).must_include("drop trigger if exists lhmt_ins_permissions")
|
|
129
|
+
value(output).must_include("drop trigger if exists lhmt_upd_permissions")
|
|
130
|
+
value(output).must_include("drop trigger if exists lhmt_del_permissions")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'should delete temporary tables and triggers for the specified table only' do
|
|
134
|
+
assert Lhm.cleanup_current_run(true, 'permissions')
|
|
135
|
+
|
|
136
|
+
all_tables = Lhm.connection.select_values('show tables')
|
|
137
|
+
all_triggers = Lhm.connection.select_values('show triggers')
|
|
138
|
+
|
|
139
|
+
refute all_tables.include?('lhmn_permissions')
|
|
140
|
+
assert all_tables.find { |t| t =~ /lhma_(.*)_users/}
|
|
141
|
+
|
|
142
|
+
refute all_triggers.find { |t| t =~ /lhmt_(.*)_permissions/}
|
|
143
|
+
assert all_triggers.find { |t| t =~ /lhmt_(.*)_users/}
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
master:
|
|
2
|
+
host: mysql-1
|
|
3
|
+
user: root
|
|
4
|
+
password: password
|
|
5
|
+
port: 33006
|
|
6
|
+
slave:
|
|
7
|
+
host: mysql-2
|
|
8
|
+
user: root
|
|
9
|
+
password: password
|
|
10
|
+
port: 33007
|
|
11
|
+
proxysql:
|
|
12
|
+
host: proxysql
|
|
13
|
+
user: root
|
|
14
|
+
password: password
|
|
15
|
+
port: 33005
|
|
16
|
+
master_toxic:
|
|
17
|
+
host: toxiproxy
|
|
18
|
+
user: root
|
|
19
|
+
password: password
|
|
20
|
+
port: 22220
|
|
21
|
+
proxysql_toxic:
|
|
22
|
+
host: toxiproxy
|
|
23
|
+
user: root
|
|
24
|
+
password: password
|
|
25
|
+
port: 22222
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
|
2
|
+
# Schmidt
|
|
3
|
+
|
|
4
|
+
require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
|
|
5
|
+
|
|
6
|
+
require 'lhm/table'
|
|
7
|
+
require 'lhm/migration'
|
|
8
|
+
require 'lhm/entangler'
|
|
9
|
+
require 'lhm/connection'
|
|
10
|
+
|
|
11
|
+
describe Lhm::Entangler do
|
|
12
|
+
include IntegrationHelper
|
|
13
|
+
|
|
14
|
+
before(:each) { connect_master! }
|
|
15
|
+
|
|
16
|
+
describe 'entanglement' do
|
|
17
|
+
before(:each) do
|
|
18
|
+
@origin = table_create('origin')
|
|
19
|
+
@destination = table_create('destination')
|
|
20
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
|
21
|
+
@connection = Lhm::Connection.new(connection: connection)
|
|
22
|
+
@entangler = Lhm::Entangler.new(@migration, @connection)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'should replay inserts from origin into destination' do
|
|
26
|
+
@entangler.run do
|
|
27
|
+
execute("insert into origin (common) values ('inserted')")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
slave do
|
|
31
|
+
value(count(:destination, 'common', 'inserted')).must_equal(1)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'should replay deletes from origin into destination' do
|
|
36
|
+
execute("insert into origin (common) values ('inserted')")
|
|
37
|
+
|
|
38
|
+
@entangler.run do
|
|
39
|
+
execute("delete from origin where common = 'inserted'")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
slave do
|
|
43
|
+
value(count(:destination, 'common', 'inserted')).must_equal(0)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'should replay updates from origin into destination' do
|
|
48
|
+
@entangler.run do
|
|
49
|
+
execute("insert into origin (common) values ('inserted')")
|
|
50
|
+
execute("update origin set common = 'updated'")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
slave do
|
|
54
|
+
value(count(:destination, 'common', 'updated')).must_equal(1)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'should remove entanglement' do
|
|
59
|
+
@entangler.run {}
|
|
60
|
+
|
|
61
|
+
execute("insert into origin (common) values ('inserted')")
|
|
62
|
+
|
|
63
|
+
slave do
|
|
64
|
+
value(count(:destination, 'common', 'inserted')).must_equal(0)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|