flydata 0.2.29 → 0.2.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|