flydata 0.4.0 → 0.4.1
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/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)
|