flydata 0.2.29 → 0.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe45a465ce979b91e41ebfa3993cdd0b136de6bf
4
- data.tar.gz: a929728a274a8fa8af440a77333f155c1a8d1442
3
+ metadata.gz: acad4ae7202b7fc525478de91925ef4ee0a24fdd
4
+ data.tar.gz: 63cf3ac658afa5be37ed7b2a85be476de280c56f
5
5
  SHA512:
6
- metadata.gz: 37477dc1882b8faf6ad42459de4de796e0b3f78151d3ac095547fc9c9817c05ae72251962e71fb21f31d1d6eb9ea86fa9b2580b15b9106351030c8ef7d1f687a
7
- data.tar.gz: 70832d4d94503232338e09e3f3f7eecceea1a96dab7d62b2cd17418c97dcf6b31c750918040c4e177b9124961e38deab6156f8221126939b6c8d43d7df820fe6
6
+ metadata.gz: fb7391185d4f9797817f0628ec46f5b9cce0ef1680fb843d0181f97dd18450c059d6b6614b7ef98e8ee88dfac171933561e448a2ca1b403665563b27b5d005a8
7
+ data.tar.gz: 7407fd538bbd7678ae19d5ddaa9dcc5b13159555b8c7b82a788c821fb70855594f49848eb141ec7a44982eb164dd63738e63a9714ed132b5b02ff2caab948d4f
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.29
1
+ 0.2.30
@@ -196,10 +196,11 @@ module FlydataCore
196
196
 
197
197
  # Log an error for the given exception. It does not log a RetryableError if
198
198
  # retry_count is less than retry_alert_limit.
199
- # It returns the original exception of a RetryableError it has one.
199
+ # It logs a DataDeliverError as INFO.
200
+ # It returns the original exception of a RetryableError if it has one.
200
201
  # This method requires method 'logger' which returns a Ruby Logger instance or
201
202
  # a compatible one.
202
- def log_retryable_error(exception, message = "unknown error occured.", retry_count = -1,
203
+ def log_flydata_error(exception, message = nil, retry_count = -1,
203
204
  retry_alert_limit = 13)
204
205
  is_retryable_error = false
205
206
  if (exception.kind_of?(FlydataCore::RetryableError))
@@ -209,9 +210,19 @@ module FlydataCore
209
210
  end
210
211
 
211
212
  unless is_retryable_error && retry_count < retry_alert_limit
212
- log_error_with_backtrace(message, retry_count: retry_count, error: exception)
213
+ if exception.kind_of?(FlydataCore::DataDeliveryError)
214
+ # Do not log a DataDeliveryError as ERROR. It's a user error which
215
+ # should not cause a system alert
216
+ message ||= "a DataDeliveryError occured."
217
+ log_debug(message, {retry_count: retry_count, error: exception}, {backtrace: true})
218
+ else
219
+ message ||= "an unknown error occured."
220
+ log_error_with_backtrace(message, retry_count: retry_count, error: exception)
221
+ end
213
222
  end
214
223
  exception
215
224
  end
225
+ # log_retryable_error for backward compatibility
226
+ alias_method :log_retryable_error, :log_flydata_error
216
227
  end
217
228
  end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'flydata-core/logger'
3
+
4
+ module FlydataCore
5
+
6
+ describe Logger do
7
+ subject { d = double("logger"); d.extend Logger; d}
8
+ shared_examples "log_flydata_error" do
9
+ let(:message) { "my error" }
10
+ context "with a regular exception" do
11
+ let(:exception) { StandardError.new("stde") }
12
+ it do
13
+ expect(subject).to receive(:log_error_with_backtrace).with(message, {retry_count: -1, error: exception})
14
+
15
+ expect(subject.send(method, exception, message)).to eq(exception)
16
+ end
17
+ context "default message" do
18
+ it do
19
+ expect(subject).to receive(:log_error_with_backtrace).with("an unknown error occured.", {retry_count: -1, error: exception})
20
+
21
+ expect(subject.send(method, exception)).to eq(exception)
22
+ end
23
+ end
24
+ end
25
+ context "with a RetryableError" do
26
+ let(:original_exception) { StandardError.new("this will go away soon") }
27
+ let(:exception) { RetryableError.new(original_exception) }
28
+ it do
29
+ expect(subject).to receive(:log_debug).never
30
+ expect(subject).to receive(:log_error_with_backtrace).never
31
+
32
+ expect(subject.send(method, exception, message)).to eq original_exception
33
+ end
34
+ end
35
+ context "with a DataDeliveryError" do
36
+ let(:exception) { TableMissingError.new("tse") }
37
+ it do
38
+ expect(subject).to receive(:log_debug).with(message, {retry_count: -1, error: exception}, {backtrace: true})
39
+
40
+ expect(subject.send(method, exception, message)).to eq exception
41
+ end
42
+ context "default message" do
43
+ it do
44
+ expect(subject).to receive(:log_debug).with("a DataDeliveryError occured.", {retry_count: -1, error: exception}, {backtrace: true})
45
+
46
+ expect(subject.send(method, exception)).to eq exception
47
+ end
48
+ end
49
+ end
50
+ context "with a RetryableError wrapping a DataDeliveryError" do
51
+ let(:original_exception) { TableMissingError.new("no table info") }
52
+ let(:exception) { RetryableError.new(original_exception) }
53
+ it do
54
+ expect(subject).to receive(:log_debug).never
55
+ expect(subject).to receive(:log_error_with_backtrace).never
56
+
57
+ expect(subject.send(method, exception, message)).to eq original_exception
58
+ end
59
+ end
60
+ end
61
+ describe "#log_flydata_error" do
62
+ let(:method) { :log_flydata_error }
63
+ it_behaves_like "log_flydata_error"
64
+ end
65
+
66
+ describe "#log_retryable_error" do
67
+ let(:method) { :log_retryable_error }
68
+ it_behaves_like "log_flydata_error"
69
+ end
70
+ end
71
+
72
+ end
data/flydata.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: flydata 0.2.29 ruby lib
5
+ # stub: flydata 0.2.30 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "flydata"
9
- s.version = "0.2.29"
9
+ s.version = "0.2.30"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Koichi Fujikawa", "Masashi Miyazaki", "Matthew Luu", "Mak Inada", "Sriram NS"]
14
- s.date = "2015-01-23"
14
+ s.date = "2015-01-26"
15
15
  s.description = "FlyData Agent"
16
16
  s.email = "sysadmin@flydata.com"
17
17
  s.executables = ["fdmysqldump", "flydata", "serverinfo"]
@@ -48,6 +48,7 @@ Gem::Specification.new do |s|
48
48
  "flydata-core/lib/flydata-core/table_def/mysql_table_def.rb",
49
49
  "flydata-core/lib/flydata-core/table_def/redshift_table_def.rb",
50
50
  "flydata-core/lib/flydata-core/thread_context.rb",
51
+ "flydata-core/spec/logger_spec.rb",
51
52
  "flydata-core/spec/spec_helper.rb",
52
53
  "flydata-core/spec/table_def/mysql_table_def_spec.rb",
53
54
  "flydata-core/spec/table_def/mysql_to_redshift_table_def_spec.rb",
@@ -121,6 +122,7 @@ Gem::Specification.new do |s|
121
122
  "lib/flydata/proxy.rb",
122
123
  "lib/flydata/sync_file_manager.rb",
123
124
  "lib/flydata/util/encryptor.rb",
125
+ "lib/flydata/util/mysql_util.rb",
124
126
  "spec/fluent_plugins_spec_helper.rb",
125
127
  "spec/fly_data_model_spec.rb",
126
128
  "spec/flydata/api/data_entry_spec.rb",
@@ -140,6 +142,7 @@ Gem::Specification.new do |s|
140
142
  "spec/flydata/parser/mysql/dump_parser_spec.rb",
141
143
  "spec/flydata/sync_file_manager_spec.rb",
142
144
  "spec/flydata/util/encryptor_spec.rb",
145
+ "spec/flydata/util/mysql_util_spec.rb",
143
146
  "spec/flydata_spec.rb",
144
147
  "spec/spec_helper.rb",
145
148
  "tmpl/redshift_mysql_data_entry.conf.tmpl"
@@ -30,6 +30,8 @@ module Flydata
30
30
  dp = flydata.data_port.get
31
31
  AgentCompatibilityCheck.new(dp).check
32
32
 
33
+ Flydata::Command::Sync.new.try_mysql_sync
34
+
33
35
  # Start sender(fluentd) process
34
36
  log_info_stdout("Starting sender process.") unless options[:quiet]
35
37
  Dir.chdir(FLYDATA_HOME){
@@ -84,16 +86,17 @@ EOF
84
86
  def restart(options = {})
85
87
  if process_exist?
86
88
  log_info_stdout('Restarting sender process.') unless options[:quiet]
87
- if Kernel.system("kill -HUP `cat #{pid_file}`")
88
- log_info_stdout('Done.') unless options[:quiet]
89
- return true
90
- else
91
- raise 'Something has gone wrong..'
89
+ begin
90
+ stop(options)
91
+ rescue
92
+ log_out_stderr 'Something has gone wrong... force stopping the FlyData process...'
93
+ kill_all(options)
92
94
  end
93
95
  else
94
96
  log_info_stdout("Process doesn't exist.") unless options[:quiet]
95
- start(options)
96
97
  end
98
+ start(options)
99
+ say('Process started.') unless options[:quiet]
97
100
  end
98
101
  def status
99
102
  if process_exist?
@@ -106,7 +109,7 @@ EOF
106
109
  # Returns true if the process is running
107
110
  `[ -f #{pid_file} ] && pgrep -P \`cat #{pid_file}\``.to_i > 0
108
111
  end
109
- def kill_all(optiosn = {})
112
+ def kill_all(options = {})
110
113
  if Kernel.system("ps ax | grep 'flydata' | grep -v grep | awk '{print \"kill \" $1}' | sh")
111
114
  log_info_stdout("Done.") unless options[:quiet]
112
115
  return true
@@ -7,6 +7,7 @@ require 'flydata/sync_file_manager'
7
7
  require 'flydata/compatibility_check'
8
8
  require 'flydata/output/forwarder'
9
9
  require 'flydata/parser/mysql/dump_parser'
10
+ require 'flydata/util/mysql_util'
10
11
  require 'flydata-core/table_def'
11
12
  #require 'ruby-prof'
12
13
 
@@ -21,6 +22,11 @@ module Flydata
21
22
  STATUS_WAITING = 'WAITING'
22
23
  STATUS_COMPLETE = 'COMPLETE'
23
24
 
25
+ attr_reader :initial_sync, :new_tables, :ddl_tables
26
+
27
+ class SyncDataEntryError < StandardError
28
+ end
29
+
24
30
  def self.slop
25
31
  Slop.new do
26
32
  on 'c', 'skip-cleanup', 'Skip server cleanup'
@@ -44,11 +50,61 @@ module Flydata
44
50
  exit 1
45
51
  end
46
52
 
47
- de = retrieve_data_entry
48
- de = load_sync_info(override_tables(de, tables))
53
+ handle_mysql_sync(tables)
54
+
55
+ unless opts.no_flydata_start?
56
+ log_info_stdout("Starting FlyData Agent...")
57
+ Flydata::Command::Sender.new.start(quiet: true)
58
+ log_info_stdout(" -> Done")
59
+ end
60
+
61
+ dashboard_url = "#{flydata.flydata_api_host}/dashboard"
62
+ redshift_console_url = "#{flydata.flydata_api_host}/redshift_clusters/query/new"
63
+ last_message = ALL_DONE_MESSAGE_TEMPLATE % [redshift_console_url, dashboard_url]
64
+ log_info_stdout(last_message)
65
+ end
66
+
67
+ def try_mysql_sync
68
+ handle_mysql_sync()
69
+ rescue SyncDataEntryError
70
+ return
71
+ end
72
+
73
+ def handle_mysql_sync(tables=nil)
74
+ de = retrieve_sync_data_entry
75
+
76
+ set_current_tables(de)
77
+ verify_input_tables(tables, de['mysql_data_entry_preference']['tables'])
78
+
79
+ unless @new_tables.empty?
80
+ say("We've noticed that these tables have not been synced yet: #{@new_tables.join(", ")}")
81
+ unless @ddl_tables.empty?
82
+ say(" WARNING: We've noticed that at least one of these tables have not had their DDL generated yet.")
83
+ say(" We recommend you run our 'flydata sync:generate_table_ddl > create_table.sql'")
84
+ say(" to generate SQL to run on Redshift to create the correct tables")
85
+ say(" Without running this sql on your Redshift cluster, there may be issues with your data")
86
+ end
87
+ if ask_yes_no("Do you want to run initial sync on all of these tables now?")
88
+ tables = @initial_sync ? [] : @new_tables
89
+ initial_sync(tables, de)
90
+ else
91
+ #If generate_table_ddl has not been run for these tables, warn user
92
+ unless @ddl_tables.empty?
93
+ say(" You can generate DDL SQL for your new tables by running this command")
94
+ say(" $> flydata sync:generate_table_ddl > create_table.sql")
95
+ end
96
+ puts "Without syncing these tables, we cannot start the flydata process"
97
+ raise "Please try again"
98
+ end
99
+ end
100
+ end
101
+ def initial_sync(tables, de=nil)
102
+ de = retrieve_sync_data_entry unless de
103
+ de = load_sync_info(de,tables)
49
104
  validate_initial_sync_status(de, tables)
50
- flush_buffer_and_stop unless de['mysql_data_entry_preference']['initial_sync']
105
+ flush_buffer_and_stop unless @initial_sync
51
106
  sync_mysql_to_redshift(de)
107
+ complete
52
108
  end
53
109
 
54
110
  def self.slop_flush
@@ -76,7 +132,10 @@ module Flydata
76
132
  sender.flush_client_buffer # TODO We should rather delete buffer files
77
133
  sender.stop
78
134
 
79
- de = retrieve_data_entry
135
+ de = retrieve_sync_data_entry
136
+ all_tables = de['mysql_data_entry_preference']['tables']
137
+ verify_input_tables(tables, all_tables)
138
+
80
139
  wait_for_server_buffer
81
140
  cleanup_sync_server(de, tables) unless opts.client?
82
141
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
@@ -88,9 +147,10 @@ module Flydata
88
147
  sync_fm.table_position_file_paths(*tables),
89
148
  sync_fm.table_binlog_pos_paths(*tables),
90
149
  sync_fm.table_binlog_pos_init_paths(*tables),
91
- sync_fm.table_rev_file_paths(*tables)
150
+ sync_fm.table_rev_file_paths(*tables),
151
+ sync_fm.table_ddl_file_paths(*tables)
92
152
  ]
93
- delete_files << sync_fm.binlog_path if tables.empty?
153
+ delete_files << sync_fm.binlog_path if tables.empty? or all_tables.empty?
94
154
  delete_files.flatten.each do |path|
95
155
  FileUtils.rm(path) if File.exists?(path)
96
156
  end
@@ -129,7 +189,7 @@ module Flydata
129
189
  end
130
190
 
131
191
  def check
132
- de = retrieve_data_entry
192
+ de = retrieve_sync_data_entry
133
193
  retry_on(RestClient::Exception) do
134
194
  status = do_check(de)
135
195
  if status['complete']
@@ -142,7 +202,7 @@ module Flydata
142
202
 
143
203
  # skip initial sync
144
204
  def skip
145
- de = retrieve_data_entry
205
+ de = retrieve_sync_data_entry
146
206
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
147
207
  binlog_path = sync_fm.binlog_path
148
208
  sync_fm.close
@@ -157,18 +217,39 @@ module Flydata
157
217
  on 'c', 'ctl-only', 'Only generate FlyData Control definitions'
158
218
  on 'y', 'yes', 'Skip command prompt assuming yes to all questions. Use this for batch operation.'
159
219
  on 's', 'skip-primary-key-check', 'Skip primary key check when generating DDL'
220
+ on 'all-tables', 'Generate all table schema'
160
221
  end
161
222
  end
162
223
 
163
224
  def generate_table_ddl(*tables)
164
- de = retrieve_data_entry
225
+ de = retrieve_sync_data_entry
165
226
  dp = flydata.data_port.get
166
227
  Flydata::MysqlCompatibilityCheck.new(dp, de['mysql_data_entry_preference']).check
167
- do_generate_table_ddl(override_tables(de, tables))
228
+ do_generate_table_ddl(de, tables)
168
229
  end
169
230
 
170
231
  private
171
232
 
233
+ def verify_input_tables(input_tables, all_tables)
234
+ return unless input_tables
235
+ inval_table = []
236
+ input_tables.each do |tab|
237
+ inval_table << tab unless all_tables.include?(tab)
238
+ end
239
+ raise "These tables are not registered tables: #{inval_table.join(", ")}" unless inval_table.empty?
240
+ end
241
+
242
+ def set_current_tables(de)
243
+ sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
244
+ full_tables = de['mysql_data_entry_preference']['tables']
245
+
246
+ @new_tables = sync_fm.get_new_table_list(full_tables, "pos")
247
+ @ddl_tables = sync_fm.get_new_table_list(full_tables, "generated_ddl")
248
+
249
+ @initial_sync = (@new_tables == full_tables)
250
+ sync_fm.close
251
+ end
252
+
172
253
  def validate_initial_sync_status(de, tables)
173
254
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
174
255
  dump_pos_info = sync_fm.load_dump_pos
@@ -181,17 +262,19 @@ module Flydata
181
262
  end
182
263
  end
183
264
 
184
- def retrieve_data_entry
185
- de = retrieve_data_entries.first
186
- raise "There are no data entry." unless de
265
+ def retrieve_sync_data_entry
266
+ de = retrieve_data_entries.first unless de
267
+ raise "There are no data entries." unless de
187
268
  case de['type']
188
269
  when 'RedshiftMysqlDataEntry'
189
270
  mp = de['mysql_data_entry_preference']
190
271
  if mp['tables_append_only']
191
- mp['tables'] = (mp['tables'].split(",") + mp['tables_append_only'].split(",")).uniq.join(",")
272
+ mp['tables'] = (mp['tables'].split(",") + mp['tables_append_only'].split(",")).uniq
273
+ else
274
+ mp['tables'] = mp['tables'].split(",").uniq
192
275
  end
193
276
  else
194
- raise "No supported data entry. Only mysql-redshift sync is supported."
277
+ raise SyncDataEntryError, "No supported data entry. Only mysql-redshift sync is supported."
195
278
  end
196
279
  de
197
280
  end
@@ -223,8 +306,7 @@ module Flydata
223
306
  log_info_stdout(message) unless message.nil? || message.empty?
224
307
  end
225
308
 
226
- DDL_DUMP_CMD_TEMPLATE = "MYSQL_PWD=\"%s\" mysqldump --protocol=tcp -d -h %s -P %s -u %s %s %s"
227
- def do_generate_table_ddl(de)
309
+ def do_generate_table_ddl(de, tables=[])
228
310
  if `which mysqldump`.empty?
229
311
  raise "mysqldump is not installed. mysqldump is required to run the command"
230
312
  end
@@ -234,20 +316,25 @@ module Flydata
234
316
  schema_name = (de['schema_name'] || nil)
235
317
 
236
318
  mp = de['mysql_data_entry_preference']
237
- params = []
238
- params << mp['password']
239
- if !mp['host'].empty? then params << mp['host'] else raise "MySQL `host` is neither defined in the data entry nor the local config file" end
240
- params << (mp['port'] or '3306')
241
- if !mp['username'].empty? then params << mp['username'] else raise "MySQL `username` is neither defined in the data entry nor the local config file" end
242
- if !mp['database'].empty? then params << mp['database'] else raise "`database` is neither defined in the data entry nor the local config file" end
243
- if !mp['tables'].empty? then params << mp['tables'].gsub(/,/, ' ') else raise "`tables` (or `tables_append_only`) is neither defined in the data entry nor the local config file" end
244
319
 
245
- command = DDL_DUMP_CMD_TEMPLATE % params
320
+ set_current_tables(de)
321
+
322
+ tables = opts.all_tables? ? mp['tables'] : (tables.empty? ? @new_tables : tables)
323
+ raise "There are no valid unsynced tables, if you want to just get ddl for all tables, please run \`flydata sync:generate_table_ddl --all-tables\`" if tables.empty?
324
+
325
+ %w(host username database).each do |conf_name|
326
+ raise "MySQL `#{conf_name}` is neither defined in the data entry nor the local config file" if mp[conf_name].to_s.empty?
327
+ end
328
+ if tables.empty?
329
+ raise "`tables` (or `tables_append_only`) is neither defined in the data entry nor the local config file"
330
+ end
331
+
332
+ command = Util::MysqlUtil.generate_mysql_ddl_dump_cmd(mp.merge(tables: tables))
246
333
 
247
334
  Open3.popen3(command) do |stdin, stdout, stderr|
248
335
  stdin.close
249
336
  stdout.set_encoding("utf-8") # mysqldump output must be in UTF-8
250
- create_flydata_ctl_table = mp['initial_sync']
337
+ create_flydata_ctl_table = @initial_sync
251
338
  while !stdout.eof?
252
339
  begin
253
340
  mysql_tabledef = FlydataCore::TableDef::MysqlTableDef.create(stdout, skip_primary_key_check: opts.skip_primary_key_check?)
@@ -281,6 +368,11 @@ module Flydata
281
368
  end
282
369
  log_error_stderr("Please fix the above error(s) to try to sync those table(s) or contact us for further help.")
283
370
  end
371
+ tables_without_error = tables - error_list.inject([]){|arr, err| arr << err[:table] if err[:table]}
372
+
373
+ sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
374
+ sync_fm.mark_generated_tables(tables_without_error)
375
+ sync_fm.close
284
376
  end
285
377
 
286
378
  def sync_mysql_to_redshift(de)
@@ -288,7 +380,7 @@ module Flydata
288
380
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
289
381
 
290
382
  # Check client condition
291
- if File.exists?(sync_fm.binlog_path) and de['mysql_data_entry_preference']['initial_sync']
383
+ if File.exists?(sync_fm.binlog_path) and @initial_sync
292
384
  raise "Already synchronized. If you want to do initial sync, run 'flydata sync:reset'"
293
385
  end
294
386
 
@@ -296,14 +388,11 @@ module Flydata
296
388
  unless Flydata::Preference::DataEntryPreference.conf_exists?(de)
297
389
  Flydata::Command::Conf.new.copy_templates
298
390
  end
299
-
300
391
  generate_mysqldump(de, sync_fm, !opts.dump_stream?) do |mysqldump_io, db_bytesize|
301
- sync_fm.save_sync_info(de['mysql_data_entry_preference']['initial_sync'], de['mysql_data_entry_preference']['tables'])
392
+ sync_fm.save_sync_info(@initial_sync, de['mysql_data_entry_preference']['tables'])
302
393
  parse_mysqldump_and_send(mysqldump_io, dp, de, sync_fm, db_bytesize)
303
394
  end
304
395
  wait_for_mysqldump_processed(dp, de, sync_fm)
305
- sync_fm.close
306
- complete
307
396
  end
308
397
 
309
398
  def generate_mysqldump(de, sync_fm, file_dump = true, &block)
@@ -333,12 +422,12 @@ module Flydata
333
422
 
334
423
  confirmation_text = <<-EOM
335
424
 
336
- FlyData Sync will start synchonizing the following database tables
425
+ FlyData Sync will start synchronizing the following database tables
337
426
  host: #{de['mysql_data_entry_preference']['host']}
338
427
  port: #{de['mysql_data_entry_preference']['port']}
339
428
  username: #{de['mysql_data_entry_preference']['username']}
340
429
  database: #{de['mysql_data_entry_preference']['database']}
341
- tables: #{tables}#{data_servers}
430
+ tables: #{tables.join(", ")}#{data_servers}
342
431
  EOM
343
432
  confirmation_text << <<-EOM if file_dump
344
433
  dump file: #{fp}
@@ -349,6 +438,8 @@ EOM
349
438
  log_info confirmation_text.strip
350
439
 
351
440
  if ask_yes_no('Start Sync?')
441
+ log_info_stdout("Checking MySQL server connection and configuration...")
442
+ Flydata::MysqlCompatibilityCheck.new(dp, de['mysql_data_entry_preference'], dump_dir: fp, backup_dir: sync_fm.backup_dir).check
352
443
  log_info_stdout("Checking database size...")
353
444
 
354
445
  db_bytesize = Flydata::Parser::Mysql::DatabaseSizeCheck.new(de['mysql_data_entry_preference']).get_db_bytesize
@@ -370,7 +461,6 @@ EOM
370
461
 
371
462
  log_info_stdout("Exporting data from database.")
372
463
  log_info_stdout("This process can take hours depending on data size and load on your database. Please be patient...")
373
- Flydata::MysqlCompatibilityCheck.new(dp, de['mysql_data_entry_preference'], dump_dir: fp, backup_dir: sync_fm.backup_dir).check
374
464
  if file_dump
375
465
  Flydata::Parser::Mysql::MysqlDumpGeneratorNoMasterData.
376
466
  new(de['mysql_data_entry_preference']).dump(fp)
@@ -448,7 +538,7 @@ EOM
448
538
  log_info_stdout("Resuming... Last processed table: #{option[:table_name]}")
449
539
  else
450
540
  #If its a new sync, ensure server side resources are clean
451
- cleanup_sync_server(de, de['mysql_data_entry_preference']['tables'].split(',')) unless opts.skip_cleanup?
541
+ cleanup_sync_server(de, de['mysql_data_entry_preference']['tables']) unless opts.skip_cleanup?
452
542
  end
453
543
  log_info_stdout("Sending data to FlyData Server...")
454
544
 
@@ -517,7 +607,7 @@ EOM
517
607
  binlog_pos = dump_pos_info[:binlog_pos]
518
608
 
519
609
  wait_for_server_data_processing
520
- tables = de['mysql_data_entry_preference']['tables'].split(',').join(' ')
610
+ tables = de['mysql_data_entry_preference']['tables']
521
611
  sync_fm.save_table_binlog_pos(tables, binlog_pos)
522
612
  sync_fm.save_dump_pos(STATUS_COMPLETE, '', -1, binlog_pos)
523
613
  end
@@ -539,26 +629,17 @@ Thank you for using FlyData!
539
629
  EOM
540
630
 
541
631
  def complete
542
- de = load_sync_info(retrieve_data_entry)
632
+ de = load_sync_info(retrieve_sync_data_entry)
543
633
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
544
634
  info = sync_fm.load_dump_pos
545
635
  if info[:status] == STATUS_COMPLETE
546
- if de['mysql_data_entry_preference']['initial_sync']
636
+ if @initial_sync
547
637
  sync_fm.save_binlog(info[:binlog_pos])
548
638
  end
549
- sync_fm.install_table_binlog_files(de['mysql_data_entry_preference']['tables'].split(','))
550
- sync_fm.reset_table_position_files(de['mysql_data_entry_preference']['tables'].split(','))
639
+ sync_fm.install_table_binlog_files(de['mysql_data_entry_preference']['tables'])
640
+ sync_fm.reset_table_position_files(de['mysql_data_entry_preference']['tables'])
551
641
  sync_fm.delete_dump_file
552
642
  sync_fm.backup_dump_dir
553
- unless opts.no_flydata_start?
554
- log_info_stdout("Starting FlyData Agent...")
555
- Flydata::Command::Sender.new.start(quiet: true)
556
- log_info_stdout(" -> Done")
557
- end
558
- dashboard_url = "#{flydata.flydata_api_host}/dashboard"
559
- redshift_console_url = "#{flydata.flydata_api_host}/redshift_clusters/query/new"
560
- last_message = ALL_DONE_MESSAGE_TEMPLATE % [redshift_console_url, dashboard_url]
561
- log_info_stdout(last_message)
562
643
  else
563
644
  raise "Initial sync status is not complete. Try running 'flydata sync'."
564
645
  end
@@ -573,20 +654,14 @@ Thank you for using FlyData!
573
654
  h.to_json
574
655
  end
575
656
 
576
- def override_tables(de, tables)
577
- de['mysql_data_entry_preference']['initial_sync'] = tables.empty?
578
- if ! de['mysql_data_entry_preference']['initial_sync']
579
- de['mysql_data_entry_preference']['tables'] = tables.join(',')
580
- end
581
- de
582
- end
583
-
584
- def load_sync_info(de)
657
+ def load_sync_info(de ,additional_tables=[])
585
658
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
586
659
  mp = de['mysql_data_entry_preference']
587
660
  unless (rs = sync_fm.load_sync_info).nil?
588
- mp['initial_sync'] = rs[:initial_sync]
661
+ @initial_sync = rs[:initial_sync]
589
662
  mp['tables'] = rs[:tables]
663
+ else
664
+ mp['tables'] = additional_tables unless @initial_sync
590
665
  end
591
666
  sync_fm.close
592
667
  de
@@ -1,4 +1,6 @@
1
+ require 'mysql2'
1
2
  require 'flydata/command_logger'
3
+ require 'flydata/util/mysql_util'
2
4
 
3
5
  module Flydata
4
6
 
@@ -118,7 +120,7 @@ module Flydata
118
120
  end
119
121
 
120
122
  def check_mysql_protocol_tcp_compat
121
- query = "MYSQL_PWD=\"#{@db_opts[:password]}\" mysql -u #{@db_opts[:username]} -h #{@db_opts[:host]} -P #{@db_opts[:port]} #{@db_opts[:database]} -e \"SHOW GRANTS;\" --protocol=tcp"
123
+ query = Util::MysqlUtil.generate_mysql_show_grants_cmd(@db_opts)
122
124
 
123
125
  Open3.popen3(query) do |stdin, stdout, stderr|
124
126
  stdin.close
@@ -9,6 +9,7 @@ lib = File.absolute_path(File.dirname(__FILE__) + '/../..')
9
9
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
10
10
  require 'flydata'
11
11
  require 'flydata/sync_file_manager'
12
+ require 'flydata/util/mysql_util'
12
13
 
13
14
  require_relative 'preference'
14
15
  require_relative 'mysql/binlog_position_file'
@@ -63,7 +64,7 @@ class MysqlBinlogFlydataInput < MysqlBinlogInput
63
64
  load_custom_conf
64
65
  $log.info "mysql host:\"#{@host}\" port:\"#{@port}\" username:\"#{@username}\" database:\"#{@database}\" tables:\"#{@tables}\" tables_append_only:\"#{tables_append_only}\""
65
66
  $log.info "mysql client version: #{`mysql -V`}"
66
- server_version = `echo 'select version();' | MYSQL_PWD="#{@password}" mysql -h #{@host} --port #{@port} -u #{@username} 2>/dev/null`
67
+ server_version = `echo 'select version();' | #{Flydata::Util::MysqlUtil.generate_mysql_cmd(host: @host, port: @port, username: @username, password: @password)} 2>/dev/null`
67
68
  $log.info "mysql server version: #{server_version}"
68
69
 
69
70
  @tables = @tables.split(/,\s*/)
@@ -1,4 +1,5 @@
1
1
  require 'fiber'
2
+ require 'flydata/util/mysql_util'
2
3
 
3
4
  module Flydata
4
5
  module Parser
@@ -39,26 +40,20 @@ module Flydata
39
40
  end
40
41
 
41
42
  class MysqlDumpGenerator
42
- # host, port, username, password, database, tables
43
- MYSQL_DUMP_CMD_TEMPLATE = "MYSQL_PWD=\"%s\" mysqldump --default-character-set=utf8 --protocol=tcp -h %s -P %s -u%s --skip-lock-tables --single-transaction --hex-blob %s %s %s"
44
- EXTRA_MYSQLDUMP_PARAMS = ""
45
43
  def initialize(conf)
46
- tables = if conf['tables']
47
- conf['tables'].split(',').join(' ')
48
- else
49
- ''
50
- end
51
- @dump_cmd = MYSQL_DUMP_CMD_TEMPLATE %
52
- [conf['password'], conf['host'], conf['port'], conf['username'], self.class::EXTRA_MYSQLDUMP_PARAMS, conf['database'], tables]
44
+ @dump_cmd = generate_dump_cmd(conf)
53
45
  @db_opts = [:host, :port, :username, :password, :database].inject({}) {|h, sym| h[sym] = conf[sym.to_s]; h}
54
46
  end
55
47
  def dump(file_path)
56
48
  raise "subclass must implement the method"
57
49
  end
50
+
51
+ def generate_dump_cmd(conf)
52
+ raise "subclass must implement the method"
53
+ end
58
54
  end
59
55
 
60
56
  class MysqlDumpGeneratorMasterData < MysqlDumpGenerator
61
- EXTRA_MYSQLDUMP_PARAMS = "--flush-logs --master-data=2"
62
57
  def dump(file_path)
63
58
  cmd = "#{@dump_cmd} -r #{file_path}"
64
59
  o, e, s = Open3.capture3(cmd)
@@ -78,10 +73,13 @@ module Flydata
78
73
  end
79
74
  true
80
75
  end
76
+
77
+ def generate_dump_cmd(conf)
78
+ Util::MysqlUtil.generate_mysqldump_with_master_data_cmd(conf)
79
+ end
81
80
  end
82
81
 
83
82
  class MysqlDumpGeneratorNoMasterData < MysqlDumpGenerator
84
- EXTRA_MYSQLDUMP_PARAMS = ""
85
83
  CHANGE_MASTER_TEMPLATE = <<EOS
86
84
  --
87
85
  -- Position to start replication or point-in-time recovery from
@@ -164,6 +162,10 @@ EOS
164
162
  end
165
163
  end
166
164
 
165
+ def generate_dump_cmd(conf)
166
+ Util::MysqlUtil.generate_mysqldump_without_master_data_cmd(conf)
167
+ end
168
+
167
169
  private
168
170
 
169
171
  def open_file_stream(file_path, &block)
@@ -596,7 +598,7 @@ EOT
596
598
 
597
599
  def initialize(de_conf)
598
600
  @de_conf = de_conf
599
- @tables = de_conf['tables'].split(',')
601
+ @tables = de_conf['tables']
600
602
  @query = SIZE_CHECK_QUERY % [@tables.collect{|t| "'#{t}'"}.join(',')]
601
603
  end
602
604
 
@@ -40,6 +40,26 @@ module Flydata
40
40
  state: items[5], substate: items[6], mysql_table: mysql_table}
41
41
  end
42
42
 
43
+ def mark_generated_tables(tables)
44
+ table_positions_dir_path = ENV['FLYDATA_TABLE_POSITIONS'] || File.join(FLYDATA_HOME, 'positions')
45
+ #Create positions if dir does not exist
46
+ unless File.directory?(table_positions_dir_path)
47
+ FileUtils.mkdir_p(table_positions_dir_path)
48
+ end
49
+ tables.each do |tab|
50
+ File.open(File.join(table_positions_dir_path, "#{tab}.generated_ddl"), 'w') {|f| f.write("1") }
51
+ end
52
+ end
53
+
54
+ def get_new_table_list(tables, file_type)
55
+ table_positions_dir_path = ENV['FLYDATA_TABLE_POSITIONS'] || File.join(FLYDATA_HOME, 'positions')
56
+ new_tables = []
57
+ tables.each do |table|
58
+ new_tables << table unless File.exists?(File.join(table_positions_dir_path, "#{table}.#{file_type}"))
59
+ end
60
+ new_tables
61
+ end
62
+
43
63
  # MysqlTable marshal file
44
64
  def mysql_table_marshal_dump_path
45
65
  dump_file_path + ".mysql_table"
@@ -79,6 +99,11 @@ module Flydata
79
99
  tables.map{|table| File.join(table_positions_dir_path, table + '.pos')}
80
100
  end
81
101
 
102
+ def table_ddl_file_paths(*tables)
103
+ tables.empty? ? Dir.glob(File.join(table_positions_dir_path, '*.generated_ddl')) :
104
+ tables.map{|table| File.join(table_positions_dir_path, table + '.generated_ddl')}
105
+ end
106
+
82
107
  def table_binlog_pos_paths(*tables)
83
108
  tables.empty? ? Dir.glob(File.join(table_positions_dir_path, '*.binlog.pos')) :
84
109
  tables.map{|table| File.join(table_positions_dir_path, table + '.binlog.pos')}
@@ -128,7 +153,7 @@ module Flydata
128
153
 
129
154
  def save_sync_info(initial_sync, tables)
130
155
  File.open(sync_info_file, "w") do |f|
131
- f.write([initial_sync, tables].join("\t"))
156
+ f.write([initial_sync, tables.join(" ")].join("\t"))
132
157
  end
133
158
  end
134
159
 
@@ -136,7 +161,7 @@ module Flydata
136
161
  return nil unless File.exists?(sync_info_file)
137
162
  items = File.open(sync_info_file, 'r').readline.split("\t")
138
163
  { initial_sync: (items[0] == 'true'),
139
- tables: items[1] }
164
+ tables: items[1].split(" ") }
140
165
  end
141
166
 
142
167
  def get_table_binlog_pos(table_name)
@@ -186,7 +211,7 @@ module Flydata
186
211
  end
187
212
 
188
213
  def save_table_binlog_pos(tables, binlog_pos)
189
- tables.split(" ").each do |table_name|
214
+ tables.each do |table_name|
190
215
  file = File.join(dump_dir, table_name + ".binlog.pos")
191
216
  File.open(file, "w") do |f|
192
217
  f.write(binlog_content(binlog_pos))
@@ -0,0 +1,79 @@
1
+ module Flydata
2
+ module Util
3
+ class MysqlUtil
4
+ DEFAULT_MYSQL_CMD_OPTION = "--default-character-set=utf8 --protocol=tcp"
5
+
6
+ # Generate mysql/mysqldump command with options
7
+ # options must be hash
8
+ # - command # mysql(default) | mysqldump
9
+ # - host
10
+ # - port
11
+ # - username
12
+ # - password
13
+ # - database
14
+ # - tables # array
15
+ # - custom_option # string
16
+ def self.generate_mysql_cmd(option)
17
+ raise ArgumentError.new("option must be hash.") unless option.kind_of?(Hash)
18
+ option = convert_keys_to_sym(option)
19
+ command = option[:command] ? option[:command] : 'mysql'
20
+ host = option[:host] ? "-h #{option[:host]}" : nil
21
+ port = option[:port] ? "-P #{option[:port]}" : nil
22
+ username = option[:username] ? "-u#{option[:username]}" : nil
23
+ password = if !(option[:password].to_s.empty?)
24
+ "-p\"#{option[:password].gsub('`', '\\\`')}\""
25
+ else
26
+ nil
27
+ end
28
+ database = option[:database]
29
+ tables = option[:tables] ? option[:tables].join(' ') : nil
30
+ default_option = option[:no_default_option] ? nil : DEFAULT_MYSQL_CMD_OPTION
31
+ custom_option = option[:custom_option]
32
+
33
+ [command, host, port, username, password, default_option,
34
+ custom_option, database, tables].compact.join(' ')
35
+ end
36
+
37
+ # DDL_DUMP_CMD_TEMPLATE = "MYSQL_PWD=\"%s\" mysqldump --protocol=tcp -d -h %s -P %s -u %s %s %s"
38
+ def self.generate_mysql_ddl_dump_cmd(option)
39
+ opt = option.dup
40
+ opt[:command] = 'mysqldump'
41
+ opt[:custom_option] = '-d'
42
+ generate_mysql_cmd(opt)
43
+ end
44
+
45
+ # MYSQL_DUMP_CMD_TEMPLATE = "MYSQL_PWD=\"%s\" mysqldump --default-character-set=utf8 --protocol=tcp -h %s -P %s -u%s --skip-lock-tables --single-transaction --hex-blob %s %s %s"
46
+ def self.generate_mysqldump_with_master_data_cmd(option)
47
+ opt = option.dup
48
+ opt[:command] = 'mysqldump'
49
+ opt[:custom_option] = '--skip-lock-tables --single-transaction --hex-blob --flush-logs --master-data=2'
50
+ generate_mysql_cmd(opt)
51
+ end
52
+
53
+ def self.generate_mysqldump_without_master_data_cmd(option)
54
+ opt = option.dup
55
+ opt[:command] = 'mysqldump'
56
+ opt[:custom_option] = '--skip-lock-tables --single-transaction --hex-blob'
57
+ generate_mysql_cmd(opt)
58
+ end
59
+
60
+ def self.generate_mysql_show_grants_cmd(option)
61
+ opt = option.dup
62
+ opt[:command] = 'mysql'
63
+ opt[:custom_option] = '-e "SHOW GRANTS;"'
64
+ generate_mysql_cmd(opt)
65
+ end
66
+
67
+ private
68
+
69
+ def self.convert_keys_to_sym(hash)
70
+ hash.inject(hash.dup) do |ret, (k, v)|
71
+ if k.kind_of?(String) && ret[k.to_sym].nil?
72
+ ret[k.to_sym] = v
73
+ end
74
+ ret
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -18,11 +18,12 @@ module Flydata
18
18
  allow(Kernel).to receive(:sleep)
19
19
  allow_any_instance_of(Flydata::Api::DataPort).to receive(:get).and_return("Wibble")
20
20
  allow_any_instance_of(Flydata::AgentCompatibilityCheck).to receive(:check).and_return(true)
21
+ Flydata::Command::Sync.any_instance.should_receive(:handle_mysql_sync).and_return("Wobble")
21
22
  end
22
23
 
23
24
  context "as daemon" do
24
25
  let(:args) { [] }
25
- it "starts fluend with daemon option" do
26
+ it "starts fluentd with daemon option" do
26
27
  expect(Kernel).to receive(:system).with( Regexp.new(
27
28
  "bash .+/bin/serverinfo"), :out => [Regexp.new(".+/flydata.log"),'a'], :err => [Regexp.new(".+/flydata.log"),'a'])
28
29
  expect(Kernel).to receive(:system).with( Regexp.new(
@@ -29,7 +29,7 @@ module Flydata
29
29
  "data_port_key"=>"a458c641",
30
30
  "mysql_data_entry_preference" =>
31
31
  { "host"=>"localhost", "port"=>3306, "username"=>"masashi",
32
- "password"=>"welcome", "database"=>"sync_test", "tables"=>"table1, table2",
32
+ "password"=>"welcome", "database"=>"sync_test", "tables"=>["table1", " table2"],
33
33
  "mysqldump_dir"=>default_mysqldump_dir, "forwarder" => "tcpforwarder",
34
34
  "data_servers"=>"localhost:9905" }
35
35
  }
@@ -75,7 +75,7 @@ module Flydata
75
75
  context 'with full options' do
76
76
  it 'issues mysqldump command with expected parameters' do
77
77
  expect(Open3).to receive(:popen3).with(
78
- 'MYSQL_PWD="welcome" mysqldump --protocol=tcp -d -h localhost -P 3306 -u masashi sync_test table1 table2')
78
+ 'mysqldump -h localhost -P 3306 -umasashi -p"welcome" --default-character-set=utf8 --protocol=tcp -d sync_test table1 table2')
79
79
  subject.send(:do_generate_table_ddl, default_data_entry)
80
80
  end
81
81
  end
@@ -97,7 +97,7 @@ module Flydata
97
97
  end
98
98
  it "uses the default port" do
99
99
  expect(Open3).to receive(:popen3).with(
100
- 'MYSQL_PWD="welcome" mysqldump --protocol=tcp -d -h localhost -P 3306 -u masashi sync_test table1 table2')
100
+ 'mysqldump -h localhost -umasashi -p"welcome" --default-character-set=utf8 --protocol=tcp -d sync_test table1 table2')
101
101
  subject.send(:do_generate_table_ddl, default_data_entry)
102
102
  end
103
103
  end
@@ -107,7 +107,7 @@ module Flydata
107
107
  end
108
108
  it "uses the specified port" do
109
109
  expect(Open3).to receive(:popen3).with(
110
- 'MYSQL_PWD="welcome" mysqldump --protocol=tcp -d -h localhost -P 1234 -u masashi sync_test table1 table2')
110
+ 'mysqldump -h localhost -P 1234 -umasashi -p"welcome" --default-character-set=utf8 --protocol=tcp -d sync_test table1 table2')
111
111
  subject.send(:do_generate_table_ddl, default_data_entry)
112
112
  end
113
113
  end
@@ -129,7 +129,7 @@ module Flydata
129
129
  end
130
130
  it "call mysqldump without MYSQL_PW set" do
131
131
  expect(Open3).to receive(:popen3).with(
132
- 'MYSQL_PWD="" mysqldump --protocol=tcp -d -h localhost -P 3306 -u masashi sync_test table1 table2')
132
+ 'mysqldump -h localhost -P 3306 -umasashi --default-character-set=utf8 --protocol=tcp -d sync_test table1 table2')
133
133
  subject.send(:do_generate_table_ddl, default_data_entry)
134
134
  end
135
135
  end
@@ -140,7 +140,7 @@ module Flydata
140
140
  end
141
141
  it "call mysqldump with MYSQL_PW set with correct symbols" do
142
142
  expect(Open3).to receive(:popen3).with(
143
- 'MYSQL_PWD="welcome&!@^@#^" mysqldump --protocol=tcp -d -h localhost -P 3306 -u masashi sync_test table1 table2')
143
+ 'mysqldump -h localhost -P 3306 -umasashi -p"welcome&!@^@#^" --default-character-set=utf8 --protocol=tcp -d sync_test table1 table2')
144
144
  subject.send(:do_generate_table_ddl, default_data_entry)
145
145
  end
146
146
  end
@@ -24,7 +24,7 @@ module Flydata
24
24
  let(:default_conf) do
25
25
  {
26
26
  'host' => 'localhost', 'port' => 3306, 'username' => 'admin',
27
- 'password' => 'pass', 'database' => 'dev', 'tables' => 'users,groups',
27
+ 'password' => 'pass', 'database' => 'dev', 'tables' => ['users','groups'],
28
28
  }
29
29
  end
30
30
 
@@ -44,6 +44,12 @@ module Flydata
44
44
  m
45
45
  end
46
46
 
47
+ describe '#initialize' do
48
+ subject { default_dump_generator.instance_variable_get(:@dump_cmd) }
49
+ it { is_expected.to eq('mysqldump -h localhost -P 3306 -uadmin -p"pass" --default-character-set=utf8 --protocol=tcp --skip-lock-tables ' +
50
+ '--single-transaction --hex-blob dev users groups') }
51
+ end
52
+
47
53
  describe '#dump' do
48
54
  before do
49
55
  expect(Mysql2::Client).to receive(:new).and_return(mysql_client)
@@ -84,7 +90,7 @@ module Flydata
84
90
  describe '#initialize' do
85
91
  context 'with password' do
86
92
  subject { default_dump_generator.instance_variable_get(:@dump_cmd) }
87
- it { is_expected.to eq('MYSQL_PWD="pass" mysqldump --default-character-set=utf8 --protocol=tcp -h localhost -P 3306 -uadmin --skip-lock-tables ' +
93
+ it { is_expected.to eq('mysqldump -h localhost -P 3306 -uadmin -p"pass" --default-character-set=utf8 --protocol=tcp --skip-lock-tables ' +
88
94
  '--single-transaction --hex-blob --flush-logs --master-data=2 dev users groups') }
89
95
  end
90
96
  context 'without password' do
@@ -92,7 +98,7 @@ module Flydata
92
98
  MysqlDumpGeneratorMasterData.new(default_conf.merge({'password' => ''}))
93
99
  end
94
100
  subject { dump_generator.instance_variable_get(:@dump_cmd) }
95
- it { is_expected.to eq('MYSQL_PWD="" mysqldump --default-character-set=utf8 --protocol=tcp -h localhost -P 3306 -uadmin --skip-lock-tables ' +
101
+ it { is_expected.to eq('mysqldump -h localhost -P 3306 -uadmin --default-character-set=utf8 --protocol=tcp --skip-lock-tables ' +
96
102
  '--single-transaction --hex-blob --flush-logs --master-data=2 dev users groups') }
97
103
  end
98
104
  end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ module Flydata
4
+ module Util
5
+
6
+ describe MysqlUtil do
7
+ let(:default_option) { {
8
+ command: 'mysqldump',
9
+ host: 'test-host',
10
+ port: 3306,
11
+ username: 'testuser',
12
+ password: 'testpassword',
13
+ database: 'testdb',
14
+ } }
15
+ let(:option) { default_option }
16
+
17
+ describe '.generate_mysql_cmd' do
18
+ subject { described_class.generate_mysql_cmd(option) }
19
+
20
+ context 'when option is not hash' do
21
+ let(:option) { nil }
22
+ it { expect{subject}.to raise_error(ArgumentError) }
23
+ end
24
+
25
+ context 'when option is empty' do
26
+ let(:option) { {} }
27
+ it { is_expected.to eq("mysql --default-character-set=utf8 --protocol=tcp") }
28
+ end
29
+
30
+ context 'when all option is specified' do
31
+ it { is_expected.to eq(
32
+ 'mysqldump -h test-host -P 3306 -utestuser -p"testpassword" --default-character-set=utf8 --protocol=tcp testdb'
33
+ ) }
34
+ end
35
+
36
+ context 'when option conatins string key' do
37
+ before {
38
+ option[:database] = nil
39
+ option['database'] = 'another_db'
40
+ }
41
+ it { is_expected.to eq(
42
+ 'mysqldump -h test-host -P 3306 -utestuser -p"testpassword" --default-character-set=utf8 --protocol=tcp another_db'
43
+ ) }
44
+ end
45
+
46
+ context 'when password is empty' do
47
+ before { option[:password] = nil }
48
+ it { is_expected.to eq(
49
+ 'mysqldump -h test-host -P 3306 -utestuser --default-character-set=utf8 --protocol=tcp testdb'
50
+ ) }
51
+ end
52
+
53
+ context 'when password is empty' do
54
+ before { option[:password] = "abc`def" }
55
+ it { is_expected.to eq(
56
+ 'mysqldump -h test-host -P 3306 -utestuser -p"abc\\`def" --default-character-set=utf8 --protocol=tcp testdb'
57
+ ) }
58
+ end
59
+
60
+ context 'when tables are specified' do
61
+ before { option[:tables] = %w(table_a table_b table_c) }
62
+ it { is_expected.to eq(
63
+ 'mysqldump -h test-host -P 3306 -utestuser -p"testpassword" --default-character-set=utf8 --protocol=tcp testdb table_a table_b table_c'
64
+ ) }
65
+ end
66
+
67
+ context 'when no_default_option is turned on' do
68
+ before { option[:no_default_option] = true }
69
+ it { is_expected.to eq(
70
+ 'mysqldump -h test-host -P 3306 -utestuser -p"testpassword" testdb'
71
+ ) }
72
+ end
73
+
74
+
75
+ context 'with options for mysql_protocol_tcp_compat' do
76
+ before {
77
+ option[:command] = 'mysql'
78
+ option[:custom_option] = '-e "SHOW GRANTS;"'
79
+ }
80
+ it { is_expected.to eq(
81
+ 'mysql -h test-host -P 3306 -utestuser -p"testpassword" --default-character-set=utf8 --protocol=tcp -e "SHOW GRANTS;" testdb'
82
+ ) }
83
+ end
84
+ end
85
+
86
+ describe '.generate_mysql_ddl_dump_cmd' do
87
+ subject { described_class.generate_mysql_ddl_dump_cmd(option) }
88
+
89
+ it { is_expected.to eq(
90
+ 'mysqldump -h test-host -P 3306 -utestuser -p"testpassword" --default-character-set=utf8 --protocol=tcp -d testdb'
91
+ ) }
92
+ end
93
+
94
+ describe '.generate_mysqldump_with_master_data_cmd' do
95
+ subject { described_class.generate_mysqldump_with_master_data_cmd(option) }
96
+ before { option[:tables] = %w(table_a table_b) }
97
+ it { is_expected.to eq(
98
+ 'mysqldump -h test-host -P 3306 -utestuser -p"testpassword" --default-character-set=utf8 --protocol=tcp --skip-lock-tables --single-transaction --hex-blob --flush-logs --master-data=2 testdb table_a table_b'
99
+ ) }
100
+ end
101
+
102
+ describe '.generate_mysqldump_without_master_data_cmd' do
103
+ subject { described_class.generate_mysqldump_without_master_data_cmd(option) }
104
+ before { option[:tables] = %w(table_a table_b) }
105
+ it { is_expected.to eq(
106
+ 'mysqldump -h test-host -P 3306 -utestuser -p"testpassword" --default-character-set=utf8 --protocol=tcp --skip-lock-tables --single-transaction --hex-blob testdb table_a table_b'
107
+ ) }
108
+ end
109
+
110
+ describe '.generate_mysql_show_grants_cmd' do
111
+ subject { described_class.generate_mysql_show_grants_cmd(option) }
112
+ it { is_expected.to eq(
113
+ 'mysql -h test-host -P 3306 -utestuser -p"testpassword" --default-character-set=utf8 --protocol=tcp -e "SHOW GRANTS;" testdb'
114
+ ) }
115
+ end
116
+ end
117
+ end
118
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flydata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.29
4
+ version: 0.2.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Fujikawa
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2015-01-23 00:00:00.000000000 Z
15
+ date: 2015-01-26 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: rest-client
@@ -451,6 +451,7 @@ files:
451
451
  - flydata-core/lib/flydata-core/table_def/mysql_table_def.rb
452
452
  - flydata-core/lib/flydata-core/table_def/redshift_table_def.rb
453
453
  - flydata-core/lib/flydata-core/thread_context.rb
454
+ - flydata-core/spec/logger_spec.rb
454
455
  - flydata-core/spec/spec_helper.rb
455
456
  - flydata-core/spec/table_def/mysql_table_def_spec.rb
456
457
  - flydata-core/spec/table_def/mysql_to_redshift_table_def_spec.rb
@@ -524,6 +525,7 @@ files:
524
525
  - lib/flydata/proxy.rb
525
526
  - lib/flydata/sync_file_manager.rb
526
527
  - lib/flydata/util/encryptor.rb
528
+ - lib/flydata/util/mysql_util.rb
527
529
  - spec/fluent_plugins_spec_helper.rb
528
530
  - spec/fly_data_model_spec.rb
529
531
  - spec/flydata/api/data_entry_spec.rb
@@ -543,6 +545,7 @@ files:
543
545
  - spec/flydata/parser/mysql/dump_parser_spec.rb
544
546
  - spec/flydata/sync_file_manager_spec.rb
545
547
  - spec/flydata/util/encryptor_spec.rb
548
+ - spec/flydata/util/mysql_util_spec.rb
546
549
  - spec/flydata_spec.rb
547
550
  - spec/spec_helper.rb
548
551
  - tmpl/redshift_mysql_data_entry.conf.tmpl