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