module Flydata class CompatibilityCheck class CompatibilityError < StandardError end def initialize(dp_hash, de_hash=nil, options={}) @dp = dp_hash @errors=[] end def check self.methods.grep(/^check_/).each do |m| begin send(m) rescue CompatibilityError => e @errors << e end end print_errors end def print_errors return if @errors.empty? puts "There may be some compatibility issues with this current server : " @errors.each do |error| puts " * #{error.message}" end raise "Please correct these errors if you wish to run FlyData Agent" end end class AgentCompatibilityCheck < CompatibilityCheck class AgentCompatibilityError < StandardError end TCP_PORT=45326 SSL_PORT=45327 def check_outgoing_ports unless can_connect_to_port?(TCP_PORT) and can_connect_to_port?(SSL_PORT) raise AgentCompatibilityError, "Cannot connect to outisde ports. Please check to make sure you have these outgoing ports open: #{TCP_PORT},#{SSL_PORT}" end end private def can_connect_to_port?(port) url = @dp["servers"].first begin e = TCPSocket.new(url, port) e.close return true rescue Errno::ETIMEDOUT => e return false end end end class MysqlCompatibilityCheck < CompatibilityCheck class MysqlCompatibilityError < StandardError end SELECT_QUERY_TMPLT = "SELECT %s" #def initialize(de_hash, dump_dir=nil) def initialize(dp_hash, de_hash, options={}) super @db_opts = [:host, :port, :username, :password, :database].inject({}) {|h, sym| h[sym] = de_hash[sym.to_s]; h} @dump_dir = options['dump_dir'] || nil end def print_errors return if @errors.empty? puts "There may be some compatibility issues with your MySQL credentials: " @errors.each do |error| puts " * #{error.message}" end raise "Please correct these errors if you wish to run FlyData Sync" end def check_mysql_user_compat client = Mysql2::Client.new(@db_opts) grants_sql = "SHOW GRANTS" correct_db = ["ON (\\*|#{@db_opts[:database]})","TO '#{@db_opts[:username]}"] necessary_permission_fields= ["SELECT","RELOAD","LOCK TABLES","REPLICATION SLAVE","REPLICATION CLIENT"] all_privileges_field= ["ALL PRIVILEGES"] result = client.query(grants_sql) # Do not catch MySQL connection problem because check should stop if no MySQL connection can be made. client.close missing_priv = [] result.each do |res| # SHOW GRANTS should only return one column res_value = res.values.first if correct_db.all? {|perm| res_value.match(perm)} necessary_permission_fields.each do |priv| missing_priv << priv unless res_value.match(priv) end return true if missing_priv.empty? or all_privileges_field.all? {|d| res_value.match(d)} end end raise MysqlCompatibilityError, "The user '#{@db_opts[:username]}' does not have the correct permissions to run FlyData Sync\n * These privileges are missing: #{missing_priv.join(", ")}" end def check_mysql_protocol_tcp_compat query = "mysql -u #{@db_opts[:username]} -h #{@db_opts[:host]} -P #{@db_opts[:port]} #{@db_opts[:database]} -e \"SHOW GRANTS;\" --protocol=tcp" query << " -p#{@db_opts[:password]}" unless @db_opts[:password].to_s.empty? Open3.popen3(query) do |stdin, stdout, stderr| stdin.close while !stderr.eof? line = stderr.gets unless /Warning: Using a password on the command line interface can be insecure./ === line 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" end end end end def check_mysql_row_mode_compat sys_var_to_check = {'@@binlog_format'=>'ROW', '@@binlog_checksum'=>'NONE', '@@log_bin_use_v1_row_events'=>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 end def check_writing_permissions write_errors = [] paths_to_check = ["~/.flydata"] paths_to_check << @dump_dir unless @dump_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) end unless write_errors.empty? error_dir = write_errors.join(", ") raise MysqlCompatibilityError, "We cannot access the directories: #{error_dir}" end end end end