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 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: []