flydata 0.1.8 → 0.1.9

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.
@@ -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)