snapback 0.0.1 → 0.0.3
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 +7 -7
- data/README.md +6 -11
- data/bin/snapback +51 -67
- data/lib/snapback.rb +130 -0
- data/lib/snapback/cli/commit.rb +72 -0
- data/lib/snapback/cli/create.rb +157 -0
- data/lib/snapback/cli/drop.rb +94 -0
- data/lib/snapback/cli/install.rb +179 -0
- data/lib/snapback/cli/mount.rb +97 -0
- data/lib/snapback/cli/rollback.rb +161 -0
- data/lib/snapback/cli/snapshot.rb +155 -0
- data/lib/snapback/cli/unmount.rb +86 -0
- data/lib/snapback/configuration/Configuration_0_0_3.rb +99 -0
- data/lib/snapback/configuration_loader.rb +24 -0
- data/lib/snapback/filesystem.rb +39 -0
- data/lib/snapback/mysql/client_control.rb +125 -0
- data/lib/snapback/mysql/service_control.rb +14 -0
- data/lib/snapback/transaction.rb +25 -10
- data/lib/snapback/version.rb +3 -0
- metadata +110 -67
- data/lib/snapback/app/commit.rb +0 -35
- data/lib/snapback/app/create.rb +0 -117
- data/lib/snapback/app/drop.rb +0 -51
- data/lib/snapback/app/install.rb +0 -156
- data/lib/snapback/app/mount.rb +0 -62
- data/lib/snapback/app/rollback.rb +0 -63
- data/lib/snapback/app/snapshot.rb +0 -74
- data/lib/snapback/app/unmount.rb +0 -39
- data/lib/snapback/database.rb +0 -102
- data/lib/snapback/dsl.rb +0 -208
- data/lib/snapback/options.rb +0 -85
@@ -0,0 +1,155 @@
|
|
1
|
+
desc 'Take a database snapshot'
|
2
|
+
arg_name 'database [, ...]'
|
3
|
+
command :snapshot do |c|
|
4
|
+
|
5
|
+
c.desc 'Disk size of changes expected'
|
6
|
+
c.flag [:s, :size]
|
7
|
+
|
8
|
+
c.action do |global_options,options,args|
|
9
|
+
help_now!('parameter -s for size is required') if options[:s].nil?
|
10
|
+
help_now!('database(s) required') if args.empty?
|
11
|
+
|
12
|
+
# Load the configuration
|
13
|
+
config = Snapback::ConfigurationLoader.factory global_options[:config]
|
14
|
+
|
15
|
+
# Connect to MySQL
|
16
|
+
mysql_client = config.mysql_client
|
17
|
+
|
18
|
+
args.each do |database|
|
19
|
+
|
20
|
+
# Start the transaction
|
21
|
+
Snapback::Transaction.new do
|
22
|
+
|
23
|
+
run_command "Selecting database: #{database}" do
|
24
|
+
mysql_client.database_select(database)
|
25
|
+
end
|
26
|
+
|
27
|
+
vg_name = config.lvm_volume_group
|
28
|
+
lv_name = "#{config.lvm_database_prefix}-#{database}"
|
29
|
+
lv_path = "/dev/#{vg_name}/#{lv_name}"
|
30
|
+
|
31
|
+
mount_database_directory = config.filesystem_mount_directory(database)
|
32
|
+
mysql_database_directory = "#{mysql_client.get_data_directory}/#{database}"
|
33
|
+
|
34
|
+
# Check
|
35
|
+
|
36
|
+
lv_available = run_command "Checking logical volume name is available" do
|
37
|
+
!File.exists?("/dev/#{vg_name}/#{config.lvm_snapshot_prefix}-#{database}")
|
38
|
+
end
|
39
|
+
|
40
|
+
if !lv_available then
|
41
|
+
raise "Logical volume #{lv_path.colorize(:red)} already exist"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Drop tablespaces
|
45
|
+
tables = run_command "Getting list of tables in database" do
|
46
|
+
mysql_client.database_tables
|
47
|
+
end
|
48
|
+
|
49
|
+
tables.each do |table|
|
50
|
+
table = table.to_s
|
51
|
+
run_command "Changing table engine to MyISAM: #{table}" do
|
52
|
+
mysql_client.table_set_engine(table, "MyISAM")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Flush
|
57
|
+
run_command "Flush tables with read lock" do
|
58
|
+
mysql_client.flush_tables
|
59
|
+
end
|
60
|
+
|
61
|
+
# Stop MySQL
|
62
|
+
run_command "Stop MySQL server" do
|
63
|
+
Snapback::MySQL::ServiceControl.stop
|
64
|
+
end
|
65
|
+
|
66
|
+
revert do
|
67
|
+
run_command "Start MySQL server" do
|
68
|
+
Snapback::MySQL::ServiceControl.start
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Unlink
|
73
|
+
run_command "Unlinking mysql data directory",
|
74
|
+
"unlink #{mysql_database_directory}"
|
75
|
+
|
76
|
+
revert do
|
77
|
+
run_command "Linking mysql data directory",
|
78
|
+
"ln -s #{mount_database_directory} #{mysql_database_directory}"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Unmount
|
82
|
+
run_command "Unmounting logical volume",
|
83
|
+
"umount #{mount_database_directory}"
|
84
|
+
|
85
|
+
revert do
|
86
|
+
run_command "Mounting logical volume",
|
87
|
+
"mount #{lv_path} #{mount_database_directory}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Deactivate
|
91
|
+
run_command "De-activating logical volume",
|
92
|
+
"lvchange -an #{lv_path}"
|
93
|
+
|
94
|
+
revert do
|
95
|
+
run_command "Re-activating logical volume",
|
96
|
+
"lvchange -ay #{lv_path}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Branch the logical volume with a snapshot
|
100
|
+
run_command "Snapshoting to #{config.lvm_snapshot_prefix}-#{database}",
|
101
|
+
"lvcreate -L #{options[:s]} -s -n #{config.lvm_snapshot_prefix}-#{database} #{lv_path}"
|
102
|
+
|
103
|
+
revert do
|
104
|
+
run_command "Removing the snapshot",
|
105
|
+
"lvremove -f /dev/#{vg_name}/#{config.lvm_snapshot_prefix}-#{database}"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Active the master drive
|
109
|
+
run_command "Activating logical volume",
|
110
|
+
"lvchange -ay #{lv_path}"
|
111
|
+
|
112
|
+
revert do
|
113
|
+
run_command "Deactivating logical volume",
|
114
|
+
"lvchange -an #{lv_path}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Mount the master drive
|
118
|
+
run_command "Mounting logical volume",
|
119
|
+
"mount #{lv_path} #{mount_database_directory}"
|
120
|
+
|
121
|
+
revert do
|
122
|
+
run_command "Unmounting logical volume",
|
123
|
+
"umount #{mount_database_directory}"
|
124
|
+
end
|
125
|
+
|
126
|
+
# Symbolic-link the MySQL data directory to the new logical volume
|
127
|
+
run_command "Linking mysql data directory",
|
128
|
+
"ln -s #{mount_database_directory} #{mysql_database_directory}"
|
129
|
+
|
130
|
+
revert do
|
131
|
+
run_command "Unlinking mysql data directory",
|
132
|
+
"unlink #{mysql_database_directory}"
|
133
|
+
end
|
134
|
+
|
135
|
+
# Change the permissions & ownership to MySQL
|
136
|
+
run_command "Changing owner to mysql: #{mysql_database_directory}",
|
137
|
+
"chown -R mysql:mysql #{mysql_database_directory}"
|
138
|
+
|
139
|
+
run_command "Changing owner to mysql: #{mount_database_directory}",
|
140
|
+
"chown -R mysql:mysql #{mount_database_directory}"
|
141
|
+
|
142
|
+
# Start MySQL
|
143
|
+
run_command "Starting MySQL server" do
|
144
|
+
Snapback::MySQL::ServiceControl.start
|
145
|
+
end
|
146
|
+
|
147
|
+
revert do
|
148
|
+
run_command "Stopping MySQL server" do
|
149
|
+
Snapback::MySQL::ServiceControl.stop
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
desc 'Unmount an existing database'
|
2
|
+
arg_name 'database [, ...]'
|
3
|
+
command :unmount do |c|
|
4
|
+
c.action do |global_options,options,args|
|
5
|
+
help_now!('database(s) required') if args.empty?
|
6
|
+
|
7
|
+
# Load the configuration
|
8
|
+
config = Snapback::ConfigurationLoader.factory global_options[:config]
|
9
|
+
|
10
|
+
# Connect to MySQL
|
11
|
+
mysql_client = config.mysql_client
|
12
|
+
|
13
|
+
# For each database
|
14
|
+
args.each do |database|
|
15
|
+
|
16
|
+
# Start the transaction
|
17
|
+
Snapback::Transaction.new do
|
18
|
+
run_command "Selecting database: #{database}" do
|
19
|
+
mysql_client.database_select(database)
|
20
|
+
end
|
21
|
+
|
22
|
+
vg_name = config.lvm_volume_group
|
23
|
+
lv_name = config.lvm_logical_database database
|
24
|
+
lv_path = "/dev/#{vg_name}/#{lv_name}"
|
25
|
+
|
26
|
+
mount_database_directory = config.filesystem_mount_directory(database)
|
27
|
+
mysql_database_directory = "#{mysql_client.get_data_directory}/#{database}"
|
28
|
+
|
29
|
+
lv_exists = run_command "Checking logical volume exists" do
|
30
|
+
File.exists?(lv_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
if !lv_exists then
|
34
|
+
raise "Logical volume #{lv_path.colorize(:red)} does not exist"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Flush
|
38
|
+
run_command "Flush tables with read lock" do
|
39
|
+
mysql_client.flush_tables
|
40
|
+
end
|
41
|
+
|
42
|
+
# Stop MySQL
|
43
|
+
run_command "Stop MySQL server" do
|
44
|
+
Snapback::MySQL::ServiceControl.stop
|
45
|
+
end
|
46
|
+
|
47
|
+
revert do
|
48
|
+
run_command "Start MySQL server" do
|
49
|
+
Snapback::MySQL::ServiceControl.start
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Unlink
|
54
|
+
run_command "Unlinking data directory",
|
55
|
+
"unlink #{mysql_database_directory}"
|
56
|
+
|
57
|
+
revert do
|
58
|
+
run_command "Re-linking data directory" do
|
59
|
+
"ln -s #{mount_database_directory} #{mysql_database_directory}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Unmount
|
64
|
+
run_command "Unmounting directory",
|
65
|
+
"unmount #{mount_database_directory}"
|
66
|
+
|
67
|
+
revert do
|
68
|
+
run_command "Re-mounting data directory" do
|
69
|
+
"mount #{lv_path} #{mount_database_directory}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Start MySQL
|
74
|
+
run_command "Start MySQL server" do
|
75
|
+
Snapback::MySQL::ServiceControl.start
|
76
|
+
end
|
77
|
+
|
78
|
+
revert do
|
79
|
+
run_command "Stop MySQL server" do
|
80
|
+
Snapback::MySQL::ServiceControl.stop
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'snapback/mysql/client_control'
|
2
|
+
|
3
|
+
module Snapback
|
4
|
+
module Configuration
|
5
|
+
class Configuration_0_0_3
|
6
|
+
|
7
|
+
def initialize(yaml)
|
8
|
+
@@yaml = yaml
|
9
|
+
end
|
10
|
+
|
11
|
+
def version
|
12
|
+
@@yaml['version']
|
13
|
+
end
|
14
|
+
|
15
|
+
def lvm_volume_group
|
16
|
+
begin
|
17
|
+
@@yaml['lvm']['volume_group']
|
18
|
+
rescue
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def lvm_logical_database(database)
|
24
|
+
begin
|
25
|
+
"#{@@yaml['lvm']['database_prefix']}-#{database}"
|
26
|
+
rescue
|
27
|
+
"snapback-database-#{database}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def lvm_logical_snapshot(database)
|
32
|
+
begin
|
33
|
+
"#{@@yaml['lvm']['snapshot_prefix']}-#{database}"
|
34
|
+
rescue
|
35
|
+
"snapback-snapshot-#{database}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def lvm_database_prefix
|
40
|
+
begin
|
41
|
+
@@yaml['lvm']['database_prefix']
|
42
|
+
rescue
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def lvm_snapshot_prefix
|
48
|
+
begin
|
49
|
+
@@yaml['lvm']['snapshot_prefix']
|
50
|
+
rescue
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def mysql_client
|
56
|
+
client = Snapback::MySQL::ClientControl.instance
|
57
|
+
|
58
|
+
client.hostname = mysql_hostname
|
59
|
+
client.username = mysql_username
|
60
|
+
client.password = mysql_password
|
61
|
+
client.connect
|
62
|
+
|
63
|
+
client
|
64
|
+
end
|
65
|
+
|
66
|
+
def mysql_hostname
|
67
|
+
begin
|
68
|
+
@@yaml['mysql']['hostname']
|
69
|
+
rescue
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def mysql_username
|
75
|
+
begin
|
76
|
+
@@yaml['mysql']['username']
|
77
|
+
rescue
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def mysql_password
|
83
|
+
begin
|
84
|
+
@@yaml['mysql']['password']
|
85
|
+
rescue
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def filesystem_mount_directory(database)
|
91
|
+
begin
|
92
|
+
"#{@@yaml['filesystem']['mount']}/#{database}"
|
93
|
+
rescue
|
94
|
+
"/mnt/snapback-#{database}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'snapback/configuration/configuration_0_0_3'
|
2
|
+
|
3
|
+
module Snapback
|
4
|
+
class ConfigurationLoader
|
5
|
+
def initialize(options={}) #:nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.factory filename
|
9
|
+
begin
|
10
|
+
# Read confirmation options
|
11
|
+
configuration = YAML.load_file filename
|
12
|
+
rescue
|
13
|
+
raise "Could not load configuration file: #{filename}"
|
14
|
+
end
|
15
|
+
|
16
|
+
case configuration['version']
|
17
|
+
when "0.0.3"
|
18
|
+
::Snapback::Configuration::Configuration_0_0_3.new configuration
|
19
|
+
else
|
20
|
+
raise "Unknown configuration version: #{configuration['version']}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Snapback
|
2
|
+
class Filesystem
|
3
|
+
|
4
|
+
LOCKED_FILES = ['.', '..', 'lost+found']
|
5
|
+
|
6
|
+
def self.mount(device, directory)
|
7
|
+
# If it's already mounted, don't worry
|
8
|
+
mount_status = Open4::popen4("mountpoint -q #{directory}") do |pid, stdin, stdout, stderr|
|
9
|
+
end
|
10
|
+
|
11
|
+
if mount_status == 0 then
|
12
|
+
return true
|
13
|
+
end
|
14
|
+
|
15
|
+
`mount #{device} #{directory}`
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.unmount(directory)
|
19
|
+
# If it's already unmounted, don't worry
|
20
|
+
mount_status = Open4::popen4("mountpoint -q #{directory}") do |pid, stdin, stdout, stderr|
|
21
|
+
end
|
22
|
+
|
23
|
+
if mount_status != 0 then
|
24
|
+
return true
|
25
|
+
end
|
26
|
+
|
27
|
+
`umount #{directory}`
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.move_mysql_files(from_directory, to_directory)
|
31
|
+
Dir.foreach(from_directory) do |filename|
|
32
|
+
if !LOCKED_FILES.include?(filename) then
|
33
|
+
run_command "Moving #{from_directory}/#{filename} to #{to_directory}",
|
34
|
+
"mv #{from_directory}/#{filename} #{to_directory}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'mysql'
|
3
|
+
|
4
|
+
module Snapback
|
5
|
+
module MySQL
|
6
|
+
class ClientControl
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
attr_accessor :hostname, :username, :password
|
10
|
+
|
11
|
+
# Connection
|
12
|
+
|
13
|
+
@connection = nil
|
14
|
+
|
15
|
+
@hostname = ""
|
16
|
+
@username = ""
|
17
|
+
@password = ""
|
18
|
+
|
19
|
+
def connect
|
20
|
+
@connection = Mysql.new @hostname, @username, @password
|
21
|
+
end
|
22
|
+
|
23
|
+
# Maintenance
|
24
|
+
|
25
|
+
def flush_tables
|
26
|
+
@connection.query "FLUSH TABLES WITH READ LOCK"
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def unlock_tables
|
31
|
+
@connection.query "UNLOCK TABLES"
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
# Configuration
|
36
|
+
|
37
|
+
def get_data_directory
|
38
|
+
rs = @connection.query "SHOW VARIABLES LIKE 'datadir'"
|
39
|
+
rs.each_hash do |row|
|
40
|
+
return row['Value'].gsub(/\/$/, '')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_innodb_file_per_table?
|
45
|
+
rs = @connection.query "SHOW VARIABLES LIKE 'innodb_file_per_table'"
|
46
|
+
rs.each_hash do |row|
|
47
|
+
return row['Value'] == "ON"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_innodb_file_per_table(on)
|
52
|
+
rs = @connection.prepare "SET GLOBAL innodb_file_per_table = ?"
|
53
|
+
rs.execute(on ? 'ON' : 'OFF')
|
54
|
+
end
|
55
|
+
|
56
|
+
# Database
|
57
|
+
|
58
|
+
def database_exists?(database)
|
59
|
+
@connection.list_dbs(database).size == 1
|
60
|
+
end
|
61
|
+
|
62
|
+
def database_create(database)
|
63
|
+
begin
|
64
|
+
@connection.query "CREATE DATABASE `#{Mysql.escape_string database}`"
|
65
|
+
true
|
66
|
+
rescue
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def database_select(database)
|
72
|
+
@connection.select_db(database)
|
73
|
+
end
|
74
|
+
|
75
|
+
def database_tables
|
76
|
+
@connection.query "SHOW TABLES"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Tables
|
80
|
+
|
81
|
+
def table_drop(table)
|
82
|
+
begin
|
83
|
+
@connection.query "DROP TABLE `#{Mysql.escape_string table}`"
|
84
|
+
true
|
85
|
+
rescue
|
86
|
+
false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def table_optimize(table)
|
91
|
+
begin
|
92
|
+
@connection.query "ALTER TABLE `#{Mysql.escape_string table}` IMPORT TABLESPACE"
|
93
|
+
rescue
|
94
|
+
# If this fails, it's usually just because the table is not InnoDB, so ignore errors
|
95
|
+
end
|
96
|
+
|
97
|
+
begin
|
98
|
+
@connection.query "OPTIMIZE TABLE `#{Mysql.escape_string table}`"
|
99
|
+
true
|
100
|
+
rescue
|
101
|
+
false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def table_set_engine(table, engine)
|
106
|
+
begin
|
107
|
+
@connection.query "ALTER TABLE `#{Mysql.escape_string table}` ENGINE = #{Mysql.escape_string engine};"
|
108
|
+
true
|
109
|
+
rescue
|
110
|
+
false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Tablespace
|
115
|
+
def tablespace_discard(table)
|
116
|
+
begin
|
117
|
+
@connection.query "ALTER TABLE `#{Mysql.escape_string table}` DISCARD TABLESPACE"
|
118
|
+
true
|
119
|
+
rescue
|
120
|
+
false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|