<commit_before>module Jetpants
  
  #--
  # Replication and binlog-related methods #####################################
  #++
  
  class DB
    # Changes the master for this instance. Supply a Jetpants::DB indicating the new
    # master, along with options :log_pos, :log_file, :user, :password.
    # Does NOT automatically start replication afterwards on self!
    #
    # If you omit :log_pos or :log_file, uses the current position/file from new_master,
    # though this is only safe if new_master is not receiving writes!
    #
    # If you omit :user and :password, tries obtaining replication credentials from the
    # current node (assuming it is already a slave) or if that fails then from the global
    # settings.
    def change_master_to(new_master, option_hash={})
      return disable_replication! unless new_master   # change_master_to(nil) alias for disable_replication!
      return if new_master == master                  # no change
      
      logfile = option_hash[:log_file]
      pos     = option_hash[:log_pos]
      if !(logfile && pos)
        raise "Cannot use coordinates of a new master that is receiving updates" if new_master.master && ! new_master.repl_paused
        logfile, pos = new_master.binlog_coordinates
      end
      
      repl_user = option_hash[:user]     || replication_credentials[:user]
      repl_pass = option_hash[:password] || replication_credentials[:pass]

      pause_replication if @master && !@repl_paused
      result = mysql_root_cmd "CHANGE MASTER TO " +
        "MASTER_HOST='#{new_master.ip}', " +
        "MASTER_PORT=#{new_master.port}, " +
        "MASTER_LOG_FILE='#{logfile}', " +
        "MASTER_LOG_POS=#{pos}, " +
        "MASTER_USER='#{repl_user}', " + 
        "MASTER_PASSWORD='#{repl_pass}'"
      
      output "Changing master to #{new_master} with coordinates (#{logfile}, #{pos}). #{result}"
      @master.slaves.delete(self) if @master rescue nil
      @master = new_master
      @repl_paused = true
      new_master.slaves << self
    end
    
    # Pauses replication
    def pause_replication
      raise "This DB object has no master" unless master
      return if @repl_paused
      output "Pausing replication from #{@master}."
      output mysql_root_cmd "STOP SLAVE"
      @repl_paused = true
    end
    alias stop_replication pause_replication
    
    # Starts replication, or restarts replication after a pause
    def resume_replication
      raise "This DB object has no master" unless master
      output "Resuming replication from #{@master}."
      output mysql_root_cmd "START SLAVE"
      @repl_paused = false
    end
    alias start_replication resume_replication
    
    # Permanently disables replication. Clears out the SHOW SLAVE STATUS output
    # entirely in MySQL versions that permit this.
    def disable_replication!
      raise "This DB object has no master" unless master
      output "Disabling replication; this db is no longer a slave."

      ver = version_tuple

      # MySQL < 5.5: allows master_host='', which clears out SHOW SLAVE STATUS
      if ver[0] == 5 && ver[1] < 5
        output mysql_root_cmd "STOP SLAVE; CHANGE MASTER TO master_host=''; RESET SLAVE"

      # MySQL 5.5.16+: allows RESET SLAVE ALL, which clears out SHOW SLAVE STATUS
      elsif ver[0] >= 5 && (ver[0] > 5 || ver[1] >= 5) && (ver[0] > 5 || ver[1] > 5 || ver[2] >= 16)
        output mysql_root_cmd "STOP SLAVE; CHANGE MASTER TO master_user='test'; RESET SLAVE ALL"

      # Other versions: no safe way to clear out SHOW SLAVE STATUS.  Still set master_user to 'test'
      # so that we know to ignore the slave status output.
      else
        output mysql_root_cmd "STOP SLAVE; CHANGE MASTER TO master_user='test'; RESET SLAVE"
      end

      @master.slaves.delete(self) rescue nil
      @master = nil
      @repl_paused = nil
    end
    alias reset_replication! disable_replication!
    
    # Wipes out the target instances and turns them into slaves of self.
    # Resumes replication on self afterwards, but does NOT automatically start
    # replication on the targets.
    # You can omit passing in the replication user/pass if this machine is itself
    # a slave OR already has at least one slave OR the global setting is fine to use here.
    # Warning: takes self offline during the process, so don't use on a master that
    # is actively in use by your application!
    def enslave!(targets, repl_user=false, repl_pass=false)
      repl_user ||= replication_credentials[:user]
      repl_pass ||= replication_credentials[:pass]
      disable_monitoring
      pause_replication if master && ! @repl_paused
      file, pos = binlog_coordinates
      clone_to!(targets)
      targets.each do |t| 
        t.change_master_to( self, 
                            log_file: file, 
                            log_pos:  pos, 
                            user:     repl_user, 
                            password: <PASSWORD>  )
      end
      resume_replication if @master # should already have happened from the clone_to! restart anyway, but just to be explicit
      enable_monitoring
    end
    
    # Wipes out the target instances and turns them into slaves of self's master.
    # Resumes replication on self afterwards, but does NOT automatically start
    # replication on the targets.
    # Warning: takes self offline during the process, so don't use on an active slave!
    def enslave_siblings!(targets)
      raise "Can only call enslave_siblings! on a slave instance" unless master
      disable_monitoring
      pause_replication unless @repl_paused
      file, pos = repl_binlog_coordinates
      clone_to!(targets)
      targets.each do |t| 
        t.change_master_to( master, 
                            log_file: file,
                            log_pos:  pos,
                            user:     replication_credentials[:user],
                            password: replication_credentials[:pass]  )
      end
      resume_replication # should already have happened from the clone_to! restart anyway, but just to be explicit
      catch_up_to_master
      enable_monitoring
    end
    
    # Shortcut to call DB#enslave_siblings! on a single target
    def enslave_sibling!(target)
      enslave_siblings!([target])
    end
    
    # Use this on a slave to return [master log file name, position] for how far
    # this slave has executed (in terms of its master's binlogs) in its SQL replication thread.
    def repl_binlog_coordinates(display_info=true)
      raise "This instance is not a slave" unless master
      status = slave_status
      file, pos = status[:relay_master_log_file], status[:exec_master_log_pos].to_i
      output "Has executed through master's binlog coordinates of (#{file}, #{pos})." if display_info
      [file, pos]<commit_msg>need to explicitly re-enable read_only on targets in DB#enslave_siblings! since otherwise restart_mysql will disable it due to the nodes being in orphaned state prior to replication setup<commit_after>module Jetpants
  
  #--
  # Replication and binlog-related methods #####################################
  #++
  
  class DB
    # Changes the master for this instance. Supply a Jetpants::DB indicating the new
    # master, along with options :log_pos, :log_file, :user, :password.
    # Does NOT automatically start replication afterwards on self!
    #
    # If you omit :log_pos or :log_file, uses the current position/file from new_master,
    # though this is only safe if new_master is not receiving writes!
    #
    # If you omit :user and :password, tries obtaining replication credentials from the
    # current node (assuming it is already a slave) or if that fails then from the global
    # settings.
    def change_master_to(new_master, option_hash={})
      return disable_replication! unless new_master   # change_master_to(nil) alias for disable_replication!
      return if new_master == master                  # no change
      
      logfile = option_hash[:log_file]
      pos     = option_hash[:log_pos]
      if !(logfile && pos)
        raise "Cannot use coordinates of a new master that is receiving updates" if new_master.master && ! new_master.repl_paused
        logfile, pos = new_master.binlog_coordinates
      end
      
      repl_user = option_hash[:user]     || replication_credentials[:user]
      repl_pass = option_hash[:password] || replication_credentials[:pass]

      pause_replication if @master && !@repl_paused
      result = mysql_root_cmd "CHANGE MASTER TO " +
        "MASTER_HOST='#{new_master.ip}', " +
        "MASTER_PORT=#{new_master.port}, " +
        "MASTER_LOG_FILE='#{logfile}', " +
        "MASTER_LOG_POS=#{pos}, " +
        "MASTER_USER='#{repl_user}', " + 
        "MASTER_PASSWORD='<PASSWORD>}'"
      
      output "Changing master to #{new_master} with coordinates (#{logfile}, #{pos}). #{result}"
      @master.slaves.delete(self) if @master rescue nil
      @master = new_master
      @repl_paused = true
      new_master.slaves << self
    end
    
    # Pauses replication
    def pause_replication
      raise "This DB object has no master" unless master
      return if @repl_paused
      output "Pausing replication from #{@master}."
      output mysql_root_cmd "STOP SLAVE"
      @repl_paused = true
    end
    alias stop_replication pause_replication
    
    # Starts replication, or restarts replication after a pause
    def resume_replication
      raise "This DB object has no master" unless master
      output "Resuming replication from #{@master}."
      output mysql_root_cmd "START SLAVE"
      @repl_paused = false
    end
    alias start_replication resume_replication
    
    # Permanently disables replication. Clears out the SHOW SLAVE STATUS output
    # entirely in MySQL versions that permit this.
    def disable_replication!
      raise "This DB object has no master" unless master
      output "Disabling replication; this db is no longer a slave."

      ver = version_tuple

      # MySQL < 5.5: allows master_host='', which clears out SHOW SLAVE STATUS
      if ver[0] == 5 && ver[1] < 5
        output mysql_root_cmd "STOP SLAVE; CHANGE MASTER TO master_host=''; RESET SLAVE"

      # MySQL 5.5.16+: allows RESET SLAVE ALL, which clears out SHOW SLAVE STATUS
      elsif ver[0] >= 5 && (ver[0] > 5 || ver[1] >= 5) && (ver[0] > 5 || ver[1] > 5 || ver[2] >= 16)
        output mysql_root_cmd "STOP SLAVE; CHANGE MASTER TO master_user='test'; RESET SLAVE ALL"

      # Other versions: no safe way to clear out SHOW SLAVE STATUS.  Still set master_user to 'test'
      # so that we know to ignore the slave status output.
      else
        output mysql_root_cmd "STOP SLAVE; CHANGE MASTER TO master_user='test'; RESET SLAVE"
      end

      @master.slaves.delete(self) rescue nil
      @master = nil
      @repl_paused = nil
    end
    alias reset_replication! disable_replication!
    
    # Wipes out the target instances and turns them into slaves of self.
    # Resumes replication on self afterwards, but does NOT automatically start
    # replication on the targets.
    # You can omit passing in the replication user/pass if this machine is itself
    # a slave OR already has at least one slave OR the global setting is fine to use here.
    # Warning: takes self offline during the process, so don't use on a master that
    # is actively in use by your application!
    def enslave!(targets, repl_user=false, repl_pass=false)
      repl_user ||= replication_credentials[:user]
      repl_pass ||= replication_credentials[:pass]
      disable_monitoring
      pause_replication if master && ! @repl_paused
      file, pos = binlog_coordinates
      clone_to!(targets)
      targets.each do |t| 
        t.change_master_to( self, 
                            log_file: file, 
                            log_pos:  pos, 
                            user:     repl_user, 
                            password: <PASSWORD>  )
      end
      resume_replication if @master # should already have happened from the clone_to! restart anyway, but just to be explicit
      enable_monitoring
    end
    
    # Wipes out the target instances and turns them into slaves of self's master.
    # Resumes replication on self afterwards, but does NOT automatically start
    # replication on the targets.
    # Warning: takes self offline during the process, so don't use on an active slave!
    def enslave_siblings!(targets)
      raise "Can only call enslave_siblings! on a slave instance" unless master
      disable_monitoring
      pause_replication unless @repl_paused
      file, pos = repl_binlog_coordinates
      clone_to!(targets)
      targets.each do |t| 
        t.change_master_to( master, 
                            log_file: file,
                            log_pos:  pos,
                            user:     replication_credentials[:user],
                            password: replication_credentials[:pass]  )
        t.enable_read_only!
      end
      resume_replication # should already have happened from the clone_to! restart anyway, but just to be explicit
      catch_up_to_master
      enable_monitoring
    end
    
    # Shortcut to call DB#enslave_siblings! on a single target
    def enslave_sibling!(target)
      enslave_siblings!([target])
    end
    
    # Use this on a slave to return [master log file name, position] for how far
    # this slave has executed (in terms of its master's binlogs) in its SQL replication thread.
    def repl_binlog_coordinates(display_info=true)
      raise "This instance is not a slave" unless master
      status = slave_status
      file, pos = status[:relay_master_log_file], status[:exec_master_log_pos].to_i
      output "Has executed through master's binlog coordinates of (#{file}, #{pos})." if display_info
      [file, pos]
{'fraction_non_alphanumeric': 0.07799261351292636, 'fraction_numerical': 0.0033311608371352015, 'mean_word_length': 2.7103707684040836, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3078229', 'n_tokens_mistral': 4025, 'n_tokens_neox': 3893, 'n_words': 1396}