lib/flydata/compatibility_check.rb in flydata-0.4.0 vs lib/flydata/compatibility_check.rb in flydata-0.4.1
- old
+ new
@@ -1,17 +1,16 @@
require 'mysql2'
require 'flydata/command_loggable'
-require 'flydata/mysql/mysql_util'
+require 'flydata-core/mysql/command_generator'
+require 'flydata-core/mysql/compatibility_checker'
+require 'flydata-core/errors'
module Flydata
class CompatibilityCheck
include CommandLoggable
- class CompatibilityError < StandardError
- end
-
def initialize(dp_hash, de_hash=nil, options={})
@dp = dp_hash
@errors=[]
end
@@ -19,11 +18,11 @@
self.methods.grep(/^check_/).each do |m|
begin
send(m)
rescue ArgumentError => e
# ignore
- rescue CompatibilityError => e
+ rescue FlydataCore::CompatibilityError => e
@errors << e
end
end
print_errors
end
@@ -38,12 +37,10 @@
end
end
class AgentCompatibilityCheck < CompatibilityCheck
- class AgentCompatibilityError < StandardError
- end
TCP_PORT=45326
SSL_PORT=45327
def check_outgoing_ports
@@ -64,25 +61,17 @@
unless errors.empty?
message = "Cannot connect to outside ports. Please check to make sure you have these outgoing ports open."
errors.each do |port, e|
message += " Port #{port}, Error #{e.class.name}: #{e.to_s}"
end
- raise AgentCompatibilityError, message
+ raise FlydataCore::AgentCompatibilityError, message
end
end
end
class MysqlCompatibilityCheck < CompatibilityCheck
- class MysqlCompatibilityError < StandardError
- end
-
- SELECT_QUERY_TMPLT = "SELECT %s"
- BINLOG_RETENTION_HOURS = 24
- SELECT_TABLE_INFO_TMPLT = "SELECT table_name, table_type, engine FROM information_schema.tables WHERE table_schema = '%s' and table_name in(%s)"
-
- #def initialize(de_hash, dump_dir=nil)
def initialize(dp_hash, de_hash, options={})
super
@db_opts = [:host, :port, :username, :password, :database, :ssl_ca].inject({}) {|h, sym| h[sym] = de_hash[sym.to_s]; h}
@db_opts[:sslca] = @db_opts[:ssl_ca] # for mysql2 gem
@dump_dir = options[:dump_dir] || nil
@@ -98,192 +87,84 @@
end
raise "Please correct these errors if you wish to run FlyData Sync"
end
def check_mysql_user_compat
- databases = ['mysql', @db_opts[:database]]
- get_grant_regex = /GRANT (?<privs>.*) ON (`)?(?<db_name>[^`]*)(`)?\.\* TO '#{@db_opts[:username]}/
- necessary_permission_fields = ["SELECT","RELOAD","LOCK TABLES","REPLICATION SLAVE","REPLICATION CLIENT"]
- all_privileges_field = ["ALL PRIVILEGES"]
-
- # Do not catch MySQL connection problem because check should stop if no MySQL connection can be made.
- grants_sql = "SHOW GRANTS"
- client = Mysql2::Client.new(@db_opts)
- result = client.query(grants_sql)
- client.close
-
- found_priv = Hash[databases.map {|d| [d,[]]}]
- missing_priv = {}
-
- result.each do |res|
- # SHOW GRANTS should only return one column
- res_value = res.values.first
- matched_values = res_value.match(get_grant_regex)
- next unless matched_values
- line_priv = matched_values["privs"].split(", ")
- if matched_values["db_name"] == "*"
- return true if (all_privileges_field - line_priv).empty?
- databases.each {|d| found_priv[d] << line_priv }
- elsif databases.include? matched_values["db_name"]
- if (all_privileges_field - line_priv).empty?
- found_priv[matched_values["db_name"]] = necessary_permission_fields
- else
- found_priv[matched_values["db_name"]] << line_priv
- end
- end
- missing_priv = get_missing_privileges(found_priv, necessary_permission_fields)
- return true if missing_priv.empty?
- end
- error_text = "The user '#{@db_opts[:username]}' does not have the correct permissions to run FlyData Sync\n"
- error_text << " * These privileges are missing...\n"
- missing_priv.each_key {|db| error_text << " for the database '#{db}': #{missing_priv[db].join(", ")}\n"}
- raise MysqlCompatibilityError, error_text
+ FlydataCore::Mysql::SyncPermissionChecker.new(@db_opts).do_check
end
def check_mysql_protocol_tcp_compat
- query = Mysql::MysqlUtil.generate_mysql_show_grants_cmd(@db_opts)
+ query = FlydataCore::Mysql::CommandGenerator.generate_mysql_show_grants_cmd(@db_opts)
Open3.popen3(query) do |stdin, stdout, stderr|
stdin.close
while !stderr.eof?
lines = []
while line = stderr.gets; lines << line.strip; end
err_reason = lines.join(" ")
log_error("Error occured during access to mysql server.", {err: err_reason})
unless /Warning: Using a password on the command line interface can be insecure/ === err_reason
- 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"
+ 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"
end
end
end
end
def check_mysql_parameters_compat
- sys_var_to_check = {'@@binlog_format'=>'ROW', '@@binlog_checksum'=>'NONE', '@@log_bin_use_v1_row_events'=>1, '@@log_slave_updates'=>1}
- errors={}
-
- client = Mysql2::Client.new(@db_opts)
-
- begin
- sys_var_to_check.each_key do |sys_var|
- sel_query = SELECT_QUERY_TMPLT % sys_var
- begin
- result = client.query(sel_query)
- unless result.first[sys_var] == sys_var_to_check[sys_var]
- errors[sys_var]=result.first[sys_var]
- end
- rescue Mysql2::Error => e
- if e.message =~ /Unknown system variable/
- unless e.message =~ /(binlog_checksum|log_bin_use_v1_row_events)/
- errors[sys_var] = false
- end
- else
- raise e
- end
- end
- end
- ensure
- client.close
- end
- unless errors.empty?
- error_explanation = ""
- errors.each_key do |err_key|
- error_explanation << "\n * #{err_key} is #{errors[err_key]} but should be #{sys_var_to_check[err_key]}"
- end
- 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"
- end
+ FlydataCore::Mysql::BinlogParameterChecker.new(@db_opts).do_check
end
def check_mysql_binlog_retention
- client = Mysql2::Client.new(@db_opts)
- begin
- if is_rds?(@db_opts[:host])
- run_rds_retention_check(client)
- else
- run_mysql_retention_check(client)
- end
- ensure
- client.close
+ if is_rds?(@db_opts[:host])
+ run_rds_retention_check
+ else
+ run_mysql_retention_check
end
end
def check_writing_permissions
write_errors = []
- paths_to_check = ["~/.flydata"]
+ paths_to_check = [FLYDATA_HOME]
paths_to_check << @dump_dir unless @dump_dir.to_s.empty?
paths_to_check << @backup_dir unless @backup_dir.to_s.empty?
paths_to_check.each do |path|
full_path = File.expand_path(path)
full_path = File.dirname(full_path) unless File.directory?(full_path)
write_errors << full_path unless File.writable?(full_path) and File.executable?(full_path)
end
unless write_errors.empty?
error_dir = write_errors.join(", ")
- raise MysqlCompatibilityError, "We cannot access the directories: #{error_dir}"
+ raise FlydataCore::MysqlCompatibilityError, "We cannot access the directories: #{error_dir}"
end
end
# If table_type='VIEW' or engine='MEMORY', raise error.
def check_mysql_table_types
return if @tables.empty?
- client = Mysql2::Client.new(@db_opts)
- sel_query = SELECT_TABLE_INFO_TMPLT % [client.escape(@db_opts[:database]), @tables.collect{|t| "'#{client.escape(t)}'"}.join(", ")]
- begin
- invalid_tables = []
- client.query(sel_query).each do |r|
- invalid_tables.push(r['table_name']) if r['table_type'] == 'VIEW' || r['engine'] == 'MEMORY'
- end
- raise MysqlCompatibilityError, "FlyData does not support VIEW and MEMORY ENGINE table. Remove following tables from data entry: #{invalid_tables.join(", ")}" unless invalid_tables.empty?
- ensure
- client.close
- end
+ option = @db_opts.dup.merge(tables: @tables)
+ FlydataCore::Mysql::TableTypeChecker.new(option).do_check
end
- def get_missing_privileges(found_priv, all_priv)
- return_hash = {}
- found_priv.each_key do |key|
- missing_priv = all_priv - found_priv[key].flatten.uniq
- return_hash[key] = missing_priv unless missing_priv.empty?
- end
- return_hash
+ def run_mysql_retention_check
+ FlydataCore::Mysql::NonRdsRetentionChecker.new(@db_opts).do_check
end
- def run_mysql_retention_check(mysql_client)
- expire_logs_days_limit = BINLOG_RETENTION_HOURS / 24
- sel_query = SELECT_QUERY_TMPLT % '@@expire_logs_days'
- result = mysql_client.query(sel_query)
- if result.first["@@expire_logs_days"]!=0 and result.first["@@expire_logs_days"] <= expire_logs_days_limit
- raise MysqlCompatibilityError, "Binary log retention is too short\n " +
- " We recommend the system variable '@@expire_logs_days' to be either set to 0 or at least #{expire_logs_days_limit} days"
+ def run_rds_retention_check
+ FlydataCore::Mysql::RdsRetentionChecker.new(@db_opts).do_check
+ rescue Mysql2::Error => e
+ if e.message =~ /command denied to user/
+ retention_hours = FlydataCore::Mysql::RdsRetentionChecker::BINLOG_RETENTION_HOURS
+ log_warn_stderr("[WARNING]Cannot verify RDS retention period on current MySQL user account.\n" +
+ "To see retention period, please run this on your RDS:\n" +
+ " $> call mysql.rds_show_configuration;\n" +
+ "Please verify that the hours is not nil and is at least #{retention_hours} hours\n" +
+ "To set binlog retention hours, you can run this on your RDS:\n" +
+ " $> call mysql.rds_set_configuration('binlog retention hours', #{retention_hours});\n"
+ )
+ else
+ raise e
end
- end
-
- def run_rds_retention_check(mysql_client)
- sql_query = "call mysql.rds_show_configuration;"
-
- begin
- result = mysql_client.query(sql_query)
- if result.first["name"]=="binlog retention hours"
- if result.first["value"].nil? or result.first["value"].to_i <= BINLOG_RETENTION_HOURS
- raise MysqlCompatibilityError, "Binary log retention is too short\n" +
- " We recommend setting RDS binlog retention to be at least #{BINLOG_RETENTION_HOURS} hours. To do this, run this on your RDS MySQL database:\n" +
- " $> call mysql.rds_set_configuration('binlog retention hours', 94);"
- end
- end
- rescue Mysql2::Error => e
- if e.message =~ /command denied to user/
- log_warn_stderr("[WARNING]Cannot verify RDS retention period on current MySQL user account.\n" +
- "To see retention period, please run this on your RDS:\n" +
- " $> call mysql.rds_show_configuration;\n" +
- "Please verify that the hours is not nil and is at least #{BINLOG_RETENTION_HOURS} hours\n" +
- "To set binlog retention hours, you can run this on your RDS:\n" +
- " $> call mysql.rds_set_configuration('binlog retention hours', #{BINLOG_RETENTION_HOURS});\n"
- )
- else
- raise e
- end
- end
-
end
def is_rds?(hostname)
hostname.match(/rds.amazonaws.com$/) != nil
end