flydata 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
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