jetpants 0.7.8 → 0.7.10
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +8 -6
- data/bin/jetpants +2 -2
- data/doc/commands.rdoc +3 -3
- data/doc/configuration.rdoc +40 -0
- data/doc/faq.rdoc +6 -2
- data/doc/jetpants_collins.rdoc +95 -0
- data/doc/plugins.rdoc +7 -4
- data/doc/requirements.rdoc +2 -2
- data/etc/jetpants.yaml.sample +5 -0
- data/lib/jetpants.rb +2 -0
- data/lib/jetpants/db/state.rb +18 -2
- data/lib/jetpants/host.rb +17 -6
- data/lib/jetpants/monkeypatch.rb +26 -0
- data/lib/jetpants/pool.rb +2 -2
- data/lib/jetpants/topology.rb +22 -0
- data/plugins/jetpants_collins/asset.rb +77 -0
- data/plugins/jetpants_collins/db.rb +77 -0
- data/plugins/jetpants_collins/host.rb +41 -0
- data/plugins/jetpants_collins/jetpants_collins.rb +214 -0
- data/plugins/jetpants_collins/pool.rb +145 -0
- data/plugins/jetpants_collins/shard.rb +106 -0
- data/plugins/jetpants_collins/topology.rb +239 -0
- metadata +37 -17
data/lib/jetpants/topology.rb
CHANGED
@@ -87,6 +87,15 @@ module Jetpants
|
|
87
87
|
raise "Plugin must override Topology#count_spares"
|
88
88
|
end
|
89
89
|
|
90
|
+
# Returns a list of valid role symbols in use in Jetpants.
|
91
|
+
def valid_roles
|
92
|
+
[:master, :active_slave, :standby_slave, :backup_slave]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns a list of valid role symbols which indicate a slave status
|
96
|
+
def slave_roles
|
97
|
+
valid_roles.reject {|r| r == :master}
|
98
|
+
end
|
90
99
|
|
91
100
|
###### Instance Methods ####################################################
|
92
101
|
|
@@ -144,6 +153,19 @@ module Jetpants
|
|
144
153
|
claim_spares(1, options)[0]
|
145
154
|
end
|
146
155
|
|
156
|
+
# Returns if the supplied role is valid
|
157
|
+
def valid_role? role
|
158
|
+
valid_roles.include? role.to_s.downcase.to_sym
|
159
|
+
end
|
160
|
+
|
161
|
+
# Converts the supplied roles (strings or symbols) into lowercase symbol versions
|
162
|
+
# Will expand out special role of :slave to be all slave roles.
|
163
|
+
def normalize_roles(*roles)
|
164
|
+
roles = roles.flatten.map {|r| r.to_s.downcase == 'slave' ? slave_roles.map(&:to_s) : r.to_s.downcase}.flatten
|
165
|
+
roles.each {|r| raise "#{r} is not a valid role" unless valid_role? r}
|
166
|
+
roles.uniq.map &:to_sym
|
167
|
+
end
|
168
|
+
|
147
169
|
synchronized
|
148
170
|
# Clears the pool list and nukes cached DB and Host object lookup tables
|
149
171
|
def clear
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Adds conversion methods to the Collins::Asset class for obtaining Jetpants equivalents
|
2
|
+
|
3
|
+
module Collins
|
4
|
+
class Asset
|
5
|
+
|
6
|
+
# Convert a Collins::Asset to a Jetpants::DB. Requires asset TYPE to be SERVER_NODE.
|
7
|
+
def to_db
|
8
|
+
raise "Can only call to_db on SERVER_NODE assets, but #{self} has type #{type}" unless type.upcase == 'SERVER_NODE'
|
9
|
+
backend_ip_address.to_db
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
# Convert a Collins::Asset to a Jetpants::Host. Requires asset TYPE to be SERVER_NODE.
|
14
|
+
def to_host
|
15
|
+
raise "Can only call to_host on SERVER_NODE assets, but #{self} has type #{type}" unless type.upcase == 'SERVER_NODE'
|
16
|
+
backend_ip_address.to_host
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Convert a Collins::Asset to either a Jetpants::Pool or a Jetpants::Shard, depending
|
21
|
+
# on the value of PRIMARY_ROLE. Requires asset TYPE to be CONFIGURATION.
|
22
|
+
def to_pool
|
23
|
+
raise "Can only call to_pool on CONFIGURATION assets, but #{self} has type #{type}" unless type.upcase == 'CONFIGURATION'
|
24
|
+
raise "Unknown primary role #{primary_role} for configuration asset #{self}" unless ['MYSQL_POOL', 'MYSQL_SHARD'].include?(primary_role.upcase)
|
25
|
+
raise "No pool attribute set on asset #{self}" unless pool && pool.length > 0
|
26
|
+
|
27
|
+
# Find the master(s) for this pool. If we got back multiple masters, first
|
28
|
+
# try ignoring the remote datacenter ones
|
29
|
+
master_assets = Jetpants.topology.server_node_assets(pool.downcase, :master)
|
30
|
+
if master_assets.count > 1
|
31
|
+
results = master_assets.select {|a| a.location.nil? || a.location.upcase == Plugin::JetCollins.datacenter}
|
32
|
+
master_assets = results if results.count > 0
|
33
|
+
end
|
34
|
+
puts "WARNING: multiple masters found for pool #{pool}; using first match" if master_assets.count > 1
|
35
|
+
|
36
|
+
if master_assets.count == 0
|
37
|
+
puts "WARNING: no masters found for pool #{pool}; ignoring pool entirely"
|
38
|
+
result = nil
|
39
|
+
|
40
|
+
elsif primary_role.upcase == 'MYSQL_POOL'
|
41
|
+
result = Jetpants::Pool.new(pool.downcase, master_assets.first.to_db)
|
42
|
+
if aliases
|
43
|
+
aliases.split(',').each {|a| result.add_alias(a.downcase)}
|
44
|
+
end
|
45
|
+
result.slave_name = slave_pool_name if slave_pool_name
|
46
|
+
result.master_read_weight = master_read_weight if master_read_weight
|
47
|
+
|
48
|
+
# We intentionally only look for active slaves in the current datacenter, since we
|
49
|
+
# treat other datacenters' slaves as backup slaves to prevent promotion or cross-DC usage
|
50
|
+
active_slave_assets = Jetpants.topology.server_node_assets(pool.downcase, :active_slave)
|
51
|
+
active_slave_assets.reject! {|a| a.location && a.location.upcase != Plugin::JetCollins.datacenter}
|
52
|
+
active_slave_assets.each do |asset|
|
53
|
+
weight = asset.slave_weight && asset.slave_weight.to_i > 0 ? asset.slave_weight.to_i : 100
|
54
|
+
result.has_active_slave(asset.to_db, weight)
|
55
|
+
end
|
56
|
+
|
57
|
+
elsif primary_role.upcase == 'MYSQL_SHARD'
|
58
|
+
result = Jetpants::Shard.new(shard_min_id.to_i,
|
59
|
+
shard_max_id == 'INFINITY' ? 'INFINITY' : shard_max_id.to_i,
|
60
|
+
master_assets.first.to_db,
|
61
|
+
shard_state.downcase.to_sym)
|
62
|
+
|
63
|
+
# We'll need to set up the parent/child relationship if a shard split is in progress,
|
64
|
+
# BUT we need to wait to do that later since the shards may have been returned by
|
65
|
+
# Collins out-of-order, so the parent shard object might not exist yet.
|
66
|
+
# For now we just remember the NAME of the parent shard.
|
67
|
+
result.has_parent = shard_parent if shard_parent
|
68
|
+
|
69
|
+
else
|
70
|
+
raise "Unknown configuration asset primary role #{primary_role} for asset #{self}"
|
71
|
+
end
|
72
|
+
|
73
|
+
result
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# JetCollins monkeypatches to add Collins integration
|
2
|
+
|
3
|
+
module Jetpants
|
4
|
+
class DB
|
5
|
+
|
6
|
+
##### JETCOLLINS MIX-IN ####################################################
|
7
|
+
|
8
|
+
include Plugin::JetCollins
|
9
|
+
|
10
|
+
collins_attr_accessor :slave_weight
|
11
|
+
|
12
|
+
# Because we only support 1 mysql instance per machine for now, we can just
|
13
|
+
# delegate this over to the host
|
14
|
+
def collins_asset
|
15
|
+
@host.collins_asset
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
##### METHOD OVERRIDES #####################################################
|
20
|
+
|
21
|
+
# Add an actual collins check to confirm a machine is a standby
|
22
|
+
def is_standby?
|
23
|
+
!(running?) || (is_slave? && !taking_connections? && collins_secondary_role == 'standby_slave')
|
24
|
+
end
|
25
|
+
|
26
|
+
# Treat any node outside of current data center as being for backups.
|
27
|
+
# This prevents inadvertent cross-data-center master promotion.
|
28
|
+
def for_backups?
|
29
|
+
hostname.start_with?('backup') || in_remote_datacenter?
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
##### CALLBACKS ############################################################
|
34
|
+
|
35
|
+
# Determine master from Collins if machine is unreachable or MySQL isn't running.
|
36
|
+
def after_probe_master
|
37
|
+
unless @running
|
38
|
+
if collins_secondary_role == 'master'
|
39
|
+
@master = false
|
40
|
+
else
|
41
|
+
pool = Jetpants.topology.pool(collins_pool)
|
42
|
+
@master = pool.master if pool
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# We completely ignore cross-data-center master unless inter_dc_mode is enabled.
|
47
|
+
# This may change in a future Jetpants release, once we support tiered replication more cleanly.
|
48
|
+
if @master && @master.in_remote_datacenter? && !Jetpants::Plugin::JetCollins.inter_dc_mode?
|
49
|
+
@remote_master = @master # keep track of it, in case we need to know later
|
50
|
+
@master = false
|
51
|
+
elsif !@master
|
52
|
+
in_remote_datacenter? # just calling to cache for current node, before we probe its slaves, so that its slaves don't need to query Collins
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Determine slaves from Collins if machine is unreachable or MySQL isn't running
|
57
|
+
def after_probe_slaves
|
58
|
+
# If this machine has a master AND has slaves of its own AND is in another data center,
|
59
|
+
# ignore its slaves entirely unless inter_dc_mode is enabled.
|
60
|
+
# This may change in a future Jetpants release, once we support tiered replication more cleanly.
|
61
|
+
@slaves = [] if @running && @master && @slaves.count > 0 && in_remote_datacenter? && !Jetpants::Plugin::JetCollins.inter_dc_mode?
|
62
|
+
|
63
|
+
unless @running
|
64
|
+
p = Jetpants.topology.pool(self)
|
65
|
+
@slaves = (p ? p.slaves_according_to_collins : [])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
##### NEW METHODS ##########################################################
|
71
|
+
|
72
|
+
def in_remote_datacenter?
|
73
|
+
@host.collins_location != Plugin::JetCollins.datacenter
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# JetCollins monkeypatches to add Collins integration
|
2
|
+
|
3
|
+
module Jetpants
|
4
|
+
class Host
|
5
|
+
|
6
|
+
##### JETCOLLINS MIX-IN ####################################################
|
7
|
+
|
8
|
+
include Plugin::JetCollins
|
9
|
+
|
10
|
+
def collins_asset
|
11
|
+
# try IP first; failing that, try hostname
|
12
|
+
selector = {ip_address: ip, details: true}
|
13
|
+
selector[:remoteLookup] = true if Jetpants.plugins['jetpants_collins']['remote_lookup']
|
14
|
+
assets = Plugin::JetCollins.find selector
|
15
|
+
|
16
|
+
if (!assets || assets.count == 0) && available?
|
17
|
+
selector = {hostname: "^#{hostname}$", details: true}
|
18
|
+
selector[:remoteLookup] = true if Jetpants.plugins['jetpants_collins']['remote_lookup']
|
19
|
+
assets = Plugin::JetCollins.find selector
|
20
|
+
end
|
21
|
+
|
22
|
+
raise "Multiple assets found for #{self}" if assets.count > 1
|
23
|
+
if ! assets || assets.count == 0
|
24
|
+
output "WARNING: no Collins assets found for this host"
|
25
|
+
nil
|
26
|
+
else
|
27
|
+
assets.first
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns which datacenter this host is in. Only a getter, intentionally no setter.
|
32
|
+
def collins_location
|
33
|
+
return @collins_location if @collins_location
|
34
|
+
ca = collins_asset
|
35
|
+
@collins_location ||= (ca ? ca.location || Plugin::JetCollins.datacenter : 'unknown')
|
36
|
+
@collins_location.upcase!
|
37
|
+
@collins_location
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'collins_client'
|
2
|
+
|
3
|
+
# Entrypoint for jetpants_collins plugin (namespace Jetpants::Plugin::JetCollins),
|
4
|
+
# which offers integration with the Collins hardware asset tracking system.
|
5
|
+
# This particular file accomplishes the following:
|
6
|
+
#
|
7
|
+
# * Provides a JetCollins mixin module. Any class including this should also
|
8
|
+
# implement a collins_asset method to convert objects to Collins assets;
|
9
|
+
# the class can then use the provided collins_get and collins_set wrappers,
|
10
|
+
# along with the collins_attr_accessor class method.
|
11
|
+
#
|
12
|
+
# * Jetpants::Plugin::JetCollins can also be used as a global Collins API
|
13
|
+
# client -- the module itself will delegate all missing methods to a
|
14
|
+
# Collins::Client object.
|
15
|
+
#
|
16
|
+
# * Loads monkeypatches for Jetpants classes DB, Host, Pool, Shard, Topology,
|
17
|
+
# and Collins class Asset.
|
18
|
+
#
|
19
|
+
# Configuration options in Jetpants config file include:
|
20
|
+
# user => collins account username (required)
|
21
|
+
# password => collins account password (required)
|
22
|
+
# url => collins URL (required)
|
23
|
+
# timeout => collins client timeout, in seconds (default: 30)
|
24
|
+
# datacenter => collins data center name that we're running Jetpants in the context of (required if multi-datacenter)
|
25
|
+
# remote_lookup => if true, supply remoteLookup parameter to search multiple datacenters (default: false)
|
26
|
+
|
27
|
+
|
28
|
+
module Jetpants
|
29
|
+
module Plugin
|
30
|
+
module JetCollins
|
31
|
+
@collins_service = nil
|
32
|
+
|
33
|
+
##### CLASS METHODS ######################################################
|
34
|
+
|
35
|
+
class << self
|
36
|
+
|
37
|
+
# We delegate missing class (module) methods to the collins API client,
|
38
|
+
# if it responds to them.
|
39
|
+
def method_missing(name, *args, &block)
|
40
|
+
if service.respond_to? name
|
41
|
+
service.send name, *args, &block
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Eigenclass mix-in for collins_attr_accessor
|
48
|
+
# Calling "collins_attr_accessor :foo" in your class body will create
|
49
|
+
# methods collins_foo and collins_foo= which automatically get/set
|
50
|
+
# Collins attribute foo
|
51
|
+
def included(base)
|
52
|
+
base.class_eval do
|
53
|
+
def self.collins_attr_accessor(*fields)
|
54
|
+
fields.each do |field|
|
55
|
+
define_method("collins_#{field}") do
|
56
|
+
(collins_get(field) || '').downcase
|
57
|
+
end
|
58
|
+
define_method("collins_#{field}=") do |value|
|
59
|
+
collins_set(field, value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# We make these 4 accessors available to ANY class including this mixin
|
65
|
+
collins_attr_accessor :primary_role, :secondary_role, :pool, :status
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the 'datacenter' config option for this plugin, or 'UNKNOWN-DC' if
|
70
|
+
# none has been configured. This only matters in multi-datacenter Collins
|
71
|
+
# topologies.
|
72
|
+
def datacenter
|
73
|
+
(Jetpants.plugins['jetpants_collins']['datacenter'] || 'UNKNOWN-DC').upcase
|
74
|
+
end
|
75
|
+
|
76
|
+
# Ordinarily, in a multi-dacenter environment, jetpants_collins places a number
|
77
|
+
# of restrictions on interacting with assets that aren't in the local datacenter,
|
78
|
+
# for safety's sake and to simplify how hierarchical replication trees are represented:
|
79
|
+
#
|
80
|
+
# * Won't change Collins attributes on remote server node assets.
|
81
|
+
# * If a local node has a master in a remote datacenter, it is ignored/hidden.
|
82
|
+
# * If a local node has a slave in a remote datacenter, it's treated as a backup_slave,
|
83
|
+
# in order to prevent cross-datacenter master promotions. If any of these
|
84
|
+
# remote-datacenter slaves have slaves of their own, they're ignored/hidden.
|
85
|
+
#
|
86
|
+
# You may DISABLE these restrictions by calling enable_inter_dc_mode. Normally you
|
87
|
+
# do NOT want to do this, except in special sitautions like a migration between
|
88
|
+
# datacenters.
|
89
|
+
def enable_inter_dc_mode
|
90
|
+
Jetpants.plugins['jetpants_collins']['inter_dc_mode'] = true
|
91
|
+
Jetpants.plugins['jetpants_collins']['remote_lookup'] = true
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns true if enable_inter_dc_mode has been called, false otherwise.
|
95
|
+
def inter_dc_mode?
|
96
|
+
Jetpants.plugins['jetpants_collins']['inter_dc_mode'] || false
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# Returns a Collins::Client object
|
103
|
+
def service
|
104
|
+
return @collins_service if @collins_service
|
105
|
+
|
106
|
+
%w(url user password).each do |setting|
|
107
|
+
raise "No Collins #{setting} set in plugins -> jetpants_collins -> #{setting}" unless Jetpants.plugins['jetpants_collins'][setting]
|
108
|
+
end
|
109
|
+
|
110
|
+
logger = Logger.new(STDOUT)
|
111
|
+
logger.level = Logger::INFO
|
112
|
+
config = {
|
113
|
+
:host => Jetpants.plugins['jetpants_collins']['url'],
|
114
|
+
:timeout => Jetpants.plugins['jetpants_collins']['timeout'] || 30,
|
115
|
+
:username => Jetpants.plugins['jetpants_collins']['user'],
|
116
|
+
:password => Jetpants.plugins['jetpants_collins']['password'],
|
117
|
+
:logger => logger,
|
118
|
+
}
|
119
|
+
@collins_service = Collins::Client.new(config)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
##### INSTANCE (MIX-IN) METHODS ##########################################
|
125
|
+
|
126
|
+
# The base class needs to implement this!
|
127
|
+
def collins_asset
|
128
|
+
raise "Any class including Plugin::JetCollins must also implement collins_asset instance method!"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Pass in a symbol, or array of symbols, to obtain from Collins for this
|
132
|
+
# asset. For example, :status, :pool, :primary_role, :secondary_role.
|
133
|
+
# If you pass in a single symbol, returns a single value.
|
134
|
+
# If you pass in an array, returns a hash mapping each of these fields to their values.
|
135
|
+
# Hash will also contain an extra field called :asset, storing the Collins::Asset object.
|
136
|
+
def collins_get(*field_names)
|
137
|
+
asset = collins_asset
|
138
|
+
if field_names.count > 1 || field_names[0].is_a?(Array)
|
139
|
+
field_names.flatten!
|
140
|
+
results = Hash[field_names.map {|field| [field, (asset ? asset.send(field) : '')]}]
|
141
|
+
results[:asset] = asset
|
142
|
+
results
|
143
|
+
elsif field_names.count == 1
|
144
|
+
return '' unless asset
|
145
|
+
asset.send field_names[0]
|
146
|
+
else
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Pass in a hash mapping field name symbols to values to set
|
152
|
+
# Symbol => String -- optionally set any Collins attribute
|
153
|
+
# :status => String -- optionally set the status value for the asset
|
154
|
+
# :asset => Collins::Asset -- optionally pass this in to avoid an extra Collins API lookup, if asset already obtained
|
155
|
+
#
|
156
|
+
# Alternatively, pass in 2 strings (field_name, value) to set just a single Collins attribute (or status)
|
157
|
+
def collins_set(*args)
|
158
|
+
attrs = (args.count == 1 ? args[0] : {args[0] => args[1]})
|
159
|
+
asset = attrs[:asset] || collins_asset
|
160
|
+
|
161
|
+
# refuse to set Collins values on machines in remote data center unless
|
162
|
+
# inter_dc_mode is enabled
|
163
|
+
if asset && asset.type.downcase == 'server_node' && asset.location && asset.location.upcase != Plugin::JetCollins.datacenter
|
164
|
+
asset = nil unless Jetpants::Plugin::JetCollins.inter_dc_mode?
|
165
|
+
end
|
166
|
+
|
167
|
+
attrs.each do |key, val|
|
168
|
+
val ||= ''
|
169
|
+
case key
|
170
|
+
when :asset
|
171
|
+
next
|
172
|
+
when :status
|
173
|
+
unless asset
|
174
|
+
output "WARNING: unable to set Collins status to #{val}"
|
175
|
+
next
|
176
|
+
end
|
177
|
+
previous_value = asset.status
|
178
|
+
if previous_value != val.to_s
|
179
|
+
success = Jetpants::Plugin::JetCollins.set_status!(asset, val)
|
180
|
+
raise "#{self}: Unable to set Collins status to #{val}" unless success
|
181
|
+
output "Collins status changed from #{previous_value} to #{val}"
|
182
|
+
end
|
183
|
+
else
|
184
|
+
unless asset
|
185
|
+
output "WARNING: unable to set Collins attribute #{key} to #{val}"
|
186
|
+
next
|
187
|
+
end
|
188
|
+
previous_value = asset.send(key)
|
189
|
+
if previous_value != val.to_s.upcase
|
190
|
+
success = Jetpants::Plugin::JetCollins.set_attribute!(asset, key.to_s.upcase, val.to_s.upcase)
|
191
|
+
raise "#{self}: Unable to set Collins attribute #{key} to #{val}" unless success
|
192
|
+
if (val.to_s == '' || !val) && (previous_value == '' || !previous_value)
|
193
|
+
false
|
194
|
+
elsif val.to_s == ''
|
195
|
+
output "Collins attribute #{key.to_s.upcase} removed (was: #{previous_value})"
|
196
|
+
elsif !previous_value || previous_value == ''
|
197
|
+
output "Collins attribute #{key.to_s.upcase} set to #{val.to_s.upcase}"
|
198
|
+
else
|
199
|
+
output "Collins attribute #{key.to_s.upcase} changed from #{previous_value} to #{val.to_s.upcase}"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
end # module JetCollins
|
208
|
+
end # module Plugin
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
# load all the monkeypatches for other Jetpants classes
|
213
|
+
%w(asset host db pool shard topology).each {|mod| require "jetpants_collins/#{mod}"}
|
214
|
+
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# JetCollins monkeypatches to add Collins integration
|
2
|
+
|
3
|
+
module Jetpants
|
4
|
+
class Pool
|
5
|
+
|
6
|
+
##### JETCOLLINS MIX-IN ####################################################
|
7
|
+
|
8
|
+
include Plugin::JetCollins
|
9
|
+
|
10
|
+
# Used at startup time, to keep track of parent/child shard relationships
|
11
|
+
attr_accessor :has_parent
|
12
|
+
|
13
|
+
# Collins accessors for configuration asset metadata
|
14
|
+
collins_attr_accessor :slave_pool_name, :aliases, :master_read_weight, :config_sort_order
|
15
|
+
|
16
|
+
# Returns a Collins::Asset for this pool. Can optionally create one if not found.
|
17
|
+
def collins_asset(create_if_missing=false)
|
18
|
+
selector = {
|
19
|
+
operation: 'and',
|
20
|
+
details: true,
|
21
|
+
type: 'CONFIGURATION',
|
22
|
+
primary_role: 'MYSQL_POOL',
|
23
|
+
pool: "^#{@name.upcase}$",
|
24
|
+
status: 'Allocated',
|
25
|
+
}
|
26
|
+
selector[:remoteLookup] = true if Jetpants.plugins['jetpants_collins']['remote_lookup']
|
27
|
+
|
28
|
+
results = Plugin::JetCollins.find selector
|
29
|
+
|
30
|
+
# If we got back multiple results, try ignoring the remote datacenter ones
|
31
|
+
if results.count > 1
|
32
|
+
filtered_results = results.select {|a| a.location.nil? || a.location.upcase == Plugin::JetCollins.datacenter}
|
33
|
+
results = filtered_results if filtered_results.count > 0
|
34
|
+
end
|
35
|
+
|
36
|
+
if results.count > 1
|
37
|
+
raise "Multiple configuration assets found for pool #{name}"
|
38
|
+
elsif results.count == 0 && create_if_missing
|
39
|
+
output "Could not find configuration asset for pool; creating now"
|
40
|
+
new_tag = 'mysql-' + @name
|
41
|
+
asset = Collins::Asset.new type: 'CONFIGURATION', tag: new_tag, status: 'Allocated'
|
42
|
+
begin
|
43
|
+
Plugin::JetCollins.create!(asset)
|
44
|
+
rescue
|
45
|
+
collins_set asset: asset,
|
46
|
+
status: 'Allocated'
|
47
|
+
end
|
48
|
+
collins_set asset: asset,
|
49
|
+
primary_role: 'MYSQL_POOL',
|
50
|
+
pool: @name.upcase
|
51
|
+
Plugin::JetCollins.get new_tag
|
52
|
+
elsif results.count == 0 && !create_if_missing
|
53
|
+
raise "Could not find configuration asset for pool #{name}"
|
54
|
+
else
|
55
|
+
results.first
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
##### METHOD OVERRIDES #####################################################
|
61
|
+
|
62
|
+
# Examines the current state of the pool (as known to Jetpants) and updates
|
63
|
+
# Collins to reflect this, in terms of the pool's configuration asset as
|
64
|
+
# well as the individual hosts.
|
65
|
+
def sync_configuration
|
66
|
+
asset = collins_asset(true)
|
67
|
+
collins_set asset: asset,
|
68
|
+
slave_pool_name: slave_name || '',
|
69
|
+
aliases: aliases.join(',') || '',
|
70
|
+
master_read_weight: master_read_weight
|
71
|
+
[@master, slaves].flatten.each do |db|
|
72
|
+
current_status = (db.collins_status || '').downcase
|
73
|
+
db.collins_status = 'Allocated' unless current_status == 'maintenance'
|
74
|
+
db.collins_pool = @name
|
75
|
+
end
|
76
|
+
@master.collins_secondary_role = 'MASTER'
|
77
|
+
slaves(:active).each do |db|
|
78
|
+
db.collins_secondary_role = 'ACTIVE_SLAVE'
|
79
|
+
weight = @active_slave_weights[db]
|
80
|
+
db.collins_slave_weight = (weight == 100 ? '' : weight)
|
81
|
+
end
|
82
|
+
|
83
|
+
slaves(:standby).each {|db| db.collins_secondary_role = 'STANDBY_SLAVE'}
|
84
|
+
slaves(:backup).each {|db| db.collins_secondary_role = 'BACKUP_SLAVE'}
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
# If the pool's master hasn't been probed yet, return active_slaves list
|
89
|
+
# based strictly on what we found in Collins. This is a major speed-up at
|
90
|
+
# start-up time, especially for tasks that need to iterate over all pools.
|
91
|
+
alias :active_slaves_from_probe :active_slaves
|
92
|
+
def active_slaves
|
93
|
+
if @master.probed?
|
94
|
+
active_slaves_from_probe
|
95
|
+
else
|
96
|
+
@active_slave_weights.keys
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
##### CALLBACKS ############################################################
|
102
|
+
|
103
|
+
# Pushes slave removal to Collins. (Normally this type of logic is handled by
|
104
|
+
# Pool#sync_configuration, but that won't handle this case, since
|
105
|
+
# sync_configuration only updates hosts still in the pool.)
|
106
|
+
def after_remove_slave!(slave_db)
|
107
|
+
slave_db.collins_pool = slave_db.collins_secondary_role = slave_db.collins_slave_weight = ''
|
108
|
+
current_status = (slave_db.collins_status || '').downcase
|
109
|
+
slave_db.collins_status = 'Unallocated' unless current_status == 'maintenance'
|
110
|
+
end
|
111
|
+
|
112
|
+
# If the demoted master was offline, record some info in Collins, otherwise
|
113
|
+
# there will be 2 masters listed
|
114
|
+
def after_master_promotion!(promoted)
|
115
|
+
Jetpants.topology.clear_asset_cache
|
116
|
+
|
117
|
+
# Find the master asset(s) for this pool, filtering down to only current datacenter
|
118
|
+
assets = Jetpants.topology.server_node_assets(@name, :master)
|
119
|
+
assets.reject! {|a| a.location && a.location.upcase != Plugin::JetCollins.datacenter}
|
120
|
+
assets.map(&:to_db).each do |db|
|
121
|
+
if db != @master || !db.running?
|
122
|
+
db.collins_status = 'Maintenance'
|
123
|
+
db.collins_pool = ''
|
124
|
+
db.collins_secondary_role = ''
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
##### NEW METHODS ##########################################################
|
131
|
+
|
132
|
+
# Called from DB#after_probe_master and DB#after_probe_slave for machines
|
133
|
+
# that are unreachable via SSH, or reachable but MySQL isn't running.
|
134
|
+
def slaves_according_to_collins
|
135
|
+
results = []
|
136
|
+
Jetpants.topology.server_node_assets(@name, :slave).each do |asset|
|
137
|
+
slave = asset.to_db
|
138
|
+
output "Collins found slave #{slave.ip} (#{slave.hostname})"
|
139
|
+
results << slave
|
140
|
+
end
|
141
|
+
results
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|