flydata 0.2.29 → 0.2.30

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