mysql-pause 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +54 -0
- data/bin/mpctl +43 -0
- data/bin/mysql-pause +46 -0
- data/lib/mysql-pause/abstract_mysql_adapter.rb +26 -0
- data/lib/mysql-pause/backend.rb +13 -0
- data/lib/mysql-pause/constants.rb +2 -0
- data/lib/mysql-pause/error.rb +45 -0
- data/lib/mysql-pause/proxy.rb +45 -0
- data/lib/mysql-pause/server.rb +45 -0
- metadata +87 -0
data/README
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
= mysql-pause
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
mysql-pause is a proxy server to pause a query to MySQL.
|
6
|
+
|
7
|
+
== Source Code
|
8
|
+
|
9
|
+
https://bitbucket.org/winebarrel/mysql-pause
|
10
|
+
|
11
|
+
== Dependency
|
12
|
+
|
13
|
+
* EventMachine
|
14
|
+
* RExec
|
15
|
+
|
16
|
+
== Install
|
17
|
+
|
18
|
+
gem install mysql-pause
|
19
|
+
|
20
|
+
== Example
|
21
|
+
=== Start server
|
22
|
+
|
23
|
+
shell> mysql-pause start
|
24
|
+
Starting daemon...
|
25
|
+
Waiting for daemon to start...
|
26
|
+
Daemon status: running pid=56921
|
27
|
+
|
28
|
+
shell> mysql -h 127.0.0.1 -P 13306 -u scott -ptiger
|
29
|
+
mysql> select 1;
|
30
|
+
+---+
|
31
|
+
| 1 |
|
32
|
+
+---+
|
33
|
+
| 1 |
|
34
|
+
+---+
|
35
|
+
1 row in set (0.00 sec)
|
36
|
+
|
37
|
+
=== Pause
|
38
|
+
|
39
|
+
shell> mpctl pause
|
40
|
+
|
41
|
+
mysql> select 1;
|
42
|
+
(...no response...)
|
43
|
+
|
44
|
+
=== Resume
|
45
|
+
|
46
|
+
shell> mpctl resume
|
47
|
+
|
48
|
+
(...resume response...)
|
49
|
+
+---+
|
50
|
+
| 1 |
|
51
|
+
+---+
|
52
|
+
| 1 |
|
53
|
+
+---+
|
54
|
+
1 row in set (18.01 sec)
|
data/bin/mpctl
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
+
|
4
|
+
require 'drb/drb'
|
5
|
+
require 'optparse'
|
6
|
+
require 'mysql-pause/constants'
|
7
|
+
|
8
|
+
COMMANDS = {
|
9
|
+
'pause' => true,
|
10
|
+
'resume' => false,
|
11
|
+
}
|
12
|
+
|
13
|
+
options = nil
|
14
|
+
|
15
|
+
ARGV.options do |parser|
|
16
|
+
cmds = COMMANDS.keys
|
17
|
+
|
18
|
+
options = {
|
19
|
+
:socket => "/var/tmp/#{APP_NAME}.sock",
|
20
|
+
}
|
21
|
+
|
22
|
+
parser.on('-i', '--inerval=N' ) {|v| options[:interval] = v.to_i }
|
23
|
+
parser.on('' , '--socket=SOCK_FILE') {|v| options[:socket] = v }
|
24
|
+
|
25
|
+
help_and_exit = lambda do |v|
|
26
|
+
$stderr.puts parser.help.sub(APP_NAME, "#{APP_NAME} {#{cmds.join('|')}}")
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
|
30
|
+
parser.on('-h', '--help', &help_and_exit)
|
31
|
+
|
32
|
+
parser.parse!
|
33
|
+
|
34
|
+
unless (ARGV.length == 0 and options.length > 1) or (ARGV.length == 1 and cmds.include?(ARGV[0]))
|
35
|
+
help_and_exit.call(true)
|
36
|
+
end
|
37
|
+
end # parse options
|
38
|
+
|
39
|
+
cmd = ARGV[0]
|
40
|
+
server_options = DRbObject.new_with_uri("drbunix:#{options[:socket]}")
|
41
|
+
|
42
|
+
server_options[:pause] = COMMANDS[cmd] if cmd
|
43
|
+
server_options[:interval] = options[:interval] if options[:interval]
|
data/bin/mysql-pause
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'mysql-pause/constants'
|
6
|
+
require 'mysql-pause/server'
|
7
|
+
|
8
|
+
options = nil
|
9
|
+
|
10
|
+
ARGV.options do |parser|
|
11
|
+
daemon_commands = %w(start stop restart status)
|
12
|
+
|
13
|
+
options = {
|
14
|
+
:addr => '0.0.0.0',
|
15
|
+
:port => 13306,
|
16
|
+
:backend_addr => '127.0.0.1',
|
17
|
+
:backend_port => 3306,
|
18
|
+
:interval => 3,
|
19
|
+
:working_dir => '/var',
|
20
|
+
:socket => "/var/tmp/#{APP_NAME}.sock",
|
21
|
+
}
|
22
|
+
|
23
|
+
parser.on('-a', '--addr=ADDR' ) {|v| options[:addr] = v }
|
24
|
+
parser.on('-p', '--port=PORT' ) {|v| options[:port] = v.to_i }
|
25
|
+
parser.on('-A', '--backend-addr=ADDR') {|v| options[:backend_addr] = v }
|
26
|
+
parser.on('-P', '--backend-port=PORT') {|v| options[:backend_port] = v.to_i }
|
27
|
+
parser.on('-i', '--inerval=N' ) {|v| options[:interval] = v.to_i }
|
28
|
+
parser.on('' , '--working-dir=DIR' ) {|v| options[:working_dir] = v }
|
29
|
+
parser.on('' , '--socket=SOCK_FILE' ) {|v| options[:socket] = v }
|
30
|
+
|
31
|
+
help_and_exit = lambda do |v|
|
32
|
+
$stderr.puts parser.help.sub(APP_NAME, "#{APP_NAME} {#{daemon_commands.join('|')}}")
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
|
36
|
+
parser.on('-h', '--help', &help_and_exit)
|
37
|
+
|
38
|
+
parser.parse!
|
39
|
+
|
40
|
+
unless ARGV.length == 1 and daemon_commands.include?(ARGV[0])
|
41
|
+
help_and_exit.call(true)
|
42
|
+
end
|
43
|
+
end # parse options
|
44
|
+
|
45
|
+
MysqlPause::Server.options = options
|
46
|
+
MysqlPause::Server.daemonize
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
3
|
+
require 'mysql-pause/error'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
class AbstractMysqlAdapter
|
8
|
+
|
9
|
+
alias mysql_pause_execute_orig execute
|
10
|
+
|
11
|
+
def execute(sql, name = nil)
|
12
|
+
begin
|
13
|
+
mysql_pause_execute_orig(sql, name)
|
14
|
+
rescue => e
|
15
|
+
raise(e) unless MysqlPause::Error.mysql_pause_error?(e)
|
16
|
+
|
17
|
+
message = "#{e.class.name}: #{e.message}: #{sql}"
|
18
|
+
@logger.warn message if @logger
|
19
|
+
reconnect!
|
20
|
+
retry
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end # AbstractMysqlAdapter
|
25
|
+
end # ConnectionAdapters
|
26
|
+
end # ActiveRecord
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module MysqlPause
|
2
|
+
class Error
|
3
|
+
|
4
|
+
ERROR_HEADER = '%MYSQL_PAUSE%'
|
5
|
+
ERROR_SEPARATOR = ';;'
|
6
|
+
ERROR_LIST = {}
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def create_error_message(error_code, sequence_id)
|
10
|
+
# fetch error info
|
11
|
+
mysql_error_code, sql_state, message = ERROR_LIST[error_code]
|
12
|
+
|
13
|
+
# create mysql error message
|
14
|
+
message = [ERROR_HEADER, error_code, message].join(ERROR_SEPARATOR)
|
15
|
+
|
16
|
+
# create header
|
17
|
+
mysql_error_code = convert_to_chars(mysql_error_code, 2)
|
18
|
+
payload = ["\xFF", mysql_error_code, '#', sql_state, message].join
|
19
|
+
payload_length = convert_to_chars(payload.length, 3)
|
20
|
+
sequence_id = convert_to_chars(sequence_id, 1)
|
21
|
+
|
22
|
+
[payload_length, sequence_id, payload].join
|
23
|
+
end
|
24
|
+
|
25
|
+
def mysql_pause_error?(e)
|
26
|
+
/\A#{Regexp.escape(MYSQL_PAUSE)}/ =~ e.message
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def convert_to_chars(number, length)
|
32
|
+
(0...length).map {|i| (number >> (8 * i)) & 0xFF }.pack("C*")
|
33
|
+
end
|
34
|
+
|
35
|
+
def define_error(name, code, values)
|
36
|
+
const_set(name, code)
|
37
|
+
ERROR_LIST[code] = values
|
38
|
+
end
|
39
|
+
end # self
|
40
|
+
|
41
|
+
# error list
|
42
|
+
define_error :ABORTED_BACKEND_CONNECTION, 1000, [2013, '08S01', 'Aborted backend connection']
|
43
|
+
|
44
|
+
end # Errot
|
45
|
+
end # MysqlPause
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'mysql-pause/backend'
|
3
|
+
require 'mysql-pause/error'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module MysqlPause
|
7
|
+
class Proxy < EM::Connection
|
8
|
+
|
9
|
+
def initialize(be_host, be_port, options)
|
10
|
+
@backend = EM.connect(be_host, be_port, MysqlPause::Backend, self)
|
11
|
+
@options = options
|
12
|
+
@logger = Logger.new($stdout)
|
13
|
+
end
|
14
|
+
|
15
|
+
def receive_data(data)
|
16
|
+
EM.defer {
|
17
|
+
if @options[:pause]
|
18
|
+
@logger.info("pause: #{data.inspect}")
|
19
|
+
sleep(@options[:interval]) while @options[:pause]
|
20
|
+
@logger.info("resume: #{data.inspect}")
|
21
|
+
end
|
22
|
+
|
23
|
+
if @backend.error?
|
24
|
+
@logger.info("backend error: #{data.inspect}")
|
25
|
+
payload_length, sequence_id = parse_mysql_packet(data)
|
26
|
+
error_message = MysqlPause::Error.create_error_message(MysqlPause::Error::ABORTED_BACKEND_CONNECTION, sequence_id + 1)
|
27
|
+
send_data(error_message)
|
28
|
+
close_connection_after_writing
|
29
|
+
else
|
30
|
+
@backend.send_data(data)
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def parse_mysql_packet(data)
|
38
|
+
header = data.slice(0, 4).unpack("C4")
|
39
|
+
payload_length = (0..2).map {|i| header[i] << (8 * i) }.inject(0) {|r, i| r + i }
|
40
|
+
sequence_id = header[3]
|
41
|
+
[payload_length, sequence_id]
|
42
|
+
end
|
43
|
+
|
44
|
+
end # Proxy
|
45
|
+
end # MysqlPause
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'rexec'
|
5
|
+
require 'rexec/daemon'
|
6
|
+
|
7
|
+
require 'mysql-pause/proxy'
|
8
|
+
|
9
|
+
module RExec; module Daemon; class Base
|
10
|
+
def self.daemon_name; APP_NAME; end
|
11
|
+
end; end; end
|
12
|
+
|
13
|
+
module MysqlPause
|
14
|
+
class Server < RExec::Daemon::Base
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def options=(options)
|
18
|
+
@@options = options
|
19
|
+
@@base_directory = options[:working_dir]
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
@@control_options = {
|
24
|
+
:pause => false,
|
25
|
+
:interval => @@options[:interval],
|
26
|
+
}
|
27
|
+
|
28
|
+
# start DRb
|
29
|
+
FileUtils.rm_f(@@options[:socket])
|
30
|
+
DRb.start_service("drbunix:#{@@options[:socket]}", @@control_options)
|
31
|
+
File.chmod(0700, @@options[:socket])
|
32
|
+
at_exit { FileUtils.rm_f(@@options[:socket]) }
|
33
|
+
|
34
|
+
EM.epoll
|
35
|
+
|
36
|
+
EM.run {
|
37
|
+
EM.start_server(
|
38
|
+
@@options[:addr], @@options[:port], MysqlPause::Proxy,
|
39
|
+
@@options[:backend_addr], @@options[:backend_port], @@control_options)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end # self
|
43
|
+
|
44
|
+
end # Server
|
45
|
+
end # MysqlPause
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mysql-pause
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- winebarrel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.3
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rexec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.5.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.5.1
|
46
|
+
description:
|
47
|
+
email: sgwr_dts@yahoo.co.jp
|
48
|
+
executables:
|
49
|
+
- mysql-pause
|
50
|
+
- mpctl
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- README
|
55
|
+
- bin/mpctl
|
56
|
+
- bin/mysql-pause
|
57
|
+
- lib/mysql-pause/abstract_mysql_adapter.rb
|
58
|
+
- lib/mysql-pause/backend.rb
|
59
|
+
- lib/mysql-pause/constants.rb
|
60
|
+
- lib/mysql-pause/error.rb
|
61
|
+
- lib/mysql-pause/proxy.rb
|
62
|
+
- lib/mysql-pause/server.rb
|
63
|
+
homepage: https://bitbucket.org/winebarrel/mysql-pause
|
64
|
+
licenses: []
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.8.23
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: mysql-pause is a proxy server to pause a query to MySQL.
|
87
|
+
test_files: []
|