jetpants 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|