snapback 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +173 -0
- data/bin/snapback +82 -0
- data/lib/snapback/app/commit.rb +35 -0
- data/lib/snapback/app/create.rb +117 -0
- data/lib/snapback/app/drop.rb +51 -0
- data/lib/snapback/app/install.rb +156 -0
- data/lib/snapback/app/mount.rb +62 -0
- data/lib/snapback/app/rollback.rb +63 -0
- data/lib/snapback/app/snapshot.rb +74 -0
- data/lib/snapback/app/unmount.rb +39 -0
- data/lib/snapback/database.rb +102 -0
- data/lib/snapback/dsl.rb +208 -0
- data/lib/snapback/options.rb +85 -0
- data/lib/snapback/transaction.rb +19 -0
- metadata +104 -0
@@ -0,0 +1,156 @@
|
|
1
|
+
require "lvm"
|
2
|
+
require "singleton"
|
3
|
+
|
4
|
+
module Snapback
|
5
|
+
module App
|
6
|
+
class Install
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
def go
|
10
|
+
puts ""
|
11
|
+
puts "Hi!"
|
12
|
+
puts ""
|
13
|
+
puts "I'm going to guide you through setting up Snapback."
|
14
|
+
puts "This script will check to see if your environment can run "
|
15
|
+
puts "Snapback and will setup configuration files for you."
|
16
|
+
puts ""
|
17
|
+
|
18
|
+
begin
|
19
|
+
run "Checking LVM is installed",
|
20
|
+
"lvm version"
|
21
|
+
rescue
|
22
|
+
raise "You do not have LVM installed on this computer. You cannot use snapback."
|
23
|
+
end
|
24
|
+
|
25
|
+
# Get information from LVM
|
26
|
+
lvm = LVM::LVM.new({:command => "sudo lvm", :version => "2.02.30"})
|
27
|
+
volume_groups = []
|
28
|
+
volume_group = nil
|
29
|
+
|
30
|
+
lvm.volume_groups.each do |vg|
|
31
|
+
volume_groups.push(vg)
|
32
|
+
end
|
33
|
+
puts ""
|
34
|
+
|
35
|
+
# Decide which Logical volume use to use
|
36
|
+
if volume_groups.size == 0 then
|
37
|
+
raise "You do not have any volume groups in LVM. Please setup LVM before using Snapback"
|
38
|
+
elsif volume_groups.size == 1 then
|
39
|
+
volume_group = volume_groups[0];
|
40
|
+
puts "You have one volume group named #{volume_group.name.green}."
|
41
|
+
puts "Snapback will use this logical volume."
|
42
|
+
else
|
43
|
+
puts "Here is a list of volume groups on your system:"
|
44
|
+
|
45
|
+
volume_groups.each do |vg|
|
46
|
+
puts "#{volume_groups.size}.\t#{vg.name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
volume_group_number = ask_int "Which volume group would you like Snapback use", volume_groups.size
|
50
|
+
volume_group = volume_groups[volume_group_number - 1];
|
51
|
+
end
|
52
|
+
|
53
|
+
puts ""
|
54
|
+
|
55
|
+
# Check for mount directory
|
56
|
+
mysql_mount_dir = ask_string "Where do you want to mount your logical volumes [/mnt/mysql]"
|
57
|
+
puts ""
|
58
|
+
|
59
|
+
if mysql_mount_dir.empty? then
|
60
|
+
mysql_mount_dir = "/mnt/mysql"
|
61
|
+
end
|
62
|
+
|
63
|
+
if !check "Checking for directory #{mysql_mount_dir}", lambda {
|
64
|
+
File.directory? mysql_mount_dir
|
65
|
+
} then
|
66
|
+
run "Creating directory #{mysql_mount_dir}",
|
67
|
+
"mkdir -p #{mysql_mount_dir}"
|
68
|
+
|
69
|
+
on_rollback lambda {
|
70
|
+
run "Removing #{mysql_mount_dir} directory",
|
71
|
+
"rm -rf #{mysql_mount_dir}"
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
# MySQL connection
|
76
|
+
|
77
|
+
while true
|
78
|
+
puts ""
|
79
|
+
puts "Enter the crudentials to connect to MySQL"
|
80
|
+
|
81
|
+
$database = Snapback::Database.instance
|
82
|
+
|
83
|
+
$database.hostname = ask_string "MySQL hostname [localhost]"
|
84
|
+
if $database.hostname.empty? then
|
85
|
+
$database.hostname = "localhost"
|
86
|
+
end
|
87
|
+
|
88
|
+
$database.username = ask_string "MySQL username [root]"
|
89
|
+
if $database.username.empty? then
|
90
|
+
$database.username = "root"
|
91
|
+
end
|
92
|
+
|
93
|
+
$database.password = ask_string "MySQL password"
|
94
|
+
|
95
|
+
puts ""
|
96
|
+
|
97
|
+
begin
|
98
|
+
check "Connecting to MySQL database", lambda {
|
99
|
+
$database.connect
|
100
|
+
true
|
101
|
+
}
|
102
|
+
rescue
|
103
|
+
show_failed
|
104
|
+
next
|
105
|
+
end
|
106
|
+
|
107
|
+
break
|
108
|
+
end
|
109
|
+
|
110
|
+
if !check "Checking #{"innodb_file_per_table"} is #{"ON"}", lambda {
|
111
|
+
$database.innodb_file_per_table
|
112
|
+
} then
|
113
|
+
debug "Setting #{"innodb_file_per_table"} to #{"ON"}"
|
114
|
+
$database.set_innodb_file_per_table true
|
115
|
+
|
116
|
+
on_rollback lambda {
|
117
|
+
debug "Setting #{"innodb_file_per_table"} to #{"OFF"}"
|
118
|
+
$database.set_innodb_file_per_table false
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
$config = {
|
123
|
+
'lvm' => {
|
124
|
+
'volume_group' => volume_group.name.to_s,
|
125
|
+
'prefix_database' => 'snapback-active',
|
126
|
+
'prefix_backup' => 'snapback-backup'
|
127
|
+
},
|
128
|
+
|
129
|
+
'mysql' => {
|
130
|
+
'hostname' => $database.hostname,
|
131
|
+
'username' => $database.username,
|
132
|
+
'password' => $database.password
|
133
|
+
},
|
134
|
+
|
135
|
+
'filesystem' => {
|
136
|
+
'mount' => '/mnt/mysql'
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
File.open($options[:config], 'w+') { |f|
|
141
|
+
f.write($config.to_yaml)
|
142
|
+
}
|
143
|
+
|
144
|
+
on_rollback lambda {
|
145
|
+
File.unlink $options[:config]
|
146
|
+
}
|
147
|
+
|
148
|
+
puts ""
|
149
|
+
puts "Snapback is now installed and configured."
|
150
|
+
puts "To start using Snapback, run the following command: "
|
151
|
+
puts ""
|
152
|
+
puts "sudo snapback --help".yellow
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Snapback
|
4
|
+
module App
|
5
|
+
class Mount
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def go
|
9
|
+
volume_group_name = "#{$config['lvm']['volume_group']}"
|
10
|
+
logical_volume_name = "#{$config['lvm']['prefix_database']}-#{$options[:database]}"
|
11
|
+
mount_dir = get_mount_dir $options[:database]
|
12
|
+
mysql_data_dir = $database.get_data_dir
|
13
|
+
|
14
|
+
exec_flush
|
15
|
+
|
16
|
+
# Stop the MySQL Server
|
17
|
+
$database.server_stop
|
18
|
+
|
19
|
+
on_rollback lambda {
|
20
|
+
$database.server_start
|
21
|
+
}
|
22
|
+
|
23
|
+
# Create a new directory for where the MySQL database can be mounted
|
24
|
+
# mkdir /mnt/mysql/{dbName};
|
25
|
+
|
26
|
+
if !File.directory? mount_dir then
|
27
|
+
run "Make mount directory",
|
28
|
+
"mkdir #{mount_dir}"
|
29
|
+
|
30
|
+
on_rollback lambda {
|
31
|
+
run "Removing mount directory",
|
32
|
+
"rmdir #{$mount_dir}"
|
33
|
+
}
|
34
|
+
|
35
|
+
run "Changing permissions of mount directory",
|
36
|
+
"chmod 0777 #{mount_dir}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# # Mount the new logical volume
|
40
|
+
# mount /dev/{vgName}/mysql-{dbName} /mnt/mysql/{dbName};
|
41
|
+
exec_mount "/dev/#{volume_group_name}/#{logical_volume_name}", mount_dir
|
42
|
+
|
43
|
+
# # Symbolic-link the MySQL data directory to the new logical volume
|
44
|
+
# ln -s /mnt/mysql/{dbName} {mysql-data-dir}/{dbName}/
|
45
|
+
exec_link "#{mysql_data_dir}/#{$options[:database]}", mount_dir
|
46
|
+
|
47
|
+
# # Change the permissions & ownership to MySQL
|
48
|
+
# chown -R mysql:mysql {mysql-data-dir}/{dbName}/
|
49
|
+
# chown -R mysql:mysql /mnt/mysql/{dbName}/
|
50
|
+
exec_chown "#{mysql_data_dir}/#{$options[:database]}"
|
51
|
+
exec_chown mount_dir
|
52
|
+
|
53
|
+
# Stop the MySQL Server
|
54
|
+
$database.server_start
|
55
|
+
|
56
|
+
on_rollback lambda {
|
57
|
+
$database.server_stop
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Snapback
|
4
|
+
module App
|
5
|
+
class Rollback
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def go
|
9
|
+
volume_group_name = "#{$config['lvm']['volume_group']}"
|
10
|
+
logical_volume_name = "#{$config['lvm']['prefix_database']}-#{$options[:database]}"
|
11
|
+
mount_dir = get_mount_dir $options[:database]
|
12
|
+
mysql_data_dir = $database.get_data_dir
|
13
|
+
|
14
|
+
# Flush the MySQL Logs
|
15
|
+
exec_flush
|
16
|
+
|
17
|
+
# Stop the MySQL Server
|
18
|
+
$database.server_stop
|
19
|
+
|
20
|
+
on_rollback lambda {
|
21
|
+
$database.server_start
|
22
|
+
}
|
23
|
+
|
24
|
+
# Remove the symbolic link
|
25
|
+
exec_unlink "#{mysql_data_dir}/#{$options[:database]}", mount_dir
|
26
|
+
|
27
|
+
# Unmount the logical volume
|
28
|
+
exec_unmount "/dev/#{volume_group_name}/#{logical_volume_name}", mount_dir
|
29
|
+
|
30
|
+
# Deactivate the logical volume
|
31
|
+
exec_deactivate "/dev/#{volume_group_name}/#{logical_volume_name}"
|
32
|
+
|
33
|
+
# Merge the old logical volume into the new one
|
34
|
+
# lvconvert --merge /dev/{vgName}/backup-{dbName}
|
35
|
+
run "Merging the snapshot into the live logical volume",
|
36
|
+
"lvconvert --merge /dev/#{volume_group_name}/#{$config['lvm']['prefix_backup']}-#{$options[:database]}"
|
37
|
+
|
38
|
+
# Active the master drive
|
39
|
+
# lvchange -ay /dev/{vgName}/mysql-{dbName}
|
40
|
+
exec_activate "/dev/#{volume_group_name}/#{logical_volume_name}"
|
41
|
+
|
42
|
+
# Mount the logical volume
|
43
|
+
exec_mount "/dev/#{volume_group_name}/#{logical_volume_name}", mount_dir
|
44
|
+
|
45
|
+
# Symbolic-link the MySQL data directory to the new logical volume
|
46
|
+
exec_link "#{mysql_data_dir}/#{$options[:database]}", mount_dir
|
47
|
+
|
48
|
+
# Change the permissions & ownership to MySQL
|
49
|
+
# chown -R mysql:mysql {mysql-data-dir}/{dbName}/
|
50
|
+
# chown -R mysql:mysql /mnt/mysql/{dbName}/
|
51
|
+
exec_chown "#{mysql_data_dir}/#{$options[:database]}"
|
52
|
+
exec_chown "#{mount_dir}"
|
53
|
+
|
54
|
+
# Start the MySQL Server
|
55
|
+
$database.server_start
|
56
|
+
|
57
|
+
on_rollback lambda {
|
58
|
+
$database.server_stop
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Snapback
|
4
|
+
module App
|
5
|
+
class Snapshot
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def go
|
9
|
+
# Ensure we have a size parameter
|
10
|
+
if $options[:size].nil? then
|
11
|
+
raise "You must specify a size attribute. E.g.: -s 100M"
|
12
|
+
end
|
13
|
+
|
14
|
+
if !$database.db_exists?($options[:database]) then
|
15
|
+
raise "Database '#{$options[:database]}' does not exist"
|
16
|
+
end
|
17
|
+
|
18
|
+
volume_group_name = "#{$config['lvm']['volume_group']}"
|
19
|
+
logical_volume_name = "#{$config['lvm']['prefix_database']}-#{$options[:database]}"
|
20
|
+
mount_dir = get_mount_dir $options[:database]
|
21
|
+
mysql_data_dir = $database.get_data_dir
|
22
|
+
|
23
|
+
# Flush the MySQL Logs
|
24
|
+
exec_flush
|
25
|
+
|
26
|
+
# Stop the MySQL Server
|
27
|
+
$database.server_stop
|
28
|
+
|
29
|
+
on_rollback lambda {
|
30
|
+
$database.server_start
|
31
|
+
}
|
32
|
+
|
33
|
+
# Unlink
|
34
|
+
exec_unlink "#{mysql_data_dir}/#{$options[:database]}", mount_dir
|
35
|
+
|
36
|
+
# Unmount
|
37
|
+
exec_unmount "/dev/#{volume_group_name}/#{logical_volume_name}", mount_dir
|
38
|
+
|
39
|
+
# Deactivate
|
40
|
+
exec_deactivate "/dev/#{volume_group_name}/#{logical_volume_name}"
|
41
|
+
|
42
|
+
# Branch the logical volume with a snapshot
|
43
|
+
run "Snapshot the logical volume",
|
44
|
+
"lvcreate -L #{$options[:size]} -s -n #{$config['lvm']['prefix_backup']}-#{$options[:database]} /dev/#{volume_group_name}/#{logical_volume_name}"
|
45
|
+
|
46
|
+
on_rollback lambda {
|
47
|
+
run "Remove the snapshot",
|
48
|
+
"lvremove /dev/#{volume_group_name}/#{$config['lvm']['prefix_backup']}-#{$options[:database]}"
|
49
|
+
}
|
50
|
+
|
51
|
+
# Active the master drive
|
52
|
+
exec_activate "/dev/#{volume_group_name}/#{logical_volume_name}"
|
53
|
+
|
54
|
+
# Mount the master drive
|
55
|
+
exec_mount "/dev/#{volume_group_name}/#{logical_volume_name}", mount_dir
|
56
|
+
|
57
|
+
# Symbolic-link the MySQL data directory to the new logical volume
|
58
|
+
# ln -s /mnt/mysql/{dbName} {mysql-data-dir}/{dbName}/
|
59
|
+
exec_link "#{mysql_data_dir}/#{$options[:database]}", mount_dir
|
60
|
+
|
61
|
+
# Change the permissions & ownership to MySQL
|
62
|
+
exec_chown "#{mysql_data_dir}/#{$options[:database]}"
|
63
|
+
exec_chown mount_dir
|
64
|
+
|
65
|
+
# Start the MySQL Server
|
66
|
+
$database.server_start
|
67
|
+
|
68
|
+
on_rollback lambda {
|
69
|
+
$database.server_stop
|
70
|
+
}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Snapback
|
4
|
+
module App
|
5
|
+
class Unmount
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def go
|
9
|
+
volume_group_name = "#{$config['lvm']['volume_group']}"
|
10
|
+
logical_volume_name = "#{$config['lvm']['prefix_database']}-#{$options[:database]}"
|
11
|
+
mount_dir = get_mount_dir $options[:database]
|
12
|
+
mysql_data_dir = $database.get_data_dir
|
13
|
+
|
14
|
+
# Flush
|
15
|
+
exec_flush
|
16
|
+
|
17
|
+
# Stop the MySQL Server
|
18
|
+
$database.server_stop
|
19
|
+
|
20
|
+
on_rollback lambda {
|
21
|
+
$database.server_start
|
22
|
+
}
|
23
|
+
|
24
|
+
# Unlink
|
25
|
+
exec_unlink "#{mysql_data_dir}/#{$options[:database]}", mount_dir
|
26
|
+
|
27
|
+
# Unmount
|
28
|
+
exec_unmount "/dev/#{volume_group_name}/#{logical_volume_name}", mount_dir
|
29
|
+
|
30
|
+
# Start the MySQL Server
|
31
|
+
$database.server_start
|
32
|
+
|
33
|
+
on_rollback lambda {
|
34
|
+
$database.server_stop
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'mysql'
|
3
|
+
|
4
|
+
# ## Database functions ##
|
5
|
+
|
6
|
+
module Snapback
|
7
|
+
class Database
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
attr_accessor :hostname, :username, :password
|
11
|
+
|
12
|
+
@connection = nil
|
13
|
+
|
14
|
+
@hostname = ""
|
15
|
+
@username = ""
|
16
|
+
@password = ""
|
17
|
+
|
18
|
+
def connect
|
19
|
+
@connection = Mysql.new @hostname, @username, @password
|
20
|
+
end
|
21
|
+
|
22
|
+
def flush_lock
|
23
|
+
begin
|
24
|
+
@connection.query "FLUSH TABLES WITH READ LOCK"
|
25
|
+
end
|
26
|
+
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def unlock
|
31
|
+
begin
|
32
|
+
@connection.query "UNLOCK TABLES"
|
33
|
+
end
|
34
|
+
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_data_dir
|
39
|
+
begin
|
40
|
+
rs = @connection.query "SHOW VARIABLES LIKE 'datadir'"
|
41
|
+
rs.each_hash do |row|
|
42
|
+
return row['Value'].gsub(/\/$/, '')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def innodb_file_per_table
|
48
|
+
begin
|
49
|
+
rs = @connection.query "SHOW VARIABLES LIKE 'innodb_file_per_table'"
|
50
|
+
rs.each_hash do |row|
|
51
|
+
return row['Value'] == "ON"
|
52
|
+
end
|
53
|
+
rescue
|
54
|
+
raise "MySQL Query failed"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_innodb_file_per_table on
|
59
|
+
begin
|
60
|
+
rs = @connection.prepare "SET GLOBAL innodb_file_per_table = ?"
|
61
|
+
rs.execute(on ? 'ON' : 'OFF')
|
62
|
+
return true
|
63
|
+
rescue
|
64
|
+
raise "MySQL Query failed"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def db_exists? database_name
|
69
|
+
sql = @connection.list_dbs database_name
|
70
|
+
sql.size == 1
|
71
|
+
end
|
72
|
+
|
73
|
+
def db_create database_name
|
74
|
+
@connection.query "CREATE DATABASE `#{Mysql.escape_string database_name}`"
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def db_drop database_name
|
79
|
+
@connection.query "DROP DATABASE `#{Mysql.escape_string database_name}`"
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def db_use database_name
|
84
|
+
@connection.select_db database_name
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
def server_stop
|
89
|
+
run "Stop MySQL server",
|
90
|
+
"service mysql stop"
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
def server_start
|
95
|
+
run "Start MySQL server and reconnect",
|
96
|
+
"service mysql start"
|
97
|
+
|
98
|
+
self.connect
|
99
|
+
true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|