flydata 0.6.3 → 0.6.4

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -2
  3. data/VERSION +1 -1
  4. data/bin/fdredshift +78 -0
  5. data/circle.yml +1 -1
  6. data/ext/flydata/{parser/mysql → source_mysql/parser}/.gitignore +0 -0
  7. data/ext/flydata/{parser/mysql → source_mysql/parser}/dump_parser_ext.cpp +3 -3
  8. data/ext/flydata/source_mysql/parser/extconf.rb +3 -0
  9. data/ext/flydata/{parser/mysql → source_mysql/parser}/parser.txt +0 -0
  10. data/ext/flydata/{parser/mysql → source_mysql/parser}/sql_parser.cpp +0 -0
  11. data/ext/flydata/{parser/mysql → source_mysql/parser}/sql_parser.h +0 -0
  12. data/flydata-core/lib/flydata-core/mysql/binlog_pos.rb +34 -32
  13. data/flydata-core/lib/flydata-core/mysql/compatibility_checker.rb +20 -0
  14. data/flydata-core/lib/flydata-core/table_def/mysql_table_def.rb +12 -4
  15. data/flydata-core/lib/flydata-core/table_def/redshift_table_def.rb +60 -6
  16. data/flydata-core/spec/mysql/binlog_pos_spec.rb +474 -0
  17. data/flydata-core/spec/table_def/mysql_table_def_spec.rb +57 -0
  18. data/flydata-core/spec/table_def/mysql_to_redshift_table_def_spec.rb +174 -20
  19. data/flydata-core/spec/table_def/mysqldump_test_col_comment_with_AUTO_INCREMENT_keyword.dump +43 -0
  20. data/flydata-core/spec/table_def/mysqldump_test_col_comment_with_not_null_keyword.dump +43 -0
  21. data/flydata-core/spec/table_def/mysqldump_test_col_comment_with_unique_keyword.dump +43 -0
  22. data/flydata-core/spec/table_def/mysqldump_test_col_comment_with_unsigned_keyword.dump +43 -0
  23. data/flydata-core/spec/table_def/redshift_table_def_spec.rb +41 -8
  24. data/flydata.gemspec +0 -0
  25. data/lib/flydata/cli.rb +11 -5
  26. data/lib/flydata/command/base.rb +14 -1
  27. data/lib/flydata/command/exclusive_runnable.rb +42 -12
  28. data/lib/flydata/command/helper.rb +6 -6
  29. data/lib/flydata/command/sender.rb +4 -3
  30. data/lib/flydata/command/setup.rb +30 -381
  31. data/lib/flydata/command/stop.rb +1 -0
  32. data/lib/flydata/command/sync.rb +273 -301
  33. data/lib/flydata/compatibility_check.rb +24 -117
  34. data/lib/flydata/fluent-plugins/in_mysql_binlog_flydata.rb +3 -3
  35. data/lib/flydata/fluent-plugins/mysql/alter_table_query_handler.rb +2 -2
  36. data/lib/flydata/fluent-plugins/mysql/binlog_record_handler.rb +6 -6
  37. data/lib/flydata/fluent-plugins/mysql/truncate_table_query_handler.rb +0 -1
  38. data/lib/flydata/parser.rb +14 -0
  39. data/lib/flydata/{parser_provider.rb → parser/parser_provider.rb} +6 -4
  40. data/lib/flydata/parser/source_table.rb +33 -0
  41. data/lib/flydata/source.rb +105 -0
  42. data/lib/flydata/source/component.rb +21 -0
  43. data/lib/flydata/source/errors.rb +7 -0
  44. data/lib/flydata/source/generate_source_dump.rb +72 -0
  45. data/lib/flydata/source/parse_dump_and_send.rb +52 -0
  46. data/lib/flydata/source/setup.rb +31 -0
  47. data/lib/flydata/source/source_pos.rb +45 -0
  48. data/lib/flydata/source/sync.rb +56 -0
  49. data/lib/flydata/source/sync_generate_table_ddl.rb +43 -0
  50. data/lib/flydata/source_file/setup.rb +17 -0
  51. data/lib/flydata/source_file/sync.rb +14 -0
  52. data/lib/flydata/{command → source_mysql/command}/mysql.rb +2 -1
  53. data/lib/flydata/{command → source_mysql/command}/mysql_command_base.rb +2 -4
  54. data/lib/flydata/{command → source_mysql/command}/mysqlbinlog.rb +2 -1
  55. data/lib/flydata/{command → source_mysql/command}/mysqldump.rb +2 -1
  56. data/lib/flydata/source_mysql/generate_source_dump.rb +53 -0
  57. data/lib/flydata/source_mysql/mysql_compatibility_check.rb +114 -0
  58. data/lib/flydata/source_mysql/parse_dump_and_send.rb +28 -0
  59. data/lib/flydata/{parser/mysql → source_mysql/parser}/.gitignore +0 -0
  60. data/lib/flydata/{parser/mysql → source_mysql/parser}/dump_parser.rb +32 -67
  61. data/lib/flydata/{parser/mysql → source_mysql/parser}/mysql_alter_table.treetop +0 -0
  62. data/lib/flydata/source_mysql/setup.rb +24 -0
  63. data/lib/flydata/source_mysql/source_pos.rb +21 -0
  64. data/lib/flydata/source_mysql/sync.rb +45 -0
  65. data/lib/flydata/source_mysql/sync_generate_table_ddl.rb +40 -0
  66. data/lib/flydata/{mysql → source_mysql}/table_ddl.rb +6 -17
  67. data/lib/flydata/source_zendesk/sync_generate_table_ddl.rb +30 -0
  68. data/lib/flydata/source_zendesk/zendesk_flydata_tabledefs.rb +133 -0
  69. data/lib/flydata/sync_file_manager.rb +132 -73
  70. data/lib/flydata/table_ddl.rb +18 -0
  71. data/spec/flydata/cli_spec.rb +1 -0
  72. data/spec/flydata/command/exclusive_runnable_spec.rb +19 -8
  73. data/spec/flydata/command/sender_spec.rb +1 -1
  74. data/spec/flydata/command/setup_spec.rb +4 -4
  75. data/spec/flydata/command/sync_spec.rb +97 -134
  76. data/spec/flydata/compatibility_check_spec.rb +16 -289
  77. data/spec/flydata/fluent-plugins/mysql/alter_table_query_handler_spec.rb +3 -3
  78. data/spec/flydata/fluent-plugins/mysql/dml_record_handler_spec.rb +1 -1
  79. data/spec/flydata/fluent-plugins/mysql/shared_query_handler_context.rb +4 -2
  80. data/spec/flydata/fluent-plugins/mysql/truncate_query_handler_spec.rb +1 -1
  81. data/spec/flydata/source_mysql/generate_source_dump_spec.rb +69 -0
  82. data/spec/flydata/source_mysql/mysql_compatibility_check_spec.rb +280 -0
  83. data/spec/flydata/{parser/mysql → source_mysql/parser}/alter_table_parser_spec.rb +2 -2
  84. data/spec/flydata/{parser/mysql → source_mysql/parser}/dump_parser_spec.rb +75 -70
  85. data/spec/flydata/source_mysql/sync_generate_table_ddl_spec.rb +137 -0
  86. data/spec/flydata/{mysql → source_mysql}/table_ddl_spec.rb +2 -2
  87. data/spec/flydata/source_spec.rb +140 -0
  88. data/spec/flydata/source_zendesk/sync_generate_table_ddl_spec.rb +33 -0
  89. data/spec/flydata/sync_file_manager_spec.rb +157 -77
  90. data/tmpl/redshift_mysql_data_entry.conf.tmpl +1 -1
  91. metadata +56 -23
  92. data/ext/flydata/parser/mysql/extconf.rb +0 -3
  93. data/lib/flydata/mysql/binlog_position.rb +0 -22
  94. data/spec/flydata/mysql/binlog_position_spec.rb +0 -35
@@ -0,0 +1,18 @@
1
+ module Flydata
2
+
3
+ class TableDdl
4
+ VERSION0 = 0 # the version where no .generated_ddl file was generated.
5
+ # Therefore, this version never shows up in anywhere.
6
+ VERSION1 = 1 # the version which doesn't handle server side encoding support.
7
+ VERSION2 = 2 # the version with server side encoding support, migrated from
8
+ # the previous versions. Format/functionality-wise, it's the
9
+ # same as Version 3.
10
+ VERSION3 = 3 # the version with server side encoding support, generated by
11
+ # sync:generated_table_ddl command.
12
+ VERSION4 = 4 # the version with server side encoding support, generated by
13
+ # the auto-generated CREATE TABLE event.
14
+
15
+ VERSION = VERSION3
16
+ end
17
+
18
+ end
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'flydata/cli'
3
+ require 'flydata/command/base'
3
4
 
4
5
  module Flydata
5
6
  describe Cli do
@@ -23,12 +23,23 @@ describe Exclusiverunnabletest do
23
23
  let(:subject_object) { described_class.new }
24
24
 
25
25
  let(:file_path) { "/tmp" }
26
- let(:lock_file) { File.join(file_path, 'exclusive_run.info') }
27
- let(:lock_file_content) { { command: 'exclusiverunnabletest:long_running_command', pid: 234 } }
26
+ let(:info_file) { File.join(file_path, 'exclusive_run.info') }
27
+ let(:info_command) { 'exclusiverunnabletest:long_running_command' }
28
+ let(:dummy_pid) { 11234 }
29
+ let(:info_file_content) { { command: info_command, pid: dummy_pid } }
30
+ let(:opts) { double('opts') }
31
+ let(:force_run) { false }
28
32
 
33
+ before do
34
+ File.unlink info_file if File.exists? info_file
35
+ end
29
36
  before do
30
37
  described_class.exclusive_run_home = file_path
31
38
  end
39
+ before do
40
+ allow(subject_object).to receive(:opts).and_return opts
41
+ allow(opts).to receive(:force_run?).and_return force_run
42
+ end
32
43
 
33
44
  describe '#normal_method_call' do
34
45
  subject { subject_object.normal_method_call }
@@ -38,17 +49,17 @@ describe Exclusiverunnabletest do
38
49
 
39
50
  context "when a lock file already exists" do
40
51
  before do
41
- File.open(lock_file, "w") {|f| f.write(lock_file_content)}
52
+ File.open(info_file, "w") {|f| f.write(info_file_content)}
42
53
  end
43
54
 
44
55
  it "does not delete the lock file" do
45
56
  expect{ subject }.to raise_error
46
57
 
47
- expect(File.exists?(lock_file)).to eq true
58
+ expect(File.exists?(info_file)).to eq true
48
59
  end
49
60
 
50
61
  after do
51
- File.delete(lock_file) if File.exists?(lock_file)
62
+ File.delete(info_file) if File.exists?(info_file)
52
63
  end
53
64
  end
54
65
  end
@@ -62,7 +73,7 @@ describe Exclusiverunnabletest do
62
73
 
63
74
  sleep 0.1
64
75
 
65
- expect(File.exists?(lock_file)).to eq true
76
+ expect(File.exists?(info_file)).to eq true
66
77
 
67
78
  th.join
68
79
  end
@@ -73,7 +84,7 @@ describe Exclusiverunnabletest do
73
84
 
74
85
  th.join
75
86
 
76
- expect(File.exists?(lock_file)).to eq false
87
+ expect(File.exists?(info_file)).to eq false
77
88
  end
78
89
  end
79
90
  end
@@ -83,7 +94,7 @@ describe Exclusiverunnabletest do
83
94
  th = Thread.new{ subject }
84
95
  sleep 0.1
85
96
  expect{ subject_object.normal_method_call }.to raise_exception(
86
- /`exclusiverunnabletest:long_running_command` is already running\..*\(pid: *[0-9]+\)/)
97
+ /`#{info_command}` is already running or terminated abnormally. \(pid:[0-9]+\)/)
87
98
 
88
99
  th.join
89
100
  end
@@ -23,7 +23,7 @@ module Flydata
23
23
  allow(data_port).to receive(:get).and_return("Wibble")
24
24
 
25
25
  allow_any_instance_of(Flydata::AgentCompatibilityCheck).to receive(:check).and_return(true)
26
- Flydata::Command::Sync.any_instance.should_receive(:try_mysql_sync).and_return("Wobble")
26
+ Flydata::Command::Sync.any_instance.should_receive(:try_initial_sync).and_return("Wobble")
27
27
  end
28
28
 
29
29
  context "as daemon" do
@@ -19,21 +19,21 @@ module Flydata
19
19
  before do
20
20
  allow(subject).to receive(:retrieve_data_entries).and_return(data_entries)
21
21
  allow(subject).to receive(:flydata).and_return(flydata)
22
- expect(flydata).to receive(:data_port).and_return(data_port)
22
+ allow(flydata).to receive(:data_port).and_return(data_port)
23
23
  allow(flydata).to receive(:flydata_api_host).and_return('localhost')
24
24
  expect(flydata).to receive(:credentials).and_return(credentials)
25
25
  expect(credentials).to receive(:authenticated?).and_return(false)
26
- expect(data_port).to receive(:get).and_return(dp)
26
+ allow(data_port).to receive(:get).and_return(dp)
27
27
  expect(sender).to receive(:process_exist?).and_return(true)
28
28
  expect(sender).to receive(:stop)
29
- expect(sync_fm).to receive(:binlog_path).and_return('/tmp')
29
+ allow(sync_fm).to receive(:source_pos_path).and_return('/tmp')
30
30
  expect(conf).to receive(:copy_templates)
31
31
  expect(login).to receive(:run)
32
32
  expect(sender).to receive(:restart)
33
+ allow(Flydata::SyncFileManager).to receive(:new).with(de).and_return(sync_fm)
33
34
  end
34
35
  it do
35
36
  expect(Flydata::Command::Conf).to receive(:new).and_return(conf)
36
- expect(Flydata::SyncFileManager).to receive(:new).with(de).and_return(sync_fm)
37
37
  expect(Flydata::Command::Login).to receive(:new).and_return(login)
38
38
  expect(Flydata::Command::Sender).to receive(:new).and_return(sender).twice
39
39
 
@@ -5,10 +5,10 @@ require 'flydata/command/sync'
5
5
  module Flydata
6
6
  module Command
7
7
  describe Sync do
8
- let(:subject_object){ described_class.new }
9
8
  subject { subject_object }
9
+ let(:subject_object){ described_class.new }
10
10
 
11
- let(:default_mysqldump_dir) do
11
+ let(:default_dump_dir) do
12
12
  File.join('/tmp', "sync_dump_#{Time.now.to_i}")
13
13
  end
14
14
  let(:default_data_entry) do
@@ -36,7 +36,7 @@ module Flydata
36
36
  "password"=>"welcome", "database"=>"sync_test", "tables"=>["table1", "table2", "table4"],
37
37
  "invalid_tables"=>["table3"],
38
38
  "new_tables"=>["table4"],
39
- "mysqldump_dir"=>default_mysqldump_dir, "forwarder" => "tcpforwarder",
39
+ "dump_dir"=>default_dump_dir, "forwarder" => "tcpforwarder",
40
40
  "data_servers"=>"localhost:9905" }
41
41
  }
42
42
  end
@@ -46,16 +46,12 @@ module Flydata
46
46
  let(:mysql_table_columns) {
47
47
  {"id"=>{:column_name=>"id", :format_type_str=>"int(11)", :format_type=>"int", :format_size=>11}, "name"=>{:column_name=>"name", :format_type_str=>"varchar(40)", :format_type=>"varchar", :format_size=>40, :default=>nil}, "created_at"=>{:column_name=>"created_at", :format_type_str=>"timestamp", :format_type=>"timestamp", :default=>"CURRENT_TIMESTAMP"}, "bin"=>{:column_name=>"bin", :format_type_str=>col4_type_str, :format_type=>col4_type, :format_size=>col4_width, :default=>nil}, "bin2"=>{:column_name=>"bin2", :format_type_str=>"blob", :format_type=>"blob"}, "varbin"=>{:column_name=>"varbin", :format_type_str=>"varchar(34)", :format_type=>"varchar", :format_size=>34, :default=>nil}}
48
48
  }
49
- let(:mysql_table) do
50
- Flydata::Parser::Mysql::MysqlTable.new("test_table", mysql_table_columns, ['id'])
51
- end
52
-
53
49
  after :each do
54
- if Dir.exists?(default_mysqldump_dir)
55
- Dir.delete(default_mysqldump_dir) rescue nil
50
+ if Dir.exists?(default_dump_dir)
51
+ Dir.delete(default_dump_dir) rescue nil
56
52
  end
57
- if File.exists?(default_mysqldump_dir)
58
- File.delete(default_mysqldump_dir) rescue nil
53
+ if File.exists?(default_dump_dir)
54
+ File.delete(default_dump_dir) rescue nil
59
55
  end
60
56
  end
61
57
 
@@ -78,7 +74,7 @@ module Flydata
78
74
  end
79
75
  end
80
76
  end
81
- describe '#generate_mysqldump' do
77
+ describe '#generate_source_dump' do
82
78
  let (:flydata) { double('flydata') }
83
79
  let (:dp) { double('dp') }
84
80
  let (:default_data_port) { double('default_data_port') }
@@ -92,10 +88,15 @@ module Flydata
92
88
  let (:target_tables) { ["test_table_1"] }
93
89
  let (:db_byte) { 1 }
94
90
  let (:disk_byte) { 100 }
91
+ before do
92
+ require 'flydata/source_mysql/generate_source_dump'
93
+ allow_any_instance_of(Flydata::SourceMysql::GenerateSourceDump).to receive(:dump_size).and_return(db_byte)
94
+ allow_any_instance_of(Flydata::SourceMysql::GenerateSourceDump).to receive(:run_compatibility_check)
95
+ end
95
96
  before do
96
97
  expect(subject).to receive(:flydata).and_return(flydata).at_least(:once)
98
+ allow(subject).to receive(:data_entry).and_return(default_data_entry)
97
99
  expect(flydata).to receive(:data_port).and_return(dp)
98
- allow(flydata).to receive(:data_entry).and_return(default_data_entry)
99
100
  expect(dp).to receive(:get).and_return(default_data_port)
100
101
  allow(File).to receive(:exists?).and_return(false)
101
102
  expect(default_sync_fm).to receive(:load_dump_pos).and_return(default_dump_pos)
@@ -106,8 +107,6 @@ module Flydata
106
107
  expect(subject).to receive(:log_info)
107
108
  expect(subject).to receive(:log_info_stdout).at_least(:once)
108
109
  expect(subject).to receive(:ask_yes_no).and_return(true).at_least(:once)
109
- Flydata::Parser::Mysql::DatabaseSizeCheck.any_instance.should_receive(:get_db_bytesize).and_return(db_byte)
110
- Flydata::MysqlCompatibilityCheck.any_instance.should_receive(:check)
111
110
  expect_any_instance_of(FlydataCore::Event::ApiEventSender).to receive(:send_event).once
112
111
  end
113
112
  context 'with no stream option' do
@@ -118,145 +117,43 @@ module Flydata
118
117
  end
119
118
  it 'will export to dump file' do
120
119
  expect(subject).to receive(:call_block_or_return_io)
121
- Flydata::Parser::Mysql::MysqlDumpGeneratorNoMasterData.any_instance.should_receive(:dump).with(default_fp)
120
+ expect_any_instance_of(Flydata::SourceMysql::GenerateSourceDump).
121
+ to receive(:dump).with(target_tables, default_fp)
122
122
 
123
- subject.send(:generate_mysqldump, default_data_entry, default_sync_fm)
123
+ subject.send(:generate_source_dump, default_data_entry, default_sync_fm)
124
124
  end
125
125
  it 'will remove dump file on interrupt' do
126
126
  expect(default_sync_fm).to receive(:delete_dump_file)
127
+ expect_any_instance_of(Flydata::SourceMysql::Parser::MysqlDumpGeneratorNoMasterData).to receive(:dump).and_raise(Interrupt)
127
128
 
128
129
  expect {
129
- Flydata::Parser::Mysql::MysqlDumpGeneratorNoMasterData.any_instance.should_receive(:dump).and_raise(Interrupt)
130
- subject.send(:generate_mysqldump, default_data_entry, default_sync_fm)
130
+ subject.send(:generate_source_dump, default_data_entry, default_sync_fm)
131
131
  }.to raise_error
132
132
  end
133
133
  end
134
134
  context 'with stream option' do
135
135
  it 'will export to io' do
136
136
  expect(default_sync_fm).to receive(:save_sync_info).once
137
- Flydata::Parser::Mysql::MysqlDumpGeneratorNoMasterData.any_instance.should_receive(:dump)
137
+ expect_any_instance_of(Flydata::SourceMysql::Parser::MysqlDumpGeneratorNoMasterData).to receive(:dump)
138
138
 
139
- subject.send(:generate_mysqldump, default_data_entry, default_sync_fm, false)
140
- end
141
- end
142
- end
143
- describe '#do_generate_table_ddl' do
144
- before do
145
- allow(subject).to receive(:data_entry).and_return(default_data_entry)
146
- allow_any_instance_of(Flydata::Api::DataEntry).to receive(:update_table_validity).and_return(true)
147
- subject.send(:set_current_tables, nil, include_all_tables: true)
148
- end
149
- shared_examples 'throws an error' do
150
- it "throws an error" do
151
- expect {
152
- subject.send(:do_generate_table_ddl, default_data_entry)
153
- }.to raise_error
154
- end
155
- end
156
- context 'with full options' do
157
- it 'issues mysqldump command with expected parameters' do
158
- expect(Open3).to receive(:popen3).with(
159
- 'mysqldump -h localhost -P 3306 -umasashi -pwelcome --default-character-set=utf8 --protocol=tcp -d sync_test table1 table2 table4 table3')
160
- subject.send(:do_generate_table_ddl, default_data_entry)
161
- end
162
- end
163
- context 'without_host' do
164
- before do
165
- default_data_entry['mysql_data_entry_preference'].delete('host')
166
- end
167
- include_examples 'throws an error'
168
- end
169
- context 'with empty host' do
170
- before do
171
- default_data_entry['mysql_data_entry_preference']['host'] = ""
172
- end
173
- include_examples 'throws an error'
174
- end
175
- context 'without_port' do
176
- before do
177
- default_data_entry['mysql_data_entry_preference'].delete('port')
178
- end
179
- it "uses the default port" do
180
- expect(Open3).to receive(:popen3).with(
181
- 'mysqldump -h localhost -umasashi -pwelcome --default-character-set=utf8 --protocol=tcp -d sync_test table1 table2 table4 table3')
182
- subject.send(:do_generate_table_ddl, default_data_entry)
183
- end
184
- end
185
- context 'with_port_override' do
186
- before do
187
- default_data_entry['mysql_data_entry_preference']['port'] = 1234
188
- end
189
- it "uses the specified port" do
190
- expect(Open3).to receive(:popen3).with(
191
- 'mysqldump -h localhost -P 1234 -umasashi -pwelcome --default-character-set=utf8 --protocol=tcp -d sync_test table1 table2 table4 table3')
192
- subject.send(:do_generate_table_ddl, default_data_entry)
193
- end
194
- end
195
- context 'without_username' do
196
- before do
197
- default_data_entry['mysql_data_entry_preference'].delete('username')
198
- end
199
- include_examples 'throws an error'
200
- end
201
- context 'with empty username' do
202
- before do
203
- default_data_entry['mysql_data_entry_preference']['username'] = ""
204
- end
205
- include_examples 'throws an error'
206
- end
207
- context 'without_password' do
208
- before do
209
- default_data_entry['mysql_data_entry_preference'].delete('password')
210
- end
211
- it "call mysqldump without MYSQL_PW set" do
212
- expect(Open3).to receive(:popen3).with(
213
- 'mysqldump -h localhost -P 3306 -umasashi --default-character-set=utf8 --protocol=tcp -d sync_test table1 table2 table4 table3')
214
- subject.send(:do_generate_table_ddl, default_data_entry)
215
- end
216
- end
217
- context 'with password containing symbols' do
218
- before do
219
- default_data_entry['mysql_data_entry_preference'].delete('password')
220
- default_data_entry['mysql_data_entry_preference']['password']="welcome&!@^@#^"
221
- end
222
- it "call mysqldump with MYSQL_PW set with correct symbols" do
223
- expect(Open3).to receive(:popen3).with(
224
- 'mysqldump -h localhost -P 3306 -umasashi -pwelcome\\&\\!@\\^@\\#\\^ --default-character-set=utf8 --protocol=tcp -d sync_test table1 table2 table4 table3')
225
- subject.send(:do_generate_table_ddl, default_data_entry)
226
- end
227
- end
228
- context 'without_database' do
229
- before do
230
- default_data_entry['mysql_data_entry_preference'].delete('database')
231
- end
232
- include_examples 'throws an error'
233
- end
234
- context 'with empty database' do
235
- before do
236
- default_data_entry['mysql_data_entry_preference']['database'] = ""
237
- end
238
- include_examples 'throws an error'
239
- end
240
- context 'with empty tables' do
241
- let(:sync_cmd) { described_class.new }
242
- before do
243
- default_data_entry['mysql_data_entry_preference']['tables'] = []
244
- default_data_entry['mysql_data_entry_preference']['invalid_tables'] = []
245
- default_data_entry['mysql_data_entry_preference']['new_tables'] = []
246
- allow(sync_cmd).to receive(:data_entry).and_return(default_data_entry)
247
- sync_cmd.send(:set_current_tables, nil, include_all_tables: true)
248
- end
249
- it 'should raise error' do
250
- expect{sync_cmd.send(:do_generate_table_ddl, default_data_entry)}.to raise_error
139
+ subject.send(:generate_source_dump, default_data_entry, default_sync_fm, false)
251
140
  end
252
141
  end
253
142
  end
254
143
  describe '#convert_to_flydata_values' do
255
- subject { subject_object.send(:convert_to_flydata_values, mysql_table, values) }
144
+ subject { subject_object.send(:convert_to_flydata_values, source_table, values) }
256
145
  let(:values) { [4, 'John', nil, col4_value, nil, nil] }
257
146
 
147
+ let(:source_table) do
148
+ Flydata::Parser::SourceTable.new("test_table", mysql_table_columns, ['id'])
149
+ end
150
+
258
151
  before do
259
- mysql_table.set_value_converters(FlydataCore::TableDef::MysqlTableDef::VALUE_CONVERTERS)
152
+ require 'flydata/parser/source_table'
153
+ end
154
+
155
+ before do
156
+ source_table.set_value_converters(FlydataCore::TableDef::MysqlTableDef::VALUE_CONVERTERS)
260
157
  end
261
158
 
262
159
  context 'with binary column' do
@@ -273,6 +170,72 @@ module Flydata
273
170
  end
274
171
  end
275
172
  end
173
+
174
+ describe '#data_entry' do
175
+ subject { subject_object.send(:data_entry) }
176
+
177
+ let(:de) { { 'mysql_data_entry_preference' => mp } }
178
+ let(:mp) { { 'tables' => 'Users,Addresses' } }
179
+
180
+ let(:sfm) { double('sfm') }
181
+ let(:ssl_ca_content) { double('ssl_ca_content') }
182
+ let(:ssl_ca_path) { double('ssl_ca_path') }
183
+
184
+ before do
185
+ allow(subject_object).to receive(:retrieve_data_entries).
186
+ and_return([de])
187
+ end
188
+
189
+ context 'type RedshiftMysqlDataEntry' do
190
+ before { de['type'] = 'RedshiftMysqlDataEntry' }
191
+ context 'called once' do
192
+ before do
193
+ expect(subject_object).to receive(:retrieve_data_entries).
194
+ and_return([de])
195
+ end
196
+ context 'without tables_append_only' do
197
+ it "expands a table list string to an array of tables" do
198
+ subject
199
+ expect(mp['tables']).to eq %w(Users Addresses)
200
+ end
201
+ end
202
+ context 'with tables_append_only' do
203
+ before { mp['tables_append_only'] = 'Invoices,Sessions,Addresses' }
204
+ it "creates an array of tables from 'tables' and 'tables_append_only' combined" do
205
+ subject
206
+ expect(mp['tables']).to eq %w(Users Addresses Invoices Sessions)
207
+ end
208
+ end
209
+ context 'with ssl_ca_content' do
210
+ before { mp["ssl_ca_content"] = ssl_ca_content }
211
+ it "saves the content to a local file via SyncFileManager" do
212
+ expect(SyncFileManager).to receive(:new).with(de).
213
+ and_return(sfm)
214
+ expect(sfm).to receive(:save_ssl_ca).with(ssl_ca_content)
215
+ expect(sfm).to receive(:ssl_ca_path).and_return(ssl_ca_path)
216
+
217
+ subject
218
+ expect(mp['ssl_ca']).to eq ssl_ca_path
219
+ expect(mp['sslca']).to eq ssl_ca_path
220
+ end
221
+ end
222
+ end
223
+ context 'called twice' do
224
+ before { subject }
225
+ it "repurposes the saved de" do
226
+ expect(subject_object).to receive(:retrieve_data_entries).never
227
+ subject
228
+ expect(mp['tables']).to eq %w(Users Addresses)
229
+ end
230
+ end
231
+ end
232
+ context 'type RedshiftFileDataEntry' do
233
+ before { de['type'] = 'RedshiftFileDataEntry' }
234
+ it "raises an error about unsupported data entry" do
235
+ expect { subject }.to raise_error /(supported data entry|data entry.*support)/
236
+ end
237
+ end
238
+ end
276
239
  end
277
240
  end
278
241
  end
@@ -3,309 +3,36 @@ require 'flydata/compatibility_check'
3
3
 
4
4
  module Flydata
5
5
  describe AgentCompatibilityCheck do
6
+ let(:subject_object) { AgentCompatibilityCheck.new("servers" => ['localhost']) }
6
7
  let(:default_data_port) do
7
8
  {
8
9
  "servers"=>["sample-test-site.com"]
9
10
  }
10
- end
11
-
12
- describe "#check" do
13
- subject { AgentCompatibilityCheck.new("servers" => ['localhost']) }
14
- context "runs all check methods" do
15
- context "when all ports are accessible" do
16
- let(:sock) { double('sock') }
17
- before do
18
- allow(TCPSocket).to receive(:new).and_return(sock)
19
- allow(sock).to receive(:close)
20
- end
21
- it "does nothing" do
22
- subject.check
23
- end
24
- end
25
- context "when a port access fails" do
26
- before do
27
- allow(TCPSocket).to receive(:new).and_raise(Errno::ETIMEDOUT)
28
- end
29
- it do
30
- expect{subject.check_outgoing_ports}.to raise_error(FlydataCore::AgentCompatibilityError, /ports/)
31
- end
32
- end
33
- end
34
11
  end
35
- end
36
-
37
- describe MysqlCompatibilityCheck do
38
- let(:default_data_port) do
39
- {
40
- "servers"=>["sample-test-site.com"]
41
- }
42
- end
43
-
44
- let(:default_mysql_cred) do
45
- {
46
- "host" => "test",
47
- "port" => 1234,
48
- "username" => "test",
49
- "password" => "password",
50
- "database" => "test_db"
51
- }
12
+ let(:sock) { double('sock') }
13
+ before do
14
+ allow(sock).to receive(:close)
52
15
  end
53
16
 
54
- describe "#check_mysql_user_compat" do
55
- let(:client) { double('client') }
56
- subject { MysqlCompatibilityCheck.new(default_data_port, default_mysql_cred) }
57
- before do
58
- allow(Mysql2::Client).to receive(:new).and_return(client)
59
- allow(client).to receive(:close)
60
- end
61
-
62
- context "with all privileges in all databases" do
63
- before do
64
- allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT ALL PRIVILEGES ON *.* TO 'test'@'host"}])
65
- end
66
- it do
67
- expect{subject.check_mysql_user_compat}.to_not raise_error
68
- end
69
- end
70
- context "with missing privileges in one database" do
71
- before do
72
- allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT ALL PRIVILEGES ON 'mysql'.* TO 'test'@'host"},
73
- {"Grants for test"=>"GRANT SELECT, RELOAD, REPLICATION CLIENT ON `test_db`.* TO 'test'@'host"}])
74
- end
75
- it do
76
- expect{subject.check_mysql_user_compat}.to raise_error(FlydataCore::MysqlCompatibilityError, /test_db': LOCK TABLES, REPLICATION SLAVE/)
77
- end
78
- end
79
- context "with all required privileges in between all and specific databases" do
80
- before do
81
- allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT RELOAD, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'test'@'host"},
82
- {"Grants for test"=>"GRANT SELECT, LOCK TABLES ON `test_db`.* TO 'test'@'host"},
83
- {"Grants for test"=>"GRANT SELECT, LOCK TABLES ON `mysql`.* TO 'test'@'host"}])
84
- end
85
- it do
86
- expect{subject.check_mysql_user_compat}.to_not raise_error
87
- end
88
- end
89
- context "with missing privileges in each database" do
90
- before do
91
- allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'test'@'host"},
92
- {"Grants for test"=>"GRANT SELECT, LOCK TABLES ON `test_db`.* TO 'test'@'host"},
93
- {"Grants for test"=>"GRANT SELECT, LOCK TABLES ON `mysql`.* TO 'test'@'host"}])
94
- end
95
- it do
96
- expect{subject.check_mysql_user_compat}.to raise_error(FlydataCore::MysqlCompatibilityError, /mysql': RELOAD\n.*test_db': RELOAD/)
97
- end
98
- end
99
- context "with all required privileges in all databases" do
100
- before do
101
- allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT SELECT, LOCK TABLES, RELOAD, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'test'@'host"}])
102
- end
103
- it do
104
- expect{subject.check_mysql_user_compat}.to_not raise_error
105
- end
106
- end
107
- context "with missing privileges in all databases" do
108
- before do
109
- allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT RELOAD, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'test'@'host"}])
110
- end
111
- it do
112
- expect{subject.check_mysql_user_compat}.to raise_error(FlydataCore::MysqlCompatibilityError, /mysql': SELECT, LOCK TABLES\n.*test_db': SELECT, LOCK TABLES/)
113
- end
114
- end
115
- context "with privileges for other databases only" do
17
+ describe "#check" do
18
+ subject { subject_object.check }
19
+ context "when all ports are accessible" do
116
20
  before do
117
- allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT REPLICATION CLIENT ON `test_db_01`.* TO 'test'@'host"},
118
- {"Grants for test"=>"GRANT LOCK TABLES ON `test_db_02`.* TO 'test'@'host"},
119
- {"Grants for test"=>"GRANT SELECT ON `text_db_03`.* TO 'test'@'host"}])
120
- end
121
- it do
122
- expect{subject.check_mysql_user_compat}.to raise_error(FlydataCore::MysqlCompatibilityError, /mysql': SELECT, RELOAD, LOCK TABLES, REPLICATION SLAVE, REPLICATION CLIENT\n.*test_db': SELECT, RELOAD, LOCK TABLES, REPLICATION SLAVE, REPLICATION CLIENT/)
123
- end
124
- end
125
-
126
- end
127
-
128
- describe "#check_mysql_binlog_retention" do
129
- context "on on-premise mysql server" do
130
- subject { MysqlCompatibilityCheck.new(default_data_port, default_mysql_cred) }
131
- context "where retention is below limit" do
132
- let(:client) { double('client') }
133
- before do
134
- allow(Mysql2::Client).to receive(:new).and_return(client)
135
- allow(client).to receive(:query).and_return([{"Variable_name" => "expire_logs_days", "Value" => 1}])
136
- allow(client).to receive(:close)
137
- allow(subject).to receive(:is_rds?).and_return(false)
138
- end
139
- it do
140
- expect{subject.check_mysql_binlog_retention}.to raise_error(FlydataCore::MysqlCompatibilityError, /expire_logs_days/)
141
- end
142
- end
143
- context "where retention is 0" do
144
- let(:client) { double('client') }
145
- before do
146
- allow(Mysql2::Client).to receive(:new).and_return(client)
147
- allow(client).to receive(:query).and_return([{"Variable_name" => "expire_logs_days", "Value" => 0}])
148
- allow(client).to receive(:close)
149
- allow(subject).to receive(:is_rds?).and_return(false)
150
- end
151
- it do
152
- expect{subject.check_mysql_binlog_retention}.to_not raise_error
153
- end
154
- end
155
- context "where retention is above limit" do
156
- let(:client) { double('client') }
157
- before do
158
- allow(Mysql2::Client).to receive(:new).and_return(client)
159
- allow(client).to receive(:query).and_return([{"expire_logs_days"=>11}])
160
- allow(client).to receive(:query).and_return([{"Variable_name" => "expire_logs_days", "Value" => 11}])
161
- allow(client).to receive(:close)
162
- allow(subject).to receive(:is_rds?).and_return(false)
163
- end
164
- it do
165
- expect{subject.check_mysql_binlog_retention}.to_not raise_error
166
- end
167
- end
168
- end
169
- context "on RDS" do
170
- subject { MysqlCompatibilityCheck.new(default_data_port, default_mysql_cred) }
171
- context "where retention period is nil" do
172
- let(:client) { double('client') }
173
- before do
174
- allow(Mysql2::Client).to receive(:new).and_return(client)
175
- allow(client).to receive(:query).with("SELECT @@expire_logs_days").and_return([{"@@expire_logs_days"=>0}])
176
- allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_return([{"name"=>"binlog retention hours", "value"=>nil}])
177
- allow(client).to receive(:close)
178
- allow(subject).to receive(:is_rds?).and_return(true)
179
- end
180
- it do
181
- expect{subject.check_mysql_binlog_retention}.to raise_error(FlydataCore::MysqlCompatibilityError, /rds_set_config/)
182
- end
21
+ allow(TCPSocket).to receive(:new).and_return(sock)
183
22
  end
184
- context "where retention period is too low" do
185
- let(:client) { double('client') }
186
- before do
187
- allow(Mysql2::Client).to receive(:new).and_return(client)
188
- allow(client).to receive(:query).with("SELECT @@expire_logs_days").and_return([{"@@expire_logs_days"=>0}])
189
- allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_return([{"name"=>"binlog retention hours", "value"=>4}])
190
- allow(client).to receive(:close)
191
- allow(subject).to receive(:is_rds?).and_return(true)
192
- end
193
- it do
194
- expect{subject.check_mysql_binlog_retention}.to raise_error(FlydataCore::MysqlCompatibilityError, /rds_set_config/)
195
- end
196
- end
197
- context "where retention period is over recommended limit" do
198
- let(:client) { double('client') }
199
- before do
200
- allow(Mysql2::Client).to receive(:new).and_return(client)
201
- allow(client).to receive(:query).with("SELECT @@expire_logs_days").and_return([{"@@expire_logs_days"=>0}])
202
- allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_return([{"name"=>"binlog retention hours", "value"=>120}])
203
- allow(client).to receive(:close)
204
- allow(subject).to receive(:is_rds?).and_return(true)
205
- end
206
- it do
207
- expect{subject.check_mysql_binlog_retention}.to_not raise_error
208
- end
209
- end
210
- context "where user has no access to rds configuration" do
211
- let(:client) { double('client') }
212
- before do
213
- allow(Mysql2::Client).to receive(:new).and_return(client)
214
- allow(client).to receive(:query).with("SELECT @@expire_logs_days").and_return([{"@@expire_logs_days"=>0}])
215
- allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_raise(Mysql2::Error, "execute command denied to user")
216
- allow(client).to receive(:close)
217
- allow(subject).to receive(:is_rds?).and_return(true)
218
- end
219
- it do
220
- expect{subject.check_mysql_binlog_retention}.to_not raise_error
221
- end
23
+ it "does nothing" do
24
+ subject
222
25
  end
223
26
  end
224
27
  end
225
-
226
- describe "#check_mysql_table_types" do
227
- let(:test_data_entry) do
228
- { "host" => "test",
229
- "port" => 1234,
230
- "username" => "test",
231
- "password" => "password",
232
- "database" => "test_db",
233
- "tables"=>["normal_table", "engine_table", "view_table"] }
234
- end
235
- let(:normal_table) { {"table_name"=>"normal_table", "table_type"=>"BASE TABLE", "engine"=>"InnoDB"} }
236
- let(:engine_table) { {"table_name"=>"engine_table", "table_type"=>"BASE TABLE", "engine"=>"MEMORY"} }
237
- let(:blackhole_table) { {"table_name"=>"blackhole_table", "table_type"=>"BASE TABLE", "engine"=>"BLACKHOLE"} }
238
- let(:view) { {"table_name"=>"view_table", "table_type"=>"VIEW", "engine"=>nil} }
239
- let(:client) { double('client') }
240
- let(:subject_object) { MysqlCompatibilityCheck.new(default_data_port,test_data_entry, {}) }
241
- let(:error) { FlydataCore::MysqlCompatibilityError }
242
- let(:base_error_msg) { "FlyData does not support VIEW and MEMORY,BLACKHOLE STORAGE ENGINE table. Remove following tables from data entry: %s" }
243
- subject { subject_object.check_mysql_table_types }
244
- before do
245
- allow(Mysql2::Client).to receive(:new).and_return(client)
246
- allow(client).to receive(:query).and_return(table_list)
247
- allow(client).to receive(:escape).and_return("aaa")
248
- allow(client).to receive(:close)
249
- end
250
- context "where data entry has VIEW and MEMORY engine table" do
251
- let(:error_msg) { base_error_msg % engine_table['table_name'] + ', ' + view['table_name'] }
252
- let(:table_list) { [ engine_table, view ] }
253
- it { expect{subject}.to raise_error(error, /#{error_msg}/) }
254
- end
255
- context "where data entry has MEMORY engine table" do
256
- let(:error_msg) { base_error_msg % engine_table['table_name'] }
257
- let(:table_list) { [ engine_table ] }
258
- it { expect{subject}.to raise_error(error, /#{error_msg}/) }
259
- end
260
- context "where data entry has BLACKHOLE engine table" do
261
- let(:error_msg) { base_error_msg % blackhole_table['table_name'] }
262
- let(:table_list) { [ blackhole_table ] }
263
- it { expect{subject}.to raise_error(error, /#{error_msg}/) }
264
- end
265
- context "where data entry has the VIEW" do
266
- let(:error_msg) { base_error_msg % view['table_name'] }
267
- let(:table_list) { [ view ] }
268
- it { expect{subject}.to raise_error(error, /#{error_msg}/) }
269
- end
270
- context "where data entry does not have either VIEW and ENGINE table" do
271
- let(:table_list) { [ normal_table ] }
272
- it { expect{subject}.to_not raise_error }
273
- end
274
- end
275
-
276
- describe "#check_rds_master_status" do
277
- let(:client) { double('client') }
278
- let(:subject_object) { MysqlCompatibilityCheck.new(default_data_port, default_mysql_cred) }
279
- subject { subject_object.check_rds_master_status }
280
- let(:master_status) { [] }
281
-
282
- before do
283
- allow(Mysql2::Client).to receive(:new).and_return(client)
284
- allow(client).to receive(:query).and_return(master_status)
285
- allow(client).to receive(:close)
286
- end
287
-
288
- context 'when host is rds' do
28
+ describe '#check_outgoing_ports' do
29
+ subject { subject_object.check_outgoing_ports }
30
+ context "when a port access fails" do
289
31
  before do
290
- default_mysql_cred['host'] = 'rdrss.xxyyzz.rds.amazonaws.com'
32
+ allow(TCPSocket).to receive(:new).and_raise(Errno::ETIMEDOUT)
291
33
  end
292
-
293
- context "where backup retention period is not set" do
294
- let(:master_status) { [] }
295
- it { expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /Backup Retention Period/) }
296
- end
297
-
298
- context "where backup retention period is set" do
299
- let(:master_status) do
300
- [{
301
- 'File' => 'mysql-bin-changelog.026292',
302
- 'Position' => '31300',
303
- 'Binlog_Do_DB' => '',
304
- 'Binlog_Ignore_DB' => '',
305
- 'Executed_Gtid_Set' => '',
306
- }]
307
- end
308
- it { expect{subject}.not_to raise_error }
34
+ it do
35
+ expect{subject}.to raise_error(FlydataCore::AgentCompatibilityError, /ports/)
309
36
  end
310
37
  end
311
38
  end