mysql-pause 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,13 @@
1
+ require 'eventmachine'
2
+
3
+ module MysqlPause
4
+ class Backend < EM::Connection
5
+ def initialize(proxy)
6
+ @proxy = proxy
7
+ end
8
+
9
+ def receive_data(data)
10
+ @proxy.send_data(data)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,2 @@
1
+ APP_NAME = 'mysql-pause'
2
+ Version = '0.1.0'
@@ -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: []