jetpants 0.7.2 → 0.7.4
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.
- data/bin/jetpants +107 -137
 - data/doc/commands.rdoc +4 -4
 - data/doc/faq.rdoc +20 -0
 - data/lib/jetpants.rb +4 -1
 - data/lib/jetpants/db.rb +11 -3
 - data/lib/jetpants/db/client.rb +62 -21
 - data/lib/jetpants/db/import_export.rb +9 -19
 - data/lib/jetpants/db/privileges.rb +13 -10
 - data/lib/jetpants/db/replication.rb +23 -15
 - data/lib/jetpants/db/server.rb +11 -0
 - data/lib/jetpants/db/state.rb +11 -1
 - data/lib/jetpants/host.rb +7 -2
 - data/lib/jetpants/pool.rb +6 -3
 - data/lib/jetpants/topology.rb +20 -2
 - data/plugins/simple_tracker/simple_tracker.rb +5 -6
 - metadata +17 -28
 
    
        data/bin/jetpants
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            #!/usr/bin/env ruby
         
     | 
| 
       2 
2 
     | 
    
         
             
            jetpants_base_dir = File.expand_path(File.dirname(__FILE__) + '/..')
         
     | 
| 
       3 
3 
     | 
    
         
             
            $:.unshift File.join(jetpants_base_dir, 'lib')
         
     | 
| 
       4 
     | 
    
         
            -
            %w[thor pry highline/import  
     | 
| 
      
 4 
     | 
    
         
            +
            %w[thor pry highline/import colored].each {|g| require g}
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            module Jetpants
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
         @@ -63,24 +63,25 @@ module Jetpants 
     | 
|
| 
       63 
63 
     | 
    
         
             
                    demoted.probe
         
     | 
| 
       64 
64 
     | 
    
         
             
                  else
         
     | 
| 
       65 
65 
     | 
    
         
             
                    demoted = ask_node 'Please enter the IP address of the node to demote:'
         
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  if demoted.running?
         
     | 
| 
      
 69 
     | 
    
         
            +
                    error 'Cannot demote a node that has no slaves!' unless demoted.has_slaves?
         
     | 
| 
      
 70 
     | 
    
         
            +
                  else
         
     | 
| 
      
 71 
     | 
    
         
            +
                    inform "Unable to connect to node #{demoted} to demote"
         
     | 
| 
      
 72 
     | 
    
         
            +
                    error  "Unable to perform promotion" unless agree "Please confirm that #{demoted} is offline [yes/no]: "
         
     | 
| 
      
 73 
     | 
    
         
            +
                    
         
     | 
| 
      
 74 
     | 
    
         
            +
                    # An asset-tracker plugin may have been populated the slave list anyway
         
     | 
| 
      
 75 
     | 
    
         
            +
                    if demoted.slaves && demoted.slaves.count > 0
         
     | 
| 
      
 76 
     | 
    
         
            +
                      demoted.slaves.each {|s| s.probe}
         
     | 
| 
       68 
77 
     | 
    
         
             
                    else
         
     | 
| 
       69 
     | 
    
         
            -
                       
     | 
| 
       70 
     | 
    
         
            -
                      error 
     | 
| 
       71 
     | 
    
         
            -
                      
         
     | 
| 
       72 
     | 
    
         
            -
                       
     | 
| 
       73 
     | 
    
         
            -
                       
     | 
| 
       74 
     | 
    
         
            -
                         
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
                        replicas = ask("Please enter a comma-seperated list of IP addresses of all current replicas of #{demoted}: ").split /\s*,\s*/
         
     | 
| 
       77 
     | 
    
         
            -
                        error "No replicas entered" unless replicas && replicas.count > 0
         
     | 
| 
       78 
     | 
    
         
            -
                        error "User supplied list of replicas appears to be invalid - #{replicas}" unless replicas.all? {|replica| is_ip? replica}
         
     | 
| 
       79 
     | 
    
         
            -
                        demoted.instance_eval {@slaves = replicas.map &:to_db}
         
     | 
| 
       80 
     | 
    
         
            -
                        demoted.slaves.each do |replica|
         
     | 
| 
       81 
     | 
    
         
            -
                          # Validate that they are really slaves of demoted
         
     | 
| 
       82 
     | 
    
         
            -
                          error "#{replica} does not appear to be a valid replica of #{demoted}" unless replica.master == demoted
         
     | 
| 
       83 
     | 
    
         
            -
                        end
         
     | 
| 
      
 78 
     | 
    
         
            +
                      replicas = ask("Please enter a comma-seperated list of IP addresses of all current replicas of #{demoted}: ").split /\s*,\s*/
         
     | 
| 
      
 79 
     | 
    
         
            +
                      error "No replicas entered" unless replicas && replicas.count > 0
         
     | 
| 
      
 80 
     | 
    
         
            +
                      error "User supplied list of replicas appears to be invalid - #{replicas}" unless replicas.all? {|replica| is_ip? replica}
         
     | 
| 
      
 81 
     | 
    
         
            +
                      demoted.instance_eval {@slaves = replicas.map &:to_db}
         
     | 
| 
      
 82 
     | 
    
         
            +
                      demoted.slaves.each do |replica|
         
     | 
| 
      
 83 
     | 
    
         
            +
                        # Validate that they are really slaves of demoted
         
     | 
| 
      
 84 
     | 
    
         
            +
                        error "#{replica} does not appear to be a valid replica of #{demoted}" unless replica.master == demoted
         
     | 
| 
       84 
85 
     | 
    
         
             
                      end
         
     | 
| 
       85 
86 
     | 
    
         
             
                    end
         
     | 
| 
       86 
87 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -117,103 +118,51 @@ module Jetpants 
     | 
|
| 
       117 
118 
     | 
    
         
             
                end
         
     | 
| 
       118 
119 
     | 
    
         | 
| 
       119 
120 
     | 
    
         | 
| 
       120 
     | 
    
         
            -
                desc ' 
     | 
| 
       121 
     | 
    
         
            -
                method_option :node, :desc => 'node to query 
     | 
| 
       122 
     | 
    
         
            -
                def  
     | 
| 
       123 
     | 
    
         
            -
                  node =  
     | 
| 
       124 
     | 
    
         
            -
                   
     | 
| 
       125 
     | 
    
         
            -
                   
     | 
| 
       126 
     | 
    
         
            -
                   
     | 
| 
       127 
     | 
    
         
            -
                
         
     | 
| 
       128 
     | 
    
         
            -
                  inform "node (#{node}) is a current slave of #{node.master}" if node.is_slave?
         
     | 
| 
       129 
     | 
    
         
            -
             
     | 
| 
       130 
     | 
    
         
            -
                  if node.has_slaves?
         
     | 
| 
       131 
     | 
    
         
            -
                    current_slaves = Terminal::Table.new :title => "slaves of master (#{node})", :headings => ["slave", "seconds behind master"] do |rows|
         
     | 
| 
       132 
     | 
    
         
            -
                      slaves.each do |slave|
         
     | 
| 
       133 
     | 
    
         
            -
                        rows << [slave, slave.seconds_behind_master]
         
     | 
| 
       134 
     | 
    
         
            -
                      end
         
     | 
| 
       135 
     | 
    
         
            -
                    end
         
     | 
| 
       136 
     | 
    
         
            -
                    puts current_slaves
         
     | 
| 
       137 
     | 
    
         
            -
                  else
         
     | 
| 
       138 
     | 
    
         
            -
                    inform "node (#{node}) currently has no slaves."
         
     | 
| 
       139 
     | 
    
         
            -
                  end
         
     | 
| 
      
 121 
     | 
    
         
            +
                desc 'summary', 'display information about a node in the context of its pool'
         
     | 
| 
      
 122 
     | 
    
         
            +
                method_option :node, :desc => 'IP address of node to query'
         
     | 
| 
      
 123 
     | 
    
         
            +
                def summary
         
     | 
| 
      
 124 
     | 
    
         
            +
                  node = ask_node('Please enter node IP: ', options[:node])
         
     | 
| 
      
 125 
     | 
    
         
            +
                  node.pool(true).probe
         
     | 
| 
      
 126 
     | 
    
         
            +
                  describe node
         
     | 
| 
      
 127 
     | 
    
         
            +
                  node.pool(true).summary(true)
         
     | 
| 
       140 
128 
     | 
    
         
             
                end
         
     | 
| 
       141 
129 
     | 
    
         | 
| 
       142 
130 
     | 
    
         | 
| 
       143 
     | 
    
         
            -
                desc ' 
     | 
| 
       144 
     | 
    
         
            -
                 
     | 
| 
       145 
     | 
    
         
            -
             
     | 
| 
       146 
     | 
    
         
            -
             
     | 
| 
       147 
     | 
    
         
            -
                  node = options[:node] || ask('Please enter the IP address of a node to query for master: ')
         
     | 
| 
       148 
     | 
    
         
            -
                  error "node (#{node}) does not appear to be an IP." unless is_ip? node
         
     | 
| 
       149 
     | 
    
         
            -
                  node = Jetpants::DB.new node
         
     | 
| 
       150 
     | 
    
         
            -
             
     | 
| 
       151 
     | 
    
         
            -
                  if node.has_slaves?
         
     | 
| 
       152 
     | 
    
         
            -
                    inform "node (#{node}) is a master to the following nodes: #{node.slaves.join(', ')}"
         
     | 
| 
       153 
     | 
    
         
            -
                  end rescue error("unable to connect to node #{node}")
         
     | 
| 
      
 131 
     | 
    
         
            +
                desc 'pools', 'display a full summary of every pool in the topology'
         
     | 
| 
      
 132 
     | 
    
         
            +
                def pools
         
     | 
| 
      
 133 
     | 
    
         
            +
                  Jetpants.pools.concurrent_each &:probe
         
     | 
| 
      
 134 
     | 
    
         
            +
                  Jetpants.pools.each &:summary
         
     | 
| 
       154 
135 
     | 
    
         | 
| 
       155 
     | 
    
         
            -
                   
     | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
       157 
     | 
    
         
            -
             
     | 
| 
       158 
     | 
    
         
            -
             
     | 
| 
       159 
     | 
    
         
            -
             
     | 
| 
       160 
     | 
    
         
            -
             
     | 
| 
       161 
     | 
    
         
            -
             
     | 
| 
       162 
     | 
    
         
            -
             
     | 
| 
       163 
     | 
    
         
            -
             
     | 
| 
       164 
     | 
    
         
            -
             
     | 
| 
       165 
     | 
    
         
            -
                   
     | 
| 
       166 
     | 
    
         
            -
             
     | 
| 
      
 136 
     | 
    
         
            +
                  counts = {master: 0, active_slave: 0, standby_slave: 0, backup_slave: 0}
         
     | 
| 
      
 137 
     | 
    
         
            +
                  Jetpants.pools.map(&:nodes).flatten.each {|n| counts[n.role] += 1}
         
     | 
| 
      
 138 
     | 
    
         
            +
                  
         
     | 
| 
      
 139 
     | 
    
         
            +
                  puts
         
     | 
| 
      
 140 
     | 
    
         
            +
                  puts "%4d global pools" % Jetpants.functional_partitions.count
         
     | 
| 
      
 141 
     | 
    
         
            +
                  puts "%4d shard pools" % Jetpants.shards.count
         
     | 
| 
      
 142 
     | 
    
         
            +
                  puts "---- --------------"
         
     | 
| 
      
 143 
     | 
    
         
            +
                  puts "%4d total pools" % Jetpants.pools.count
         
     | 
| 
      
 144 
     | 
    
         
            +
                  puts
         
     | 
| 
      
 145 
     | 
    
         
            +
                  
         
     | 
| 
      
 146 
     | 
    
         
            +
                  total = 0
         
     | 
| 
      
 147 
     | 
    
         
            +
                  counts.each do |role, count|
         
     | 
| 
      
 148 
     | 
    
         
            +
                    puts "%4d %ss" % [count, role.to_s.tr('_', ' ')]
         
     | 
| 
      
 149 
     | 
    
         
            +
                    total += count
         
     | 
| 
       167 
150 
     | 
    
         
             
                  end
         
     | 
| 
      
 151 
     | 
    
         
            +
                  puts "---- --------------"
         
     | 
| 
      
 152 
     | 
    
         
            +
                  puts "%4d total nodes" % total
         
     | 
| 
      
 153 
     | 
    
         
            +
                  puts
         
     | 
| 
       168 
154 
     | 
    
         
             
                end
         
     | 
| 
       169 
     | 
    
         
            -
                
         
     | 
| 
       170 
     | 
    
         
            -
                
         
     | 
| 
       171 
     | 
    
         
            -
                desc 'node_info', 'show information about a given node'
         
     | 
| 
       172 
     | 
    
         
            -
                method_option :node, :desc => 'node to query for information'
         
     | 
| 
       173 
     | 
    
         
            -
                def node_info
         
     | 
| 
       174 
     | 
    
         
            -
                  node = options[:node] || ask('Please enter node: ')
         
     | 
| 
       175 
     | 
    
         
            -
                  error "node address (#{node}) does not appear to be an ip" unless is_ip? node
         
     | 
| 
       176 
     | 
    
         
            -
                
         
     | 
| 
       177 
     | 
    
         
            -
                  node = Jetpants::DB.new node
         
     | 
| 
       178 
     | 
    
         
            -
                  role =  case
         
     | 
| 
       179 
     | 
    
         
            -
                          when node.has_slaves?
         
     | 
| 
       180 
     | 
    
         
            -
                            :master
         
     | 
| 
       181 
     | 
    
         
            -
                          when node.is_slave?
         
     | 
| 
       182 
     | 
    
         
            -
                            :slave
         
     | 
| 
       183 
     | 
    
         
            -
                          else
         
     | 
| 
       184 
     | 
    
         
            -
                            :node
         
     | 
| 
       185 
     | 
    
         
            -
                          end rescue error("unable to connect to node #{node}")
         
     | 
| 
       186 
155 
     | 
    
         | 
| 
       187 
     | 
    
         
            -
             
     | 
| 
       188 
     | 
    
         
            -
                
         
     | 
| 
       189 
     | 
    
         
            -
                   
     | 
| 
       190 
     | 
    
         
            -
                   
     | 
| 
       191 
     | 
    
         
            -
             
     | 
| 
       192 
     | 
    
         
            -
                  node_info << [:read_only, node.read_only? ? 'true' : 'false'] 
         
     | 
| 
       193 
     | 
    
         
            -
                  
         
     | 
| 
       194 
     | 
    
         
            -
                  if node.is_slave?
         
     | 
| 
       195 
     | 
    
         
            -
                    slave_status = node.slave_status
         
     | 
| 
       196 
     | 
    
         
            -
                    slave_siblings = node.master.slaves.reject {|slave| slave == node}
         
     | 
| 
       197 
     | 
    
         
            -
                
         
     | 
| 
       198 
     | 
    
         
            -
                    node_info << [:master, node.master]
         
     | 
| 
       199 
     | 
    
         
            -
                    node_info << [:sibling_slaves, slave_siblings.join(', ')]
         
     | 
| 
       200 
     | 
    
         
            -
                    node_info << [:replicating, node.replicating? ? 'true' : 'false']
         
     | 
| 
       201 
     | 
    
         
            -
                    if node.replicating?
         
     | 
| 
       202 
     | 
    
         
            -
                      node_info << [:seconds_behind_master, node.seconds_behind_master]
         
     | 
| 
       203 
     | 
    
         
            -
                      node_info << [:master_log, slave_status[:master_log_file]]
         
     | 
| 
       204 
     | 
    
         
            -
                      node_info << [:master_position, slave_status[:exec_master_log_pos]]
         
     | 
| 
       205 
     | 
    
         
            -
                    end
         
     | 
| 
      
 156 
     | 
    
         
            +
                desc 'pools_compact', 'display a compact summary (master, name, and size) of every pool in the topology'
         
     | 
| 
      
 157 
     | 
    
         
            +
                def pools_compact
         
     | 
| 
      
 158 
     | 
    
         
            +
                  puts
         
     | 
| 
      
 159 
     | 
    
         
            +
                  Jetpants.shards.each do |s| 
         
     | 
| 
      
 160 
     | 
    
         
            +
                    puts "[%-12s] %8s to %-11s = %3s GB" % [s.ip, s.min_id, s.max_id, s.data_set_size(true)]
         
     | 
| 
       206 
161 
     | 
    
         
             
                  end
         
     | 
| 
       207 
     | 
    
         
            -
                   
     | 
| 
       208 
     | 
    
         
            -
             
     | 
| 
       209 
     | 
    
         
            -
                  if node.has_slaves?
         
     | 
| 
       210 
     | 
    
         
            -
                    current_slaves = Terminal::Table.new :title => "slaves of #{node}", :headings => ["slave", "seconds behind master"] do |rows|
         
     | 
| 
       211 
     | 
    
         
            -
                      node.slaves.each do |slave|
         
     | 
| 
       212 
     | 
    
         
            -
                        rows << [slave, slave.seconds_behind_master]
         
     | 
| 
       213 
     | 
    
         
            -
                      end
         
     | 
| 
       214 
     | 
    
         
            -
                    end
         
     | 
| 
       215 
     | 
    
         
            -
                    puts current_slaves
         
     | 
| 
      
 162 
     | 
    
         
            +
                  Jetpants.functional_partitions.each do |p| 
         
     | 
| 
      
 163 
     | 
    
         
            +
                    puts "[%-12s] %-23s = %3s GB" % [p.ip, p.name, p.data_set_size(true)]
         
     | 
| 
       216 
164 
     | 
    
         
             
                  end
         
     | 
| 
      
 165 
     | 
    
         
            +
                  puts
         
     | 
| 
       217 
166 
     | 
    
         
             
                end
         
     | 
| 
       218 
167 
     | 
    
         | 
| 
       219 
168 
     | 
    
         | 
| 
         @@ -228,9 +177,9 @@ module Jetpants 
     | 
|
| 
       228 
177 
     | 
    
         
             
                method_option :target, :desc => 'IP of node to clone to'
         
     | 
| 
       229 
178 
     | 
    
         
             
                def clone_slave
         
     | 
| 
       230 
179 
     | 
    
         
             
                  puts "This task clones the data set of a standby slave."
         
     | 
| 
       231 
     | 
    
         
            -
                  source =  
     | 
| 
       232 
     | 
    
         
            -
                   
     | 
| 
       233 
     | 
    
         
            -
                   
     | 
| 
      
 180 
     | 
    
         
            +
                  source = ask_node('Please enter IP of node to clone from: ', options[:source])
         
     | 
| 
      
 181 
     | 
    
         
            +
                  describe source
         
     | 
| 
      
 182 
     | 
    
         
            +
                  
         
     | 
| 
       234 
183 
     | 
    
         
             
                  target = options[:target] || ask('Please enter comma-separated list of IP addresses to clone to: ')
         
     | 
| 
       235 
184 
     | 
    
         
             
                  targets = target.split(',').map do |ip|
         
     | 
| 
       236 
185 
     | 
    
         
             
                    ip.strip!
         
     | 
| 
         @@ -257,13 +206,14 @@ module Jetpants 
     | 
|
| 
       257 
206 
     | 
    
         
             
                method_option :node, :desc => 'IP of standby slave to activate'
         
     | 
| 
       258 
207 
     | 
    
         
             
                def activate_slave
         
     | 
| 
       259 
208 
     | 
    
         
             
                  puts "This task turns a standby slave into an active slave, OR alters an active slave's weight."
         
     | 
| 
       260 
     | 
    
         
            -
                  node =  
     | 
| 
      
 209 
     | 
    
         
            +
                  node = ask_node('Please enter node IP: ', options[:node])
         
     | 
| 
      
 210 
     | 
    
         
            +
                  describe node
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
       261 
212 
     | 
    
         
             
                  weight = options[:weight] || ask('Please enter weight, or ENTER for default of 100: ')
         
     | 
| 
       262 
213 
     | 
    
         
             
                  weight = 100 if weight == ''
         
     | 
| 
       263 
214 
     | 
    
         
             
                  weight = weight.to_i
         
     | 
| 
       264 
     | 
    
         
            -
                  error "node address (#{node}) does not appear to be an IP" unless is_ip? node
         
     | 
| 
       265 
215 
     | 
    
         
             
                  error "Adding a slave of weight 0 makes no sense, use pull_slave instead" if weight == 0
         
     | 
| 
       266 
     | 
    
         
            -
                   
     | 
| 
      
 216 
     | 
    
         
            +
                  
         
     | 
| 
       267 
217 
     | 
    
         
             
                  node.pool.mark_slave_active(node, weight)
         
     | 
| 
       268 
218 
     | 
    
         
             
                  Jetpants.topology.write_config
         
     | 
| 
       269 
219 
     | 
    
         
             
                end
         
     | 
| 
         @@ -277,9 +227,9 @@ module Jetpants 
     | 
|
| 
       277 
227 
     | 
    
         
             
                method_option :node, :desc => 'IP of active slave to pull'
         
     | 
| 
       278 
228 
     | 
    
         
             
                def pull_slave
         
     | 
| 
       279 
229 
     | 
    
         
             
                  puts "This task turns an active slave into a standby slave."
         
     | 
| 
       280 
     | 
    
         
            -
                  node =  
     | 
| 
       281 
     | 
    
         
            -
                   
     | 
| 
       282 
     | 
    
         
            -
                   
     | 
| 
      
 230 
     | 
    
         
            +
                  node = ask_node('Please enter node IP: ', options[:node])
         
     | 
| 
      
 231 
     | 
    
         
            +
                  describe node
         
     | 
| 
      
 232 
     | 
    
         
            +
                  raise "Node is not an active slave" unless node.role == :active_slave
         
     | 
| 
       283 
233 
     | 
    
         
             
                  node.pool.mark_slave_standby(node)
         
     | 
| 
       284 
234 
     | 
    
         
             
                  Jetpants.topology.write_config
         
     | 
| 
       285 
235 
     | 
    
         
             
                end
         
     | 
| 
         @@ -288,12 +238,11 @@ module Jetpants 
     | 
|
| 
       288 
238 
     | 
    
         
             
                desc 'destroy_slave', 'remove a standby slave from its pool'
         
     | 
| 
       289 
239 
     | 
    
         
             
                method_option :node, :desc => 'IP of standby slave to remove'
         
     | 
| 
       290 
240 
     | 
    
         
             
                def destroy_slave
         
     | 
| 
       291 
     | 
    
         
            -
                  puts "This task removes a standby slave from its pool entirely. THIS IS PERMANENT, ie, it does a RESET SLAVE on the target."
         
     | 
| 
       292 
     | 
    
         
            -
                  node =  
     | 
| 
       293 
     | 
    
         
            -
                   
     | 
| 
       294 
     | 
    
         
            -
                  node  
     | 
| 
       295 
     | 
    
         
            -
                  raise " 
     | 
| 
       296 
     | 
    
         
            -
                  raise "Aborting" unless ask('Please type YES in all capital letters to confirm: ') == 'YES'
         
     | 
| 
      
 241 
     | 
    
         
            +
                  puts "This task removes a standby/backup slave from its pool entirely. THIS IS PERMANENT, ie, it does a RESET SLAVE on the target."
         
     | 
| 
      
 242 
     | 
    
         
            +
                  node = ask_node('Please enter node IP: ', options[:node])
         
     | 
| 
      
 243 
     | 
    
         
            +
                  describe node
         
     | 
| 
      
 244 
     | 
    
         
            +
                  raise "Node is not a standby or backup slave" unless (node.is_standby? || node.for_backups?)
         
     | 
| 
      
 245 
     | 
    
         
            +
                  raise "Aborting" unless ask('Please type YES in all capital letters to confirm removing node from its pool: ') == 'YES'
         
     | 
| 
       297 
246 
     | 
    
         
             
                  node.pool.remove_slave!(node)
         
     | 
| 
       298 
247 
     | 
    
         
             
                end
         
     | 
| 
       299 
248 
     | 
    
         | 
| 
         @@ -302,10 +251,9 @@ module Jetpants 
     | 
|
| 
       302 
251 
     | 
    
         
             
                method_option :node, :desc => 'IP of standby slave to rebuild'
         
     | 
| 
       303 
252 
     | 
    
         
             
                def rebuild_slave
         
     | 
| 
       304 
253 
     | 
    
         
             
                  puts "This task exports all data on a standby/backup slave and then re-imports it."
         
     | 
| 
       305 
     | 
    
         
            -
                  node =  
     | 
| 
       306 
     | 
    
         
            -
                   
     | 
| 
       307 
     | 
    
         
            -
                  node  
     | 
| 
       308 
     | 
    
         
            -
                  raise "Node is not a standby or backup slave" unless node.is_standby? || node.for_backups?
         
     | 
| 
      
 254 
     | 
    
         
            +
                  node = ask_node('Please enter node IP: ', options[:node])
         
     | 
| 
      
 255 
     | 
    
         
            +
                  describe node
         
     | 
| 
      
 256 
     | 
    
         
            +
                  raise "Node is not a standby or backup slave" unless (node.is_standby? || node.for_backups?)
         
     | 
| 
       309 
257 
     | 
    
         
             
                  raise "Cannot rebuild non-shard slaves from command suite; use jetpants console instead" unless node.pool.is_a?(Shard)
         
     | 
| 
       310 
258 
     | 
    
         
             
                  node.rebuild!
         
     | 
| 
       311 
259 
     | 
    
         
             
                end
         
     | 
| 
         @@ -417,11 +365,7 @@ module Jetpants 
     | 
|
| 
       417 
365 
     | 
    
         
             
                method_option :min_id, :desc => 'Minimum ID of parent shard being split'
         
     | 
| 
       418 
366 
     | 
    
         
             
                method_option :max_id, :desc => 'Maximum ID of parent shard being split'
         
     | 
| 
       419 
367 
     | 
    
         
             
                def shard_split_child_writes
         
     | 
| 
       420 
     | 
    
         
            -
                   
     | 
| 
       421 
     | 
    
         
            -
                  shard_max = options[:max_id] || ask('Please enter max ID of the parent shard: ')
         
     | 
| 
       422 
     | 
    
         
            -
                  s = Jetpants.topology.shard shard_min, shard_max
         
     | 
| 
       423 
     | 
    
         
            -
                  raise "Shard not found" unless s
         
     | 
| 
       424 
     | 
    
         
            -
                  raise "Shard isn't in expected state" unless s.state == :deprecated && s.children.count > 1
         
     | 
| 
      
 368 
     | 
    
         
            +
                  s = ask_shard_being_split
         
     | 
| 
       425 
369 
     | 
    
         
             
                  s.move_writes_to_children
         
     | 
| 
       426 
370 
     | 
    
         
             
                  Jetpants.topology.write_config
         
     | 
| 
       427 
371 
     | 
    
         
             
                end
         
     | 
| 
         @@ -439,11 +383,7 @@ module Jetpants 
     | 
|
| 
       439 
383 
     | 
    
         
             
                method_option :min_id, :desc => 'Minimum ID of parent shard being split'
         
     | 
| 
       440 
384 
     | 
    
         
             
                method_option :max_id, :desc => 'Maximum ID of parent shard being split'
         
     | 
| 
       441 
385 
     | 
    
         
             
                def shard_split_cleanup
         
     | 
| 
       442 
     | 
    
         
            -
                   
     | 
| 
       443 
     | 
    
         
            -
                  shard_max = options[:max_id] || ask('Please enter max ID of the parent shard: ')
         
     | 
| 
       444 
     | 
    
         
            -
                  s = Jetpants.topology.shard shard_min, shard_max
         
     | 
| 
       445 
     | 
    
         
            -
                  raise "Shard not found" unless s
         
     | 
| 
       446 
     | 
    
         
            -
                  raise "Shard isn't in expected state" unless s.state == :deprecated && s.children.count > 1
         
     | 
| 
      
 386 
     | 
    
         
            +
                  s = ask_shard_being_split
         
     | 
| 
       447 
387 
     | 
    
         
             
                  s.cleanup!
         
     | 
| 
       448 
388 
     | 
    
         
             
                end
         
     | 
| 
       449 
389 
     | 
    
         
             
                def self.after_shard_split_cleanup
         
     | 
| 
         @@ -456,6 +396,11 @@ module Jetpants 
     | 
|
| 
       456 
396 
     | 
    
         
             
                desc 'shard_cutover', 'truncate the current last shard range, and add a new shard after it'
         
     | 
| 
       457 
397 
     | 
    
         
             
                method_option :cutover_id, :desc => 'Minimum ID of new last shard being created'
         
     | 
| 
       458 
398 
     | 
    
         
             
                def shard_cutover
         
     | 
| 
      
 399 
     | 
    
         
            +
                  # ensure the spares are available before beginning
         
     | 
| 
      
 400 
     | 
    
         
            +
                  raise "Not enough total spare machines!" unless Jetpants.topology.count_spares >= Jetpants.standby_slaves_per_pool + 1
         
     | 
| 
      
 401 
     | 
    
         
            +
                  raise "Not enough standby_slave role spare machines!" unless Jetpants.topology.count_spares(role: 'standby_slave') >= Jetpants.standby_slaves_per_pool
         
     | 
| 
      
 402 
     | 
    
         
            +
                  raise "Cannot find a spare master-role machine!" unless Jetpants.topology.count_spares(role: 'master') >= 1
         
     | 
| 
      
 403 
     | 
    
         
            +
             
     | 
| 
       459 
404 
     | 
    
         
             
                  cutover_id = options[:cutover_id] || ask('Please enter min ID of the new shard to be created: ')
         
     | 
| 
       460 
405 
     | 
    
         
             
                  cutover_id = cutover_id.to_i
         
     | 
| 
       461 
406 
     | 
    
         
             
                  last_shard = Jetpants.topology.shards.select {|s| s.max_id == 'INFINITY' && s.in_config?}.first
         
     | 
| 
         @@ -511,11 +456,36 @@ module Jetpants 
     | 
|
| 
       511 
456 
     | 
    
         
             
                    puts message.blue
         
     | 
| 
       512 
457 
     | 
    
         
             
                  end
         
     | 
| 
       513 
458 
     | 
    
         | 
| 
       514 
     | 
    
         
            -
                  def  
     | 
| 
       515 
     | 
    
         
            -
                    node  
     | 
| 
      
 459 
     | 
    
         
            +
                  def describe node
         
     | 
| 
      
 460 
     | 
    
         
            +
                    puts "Node #{node} (#{node.hostname}:#{node.port}) has role #{node.role} in pool #{node.pool(true)}.".green
         
     | 
| 
      
 461 
     | 
    
         
            +
                  end
         
     | 
| 
      
 462 
     | 
    
         
            +
                  
         
     | 
| 
      
 463 
     | 
    
         
            +
                  def ask_node(prompt, supplied_node=false)
         
     | 
| 
      
 464 
     | 
    
         
            +
                    node = supplied_node || ask(prompt)
         
     | 
| 
       516 
465 
     | 
    
         
             
                    error "Node (#{node}) does not appear to be an IP address." unless is_ip? node
         
     | 
| 
       517 
466 
     | 
    
         
             
                    node.to_db
         
     | 
| 
       518 
467 
     | 
    
         
             
                  end
         
     | 
| 
      
 468 
     | 
    
         
            +
                  
         
     | 
| 
      
 469 
     | 
    
         
            +
                  def ask_shard_being_split
         
     | 
| 
      
 470 
     | 
    
         
            +
                    shards_being_split = Jetpants.shards.select {|s| s.children.count > 0}
         
     | 
| 
      
 471 
     | 
    
         
            +
                    if shards_being_split.count == 0
         
     | 
| 
      
 472 
     | 
    
         
            +
                      raise 'No shards are currently being split. You can only use this task after running "jetpants shard_split".'
         
     | 
| 
      
 473 
     | 
    
         
            +
                    elsif shards_being_split.count == 1
         
     | 
| 
      
 474 
     | 
    
         
            +
                      s = shards_being_split[0]
         
     | 
| 
      
 475 
     | 
    
         
            +
                      puts "Detected #{s} as the only shard currently involved in a split operation."
         
     | 
| 
      
 476 
     | 
    
         
            +
                      error "Aborting." unless agree "Is this the right shard that you want to perform this action on? [yes/no]: "
         
     | 
| 
      
 477 
     | 
    
         
            +
                    else
         
     | 
| 
      
 478 
     | 
    
         
            +
                      puts "The following shards are currently involved in a split operation:"
         
     | 
| 
      
 479 
     | 
    
         
            +
                      shards_being_split.each {|sbs| puts "* #{sbs}"}
         
     | 
| 
      
 480 
     | 
    
         
            +
                      puts "Which shard would you like to perform this action on?"
         
     | 
| 
      
 481 
     | 
    
         
            +
                      shard_min = options[:min_id] || ask('Please enter min ID of the parent shard: ')
         
     | 
| 
      
 482 
     | 
    
         
            +
                      shard_max = options[:max_id] || ask('Please enter max ID of the parent shard: ')
         
     | 
| 
      
 483 
     | 
    
         
            +
                      s = Jetpants.topology.shard shard_min, shard_max
         
     | 
| 
      
 484 
     | 
    
         
            +
                      raise "Shard not found" unless s
         
     | 
| 
      
 485 
     | 
    
         
            +
                    end
         
     | 
| 
      
 486 
     | 
    
         
            +
                    raise "Shard isn't in expected state" unless s.state == :deprecated
         
     | 
| 
      
 487 
     | 
    
         
            +
                    s
         
     | 
| 
      
 488 
     | 
    
         
            +
                  end
         
     | 
| 
       519 
489 
     | 
    
         
             
                end
         
     | 
| 
       520 
490 
     | 
    
         | 
| 
       521 
491 
     | 
    
         
             
                def self.reminders(*strings)
         
     | 
    
        data/doc/commands.rdoc
    CHANGED
    
    | 
         @@ -103,13 +103,13 @@ With an asset tracker, \Jetpants allows you to mark a shard as read-only or comp 
     | 
|
| 
       103 
103 
     | 
    
         | 
| 
       104 
104 
     | 
    
         
             
            == Informational commands
         
     | 
| 
       105 
105 
     | 
    
         | 
| 
       106 
     | 
    
         
            -
            These commands  
     | 
| 
      
 106 
     | 
    
         
            +
            These commands display status information about a particular node, pool, or the entire topology.
         
     | 
| 
       107 
107 
     | 
    
         | 
| 
       108 
     | 
    
         
            -
            <b><tt>jetpants  
     | 
| 
      
 108 
     | 
    
         
            +
            <b><tt>jetpants summary</tt></b> displays information about a node, along with its pool. Does not require an asset tracker.
         
     | 
| 
       109 
109 
     | 
    
         | 
| 
       110 
     | 
    
         
            -
            <b><tt>jetpants  
     | 
| 
      
 110 
     | 
    
         
            +
            <b><tt>jetpants pools</tt></b> displays full information about all pools (name, size, full node list including roles), and then displays counts of all nodes by role. Requires an asset tracker to obtain the list of all pools. This command may take a minute or two to run (depending on topology size) since it has to probe every node to determine roles.
         
     | 
| 
       111 
111 
     | 
    
         | 
| 
       112 
     | 
    
         
            -
            <b><tt>jetpants  
     | 
| 
      
 112 
     | 
    
         
            +
            <b><tt>jetpants pools_compact</tt></b> displays condensed information about all pools (name, master IP, size). Requires an asset tracker to obtain the list of all pools.
         
     | 
| 
       113 
113 
     | 
    
         | 
| 
       114 
114 
     | 
    
         | 
| 
       115 
115 
     | 
    
         
             
            == Miscellaneous
         
     | 
    
        data/doc/faq.rdoc
    CHANGED
    
    | 
         @@ -7,6 +7,26 @@ 
     | 
|
| 
       7 
7 
     | 
    
         
             
            The benefit of a toolkit is that you can still leverage standard MySQL replication, still use InnoDB/XtraDB as a robust storage engine choice, etc. \Jetpants largely doesn't interfere with any of that, and instead just provides tools to help you manage a large MySQL topology and support a range-based sharding scheme.
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         | 
| 
      
 10 
     | 
    
         
            +
            == If it's not a server, how does my application know which pool/shard to send queries to?
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            \Jetpants doesn't perform query routing; it leaves that to your application. The purpose of Jetpants is to *operationally* manage your database topology.  It provides an object hierarchy that allows you to manipulate and automate your DB topology, and offers efficient implementations of common operational tasks in a sharded environment (bulk data importing/exporting, replica cloning, shard splitting, master promotion).
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            That said, you could integrate it into an application and implement query routing using its methods like Jetpants::Topology#shard_db_for_id. This method just compares a sharding key value to the list of shard ranges, to figure out which DB the query should go to.  It's simple to port this logic to non-Ruby apps as well, since the list of shard ranges can be expressed in a portable format like YAML or JSON.
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            Doing this more seamlessly would require some amount of parsing of SQL in your app or service.  This might be a small amount of code (if you're just scanning queries for "WHERE sharding_key_column = ?") or might be a lot (if you're trying to support joins and complex expressions).
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            == Can I use \Jetpants to query some or all shards at once?
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            Absolutely! You can even use it to implement some simple map/reduce-style distributed computation in MySQL:
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
               Jetpants.topology.shards.concurrent_map {|s| s.query_return_first_value 'SELECT MAX(id) from foo'}.reduce(0) {|overall_max, val| [val, overall_max].max}
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            This example concurrently queries all shards to determine the maximum ID in existence for table foo. This might be useful if, say, you're using Memcached as an ID generation service, and need to determine the previous highest-assigned ID after a restart or failover event.
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            Another simple example would be to obtain counts of rows across all shards. In theory you can apply this same technique to other map/reduce style requirements with arbitrarily complex queries.
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
       10 
30 
     | 
    
         
             
            == Is \Jetpants still useful if my architecture isn't sharded?
         
     | 
| 
       11 
31 
     | 
    
         | 
| 
       12 
32 
     | 
    
         
             
            Potentially, since \Jetpants fully supports "global" pools, also known as "functional partitions". You can even use \Jetpants to help manage a standard single-pool MySQL topology (1 master and some number of slaves) for handling common operations like slave cloning and master promotions. That said, there are other tools that may be easier to use if your MySQL footprint is smaller than, say, a dozen machines.
         
     | 
    
        data/lib/jetpants.rb
    CHANGED
    
    | 
         @@ -60,7 +60,9 @@ module Jetpants 
     | 
|
| 
       60 
60 
     | 
    
         
             
                # Returns a hash containing :user => username string, :pass => password string
         
     | 
| 
       61 
61 
     | 
    
         
             
                # for the MySQL replication user, as found in Jetpants' configuration. Plugins
         
     | 
| 
       62 
62 
     | 
    
         
             
                # may freely override this if there's a better way to obtain this password --
         
     | 
| 
       63 
     | 
    
         
            -
                # for example, by parsing master.info on a slave in your topology.
         
     | 
| 
      
 63 
     | 
    
         
            +
                # for example, by parsing master.info on a specific slave in your topology.
         
     | 
| 
      
 64 
     | 
    
         
            +
                # SEE ALSO: DB#replication_credentials, which only falls back to the global
         
     | 
| 
      
 65 
     | 
    
         
            +
                # version when needed.
         
     | 
| 
       64 
66 
     | 
    
         
             
                def replication_credentials
         
     | 
| 
       65 
67 
     | 
    
         
             
                  {user: @config['mysql_repl_user'], pass: @config['mysql_repl_password']}
         
     | 
| 
       66 
68 
     | 
    
         
             
                end
         
     | 
| 
         @@ -97,4 +99,5 @@ module Jetpants 
     | 
|
| 
       97 
99 
     | 
    
         | 
| 
       98 
100 
     | 
    
         
             
              # Finally, initialize topology object
         
     | 
| 
       99 
101 
     | 
    
         
             
              @topology = Topology.new
         
     | 
| 
      
 102 
     | 
    
         
            +
              @topology.load_pools
         
     | 
| 
       100 
103 
     | 
    
         
             
            end
         
     | 
    
        data/lib/jetpants/db.rb
    CHANGED
    
    | 
         @@ -31,6 +31,10 @@ module Jetpants 
     | 
|
| 
       31 
31 
     | 
    
         
             
                @@all_dbs = {}
         
     | 
| 
       32 
32 
     | 
    
         
             
                @@all_dbs_mutex = Mutex.new
         
     | 
| 
       33 
33 
     | 
    
         | 
| 
      
 34 
     | 
    
         
            +
                def self.clear
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @@all_dbs_mutex.synchronize {@@all_dbs = {}}
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
                
         
     | 
| 
       34 
38 
     | 
    
         
             
                # Because this class is rather large, methods have been grouped together
         
     | 
| 
       35 
39 
     | 
    
         
             
                # and moved to separate files in lib/jetpants/db. We load these all now.
         
     | 
| 
       36 
40 
     | 
    
         
             
                # They each just re-open the DB class and add some methods.
         
     | 
| 
         @@ -51,15 +55,19 @@ module Jetpants 
     | 
|
| 
       51 
55 
     | 
    
         | 
| 
       52 
56 
     | 
    
         
             
                def initialize(ip, port=3306)
         
     | 
| 
       53 
57 
     | 
    
         
             
                  @ip, @port = ip, port.to_i
         
     | 
| 
       54 
     | 
    
         
            -
                  @ 
     | 
| 
      
 58 
     | 
    
         
            +
                  @host = Host.new(ip)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  
         
     | 
| 
      
 60 
     | 
    
         
            +
                  # These get set upon DB#probe being run
         
     | 
| 
       55 
61 
     | 
    
         
             
                  @master = nil
         
     | 
| 
       56 
62 
     | 
    
         
             
                  @slaves = nil
         
     | 
| 
       57 
63 
     | 
    
         
             
                  @repl_paused = nil
         
     | 
| 
       58 
64 
     | 
    
         
             
                  @running = nil
         
     | 
| 
       59 
     | 
    
         
            -
                   
     | 
| 
      
 65 
     | 
    
         
            +
                  
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # These get set upon DB#connect being run
         
     | 
| 
      
 67 
     | 
    
         
            +
                  @user = nil
         
     | 
| 
      
 68 
     | 
    
         
            +
                  @schema = nil
         
     | 
| 
       60 
69 
     | 
    
         
             
                end
         
     | 
| 
       61 
70 
     | 
    
         | 
| 
       62 
     | 
    
         
            -
                
         
     | 
| 
       63 
71 
     | 
    
         
             
                ###### Host methods ########################################################
         
     | 
| 
       64 
72 
     | 
    
         | 
| 
       65 
73 
     | 
    
         
             
                # Jetpants::DB delegates missing methods to its Jetpants::Host.
         
     | 
    
        data/lib/jetpants/db/client.rb
    CHANGED
    
    | 
         @@ -5,21 +5,24 @@ module Jetpants 
     | 
|
| 
       5 
5 
     | 
    
         
             
              #++
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
              class DB
         
     | 
| 
       8 
     | 
    
         
            -
                # Runs the provided SQL statement as root,  
     | 
| 
      
 8 
     | 
    
         
            +
                # Runs the provided SQL statement as root, locally via an SSH command line, and
         
     | 
| 
      
 9 
     | 
    
         
            +
                # returns the response as a single string.
         
     | 
| 
       9 
10 
     | 
    
         
             
                # Available options:
         
     | 
| 
       10 
11 
     | 
    
         
             
                # :terminator:: how to terminate the query, such as '\G' or ';'. (default: '\G')
         
     | 
| 
       11 
12 
     | 
    
         
             
                # :parse:: parse a single-row, vertical-format result (:terminator must be '\G') and return it as a hash
         
     | 
| 
      
 13 
     | 
    
         
            +
                # :schema:: name of schema to use, or true to use this DB's default. This may have implications when used with filtered replication! (default: nil, meaning no schema)
         
     | 
| 
       12 
14 
     | 
    
         
             
                # :attempts:: by default, queries will be attempted up to 3 times. set this to 0 or false for non-idempotent queries.
         
     | 
| 
       13 
15 
     | 
    
         
             
                def mysql_root_cmd(cmd, options={})
         
     | 
| 
       14 
16 
     | 
    
         
             
                  terminator = options[:terminator] || '\G'
         
     | 
| 
       15 
17 
     | 
    
         
             
                  attempts = (options[:attempts].nil? ? 3 : (options[:attempts].to_i || 1))
         
     | 
| 
      
 18 
     | 
    
         
            +
                  schema = (options[:schema] == true ? app_schema : options[:schema])
         
     | 
| 
       16 
19 
     | 
    
         
             
                  failures = 0
         
     | 
| 
       17 
20 
     | 
    
         | 
| 
       18 
21 
     | 
    
         
             
                  begin
         
     | 
| 
       19 
22 
     | 
    
         
             
                    raise "MySQL is not running" unless running?
         
     | 
| 
       20 
23 
     | 
    
         
             
                    supply_root_pw = (Jetpants.mysql_root_password ? "-p#{Jetpants.mysql_root_password}" : '')
         
     | 
| 
       21 
24 
     | 
    
         
             
                    supply_port = (@port == 3306 ? '' : "-h 127.0.0.1 -P #{@port}")
         
     | 
| 
       22 
     | 
    
         
            -
                    real_cmd = %Q{mysql #{supply_root_pw} #{supply_port} -ss -e "#{cmd}#{terminator}" #{ 
     | 
| 
      
 25 
     | 
    
         
            +
                    real_cmd = %Q{mysql #{supply_root_pw} #{supply_port} -ss -e "#{cmd}#{terminator}" #{schema}}
         
     | 
| 
       23 
26 
     | 
    
         
             
                    real_cmd.untaint
         
     | 
| 
       24 
27 
     | 
    
         
             
                    result = ssh_cmd!(real_cmd)
         
     | 
| 
       25 
28 
     | 
    
         
             
                    raise result if result && result.downcase.start_with?('error ')
         
     | 
| 
         @@ -34,53 +37,91 @@ module Jetpants 
     | 
|
| 
       34 
37 
     | 
    
         
             
                  end
         
     | 
| 
       35 
38 
     | 
    
         
             
                end
         
     | 
| 
       36 
39 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
                # Returns a Sequel database object
         
     | 
| 
       38 
     | 
    
         
            -
                 
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
      
 40 
     | 
    
         
            +
                # Returns a Sequel database object for use in sending queries to the DB remotely.
         
     | 
| 
      
 41 
     | 
    
         
            +
                # Initializes (or re-initializes) the connection pool upon first use or upon
         
     | 
| 
      
 42 
     | 
    
         
            +
                # requesting a different user or schema. Note that we only maintain one connection
         
     | 
| 
      
 43 
     | 
    
         
            +
                # pool per DB.
         
     | 
| 
      
 44 
     | 
    
         
            +
                # Valid options include :user, :pass, :schema, :max_conns or omit these to use
         
     | 
| 
      
 45 
     | 
    
         
            +
                # defaults.
         
     | 
| 
      
 46 
     | 
    
         
            +
                def connect(options={})
         
     | 
| 
      
 47 
     | 
    
         
            +
                  options[:user]    ||= app_credentials[:user]
         
     | 
| 
      
 48 
     | 
    
         
            +
                  options[:schema]  ||= app_schema
         
     | 
| 
      
 49 
     | 
    
         
            +
                  
         
     | 
| 
      
 50 
     | 
    
         
            +
                  return @db if @db && @user == options[:user] && @schema == options[:schema]
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  disconnect if @db
         
     | 
| 
      
 53 
     | 
    
         
            +
                  
         
     | 
| 
       40 
54 
     | 
    
         
             
                  @db = Sequel.connect(
         
     | 
| 
       41 
55 
     | 
    
         
             
                    :adapter          =>  'mysql2',
         
     | 
| 
       42 
56 
     | 
    
         
             
                    :host             =>  @ip,
         
     | 
| 
       43 
57 
     | 
    
         
             
                    :port             =>  @port,
         
     | 
| 
       44 
     | 
    
         
            -
                    :user             =>   
     | 
| 
       45 
     | 
    
         
            -
                    :password         =>   
     | 
| 
       46 
     | 
    
         
            -
                    :database         =>   
     | 
| 
       47 
     | 
    
         
            -
                    :max_connections  =>  Jetpants.max_concurrency)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    :user             =>  options[:user],
         
     | 
| 
      
 59 
     | 
    
         
            +
                    :password         =>  options[:pass] || app_credentials[:pass],
         
     | 
| 
      
 60 
     | 
    
         
            +
                    :database         =>  options[:schema],
         
     | 
| 
      
 61 
     | 
    
         
            +
                    :max_connections  =>  options[:max_conns] || Jetpants.max_concurrency)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  @user = options[:user]
         
     | 
| 
      
 63 
     | 
    
         
            +
                  @schema = options[:schema]
         
     | 
| 
      
 64 
     | 
    
         
            +
                  @db
         
     | 
| 
       48 
65 
     | 
    
         
             
                end
         
     | 
| 
       49 
     | 
    
         
            -
                alias init_db_connection_pool mysql
         
     | 
| 
       50 
66 
     | 
    
         | 
| 
       51 
     | 
    
         
            -
                # Closes  
     | 
| 
       52 
     | 
    
         
            -
                 
     | 
| 
       53 
     | 
    
         
            -
                # a literal true value to switch to the default app user in Jetpants configuration
         
     | 
| 
       54 
     | 
    
         
            -
                def reconnect(new_user=false)
         
     | 
| 
       55 
     | 
    
         
            -
                  @user = (new_user == true ? Jetpants.app_credentials[:user] : new_user) if new_user
         
     | 
| 
      
 67 
     | 
    
         
            +
                # Closes the database connection(s) in the connection pool.
         
     | 
| 
      
 68 
     | 
    
         
            +
                def disconnect
         
     | 
| 
       56 
69 
     | 
    
         
             
                  if @db
         
     | 
| 
       57 
70 
     | 
    
         
             
                    @db.disconnect rescue nil
         
     | 
| 
       58 
71 
     | 
    
         
             
                    @db = nil
         
     | 
| 
       59 
72 
     | 
    
         
             
                  end
         
     | 
| 
       60 
     | 
    
         
            -
                   
     | 
| 
      
 73 
     | 
    
         
            +
                  @user = nil
         
     | 
| 
      
 74 
     | 
    
         
            +
                  @schema = nil
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
                
         
     | 
| 
      
 77 
     | 
    
         
            +
                # Disconnects and reconnects to the database.
         
     | 
| 
      
 78 
     | 
    
         
            +
                def reconnect(options={})
         
     | 
| 
      
 79 
     | 
    
         
            +
                  disconnect # force disconnection even if we're not changing user or schema
         
     | 
| 
      
 80 
     | 
    
         
            +
                  connect(options)
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
                
         
     | 
| 
      
 83 
     | 
    
         
            +
                # Returns a Sequel database object representing the current connection. If no
         
     | 
| 
      
 84 
     | 
    
         
            +
                # current connection, this will automatically connect with default options.
         
     | 
| 
      
 85 
     | 
    
         
            +
                def connection
         
     | 
| 
      
 86 
     | 
    
         
            +
                  @db || connect
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
                alias mysql connection
         
     | 
| 
      
 89 
     | 
    
         
            +
                
         
     | 
| 
      
 90 
     | 
    
         
            +
                # Returns a hash containing :user and :pass indicating how the application connects to
         
     | 
| 
      
 91 
     | 
    
         
            +
                # this database instance.  By default this just delegates to Jetpants.application_credentials,
         
     | 
| 
      
 92 
     | 
    
         
            +
                # which obtains credentials from the Jetpants config file. Plugins may override this
         
     | 
| 
      
 93 
     | 
    
         
            +
                # to use different credentials for particular hosts or in certain situations.
         
     | 
| 
      
 94 
     | 
    
         
            +
                def app_credentials
         
     | 
| 
      
 95 
     | 
    
         
            +
                  Jetpants.app_credentials
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
                
         
     | 
| 
      
 98 
     | 
    
         
            +
                # Returns the schema name ("database name" in MySQL parlance) to use for connections.
         
     | 
| 
      
 99 
     | 
    
         
            +
                # Defaults to just calling Jetpants.mysql_schema, but plugins may override.
         
     | 
| 
      
 100 
     | 
    
         
            +
                def app_schema
         
     | 
| 
      
 101 
     | 
    
         
            +
                  Jetpants.mysql_schema
         
     | 
| 
       61 
102 
     | 
    
         
             
                end
         
     | 
| 
       62 
103 
     | 
    
         | 
| 
       63 
104 
     | 
    
         
             
                # Execute a write (INSERT, UPDATE, DELETE, REPLACE, etc) query.
         
     | 
| 
       64 
105 
     | 
    
         
             
                # If the query is an INSERT, returns the last insert ID (if an auto_increment
         
     | 
| 
       65 
106 
     | 
    
         
             
                # column is involved).  Otherwise returns the number of affected rows.
         
     | 
| 
       66 
107 
     | 
    
         
             
                def query(sql, *binds)
         
     | 
| 
       67 
     | 
    
         
            -
                  ds =  
     | 
| 
       68 
     | 
    
         
            -
                   
     | 
| 
      
 108 
     | 
    
         
            +
                  ds = connection.fetch(sql, *binds)
         
     | 
| 
      
 109 
     | 
    
         
            +
                  connection.execute_dui(ds.update_sql) {|c| return c.last_id > 0 ? c.last_id : c.affected_rows}
         
     | 
| 
       69 
110 
     | 
    
         
             
                end
         
     | 
| 
       70 
111 
     | 
    
         | 
| 
       71 
112 
     | 
    
         
             
                # Execute a read (SELECT) query. Returns an array of hashes.
         
     | 
| 
       72 
113 
     | 
    
         
             
                def query_return_array(sql, *binds)
         
     | 
| 
       73 
     | 
    
         
            -
                   
     | 
| 
      
 114 
     | 
    
         
            +
                  connection.fetch(sql, *binds).all
         
     | 
| 
       74 
115 
     | 
    
         
             
                end
         
     | 
| 
       75 
116 
     | 
    
         | 
| 
       76 
117 
     | 
    
         
             
                # Execute a read (SELECT) query. Returns a hash of the first row only.
         
     | 
| 
       77 
118 
     | 
    
         
             
                def query_return_first(sql, *binds)
         
     | 
| 
       78 
     | 
    
         
            -
                   
     | 
| 
      
 119 
     | 
    
         
            +
                  connection.fetch(sql, *binds).first
         
     | 
| 
       79 
120 
     | 
    
         
             
                end
         
     | 
| 
       80 
121 
     | 
    
         | 
| 
       81 
122 
     | 
    
         
             
                # Execute a read (SELECT) query. Returns the value of the first column of the first row only.
         
     | 
| 
       82 
123 
     | 
    
         
             
                def query_return_first_value(sql, *binds)
         
     | 
| 
       83 
     | 
    
         
            -
                   
     | 
| 
      
 124 
     | 
    
         
            +
                  connection.fetch(sql, *binds).single_value
         
     | 
| 
       84 
125 
     | 
    
         
             
                end
         
     | 
| 
       85 
126 
     | 
    
         | 
| 
       86 
127 
     | 
    
         
             
                # Parses the result of a MySQL query run with a \G terminator. Useful when
         
     | 
| 
         @@ -10,7 +10,7 @@ module Jetpants 
     | 
|
| 
       10 
10 
     | 
    
         
             
                  output 'Exporting table definitions'
         
     | 
| 
       11 
11 
     | 
    
         
             
                  supply_root_pw = (Jetpants.mysql_root_password ? "-p#{Jetpants.mysql_root_password}" : '')
         
     | 
| 
       12 
12 
     | 
    
         
             
                  supply_port = (@port == 3306 ? '' : "-h 127.0.0.1 -P #{@port}")
         
     | 
| 
       13 
     | 
    
         
            -
                  cmd = "mysqldump #{supply_root_pw} #{supply_port} -d #{ 
     | 
| 
      
 13 
     | 
    
         
            +
                  cmd = "mysqldump #{supply_root_pw} #{supply_port} -d #{app_schema} " + tables.join(' ') + " >#{Jetpants.export_location}/create_tables_#{@port}.sql"
         
     | 
| 
       14 
14 
     | 
    
         
             
                  cmd.untaint
         
     | 
| 
       15 
15 
     | 
    
         
             
                  result = ssh_cmd(cmd)
         
     | 
| 
       16 
16 
     | 
    
         
             
                  output result
         
     | 
| 
         @@ -23,7 +23,7 @@ module Jetpants 
     | 
|
| 
       23 
23 
     | 
    
         
             
                # CAUTION IF RUNNING THIS MANUALLY!
         
     | 
| 
       24 
24 
     | 
    
         
             
                def import_schemata!
         
     | 
| 
       25 
25 
     | 
    
         
             
                  output 'Dropping and re-creating table definitions'
         
     | 
| 
       26 
     | 
    
         
            -
                  result = mysql_root_cmd "source #{Jetpants.export_location}/create_tables_#{@port}.sql", : 
     | 
| 
      
 26 
     | 
    
         
            +
                  result = mysql_root_cmd "source #{Jetpants.export_location}/create_tables_#{@port}.sql", terminator: '', schema: true
         
     | 
| 
       27 
27 
     | 
    
         
             
                  output result
         
     | 
| 
       28 
28 
     | 
    
         
             
                end
         
     | 
| 
       29 
29 
     | 
    
         | 
| 
         @@ -44,12 +44,12 @@ module Jetpants 
     | 
|
| 
       44 
44 
     | 
    
         
             
                  create_user(import_export_user)
         
     | 
| 
       45 
45 
     | 
    
         
             
                  grant_privileges(import_export_user)               # standard privs
         
     | 
| 
       46 
46 
     | 
    
         
             
                  grant_privileges(import_export_user, '*', 'FILE')  # FILE global privs
         
     | 
| 
       47 
     | 
    
         
            -
                  reconnect(import_export_user)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  reconnect(user: import_export_user)
         
     | 
| 
       48 
48 
     | 
    
         
             
                  @counts ||= {}
         
     | 
| 
       49 
49 
     | 
    
         
             
                  tables.each {|t| @counts[t.name] = export_table_data t, min_id, max_id}
         
     | 
| 
       50 
50 
     | 
    
         
             
                ensure
         
     | 
| 
       51 
     | 
    
         
            -
                  reconnect( 
     | 
| 
       52 
     | 
    
         
            -
                  drop_user 
     | 
| 
      
 51 
     | 
    
         
            +
                  reconnect(user: app_credentials[:user])
         
     | 
| 
      
 52 
     | 
    
         
            +
                  drop_user import_export_user
         
     | 
| 
       53 
53 
     | 
    
         
             
                end
         
     | 
| 
       54 
54 
     | 
    
         | 
| 
       55 
55 
     | 
    
         
             
                # Exports data for a table. Only includes the data subset that falls
         
     | 
| 
         @@ -108,7 +108,7 @@ module Jetpants 
     | 
|
| 
       108 
108 
     | 
    
         
             
                  create_user(import_export_user)
         
     | 
| 
       109 
109 
     | 
    
         
             
                  grant_privileges(import_export_user)               # standard privs
         
     | 
| 
       110 
110 
     | 
    
         
             
                  grant_privileges(import_export_user, '*', 'FILE')  # FILE global privs
         
     | 
| 
       111 
     | 
    
         
            -
                  reconnect(import_export_user)
         
     | 
| 
      
 111 
     | 
    
         
            +
                  reconnect(user: import_export_user)
         
     | 
| 
       112 
112 
     | 
    
         | 
| 
       113 
113 
     | 
    
         
             
                  import_counts = {}
         
     | 
| 
       114 
114 
     | 
    
         
             
                  tables.each {|t| import_counts[t.name] = import_table_data t, min_id, max_id}
         
     | 
| 
         @@ -124,7 +124,7 @@ module Jetpants 
     | 
|
| 
       124 
124 
     | 
    
         
             
                  end
         
     | 
| 
       125 
125 
     | 
    
         | 
| 
       126 
126 
     | 
    
         
             
                ensure
         
     | 
| 
       127 
     | 
    
         
            -
                  reconnect( 
     | 
| 
      
 127 
     | 
    
         
            +
                  reconnect(user: app_credentials[:user])
         
     | 
| 
       128 
128 
     | 
    
         
             
                  drop_user(import_export_user)
         
     | 
| 
       129 
129 
     | 
    
         
             
                end
         
     | 
| 
       130 
130 
     | 
    
         | 
| 
         @@ -194,7 +194,7 @@ module Jetpants 
     | 
|
| 
       194 
194 
     | 
    
         
             
                # Supply the ID range (in terms of the table's sharding key)
         
     | 
| 
       195 
195 
     | 
    
         
             
                # of rows to KEEP.
         
     | 
| 
       196 
196 
     | 
    
         
             
                def prune_data_to_range(tables, keep_min_id, keep_max_id)
         
     | 
| 
       197 
     | 
    
         
            -
                  reconnect( 
     | 
| 
      
 197 
     | 
    
         
            +
                  reconnect(user: app_credentials[:user])
         
     | 
| 
       198 
198 
     | 
    
         
             
                  tables.each do |t|
         
     | 
| 
       199 
199 
     | 
    
         
             
                    output "Cleaning up data, pruning to only keep range #{keep_min_id}-#{keep_max_id}", t
         
     | 
| 
       200 
200 
     | 
    
         
             
                    rows_deleted = 0
         
     | 
| 
         @@ -260,7 +260,6 @@ module Jetpants 
     | 
|
| 
       260 
260 
     | 
    
         
             
                  stop_query_killer
         
     | 
| 
       261 
261 
     | 
    
         
             
                  disable_binary_logging
         
     | 
| 
       262 
262 
     | 
    
         
             
                  restart_mysql
         
     | 
| 
       263 
     | 
    
         
            -
                  reconnect
         
     | 
| 
       264 
263 
     | 
    
         
             
                  pause_replication if is_slave?
         
     | 
| 
       265 
264 
     | 
    
         | 
| 
       266 
265 
     | 
    
         
             
                  # Automatically detect missing min/max. Assumes that all tables' primary keys
         
     | 
| 
         @@ -293,15 +292,6 @@ module Jetpants 
     | 
|
| 
       293 
292 
     | 
    
         
             
                  enable_monitoring
         
     | 
| 
       294 
293 
     | 
    
         
             
                end
         
     | 
| 
       295 
294 
     | 
    
         | 
| 
       296 
     | 
    
         
            -
                # Returns the data set size in bytes (if in_gb is false or omitted) or in gigabytes
         
     | 
| 
       297 
     | 
    
         
            -
                # (if in_gb is true).  Note that this is actually in gibibytes (2^30) rather than
         
     | 
| 
       298 
     | 
    
         
            -
                # a metric gigabyte.  This puts it on the same scale as the output to tools like 
         
     | 
| 
       299 
     | 
    
         
            -
                # "du -h" and "df -h".
         
     | 
| 
       300 
     | 
    
         
            -
                def data_set_size(in_gb=false)
         
     | 
| 
       301 
     | 
    
         
            -
                  bytes = dir_size("#{mysql_directory}/#{Jetpants.mysql_schema}")
         
     | 
| 
       302 
     | 
    
         
            -
                  in_gb ? (bytes / 1073741824.0).round : bytes
         
     | 
| 
       303 
     | 
    
         
            -
                end
         
     | 
| 
       304 
     | 
    
         
            -
                
         
     | 
| 
       305 
295 
     | 
    
         
             
                # Copies mysql db files from self to one or more additional DBs.
         
     | 
| 
       306 
296 
     | 
    
         
             
                # WARNING: temporarily shuts down mysql on self, and WILL OVERWRITE CONTENTS
         
     | 
| 
       307 
297 
     | 
    
         
             
                # OF MYSQL DIRECTORY ON TARGETS.  Confirms first that none of the targets
         
     | 
| 
         @@ -321,7 +311,7 @@ module Jetpants 
     | 
|
| 
       321 
311 
     | 
    
         
             
                  fast_copy_chain(mysql_directory, 
         
     | 
| 
       322 
312 
     | 
    
         
             
                                  destinations,
         
     | 
| 
       323 
313 
     | 
    
         
             
                                  port: 3306,
         
     | 
| 
       324 
     | 
    
         
            -
                                  files: ['ibdata1', 'mysql', 'test',  
     | 
| 
      
 314 
     | 
    
         
            +
                                  files: ['ibdata1', 'mysql', 'test', app_schema],
         
     | 
| 
       325 
315 
     | 
    
         
             
                                  overwrite: true)
         
     | 
| 
       326 
316 
     | 
    
         
             
                  [self, targets].flatten.concurrent_each {|t| t.start_mysql; t.start_query_killer}
         
     | 
| 
       327 
317 
     | 
    
         
             
                end
         
     | 
| 
         @@ -9,28 +9,30 @@ module Jetpants 
     | 
|
| 
       9 
9 
     | 
    
         
             
                # configuration will be used instead.  Does not automatically grant any
         
     | 
| 
       10 
10 
     | 
    
         
             
                # privileges; use DB#grant_privileges for that.
         
     | 
| 
       11 
11 
     | 
    
         
             
                def create_user(username=false, database=false, password=false)
         
     | 
| 
       12 
     | 
    
         
            -
                  username ||=  
     | 
| 
       13 
     | 
    
         
            -
                  database ||=  
     | 
| 
       14 
     | 
    
         
            -
                  password ||=  
     | 
| 
      
 12 
     | 
    
         
            +
                  username ||= app_credentials[:user]
         
     | 
| 
      
 13 
     | 
    
         
            +
                  database ||= app_schema
         
     | 
| 
      
 14 
     | 
    
         
            +
                  password ||= app_credentials[:pass]
         
     | 
| 
       15 
15 
     | 
    
         
             
                  commands = []
         
     | 
| 
       16 
16 
     | 
    
         
             
                  Jetpants.mysql_grant_ips.each do |ip|
         
     | 
| 
       17 
17 
     | 
    
         
             
                    commands << "CREATE USER '#{username}'@'#{ip}' IDENTIFIED BY '#{password}'"
         
     | 
| 
       18 
18 
     | 
    
         
             
                  end
         
     | 
| 
       19 
19 
     | 
    
         
             
                  commands << "FLUSH PRIVILEGES"
         
     | 
| 
       20 
     | 
    
         
            -
                   
     | 
| 
      
 20 
     | 
    
         
            +
                  commands = commands.join '; '
         
     | 
| 
      
 21 
     | 
    
         
            +
                  mysql_root_cmd commands, schema: true
         
     | 
| 
       21 
22 
     | 
    
         
             
                end
         
     | 
| 
       22 
23 
     | 
    
         | 
| 
       23 
24 
     | 
    
         
             
                # Drops a user. Can optionally make this statement skip replication, if you
         
     | 
| 
       24 
25 
     | 
    
         
             
                # want to drop a user on master and not on its slaves.
         
     | 
| 
       25 
26 
     | 
    
         
             
                def drop_user(username=false, skip_binlog=false)
         
     | 
| 
       26 
     | 
    
         
            -
                  username ||=  
     | 
| 
      
 27 
     | 
    
         
            +
                  username ||= app_credentials[:user]
         
     | 
| 
       27 
28 
     | 
    
         
             
                  commands = []
         
     | 
| 
       28 
29 
     | 
    
         
             
                  commands << 'SET sql_log_bin = 0' if skip_binlog
         
     | 
| 
       29 
30 
     | 
    
         
             
                  Jetpants.mysql_grant_ips.each do |ip|
         
     | 
| 
       30 
31 
     | 
    
         
             
                    commands << "DROP USER '#{username}'@'#{ip}'"
         
     | 
| 
       31 
32 
     | 
    
         
             
                  end
         
     | 
| 
       32 
33 
     | 
    
         
             
                  commands << "FLUSH PRIVILEGES"
         
     | 
| 
       33 
     | 
    
         
            -
                   
     | 
| 
      
 34 
     | 
    
         
            +
                  commands = commands.join '; '
         
     | 
| 
      
 35 
     | 
    
         
            +
                  mysql_root_cmd commands, schema: true
         
     | 
| 
       34 
36 
     | 
    
         
             
                end
         
     | 
| 
       35 
37 
     | 
    
         | 
| 
       36 
38 
     | 
    
         
             
                # Grants privileges to the given username for the specified database.
         
     | 
| 
         @@ -50,8 +52,8 @@ module Jetpants 
     | 
|
| 
       50 
52 
     | 
    
         
             
                # Helper method that can do grants or revokes.
         
     | 
| 
       51 
53 
     | 
    
         
             
                def grant_or_revoke_privileges(statement, username, database, privileges)
         
     | 
| 
       52 
54 
     | 
    
         
             
                  preposition = (statement.downcase == 'revoke' ? 'FROM' : 'TO')
         
     | 
| 
       53 
     | 
    
         
            -
                  username ||=  
     | 
| 
       54 
     | 
    
         
            -
                  database ||=  
     | 
| 
      
 55 
     | 
    
         
            +
                  username ||= app_credentials[:user]
         
     | 
| 
      
 56 
     | 
    
         
            +
                  database ||= app_schema
         
     | 
| 
       55 
57 
     | 
    
         
             
                  privileges = Jetpants.mysql_grant_privs if privileges.empty?
         
     | 
| 
       56 
58 
     | 
    
         
             
                  privileges = privileges.join(',')
         
     | 
| 
       57 
59 
     | 
    
         
             
                  commands = []
         
     | 
| 
         @@ -60,14 +62,15 @@ module Jetpants 
     | 
|
| 
       60 
62 
     | 
    
         
             
                    commands << "#{statement} #{privileges} ON #{database}.* #{preposition} '#{username}'@'#{ip}'"
         
     | 
| 
       61 
63 
     | 
    
         
             
                  end
         
     | 
| 
       62 
64 
     | 
    
         
             
                  commands << "FLUSH PRIVILEGES"
         
     | 
| 
       63 
     | 
    
         
            -
                   
     | 
| 
      
 65 
     | 
    
         
            +
                  commands = commands.join '; '
         
     | 
| 
      
 66 
     | 
    
         
            +
                  mysql_root_cmd commands, schema: true
         
     | 
| 
       64 
67 
     | 
    
         
             
                end
         
     | 
| 
       65 
68 
     | 
    
         | 
| 
       66 
69 
     | 
    
         
             
                # Disables access to a DB by the application user, and sets the DB to 
         
     | 
| 
       67 
70 
     | 
    
         
             
                # read-only. Useful when decommissioning instances from a shard that's
         
     | 
| 
       68 
71 
     | 
    
         
             
                # been split.
         
     | 
| 
       69 
72 
     | 
    
         
             
                def revoke_all_access!
         
     | 
| 
       70 
     | 
    
         
            -
                  user_name =  
     | 
| 
      
 73 
     | 
    
         
            +
                  user_name = app_credentials[:user]
         
     | 
| 
       71 
74 
     | 
    
         
             
                  enable_read_only!
         
     | 
| 
       72 
75 
     | 
    
         
             
                  output "Revoking access for user #{user_name}."
         
     | 
| 
       73 
76 
     | 
    
         
             
                  output(drop_user(user_name, true)) # drop the user without replicating the drop statement to slaves
         
     | 
| 
         @@ -12,8 +12,9 @@ module Jetpants 
     | 
|
| 
       12 
12 
     | 
    
         
             
                # If you omit :log_pos or :log_file, uses the current position/file from new_master,
         
     | 
| 
       13 
13 
     | 
    
         
             
                # though this is only safe if new_master is not receiving writes!
         
     | 
| 
       14 
14 
     | 
    
         
             
                #
         
     | 
| 
       15 
     | 
    
         
            -
                # If you omit :user  
     | 
| 
       16 
     | 
    
         
            -
                #  
     | 
| 
      
 15 
     | 
    
         
            +
                # If you omit :user and :password, tries obtaining replication credentials from the
         
     | 
| 
      
 16 
     | 
    
         
            +
                # current node (assuming it is already a slave) or if that fails then from the global
         
     | 
| 
      
 17 
     | 
    
         
            +
                # settings.
         
     | 
| 
       17 
18 
     | 
    
         
             
                def change_master_to(new_master, option_hash={})
         
     | 
| 
       18 
19 
     | 
    
         
             
                  return disable_replication! unless new_master   # change_master_to(nil) alias for disable_replication!
         
     | 
| 
       19 
20 
     | 
    
         
             
                  return if new_master == master                  # no change
         
     | 
| 
         @@ -25,8 +26,8 @@ module Jetpants 
     | 
|
| 
       25 
26 
     | 
    
         
             
                    logfile, pos = new_master.binlog_coordinates
         
     | 
| 
       26 
27 
     | 
    
         
             
                  end
         
     | 
| 
       27 
28 
     | 
    
         | 
| 
       28 
     | 
    
         
            -
                  repl_user = option_hash[:user]     ||  
     | 
| 
       29 
     | 
    
         
            -
                  repl_pass = option_hash[:password] ||  
     | 
| 
      
 29 
     | 
    
         
            +
                  repl_user = option_hash[:user]     || replication_credentials[:user]
         
     | 
| 
      
 30 
     | 
    
         
            +
                  repl_pass = option_hash[:password] || replication_credentials[:pass]
         
     | 
| 
       30 
31 
     | 
    
         | 
| 
       31 
32 
     | 
    
         
             
                  pause_replication if @master && !@repl_paused
         
     | 
| 
       32 
33 
     | 
    
         
             
                  result = mysql_root_cmd "CHANGE MASTER TO " +
         
     | 
| 
         @@ -78,12 +79,12 @@ module Jetpants 
     | 
|
| 
       78 
79 
     | 
    
         
             
                # Resumes replication on self afterwards, but does NOT automatically start
         
     | 
| 
       79 
80 
     | 
    
         
             
                # replication on the targets.
         
     | 
| 
       80 
81 
     | 
    
         
             
                # You can omit passing in the replication user/pass if this machine is itself
         
     | 
| 
       81 
     | 
    
         
            -
                # a slave OR already has at least one slave.
         
     | 
| 
      
 82 
     | 
    
         
            +
                # a slave OR already has at least one slave OR the global setting is fine to use here.
         
     | 
| 
       82 
83 
     | 
    
         
             
                # Warning: takes self offline during the process, so don't use on a master that
         
     | 
| 
       83 
84 
     | 
    
         
             
                # is actively in use by your application!
         
     | 
| 
       84 
85 
     | 
    
         
             
                def enslave!(targets, repl_user=false, repl_pass=false)
         
     | 
| 
       85 
     | 
    
         
            -
                  repl_user ||=  
     | 
| 
       86 
     | 
    
         
            -
                  repl_pass ||=  
     | 
| 
      
 86 
     | 
    
         
            +
                  repl_user ||= replication_credentials[:user]
         
     | 
| 
      
 87 
     | 
    
         
            +
                  repl_pass ||= replication_credentials[:pass]
         
     | 
| 
       87 
88 
     | 
    
         
             
                  disable_monitoring
         
     | 
| 
       88 
89 
     | 
    
         
             
                  pause_replication if master && ! @repl_paused
         
     | 
| 
       89 
90 
     | 
    
         
             
                  file, pos = binlog_coordinates
         
     | 
| 
         @@ -113,8 +114,8 @@ module Jetpants 
     | 
|
| 
       113 
114 
     | 
    
         
             
                    t.change_master_to( master, 
         
     | 
| 
       114 
115 
     | 
    
         
             
                                        log_file: file,
         
     | 
| 
       115 
116 
     | 
    
         
             
                                        log_pos:  pos,
         
     | 
| 
       116 
     | 
    
         
            -
                                        user:      
     | 
| 
       117 
     | 
    
         
            -
                                        password:  
     | 
| 
      
 117 
     | 
    
         
            +
                                        user:     replication_credentials[:user],
         
     | 
| 
      
 118 
     | 
    
         
            +
                                        password: replication_credentials[:pass]  )
         
     | 
| 
       118 
119 
     | 
    
         
             
                  end
         
     | 
| 
       119 
120 
     | 
    
         
             
                  resume_replication # should already have happened from the clone_to! restart anyway, but just to be explicit
         
     | 
| 
       120 
121 
     | 
    
         
             
                  catch_up_to_master
         
     | 
| 
         @@ -205,14 +206,21 @@ module Jetpants 
     | 
|
| 
       205 
206 
     | 
    
         
             
                end
         
     | 
| 
       206 
207 
     | 
    
         | 
| 
       207 
208 
     | 
    
         
             
                # Reads an existing master.info file on this db or one of its slaves,
         
     | 
| 
       208 
     | 
    
         
            -
                # propagates the info back to the Jetpants singleton, and returns it
         
     | 
| 
      
 209 
     | 
    
         
            +
                # propagates the info back to the Jetpants singleton, and returns it as
         
     | 
| 
      
 210 
     | 
    
         
            +
                # a hash containing :user and :pass.
         
     | 
| 
      
 211 
     | 
    
         
            +
                # If the node is not a slave and has no slaves, will use the global Jetpants
         
     | 
| 
      
 212 
     | 
    
         
            +
                # config instead.
         
     | 
| 
       209 
213 
     | 
    
         
             
                def replication_credentials
         
     | 
| 
       210 
     | 
    
         
            -
                   
     | 
| 
       211 
     | 
    
         
            -
             
     | 
| 
      
 214 
     | 
    
         
            +
                  user = false
         
     | 
| 
      
 215 
     | 
    
         
            +
                  pass = false
         
     | 
| 
      
 216 
     | 
    
         
            +
                  if master || slaves.count > 0
         
     | 
| 
      
 217 
     | 
    
         
            +
                    target = (@master ? self : @slaves[0])
         
     | 
| 
      
 218 
     | 
    
         
            +
                    results = target.ssh_cmd("cat #{mysql_directory}/master.info | head -6 | tail -2").split
         
     | 
| 
      
 219 
     | 
    
         
            +
                    if results.count == 2 && results[0] != 'test'
         
     | 
| 
      
 220 
     | 
    
         
            +
                      user, pass = results
         
     | 
| 
      
 221 
     | 
    
         
            +
                    end
         
     | 
| 
       212 
222 
     | 
    
         
             
                  end
         
     | 
| 
       213 
     | 
    
         
            -
                   
     | 
| 
       214 
     | 
    
         
            -
                  user, pass = target.ssh_cmd("cat #{mysql_directory}/master.info | head -6 | tail -2").split
         
     | 
| 
       215 
     | 
    
         
            -
                  {user: user, pass: pass}
         
     | 
| 
      
 223 
     | 
    
         
            +
                  user && pass ? {user: user, pass: pass} : Jetpants.replication_credentials
         
     | 
| 
       216 
224 
     | 
    
         
             
                end
         
     | 
| 
       217 
225 
     | 
    
         | 
| 
       218 
226 
     | 
    
         
             
                # Disables binary logging in my.cnf.  Does not take effect until you restart
         
     | 
    
        data/lib/jetpants/db/server.rb
    CHANGED
    
    | 
         @@ -31,10 +31,21 @@ module Jetpants 
     | 
|
| 
       31 
31 
     | 
    
         
             
                # Restarts MySQL.
         
     | 
| 
       32 
32 
     | 
    
         
             
                def restart_mysql
         
     | 
| 
       33 
33 
     | 
    
         
             
                  @repl_paused = false if @master
         
     | 
| 
      
 34 
     | 
    
         
            +
                  
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # Disconnect if we were previously connected
         
     | 
| 
      
 36 
     | 
    
         
            +
                  user, schema = false, false
         
     | 
| 
      
 37 
     | 
    
         
            +
                  if @db
         
     | 
| 
      
 38 
     | 
    
         
            +
                    user, schema = @user, @schema
         
     | 
| 
      
 39 
     | 
    
         
            +
                    disconnect
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  
         
     | 
| 
       34 
42 
     | 
    
         
             
                  output "Attempting to restart MySQL"
         
     | 
| 
       35 
43 
     | 
    
         
             
                  output service(:restart, 'mysql')
         
     | 
| 
       36 
44 
     | 
    
         
             
                  confirm_listening
         
     | 
| 
       37 
45 
     | 
    
         
             
                  @running = true
         
     | 
| 
      
 46 
     | 
    
         
            +
                  
         
     | 
| 
      
 47 
     | 
    
         
            +
                  # Reconnect if we were previously connected
         
     | 
| 
      
 48 
     | 
    
         
            +
                  connect(user: user, schema: schema) if user || schema
         
     | 
| 
       38 
49 
     | 
    
         
             
                end
         
     | 
| 
       39 
50 
     | 
    
         | 
| 
       40 
51 
     | 
    
         
             
                # Has no built-in effect. Plugins can override it, and/or implement
         
     | 
    
        data/lib/jetpants/db/state.rb
    CHANGED
    
    | 
         @@ -73,7 +73,7 @@ module Jetpants 
     | 
|
| 
       73 
73 
     | 
    
         
             
                # remembers the state from the previous probe and any actions since then.
         
     | 
| 
       74 
74 
     | 
    
         
             
                def replicating?
         
     | 
| 
       75 
75 
     | 
    
         
             
                  status = slave_status
         
     | 
| 
       76 
     | 
    
         
            -
                  [status[:slave_io_running], status[:slave_sql_running]].all? {|s| s.downcase == 'yes'}
         
     | 
| 
      
 76 
     | 
    
         
            +
                  [status[:slave_io_running], status[:slave_sql_running]].all? {|s| s && s.downcase == 'yes'}
         
     | 
| 
       77 
77 
     | 
    
         
             
                end
         
     | 
| 
       78 
78 
     | 
    
         | 
| 
       79 
79 
     | 
    
         
             
                # Returns true if this instance has a master, false otherwise.
         
     | 
| 
         @@ -174,6 +174,16 @@ module Jetpants 
     | 
|
| 
       174 
174 
     | 
    
         
             
                  end
         
     | 
| 
       175 
175 
     | 
    
         
             
                end
         
     | 
| 
       176 
176 
     | 
    
         | 
| 
      
 177 
     | 
    
         
            +
                # Returns the data set size in bytes (if in_gb is false or omitted) or in gigabytes
         
     | 
| 
      
 178 
     | 
    
         
            +
                # (if in_gb is true).  Note that this is actually in gibibytes (2^30) rather than
         
     | 
| 
      
 179 
     | 
    
         
            +
                # a metric gigabyte.  This puts it on the same scale as the output to tools like 
         
     | 
| 
      
 180 
     | 
    
         
            +
                # "du -h" and "df -h".
         
     | 
| 
      
 181 
     | 
    
         
            +
                def data_set_size(in_gb=false)
         
     | 
| 
      
 182 
     | 
    
         
            +
                  bytes = dir_size("#{mysql_directory}/#{app_schema}")
         
     | 
| 
      
 183 
     | 
    
         
            +
                  in_gb ? (bytes / 1073741824.0).round : bytes
         
     | 
| 
      
 184 
     | 
    
         
            +
                end
         
     | 
| 
      
 185 
     | 
    
         
            +
                
         
     | 
| 
      
 186 
     | 
    
         
            +
                
         
     | 
| 
       177 
187 
     | 
    
         
             
                ###### Private methods #####################################################
         
     | 
| 
       178 
188 
     | 
    
         | 
| 
       179 
189 
     | 
    
         
             
                private
         
     | 
    
        data/lib/jetpants/host.rb
    CHANGED
    
    | 
         @@ -8,11 +8,15 @@ module Jetpants 
     | 
|
| 
       8 
8 
     | 
    
         
             
              class Host
         
     | 
| 
       9 
9 
     | 
    
         
             
                include CallbackHandler
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
      
 11 
     | 
    
         
            +
                # IP address of the Host, as a string.
         
     | 
| 
      
 12 
     | 
    
         
            +
                attr_reader :ip
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
       11 
14 
     | 
    
         
             
                @@all_hosts = {}
         
     | 
| 
       12 
15 
     | 
    
         
             
                @@all_hosts_mutex = Mutex.new
         
     | 
| 
       13 
16 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
                 
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
      
 17 
     | 
    
         
            +
                def self.clear
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @@all_hosts_mutex.synchronize {@@all_hosts = {}}
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
       16 
20 
     | 
    
         | 
| 
       17 
21 
     | 
    
         
             
                # We override Host.new so that attempting to create a duplicate Host object
         
     | 
| 
       18 
22 
     | 
    
         
             
                # (that is, one with the same IP as an existing Host object) returns the
         
     | 
| 
         @@ -369,6 +373,7 @@ module Jetpants 
     | 
|
| 
       369 
373 
     | 
    
         | 
| 
       370 
374 
     | 
    
         
             
                # Returns the machine's hostname
         
     | 
| 
       371 
375 
     | 
    
         
             
                def hostname
         
     | 
| 
      
 376 
     | 
    
         
            +
                  return 'unknown' unless available?
         
     | 
| 
       372 
377 
     | 
    
         
             
                  @hostname ||= ssh_cmd('hostname').chomp
         
     | 
| 
       373 
378 
     | 
    
         
             
                end
         
     | 
| 
       374 
379 
     | 
    
         | 
    
        data/lib/jetpants/pool.rb
    CHANGED
    
    | 
         @@ -128,14 +128,15 @@ module Jetpants 
     | 
|
| 
       128 
128 
     | 
    
         
             
                # Note that a plugin may want to override this (or implement after_remove_slave!)
         
     | 
| 
       129 
129 
     | 
    
         
             
                # to actually sync the change to an asset tracker, depending on how the plugin
         
     | 
| 
       130 
130 
     | 
    
         
             
                # implements Pool#sync_configuration. (If the implementation makes sync_configuration
         
     | 
| 
       131 
     | 
    
         
            -
                # work by iterating over the pool's current slaves 
     | 
| 
       132 
     | 
    
         
            -
                # been removed.)
         
     | 
| 
      
 131 
     | 
    
         
            +
                # work by iterating over the pool's current slaves to update their status/role/pool, it 
         
     | 
| 
      
 132 
     | 
    
         
            +
                # won't see any slaves that have been removed, and therefore won't update them.)
         
     | 
| 
       133 
133 
     | 
    
         
             
                def remove_slave!(slave_db)
         
     | 
| 
       134 
134 
     | 
    
         
             
                  raise "Slave is not in this pool" unless slave_db.pool == self
         
     | 
| 
       135 
135 
     | 
    
         
             
                  slave_db.disable_monitoring
         
     | 
| 
       136 
136 
     | 
    
         
             
                  slave_db.stop_replication
         
     | 
| 
       137 
137 
     | 
    
         
             
                  slave_db.repl_binlog_coordinates # displays how far we replicated, in case you need to roll back this change manually
         
     | 
| 
       138 
138 
     | 
    
         
             
                  slave_db.disable_replication!
         
     | 
| 
      
 139 
     | 
    
         
            +
                  sync_configuration # may or may not be sufficient -- see note above.
         
     | 
| 
       139 
140 
     | 
    
         
             
                end
         
     | 
| 
       140 
141 
     | 
    
         | 
| 
       141 
142 
     | 
    
         
             
                # Informs this pool that it has an alias. A pool may have any number of aliases.
         
     | 
| 
         @@ -167,7 +168,9 @@ module Jetpants 
     | 
|
| 
       167 
168 
     | 
    
         
             
                      elsif s == @master
         
     | 
| 
       168 
169 
     | 
    
         
             
                        details[s] = {coordinates: s.binlog_coordinates(false), lag: 'N/A'}
         
     | 
| 
       169 
170 
     | 
    
         
             
                      else
         
     | 
| 
       170 
     | 
    
         
            -
                         
     | 
| 
      
 171 
     | 
    
         
            +
                        lag = s.seconds_behind_master
         
     | 
| 
      
 172 
     | 
    
         
            +
                        lag_str = lag.nil? ? 'NULL' : lag.to_s + 's'
         
     | 
| 
      
 173 
     | 
    
         
            +
                        details[s] = {coordinates: s.repl_binlog_coordinates(false), lag: lag_str}
         
     | 
| 
       171 
174 
     | 
    
         
             
                      end
         
     | 
| 
       172 
175 
     | 
    
         
             
                    end
         
     | 
| 
       173 
176 
     | 
    
         
             
                  end
         
     | 
    
        data/lib/jetpants/topology.rb
    CHANGED
    
    | 
         @@ -9,12 +9,16 @@ module Jetpants 
     | 
|
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
                def initialize
         
     | 
| 
       11 
11 
     | 
    
         
             
                  @pools  = [] # array of Pool objects
         
     | 
| 
       12 
     | 
    
         
            -
                  load_pools
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # We intentionally don't call load_pools here. The caller must do that.
         
     | 
| 
      
 13 
     | 
    
         
            +
                  # This allows Jetpants module to create Jetpants.topology object, and THEN
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # invoke load_pools, which might then refer back to Jetpants.topology.
         
     | 
| 
       13 
15 
     | 
    
         
             
                end
         
     | 
| 
       14 
16 
     | 
    
         | 
| 
       15 
17 
     | 
    
         
             
                ###### Class methods #######################################################
         
     | 
| 
       16 
18 
     | 
    
         | 
| 
       17 
19 
     | 
    
         
             
                # Metaprogramming hackery to create a "synchronized" method decorator
         
     | 
| 
      
 20 
     | 
    
         
            +
                # Note that all synchronized methods share the same mutex, so don't make one
         
     | 
| 
      
 21 
     | 
    
         
            +
                # synchronized method call another!
         
     | 
| 
       18 
22 
     | 
    
         
             
                @lock = Mutex.new
         
     | 
| 
       19 
23 
     | 
    
         
             
                @do_sync = false
         
     | 
| 
       20 
24 
     | 
    
         
             
                @synchronized_methods = {} # symbol => true
         
     | 
| 
         @@ -84,7 +88,7 @@ module Jetpants 
     | 
|
| 
       84 
88 
     | 
    
         
             
                end
         
     | 
| 
       85 
89 
     | 
    
         | 
| 
       86 
90 
     | 
    
         | 
| 
       87 
     | 
    
         
            -
                ######  
     | 
| 
      
 91 
     | 
    
         
            +
                ###### Instance Methods ####################################################
         
     | 
| 
       88 
92 
     | 
    
         | 
| 
       89 
93 
     | 
    
         
             
                # Returns array of this topology's Jetpants::Pool objects of type Jetpants::Shard
         
     | 
| 
       90 
94 
     | 
    
         
             
                def shards
         
     | 
| 
         @@ -140,5 +144,19 @@ module Jetpants 
     | 
|
| 
       140 
144 
     | 
    
         
             
                  claim_spares(1, options)[0]
         
     | 
| 
       141 
145 
     | 
    
         
             
                end
         
     | 
| 
       142 
146 
     | 
    
         | 
| 
      
 147 
     | 
    
         
            +
                synchronized
         
     | 
| 
      
 148 
     | 
    
         
            +
                # Clears the pool list and nukes cached DB and Host object lookup tables
         
     | 
| 
      
 149 
     | 
    
         
            +
                def clear
         
     | 
| 
      
 150 
     | 
    
         
            +
                  @pools = []
         
     | 
| 
      
 151 
     | 
    
         
            +
                  DB.clear
         
     | 
| 
      
 152 
     | 
    
         
            +
                  Host.clear
         
     | 
| 
      
 153 
     | 
    
         
            +
                end
         
     | 
| 
      
 154 
     | 
    
         
            +
                
         
     | 
| 
      
 155 
     | 
    
         
            +
                # Empties and then reloads the pool list
         
     | 
| 
      
 156 
     | 
    
         
            +
                def refresh
         
     | 
| 
      
 157 
     | 
    
         
            +
                  clear
         
     | 
| 
      
 158 
     | 
    
         
            +
                  load_pools
         
     | 
| 
      
 159 
     | 
    
         
            +
                  true
         
     | 
| 
      
 160 
     | 
    
         
            +
                end
         
     | 
| 
       143 
161 
     | 
    
         
             
              end
         
     | 
| 
       144 
162 
     | 
    
         
             
            end
         
     | 
| 
         @@ -43,9 +43,8 @@ module Jetpants 
     | 
|
| 
       43 
43 
     | 
    
         
             
                  end
         
     | 
| 
       44 
44 
     | 
    
         | 
| 
       45 
45 
     | 
    
         
             
                  def determine_pool_and_role(ip, port=3306)
         
     | 
| 
       46 
     | 
    
         
            -
                    ip += ":#{port}" 
     | 
| 
       47 
     | 
    
         
            -
                    
         
     | 
| 
       48 
     | 
    
         
            -
                    [@global_pools + @shards].each do |h|
         
     | 
| 
      
 46 
     | 
    
         
            +
                    ip += ":#{port}"
         
     | 
| 
      
 47 
     | 
    
         
            +
                    (@global_pools + @shards).each do |h|
         
     | 
| 
       49 
48 
     | 
    
         
             
                      pool = (h['name'] ? Jetpants.topology.pool(h['name']) : Jetpants.topology.shard(h['min_id'], h['max_id']))
         
     | 
| 
       50 
49 
     | 
    
         
             
                      return [pool, 'MASTER'] if h['master'] == ip
         
     | 
| 
       51 
50 
     | 
    
         
             
                      h['slaves'].each do |s|
         
     | 
| 
         @@ -57,9 +56,9 @@ module Jetpants 
     | 
|
| 
       57 
56 
     | 
    
         
             
                  end
         
     | 
| 
       58 
57 
     | 
    
         | 
| 
       59 
58 
     | 
    
         
             
                  def determine_slaves(ip, port=3306)
         
     | 
| 
       60 
     | 
    
         
            -
                    ip += ":#{port}" 
     | 
| 
      
 59 
     | 
    
         
            +
                    ip += ":#{port}"
         
     | 
| 
       61 
60 
     | 
    
         | 
| 
       62 
     | 
    
         
            -
                     
     | 
| 
      
 61 
     | 
    
         
            +
                    (@global_pools + @shards).each do |h|
         
     | 
| 
       63 
62 
     | 
    
         
             
                      next unless h['master'] == ip
         
     | 
| 
       64 
63 
     | 
    
         
             
                      return h['slaves'].map {|s| s['host'].to_db}
         
     | 
| 
       65 
64 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -71,4 +70,4 @@ module Jetpants 
     | 
|
| 
       71 
70 
     | 
    
         
             
            end
         
     | 
| 
       72 
71 
     | 
    
         | 
| 
       73 
72 
     | 
    
         
             
            # load all the monkeypatches for other Jetpants classes
         
     | 
| 
       74 
     | 
    
         
            -
            %w(pool shard topology).each {|mod| require "simple_tracker/#{mod}"}
         
     | 
| 
      
 73 
     | 
    
         
            +
            %w(pool shard topology db).each {|mod| require "simple_tracker/#{mod}"}
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -2,7 +2,7 @@ 
     | 
|
| 
       2 
2 
     | 
    
         
             
            name: jetpants
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version 
         
     | 
| 
       4 
4 
     | 
    
         
             
              prerelease: 
         
     | 
| 
       5 
     | 
    
         
            -
              version: 0.7. 
     | 
| 
      
 5 
     | 
    
         
            +
              version: 0.7.4
         
     | 
| 
       6 
6 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       7 
7 
     | 
    
         
             
            authors: 
         
     | 
| 
       8 
8 
     | 
    
         
             
            - Evan Elias
         
     | 
| 
         @@ -11,7 +11,7 @@ autorequire: 
     | 
|
| 
       11 
11 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       12 
12 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
            date: 2012- 
     | 
| 
      
 14 
     | 
    
         
            +
            date: 2012-09-05 00:00:00 Z
         
     | 
| 
       15 
15 
     | 
    
         
             
            dependencies: 
         
     | 
| 
       16 
16 
     | 
    
         
             
            - !ruby/object:Gem::Dependency 
         
     | 
| 
       17 
17 
     | 
    
         
             
              name: mysql2
         
     | 
| 
         @@ -80,7 +80,7 @@ dependencies: 
     | 
|
| 
       80 
80 
     | 
    
         
             
              type: :runtime
         
     | 
| 
       81 
81 
     | 
    
         
             
              version_requirements: *id006
         
     | 
| 
       82 
82 
     | 
    
         
             
            - !ruby/object:Gem::Dependency 
         
     | 
| 
       83 
     | 
    
         
            -
              name:  
     | 
| 
      
 83 
     | 
    
         
            +
              name: colored
         
     | 
| 
       84 
84 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       85 
85 
     | 
    
         
             
              requirement: &id007 !ruby/object:Gem::Requirement 
         
     | 
| 
       86 
86 
     | 
    
         
             
                none: false
         
     | 
| 
         @@ -90,17 +90,6 @@ dependencies: 
     | 
|
| 
       90 
90 
     | 
    
         
             
                    version: "0"
         
     | 
| 
       91 
91 
     | 
    
         
             
              type: :runtime
         
     | 
| 
       92 
92 
     | 
    
         
             
              version_requirements: *id007
         
     | 
| 
       93 
     | 
    
         
            -
            - !ruby/object:Gem::Dependency 
         
     | 
| 
       94 
     | 
    
         
            -
              name: colored
         
     | 
| 
       95 
     | 
    
         
            -
              prerelease: false
         
     | 
| 
       96 
     | 
    
         
            -
              requirement: &id008 !ruby/object:Gem::Requirement 
         
     | 
| 
       97 
     | 
    
         
            -
                none: false
         
     | 
| 
       98 
     | 
    
         
            -
                requirements: 
         
     | 
| 
       99 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       100 
     | 
    
         
            -
                  - !ruby/object:Gem::Version 
         
     | 
| 
       101 
     | 
    
         
            -
                    version: "0"
         
     | 
| 
       102 
     | 
    
         
            -
              type: :runtime
         
     | 
| 
       103 
     | 
    
         
            -
              version_requirements: *id008
         
     | 
| 
       104 
93 
     | 
    
         
             
            description: Jetpants is an automation toolkit for handling monstrously large MySQL database topologies. It is geared towards common operational tasks like cloning slaves, rebalancing shards, and performing master promotions. It features a command suite for easy use by operations staff, though it's also a full Ruby library for use in developing custom migration scripts and database automation.
         
     | 
| 
       105 
94 
     | 
    
         
             
            email: 
         
     | 
| 
       106 
95 
     | 
    
         
             
            - me@evanelias.com
         
     | 
| 
         @@ -111,40 +100,40 @@ extensions: [] 
     | 
|
| 
       111 
100 
     | 
    
         | 
| 
       112 
101 
     | 
    
         
             
            extra_rdoc_files: 
         
     | 
| 
       113 
102 
     | 
    
         
             
            - README.rdoc
         
     | 
| 
      
 103 
     | 
    
         
            +
            - doc/plugins.rdoc
         
     | 
| 
       114 
104 
     | 
    
         
             
            - doc/configuration.rdoc
         
     | 
| 
       115 
105 
     | 
    
         
             
            - doc/faq.rdoc
         
     | 
| 
       116 
     | 
    
         
            -
            - doc/requirements.rdoc
         
     | 
| 
       117 
106 
     | 
    
         
             
            - doc/commands.rdoc
         
     | 
| 
       118 
     | 
    
         
            -
            - doc/ 
     | 
| 
      
 107 
     | 
    
         
            +
            - doc/requirements.rdoc
         
     | 
| 
       119 
108 
     | 
    
         
             
            files: 
         
     | 
| 
       120 
109 
     | 
    
         
             
            - Gemfile
         
     | 
| 
       121 
110 
     | 
    
         
             
            - README.rdoc
         
     | 
| 
      
 111 
     | 
    
         
            +
            - doc/plugins.rdoc
         
     | 
| 
       122 
112 
     | 
    
         
             
            - doc/configuration.rdoc
         
     | 
| 
       123 
113 
     | 
    
         
             
            - doc/faq.rdoc
         
     | 
| 
       124 
     | 
    
         
            -
            - doc/requirements.rdoc
         
     | 
| 
       125 
114 
     | 
    
         
             
            - doc/commands.rdoc
         
     | 
| 
       126 
     | 
    
         
            -
            - doc/ 
     | 
| 
       127 
     | 
    
         
            -
            - lib/jetpants/ 
     | 
| 
      
 115 
     | 
    
         
            +
            - doc/requirements.rdoc
         
     | 
| 
      
 116 
     | 
    
         
            +
            - lib/jetpants/callback.rb
         
     | 
| 
      
 117 
     | 
    
         
            +
            - lib/jetpants/topology.rb
         
     | 
| 
      
 118 
     | 
    
         
            +
            - lib/jetpants/db/server.rb
         
     | 
| 
      
 119 
     | 
    
         
            +
            - lib/jetpants/db/state.rb
         
     | 
| 
       128 
120 
     | 
    
         
             
            - lib/jetpants/db/import_export.rb
         
     | 
| 
       129 
121 
     | 
    
         
             
            - lib/jetpants/db/privileges.rb
         
     | 
| 
       130 
122 
     | 
    
         
             
            - lib/jetpants/db/client.rb
         
     | 
| 
       131 
123 
     | 
    
         
             
            - lib/jetpants/db/replication.rb
         
     | 
| 
       132 
     | 
    
         
            -
            - lib/jetpants/db/server.rb
         
     | 
| 
       133 
     | 
    
         
            -
            - lib/jetpants/db/state.rb
         
     | 
| 
       134 
     | 
    
         
            -
            - lib/jetpants/db.rb
         
     | 
| 
       135 
124 
     | 
    
         
             
            - lib/jetpants/shard.rb
         
     | 
| 
      
 125 
     | 
    
         
            +
            - lib/jetpants/db.rb
         
     | 
| 
      
 126 
     | 
    
         
            +
            - lib/jetpants/host.rb
         
     | 
| 
       136 
127 
     | 
    
         
             
            - lib/jetpants/pool.rb
         
     | 
| 
      
 128 
     | 
    
         
            +
            - lib/jetpants/monkeypatch.rb
         
     | 
| 
       137 
129 
     | 
    
         
             
            - lib/jetpants/table.rb
         
     | 
| 
       138 
     | 
    
         
            -
            - lib/jetpants/topology.rb
         
     | 
| 
       139 
     | 
    
         
            -
            - lib/jetpants/callback.rb
         
     | 
| 
       140 
     | 
    
         
            -
            - lib/jetpants/host.rb
         
     | 
| 
       141 
130 
     | 
    
         
             
            - lib/jetpants.rb
         
     | 
| 
       142 
131 
     | 
    
         
             
            - bin/jetpants
         
     | 
| 
       143 
     | 
    
         
            -
            - plugins/simple_tracker/ 
     | 
| 
      
 132 
     | 
    
         
            +
            - plugins/simple_tracker/topology.rb
         
     | 
| 
       144 
133 
     | 
    
         
             
            - plugins/simple_tracker/shard.rb
         
     | 
| 
       145 
     | 
    
         
            -
            - plugins/simple_tracker/pool.rb
         
     | 
| 
       146 
134 
     | 
    
         
             
            - plugins/simple_tracker/simple_tracker.rb
         
     | 
| 
       147 
     | 
    
         
            -
            - plugins/simple_tracker/ 
     | 
| 
      
 135 
     | 
    
         
            +
            - plugins/simple_tracker/db.rb
         
     | 
| 
      
 136 
     | 
    
         
            +
            - plugins/simple_tracker/pool.rb
         
     | 
| 
       148 
137 
     | 
    
         
             
            - etc/jetpants.yaml.sample
         
     | 
| 
       149 
138 
     | 
    
         
             
            homepage: https://github.com/tumblr/jetpants/
         
     | 
| 
       150 
139 
     | 
    
         
             
            licenses: []
         
     |