jetpants 0.7.0
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/Gemfile +3 -0
- data/README.rdoc +88 -0
- data/bin/jetpants +442 -0
- data/doc/commands.rdoc +119 -0
- data/doc/configuration.rdoc +27 -0
- data/doc/plugins.rdoc +120 -0
- data/doc/requirements.rdoc +54 -0
- data/etc/jetpants.yaml.sample +58 -0
- data/lib/jetpants.rb +100 -0
- data/lib/jetpants/callback.rb +131 -0
- data/lib/jetpants/db.rb +122 -0
- data/lib/jetpants/db/client.rb +103 -0
- data/lib/jetpants/db/import_export.rb +330 -0
- data/lib/jetpants/db/privileges.rb +89 -0
- data/lib/jetpants/db/replication.rb +226 -0
- data/lib/jetpants/db/server.rb +79 -0
- data/lib/jetpants/db/state.rb +212 -0
- data/lib/jetpants/host.rb +396 -0
- data/lib/jetpants/monkeypatch.rb +74 -0
- data/lib/jetpants/pool.rb +272 -0
- data/lib/jetpants/shard.rb +311 -0
- data/lib/jetpants/table.rb +146 -0
- data/lib/jetpants/topology.rb +144 -0
- data/plugins/simple_tracker/db.rb +23 -0
- data/plugins/simple_tracker/pool.rb +70 -0
- data/plugins/simple_tracker/shard.rb +76 -0
- data/plugins/simple_tracker/simple_tracker.rb +74 -0
- data/plugins/simple_tracker/topology.rb +66 -0
- data/tasks/promotion.rb +260 -0
- metadata +191 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
# Entrypoint for simple_tracker example asset tracker plugin.
|
2
|
+
# config options:
|
3
|
+
# tracker_data_file_path -- path and filename of where to save the asset data JSON file
|
4
|
+
# app_config_file_path -- path and filename of where to save the database configuration YAML file for a fictional web app
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Jetpants
|
9
|
+
module Plugin
|
10
|
+
|
11
|
+
# The SimpleTracker class just handles the manipulations of the asset JSON file and the application
|
12
|
+
# YAML file. The Jetpants::Topology class is monkeypatched to maintain a single SimpleTracker object,
|
13
|
+
# which it uses to interact with these files.
|
14
|
+
class SimpleTracker
|
15
|
+
# Array of hashes, each containing info from Pool#to_hash
|
16
|
+
attr_accessor :global_pools
|
17
|
+
|
18
|
+
# Array of hashes, each containing info from Shard#to_hash
|
19
|
+
attr_accessor :shards
|
20
|
+
|
21
|
+
# Array of any of the following:
|
22
|
+
# * hashes each containing key 'node'. could expand to include 'role' or other metadata as well,
|
23
|
+
# but currently not supported.
|
24
|
+
# * objects responding to to_db, such as String or Jetpants::DB
|
25
|
+
attr_accessor :spares
|
26
|
+
|
27
|
+
attr_reader :app_config_file_path
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@tracker_data_file_path = Jetpants.plugins['simple_tracker']['tracker_data_file_path'] || '/etc/jetpants_tracker.json'
|
31
|
+
@app_config_file_path = Jetpants.plugins['simple_tracker']['app_config_file_path'] || '/var/lib/mysite/config/databases.yaml'
|
32
|
+
data = JSON.parse(File.read(@tracker_data_file_path)) rescue {'pools' => {}, 'shards' => [], 'spares' => []}
|
33
|
+
@global_pools = data['pools']
|
34
|
+
@shards = data['shards']
|
35
|
+
@spares = data['spares']
|
36
|
+
end
|
37
|
+
|
38
|
+
def save
|
39
|
+
File.open(@tracker_data_file_path, 'w') do |f|
|
40
|
+
data = {'pools' => @global_pools, 'shards' => @shards, 'spares' => @spares}
|
41
|
+
f.puts JSON.pretty_generate(data)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def determine_pool_and_role(ip, port=3306)
|
46
|
+
ip += ":#{port}" if port.to_i != 3306
|
47
|
+
|
48
|
+
[@global_pools + @shards].each do |h|
|
49
|
+
pool = (h['name'] ? Jetpants.topology.pool(h['name']) : Jetpants.topology.shard(h['min_id'], h['max_id']))
|
50
|
+
return [pool, 'MASTER'] if h['master'] == ip
|
51
|
+
h['slaves'].each do |s|
|
52
|
+
return [pool, s['role']] if s['host'] == ip
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
raise "Unable to find #{ip} among tracked assets"
|
57
|
+
end
|
58
|
+
|
59
|
+
def determine_slaves(ip, port=3306)
|
60
|
+
ip += ":#{port}" if port.to_i != 3306
|
61
|
+
|
62
|
+
[@global_pools + @shards].each do |h|
|
63
|
+
next unless h['master'] == ip
|
64
|
+
return h['slaves'].map {|s| s['host'].to_db}
|
65
|
+
end
|
66
|
+
[] # return empty array if not a master
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# load all the monkeypatches for other Jetpants classes
|
74
|
+
%w(pool shard topology).each {|mod| require "simple_tracker/#{mod}"}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Jetpants
|
2
|
+
class Topology
|
3
|
+
|
4
|
+
attr_accessor :tracker
|
5
|
+
|
6
|
+
##### METHOD OVERRIDES #####################################################
|
7
|
+
|
8
|
+
# Populates @pools by reading asset tracker data
|
9
|
+
def load_pools
|
10
|
+
@tracker = Jetpants::Plugin::SimpleTracker.new
|
11
|
+
|
12
|
+
# Create Pool and Shard objects
|
13
|
+
@pools.concat(@tracker.global_pools.map {|h| Pool.from_hash(h)}.compact)
|
14
|
+
all_shards = @tracker.shards.map {|h| Shard.from_hash(h)}.reject {|s| s.state == :recycle}
|
15
|
+
@pools.concat all_shards
|
16
|
+
|
17
|
+
# Now that all shards exist, we can safely assign parent/child relationships
|
18
|
+
@tracker.shards.each {|h| Shard.assign_relationships(h, all_shards)}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Generates a database configuration file for a hypothetical web application
|
22
|
+
def write_config
|
23
|
+
config_file_path = @tracker.app_config_file_path
|
24
|
+
|
25
|
+
# Convert the pool list into a hash
|
26
|
+
db_data = {
|
27
|
+
'database' => {
|
28
|
+
'pools' => functional_partitions.map {|p| p.to_hash(true)},
|
29
|
+
'shards' => shards.select {|s| s.in_config?}.map {|s| s.to_hash(true)},
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
# Convert that hash to YAML and write it to a file
|
34
|
+
File.open(config_file_path, 'w') do |f|
|
35
|
+
f.write db_data.to_yaml
|
36
|
+
end
|
37
|
+
puts "Regenerated #{config_file_path}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def claim_spares(count, options={})
|
41
|
+
raise "Not enough spare machines -- requested #{count}, only have #{@tracker.spares.count}" if @tracker.spares.count < count
|
42
|
+
hashes = @tracker.spares.shift(count)
|
43
|
+
hashes.map {|h| h['node'] ? h['node'].to_db : h.to_db}
|
44
|
+
end
|
45
|
+
|
46
|
+
def count_spares(options={})
|
47
|
+
@tracker.spares.count
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
##### NEW METHODS ##########################################################
|
52
|
+
|
53
|
+
# Called by Pool#sync_configuration to update our asset tracker json.
|
54
|
+
# This actually re-writes all the json. With a more dynamic asset tracker
|
55
|
+
# (something backed by a database, for example) this wouldn't be necessary -
|
56
|
+
# instead Pool#sync_configuration could just update the info for that pool
|
57
|
+
# only.
|
58
|
+
def update_tracker_data
|
59
|
+
@tracker.global_pools = functional_partitions.map &:to_hash
|
60
|
+
@tracker.shards = shards.map &:to_hash
|
61
|
+
@tracker.save
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
data/tasks/promotion.rb
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
module Jetpants
|
2
|
+
module Tasks
|
3
|
+
class Promotion
|
4
|
+
|
5
|
+
def initialize nodes = {}
|
6
|
+
@demoted = nodes['demote']
|
7
|
+
@promoted = nodes['promote']
|
8
|
+
super
|
9
|
+
Jetpants.verify_replication = false # since master may be offline
|
10
|
+
advise
|
11
|
+
establish_roles
|
12
|
+
prepare
|
13
|
+
end
|
14
|
+
|
15
|
+
def error message
|
16
|
+
abort ['ERROR:'.red, message].join ' '
|
17
|
+
end
|
18
|
+
|
19
|
+
def inform message
|
20
|
+
puts message.blue
|
21
|
+
end
|
22
|
+
|
23
|
+
def is_ip? address
|
24
|
+
address =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
|
25
|
+
end
|
26
|
+
|
27
|
+
def establish_roles
|
28
|
+
establish_demoted
|
29
|
+
establish_replicas
|
30
|
+
establish_promoted
|
31
|
+
end
|
32
|
+
|
33
|
+
def establish_demoted
|
34
|
+
# derive demoted from promoted if possible
|
35
|
+
if @promoted and not @demoted
|
36
|
+
error "invalid ip address #{@promoted}" unless is_ip? @promoted
|
37
|
+
@promoted = Jetpants::DB.new @promoted
|
38
|
+
|
39
|
+
# bail the promoted node isn't a slave or we can't connect
|
40
|
+
unless @promoted.is_slave?
|
41
|
+
error "node (#{@promoted}) does not appear to be a replica of another node"
|
42
|
+
end rescue error("unable to connect to node #{@promoted} to promote")
|
43
|
+
|
44
|
+
# recommend a node to demote
|
45
|
+
agreed = agree [
|
46
|
+
"Would you like to demote the following node?",
|
47
|
+
"address: #{@promoted.master}",
|
48
|
+
"slaves : #{@promoted.master.slaves.join(', ')}",
|
49
|
+
"- yes/no -"
|
50
|
+
].join "\n"
|
51
|
+
error "unable to promote #{@promoted} unless you demote #{@promoted.master}" unless agreed
|
52
|
+
|
53
|
+
@demoted = @promoted.master.ip
|
54
|
+
end
|
55
|
+
|
56
|
+
# unable to derive demoted, so ask and convert to a DB object
|
57
|
+
unless @demoted.kind_of? Jetpants::DB
|
58
|
+
@demoted = ask 'Please enter the node to demote:' unless @demoted
|
59
|
+
error "Invalid IP address #{@demoted}" unless is_ip? @demoted
|
60
|
+
@demoted = @demoted.to_db
|
61
|
+
end
|
62
|
+
|
63
|
+
# connect and ensure node is a master; handle offline nodes appropriately
|
64
|
+
if @demoted.available?
|
65
|
+
error 'Cannot demote a node that has no slaves!' unless @demoted.has_slaves?
|
66
|
+
else
|
67
|
+
inform "unable to connect to node #{@demoted} to demote"
|
68
|
+
error "unable to perform promotion" unless agree "please confirm that #{@demoted} is offline: yes/no "
|
69
|
+
@replicas = @demoted.slaves # An asset-tracker plugin may have been populated the slave list anyway
|
70
|
+
if !@replicas || @replicas.count < 1
|
71
|
+
replicas = ask "please provide a comma seperated list of current replicas of #{@demoted}: ", lambda {|replicas| replicas.split /,\s*/}
|
72
|
+
error "user supplied list of replicas appears to be invalid - #{replicas}" unless replicas.all? {|replica| is_ip? replica}
|
73
|
+
@replicas = replicas.collect {|replica| replica.to_db}
|
74
|
+
|
75
|
+
# ensure they were replicas of @demoted
|
76
|
+
@replicas.each do |replica|
|
77
|
+
error "#{replica} does not appear to be a valid replica of #{@demoted}" unless replica.master == @demoted
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
error 'unable to establish demoteable node' unless @demoted.kind_of? Jetpants::DB
|
83
|
+
end
|
84
|
+
|
85
|
+
def establish_replicas
|
86
|
+
@replicas ||= @demoted.slaves
|
87
|
+
error 'no replicas to promote' if @replicas.empty?
|
88
|
+
error 'replicas appear to be invalid' unless @replicas.all? {|replica| replica.kind_of? Jetpants::DB}
|
89
|
+
inform "#{@demoted} has the following replicas: #{@replicas.join(', ')}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def establish_promoted
|
93
|
+
# user supplied node to promote
|
94
|
+
if @promoted and not @promoted.kind_of? Jetpants::DB
|
95
|
+
error "invalid ip address #{@promoted}" unless is_ip? @promoted
|
96
|
+
@promoted = Jetpants::DB.new @promoted
|
97
|
+
end
|
98
|
+
|
99
|
+
# user hasn't supplied a valid node to promote
|
100
|
+
unless @replicas.include? @promoted
|
101
|
+
inform "unable to promote node (#{@promoted}) that is not a replica of #{@demoted}" if @promoted
|
102
|
+
|
103
|
+
# recommend a node
|
104
|
+
puts "\nREPLICA LIST:"
|
105
|
+
@replicas.sort_by {|replica| replica.seconds_behind_master}.each do |node|
|
106
|
+
file, pos = node.repl_binlog_coordinates(false)
|
107
|
+
puts " * %-13s %-30s lag: %2ds coordinates: (%-13s, %d)" % [node.ip, node.hostname, node.seconds_behind_master, file, pos]
|
108
|
+
end
|
109
|
+
puts
|
110
|
+
recommended = @replicas.sort_by {|replica| replica.seconds_behind_master}.reject {|r| r.for_backups?}.first
|
111
|
+
agreed = agree [
|
112
|
+
"Would you like to promote the following replica?",
|
113
|
+
"#{recommended.ip} (#{recommended.hostname})",
|
114
|
+
"- yes/no -"
|
115
|
+
].join "\n"
|
116
|
+
@promoted = recommended if agreed
|
117
|
+
|
118
|
+
# choose a new node if they disagreed with our recommendation
|
119
|
+
unless agreed
|
120
|
+
choose do |promote|
|
121
|
+
promote.prompt = 'Please choose a replica to promote:'
|
122
|
+
@replicas.each do |replica|
|
123
|
+
promote.choice "#{replica} - replication lag: #{replica.seconds_behind_master} seconds" do
|
124
|
+
@promoted = replica
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
raise "You chose a backup slave. These are not suitable for promotion. Please try again." if @promoted.for_backups?
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
error "unable to establish node to promote" unless @promoted.kind_of? Jetpants::DB
|
133
|
+
end
|
134
|
+
|
135
|
+
def advise
|
136
|
+
@states = {
|
137
|
+
preparing: "processing promotion requirements",
|
138
|
+
prepared: "preparing to disable writes on #{@demoted}",
|
139
|
+
read_only: "writes have been disabled on #{@demoted}, preparing to demote #{@demoted} and promote #{@promoted}",
|
140
|
+
promoted: "#{@promoted} has been promoted, please prepare database config for deploy.",
|
141
|
+
deployable: "promotion is complete, please commit and deploy.",
|
142
|
+
}
|
143
|
+
inform @states[@state.to_sym]
|
144
|
+
end
|
145
|
+
|
146
|
+
state_machine :initial => :preparing do
|
147
|
+
after_transition any => any, :do => :advise
|
148
|
+
|
149
|
+
event :prepare do
|
150
|
+
transition :preparing => :prepared, :if => :roles_populated?
|
151
|
+
end
|
152
|
+
after_transition :preparing => :prepared, :do => :disable_writes
|
153
|
+
|
154
|
+
event :disable_writes do
|
155
|
+
transition :prepared => :read_only, :if => :read_only!
|
156
|
+
end
|
157
|
+
after_transition :prepared => :read_only, :do => :promote
|
158
|
+
|
159
|
+
event :promote do
|
160
|
+
transition :read_only => :promoted, :if => :execute_promotion
|
161
|
+
end
|
162
|
+
after_transition :read_only => :promoted, :do => :prepare_config
|
163
|
+
|
164
|
+
event :prepare_config do
|
165
|
+
transition :promoted => :deployable, :if => :nodes_consistent?
|
166
|
+
end
|
167
|
+
after_transition :promoted => :deployable, :do => :summarize_promotion
|
168
|
+
|
169
|
+
state :preparing, :prepared do
|
170
|
+
def is_db? node
|
171
|
+
node.kind_of? Jetpants::DB
|
172
|
+
end
|
173
|
+
|
174
|
+
def roles_populated?
|
175
|
+
# ensure our roles are populated with dbs
|
176
|
+
[@demoted, @promoted, @replicas].all? do |role|
|
177
|
+
is_db? role or role.all? do |node|
|
178
|
+
is_db? node
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def read_only!
|
184
|
+
unless @demoted.available?
|
185
|
+
status = @promoted.slave_status
|
186
|
+
@log, @position = status[:master_log_file], status[:exec_master_log_pos].to_i
|
187
|
+
return true
|
188
|
+
end
|
189
|
+
|
190
|
+
# set read_only if needed
|
191
|
+
@demoted.read_only! unless @demoted.read_only?
|
192
|
+
# bail if we're unable to set read_only
|
193
|
+
error "unable to set 'read_only' on #{@demoted}" unless @demoted.read_only?
|
194
|
+
# record the current log possition to ensure writes are not taking place later.
|
195
|
+
@log, @position = @demoted.binlog_coordinates
|
196
|
+
error "#{@demoted} is still taking writes, unable to promote #{@promoted}" unless writes_disabled?
|
197
|
+
@demoted.read_only?
|
198
|
+
end
|
199
|
+
|
200
|
+
def writes_disabled?
|
201
|
+
return true unless @demoted.available?
|
202
|
+
|
203
|
+
# ensure no writes have been logged since read_only!
|
204
|
+
[@log, @position] == @demoted.binlog_coordinates
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
state :read_only, :promoted, :promoted, :deployable do
|
210
|
+
def nodes_consistent?
|
211
|
+
return true unless @demoted.available?
|
212
|
+
@replicas.all? {|replica| replica.slave_status[:exec_master_log_pos].to_i == @position}
|
213
|
+
end
|
214
|
+
|
215
|
+
def ensure_nodes_consistent?
|
216
|
+
inform "ensuring replicas are in a consistent state"
|
217
|
+
until nodes_consistent? do
|
218
|
+
print '.'
|
219
|
+
sleep 0.5
|
220
|
+
end
|
221
|
+
nodes_consistent?
|
222
|
+
end
|
223
|
+
|
224
|
+
def promotable?
|
225
|
+
disable_replication if ensure_nodes_consistent? and @promoted.disable_read_only!
|
226
|
+
end
|
227
|
+
|
228
|
+
def execute_promotion
|
229
|
+
error 'nodes are not in a promotable state.' unless promotable?
|
230
|
+
error 'replicas are not in a consistent state' unless nodes_consistent?
|
231
|
+
|
232
|
+
@demoted.pool.master_promotion! @promoted
|
233
|
+
end
|
234
|
+
|
235
|
+
def replicas_replicating? replicas = @replicas
|
236
|
+
replicas.all? {|replica| replica.replicating?}
|
237
|
+
end
|
238
|
+
|
239
|
+
def disable_replication replicas = @replicas
|
240
|
+
replicas.each do |replica|
|
241
|
+
replica.pause_replication if replica.replicating?
|
242
|
+
end
|
243
|
+
not replicas_replicating? replicas
|
244
|
+
end
|
245
|
+
|
246
|
+
def summarize_promotion transition
|
247
|
+
summary = Terminal::Table.new :title => 'Promotion Summary:' do |rows|
|
248
|
+
rows << ['demoted', @demoted]
|
249
|
+
rows << ['promoted', @promoted]
|
250
|
+
rows << ["replicas of #{@promoted}", @promoted.slaves.join(', ')]
|
251
|
+
end
|
252
|
+
puts summary
|
253
|
+
exit
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
metadata
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jetpants
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.7.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Evan Elias
|
9
|
+
- Dallas Marlow
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2012-06-07 00:00:00 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: mysql2
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sequel
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0"
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: net-ssh
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: state_machine
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: pry
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
type: :runtime
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: thor
|
73
|
+
prerelease: false
|
74
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
type: :runtime
|
81
|
+
version_requirements: *id006
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: highline
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: "0"
|
91
|
+
type: :runtime
|
92
|
+
version_requirements: *id007
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: terminal-table
|
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
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: colored
|
106
|
+
prerelease: false
|
107
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: "0"
|
113
|
+
type: :runtime
|
114
|
+
version_requirements: *id009
|
115
|
+
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.
|
116
|
+
email:
|
117
|
+
- me@evanelias.com
|
118
|
+
- dallasmarlow@gmail.com
|
119
|
+
executables:
|
120
|
+
- jetpants
|
121
|
+
extensions: []
|
122
|
+
|
123
|
+
extra_rdoc_files:
|
124
|
+
- README.rdoc
|
125
|
+
- doc/plugins.rdoc
|
126
|
+
- doc/configuration.rdoc
|
127
|
+
- doc/commands.rdoc
|
128
|
+
- doc/requirements.rdoc
|
129
|
+
files:
|
130
|
+
- Gemfile
|
131
|
+
- README.rdoc
|
132
|
+
- doc/plugins.rdoc
|
133
|
+
- doc/configuration.rdoc
|
134
|
+
- doc/commands.rdoc
|
135
|
+
- doc/requirements.rdoc
|
136
|
+
- lib/jetpants/callback.rb
|
137
|
+
- lib/jetpants/topology.rb
|
138
|
+
- lib/jetpants/db/server.rb
|
139
|
+
- lib/jetpants/db/state.rb
|
140
|
+
- lib/jetpants/db/import_export.rb
|
141
|
+
- lib/jetpants/db/privileges.rb
|
142
|
+
- lib/jetpants/db/client.rb
|
143
|
+
- lib/jetpants/db/replication.rb
|
144
|
+
- lib/jetpants/shard.rb
|
145
|
+
- lib/jetpants/db.rb
|
146
|
+
- lib/jetpants/host.rb
|
147
|
+
- lib/jetpants/pool.rb
|
148
|
+
- lib/jetpants/monkeypatch.rb
|
149
|
+
- lib/jetpants/table.rb
|
150
|
+
- lib/jetpants.rb
|
151
|
+
- bin/jetpants
|
152
|
+
- plugins/simple_tracker/topology.rb
|
153
|
+
- plugins/simple_tracker/shard.rb
|
154
|
+
- plugins/simple_tracker/simple_tracker.rb
|
155
|
+
- plugins/simple_tracker/db.rb
|
156
|
+
- plugins/simple_tracker/pool.rb
|
157
|
+
- tasks/promotion.rb
|
158
|
+
- etc/jetpants.yaml.sample
|
159
|
+
homepage: https://github.com/tumblr/jetpants/
|
160
|
+
licenses: []
|
161
|
+
|
162
|
+
post_install_message:
|
163
|
+
rdoc_options:
|
164
|
+
- --line-numbers
|
165
|
+
- --title
|
166
|
+
- "Jetpants: a MySQL automation toolkit by Tumblr"
|
167
|
+
- --main
|
168
|
+
- README.rdoc
|
169
|
+
require_paths:
|
170
|
+
- lib
|
171
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
172
|
+
none: false
|
173
|
+
requirements:
|
174
|
+
- - ">="
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: 1.9.2
|
177
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
178
|
+
none: false
|
179
|
+
requirements:
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: "0"
|
183
|
+
requirements: []
|
184
|
+
|
185
|
+
rubyforge_project:
|
186
|
+
rubygems_version: 1.8.10
|
187
|
+
signing_key:
|
188
|
+
specification_version: 3
|
189
|
+
summary: "Jetpants: a MySQL automation toolkit by Tumblr"
|
190
|
+
test_files: []
|
191
|
+
|