flydata 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,11 @@
1
1
  module Flydata
2
2
  module Command
3
3
  class Start < Base
4
+ def self.slop
5
+ Flydata::Command::Sender.slop_start # Needs options for Sender#start
6
+ end
4
7
  def run
5
- sender = Flydata::Command::Sender.new
8
+ sender = Flydata::Command::Sender.new(opts)
6
9
  sender.start
7
10
  end
8
11
  end
@@ -18,6 +18,17 @@ module Flydata
18
18
  STATUS_COMPLETE = 'COMPLETE'
19
19
 
20
20
  def run(*tables)
21
+ sender = Flydata::Command::Sender.new
22
+ if (sender.process_exist?)
23
+ if tables.empty?
24
+ # full sync
25
+ puts "FlyData Agent is already running. If you'd like to restart FlyData Sync from scratch, run 'flydata sync:reset' first."
26
+ else
27
+ # per-table sync
28
+ puts "Flydata Agent is already running. If you'd like to Sync the table(s), run 'flydata flush' first."
29
+ end
30
+ exit 1
31
+ end
21
32
  de = retrieve_data_entries.first
22
33
  raise "There are no data entry." unless de
23
34
  case de['type']
@@ -36,6 +47,12 @@ module Flydata
36
47
  end
37
48
 
38
49
  def reset
50
+ return unless ask_yes_no("This resets the current Sync. Are you sure?")
51
+ sender = Flydata::Command::Sender.new
52
+ sender.flush_client_buffer # TODO We should rather delete buffer files
53
+ # TODO Reset server when API becomes available
54
+ sender.stop
55
+
39
56
  de = retrieve_data_entries.first
40
57
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
41
58
  [
@@ -51,16 +68,36 @@ module Flydata
51
68
  end
52
69
  end
53
70
 
71
+ def wait_for_server_data_processing
72
+ state = :PROCESS
73
+ puts "Processing data..."
74
+ sleep 10
75
+ status = nil
76
+ while (status = check)
77
+ if state == :PROCESS && status['state'] == 'uploading'
78
+ puts " -> Done"
79
+ state = :UPLOAD
80
+ puts "Uploading data to Redshift..."
81
+ end
82
+ print_progress(status)
83
+ sleep 10
84
+ end
85
+ if (state == :PROCESS)
86
+ # :UPLOAD state was skipped due to no data
87
+ puts " -> Done"
88
+ puts "Uploading data to Redshift..."
89
+ end
90
+ puts " -> Done"
91
+ end
92
+
54
93
  def check
55
94
  de = retrieve_data_entries.first
56
95
  retry_on(RestClient::Exception) do
57
- ret = do_check(de)
58
- if ret['complete']
59
- puts "No buffer data on FlyData. #{ret.inspect}"
60
- true
96
+ status = do_check(de)
97
+ if status['complete']
98
+ nil
61
99
  else
62
- puts "Now processing data on FlyData. #{ret.inspect}"
63
- false
100
+ status
64
101
  end
65
102
  end
66
103
  end
@@ -93,6 +130,11 @@ module Flydata
93
130
  flydata.data_entry.buffer_stat(de['id'], env_mode)
94
131
  end
95
132
 
133
+ def print_progress(buffer_stat)
134
+ message = buffer_stat['message']
135
+ puts message unless message.nil? || message.empty?
136
+ end
137
+
96
138
  DDL_DUMP_CMD_TEMPLATE = "mysqldump --protocol=tcp -d -h %s -P %s -u %s %s %s %s"
97
139
  def do_generate_table_ddl(de)
98
140
  if `which mysqldump`.empty?
@@ -110,10 +152,11 @@ module Flydata
110
152
 
111
153
  command = DDL_DUMP_CMD_TEMPLATE % params
112
154
 
113
- IO.popen(command, 'r') do |io|
155
+ Open3.popen3(command) do |stdin, stdout, stderr|
156
+ stdin.close
114
157
  create_flydata_ctl_table = mp['initial_sync']
115
- while !io.eof?
116
- mysql_tabledef = Flydata::TableDef::MysqlTableDef.create(io)
158
+ while !stdout.eof?
159
+ mysql_tabledef = Flydata::TableDef::MysqlTableDef.create(stdout)
117
160
  if mysql_tabledef.nil?
118
161
  # stream had no more create table definition
119
162
  break
@@ -122,6 +165,10 @@ module Flydata
122
165
  puts Flydata::TableDef::RedshiftTableDef.from_flydata_tabledef(flydata_tabledef, flydata_ctl_table: create_flydata_ctl_table)
123
166
  create_flydata_ctl_table = false
124
167
  end
168
+ while !stderr.eof?
169
+ line = stderr.gets
170
+ $stderr.print line unless /Warning: Using a password on the command line interface can be insecure./ === line
171
+ end
125
172
  end
126
173
  end
127
174
 
@@ -131,7 +178,7 @@ module Flydata
131
178
 
132
179
  # Check client condition
133
180
  if File.exists?(sync_fm.binlog_path) and de['mysql_data_entry_preference']['initial_sync']
134
- raise "Already synchronized. If you want to do initial sync, delete #{sync_fm.binlog_path}."
181
+ raise "Already synchronized. If you want to do initial sync, run 'flydata sync:reset'"
135
182
  end
136
183
 
137
184
  # Copy template if not exists
@@ -141,7 +188,7 @@ module Flydata
141
188
 
142
189
  if generate_mysqldump(de, sync_fm)
143
190
  sync_fm.save_sync_info(de['mysql_data_entry_preference']['initial_sync'], de['mysql_data_entry_preference']['tables'])
144
- parse_mysqldump(dp, de, sync_fm)
191
+ parse_mysqldump_and_send(dp, de, sync_fm)
145
192
  complete
146
193
  end
147
194
  end
@@ -156,32 +203,40 @@ module Flydata
156
203
  end
157
204
  end
158
205
 
159
- puts "Running mysqldump... host:#{de['mysql_data_entry_preference']['host']} " +
160
- "username:#{de['mysql_data_entry_preference']['username']} " +
161
- "database:#{de['mysql_data_entry_preference']['database']}"
162
- if de['mysql_data_entry_preference']['data_servers']
163
- puts "Send to Custom Data Servers: #{de['mysql_data_entry_preference']['data_servers']}"
164
- end
165
-
166
- if de['mysql_data_entry_preference']['tables']
167
- puts " target tables: #{de['mysql_data_entry_preference']['tables']}"
168
- else
169
- puts " target tables: <all-tables>"
170
- end
171
-
172
206
  fp = sync_fm.dump_file_path
173
207
  if File.exists?(fp) and File.size(fp) > 0 and not overwrite
174
208
  puts " -> Skip"
175
209
  return fp
176
210
  end
177
211
 
178
- puts "[Confirm] mysqldump path: #{fp}"
179
- if ask_yes_no('OK?')
212
+ tables = de['mysql_data_entry_preference']['tables']
213
+ tables ||= '<all tables>'
214
+ data_servers = de['mysql_data_entry_preference']['data_servers'] ? "\n data servers: #{de['mysql_data_entry_preference']['data_servers']}" : ""
215
+
216
+ confirmation_text = <<-EOM
217
+
218
+ FlyData Sync will start synchonizing the following database tables
219
+ host: #{de['mysql_data_entry_preference']['host']}
220
+ port: #{de['mysql_data_entry_preference']['port']}
221
+ username: #{de['mysql_data_entry_preference']['username']}
222
+ database: #{de['mysql_data_entry_preference']['database']}
223
+ tables: #{tables}#{data_servers}
224
+ dump file: #{fp}
225
+
226
+ Dump file saves contents of your tables temporarily. Make sure you have enough disk space.
227
+ EOM
228
+
229
+ print confirmation_text
230
+
231
+ if ask_yes_no('Start Sync?')
232
+ puts "Exporting data from database..."
180
233
  Flydata::Mysql::MysqlDumpGeneratorNoMasterData.new(de['mysql_data_entry_preference']).dump(fp)
181
234
  else
182
235
  newline
183
- puts "You can change the mysqldump path with 'mysqldump_path' in the conf file."
184
- puts "Edit '#{Flydata::Preference::DataEntryPreference.conf_path(de)}'"
236
+ puts "You can change the dump file path with 'mysqldump_path' property in the following conf file."
237
+ puts
238
+ puts " #{Flydata::Preference::DataEntryPreference.conf_path(de)}"
239
+ puts
185
240
  return nil
186
241
  end
187
242
  puts " -> Done"
@@ -208,8 +263,8 @@ module Flydata
208
263
  # <- checkpoint
209
264
  #...
210
265
  #CREATE TABLE ...
211
- def parse_mysqldump(dp, de, sync_fm)
212
- puts "Parsing mysqldump file..."
266
+ def parse_mysqldump_and_send(dp, de, sync_fm)
267
+ puts "Sending data to FlyData Server..."
213
268
 
214
269
  # Prepare forwarder
215
270
  de_tag_name = de["tag_name#{env_suffix}"]
@@ -250,15 +305,13 @@ module Flydata
250
305
  ret = flydata.redshift_cluster.run_query(sql)
251
306
  if ret['message'].index('ERROR:')
252
307
  if ret['message'].index('already exists')
253
- puts " -> Skip"
308
+ puts " -> Skip"
254
309
  else
255
310
  raise "Failed to create table. error=#{ret['message']}"
256
311
  end
257
312
  else
258
- puts " -> OK"
313
+ puts " -> OK"
259
314
  end
260
- else
261
- puts "- Parsing table: #{mysql_table.table_name}"
262
315
  end
263
316
 
264
317
  # dump mysql_table for resume
@@ -273,19 +326,17 @@ module Flydata
273
326
  end
274
327
  ret = forwarder.emit(records)
275
328
  tmp_num_inserted_record += 1
276
- print '.'
277
329
  ret
278
330
  },
279
331
  # checkpoint
280
332
  Proc.new { |mysql_table, last_pos, binlog_pos, state, substate|
281
333
  # flush if buffer records exist
282
334
  if tmp_num_inserted_record > 0 && forwarder.buffer_record_count > 0
283
- puts
284
335
  forwarder.flush # send buffer data to the server before checkpoint
285
336
  end
286
337
 
287
338
  # show the current progress
288
- puts " #{(last_pos.to_f/dump_file_size * 100).round(1)}% (#{last_pos}/#{dump_file_size}) #{Time.now.to_i - bench_start_time.to_i}sec"
339
+ puts " -> #{(last_pos.to_f/dump_file_size * 100).round(1)}% (#{last_pos}/#{dump_file_size}) completed..."
289
340
 
290
341
  # save check point
291
342
  table_name = mysql_table.nil? ? '' : mysql_table.table_name
@@ -294,37 +345,57 @@ module Flydata
294
345
  )
295
346
  forwarder.close
296
347
 
348
+ puts " -> Done"
349
+
297
350
  if ENV['FLYDATA_BENCHMARK']
298
- puts "Done!"
299
351
  bench_end_time = Time.now
300
352
  elapsed_time = bench_end_time.to_i - bench_start_time.to_i
301
353
  puts "Elapsed:#{elapsed_time}sec start:#{bench_start_time} end:#{bench_end_time}"
302
354
  return true
303
355
  end
304
356
  # wait until finish
305
- puts "Start waiting until all data is processed on FlyData..."
306
- sleep 10
307
- until check
308
- sleep 10
309
- end
357
+ wait_for_server_data_processing
310
358
 
311
359
  sync_fm.save_dump_pos(STATUS_COMPLETE, '', dump_file_size, binlog_pos)
312
360
  tables = de['mysql_data_entry_preference']['tables'].split(',').join(' ')
313
361
  sync_fm.save_table_binlog_pos(tables, binlog_pos)
314
362
  end
315
363
 
364
+ ALL_DONE_MESSAGE_TEMPLATE = <<-EOM
365
+
366
+ Congratulations! FlyData has started synchronizing your database tables.
367
+
368
+ What's next?
369
+
370
+ - Check data on Redshift (%s)
371
+ - Check your FlyData usage on the FlyData Dashboard (%s)
372
+ - To manage the FlyData Agent, use the 'flydata' command (type 'flydata' for help)
373
+ - If you encounter an issue,
374
+ please check our documentation (https://www.flydata.com/docs/) or
375
+ contact our customer support team (support@flydata.com)
376
+
377
+ Thank you for using FlyData!
378
+ EOM
316
379
  def complete
317
380
  de = load_sync_info(retrieve_data_entries.first)
318
381
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
319
382
  info = sync_fm.load_dump_pos
320
383
  if info[:status] == STATUS_COMPLETE
384
+ puts "Starting FlyData Agent..."
321
385
  if de['mysql_data_entry_preference']['initial_sync']
322
386
  sync_fm.save_binlog(info[:binlog_pos])
323
387
  end
324
388
  sync_fm.move_table_binlog_files(de['mysql_data_entry_preference']['tables'].split(','))
325
389
  sync_fm.reset_table_position_files(de['mysql_data_entry_preference']['tables'].split(','))
326
390
  sync_fm.backup_dump_dir
327
- Flydata::Command::Sender.new.start
391
+ Flydata::Command::Sender.new.start(quiet: true)
392
+ puts " -> Done"
393
+
394
+ data_port = flydata.data_port.get
395
+ dashboard_url = "#{flydata.flydata_api_host}/data_ports/#{data_port['id']}"
396
+ redshift_console_url = "#{flydata.flydata_api_host}/redshift_clusters/query/new"
397
+ last_message = ALL_DONE_MESSAGE_TEMPLATE % [redshift_console_url, dashboard_url]
398
+ puts last_message
328
399
  else
329
400
  raise "Initial sync status is not complete. Try running 'flydata sync'."
330
401
  end
@@ -359,10 +430,7 @@ module Flydata
359
430
  def flush_buffer_and_stop
360
431
  sender = Flydata::Command::Sender.new
361
432
  sender.flush_client_buffer
362
- puts "Checking the server."
363
- until check
364
- sleep 10
365
- end
433
+ wait_for_server_data_processing
366
434
  sender.stop(quiet: true)
367
435
  end
368
436
  end
@@ -374,10 +442,10 @@ module Flydata
374
442
  def self.create(forwarder_key, tag, servers, options = {})
375
443
  case forwarder_key
376
444
  when nil, "tcpforwarder"
377
- puts "Creating TCP connection"
445
+ puts "Creating TCP connection" if FLYDATA_DEBUG
378
446
  forward = TcpForwarder.new(tag, servers, options)
379
447
  when "sslforwarder"
380
- puts "Creating SSL connection"
448
+ puts "Creating SSL connection" if FLYDATA_DEBUG
381
449
  forward = SslForwarder.new(tag, servers, options)
382
450
  else
383
451
  raise "Unsupported Forwarding type #{forwarder_key}"
@@ -432,7 +500,6 @@ module Flydata
432
500
  #TODO retry logic
433
501
  def send
434
502
  if @buffer_size > 0
435
- puts " -> Sending #{@buffer_record_count}records #{@buffer_size}byte"
436
503
  else
437
504
  return false
438
505
  end
@@ -1139,12 +1206,12 @@ EOS
1139
1206
  else
1140
1207
  if chars.end_with?(')')
1141
1208
  chars = chars[0..-2]
1142
- @values << (chars == 'NULL' ? nil : chars)
1209
+ @values << (chars == 'NULL' ? nil : remove_leading_zeros(chars))
1143
1210
  @values_set << @values
1144
1211
  @values = []
1145
1212
  cur_state = State::NEXT_VALUES
1146
1213
  else
1147
- @values << (chars == 'NULL' ? nil : chars)
1214
+ @values << (chars == 'NULL' ? nil : remove_leading_zeros(chars))
1148
1215
  end
1149
1216
  end
1150
1217
  else
@@ -1154,10 +1221,10 @@ EOS
1154
1221
  return @values_set
1155
1222
  end
1156
1223
 
1157
- ESCAPE_HASH_TABLE = {"\\\\" => "\\", "\\'" => "'", "\\n" => "\n", "\\r" => "\r"}
1224
+ ESCAPE_HASH_TABLE = {"\\\\" => "\\", "\\'" => "'", "\\\"" => "\"", "\\n" => "\n", "\\r" => "\r"}
1158
1225
 
1159
1226
  def replace_escape_char(original)
1160
- original.gsub(/\\\\|\\'|\\n|\\r/, ESCAPE_HASH_TABLE)
1227
+ original.gsub(/\\\\|\\'|\\"|\\n|\\r/, ESCAPE_HASH_TABLE)
1161
1228
  end
1162
1229
 
1163
1230
  # This method assume that the last character is '(single quotation)
@@ -1175,6 +1242,14 @@ EOS
1175
1242
  end
1176
1243
  flag
1177
1244
  end
1245
+
1246
+ def remove_leading_zeros(number_string)
1247
+ if number_string.start_with?('0')
1248
+ number_string.sub(/^0*([1-9][0-9]*(\.\d*)?|0(\.\d*)?)$/,'\1')
1249
+ else
1250
+ number_string
1251
+ end
1252
+ end
1178
1253
  end
1179
1254
  end
1180
1255
  end
@@ -13,8 +13,14 @@ module Flydata
13
13
  end
14
14
 
15
15
  def print_usage
16
- puts '-' * 64
17
- puts <<-EOM
16
+ puts usage_text
17
+ end
18
+
19
+ def usage_text
20
+ text = ""
21
+ text += '-' * 64
22
+ text += "\n"
23
+ text += <<-EOM
18
24
  Usage: flydata COMMAND
19
25
  setup # setup initially
20
26
  start # start flydata process
@@ -32,7 +38,10 @@ please setup flydata again by following commands.
32
38
  You can check the logs of sender(flydata) process.
33
39
  Log path: #{File.join(FLYDATA_HOME, 'flydata.log')}
34
40
  EOM
35
- puts '-' * 64
41
+ text += "\n"
42
+ text += '-' * 64
43
+ text += "\n"
44
+ text
36
45
  end
37
46
 
38
47
  def to_command_class(class_name)
@@ -5,7 +5,7 @@ class MysqlTableDef
5
5
  TYPE_MAP_M2F = {
6
6
  'bigint' => 'int8',
7
7
  'binary' => 'binary',
8
- 'blob' => 'varbinary',
8
+ 'blob' => 'varbinary(65535)',
9
9
  'bool' => 'int1',
10
10
  'boolean' => 'int1',
11
11
  'char' => 'varchar',
@@ -20,9 +20,9 @@ class MysqlTableDef
20
20
  'float' => 'float4',
21
21
  'int' => 'int4',
22
22
  'integer' => 'int4',
23
- 'longblob' => 'varbinary',
23
+ 'longblob' => 'varbinary(4294967295)',
24
24
  'longtext' => 'text',
25
- 'mediumblob' => 'varbinary',
25
+ 'mediumblob' => 'varbinary(16777215)',
26
26
  'mediumint' => 'int3',
27
27
  'mediumtext' => 'text',
28
28
  'numeric' => 'numeric',
@@ -30,7 +30,7 @@ class MysqlTableDef
30
30
  'text' => 'text',
31
31
  'time' => 'time',
32
32
  'timestamp' => 'datetime',
33
- 'tinyblob' => 'varbinary',
33
+ 'tinyblob' => 'varbinary(255)',
34
34
  'tinyint' => 'int1',
35
35
  'tinytext' => 'text',
36
36
  'varbinary' => 'varbinary',
@@ -113,6 +113,8 @@ class MysqlTableDef
113
113
  # index creation. No action required.
114
114
  elsif stripped_line.start_with?("CONSTRAINT")
115
115
  # constraint definition. No acction required.
116
+ elsif stripped_line.start_with?("UNIQUE KEY")
117
+ # constraint definition. No acction required.
116
118
  else
117
119
  $stderr.puts "Unknown table definition. Skip. (#{line})"
118
120
  end
@@ -174,6 +176,7 @@ class MysqlTableDef
174
176
 
175
177
  cond = :options
176
178
  when :options
179
+ column[:type] += ' unsigned' if line =~ /unsigned/i
177
180
  column[:auto_increment] = true if line =~ /AUTO_INCREMENT/i
178
181
  column[:not_null] = true if line =~ /NOT NULL/i
179
182
  if /DEFAULT\s+((?:[^'\s]+\b)|(?:'(?:\\'|[^'])*'))/i.match(line)