lhm-shopify 3.4.0 → 3.5.5

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.
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