flydata 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/VERSION +1 -1
- data/flydata-core/lib/flydata-core/errors.rb +12 -0
- data/{lib/flydata/mysql/mysql_util.rb → flydata-core/lib/flydata-core/mysql/command_generator.rb} +39 -6
- data/flydata-core/lib/flydata-core/mysql/compatibility_checker.rb +222 -0
- data/flydata-core/lib/flydata-core/query_job.rb +7 -0
- data/flydata-core/lib/flydata-core/query_job/redshift.rb +27 -0
- data/{spec/flydata/mysql/mysql_util_spec.rb → flydata-core/spec/mysql/command_generator_spec.rb} +22 -3
- data/flydata-core/spec/mysql/compatibility_checker.rb +9 -0
- data/flydata-core/spec/query_job/redshift_spec.rb +34 -0
- data/flydata.gemspec +14 -9
- data/lib/flydata/api/data_entry.rb +9 -0
- data/lib/flydata/command/mysql.rb +2 -1
- data/lib/flydata/command/sync.rb +62 -31
- data/lib/flydata/compatibility_check.rb +33 -152
- data/lib/flydata/fluent-plugins/in_mysql_binlog_flydata.rb +4 -4
- data/lib/flydata/mysql/table_ddl.rb +2 -2
- data/lib/flydata/parser/mysql/dump_parser.rb +2 -2
- data/spec/flydata/command/sync_spec.rb +17 -15
- data/spec/flydata/compatibility_check_spec.rb +13 -12
- data/spec/flydata/mysql/table_ddl_spec.rb +1 -1
- metadata +12 -7
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.4.
|
5
|
+
# stub: flydata 0.4.1 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "flydata"
|
9
|
-
s.version = "0.4.
|
9
|
+
s.version = "0.4.1"
|
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-06-
|
14
|
+
s.date = "2015-06-19"
|
15
15
|
s.description = "FlyData Agent"
|
16
16
|
s.email = "sysadmin@flydata.com"
|
17
17
|
s.executables = ["fdmysqldump", "flydata", "serverinfo"]
|
@@ -47,6 +47,10 @@ Gem::Specification.new do |s|
|
|
47
47
|
"flydata-core/lib/flydata-core/fluent-plugins/multi_buffer.rb",
|
48
48
|
"flydata-core/lib/flydata-core/fluent/config_helper.rb",
|
49
49
|
"flydata-core/lib/flydata-core/logger.rb",
|
50
|
+
"flydata-core/lib/flydata-core/mysql/command_generator.rb",
|
51
|
+
"flydata-core/lib/flydata-core/mysql/compatibility_checker.rb",
|
52
|
+
"flydata-core/lib/flydata-core/query_job.rb",
|
53
|
+
"flydata-core/lib/flydata-core/query_job/redshift.rb",
|
50
54
|
"flydata-core/lib/flydata-core/record/record.rb",
|
51
55
|
"flydata-core/lib/flydata-core/redshift/string.rb",
|
52
56
|
"flydata-core/lib/flydata-core/table_def.rb",
|
@@ -58,6 +62,9 @@ Gem::Specification.new do |s|
|
|
58
62
|
"flydata-core/spec/config/user_maintenance_spec.rb",
|
59
63
|
"flydata-core/spec/fluent/config_helper_spec.rb",
|
60
64
|
"flydata-core/spec/logger_spec.rb",
|
65
|
+
"flydata-core/spec/mysql/command_generator_spec.rb",
|
66
|
+
"flydata-core/spec/mysql/compatibility_checker.rb",
|
67
|
+
"flydata-core/spec/query_job/redshift_spec.rb",
|
61
68
|
"flydata-core/spec/redshift/string_spec.rb",
|
62
69
|
"flydata-core/spec/spec_helper.rb",
|
63
70
|
"flydata-core/spec/table_def/autoload_redshift_table_def_spec.rb",
|
@@ -145,7 +152,6 @@ Gem::Specification.new do |s|
|
|
145
152
|
"lib/flydata/heroku/instance_methods.rb",
|
146
153
|
"lib/flydata/log_monitor.rb",
|
147
154
|
"lib/flydata/mysql/binlog_position.rb",
|
148
|
-
"lib/flydata/mysql/mysql_util.rb",
|
149
155
|
"lib/flydata/mysql/table_ddl.rb",
|
150
156
|
"lib/flydata/output/forwarder.rb",
|
151
157
|
"lib/flydata/parser/mysql/dump_parser.rb",
|
@@ -201,7 +207,6 @@ Gem::Specification.new do |s|
|
|
201
207
|
"spec/flydata/helper/worker_spec.rb",
|
202
208
|
"spec/flydata/heroku_spec.rb",
|
203
209
|
"spec/flydata/mysql/binlog_position_spec.rb",
|
204
|
-
"spec/flydata/mysql/mysql_util_spec.rb",
|
205
210
|
"spec/flydata/mysql/table_ddl_spec.rb",
|
206
211
|
"spec/flydata/output/forwarder_spec.rb",
|
207
212
|
"spec/flydata/parser/mysql/alter_table_parser_spec.rb",
|
@@ -216,7 +221,7 @@ Gem::Specification.new do |s|
|
|
216
221
|
]
|
217
222
|
s.homepage = "http://flydata.com/"
|
218
223
|
s.licenses = ["All right reserved."]
|
219
|
-
s.rubygems_version = "2.4.
|
224
|
+
s.rubygems_version = "2.4.6"
|
220
225
|
s.summary = "FlyData Agent"
|
221
226
|
|
222
227
|
if s.respond_to? :specification_version then
|
@@ -235,7 +240,7 @@ Gem::Specification.new do |s|
|
|
235
240
|
s.add_runtime_dependency(%q<treetop>, [">= 1.5.3", "~> 1.5"])
|
236
241
|
s.add_runtime_dependency(%q<sys-filesystem>, [">= 1.1.3", "~> 1.1"])
|
237
242
|
s.add_runtime_dependency(%q<io-console>, [">= 0.4.2", "~> 0.4.2"])
|
238
|
-
s.add_runtime_dependency(%q<kodama>, [">= 0.1.
|
243
|
+
s.add_runtime_dependency(%q<kodama>, [">= 0.1.6", "~> 0.1.2"])
|
239
244
|
s.add_runtime_dependency(%q<serverengine>, ["~> 1.5"])
|
240
245
|
s.add_development_dependency(%q<jeweler>, [">= 1.8.8", "~> 1.8"])
|
241
246
|
s.add_development_dependency(%q<rspec>, ["~> 3.0"])
|
@@ -259,7 +264,7 @@ Gem::Specification.new do |s|
|
|
259
264
|
s.add_dependency(%q<treetop>, [">= 1.5.3", "~> 1.5"])
|
260
265
|
s.add_dependency(%q<sys-filesystem>, [">= 1.1.3", "~> 1.1"])
|
261
266
|
s.add_dependency(%q<io-console>, [">= 0.4.2", "~> 0.4.2"])
|
262
|
-
s.add_dependency(%q<kodama>, [">= 0.1.
|
267
|
+
s.add_dependency(%q<kodama>, [">= 0.1.6", "~> 0.1.2"])
|
263
268
|
s.add_dependency(%q<serverengine>, ["~> 1.5"])
|
264
269
|
s.add_dependency(%q<jeweler>, [">= 1.8.8", "~> 1.8"])
|
265
270
|
s.add_dependency(%q<rspec>, ["~> 3.0"])
|
@@ -284,7 +289,7 @@ Gem::Specification.new do |s|
|
|
284
289
|
s.add_dependency(%q<treetop>, [">= 1.5.3", "~> 1.5"])
|
285
290
|
s.add_dependency(%q<sys-filesystem>, [">= 1.1.3", "~> 1.1"])
|
286
291
|
s.add_dependency(%q<io-console>, [">= 0.4.2", "~> 0.4.2"])
|
287
|
-
s.add_dependency(%q<kodama>, [">= 0.1.
|
292
|
+
s.add_dependency(%q<kodama>, [">= 0.1.6", "~> 0.1.2"])
|
288
293
|
s.add_dependency(%q<serverengine>, ["~> 1.5"])
|
289
294
|
s.add_dependency(%q<jeweler>, [">= 1.8.8", "~> 1.8"])
|
290
295
|
s.add_dependency(%q<rspec>, ["~> 3.0"])
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'json'
|
1
2
|
require 'flydata/api/base'
|
2
3
|
|
3
4
|
module Flydata
|
@@ -17,6 +18,14 @@ module Flydata
|
|
17
18
|
def cleanup_sync(data_entry_id, tables)
|
18
19
|
@client.post("/#{@model_name.pluralize}/#{data_entry_id}/cleanup_sync", nil, {tables: tables.join(',')})
|
19
20
|
end
|
21
|
+
|
22
|
+
# Update validity of tables
|
23
|
+
# table_validity_hash: { "bad_table": "error reason", "good_table": nil }
|
24
|
+
# table "bad_table" will be marked invalid with reason "error reason"
|
25
|
+
# table "good table" will be marked valid, that is, clear its error reason if it's set.
|
26
|
+
def update_table_validity(data_entry_id, table_validity_hash)
|
27
|
+
@client.post("/#{@model_name.pluralize}/#{data_entry_id}/update_table_validity", {:headers => {:content_type => :json}}, table_validity_hash.to_json)
|
28
|
+
end
|
20
29
|
end
|
21
30
|
end
|
22
31
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'open3'
|
2
2
|
require 'flydata/command/sync'
|
3
|
+
require 'flydata-core/mysql/command_generator'
|
3
4
|
|
4
5
|
module Flydata
|
5
6
|
module Command
|
@@ -8,7 +9,7 @@ module Flydata
|
|
8
9
|
def run
|
9
10
|
de = retrieve_sync_data_entry
|
10
11
|
de['mysql_data_entry_preference'].delete('tables')
|
11
|
-
cmd =
|
12
|
+
cmd = FlydataCore::Mysql::CommandGenerator::generate_mysql_cmd(de['mysql_data_entry_preference'])
|
12
13
|
if $stdin.tty?
|
13
14
|
# interactive shell
|
14
15
|
system cmd
|
data/lib/flydata/command/sync.rb
CHANGED
@@ -13,9 +13,9 @@ require 'flydata/output/forwarder'
|
|
13
13
|
require 'flydata/parser/mysql/dump_parser'
|
14
14
|
require 'flydata/preference/data_entry_preference'
|
15
15
|
require 'flydata/sync_file_manager'
|
16
|
-
require 'flydata/mysql/mysql_util'
|
17
16
|
require 'flydata-core/table_def'
|
18
17
|
require 'flydata/mysql/table_ddl'
|
18
|
+
require 'flydata-core/mysql/command_generator'
|
19
19
|
#require 'ruby-prof'
|
20
20
|
|
21
21
|
module Flydata
|
@@ -74,11 +74,8 @@ module Flydata
|
|
74
74
|
exit 1
|
75
75
|
end
|
76
76
|
|
77
|
-
# Setup instance variables
|
78
|
-
set_current_tables(tables)
|
79
|
-
|
80
77
|
# Start initial sync with check
|
81
|
-
handle_mysql_sync
|
78
|
+
handle_mysql_sync(tables)
|
82
79
|
|
83
80
|
# Start continuous sync by starting fluentd process
|
84
81
|
unless opts.no_flydata_start?
|
@@ -97,9 +94,6 @@ module Flydata
|
|
97
94
|
# Public method
|
98
95
|
# - Called from Sender#start/restart
|
99
96
|
def try_mysql_sync
|
100
|
-
# Setup instance variables
|
101
|
-
set_current_tables
|
102
|
-
|
103
97
|
# Start initial sync
|
104
98
|
handle_mysql_sync
|
105
99
|
rescue SyncDataEntryError
|
@@ -233,7 +227,7 @@ EOS
|
|
233
227
|
Flydata::MysqlCompatibilityCheck.new(dp, de['mysql_data_entry_preference']).check
|
234
228
|
|
235
229
|
# Set instance variables
|
236
|
-
set_current_tables(tables)
|
230
|
+
set_current_tables(tables, include_invalid_tables: true)
|
237
231
|
|
238
232
|
do_generate_table_ddl(de)
|
239
233
|
end
|
@@ -290,10 +284,18 @@ EOS
|
|
290
284
|
|
291
285
|
# Initial sync
|
292
286
|
|
293
|
-
def handle_mysql_sync
|
287
|
+
def handle_mysql_sync(tables = nil)
|
294
288
|
de = data_entry
|
295
289
|
|
296
|
-
|
290
|
+
# Setup instance variables
|
291
|
+
sync_resumed = set_current_tables(tables)
|
292
|
+
|
293
|
+
if sync_resumed
|
294
|
+
# skip confirmation prompts and resume sync right away. #initial_sync knows
|
295
|
+
# where to resume from.
|
296
|
+
log_info_stdout("Resuming the initial sync...")
|
297
|
+
initial_sync(de, sync_resumed: true)
|
298
|
+
elsif !@new_tables.empty?
|
297
299
|
show_purpose_name
|
298
300
|
unsynced_table_message = "We've noticed that these tables have not been synced yet: #{@new_tables.join(", ")}\n"
|
299
301
|
unless @ddl_tables.empty?
|
@@ -305,7 +307,7 @@ EOS
|
|
305
307
|
end
|
306
308
|
log_info_stdout(unsynced_table_message)
|
307
309
|
if ask_yes_no("Do you want to run initial sync on all of these tables now?")
|
308
|
-
initial_sync(de)
|
310
|
+
initial_sync(de, sync_resumed: false)
|
309
311
|
else
|
310
312
|
#If generate_table_ddl has not been run for these tables, warn user
|
311
313
|
unless @ddl_tables.empty?
|
@@ -318,13 +320,16 @@ EOS
|
|
318
320
|
end
|
319
321
|
end
|
320
322
|
|
321
|
-
def initial_sync(de)
|
323
|
+
def initial_sync(de, opt)
|
322
324
|
# Load sync information from file
|
323
|
-
log_info_stdout("Checking FlyData Servers for existing buffer data...")
|
324
|
-
load_sync_info(de)
|
325
325
|
validate_initial_sync_status
|
326
326
|
begin
|
327
|
-
|
327
|
+
unless @full_initial_sync || opt[:sync_resumed]
|
328
|
+
# flush is unnecessary for full initial sync or sync resume because in either
|
329
|
+
# case it's guaranteed that agent is stopped with no leftover buffer.
|
330
|
+
log_info_stdout("Sending the existing buffer data...")
|
331
|
+
flush_buffer_and_stop(target_tables_for_api)
|
332
|
+
end
|
328
333
|
sync_mysql_to_redshift(de)
|
329
334
|
rescue ServerDataProcessingTimeout => e
|
330
335
|
ee = ServerDataProcessingTimeout.new("Delayed Data Processing")
|
@@ -748,6 +753,7 @@ EOM
|
|
748
753
|
mp = de['mysql_data_entry_preference']
|
749
754
|
|
750
755
|
tables = opts.all_tables? ? @full_tables : (@input_tables.empty? ? @new_tables : @input_tables)
|
756
|
+
|
751
757
|
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?
|
752
758
|
|
753
759
|
%w(host username database).each do |conf_name|
|
@@ -759,7 +765,7 @@ EOM
|
|
759
765
|
|
760
766
|
create_flydata_ctl_table = true
|
761
767
|
option = {skip_parimay_key_check: opts.skip_primary_key_check?}.merge(mp)
|
762
|
-
Mysql::
|
768
|
+
missing_tables = FlydataCore::Mysql::CommandGenerator.each_mysql_tabledef(tables, option) do |mysql_tabledef, error|
|
763
769
|
if error
|
764
770
|
error_list << error.err_hash
|
765
771
|
next
|
@@ -768,18 +774,37 @@ EOM
|
|
768
774
|
puts FlydataCore::TableDef::SyncRedshiftTableDef.from_flydata_tabledef(flydata_tabledef, flydata_ctl_table: create_flydata_ctl_table, schema_name: schema_name, ctl_only: opts.ctl_only?)
|
769
775
|
create_flydata_ctl_table = false
|
770
776
|
end
|
777
|
+
if missing_tables
|
778
|
+
missing_tables.each {|missing_table| error_list << { error: 'table does not exist in the MySQL database', table: missing_table } }
|
779
|
+
end
|
780
|
+
table_validity_hash = {}
|
781
|
+
tables_without_error = tables
|
771
782
|
unless error_list.empty?
|
772
|
-
log_error_stderr("
|
783
|
+
log_error_stderr("\n\nERROR: FlyData Sync will not sync the following table(s) due to an error.")
|
773
784
|
group_error = error_list.group_by {|d| d[:error]}
|
774
|
-
group_error.each_key do |
|
775
|
-
|
776
|
-
|
777
|
-
|
785
|
+
group_error.each_key do |error|
|
786
|
+
group_error[error].each do |hash|
|
787
|
+
if table = hash[:table]
|
788
|
+
log_error_stderr(" - #{table} (#{error})")
|
789
|
+
table_validity_hash[table] = error
|
790
|
+
end
|
778
791
|
end
|
779
792
|
end
|
780
|
-
log_error_stderr(
|
793
|
+
log_error_stderr(<<EOS)
|
794
|
+
To sync these table(s), please fix the error(s) and run "flydata sync:generate_table_ddl" again.
|
795
|
+
EOS
|
796
|
+
tables_without_error = tables - error_list.inject([]){|arr, err| arr << err[:table] if err[:table]}
|
797
|
+
unless tables_without_error.empty?
|
798
|
+
log_error_stderr(<<EOS)
|
799
|
+
|
800
|
+
The other tables are ready to sync. To start sync, run the generated script on the Redshift cluster and run "flydata start".
|
801
|
+
EOS
|
802
|
+
end
|
781
803
|
end
|
782
|
-
|
804
|
+
|
805
|
+
fixed_tables = @invalid_tables & tables_without_error
|
806
|
+
fixed_tables.each {|table| table_validity_hash[table] = nil }
|
807
|
+
flydata.data_entry.update_table_validity(de['id'], {updated_tables: table_validity_hash}) unless table_validity_hash.empty?
|
783
808
|
|
784
809
|
sync_fm = create_sync_file_manager(de)
|
785
810
|
sync_fm.save_generated_ddl(tables_without_error, Mysql::TableDdl::VERSION)
|
@@ -819,20 +844,25 @@ Thank you for using FlyData!
|
|
819
844
|
|
820
845
|
# Utility methods
|
821
846
|
|
822
|
-
def set_current_tables(input_tables = nil)
|
847
|
+
def set_current_tables(input_tables = nil, options = {})
|
823
848
|
de = data_entry
|
824
849
|
sync_fm = create_sync_file_manager(de)
|
825
850
|
@input_tables = input_tables || []
|
826
|
-
@
|
851
|
+
@invalid_tables = de['mysql_data_entry_preference']['invalid_tables'] # tables marked as invalid as a result of previous check
|
852
|
+
@full_tables = options[:include_invalid_tables] ? de['mysql_data_entry_preference']['tables'] + @invalid_tables : de['mysql_data_entry_preference']['tables']
|
827
853
|
|
828
854
|
@new_tables = sync_fm.get_new_table_list(@full_tables, "pos")
|
829
855
|
@ddl_tables = sync_fm.get_new_table_list(@full_tables, "generated_ddl")
|
830
856
|
|
831
857
|
@full_initial_sync = (@new_tables == @full_tables)
|
832
858
|
|
859
|
+
sync_resumed = load_sync_info(sync_fm)
|
860
|
+
|
833
861
|
sync_fm.close
|
834
862
|
|
835
863
|
verify_input_tables(@input_tables, @full_tables)
|
864
|
+
|
865
|
+
sync_resumed
|
836
866
|
end
|
837
867
|
|
838
868
|
def validate_initial_sync_status
|
@@ -847,17 +877,17 @@ Thank you for using FlyData!
|
|
847
877
|
end
|
848
878
|
end
|
849
879
|
|
850
|
-
def load_sync_info(
|
880
|
+
def load_sync_info(sync_fm)
|
881
|
+
sync_resumed = false
|
851
882
|
# for debug
|
852
883
|
raise "!AssertionError. set_current_tables needs to be called in advance" if @full_tables.nil?
|
853
884
|
|
854
|
-
sync_fm = create_sync_file_manager(de)
|
855
885
|
if (rs = sync_fm.load_sync_info)
|
856
886
|
@full_initial_sync = rs[:initial_sync]
|
857
887
|
@input_tables = rs[:tables]
|
888
|
+
sync_resumed = true
|
858
889
|
end
|
859
|
-
|
860
|
-
de
|
890
|
+
sync_resumed
|
861
891
|
end
|
862
892
|
|
863
893
|
def target_tables
|
@@ -886,7 +916,7 @@ Thank you for using FlyData!
|
|
886
916
|
end
|
887
917
|
|
888
918
|
def retrieve_sync_data_entry
|
889
|
-
de = retrieve_data_entries.first
|
919
|
+
de = retrieve_data_entries.first
|
890
920
|
raise "There are no data entries." unless de
|
891
921
|
case de['type']
|
892
922
|
when 'RedshiftMysqlDataEntry'
|
@@ -897,6 +927,7 @@ Thank you for using FlyData!
|
|
897
927
|
else
|
898
928
|
mp['tables'] = mp['tables'].split(",").uniq
|
899
929
|
end
|
930
|
+
mp['invalid_tables'] = mp['invalid_tables'].kind_of?(String) ? mp['invalid_tables'].split(",").uniq : []
|
900
931
|
|
901
932
|
unless mp['ssl_ca_content'].to_s.strip.empty?
|
902
933
|
sync_fm = create_sync_file_manager(de)
|
@@ -1,15 +1,14 @@
|
|
1
1
|
require 'mysql2'
|
2
2
|
require 'flydata/command_loggable'
|
3
|
-
require 'flydata/mysql/
|
3
|
+
require 'flydata-core/mysql/command_generator'
|
4
|
+
require 'flydata-core/mysql/compatibility_checker'
|
5
|
+
require 'flydata-core/errors'
|
4
6
|
|
5
7
|
module Flydata
|
6
8
|
|
7
9
|
class CompatibilityCheck
|
8
10
|
include CommandLoggable
|
9
11
|
|
10
|
-
class CompatibilityError < StandardError
|
11
|
-
end
|
12
|
-
|
13
12
|
def initialize(dp_hash, de_hash=nil, options={})
|
14
13
|
@dp = dp_hash
|
15
14
|
@errors=[]
|
@@ -21,7 +20,7 @@ module Flydata
|
|
21
20
|
send(m)
|
22
21
|
rescue ArgumentError => e
|
23
22
|
# ignore
|
24
|
-
rescue CompatibilityError => e
|
23
|
+
rescue FlydataCore::CompatibilityError => e
|
25
24
|
@errors << e
|
26
25
|
end
|
27
26
|
end
|
@@ -40,8 +39,6 @@ module Flydata
|
|
40
39
|
end
|
41
40
|
|
42
41
|
class AgentCompatibilityCheck < CompatibilityCheck
|
43
|
-
class AgentCompatibilityError < StandardError
|
44
|
-
end
|
45
42
|
|
46
43
|
TCP_PORT=45326
|
47
44
|
SSL_PORT=45327
|
@@ -66,21 +63,13 @@ module Flydata
|
|
66
63
|
errors.each do |port, e|
|
67
64
|
message += " Port #{port}, Error #{e.class.name}: #{e.to_s}"
|
68
65
|
end
|
69
|
-
raise AgentCompatibilityError, message
|
66
|
+
raise FlydataCore::AgentCompatibilityError, message
|
70
67
|
end
|
71
68
|
end
|
72
69
|
end
|
73
70
|
|
74
71
|
class MysqlCompatibilityCheck < CompatibilityCheck
|
75
72
|
|
76
|
-
class MysqlCompatibilityError < StandardError
|
77
|
-
end
|
78
|
-
|
79
|
-
SELECT_QUERY_TMPLT = "SELECT %s"
|
80
|
-
BINLOG_RETENTION_HOURS = 24
|
81
|
-
SELECT_TABLE_INFO_TMPLT = "SELECT table_name, table_type, engine FROM information_schema.tables WHERE table_schema = '%s' and table_name in(%s)"
|
82
|
-
|
83
|
-
#def initialize(de_hash, dump_dir=nil)
|
84
73
|
def initialize(dp_hash, de_hash, options={})
|
85
74
|
super
|
86
75
|
@db_opts = [:host, :port, :username, :password, :database, :ssl_ca].inject({}) {|h, sym| h[sym] = de_hash[sym.to_s]; h}
|
@@ -100,47 +89,11 @@ module Flydata
|
|
100
89
|
end
|
101
90
|
|
102
91
|
def check_mysql_user_compat
|
103
|
-
|
104
|
-
get_grant_regex = /GRANT (?<privs>.*) ON (`)?(?<db_name>[^`]*)(`)?\.\* TO '#{@db_opts[:username]}/
|
105
|
-
necessary_permission_fields = ["SELECT","RELOAD","LOCK TABLES","REPLICATION SLAVE","REPLICATION CLIENT"]
|
106
|
-
all_privileges_field = ["ALL PRIVILEGES"]
|
107
|
-
|
108
|
-
# Do not catch MySQL connection problem because check should stop if no MySQL connection can be made.
|
109
|
-
grants_sql = "SHOW GRANTS"
|
110
|
-
client = Mysql2::Client.new(@db_opts)
|
111
|
-
result = client.query(grants_sql)
|
112
|
-
client.close
|
113
|
-
|
114
|
-
found_priv = Hash[databases.map {|d| [d,[]]}]
|
115
|
-
missing_priv = {}
|
116
|
-
|
117
|
-
result.each do |res|
|
118
|
-
# SHOW GRANTS should only return one column
|
119
|
-
res_value = res.values.first
|
120
|
-
matched_values = res_value.match(get_grant_regex)
|
121
|
-
next unless matched_values
|
122
|
-
line_priv = matched_values["privs"].split(", ")
|
123
|
-
if matched_values["db_name"] == "*"
|
124
|
-
return true if (all_privileges_field - line_priv).empty?
|
125
|
-
databases.each {|d| found_priv[d] << line_priv }
|
126
|
-
elsif databases.include? matched_values["db_name"]
|
127
|
-
if (all_privileges_field - line_priv).empty?
|
128
|
-
found_priv[matched_values["db_name"]] = necessary_permission_fields
|
129
|
-
else
|
130
|
-
found_priv[matched_values["db_name"]] << line_priv
|
131
|
-
end
|
132
|
-
end
|
133
|
-
missing_priv = get_missing_privileges(found_priv, necessary_permission_fields)
|
134
|
-
return true if missing_priv.empty?
|
135
|
-
end
|
136
|
-
error_text = "The user '#{@db_opts[:username]}' does not have the correct permissions to run FlyData Sync\n"
|
137
|
-
error_text << " * These privileges are missing...\n"
|
138
|
-
missing_priv.each_key {|db| error_text << " for the database '#{db}': #{missing_priv[db].join(", ")}\n"}
|
139
|
-
raise MysqlCompatibilityError, error_text
|
92
|
+
FlydataCore::Mysql::SyncPermissionChecker.new(@db_opts).do_check
|
140
93
|
end
|
141
94
|
|
142
95
|
def check_mysql_protocol_tcp_compat
|
143
|
-
query = Mysql::
|
96
|
+
query = FlydataCore::Mysql::CommandGenerator.generate_mysql_show_grants_cmd(@db_opts)
|
144
97
|
|
145
98
|
Open3.popen3(query) do |stdin, stdout, stderr|
|
146
99
|
stdin.close
|
@@ -151,64 +104,27 @@ module Flydata
|
|
151
104
|
log_error("Error occured during access to mysql server.", {err: err_reason})
|
152
105
|
|
153
106
|
unless /Warning: Using a password on the command line interface can be insecure/ === err_reason
|
154
|
-
raise MysqlCompatibilityError, "Cannot connect to MySQL database. Please make sure you can connect with this command:\n $ mysql -u #{@db_opts[:username]} -h #{@db_opts[:host]} -P #{@db_opts[:port]} #{@db_opts[:database]} --protocol=tcp -p"
|
107
|
+
raise FlydataCore::MysqlCompatibilityError, "Cannot connect to MySQL database. Please make sure you can connect with this command:\n $ mysql -u #{@db_opts[:username]} -h #{@db_opts[:host]} -P #{@db_opts[:port]} #{@db_opts[:database]} --protocol=tcp -p"
|
155
108
|
end
|
156
109
|
end
|
157
110
|
end
|
158
111
|
end
|
159
112
|
|
160
113
|
def check_mysql_parameters_compat
|
161
|
-
|
162
|
-
errors={}
|
163
|
-
|
164
|
-
client = Mysql2::Client.new(@db_opts)
|
165
|
-
|
166
|
-
begin
|
167
|
-
sys_var_to_check.each_key do |sys_var|
|
168
|
-
sel_query = SELECT_QUERY_TMPLT % sys_var
|
169
|
-
begin
|
170
|
-
result = client.query(sel_query)
|
171
|
-
unless result.first[sys_var] == sys_var_to_check[sys_var]
|
172
|
-
errors[sys_var]=result.first[sys_var]
|
173
|
-
end
|
174
|
-
rescue Mysql2::Error => e
|
175
|
-
if e.message =~ /Unknown system variable/
|
176
|
-
unless e.message =~ /(binlog_checksum|log_bin_use_v1_row_events)/
|
177
|
-
errors[sys_var] = false
|
178
|
-
end
|
179
|
-
else
|
180
|
-
raise e
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
ensure
|
185
|
-
client.close
|
186
|
-
end
|
187
|
-
unless errors.empty?
|
188
|
-
error_explanation = ""
|
189
|
-
errors.each_key do |err_key|
|
190
|
-
error_explanation << "\n * #{err_key} is #{errors[err_key]} but should be #{sys_var_to_check[err_key]}"
|
191
|
-
end
|
192
|
-
raise MysqlCompatibilityError, "These system variable(s) are not the correct value: #{error_explanation}\n Please change these system variables for FlyData Sync to run correctly"
|
193
|
-
end
|
114
|
+
FlydataCore::Mysql::BinlogParameterChecker.new(@db_opts).do_check
|
194
115
|
end
|
195
116
|
|
196
117
|
def check_mysql_binlog_retention
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
else
|
202
|
-
run_mysql_retention_check(client)
|
203
|
-
end
|
204
|
-
ensure
|
205
|
-
client.close
|
118
|
+
if is_rds?(@db_opts[:host])
|
119
|
+
run_rds_retention_check
|
120
|
+
else
|
121
|
+
run_mysql_retention_check
|
206
122
|
end
|
207
123
|
end
|
208
124
|
|
209
125
|
def check_writing_permissions
|
210
126
|
write_errors = []
|
211
|
-
paths_to_check = [
|
127
|
+
paths_to_check = [FLYDATA_HOME]
|
212
128
|
paths_to_check << @dump_dir unless @dump_dir.to_s.empty?
|
213
129
|
paths_to_check << @backup_dir unless @backup_dir.to_s.empty?
|
214
130
|
paths_to_check.each do |path|
|
@@ -218,71 +134,36 @@ module Flydata
|
|
218
134
|
end
|
219
135
|
unless write_errors.empty?
|
220
136
|
error_dir = write_errors.join(", ")
|
221
|
-
raise MysqlCompatibilityError, "We cannot access the directories: #{error_dir}"
|
137
|
+
raise FlydataCore::MysqlCompatibilityError, "We cannot access the directories: #{error_dir}"
|
222
138
|
end
|
223
139
|
end
|
224
140
|
|
225
141
|
# If table_type='VIEW' or engine='MEMORY', raise error.
|
226
142
|
def check_mysql_table_types
|
227
143
|
return if @tables.empty?
|
228
|
-
|
229
|
-
|
230
|
-
begin
|
231
|
-
invalid_tables = []
|
232
|
-
client.query(sel_query).each do |r|
|
233
|
-
invalid_tables.push(r['table_name']) if r['table_type'] == 'VIEW' || r['engine'] == 'MEMORY'
|
234
|
-
end
|
235
|
-
raise MysqlCompatibilityError, "FlyData does not support VIEW and MEMORY ENGINE table. Remove following tables from data entry: #{invalid_tables.join(", ")}" unless invalid_tables.empty?
|
236
|
-
ensure
|
237
|
-
client.close
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
def get_missing_privileges(found_priv, all_priv)
|
242
|
-
return_hash = {}
|
243
|
-
found_priv.each_key do |key|
|
244
|
-
missing_priv = all_priv - found_priv[key].flatten.uniq
|
245
|
-
return_hash[key] = missing_priv unless missing_priv.empty?
|
246
|
-
end
|
247
|
-
return_hash
|
144
|
+
option = @db_opts.dup.merge(tables: @tables)
|
145
|
+
FlydataCore::Mysql::TableTypeChecker.new(option).do_check
|
248
146
|
end
|
249
147
|
|
250
|
-
def run_mysql_retention_check
|
251
|
-
|
252
|
-
sel_query = SELECT_QUERY_TMPLT % '@@expire_logs_days'
|
253
|
-
result = mysql_client.query(sel_query)
|
254
|
-
if result.first["@@expire_logs_days"]!=0 and result.first["@@expire_logs_days"] <= expire_logs_days_limit
|
255
|
-
raise MysqlCompatibilityError, "Binary log retention is too short\n " +
|
256
|
-
" We recommend the system variable '@@expire_logs_days' to be either set to 0 or at least #{expire_logs_days_limit} days"
|
257
|
-
end
|
148
|
+
def run_mysql_retention_check
|
149
|
+
FlydataCore::Mysql::NonRdsRetentionChecker.new(@db_opts).do_check
|
258
150
|
end
|
259
151
|
|
260
|
-
def run_rds_retention_check
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
log_warn_stderr("[WARNING]Cannot verify RDS retention period on current MySQL user account.\n" +
|
275
|
-
"To see retention period, please run this on your RDS:\n" +
|
276
|
-
" $> call mysql.rds_show_configuration;\n" +
|
277
|
-
"Please verify that the hours is not nil and is at least #{BINLOG_RETENTION_HOURS} hours\n" +
|
278
|
-
"To set binlog retention hours, you can run this on your RDS:\n" +
|
279
|
-
" $> call mysql.rds_set_configuration('binlog retention hours', #{BINLOG_RETENTION_HOURS});\n"
|
280
|
-
)
|
281
|
-
else
|
282
|
-
raise e
|
283
|
-
end
|
152
|
+
def run_rds_retention_check
|
153
|
+
FlydataCore::Mysql::RdsRetentionChecker.new(@db_opts).do_check
|
154
|
+
rescue Mysql2::Error => e
|
155
|
+
if e.message =~ /command denied to user/
|
156
|
+
retention_hours = FlydataCore::Mysql::RdsRetentionChecker::BINLOG_RETENTION_HOURS
|
157
|
+
log_warn_stderr("[WARNING]Cannot verify RDS retention period on current MySQL user account.\n" +
|
158
|
+
"To see retention period, please run this on your RDS:\n" +
|
159
|
+
" $> call mysql.rds_show_configuration;\n" +
|
160
|
+
"Please verify that the hours is not nil and is at least #{retention_hours} hours\n" +
|
161
|
+
"To set binlog retention hours, you can run this on your RDS:\n" +
|
162
|
+
" $> call mysql.rds_set_configuration('binlog retention hours', #{retention_hours});\n"
|
163
|
+
)
|
164
|
+
else
|
165
|
+
raise e
|
284
166
|
end
|
285
|
-
|
286
167
|
end
|
287
168
|
|
288
169
|
def is_rds?(hostname)
|