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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
data.tar.gz: 1a664b28c0d9ab5bc744ee05541062e603d3e637
|
4
|
+
metadata.gz: de56065c29b02b45a8ab80b1f06922c2ad5349b3
|
5
|
+
SHA512:
|
6
|
+
data.tar.gz: d82b6e0c7f95d7f83251da16087b9ca15c8ba0deb3a9762eca97d85944802074b4f4e8a32cff1f7f9e6b246f137a41642dfd6a5cb4d91b94278278a0572006ab
|
7
|
+
metadata.gz: f264844f630a2f7973c99ec5dd7c912af64bae519523110f287888ca260b00407cb56021c7677527a88ade801b5751fc4d97f40a9cafa6821e833aa7e3a151fd
|
data/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# Snapback
|
2
|
+
|
3
|
+
Version 0.1 Alpha
|
4
|
+
|
5
|
+
Create MySQL snapshots for easy development rollback.
|
6
|
+
|
7
|
+
## Project Details
|
8
|
+
|
9
|
+
### Use case
|
10
|
+
|
11
|
+
Large databases can be cumbersome to reload particularly when testing database migrations.
|
12
|
+
This gem utilises logical volume management (LVM) to create database snapshots, allowing you to rollback to specified point in time.
|
13
|
+
|
14
|
+
### Problem domain
|
15
|
+
|
16
|
+
This scripts automates the concepts that are discussed in the following articles:
|
17
|
+
|
18
|
+
* lullabot.com -- [MySQL Backups Using LVM Snapshots](http://www.lullabot.com/articles/mysql-backups-using-lvm-snapshots)
|
19
|
+
* tldp.org -- [Taking a Backup Using Snapshots](http://tldp.org/HOWTO/LVM-HOWTO/snapshots_backup.html)
|
20
|
+
* mysqlperformanceblog.com -- [Faster Point In Time Recovery with LVM2 Snaphots and Binary Logs](http://www.mysqlperformanceblog.com/2012/02/23/faster-point-in-time-recovery-with-lvm2-snaphots-and-binary-logs/)
|
21
|
+
|
22
|
+
If you'd like more information on how to setup LVM on a Linux installation, see:
|
23
|
+
|
24
|
+
* howtogeek.com -- [What is Logical Volume Management and How Do You Enable It in Ubuntu?](http://www.howtogeek.com/howto/36568/what-is-logical-volume-management-and-how-do-you-enable-it-in-ubuntu/)
|
25
|
+
|
26
|
+
|
27
|
+
### Dependencies
|
28
|
+
|
29
|
+
* Linux (tested on Ubuntu 12.04.1 LTS)
|
30
|
+
* Sudo shell access
|
31
|
+
* Ruby (tested on version 1.8.7)
|
32
|
+
* Logical volume management (tested on version 2.02.66(2))
|
33
|
+
* MySQL (tested on version 5.5.29-0ubuntu0.12.04.1)
|
34
|
+
|
35
|
+
### Installation
|
36
|
+
|
37
|
+
# If the MySQL client libraries aren't already installed, you may need to run this
|
38
|
+
sudo apt-get update
|
39
|
+
sudo apt-get install build-essential
|
40
|
+
sudo apt-get install libmysql-ruby
|
41
|
+
sudo apt-get install libmysqlclient-dev
|
42
|
+
|
43
|
+
# Install the gem
|
44
|
+
sudo gem install snapback
|
45
|
+
|
46
|
+
The gem should also automatically install the following gems:
|
47
|
+
|
48
|
+
* mysql
|
49
|
+
* colorize
|
50
|
+
* open4
|
51
|
+
* ruby-lvm
|
52
|
+
|
53
|
+
You must then enable multiple tablespaces.
|
54
|
+
Add a line to the [mysqld] section of your MySQL my.cnf:
|
55
|
+
|
56
|
+
[mysqld]
|
57
|
+
innodb_file_per_table=ON
|
58
|
+
|
59
|
+
### Configuration
|
60
|
+
|
61
|
+
If you're on Ubuntu (or other Linux system that uses AppArmor), you may need to allow MySQL to use the mounted directories.
|
62
|
+
|
63
|
+
sudo nano /etc/apparmor.d/usr.sbin/mysqld
|
64
|
+
|
65
|
+
Add the following lines to the file:
|
66
|
+
|
67
|
+
/mnt/mysql/ rwk,
|
68
|
+
/mnt/mysql/** rwk,
|
69
|
+
|
70
|
+
Restart AppArmor and MySQL
|
71
|
+
|
72
|
+
service apparmor restart
|
73
|
+
service mysql restart
|
74
|
+
|
75
|
+
### Usage
|
76
|
+
|
77
|
+
Only recommended for use on virtual machines.
|
78
|
+
Not recommended for production environments.
|
79
|
+
|
80
|
+
## Setup
|
81
|
+
|
82
|
+
To start using this application, you must run this once:
|
83
|
+
|
84
|
+
sudo snapback install
|
85
|
+
|
86
|
+
This creates the appropriate directories and checks that you have the required programs installed.
|
87
|
+
It will also ask you questions about your environment and save your configuration in your home directory.
|
88
|
+
|
89
|
+
## Creating a new database
|
90
|
+
|
91
|
+
To create a new database, you must specify the following values:
|
92
|
+
|
93
|
+
* The name of the new database
|
94
|
+
* The size you expect the database to be (+10% for good measure)
|
95
|
+
|
96
|
+
E.g.: Create a database called "camera" which should hold 1G
|
97
|
+
|
98
|
+
sudo snapback create camera --size 1G
|
99
|
+
|
100
|
+
## Snapshot an existing database
|
101
|
+
|
102
|
+
To make a snapshot, you must specify the following values:
|
103
|
+
|
104
|
+
* The name of the database being snapshot
|
105
|
+
* The amount you expect the database to grow by (+10% for good measure). Remember that deleting items in the database will cause the database grow (as you're recording changes, not size of files).
|
106
|
+
|
107
|
+
E.g.: Snapshot a database called "camera", where the changes will amount to an extra 100MB.
|
108
|
+
|
109
|
+
sudo snapback snapshot camera --size 100M
|
110
|
+
|
111
|
+
## Rollback a database to the snapshot
|
112
|
+
|
113
|
+
Once you've finished trying to make changes in your database, you can create a snapshot
|
114
|
+
|
115
|
+
E.g.: Rollback the database "camera" back to the date of the snapshot.
|
116
|
+
|
117
|
+
sudo snapback rollback camera
|
118
|
+
|
119
|
+
## Drop a database
|
120
|
+
|
121
|
+
Once you've finished with the database, you can drop it from MySQL and remove the logical volumes.
|
122
|
+
|
123
|
+
E.g.: Drop the database "camera".
|
124
|
+
|
125
|
+
sudo snapback drop camera
|
126
|
+
|
127
|
+
## Mount existing devices
|
128
|
+
|
129
|
+
If you want to mount a logical volume (e.g.: after a reboot), you can use the mount command.
|
130
|
+
|
131
|
+
E.g.: Continue using the database "camera"
|
132
|
+
|
133
|
+
sudo snapback mount camera
|
134
|
+
|
135
|
+
## Unmount existing devices
|
136
|
+
|
137
|
+
If (for any reason) you want to unmount a logical volume (e.g.: disable a database temporarily), you can use the unmount command.
|
138
|
+
|
139
|
+
E.g.: Stop using the database "camera"
|
140
|
+
|
141
|
+
sudo snapback unmount camera
|
142
|
+
|
143
|
+
## LVM Issues
|
144
|
+
|
145
|
+
If you're getting an error message similar to following:
|
146
|
+
|
147
|
+
File descriptor ? (/home/?/.snapback.yml) leaked on lvcreate invocation.
|
148
|
+
|
149
|
+
You can circumvent this error by adding the following environmental variable before your command:
|
150
|
+
|
151
|
+
LVM_SUPPRESS_FD_WARNINGS=1
|
152
|
+
|
153
|
+
For example:
|
154
|
+
|
155
|
+
sudo LVM_SUPPRESS_FD_WARNINGS=1 sudo snapback snapshot camera --size 100M
|
156
|
+
|
157
|
+
## Licence
|
158
|
+
|
159
|
+
Copyright (C) 2013, [Bashkim Isai](http://www.bashkim.com.au)
|
160
|
+
|
161
|
+
This script is distributed under the MIT licence.
|
162
|
+
|
163
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
164
|
+
|
165
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
166
|
+
|
167
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
168
|
+
|
169
|
+
## Contributors
|
170
|
+
|
171
|
+
* @bashaus -- [Bashkim Isai](http://www.bashkim.com.au/)
|
172
|
+
|
173
|
+
If you fork this project and create a pull request add your GitHub username, your full name and website to the end of list above.
|
data/bin/snapback
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'yaml'
|
5
|
+
require 'optparse'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
require 'snapback/options'
|
9
|
+
require 'snapback/transaction'
|
10
|
+
require 'snapback/database'
|
11
|
+
require 'snapback/dsl'
|
12
|
+
|
13
|
+
begin
|
14
|
+
# Ensure user is root
|
15
|
+
if Process.uid != 0 then
|
16
|
+
raise "Must run as root"
|
17
|
+
end
|
18
|
+
rescue
|
19
|
+
$stderr.puts "#{$!.to_s.red}"
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
$options = Snapback::Options.parse(ARGV)
|
25
|
+
|
26
|
+
case $options[:command]
|
27
|
+
when "install"
|
28
|
+
require "snapback/app/install"
|
29
|
+
Snapback::App::Install.instance.go
|
30
|
+
|
31
|
+
puts ""
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
# Read confirmation options
|
37
|
+
$config = YAML.load_file($options[:config])
|
38
|
+
rescue
|
39
|
+
raise "Could not load configuration file: #{$options[:config]}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Connect to MySQL
|
43
|
+
$database = Snapback::Database.instance
|
44
|
+
$database.hostname = $config['mysql']['hostname']
|
45
|
+
$database.username = $config['mysql']['username']
|
46
|
+
$database.password = $config['mysql']['password']
|
47
|
+
$database.connect
|
48
|
+
|
49
|
+
case $options[:command]
|
50
|
+
when "create"
|
51
|
+
require "snapback/app/create"
|
52
|
+
Snapback::App::Create.instance.go
|
53
|
+
when "snapshot"
|
54
|
+
require "snapback/app/snapshot"
|
55
|
+
Snapback::App::Snapshot.instance.go
|
56
|
+
when "commit"
|
57
|
+
require "snapback/app/commit"
|
58
|
+
Snapback::App::Commit.instance.go
|
59
|
+
when "rollback"
|
60
|
+
require "snapback/app/rollback"
|
61
|
+
Snapback::App::Rollback.instance.go
|
62
|
+
when "drop"
|
63
|
+
require "snapback/app/drop"
|
64
|
+
Snapback::App::Drop.instance.go
|
65
|
+
when "mount"
|
66
|
+
require "snapback/app/mount"
|
67
|
+
Snapback::App::Mount.instance.go
|
68
|
+
when "unmount"
|
69
|
+
require "snapback/app/unmount"
|
70
|
+
Snapback::App::Unmount.instance.go
|
71
|
+
end
|
72
|
+
rescue
|
73
|
+
puts ""
|
74
|
+
$stderr.puts "#{$!.to_s.red}"
|
75
|
+
puts "Use -v (--verbose) to view entire process".red if !$options[:verbose]
|
76
|
+
puts "Rolling back".red
|
77
|
+
puts ""
|
78
|
+
|
79
|
+
Snapback::Transaction.instance.do_rollback
|
80
|
+
end
|
81
|
+
|
82
|
+
puts ""
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Snapback
|
4
|
+
module App
|
5
|
+
class Commit
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def go
|
9
|
+
volume_group_name = "#{$config['lvm']['volume_group']}"
|
10
|
+
logical_volume_name = "#{$config['lvm']['prefix_backup']}-#{$options[:database]}"
|
11
|
+
|
12
|
+
# Flush
|
13
|
+
exec_flush
|
14
|
+
|
15
|
+
# Stop the MySQL Server
|
16
|
+
$database.server_stop
|
17
|
+
|
18
|
+
on_rollback lambda {
|
19
|
+
$database.server_start
|
20
|
+
}
|
21
|
+
|
22
|
+
# Remove logical volume
|
23
|
+
run "Committing logical volume",
|
24
|
+
"lvremove -f /dev/#{volume_group_name}/#{logical_volume_name}"
|
25
|
+
|
26
|
+
# Start the MySQL Server
|
27
|
+
$database.server_start
|
28
|
+
|
29
|
+
on_rollback lambda {
|
30
|
+
$database.server_stop
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Snapback
|
4
|
+
module App
|
5
|
+
class Create
|
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 500M"
|
12
|
+
end
|
13
|
+
|
14
|
+
volume_group_name = "#{$config['lvm']['volume_group']}"
|
15
|
+
logical_volume_name = "#{$config['lvm']['prefix_database']}-#{$options[:database]}"
|
16
|
+
mount_dir = get_mount_dir $options[:database]
|
17
|
+
mysql_data_dir = $database.get_data_dir
|
18
|
+
|
19
|
+
if !check "Checking database exists #{$options[:database]}", lambda {
|
20
|
+
$database.db_exists?($options[:database])
|
21
|
+
} then
|
22
|
+
check "Creating database '#{$options[:database]}'", lambda {
|
23
|
+
$database.db_create($options[:database])
|
24
|
+
}
|
25
|
+
|
26
|
+
on_rollback lambda {
|
27
|
+
check "Database '#{$options[:database]}' is being dropped", lambda {
|
28
|
+
$database.db_drop($options[:database])
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
exec_flush
|
34
|
+
|
35
|
+
# Stop the MySQL Server
|
36
|
+
$database.server_stop
|
37
|
+
|
38
|
+
on_rollback lambda {
|
39
|
+
$database.server_start
|
40
|
+
}
|
41
|
+
|
42
|
+
# Create a new logical volume (500MB)
|
43
|
+
# lvcreate –L 500MB –n mysql-{dbName} {vgName};
|
44
|
+
run "Create logical volume",
|
45
|
+
"lvcreate -L #{$options[:size]} -n #{logical_volume_name} #{volume_group_name}"
|
46
|
+
|
47
|
+
on_rollback lambda {
|
48
|
+
run "Removing logical volume",
|
49
|
+
"lvremove -f /dev/#{volume_group_name}/#{logical_volume_name}"
|
50
|
+
}
|
51
|
+
|
52
|
+
# Format the logical volume in ext4 format
|
53
|
+
# mkfs.ext4 /dev/{vgName}/mysql-{dbName};
|
54
|
+
run "Format logical volume filesystem",
|
55
|
+
"mkfs.ext4 /dev/#{volume_group_name}/#{logical_volume_name}"
|
56
|
+
|
57
|
+
# Create a new directory for where the MySQL database can be mounted
|
58
|
+
# mkdir /mnt/mysql/{dbName};
|
59
|
+
|
60
|
+
if !File.directory? mount_dir then
|
61
|
+
run "Make mount directory",
|
62
|
+
"mkdir #{mount_dir}"
|
63
|
+
|
64
|
+
on_rollback lambda {
|
65
|
+
run "Removing mount directory",
|
66
|
+
"rmdir #{$mount_dir}"
|
67
|
+
}
|
68
|
+
|
69
|
+
run "Changing permissions of mount directory",
|
70
|
+
"chmod 0777 #{mount_dir}"
|
71
|
+
end
|
72
|
+
|
73
|
+
# # Mount the new logical volume
|
74
|
+
# mount /dev/{vgName}/mysql-{dbName} /mnt/mysql/{dbName};
|
75
|
+
exec_mount "/dev/#{volume_group_name}/#{logical_volume_name}", mount_dir
|
76
|
+
|
77
|
+
# # Move the contents of the database to the new logical volume
|
78
|
+
# mv {mysql-data-dir}/{dbName}/* /mnt/mysql/{dbName};
|
79
|
+
|
80
|
+
move_mysql_files "#{mysql_data_dir}/#{$options[:database]}", mount_dir
|
81
|
+
|
82
|
+
on_rollback lambda {
|
83
|
+
move_mysql_files mount_dir, "#{mysql_data_dir}/#{$options[:database]}"
|
84
|
+
}
|
85
|
+
|
86
|
+
# # Remove the folder in the MySQL data directory
|
87
|
+
# rmdir {mysql-data-dir}/{dbName}/
|
88
|
+
run "Remove mysql database directory",
|
89
|
+
"rm -rf #{mysql_data_dir}/#{$options[:database]}"
|
90
|
+
|
91
|
+
on_rollback lambda {
|
92
|
+
run "Re-creating MySQL database directory",
|
93
|
+
"mkdir #{mysql_data_dir}/#{$options[:database]}"
|
94
|
+
|
95
|
+
exec_chown "#{mysql_data_dir}/#{$options[:database]}"
|
96
|
+
}
|
97
|
+
|
98
|
+
# # Symbolic-link the MySQL data directory to the new logical volume
|
99
|
+
# ln -s /mnt/mysql/{dbName} {mysql-data-dir}/{dbName}/
|
100
|
+
exec_link "#{mysql_data_dir}/#{$options[:database]}", mount_dir
|
101
|
+
|
102
|
+
# # Change the permissions & ownership to MySQL
|
103
|
+
# chown -R mysql:mysql {mysql-data-dir}/{dbName}/
|
104
|
+
# chown -R mysql:mysql /mnt/mysql/{dbName}/
|
105
|
+
exec_chown "#{mysql_data_dir}/#{$options[:database]}"
|
106
|
+
exec_chown mount_dir
|
107
|
+
|
108
|
+
# Stop the MySQL Server
|
109
|
+
$database.server_start
|
110
|
+
|
111
|
+
on_rollback lambda {
|
112
|
+
$database.server_stop
|
113
|
+
}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "singleton"
|
2
|
+
require "src/app/commit"
|
3
|
+
|
4
|
+
module Snapback
|
5
|
+
module App
|
6
|
+
class Drop
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
def go
|
10
|
+
volume_group_name = "#{$config['lvm']['volume_group']}"
|
11
|
+
logical_volume_name = "#{$config['lvm']['prefix_database']}-#{$options[:database]}"
|
12
|
+
mount_dir = get_mount_dir $options[:database]
|
13
|
+
mysql_data_dir = $database.get_data_dir
|
14
|
+
|
15
|
+
# Remove the backup
|
16
|
+
Snapback::App::Commit.instance.go
|
17
|
+
|
18
|
+
if !$database.db_exists?($options[:database]) then
|
19
|
+
raise "Database '#{$options[:database]}' does not exist"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Flush
|
23
|
+
exec_flush
|
24
|
+
|
25
|
+
# Stop the MySQL Server
|
26
|
+
$database.server_stop
|
27
|
+
|
28
|
+
on_rollback lambda {
|
29
|
+
$database.server_start
|
30
|
+
}
|
31
|
+
|
32
|
+
# Unlink
|
33
|
+
exec_unlink "#{mysql_data_dir}/#{$options[:database]}", mount_dir
|
34
|
+
|
35
|
+
# Unmount
|
36
|
+
exec_unmount "/dev/#{volume_group_name}/#{logical_volume_name}", mount_dir
|
37
|
+
|
38
|
+
# Remove logical volume
|
39
|
+
run "Remove logical volume",
|
40
|
+
"lvremove -f /dev/#{volume_group_name}/#{logical_volume_name}"
|
41
|
+
|
42
|
+
# Start the MySQL Server
|
43
|
+
$database.server_start
|
44
|
+
|
45
|
+
on_rollback lambda {
|
46
|
+
$database.server_stop
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|