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 +4 -4
- data/VERSION +1 -1
- data/flydata-core/lib/flydata-core/logger.rb +14 -3
- data/flydata-core/spec/logger_spec.rb +72 -0
- data/flydata.gemspec +6 -3
- data/lib/flydata/command/sender.rb +10 -7
- data/lib/flydata/command/sync.rb +134 -59
- data/lib/flydata/compatibility_check.rb +3 -1
- data/lib/flydata/fluent-plugins/in_mysql_binlog_flydata.rb +2 -1
- data/lib/flydata/parser/mysql/dump_parser.rb +15 -13
- data/lib/flydata/sync_file_manager.rb +28 -3
- data/lib/flydata/util/mysql_util.rb +79 -0
- data/spec/flydata/command/sender_spec.rb +2 -1
- data/spec/flydata/command/sync_spec.rb +6 -6
- data/spec/flydata/parser/mysql/dump_parser_spec.rb +9 -3
- data/spec/flydata/util/mysql_util_spec.rb +118 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: acad4ae7202b7fc525478de91925ef4ee0a24fdd
|
4
|
+
data.tar.gz: 63cf3ac658afa5be37ed7b2a85be476de280c56f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb7391185d4f9797817f0628ec46f5b9cce0ef1680fb843d0181f97dd18450c059d6b6614b7ef98e8ee88dfac171933561e448a2ca1b403665563b27b5d005a8
|
7
|
+
data.tar.gz: 7407fd538bbd7678ae19d5ddaa9dcc5b13159555b8c7b82a788c821fb70855594f49848eb141ec7a44982eb164dd63738e63a9714ed132b5b02ff2caab948d4f
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
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
|
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
|
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
|
-
|
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.
|
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.
|
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-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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(
|
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
|
data/lib/flydata/command/sync.rb
CHANGED
@@ -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
|
-
|
48
|
-
|
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
|
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 =
|
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 =
|
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 =
|
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 =
|
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(
|
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
|
185
|
-
de = retrieve_data_entries.first
|
186
|
-
raise "There are no data
|
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
|
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
|
-
|
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
|
-
|
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 =
|
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
|
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(
|
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
|
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']
|
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']
|
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(
|
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
|
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']
|
550
|
-
sync_fm.reset_table_position_files(de['mysql_data_entry_preference']['tables']
|
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
|
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
|
-
|
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 =
|
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();' |
|
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
|
-
|
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']
|
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.
|
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
|
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
|
-
'
|
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
|
-
'
|
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
|
-
'
|
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
|
-
'
|
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
|
-
'
|
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('
|
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('
|
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.
|
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-
|
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
|