jetpants 0.7.8 → 0.7.10
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/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
|