lhm-shopify 3.4.0 → 3.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +24 -15
  3. data/.gitignore +1 -6
  4. data/Appraisals +24 -0
  5. data/CHANGELOG.md +30 -0
  6. data/Gemfile.lock +66 -0
  7. data/README.md +55 -4
  8. data/Rakefile +11 -0
  9. data/dev.yml +31 -6
  10. data/docker-compose.yml +58 -0
  11. data/gemfiles/activerecord_5.2.gemfile +9 -0
  12. data/gemfiles/activerecord_5.2.gemfile.lock +65 -0
  13. data/gemfiles/activerecord_6.0.gemfile +7 -0
  14. data/gemfiles/activerecord_6.0.gemfile.lock +67 -0
  15. data/gemfiles/activerecord_6.1.gemfile +7 -0
  16. data/gemfiles/activerecord_6.1.gemfile.lock +66 -0
  17. data/gemfiles/activerecord_7.0.0.alpha2.gemfile +7 -0
  18. data/gemfiles/activerecord_7.0.0.alpha2.gemfile.lock +64 -0
  19. data/lhm.gemspec +7 -3
  20. data/lib/lhm/atomic_switcher.rb +5 -11
  21. data/lib/lhm/chunk_insert.rb +7 -10
  22. data/lib/lhm/chunker.rb +21 -10
  23. data/lib/lhm/cleanup/current.rb +9 -12
  24. data/lib/lhm/connection.rb +108 -0
  25. data/lib/lhm/entangler.rb +8 -13
  26. data/lib/lhm/invoker.rb +6 -4
  27. data/lib/lhm/locked_switcher.rb +2 -0
  28. data/lib/lhm/migrator.rb +2 -0
  29. data/lib/lhm/printer.rb +10 -6
  30. data/lib/lhm/proxysql_helper.rb +10 -0
  31. data/lib/lhm/sql_retry.rb +129 -10
  32. data/lib/lhm/throttler/slave_lag.rb +19 -2
  33. data/lib/lhm/version.rb +1 -1
  34. data/lib/lhm.rb +41 -16
  35. data/scripts/helpers/wait-for-dbs.sh +21 -0
  36. data/scripts/mysql/reader/create_replication.sql +10 -0
  37. data/scripts/mysql/writer/create_test_db.sql +1 -0
  38. data/scripts/mysql/writer/create_users.sql +6 -0
  39. data/scripts/proxysql/proxysql.cnf +117 -0
  40. data/spec/integration/atomic_switcher_spec.rb +53 -17
  41. data/spec/integration/chunk_insert_spec.rb +3 -2
  42. data/spec/integration/chunker_spec.rb +18 -16
  43. data/spec/integration/cleanup_spec.rb +49 -38
  44. data/spec/integration/database.yml +25 -0
  45. data/spec/integration/entangler_spec.rb +7 -5
  46. data/spec/integration/integration_helper.rb +25 -10
  47. data/spec/integration/lhm_spec.rb +114 -40
  48. data/spec/integration/lock_wait_timeout_spec.rb +2 -2
  49. data/spec/integration/locked_switcher_spec.rb +4 -4
  50. data/spec/integration/proxysql_spec.rb +34 -0
  51. data/spec/integration/sql_retry/db_connection_helper.rb +52 -0
  52. data/spec/integration/sql_retry/lock_wait_spec.rb +8 -6
  53. data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +17 -4
  54. data/spec/integration/sql_retry/proxysql_helper.rb +22 -0
  55. data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +109 -0
  56. data/spec/integration/table_spec.rb +11 -19
  57. data/spec/integration/toxiproxy_helper.rb +40 -0
  58. data/spec/test_helper.rb +24 -0
  59. data/spec/unit/atomic_switcher_spec.rb +4 -6
  60. data/spec/unit/chunk_insert_spec.rb +7 -2
  61. data/spec/unit/chunker_spec.rb +47 -42
  62. data/spec/unit/connection_spec.rb +111 -0
  63. data/spec/unit/entangler_spec.rb +85 -22
  64. data/spec/unit/intersection_spec.rb +4 -4
  65. data/spec/unit/lhm_spec.rb +23 -6
  66. data/spec/unit/locked_switcher_spec.rb +13 -18
  67. data/spec/unit/migrator_spec.rb +17 -19
  68. data/spec/unit/printer_spec.rb +14 -26
  69. data/spec/unit/sql_helper_spec.rb +8 -12
  70. data/spec/unit/table_spec.rb +5 -5
  71. data/spec/unit/throttler/slave_lag_spec.rb +14 -9
  72. data/spec/unit/throttler_spec.rb +12 -12
  73. data/spec/unit/unit_helper.rb +13 -0
  74. metadata +85 -14
  75. data/bin/.gitkeep +0 -0
  76. data/dbdeployer/config.json +0 -32
  77. data/dbdeployer/install.sh +0 -64
  78. data/gemfiles/ar-2.3_mysql.gemfile +0 -6
  79. data/gemfiles/ar-3.2_mysql.gemfile +0 -5
  80. data/gemfiles/ar-3.2_mysql2.gemfile +0 -5
  81. data/gemfiles/ar-4.0_mysql2.gemfile +0 -5
  82. data/gemfiles/ar-4.1_mysql2.gemfile +0 -5
  83. data/gemfiles/ar-4.2_mysql2.gemfile +0 -5
  84. data/gemfiles/ar-5.0_mysql2.gemfile +0 -5
@@ -2,6 +2,7 @@
2
2
  # Schmidt
3
3
 
4
4
  require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
+ require 'integration/toxiproxy_helper'
5
6
 
6
7
  describe Lhm do
7
8
  include IntegrationHelper
@@ -17,7 +18,7 @@ describe Lhm do
17
18
  end
18
19
 
19
20
  slave do
20
- table_read(:users).columns['logins'].must_equal({
21
+ value(table_read(:users).columns['logins']).must_equal({
21
22
  :type => 'int(12)',
22
23
  :is_nullable => 'YES',
23
24
  :column_default => '0',
@@ -35,7 +36,7 @@ describe Lhm do
35
36
  end
36
37
 
37
38
  slave do
38
- table_read(:custom_primary_key).columns['logins'].must_equal({
39
+ value(table_read(:custom_primary_key).columns['logins']).must_equal({
39
40
  :type => 'int(12)',
40
41
  :is_nullable => 'YES',
41
42
  :column_default => '0',
@@ -53,7 +54,7 @@ describe Lhm do
53
54
  end
54
55
 
55
56
  slave do
56
- table_read(:composite_primary_key).columns['logins'].must_equal({
57
+ value(table_read(:composite_primary_key).columns['logins']).must_equal({
57
58
  :type => 'int(12)',
58
59
  :is_nullable => 'YES',
59
60
  :column_default => '0',
@@ -82,7 +83,7 @@ describe Lhm do
82
83
  end
83
84
 
84
85
  slave do
85
- connection.primary_key('users').must_equal(['username', 'id'])
86
+ value(connection.primary_key('users')).must_equal(['username', 'id'])
86
87
  end
87
88
  end
88
89
 
@@ -104,7 +105,7 @@ describe Lhm do
104
105
 
105
106
  it 'migrates the existing data' do
106
107
  slave do
107
- count_all(:permissions).must_equal(11)
108
+ value(count_all(:permissions)).must_equal(11)
108
109
  end
109
110
  end
110
111
  end
@@ -120,7 +121,7 @@ describe Lhm do
120
121
 
121
122
  it 'migrates all data' do
122
123
  slave do
123
- count_all(:permissions).must_equal(13)
124
+ value(count_all(:permissions)).must_equal(13)
124
125
  end
125
126
  end
126
127
  end
@@ -132,7 +133,7 @@ describe Lhm do
132
133
  end
133
134
 
134
135
  slave do
135
- table_read(:users).columns['logins'].must_equal({
136
+ value(table_read(:users).columns['logins']).must_equal({
136
137
  :type => 'int(12)',
137
138
  :is_nullable => 'YES',
138
139
  :column_default => '0',
@@ -150,7 +151,7 @@ describe Lhm do
150
151
  end
151
152
 
152
153
  slave do
153
- count_all(:users).must_equal(23)
154
+ value(count_all(:users)).must_equal(23)
154
155
  end
155
156
  end
156
157
 
@@ -170,7 +171,7 @@ describe Lhm do
170
171
  end
171
172
 
172
173
  slave do
173
- index_on_columns?(:users, [:comment, :created_at]).must_equal(true)
174
+ value(index_on_columns?(:users, [:comment, :created_at])).must_equal(true)
174
175
  end
175
176
  end
176
177
 
@@ -180,7 +181,7 @@ describe Lhm do
180
181
  end
181
182
 
182
183
  slave do
183
- index?(:users, :my_index_name).must_equal(true)
184
+ value(index?(:users, :my_index_name)).must_equal(true)
184
185
  end
185
186
  end
186
187
 
@@ -190,7 +191,7 @@ describe Lhm do
190
191
  end
191
192
 
192
193
  slave do
193
- index_on_columns?(:users, :group).must_equal(true)
194
+ value(index_on_columns?(:users, :group)).must_equal(true)
194
195
  end
195
196
  end
196
197
 
@@ -200,7 +201,7 @@ describe Lhm do
200
201
  end
201
202
 
202
203
  slave do
203
- index_on_columns?(:users, :comment, :unique).must_equal(true)
204
+ value(index_on_columns?(:users, :comment, :unique)).must_equal(true)
204
205
  end
205
206
  end
206
207
 
@@ -210,7 +211,7 @@ describe Lhm do
210
211
  end
211
212
 
212
213
  slave do
213
- index_on_columns?(:users, [:username, :created_at]).must_equal(false)
214
+ value(index_on_columns?(:users, [:username, :created_at])).must_equal(false)
214
215
  end
215
216
  end
216
217
 
@@ -220,7 +221,7 @@ describe Lhm do
220
221
  end
221
222
 
222
223
  slave do
223
- index?(:users, :index_with_a_custom_name).must_equal(false)
224
+ value(index?(:users, :index_with_a_custom_name)).must_equal(false)
224
225
  end
225
226
  end
226
227
 
@@ -230,7 +231,7 @@ describe Lhm do
230
231
  end
231
232
 
232
233
  slave do
233
- index?(:users, :index_with_a_custom_name).must_equal(false)
234
+ value(index?(:users, :index_with_a_custom_name)).must_equal(false)
234
235
  end
235
236
  end
236
237
 
@@ -240,7 +241,7 @@ describe Lhm do
240
241
  end
241
242
 
242
243
  slave do
243
- table_read(:users).columns['flag'].must_equal({
244
+ value(table_read(:users).columns['flag']).must_equal({
244
245
  :type => 'tinyint(1)',
245
246
  :is_nullable => 'YES',
246
247
  :column_default => nil,
@@ -256,7 +257,7 @@ describe Lhm do
256
257
  end
257
258
 
258
259
  slave do
259
- table_read(:users).columns['comment'].must_equal({
260
+ value(table_read(:users).columns['comment']).must_equal({
260
261
  :type => 'varchar(20)',
261
262
  :is_nullable => 'NO',
262
263
  :column_default => 'none',
@@ -274,7 +275,7 @@ describe Lhm do
274
275
  end
275
276
 
276
277
  slave do
277
- table_read(:small_table).columns['id'].must_equal({
278
+ value(table_read(:small_table).columns['id']).must_equal({
278
279
  :type => 'int(5)',
279
280
  :is_nullable => 'NO',
280
281
  :column_default => nil,
@@ -295,7 +296,7 @@ describe Lhm do
295
296
  slave do
296
297
  table_data = table_read(:users)
297
298
  assert_nil table_data.columns['username']
298
- table_read(:users).columns['login'].must_equal({
299
+ value(table_read(:users).columns['login']).must_equal({
299
300
  :type => 'varchar(255)',
300
301
  :is_nullable => 'YES',
301
302
  :column_default => nil,
@@ -305,7 +306,7 @@ describe Lhm do
305
306
 
306
307
  result = select_one('SELECT login from users')
307
308
  result = result['login'] if result.respond_to?(:has_key?)
308
- result.must_equal('a user')
309
+ value(result).must_equal('a user')
309
310
  end
310
311
  end
311
312
 
@@ -320,7 +321,7 @@ describe Lhm do
320
321
  slave do
321
322
  table_data = table_read(:users)
322
323
  assert_nil table_data.columns['group']
323
- table_read(:users).columns['fnord'].must_equal({
324
+ value(table_read(:users).columns['fnord']).must_equal({
324
325
  :type => 'varchar(255)',
325
326
  :is_nullable => 'YES',
326
327
  :column_default => 'Superfriends',
@@ -330,7 +331,7 @@ describe Lhm do
330
331
 
331
332
  result = select_one('SELECT `fnord` from users')
332
333
  result = result['fnord'] if result.respond_to?(:has_key?)
333
- result.must_equal('Superfriends')
334
+ value(result).must_equal('Superfriends')
334
335
  end
335
336
  end
336
337
 
@@ -347,7 +348,7 @@ describe Lhm do
347
348
  slave do
348
349
  table_data = table_read(:users)
349
350
  assert_nil table_data.columns['username']
350
- table_read(:users).columns['user_name'].must_equal({
351
+ value(table_read(:users).columns['user_name']).must_equal({
351
352
  :type => 'varchar(255)',
352
353
  :is_nullable => 'YES',
353
354
  :column_default => nil,
@@ -357,7 +358,7 @@ describe Lhm do
357
358
 
358
359
  result = select_one('SELECT `user_name` from users')
359
360
  result = result['user_name'] if result.respond_to?(:has_key?)
360
- result.must_equal('a user')
361
+ value(result).must_equal('a user')
361
362
  end
362
363
  end
363
364
 
@@ -375,7 +376,7 @@ describe Lhm do
375
376
  slave do
376
377
  table_data = table_read(:users)
377
378
  assert_nil table_data.columns['reference']
378
- table_read(:users).columns['ref'].must_equal({
379
+ value(table_read(:users).columns['ref']).must_equal({
379
380
  :type => 'int(11)',
380
381
  :is_nullable => 'YES',
381
382
  :column_default => nil,
@@ -385,7 +386,7 @@ describe Lhm do
385
386
 
386
387
  result = select_one('SELECT `ref` from users')
387
388
  result = result['ref'] if result.respond_to?(:has_key?)
388
- result.must_equal(10)
389
+ value(result).must_equal(10)
389
390
  end
390
391
  end
391
392
 
@@ -402,7 +403,7 @@ describe Lhm do
402
403
  slave do
403
404
  table_data = table_read(:users)
404
405
  assert_nil table_data.columns['group']
405
- table_read(:users).columns['fnord'].must_equal({
406
+ value(table_read(:users).columns['fnord']).must_equal({
406
407
  :type => 'varchar(255)',
407
408
  :is_nullable => 'YES',
408
409
  :column_default => nil,
@@ -412,7 +413,7 @@ describe Lhm do
412
413
 
413
414
  result = select_one('SELECT `fnord` from users')
414
415
  result = result['fnord'] if result.respond_to?(:has_key?)
415
- result.must_equal(nil)
416
+ assert_nil(result)
416
417
  end
417
418
  end
418
419
 
@@ -427,7 +428,7 @@ describe Lhm do
427
428
  slave do
428
429
  table_data = table_read(:users)
429
430
  assert_nil table_data.columns['username']
430
- table_read(:users).columns['user_name'].must_equal({
431
+ value(table_read(:users).columns['user_name']).must_equal({
431
432
  :type => 'varchar(255)',
432
433
  :is_nullable => 'YES',
433
434
  :column_default => nil,
@@ -437,7 +438,7 @@ describe Lhm do
437
438
 
438
439
  result = select_one('SELECT `user_name` from users')
439
440
  result = result['user_name'] if result.respond_to?(:has_key?)
440
- result.must_equal('a user')
441
+ value(result).must_equal('a user')
441
442
  end
442
443
  end
443
444
 
@@ -454,7 +455,7 @@ describe Lhm do
454
455
  slave do
455
456
  table_data = table_read(:users)
456
457
  assert_nil table_data.columns['username']
457
- table_read(:users).columns['user_name'].must_equal({
458
+ value(table_read(:users).columns['user_name']).must_equal({
458
459
  :type => 'varchar(255)',
459
460
  :is_nullable => 'NO',
460
461
  :column_default => nil,
@@ -464,7 +465,7 @@ describe Lhm do
464
465
 
465
466
  result = select_one('SELECT `user_name` from users')
466
467
  result = result['user_name'] if result.respond_to?(:has_key?)
467
- result.must_equal('a user')
468
+ value(result).must_equal('a user')
468
469
  end
469
470
  end
470
471
 
@@ -501,7 +502,7 @@ describe Lhm do
501
502
  slave do
502
503
  table_data = table_read(:users)
503
504
  assert_nil table_data.columns['fnord']
504
- table_read(:users).columns['group'].must_equal({
505
+ value(table_read(:users).columns['group']).must_equal({
505
506
  :type => 'varchar(255)',
506
507
  :is_nullable => 'YES',
507
508
  :column_default => 'Superfriends',
@@ -525,11 +526,11 @@ describe Lhm do
525
526
  end
526
527
 
527
528
  slave do
528
- table_read(:lines).columns.must_include 'by'
529
- table_read(:lines).columns.wont_include 'lines'
530
- index_on_columns?(:lines, ['between'], :unique).must_equal true
531
- index_on_columns?(:lines, ['by']).must_equal false
532
- count_all(:lines).must_equal(2)
529
+ value(table_read(:lines).columns).must_include 'by'
530
+ value(table_read(:lines).columns).wont_include 'lines'
531
+ value(index_on_columns?(:lines, ['between'], :unique)).must_equal true
532
+ value(index_on_columns?(:lines, ['by'])).must_equal false
533
+ value(count_all(:lines)).must_equal(2)
533
534
  end
534
535
  end
535
536
 
@@ -554,7 +555,7 @@ describe Lhm do
554
555
  insert.join
555
556
 
556
557
  slave do
557
- count_all(:users).must_equal(60)
558
+ value(count_all(:users)).must_equal(60)
558
559
  end
559
560
  end
560
561
 
@@ -577,7 +578,80 @@ describe Lhm do
577
578
  delete.join
578
579
 
579
580
  slave do
580
- count_all(:users).must_equal(40)
581
+ value(count_all(:users)).must_equal(40)
582
+ end
583
+ end
584
+ end
585
+
586
+ describe 'connection' do
587
+ include ToxiproxyHelper
588
+
589
+ before(:each) do
590
+ @logs = StringIO.new
591
+ Lhm.logger = Logger.new(@logs)
592
+ end
593
+
594
+ it " should not try to reconnect if reconnect_with_consistent_host is not provided" do
595
+ connect_master_with_toxiproxy!
596
+
597
+ table_create(:users)
598
+ 100.times { |n| execute("insert into users set reference = '#{ n }'") }
599
+
600
+ assert_raises ActiveRecord::StatementInvalid do
601
+ Toxiproxy[:mysql_master].down do
602
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
603
+ t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL")
604
+ t.ddl("ALTER TABLE #{t.name} DROP PRIMARY KEY, ADD PRIMARY KEY (username, id)")
605
+ t.ddl("ALTER TABLE #{t.name} ADD INDEX (id)")
606
+ t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL AUTO_INCREMENT")
607
+ end
608
+ end
609
+ end
610
+ end
611
+
612
+ it "should reconnect if reconnect_with_consistent_host is true" do
613
+ connect_master_with_toxiproxy!
614
+ mysql_disabled = false
615
+
616
+ table_create(:users)
617
+ 100.times { |n| execute("insert into users set reference = '#{ n }'") }
618
+
619
+ # Redeclare Lhm::ChunkInsert to use Hook to disable MySQL writer for 3 seconds before first insert
620
+ Lhm::ChunkInsert.class_eval do
621
+ extend AfterDo
622
+
623
+ before(:insert_and_return_count_of_rows_created) do
624
+ unless mysql_disabled
625
+ mysql_disabled = true
626
+ Thread.new do
627
+ Toxiproxy[:mysql_master].down do
628
+ sleep 3
629
+ end
630
+ end
631
+ end
632
+ end
633
+
634
+ # Need to call `#method_added` manually to have the hooks take into effect
635
+ method_added(:insert_and_return_count_of_rows_created)
636
+ end
637
+
638
+ Lhm.change_table(:users, atomic_switch: false, reconnect_with_consistent_host: true) do |t|
639
+ t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL")
640
+ t.ddl("ALTER TABLE #{t.name} DROP PRIMARY KEY, ADD PRIMARY KEY (username, id)")
641
+ t.ddl("ALTER TABLE #{t.name} ADD INDEX (id)")
642
+ t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL AUTO_INCREMENT")
643
+ end
644
+
645
+ log_lines = @logs.string.split("\n")
646
+
647
+ assert log_lines.one?{ |line| line.include?("Lost connection to MySQL, will retry to connect to same host")}
648
+ assert log_lines.one?{ |line| line.include?("LHM successfully reconnected to initial host")}
649
+ assert log_lines.one?{ |line| line.include?("100% complete")}
650
+
651
+ Lhm::ChunkInsert.remove_all_callbacks
652
+
653
+ slave do
654
+ value(count_all(:users)).must_equal(100)
581
655
  end
582
656
  end
583
657
  end
@@ -24,7 +24,7 @@ describe Lhm do
24
24
  session_innodb_lock_wait_timeout = connection.select_one("SHOW SESSION VARIABLES LIKE 'innodb_lock_wait_timeout'")['Value'].to_i
25
25
  session_lock_wait_timeout = connection.select_one("SHOW SESSION VARIABLES LIKE 'lock_wait_timeout'")['Value'].to_i
26
26
 
27
- session_lock_wait_timeout.must_equal global_lock_wait_timeout + Lhm::Invoker::LOCK_WAIT_TIMEOUT_DELTA
28
- session_innodb_lock_wait_timeout.must_equal global_innodb_lock_wait_timeout + Lhm::Invoker::LOCK_WAIT_TIMEOUT_DELTA
27
+ value(session_lock_wait_timeout).must_equal global_lock_wait_timeout + Lhm::Invoker::LOCK_WAIT_TIMEOUT_DELTA
28
+ value(session_innodb_lock_wait_timeout).must_equal global_innodb_lock_wait_timeout + Lhm::Invoker::LOCK_WAIT_TIMEOUT_DELTA
29
29
  end
30
30
  end
@@ -32,8 +32,8 @@ describe Lhm::LockedSwitcher do
32
32
  switcher.run
33
33
 
34
34
  slave do
35
- data_source_exists?(@origin).must_equal true
36
- table_read(@migration.archive_name).columns.keys.must_include 'origin'
35
+ value(data_source_exists?(@origin)).must_equal true
36
+ value(table_read(@migration.archive_name).columns.keys).must_include 'origin'
37
37
  end
38
38
  end
39
39
 
@@ -42,8 +42,8 @@ describe Lhm::LockedSwitcher do
42
42
  switcher.run
43
43
 
44
44
  slave do
45
- data_source_exists?(@destination).must_equal false
46
- table_read(@origin.name).columns.keys.must_include 'destination'
45
+ value(data_source_exists?(@destination)).must_equal false
46
+ value(table_read(@origin.name).columns.keys).must_include 'destination'
47
47
  end
48
48
  end
49
49
  end
@@ -0,0 +1,34 @@
1
+ describe "ProxySQL integration" do
2
+ it "Should contact the writer" do
3
+ conn = Mysql2::Client.new(
4
+ host: '127.0.0.1',
5
+ username: "writer",
6
+ password: "password",
7
+ port: "33005",
8
+ )
9
+
10
+ assert_equal conn.query("SELECT @@global.hostname as host").each.first["host"], "mysql-1"
11
+ end
12
+
13
+ it "Should contact the reader" do
14
+ conn = Mysql2::Client.new(
15
+ host: '127.0.0.1',
16
+ username: "reader",
17
+ password: "password",
18
+ port: "33005",
19
+ )
20
+
21
+ assert_equal conn.query("SELECT @@global.hostname as host").each.first["host"], "mysql-2"
22
+ end
23
+
24
+ it "Should override default hostgroup from user if rule matches" do
25
+ conn = Mysql2::Client.new(
26
+ host: '127.0.0.1',
27
+ username: "reader",
28
+ password: "password",
29
+ port: "33005",
30
+ )
31
+
32
+ assert_equal conn.query("SELECT @@global.hostname as host #{Lhm::ProxySQLHelper::ANNOTATION}").each.first["host"], "mysql-1"
33
+ end
34
+ end
@@ -0,0 +1,52 @@
1
+ require 'yaml'
2
+ require 'mysql2'
3
+
4
+ class DBConnectionHelper
5
+
6
+ DATABASE_CONFIG_FILE = "database.yml"
7
+
8
+ class << self
9
+ def db_config
10
+ @db_config ||= YAML.load_file(File.expand_path(File.dirname(__FILE__)) + "/../#{DATABASE_CONFIG_FILE}")
11
+ end
12
+
13
+ def new_mysql_connection(role = :master, with_data = false, toxic = false)
14
+
15
+ key = role.to_s + toxic_postfix(toxic)
16
+
17
+ conn = ActiveRecord::Base.establish_connection(
18
+ :host => '127.0.0.1',
19
+ :adapter => "mysql2",
20
+ :username => db_config[key]['user'],
21
+ :password => db_config[key]['password'],
22
+ :database => test_db_name,
23
+ :port => db_config[key]['port']
24
+ )
25
+ conn = conn.connection
26
+ init_with_dummy_data(conn) if with_data
27
+ conn
28
+ end
29
+
30
+ def toxic_postfix(toxic)
31
+ toxic ? "_toxic" : ""
32
+ end
33
+
34
+ def test_db_name
35
+ @test_db_name ||= "test"
36
+ end
37
+
38
+ def test_table_name
39
+ @test_table_name ||= "test"
40
+ end
41
+
42
+ def init_with_dummy_data(conn)
43
+ conn.execute("DROP TABLE IF EXISTS #{test_table_name} ")
44
+ conn.execute("CREATE TABLE #{test_table_name} (id int)")
45
+
46
+ 1.upto(9) do |i|
47
+ query = "INSERT INTO #{test_table_name} (id) VALUE (#{i})"
48
+ conn.execute(query)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -21,6 +21,8 @@ describe Lhm::SqlRetry do
21
21
 
22
22
  # Assert our pre-conditions
23
23
  assert_equal 2, @helper.record_count
24
+
25
+ Mysql2::Client.any_instance.stubs(:active?).returns(true)
24
26
  end
25
27
 
26
28
  after(:each) do
@@ -41,7 +43,7 @@ describe Lhm::SqlRetry do
41
43
 
42
44
  exception = assert_raises { @helper.trigger_wait_lock }
43
45
 
44
- assert_equal "Lock wait timeout exceeded; try restarting transaction", exception.message
46
+ assert_match /Lock wait timeout exceeded; try restarting transaction/, exception.message
45
47
  assert_equal Mysql2::Error::TimeoutError, exception.class
46
48
 
47
49
  assert_equal 2, @helper.record_count # no records inserted
@@ -53,12 +55,12 @@ describe Lhm::SqlRetry do
53
55
  it "successfully executes the SQL despite the errors encountered" do
54
56
  # Start a thread to retry, once the lock is held, execute the block
55
57
  @helper.with_waiting_lock do |waiting_connection|
56
- sql_retry = Lhm::SqlRetry.new(waiting_connection, {
58
+ sql_retry = Lhm::SqlRetry.new(waiting_connection, retry_options: {
57
59
  base_interval: 0.2, # first retry after 200ms
58
60
  multiplier: 1, # subsequent retries wait 1x longer than first retry (no change)
59
61
  tries: 3, # we only need 3 tries (including the first) for the scenario described below
60
62
  rand_factor: 0 # do not introduce randomness to wait timer
61
- })
63
+ }, reconnect_with_consistent_host: false)
62
64
 
63
65
  # RetryTestHelper is configured to hold lock for 5 seconds and timeout after 2 seconds.
64
66
  # Therefore the sequence of events will be:
@@ -96,12 +98,12 @@ describe Lhm::SqlRetry do
96
98
  puts "*" * 64
97
99
  # Start a thread to retry, once the lock is held, execute the block
98
100
  @helper.with_waiting_lock do |waiting_connection|
99
- sql_retry = Lhm::SqlRetry.new(waiting_connection, {
101
+ sql_retry = Lhm::SqlRetry.new(waiting_connection, retry_options: {
100
102
  base_interval: 0.2, # first retry after 200ms
101
103
  multiplier: 1, # subsequent retries wait 1x longer than first retry (no change)
102
104
  tries: 2, # we need 3 tries (including the first) for the scenario described below, but we only get two...we will fail
103
105
  rand_factor: 0 # do not introduce randomness to wait timer
104
- })
106
+ }, reconnect_with_consistent_host: false)
105
107
 
106
108
  # RetryTestHelper is configured to hold lock for 5 seconds and timeout after 2 seconds.
107
109
  # Therefore the sequence of events will be:
@@ -116,7 +118,7 @@ describe Lhm::SqlRetry do
116
118
 
117
119
  exception = assert_raises { @helper.trigger_wait_lock }
118
120
 
119
- assert_equal "Lock wait timeout exceeded; try restarting transaction", exception.message
121
+ assert_match /Lock wait timeout exceeded; try restarting transaction/, exception.message
120
122
  assert_equal Mysql2::Error::TimeoutError, exception.class
121
123
 
122
124
  assert_equal 2, @helper.record_count # no records inserted
@@ -1,5 +1,7 @@
1
- require 'yaml'
1
+ require 'integration/integration_helper'
2
+
2
3
  class LockWaitTimeoutTestHelper
4
+
3
5
  def initialize(lock_duration:, innodb_lock_wait_timeout:)
4
6
  # This connection will be used exclusively to setup the test,
5
7
  # assert pre-conditions and assert post-conditions.
@@ -68,7 +70,7 @@ class LockWaitTimeoutTestHelper
68
70
 
69
71
  def insert_records_at_ids(connection, ids)
70
72
  ids.each do |id|
71
- connection.query "INSERT INTO #{test_table_name} (id) VALUES (#{id})"
73
+ mysql_exec(connection, "INSERT INTO #{test_table_name} (id) VALUES (#{id})")
72
74
  end
73
75
  end
74
76
 
@@ -79,11 +81,10 @@ class LockWaitTimeoutTestHelper
79
81
  def new_mysql_connection
80
82
  Mysql2::Client.new(
81
83
  host: '127.0.0.1',
82
- database: test_db_name,
83
84
  username: db_config['master']['user'],
84
85
  password: db_config['master']['password'],
85
86
  port: db_config['master']['port'],
86
- socket: db_config['master']['socket']
87
+ database: test_db_name
87
88
  )
88
89
  end
89
90
 
@@ -98,4 +99,16 @@ class LockWaitTimeoutTestHelper
98
99
  def test_table_name
99
100
  @test_table_name ||= "lock_wait"
100
101
  end
102
+
103
+ private
104
+
105
+ def mysql_exec(connection, statement)
106
+ if connection.class == Mysql2::Client
107
+ connection.query(statement)
108
+ elsif connection.class.to_s.include?("ActiveRecord")
109
+ connection.execute(statement)
110
+ else
111
+ raise StandardError.new("Unrecognized MySQL client")
112
+ end
113
+ end
101
114
  end
@@ -0,0 +1,22 @@
1
+ class ProxySQLHelper
2
+ class << self
3
+ # Flips the destination hostgroup for /maintenance:lhm/ from 0 (i.e. writer) to 1 (i.e. reader)
4
+ def with_lhm_hostgroup_flip
5
+ conn = Mysql2::Client.new(
6
+ host: '127.0.0.1',
7
+ username: "remote-admin",
8
+ password: "password",
9
+ port: "6032",
10
+ )
11
+
12
+ begin
13
+ conn.query("UPDATE mysql_query_rules SET destination_hostgroup=1 WHERE match_pattern=\"maintenance:lhm\"")
14
+ conn.query("LOAD MYSQL QUERY RULES TO RUNTIME;")
15
+ yield
16
+ ensure
17
+ conn.query("UPDATE mysql_query_rules SET destination_hostgroup=0 WHERE match_pattern=\"maintenance:lhm\"")
18
+ conn.query("LOAD MYSQL QUERY RULES TO RUNTIME;")
19
+ end
20
+ end
21
+ end
22
+ end