snapback 0.0.1
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 -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
|