mysql_health 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mysql_health.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 osterman
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # MysqlHealth
2
+
3
+ MySQL Health is a standalone HTTP server that will respond with a 200 status code when MySQL is operating as expected.
4
+
5
+ This script is intended to be used in conjuction with HAProxy "option httpchk" for a TCP load balancer distributing load across mysql servers.
6
+
7
+ ## FAQs
8
+
9
+ 1. If you get the error "caught DBI::InterfaceError exception 'Could not load driver (uninitialized constant MysqlError)'" on OSX, try doing this:
10
+ `export DYLD_LIBRARY_PATH="/usr/local/mysql/lib:$DYLD_LIBRARY_PATH"`
11
+
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'mysql_health'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install mysql_health
26
+
27
+ ## Usage
28
+
29
+ Usage: mysql_health options
30
+ --check:master Master health check
31
+ --check:slave Slave health check
32
+ --check:allow-overlapping Allow overlapping health checks (default: false)
33
+ --check:interval INTERVAL Check health every INTERVAL (default: 10s)
34
+ --check:delay DELAY Delay health checks for INTERVAL (default: 0s)
35
+ --check:dsn DSN MySQL DSN (default: DBI:Mysql:mysql:localhost)
36
+ --check:username USERNAME MySQL Username (default: root)
37
+ --check:password PASSWORD MySQL Password (default: )
38
+ -l, --server:listen ADDR Server listen address (default: 0.0.0.0)
39
+ -p, --server:port PORT Server listen port (default: 3305)
40
+ -d, --server:daemonize Daemonize the process (default: false)
41
+ -P, --server:pid-file PID-FILE Pid-File to save the process id (default: false)
42
+ --log:level LEVEL Logging level (default: INFO)
43
+ --log:file FILE Write logs to FILE (default: STDERR)
44
+ --log:age DAYS Rotate logs after DAYS pass (default: 7)
45
+ --log:size SIZE Rotate logs after the grow past SIZE bytes (default: 10485760)
46
+
47
+ ## Examples
48
+
49
+ Start the server on port 1234 and check the status of the slave every 30 seconds:
50
+
51
+ mysql_health --check:slave --check:interval 30 --server:port 1234
52
+
53
+ Start the server on port 1234 and check the status of the master every 30 seconds:
54
+
55
+ mysql_health --check:master --check:interval 30 --server:port 1234
56
+
57
+ ## Contributing
58
+
59
+ 1. Fork it
60
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
61
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
62
+ 4. Push to the branch (`git push origin my-new-feature`)
63
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/mysql_health ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # mysql_health - a service for monitoring MySQL and exposing its health through an HTTP interface
4
+ # Copyright (C) 2012 Erik Osterman <e@osterman.com>
5
+ #
6
+ # This file is part of mysql_health.
7
+ #
8
+ # mysql_health is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # mysql_health is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with mysql_health. If not, see <http://www.gnu.org/licenses/>.
20
+ #
21
+
22
+ $:.unshift(File.expand_path('.')) # Ruby 1.9 doesn't have . in the load path...
23
+ $:.push(File.expand_path('lib/'))
24
+
25
+ require 'rubygems'
26
+ require 'mysql_health'
27
+
28
+ command_line = MysqlHealth::CommandLine.new
29
+ command_line.execute
30
+
@@ -0,0 +1,27 @@
1
+ #
2
+ # mysql_health - a service for monitoring MySQL and exposing its health through an HTTP interface
3
+ # Copyright (C) 2012 Erik Osterman <e@osterman.com>
4
+ #
5
+ # This file is part of mysql_health.
6
+ #
7
+ # mysql_health is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # mysql_health is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with mysql_health. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+ require 'mysql_health/version'
21
+ require 'mysql_health/health'
22
+ require 'mysql_health/server'
23
+ require 'mysql_health/command_line'
24
+
25
+ module MysqlHealth
26
+
27
+ end
@@ -0,0 +1,199 @@
1
+ #
2
+ # mysql_health - a service for monitoring MySQL and exposing its health through an HTTP interface
3
+ # Copyright (C) 2012 Erik Osterman <e@osterman.com>
4
+ #
5
+ # This file is part of mysql_health.
6
+ #
7
+ # mysql_health is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # mysql_health is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with mysql_health. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+ require 'logger'
21
+ require 'optparse'
22
+
23
+ module MysqlHealth
24
+ @@health = nil
25
+ @@log = nil
26
+
27
+ def self.health
28
+ @@health
29
+ end
30
+
31
+ def self.health=(health)
32
+ @@health = health
33
+ end
34
+
35
+ def self.log
36
+ @@log
37
+ end
38
+
39
+ def self.log=(log)
40
+ @@log = log
41
+ end
42
+
43
+ class ArgumentException < Exception; end
44
+ class CommandLine
45
+ attr_accessor :options
46
+ def initialize
47
+ @options = {}
48
+ @options[:server] = {}
49
+ @options[:check] = {}
50
+ @options[:log] = {}
51
+
52
+ begin
53
+ @optparse = OptionParser.new do |opts|
54
+ opts.banner = "Usage: #{$0} options"
55
+ #
56
+ # Health check
57
+ #
58
+ @options[:check][:master] = false
59
+ opts.on( '--check:master', 'Master health check') do
60
+ @options[:check][:master] = true
61
+ end
62
+
63
+ @options[:check][:slave] = false
64
+ opts.on( '--check:slave', 'Slave health check') do |host|
65
+ @options[:check][:slave] = true
66
+ end
67
+
68
+ @options[:check][:allow_overlapping] = false
69
+ opts.on( '--check:allow-overlapping', "Allow overlapping health checks (default: #{@options[:check][:allow_overlapping]})") do
70
+ @options[:check][:allow_overlapping] = true
71
+ end
72
+
73
+ @options[:check][:interval] = '10s'
74
+ opts.on( '--check:interval INTERVAL', "Check health every INTERVAL (default: #{@options[:check][:interval]})") do |interval|
75
+ @options[:check][:interval] = interval.to_s
76
+ end
77
+
78
+ @options[:check][:delay] = '0s'
79
+ opts.on( '--check:delay DELAY', "Delay health checks for INTERVAL (default: #{@options[:check][:delay]})") do |delay|
80
+ @options[:check][:delay] = interval.to_s
81
+ end
82
+
83
+ @options[:check][:dsn] ||= "DBI:Mysql:mysql:localhost"
84
+ opts.on( '--check:dsn DSN', "MySQL DSN (default: #{@options[:check][:dsn]})") do |dsn|
85
+ @options[:check][:dsn] = dsn.to_s
86
+ end
87
+
88
+ @options[:check][:username] ||= "root"
89
+ opts.on( '--check:username USERNAME', "MySQL Username (default: #{@options[:check][:username]})") do |username|
90
+ @options[:check][:username] = username.to_s
91
+ end
92
+
93
+ @options[:check][:password] ||= ""
94
+ opts.on( '--check:password PASSWORD', "MySQL Password (default: #{@options[:check][:password]})") do |password|
95
+ @options[:check][:password] = interval.to_s
96
+ end
97
+
98
+ # Server
99
+ @options[:server][:listen] = '0.0.0.0'
100
+ opts.on( '-l', '--server:listen ADDR', "Server listen address (default: #{@options[:server][:listen]})") do |addr|
101
+ @options[:server][:addr] = host.to_s
102
+ end
103
+
104
+ @options[:server][:port] = 3305
105
+ opts.on( '-p', '--server:port PORT', "Server listen port (default: #{@options[:server][:port]})") do |port|
106
+ @options[:server][:port] = port.to_i
107
+ end
108
+
109
+ @options[:server][:daemonize] = false
110
+ opts.on( '-d', '--server:daemonize', "Daemonize the process (default: #{@options[:server][:daemonize]})") do
111
+ @options[:server][:daemonize] = true
112
+ end
113
+
114
+ @options[:server][:pid_file] = false
115
+ opts.on('-P', '--server:pid-file PID-FILE', "Pid-File to save the process id (default: #{@options[:server][:pid_file]})") do |pid_file|
116
+ @options[:server][:pid_file] = pid_file
117
+ end
118
+
119
+
120
+ #
121
+ # Logging
122
+ #
123
+
124
+ @options[:log][:level] = Logger::INFO
125
+ opts.on( '--log:level LEVEL', 'Logging level (default: INFO)' ) do|level|
126
+ @options[:log][:level] = Logger.const_get level.upcase
127
+ end
128
+
129
+ @options[:log][:file] = STDERR
130
+ opts.on( '--log:file FILE', 'Write logs to FILE (default: STDERR)' ) do|file|
131
+ @options[:log][:file] = File.open(file, File::WRONLY | File::APPEND | File::CREAT)
132
+ end
133
+
134
+ @options[:log][:age] = 7
135
+ opts.on( '--log:age DAYS', "Rotate logs after DAYS pass (default: #{@options[:log][:age]})" ) do|days|
136
+ @options[:log][:age] = days.to_i
137
+ end
138
+
139
+ @options[:log][:size] = 1024*1024*10
140
+ opts.on( '--log:size SIZE', "Rotate logs after the grow past SIZE bytes (default: #{@options[:log][:size]})" ) do |size|
141
+ @options[:log][:size] = size.to_i
142
+ end
143
+ end
144
+ @optparse.parse!
145
+
146
+ raise ArgumentException.new("No action specified") if @options[:check][:master] == false && @options[:check][:slave] == false
147
+ @log = Logger.new(@options[:log][:file], @options[:log][:age], @options[:log][:size])
148
+ @log.level = @options[:log][:level]
149
+
150
+ daemonize if @options[:server][:daemonize]
151
+ write_pid_file if @options[:server][:pid_file]
152
+
153
+ MysqlHealth.log = @log
154
+ MysqlHealth.health = Health.new(@options[:check])
155
+
156
+ rescue ArgumentException => e
157
+ puts e.message
158
+ puts @optparse
159
+ exit 1
160
+ end
161
+ end
162
+
163
+ def daemonize
164
+ # Become a daemon
165
+ if RUBY_VERSION < "1.9"
166
+ exit if fork
167
+ Process.setsid
168
+ exit if fork
169
+ Dir.chdir "/"
170
+ STDIN.reopen "/dev/null"
171
+ STDOUT.reopen "/dev/null", "a"
172
+ STDERR.reopen "/dev/null", "a"
173
+ else
174
+ Process.daemon
175
+ end
176
+ end
177
+
178
+ def write_pid_file
179
+ @log.debug("writing pid file #{@options[:server][:pid_file]}")
180
+ File.open(@options[:server][:pid_file], 'w') do |f|
181
+ f.write(Process.pid)
182
+ end
183
+ end
184
+
185
+ def execute
186
+ begin
187
+ ::EM.run do
188
+ ::EM.start_server @options[:server][:listen], @options[:server][:port], Server
189
+ end
190
+ rescue ArgumentException => e
191
+ @log.fatal(e.message)
192
+ rescue Interrupt => e
193
+ @log.info("exiting...")
194
+ rescue Exception => e
195
+ @log.fatal(e.message + e.backtrace.join("\n"))
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,181 @@
1
+ #
2
+ # mysql_health - a service for monitoring MySQL and exposing its health through an HTTP interface
3
+ # Copyright (C) 2012 Erik Osterman <e@osterman.com>
4
+ #
5
+ # This file is part of mysql_health.
6
+ #
7
+ # mysql_health is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # mysql_health is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with mysql_health. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+ require 'dbi'
21
+ require 'mysql'
22
+ require 'json'
23
+ require 'rufus-scheduler'
24
+
25
+ module MysqlHealth
26
+ class Health
27
+ @master_status = nil
28
+ @slave_status = nil
29
+ @scheduler = nil
30
+ @options = nil
31
+
32
+ def initialize(options = {})
33
+ @options = options
34
+
35
+ @mutex = Mutex.new
36
+ @scheduler = Rufus::Scheduler.start_new
37
+ def @scheduler.handle_exception(job, e)
38
+ MysqlHealth.log.error "job #{job.job_id} caught #{e.class} exception '#{e}' #{e.backtrace.join("\n")}"
39
+ end
40
+
41
+ if options[:master]
42
+ master_status = {}
43
+ master_status[:status] = 503
44
+ master_status[:content] = "Health of master not yet determined\n"
45
+ self.master_status=(master_status)
46
+ @scheduler.every options[:interval], :allow_overlapping => options[:allow_overlapping], :first_in => options[:delay] do
47
+ check_master
48
+ end
49
+ else
50
+ master_status = {}
51
+ master_status[:status] = '501 Not Enabled'
52
+ master_status[:content] = "Health of master not enabled\n"
53
+ self.master_status=(master_status)
54
+ end
55
+
56
+ if options[:slave]
57
+ slave_status = {}
58
+ slave_status[:status] = '501 Not Enabled'
59
+ slave_status[:content] = "Health of slave not yet determined\n"
60
+ self.slave_status=(slave_status)
61
+ @scheduler.every options[:interval], :allow_overlapping => options[:allow_overlapping], :first_in => options[:delay] do
62
+ check_slave
63
+ end
64
+ else
65
+ slave_status = {}
66
+ slave_status[:status] = '501 Not Enabled'
67
+ slave_status[:content] = "Health of slave not enabled\n"
68
+ self.slave_status=(slave_status)
69
+ end
70
+ end
71
+
72
+ def master_status=(response)
73
+ @mutex.synchronize do
74
+ MysqlHealth.log.info("master status: #{response[:status]}")
75
+ @master_status = response
76
+ end
77
+ end
78
+
79
+ def master_status
80
+ master_status = nil
81
+ @mutex.synchronize do
82
+ master_status = @master_status
83
+ end
84
+ return master_status
85
+ end
86
+
87
+ def slave_status=(response)
88
+ @mutex.synchronize do
89
+ MysqlHealth.log.info("slave status: #{response[:status]}")
90
+ @slave_status = response
91
+ end
92
+ end
93
+
94
+ def slave_status
95
+ slave_status = nil
96
+ @mutex.synchronize do
97
+ slave_status = @slave_status
98
+ end
99
+ return slave_status
100
+ end
101
+
102
+ def read_only?(dbh)
103
+ variables = dbh.select_all("SHOW VARIABLES WHERE Variable_name = 'read_only' AND Value = 'ON'")
104
+ return (variables.length == 1)
105
+ end
106
+
107
+ def check_master
108
+ MysqlHealth.log.debug("check_master")
109
+
110
+ # connect to the MySQL server
111
+ dbh = DBI.connect(@options[:dsn], @options[:username], @options[:password])
112
+
113
+ response = {}
114
+ response[:content_type] = 'text/plain'
115
+
116
+ status = {}
117
+ dbh.select_all('SHOW STATUS') do |row|
118
+ status[row[0].downcase.to_sym] = row[1]
119
+ end
120
+ mysqladmin_status = "Uptime: %s Threads: %s Questions: %s Slow queries: %s Opens: %s Flush tables: %s Open tables: %s Queries per second avg: %.3f\n" %
121
+ [ status[:uptime], status[:threads_running], status[:questions], status[:slow_queries], status[:opened_tables], status[:flush_commands], status[:open_tables], status[:queries].to_i/status[:uptime].to_i]
122
+ if status.length > 0
123
+ if read_only?(dbh)
124
+ response[:status] = '503 Service Read Only'
125
+ response[:content] = mysqladmin_status
126
+ else
127
+ response[:status] = '200 OK'
128
+ response[:content] = mysqladmin_status
129
+ end
130
+ else
131
+ response[:status] = '503 Service Unavailable'
132
+ response[:content] = mysqladmin_status
133
+ end
134
+ self.master_status=(response)
135
+ end
136
+
137
+ def check_slave
138
+ MysqlHealth.log.debug("check_slave")
139
+
140
+ # connect to the MySQL server
141
+ dbh = DBI.connect(@options[:dsn], @options[:username], @options[:password])
142
+
143
+ response = {}
144
+ response[:content_type] = 'text/plain'
145
+
146
+ show_slave_status = []
147
+ status = {}
148
+ dbh.select_all('SHOW SLAVE STATUS') do |row|
149
+ status[row[0].downcase.to_sym] = row[1]
150
+ show_slave_status << "#{row[0]}: #{row[1]}"
151
+ end
152
+
153
+ if status.length > 0
154
+ seconds_behind_master = status[:seconds_behind_master]
155
+
156
+ # We return a "203 Non-Authoritative Information" when replication is shot. We don't want to reduce site performance, but still want to track that something is awry.
157
+ if seconds_behind_master.eql?('NULL')
158
+ response[:status] = '203 Slave Stopped'
159
+ response[:content] = status.to_json
160
+ response[:content_type] = 'application/json'
161
+ elsif seconds_behind_master.to_i > 60*30
162
+ response[:status] = '203 Slave Behind'
163
+ response[:content] = status.to_json
164
+ response[:content_type] = 'application/json'
165
+ elsif read_only?(dbh)
166
+ response[:status] = '200 OK ' + seconds_behind_master + ' Seconds Behind Master'
167
+ response[:content] = status.to_json
168
+ response[:content_type] = 'application/json'
169
+ else
170
+ response[:status] = '503 Service Unavailable'
171
+ response[:content] = status.to_json
172
+ response[:content_type] = 'application/json'
173
+ end
174
+ else
175
+ response[:status] = '503 Slave Not Configured'
176
+ response[:content] = show_slave_status.join("\n")
177
+ end
178
+ self.slave_status=(response)
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,76 @@
1
+ #
2
+ # mysql_health - a service for monitoring MySQL and exposing its health through an HTTP interface
3
+ # Copyright (C) 2012 Erik Osterman <e@osterman.com>
4
+ #
5
+ # This file is part of mysql_health.
6
+ #
7
+ # mysql_health is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # mysql_health is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with mysql_health. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+ require 'json'
21
+ require 'eventmachine'
22
+ require 'eventmachine_httpserver'
23
+ require 'evma_httpserver/response'
24
+ require 'forwardable'
25
+
26
+ module MysqlHealth
27
+ class Server < ::EM::Connection
28
+ include ::EM::HttpServer
29
+
30
+ def post_init
31
+ super
32
+ no_environment_strings
33
+ end
34
+
35
+ def http_response(data)
36
+ MysqlHealth.log.debug("http_response")
37
+ response = EventMachine::DelegatedHttpResponse.new(self)
38
+ if data.instance_of?(OpenStruct)
39
+ puts data.table.inspect
40
+ puts data.inspect
41
+ end
42
+
43
+ if data.nil?
44
+ response.status = '500 Server Error'
45
+ response.content = "Empty call to http_response\n"
46
+ else
47
+ data.each_pair do |k,v|
48
+ MysqlHealth.log.debug("#{k}=#{v}")
49
+ if k == :content_type
50
+ response.send(k, v)
51
+ else
52
+ response.send("#{k}=".to_sym, v)
53
+ end
54
+ end
55
+ end
56
+ response
57
+ end
58
+
59
+ def process_http_request
60
+ response = nil
61
+ begin
62
+ case @http_path_info
63
+ when '/master_status'
64
+ response = http_response(MysqlHealth.health.master_status)
65
+ when '/slave_status'
66
+ response = http_response(MysqlHealth.health.slave_status)
67
+ else
68
+ response = http_response({:status => '501 Not Implemented'})
69
+ end
70
+ rescue Exception => e
71
+ response = http_response({:status => '500 Server Error', :content => e.message + "\n" + e.backtrace.join("\n")})
72
+ end
73
+ response.send_response
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,22 @@
1
+ #
2
+ # mysql_health - a service for monitoring MySQL and exposing its health through an HTTP interface
3
+ # Copyright (C) 2012 Erik Osterman <e@osterman.com>
4
+ #
5
+ # This file is part of mysql_health.
6
+ #
7
+ # mysql_health is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # mysql_health is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with mysql_health. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+ module MysqlHealth
21
+ VERSION = "0.5.0"
22
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/mysql_health/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Erik Osterman"]
6
+ gem.email = ["e@osterman.com"]
7
+ gem.summary = %q{A service for monitoring MySQL and exposing its health through an HTTP interface.}
8
+ gem.description = %q{A service for monitoring MySQL and exposing its health through an HTTP interface for use with TCP load balancers (like haproxy) that support out-of-band health checks using HTTP.}
9
+ gem.homepage = "https://github.com/osterman/mysql_health"
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "mysql_health"
14
+ gem.license = 'GPLv3'
15
+ gem.require_paths = ["lib"]
16
+ gem.version = MysqlHealth::VERSION
17
+ gem.add_runtime_dependency('rufus-scheduler', '>= 2.0.17')
18
+ gem.add_runtime_dependency('eventmachine', '>= 1.0.0.beta.4')
19
+ gem.add_runtime_dependency('eventmachine_httpserver', '>= 0.2.1')
20
+ gem.add_runtime_dependency('json', '>= 1.5.3')
21
+ gem.add_runtime_dependency('dbi', '>= 0.4.5')
22
+ gem.add_runtime_dependency('mysql', '>= 2.8.1')
23
+ end
@@ -0,0 +1,117 @@
1
+ #!/bin/bash
2
+ #
3
+ # mysql_health - a service for monitoring MySQL and exposing its health through an HTTP interface.
4
+ #
5
+ # chkconfig: 345 99 99
6
+ # description: mysql_health
7
+ # processname: mysql_health
8
+
9
+
10
+ # Source function library.
11
+ . /etc/rc.d/init.d/functions
12
+
13
+ MYSQL_HEALTH_NICE="-n 19"
14
+ MYSQL_HEALTH_BIN="/usr/bin/mysql_health"
15
+ MYSQL_HEALTH_LOG="/var/log/mysql_health.log"
16
+ MYSQL_HEALTH_PID_FILE="/var/run/mysql_health.pid"
17
+ MYSQL_HEALTH_HOME="/tmp"
18
+ MYSQL_HEALTH_ARGS=""
19
+
20
+ [ -f /etc/sysconfig/mysql_health ] && . /etc/sysconfig/mysql_health
21
+
22
+ [ -x /usr/bin/mysql_health ] || exit 0
23
+
24
+
25
+ start_mysql_health()
26
+ {
27
+ cd $MYSQL_HEALTH_HOME
28
+ echo -n $"Starting mysql_health"
29
+ nice $MYSQL_HEALTH_NICE $MYSQL_HEALTH_BIN --server:pid-file="$MYSQL_HEALTH_PID_FILE" --log:file "$MYSQL_HEALTH_LOG" --server:daemonize $MYSQL_HEALTH_ARGS
30
+ result=$?
31
+ if [ "$result" -eq 0 ]; then
32
+ sleep 0.5
33
+ status -p "$MYSQL_HEALTH_PID_FILE" >/dev/null
34
+ result=$?
35
+ if [ "$result" -eq 0 ]; then
36
+ success
37
+ else
38
+ failure
39
+ fi
40
+ else
41
+ failure
42
+ fi
43
+ echo
44
+ return $result
45
+ }
46
+
47
+ stop_mysql_health()
48
+ {
49
+ echo -n $"Stopping mysql_health"
50
+
51
+ if [ -f "$MYSQL_HEALTH_PID_FILE" ]; then
52
+ kill $(cat "$MYSQL_HEALTH_PID_FILE") 2>/dev/null
53
+ result=$?
54
+ else
55
+ result=1
56
+ fi
57
+
58
+ if [ "$result" -eq 0 ]; then
59
+ result=1
60
+ for i in {1..60}; do
61
+ if [ -f "$MYSQL_HEALTH_PID_FILE" ]; then
62
+ echo -n "."
63
+ sleep 1
64
+ else
65
+ result=0
66
+ break
67
+ fi
68
+ done
69
+ fi
70
+
71
+ if [ "$result" -eq 0 ];then
72
+ success
73
+ else
74
+ failure
75
+ fi
76
+ echo
77
+ return $result
78
+ }
79
+
80
+
81
+ status_mysql_health() {
82
+ echo -n "mysql_health"
83
+ if [ -f "$MYSQL_HEALTH_PID_FILE" ]; then
84
+ status -p "$MYSQL_HEALTH_PID_FILE"
85
+ else
86
+ echo " is not running"
87
+ fi
88
+ }
89
+
90
+ status=0
91
+ # See how we were called.
92
+ case "$1" in
93
+ start)
94
+ start_mysql_health
95
+ status=$?
96
+ ;;
97
+ stop)
98
+ stop_mysql_health
99
+ status=$?
100
+ ;;
101
+ restart|reload)
102
+ stop_mysql_health
103
+ sleep 1
104
+ start_mysql_health
105
+ status=$?
106
+ ;;
107
+ status)
108
+ status_mysql_health
109
+ status=$?
110
+ ;;
111
+ *)
112
+ echo $"Usage: $0 {start|stop|restart|reload}"
113
+ status=1
114
+ esac
115
+
116
+ exit $status
117
+ exit 0
@@ -0,0 +1,73 @@
1
+ %define ruby_sitelib %(ruby -rrbconfig -e "puts Config::CONFIG['sitelibdir']")
2
+ %define gemdir %(ruby -rubygems -e 'puts Gem::dir' 2>/dev/null)
3
+ %define gemname mysql_health
4
+ %define gemversion 0.3.0
5
+ %define geminstdir %{gemdir}/gems/%{gemname}-%{gemversion}
6
+ %define gemfile %{gemname}-%{gemversion}.gem
7
+ %define gemsummary %(ruby -rrubygems -e 'puts YAML.load(`gem specification %{gemfile}`).summary')
8
+ %define gemdesc %(ruby -rrubygems -e 'puts YAML.load(`gem specification %{gemfile}`).description')
9
+ %define gemhomepage %(ruby -rrubygems -e 'puts YAML.load(`gem specification %{gemfile}`).homepage')
10
+ %define gemlicense %(ruby -rrubygems -e 'puts YAML.load(`gem specification %{gemfile}`).license || "Unknown"')
11
+ %define gemdeps %(ruby -rrubygems -e 'puts YAML.load(`gem specification %{gemfile}`.chomp).dependencies.map { |d| "rubygem(%s) %s" % [d.name, d.requirement] }.sort.join(", ")')
12
+ %define gemrelease %(date +"%%Y%%m%%d%%H%%M%%S")
13
+
14
+ Summary: %{gemsummary}
15
+ # The version is repeated in the name so as to allow multiple versions of the gem to be installed on the system.
16
+ Name: rubygem-%{gemname}-%{gemversion}
17
+ Version: %{gemversion}
18
+ Release: %{gemrelease}%{?dist}
19
+ Group: Development/Languages
20
+ License: %{gemlicense}
21
+ URL: %{gemhomepage}
22
+ Source0: http://rubygems.org/gems/%{gemname}-%{version}.gem
23
+ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
24
+ Requires: rubygems
25
+
26
+ Requires: %{gemdeps}
27
+
28
+ BuildRequires: rubygems
29
+ BuildRequires: rubygem(bundler)
30
+ BuildArch: noarch
31
+ Provides: rubygem(%{gemname}) = %{version}
32
+
33
+ %description
34
+ %{gemdesc}
35
+
36
+ %prep
37
+
38
+ %build
39
+
40
+ %install
41
+ rm -rf %{buildroot}
42
+ install --directory 0755 %{buildroot}%{gemdir}
43
+ gem install --local --install-dir %{buildroot}%{gemdir} \
44
+ --force --rdoc %{SOURCE0}
45
+ install --directory 0755 %{buildroot}/%{_bindir}
46
+ mv %{buildroot}%{gemdir}/bin/%{gemname} %{buildroot}/%{_bindir}
47
+ find %{buildroot}%{geminstdir}/bin -type f | xargs chmod a+x
48
+
49
+ install --directory --mode 0755 %{buildroot}%{_sysconfdir}/%{gemname}
50
+ install --directory --mode 0755 %{buildroot}%{_initrddir}
51
+ install --mode 755 %{buildroot}%{geminstdir}/redhat/%{gemname}.initrc %{buildroot}%{_initrddir}/%{gemname}
52
+
53
+ install --directory --mode 0755 %{buildroot}%{_sysconfdir}/sysconfig
54
+ cat<<__EOF__>%{buildroot}/%{_sysconfdir}/sysconfig/%{gemname}
55
+ __EOF__
56
+
57
+ %clean
58
+ rm -rf %{buildroot}
59
+
60
+ %files
61
+ %defattr(-, root, root, -)
62
+ %{_bindir}/%{gemname}
63
+ %{gemdir}/gems/%{gemname}-%{version}/
64
+ %doc %{gemdir}/doc/%{gemname}-%{version}
65
+ %{gemdir}/cache/%{gemname}-%{version}.gem
66
+ %{gemdir}/specifications/%{gemname}-%{version}.gemspec
67
+ %{_initrddir}/%{gemname}
68
+ %{_sysconfdir}/%{gemname}/
69
+ %{_sysconfdir}/sysconfig/%{gemname}
70
+
71
+ %changelog
72
+ * Sun Jul 29 2012 Erik Osterman <e@osterman.com>
73
+ - Initial package
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mysql_health
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
10
+ platform: ruby
11
+ authors:
12
+ - Erik Osterman
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-07-29 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rufus-scheduler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 0
30
+ - 17
31
+ version: 2.0.17
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: eventmachine
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 0
44
+ - 0
45
+ - beta
46
+ - 4
47
+ version: 1.0.0.beta.4
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: eventmachine_httpserver
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ - 2
60
+ - 1
61
+ version: 0.2.1
62
+ type: :runtime
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: json
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 1
73
+ - 5
74
+ - 3
75
+ version: 1.5.3
76
+ type: :runtime
77
+ version_requirements: *id004
78
+ - !ruby/object:Gem::Dependency
79
+ name: dbi
80
+ prerelease: false
81
+ requirement: &id005 !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 0
87
+ - 4
88
+ - 5
89
+ version: 0.4.5
90
+ type: :runtime
91
+ version_requirements: *id005
92
+ - !ruby/object:Gem::Dependency
93
+ name: mysql
94
+ prerelease: false
95
+ requirement: &id006 !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 2
101
+ - 8
102
+ - 1
103
+ version: 2.8.1
104
+ type: :runtime
105
+ version_requirements: *id006
106
+ description: A service for monitoring MySQL and exposing its health through an HTTP interface for use with TCP load balancers (like haproxy) that support out-of-band health checks using HTTP.
107
+ email:
108
+ - e@osterman.com
109
+ executables:
110
+ - mysql_health
111
+ extensions: []
112
+
113
+ extra_rdoc_files: []
114
+
115
+ files:
116
+ - .gitignore
117
+ - Gemfile
118
+ - LICENSE
119
+ - README.md
120
+ - Rakefile
121
+ - bin/mysql_health
122
+ - lib/mysql_health.rb
123
+ - lib/mysql_health/command_line.rb
124
+ - lib/mysql_health/health.rb
125
+ - lib/mysql_health/server.rb
126
+ - lib/mysql_health/version.rb
127
+ - mysql_health.gemspec
128
+ - redhat/mysql_health.initrc
129
+ - redhat/rubygem-mysql_health.spec
130
+ has_rdoc: true
131
+ homepage: https://github.com/osterman/mysql_health
132
+ licenses:
133
+ - GPLv3
134
+ post_install_message:
135
+ rdoc_options: []
136
+
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ segments:
144
+ - 0
145
+ version: "0"
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ segments:
151
+ - 0
152
+ version: "0"
153
+ requirements: []
154
+
155
+ rubyforge_project:
156
+ rubygems_version: 1.3.6
157
+ signing_key:
158
+ specification_version: 3
159
+ summary: A service for monitoring MySQL and exposing its health through an HTTP interface.
160
+ test_files: []
161
+