mysqlknife 1.4.0 → 2.0.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.
- checksums.yaml +4 -4
- data/README.md +45 -62
- data/lib/mysqlknife.rb +5 -2
- data/lib/mysqlknife/cli.rb +120 -101
- data/lib/mysqlknife/command.rb +26 -0
- data/lib/mysqlknife/configs.rb +123 -0
- data/lib/mysqlknife/iterm.rb +29 -0
- data/lib/mysqlknife/log.rb +34 -0
- data/lib/mysqlknife/mysql.rb +24 -0
- data/lib/mysqlknife/mysql/command.rb +63 -0
- data/lib/mysqlknife/mysql/kill.rb +73 -0
- data/lib/mysqlknife/mysql/replica.rb +41 -0
- data/lib/mysqlknife/mysql/sql.rb +58 -0
- data/lib/mysqlknife/parameters.rb +84 -0
- data/lib/mysqlknife/ssh.rb +18 -0
- data/lib/mysqlknife/version.rb +1 -1
- metadata +13 -9
- data/lib/mysqlknife/checksum.rb +0 -26
- data/lib/mysqlknife/config.rb +0 -60
- data/lib/mysqlknife/connection.rb +0 -30
- data/lib/mysqlknife/kill.rb +0 -72
- data/lib/mysqlknife/replica.rb +0 -51
- data/lib/mysqlknife/sql.rb +0 -114
- data/lib/mysqlknife/swap.rb +0 -67
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mysqlknife
|
4
|
+
class Configs
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
attr_writer :mysql_host,
|
8
|
+
:mysql_databases
|
9
|
+
attr_reader :path,
|
10
|
+
:ssh_host,
|
11
|
+
:ssh_port,
|
12
|
+
:ssh_user,
|
13
|
+
:ssh_password,
|
14
|
+
:ssh_key,
|
15
|
+
:ssh_color,
|
16
|
+
:ssh_use,
|
17
|
+
:connections,
|
18
|
+
:configs,
|
19
|
+
:name,
|
20
|
+
:mysql_host,
|
21
|
+
:mysql_username,
|
22
|
+
:mysql_password,
|
23
|
+
:mysql_port,
|
24
|
+
:mysql_databases
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
# Define path for config file:
|
28
|
+
if ENV['ENV'] == 'test'
|
29
|
+
path = File.expand_path(File.join(Dir.pwd, '.db.yml'))
|
30
|
+
else
|
31
|
+
path = File.expand_path("#{Dir.home}/.db.yml")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Load config file:
|
35
|
+
@configs = YAML.load_file(path) if File.exist?(path)
|
36
|
+
|
37
|
+
if @configs.nil?
|
38
|
+
puts "Not exist config file in #{path}."
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
# Define generic variables:
|
43
|
+
@path = path
|
44
|
+
@ssh_host = @configs['ssh']['host']
|
45
|
+
@ssh_port = @configs['ssh']['port']
|
46
|
+
@ssh_user = @configs['ssh']['user']
|
47
|
+
@ssh_password = @configs['ssh']['password']
|
48
|
+
@ssh_key = @configs['ssh']['keys']
|
49
|
+
@connections = @configs['databases'].keys.sort
|
50
|
+
end
|
51
|
+
|
52
|
+
def method_missing(name, *args)
|
53
|
+
method = name.to_s.split(/_/)
|
54
|
+
if method.first == 'db'
|
55
|
+
param = method[1]
|
56
|
+
conn = args[0]
|
57
|
+
|
58
|
+
if @configs['databases'].include?(conn)
|
59
|
+
@configs['databases'][conn][param]
|
60
|
+
else
|
61
|
+
puts "Not exist connecion name: #{conn}"
|
62
|
+
exit 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def settings(name)
|
68
|
+
@name = name
|
69
|
+
@ssh_color = db_color(name)
|
70
|
+
@ssh_use = db_ssh(name)
|
71
|
+
@mysql_port = db_port(name)
|
72
|
+
@mysql_username = db_username(name)
|
73
|
+
@mysql_password = db_password(name)
|
74
|
+
end
|
75
|
+
|
76
|
+
def slaves(name)
|
77
|
+
slaves = db_slaves(name)
|
78
|
+
|
79
|
+
if slaves.nil?
|
80
|
+
[]
|
81
|
+
else
|
82
|
+
slaves
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def hosts(name)
|
87
|
+
hosts = []
|
88
|
+
hosts << db_host(name)
|
89
|
+
hosts.concat(slaves(name))
|
90
|
+
hosts.sort
|
91
|
+
end
|
92
|
+
|
93
|
+
def show(name)
|
94
|
+
host = db_host(name)
|
95
|
+
port = db_port(name)
|
96
|
+
username = db_username(name)
|
97
|
+
password = db_password(name)
|
98
|
+
slaves = db_slaves(name)
|
99
|
+
slaves = slaves.join(', ') unless slaves.nil?
|
100
|
+
|
101
|
+
%W[Master:\ #{host}
|
102
|
+
Slaves:\ #{slaves}
|
103
|
+
Port:\ #{port}
|
104
|
+
Username:\ #{username}
|
105
|
+
Password:\ #{password}].join("\n")
|
106
|
+
end
|
107
|
+
|
108
|
+
def tools(name)
|
109
|
+
unless db_tools(name).nil?
|
110
|
+
db_tools(name).map {|tool| tool.split.first(1).first }
|
111
|
+
else
|
112
|
+
puts "Please, add custom tools in config file."
|
113
|
+
exit 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def tool(name, tool)
|
118
|
+
unless db_tools(name).nil?
|
119
|
+
db_tools(name).select { |command| /#{tool}/.match(command.to_s) }.first
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mysqlknife
|
4
|
+
class Iterm
|
5
|
+
@@colors = { orange: [255,128,000],
|
6
|
+
green: [000,255,000],
|
7
|
+
red: [255,000,000] }
|
8
|
+
|
9
|
+
def name(name = nil)
|
10
|
+
print "\033]0;#{name}\007"
|
11
|
+
end
|
12
|
+
|
13
|
+
def color(name)
|
14
|
+
name = name.to_sym
|
15
|
+
|
16
|
+
red = @@colors[name][0]
|
17
|
+
green = @@colors[name][1]
|
18
|
+
blue = @@colors[name][2]
|
19
|
+
|
20
|
+
print "\033]6;1;bg;red;brightness;#{red}\a"
|
21
|
+
print "\033]6;1;bg;green;brightness;#{green}\a"
|
22
|
+
print "\033]6;1;bg;blue;brightness;#{blue}\a"
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset
|
26
|
+
print "\033]6;1;bg;*;default\a"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mysqlknife
|
4
|
+
class Log
|
5
|
+
attr_writer :verbose
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@log = Logger.new(STDOUT)
|
9
|
+
end
|
10
|
+
|
11
|
+
def kill(process)
|
12
|
+
process = process.map { |k, v| "#{k}: #{v}" }.join(', ')
|
13
|
+
@log.info("Kill MySQL Process: #{process}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def sql(statement)
|
17
|
+
@log.debug("SQL Statement: #{statement}") if $DEBUG
|
18
|
+
end
|
19
|
+
|
20
|
+
def sql_error(error, sql)
|
21
|
+
@log.debug("SQL Error: #{error}")
|
22
|
+
@log.debug("SQL Statement: #{sql}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def command(command)
|
26
|
+
@log.debug("Bash Command: #{command}") if $DEBUG
|
27
|
+
end
|
28
|
+
|
29
|
+
def skip_repl_error(status)
|
30
|
+
status = status.map { |k, v| "#{k}: #{v}" }.join(', ')
|
31
|
+
@log.info("MySQL Skip error replica: #{status}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mysqlknife
|
4
|
+
class MySQL
|
5
|
+
def initialize
|
6
|
+
@cnf = Configs.instance
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute(sql)
|
10
|
+
conn = Mysql2::Client.new(host: @cnf.mysql_host,
|
11
|
+
port: @cnf.mysql_port,
|
12
|
+
username: @cnf.mysql_username,
|
13
|
+
password: @cnf.mysql_password,
|
14
|
+
database: @cnf.mysql_database)
|
15
|
+
|
16
|
+
Mysqlknife::Log.new.sql(sql)
|
17
|
+
|
18
|
+
return conn.query(sql)
|
19
|
+
rescue Mysql2::Error => error
|
20
|
+
Mysqlknife::Log.new.sql_error(error, sql)
|
21
|
+
exit(1)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mysqlknife
|
4
|
+
module Mysql
|
5
|
+
class Command
|
6
|
+
def initialize
|
7
|
+
@cnf = Configs.instance
|
8
|
+
end
|
9
|
+
|
10
|
+
def check
|
11
|
+
find_executable('mysql')
|
12
|
+
find_executable('mysqlshow')
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse(command)
|
16
|
+
host = @cnf.mysql_host
|
17
|
+
port = @cnf.mysql_port
|
18
|
+
username = @cnf.mysql_username
|
19
|
+
password = password(@cnf.mysql_password)
|
20
|
+
|
21
|
+
eval("\"#{command}\"")
|
22
|
+
end
|
23
|
+
|
24
|
+
def prompt
|
25
|
+
if !!(@cnf.mysql_host =~ Resolv::IPv4::Regex)
|
26
|
+
@cnf.name
|
27
|
+
else
|
28
|
+
@cnf.mysql_host.partition('.').first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute(sentence)
|
33
|
+
"--execute='#{sentence}'" unless sentence.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def password(password)
|
37
|
+
"-p#{password}" unless password.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
def console(sentence = nil)
|
41
|
+
%W[mysql
|
42
|
+
-h #{@cnf.mysql_host}
|
43
|
+
-P #{@cnf.mysql_port}
|
44
|
+
-u #{@cnf.mysql_username}
|
45
|
+
#{password(@cnf.mysql_password)}
|
46
|
+
--prompt='#{prompt}[\\d]> '
|
47
|
+
#{@cnf.mysql_database}
|
48
|
+
#{execute(sentence)}
|
49
|
+
].join(' ')
|
50
|
+
end
|
51
|
+
|
52
|
+
def describe(*args)
|
53
|
+
%W[mysqlshow
|
54
|
+
-h #{@cnf.mysql_host}
|
55
|
+
-P #{@cnf.mysql_port}
|
56
|
+
-u #{@cnf.mysql_username}
|
57
|
+
#{password(@cnf.mysql_password)}
|
58
|
+
--keys
|
59
|
+
#{args.join(' ')}].join(' ')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mysqlknife
|
4
|
+
module Mysql
|
5
|
+
class Kill
|
6
|
+
attr_writer :where
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@mysql = MySQL.new
|
10
|
+
@mysql_sql = Mysql::SQL.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def clear
|
14
|
+
list.each do |process|
|
15
|
+
kill(process['id'])
|
16
|
+
|
17
|
+
Mysqlknife::Log.new.kill(process)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_user
|
22
|
+
sql = @mysql_sql.current_user
|
23
|
+
result = @mysql.execute(sql).first['CURRENT_USER'] =~ /^root\@/
|
24
|
+
|
25
|
+
return true if result
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_grants
|
29
|
+
sql = @mysql_sql.current_user_grants
|
30
|
+
result = @mysql.execute(sql).first.values.to_s =~ /PROCESS/
|
31
|
+
|
32
|
+
return true if result
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_privileges
|
36
|
+
check_user || check_grants
|
37
|
+
end
|
38
|
+
|
39
|
+
def list
|
40
|
+
@mysql.execute(@mysql_sql.show_processlist(@where))
|
41
|
+
end
|
42
|
+
|
43
|
+
def show
|
44
|
+
process = list
|
45
|
+
|
46
|
+
unless process.first.nil?
|
47
|
+
table = Terminal::Table.new do |t|
|
48
|
+
t.add_row(process.first.keys)
|
49
|
+
t.add_separator
|
50
|
+
process.each do |row|
|
51
|
+
t.add_row(row.values)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
puts table
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def rds?
|
59
|
+
! @mysql.execute(@mysql_sql.show_procedure('rds_kill')).first
|
60
|
+
end
|
61
|
+
|
62
|
+
def kill(id)
|
63
|
+
if check_privileges
|
64
|
+
if rds?
|
65
|
+
@mysql.execute(@mysql_sql.mysql_kill(id))
|
66
|
+
else
|
67
|
+
@mysql.execute(@mysql_sql.rds_kill(id))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mysqlknife
|
4
|
+
module Mysql
|
5
|
+
class Replica
|
6
|
+
attr_writer :behind
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@mysql = MySQL.new
|
10
|
+
@mysql_cmd = Mysql::Command.new
|
11
|
+
@mysql_sql = Mysql::SQL.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def check_status_negative(behind)
|
15
|
+
@behind < 0 && behind < 0 && behind <= @behind && behind != 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def rds?
|
19
|
+
! @mysql.execute(@mysql_sql.show_procedure('rds_skip_repl_error')).first
|
20
|
+
end
|
21
|
+
|
22
|
+
def slave_status
|
23
|
+
@mysql.execute(@mysql_sql.slave_status).first
|
24
|
+
end
|
25
|
+
|
26
|
+
def skip
|
27
|
+
status = slave_status
|
28
|
+
|
29
|
+
if check_status_negative(status['Seconds_Behind_Master'])
|
30
|
+
Mysqlknife::Log.new.skip_repl_error(status)
|
31
|
+
|
32
|
+
if rds?
|
33
|
+
@mysql.execute(@mysql_sql.rds_skip_repl_error)
|
34
|
+
else
|
35
|
+
@mysql.execute(@mysql_sql.mysql_skip_repl_error)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mysqlknife
|
4
|
+
module Mysql
|
5
|
+
class SQL
|
6
|
+
def current_user
|
7
|
+
'SELECT CURRENT_USER;'
|
8
|
+
end
|
9
|
+
|
10
|
+
def current_user_grants
|
11
|
+
'SHOW GRANTS FOR CURRENT_USER;'
|
12
|
+
end
|
13
|
+
|
14
|
+
def replica_lag
|
15
|
+
'SHOW SLAVE STATUS\G'
|
16
|
+
end
|
17
|
+
|
18
|
+
def show_processlist(where = nil)
|
19
|
+
sql = %w(SELECT id, user, host, db, command, time, state, info
|
20
|
+
FROM INFORMATION_SCHEMA.PROCESSLIST
|
21
|
+
WHERE state NOT REGEXP '(slave|relay|event)'
|
22
|
+
AND user NOT IN ('rdsadmin',
|
23
|
+
'rdsrepladmin',
|
24
|
+
'system user',
|
25
|
+
'event_scheduler')
|
26
|
+
AND id != CONNECTION_ID()
|
27
|
+
AND command != 'Binlog Dump').join(' ')
|
28
|
+
sql << " AND #{where};" unless where.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def show_procedure(name)
|
32
|
+
"SHOW PROCEDURE STATUS LIKE '#{name}';"
|
33
|
+
end
|
34
|
+
|
35
|
+
def mysql_skip_repl_error
|
36
|
+
%w(STOP SLAVE;
|
37
|
+
SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
|
38
|
+
START SLAVE;).join(' ')
|
39
|
+
end
|
40
|
+
|
41
|
+
def mysql_kill(pid)
|
42
|
+
"kill #{pid};"
|
43
|
+
end
|
44
|
+
|
45
|
+
def slave_status
|
46
|
+
'SHOW SLAVE STATUS;'
|
47
|
+
end
|
48
|
+
|
49
|
+
def rds_kill(pid)
|
50
|
+
"CALL mysql.rds_kill(#{pid});"
|
51
|
+
end
|
52
|
+
|
53
|
+
def rds_skip_repl_error
|
54
|
+
'CALL mysql.rds_skip_repl_error;'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|