cluster 0.5.33

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+ PKG_NAME = ENV['PKG_NAME'] || 'cluster'
2
+ require File.join(File.dirname(__FILE__), 'lib', PKG_NAME, 'version')
3
+
4
+ namespace :cluster do
5
+ desc "Display #{Cluster::NAME} current version."
6
+ task :version do
7
+ puts "#{Cluster::NAME} is at version #{Cluster::Version::CURRENT}"
8
+ end
9
+
10
+ desc "Update the #{Cluster::NAME}.gem file at S3."
11
+ task :update => :package do
12
+ # Using right_aws directly to get around having to
13
+ # have a fantastic require structure to use the
14
+ # cluster tool itself to do this.
15
+ #
16
+ # Most particularly this means that error messages will
17
+ # show up...
18
+ require 'right_aws'
19
+
20
+ Rightscale::HttpConnection.params = Rightscale::HttpConnection.params.merge(:ca_file => File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'cluster', 'infrastructures', 'aws-certificates.crt')))
21
+
22
+ oven = RightAws::S3.new(ENV['AMAZON_ACCESS_KEY_ID'], ENV['AMAZON_SECRET_ACCESS_KEY']).bucket(Cluster::BUCKET)
23
+
24
+ unless oven
25
+ STDERR.puts "Bucket by the name of #{Cluster::BUCKET} cannot be found or accessed."
26
+ exit 1
27
+ end
28
+
29
+ filename = File.join('pkg', "#{Cluster::NAME}-#{Cluster::Version::STRING}.gem")
30
+ file = open(filename)
31
+ oven.put("#{Cluster::NAME}.gem", file, {}, 'public-read')
32
+ end
33
+ end
34
+
35
+ # FIXME these RightAws calls could be refactored into one.
36
+ # Not only that but they should probably be using the cluster
37
+ # infrastructure...
38
+ namespace :images do
39
+ desc "List the current images used"
40
+ task :show do
41
+ require 'right_aws'
42
+
43
+ oven = RightAws::S3.new(ENV['AMAZON_ACCESS_KEY_ID'], ENV['AMAZON_SECRET_ACCESS_KEY']).bucket(Cluster::BUCKET)
44
+
45
+ unless oven
46
+ STDERR.puts "Bucket by the name of #{Cluster::BUCKET} cannot be found or accessed."
47
+ exit 1
48
+ end
49
+
50
+ filename = 'cluster_images.yml'
51
+ puts oven.get(filename)
52
+ end
53
+
54
+ desc "Update the ec2 base image data"
55
+ task :update do
56
+ require 'right_aws'
57
+
58
+ oven = RightAws::S3.new(ENV['AMAZON_ACCESS_KEY_ID'], ENV['AMAZON_SECRET_ACCESS_KEY']).bucket(Cluster::BUCKET)
59
+
60
+ unless oven
61
+ STDERR.puts "Bucket by the name of #{Cluster::BUCKET} cannot be found or accessed."
62
+ exit 1
63
+ end
64
+
65
+ filename = 'cluster_images.yml'
66
+ file = open(filename)
67
+ oven.put(filename, file, {}, 'public-read')
68
+ end
69
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.32
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'cluster'
5
+ rescue LoadError
6
+ unless $:.include? '.'
7
+ $: << '.'
8
+ retry
9
+ end
10
+ end
11
+
12
+ require File.join('cluster', 'cli')
13
+
14
+ Cluster::Cli.new
@@ -0,0 +1,676 @@
1
+ #!/usr/bin/perl
2
+ #
3
+ # Copyright (C) 2008-2009 Eric Hammond
4
+ #
5
+ use strict;
6
+ use warnings;
7
+ (our $Prog) = ($0 =~ m%([^/]+)$%);
8
+ use Getopt::Long;
9
+ use Pod::Usage;
10
+ use File::Slurp;
11
+ use Time::HiRes qw(ualarm usleep);
12
+ use Net::Amazon::EC2 0.11;
13
+ use POSIX ':signal_h';
14
+ my $SIGALRM = 14;
15
+
16
+ #---- OPTIONS ----
17
+
18
+ my $Help = 0;
19
+ my $Debug = 0;
20
+ my $Quiet = 0;
21
+ my $Noaction = 0;
22
+
23
+ my $aws_access_key_id = $ENV{AWS_ACCESS_KEY_ID};
24
+ my $aws_secret_access_key = $ENV{AWS_SECRET_ACCESS_KEY};
25
+ my $aws_access_key_id_file = $ENV{AWS_ACCESS_KEY_ID};
26
+ my $aws_secret_access_key_file = $ENV{AWS_SECRET_ACCESS_KEY};
27
+ my $region = undef;
28
+ my $description = $Prog;
29
+ my $xfs_filesystem = undef;
30
+ my $mysql = 0;
31
+ my $mysql_defaults_file = undef;
32
+ my $mysql_master_status_file = undef;
33
+ my $mysql_username = undef;
34
+ my $mysql_password = undef;
35
+ my $mysql_host = undef;
36
+ my $mysql_stop = 0;
37
+ my $snapshot_timeout = 10.0; # seconds
38
+ my $lock_timeout = 0.5; # seconds
39
+ my $lock_tries = 60;
40
+ my $lock_sleep = 5.0; # seconds
41
+
42
+ Getopt::Long::config('no_ignore_case');
43
+ GetOptions(
44
+ 'help|?' => \$Help,
45
+ 'debug' => \$Debug,
46
+ 'quiet' => \$Quiet,
47
+ 'noaction' => \$Noaction,
48
+
49
+ 'aws-access-key-id=s' => \$aws_access_key_id,
50
+ 'aws-secret-access-key=s' => \$aws_secret_access_key,
51
+ 'aws-access-key-id-file=s' => \$aws_access_key_id_file,
52
+ 'aws-secret-access-key-file=s' => \$aws_secret_access_key_file,
53
+ 'region=s' => \$region,
54
+ 'description=s' => \$description,
55
+ 'xfs-filesystem=s' => \$xfs_filesystem,
56
+ 'mysql' => \$mysql,
57
+ 'mysql-defaults-file=s' => \$mysql_defaults_file,
58
+ 'mysql-master-status-file=s' => \$mysql_master_status_file,
59
+ 'mysql-username=s' => \$mysql_username,
60
+ 'mysql-password=s' => \$mysql_password,
61
+ 'mysql-host=s' => \$mysql_host,
62
+ 'mysql-stop' => \$mysql_stop,
63
+ 'snapshot-timeout=s' => \$snapshot_timeout,
64
+ 'lock-timeout=s' => \$lock_timeout,
65
+ 'lock-tries=s' => \$lock_tries,
66
+ 'lock-sleep=s' => \$lock_sleep,
67
+ ) or pod2usage(2);
68
+
69
+ pod2usage(1) if $Help;
70
+
71
+ my @volume_ids = @ARGV;
72
+ pod2usage(2) unless scalar @volume_ids;
73
+
74
+ #---- MAIN ----
75
+
76
+ ($aws_access_key_id, $aws_secret_access_key) = determine_access_keys(
77
+ $aws_access_key_id, $aws_secret_access_key,
78
+ $aws_access_key_id_file, $aws_secret_access_key_file,
79
+ );
80
+ die "$Prog: ERROR: Can't find AWS access key or secret access key"
81
+ unless $aws_access_key_id and $aws_secret_access_key;
82
+
83
+ my $mysql_stopped = undef;
84
+ my $mysql_dbh = undef;
85
+ if ( $mysql_stop ) {
86
+ $mysql_stopped = mysql_stop();
87
+ } elsif ( $mysql ) {
88
+ $mysql_dbh = mysql_connect($mysql_host, $mysql_username, $mysql_password);
89
+ mysql_lock($mysql_dbh);
90
+ }
91
+
92
+ # sync may help flush changed blocks, increasing the consistency on
93
+ # non-XFS file systems, and reducing the time the freeze locks changes
94
+ # out on XFS file systems.
95
+ run_command(['sync']);
96
+
97
+ xfs_freeze($xfs_filesystem) if $xfs_filesystem;
98
+
99
+ ebs_snapshot(\@volume_ids, $region, $description);
100
+
101
+ exit 0;
102
+
103
+ END {
104
+ xfs_thaw($xfs_filesystem) if $xfs_filesystem;
105
+
106
+ if ( defined($mysql_master_status_file) ) {
107
+ $Debug and warn "$Prog: ", scalar localtime,
108
+ ": removing master info file: $mysql_master_status_file\n";
109
+ if ( not $Noaction ) {
110
+ unlink $mysql_master_status_file
111
+ or die "$Prog: Couldn't remove file: $mysql_master_status_file: $!\n";
112
+ }
113
+ }
114
+
115
+ if ( $mysql_stopped ) {
116
+ mysql_start();
117
+ } elsif ( $mysql_dbh ) {
118
+ mysql_unlock($mysql_dbh);
119
+ $Debug and warn "$Prog: ", scalar localtime, ": MySQL disconnect\n";
120
+ $mysql_dbh->disconnect;
121
+ }
122
+
123
+ $Debug and warn "$Prog: ", scalar localtime, ": done\n";
124
+ }
125
+
126
+ #---- METHODS ----
127
+
128
+ sub ebs_snapshot {
129
+ my ($volume_ids, $region, $description) = @_;
130
+
131
+ $Debug and warn "$Prog: ", scalar localtime, ": create EC2 object\n";
132
+
133
+ # EC2 API object
134
+ my $ec2 = Net::Amazon::EC2->new(
135
+ AWSAccessKeyId => $aws_access_key_id,
136
+ SecretAccessKey => $aws_secret_access_key,
137
+ ($region ? (base_url => "http://$region.ec2.amazonaws.com") : ()),
138
+ # ($Debug ? (debug => 1) : ()),
139
+ );
140
+
141
+ VOLUME:
142
+ for my $volume_id ( @$volume_ids ) {
143
+ # Snapshot
144
+ $Debug and
145
+ warn "$Prog: ", scalar localtime, ": ec2-create-snapshot $volume_id\n";
146
+ if ( $Noaction ) {
147
+ warn "snapshot SKIPPED (--noaction)\n";
148
+ next VOLUME;
149
+ }
150
+
151
+ my $snapshot;
152
+ eval {
153
+ ualarm($snapshot_timeout * 1_000_000);
154
+ $snapshot = $ec2->create_snapshot(
155
+ VolumeId => $volume_id,
156
+ Description => $description,
157
+ );
158
+ ualarm(0);
159
+ };
160
+ ualarm(0);
161
+ if ( $@ ){
162
+ warn "$Prog: ERROR: create_snapshot: $@";
163
+ return undef;
164
+ }
165
+
166
+ my $snapshot_id;
167
+ if ( not defined $snapshot ) {
168
+ warn "$Prog: ERROR: create_snapshot returned undef\n";
169
+ } elsif ( not ref $snapshot ) {
170
+ warn "$Prog: ERROR: create_snapshot returned '$snapshot'\n";
171
+ } elsif ( $snapshot->can('snapshot_id') ) {
172
+ $snapshot_id = $snapshot->snapshot_id;
173
+ $Quiet or print "$snapshot_id\n";
174
+ } else {
175
+ for my $error ( @{$snapshot->errors} ) {
176
+ warn "$Prog: ERROR: ".$error->message."\n";
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ sub mysql_connect {
183
+ my ($mysql_host, $mysql_username, $mysql_password) = @_;
184
+
185
+ require DBI;
186
+ DBI->import;
187
+
188
+ if ( defined($mysql_defaults_file) )
189
+ {
190
+ ($mysql_host, $mysql_username, $mysql_password) = read_mydefaultsfile(
191
+ $mysql_defaults_file,
192
+ );
193
+ } else {
194
+ ($mysql_host, $mysql_username, $mysql_password) = read_mycnf(
195
+ $mysql_host, $mysql_username, $mysql_password,
196
+ );
197
+ }
198
+
199
+ $mysql_host ||= 'localhost';
200
+
201
+ $Debug and warn "$Prog: ", scalar localtime,
202
+ ": MySQL connect as $mysql_username\n";
203
+
204
+ my $mysql_dbh = DBI->connect("DBI:mysql:;host=$mysql_host",
205
+ $mysql_username, $mysql_password)
206
+ or die "$Prog: ERROR: Unable to connect to MySQL"
207
+ . " on $mysql_host as $mysql_username";
208
+
209
+ return $mysql_dbh;
210
+ }
211
+
212
+ sub mysql_lock {
213
+ my ($mysql_dbh) = @_;
214
+
215
+ # Don't pass FLUSH TABLES statements on to replication slaves
216
+ # as this can interfere with long-running queries on the slaves.
217
+ $mysql_dbh->do(q{ SET SQL_LOG_BIN=0 }) unless $Noaction;
218
+
219
+ # Try a flush first without locking so the later flush with lock
220
+ # goes faster. This may not be needed as it seems to interfere with
221
+ # some statements anyway.
222
+ sql_timeout_retry(
223
+ q{ FLUSH LOCAL TABLES },
224
+ "MySQL flush",
225
+ $lock_timeout,
226
+ $lock_tries,
227
+ $lock_sleep,
228
+ );
229
+
230
+ # Get a lock on the entire database
231
+ sql_timeout_retry(
232
+ q{ FLUSH LOCAL TABLES WITH READ LOCK },
233
+ "MySQL flush & lock",
234
+ $lock_timeout,
235
+ $lock_tries,
236
+ $lock_sleep,
237
+ );
238
+
239
+ my ($mysql_logfile, $mysql_position,
240
+ $mysql_binlog_do_db, $mysql_binlog_ignore_db);
241
+ if ( not $Noaction ) {
242
+ # This might be a slave database already
243
+ my $slave_status = $mysql_dbh->selectrow_hashref(q{ SHOW SLAVE STATUS });
244
+ $mysql_logfile = $slave_status->{Master_Log_File};
245
+ $mysql_position = $slave_status->{Read_Master_Log_Pos};
246
+ $mysql_binlog_do_db = $slave_status->{Replicate_Do_DB};
247
+ $mysql_binlog_ignore_db = $slave_status->{Replicate_Ignore_DB};
248
+
249
+ # or this might be the master
250
+ ($mysql_logfile, $mysql_position,
251
+ $mysql_binlog_do_db, $mysql_binlog_ignore_db) =
252
+ $mysql_dbh->selectrow_array(q{ SHOW MASTER STATUS })
253
+ unless $mysql_logfile;
254
+ }
255
+
256
+ $mysql_dbh->do(q{ SET SQL_LOG_BIN=1 }) unless $Noaction;
257
+
258
+ print "$Prog: master_log_file=\"$mysql_logfile\",",
259
+ " master_log_pos=$mysql_position\n"
260
+ if $mysql_logfile and not $Quiet;
261
+
262
+ if ( defined($mysql_master_status_file) && $mysql_logfile ) {
263
+ $Debug and warn "$Prog: ", scalar localtime,
264
+ ": writing MASTER STATUS to $mysql_master_status_file\n";
265
+ if ( not $Noaction ) {
266
+ open(MYSQLMASTERSTATUS,"> $mysql_master_status_file") or
267
+ die "$Prog: Unable to open for write: $mysql_master_status_file: $!\n";
268
+ print MYSQLMASTERSTATUS <<"EOM";
269
+ master_log_file="$mysql_logfile"
270
+ master_log_pos="$mysql_position"
271
+ master_binlog_do_db="$mysql_binlog_do_db"
272
+ master_binlog_ignore_db="$mysql_binlog_ignore_db"
273
+ EOM
274
+ close(MYSQLMASTERSTATUS);
275
+ }
276
+ }
277
+
278
+ return ($mysql_logfile, $mysql_position);
279
+ }
280
+
281
+ sub mysql_unlock {
282
+ my ($mysql_dbh) = @_;
283
+
284
+ $Debug and warn "$Prog: ", scalar localtime, ": MySQL unlock\n";
285
+ $mysql_dbh->do(q{ UNLOCK TABLES }) unless $Noaction;
286
+
287
+ }
288
+
289
+ sub mysql_stop {
290
+ $Debug and warn "$Prog: ", scalar localtime, ": MySQL stop\n";
291
+
292
+ my $result = system('/usr/bin/mysqladmin', 'shutdown');
293
+
294
+ if ( $result != 0 ) {
295
+ die "$Prog: Unable to stop mysqld: $? $!";
296
+ }
297
+
298
+ return 1;
299
+ }
300
+
301
+ sub mysql_start {
302
+ $Debug and warn "$Prog: ", scalar localtime, ": MySQL start\n";
303
+
304
+ my $result = system('/etc/init.d/mysql', 'start');
305
+
306
+ if ( $result != 0 ) {
307
+ warn "$Prog: Unable to start mysqld: $? $!";
308
+ }
309
+ }
310
+
311
+ # See also:
312
+ #http://search.cpan.org/dist/DBI/DBI.pm#Signal_Handling_and_Canceling_Operations
313
+ sub sql_timeout_retry {
314
+ my ($sql, $description, $lock_timeout, $lock_tries, $lock_sleep) = @_;
315
+
316
+ my $action = POSIX::SigAction->new(
317
+ sub { die "timeout" },
318
+ POSIX::SigSet->new( SIGALRM ),
319
+ );
320
+ my $oldaction = POSIX::SigAction->new();
321
+ sigaction($SIGALRM, $action, $oldaction);
322
+ LOCK:
323
+ while ( $lock_tries -- ) {
324
+ $Debug and warn "$Prog: ", scalar localtime, ": $description\n";
325
+ eval {
326
+ ualarm($lock_timeout * 1_000_000);
327
+ $mysql_dbh->do($sql) unless $Noaction;
328
+ ualarm(0);
329
+ };
330
+ ualarm(0);
331
+ last LOCK unless $@ =~ /timeout/;
332
+
333
+ } continue {
334
+ $Quiet or warn "$Prog: MySQL timeout at $lock_timeout sec\n";
335
+ $Debug and warn "$Prog: Trying again in $lock_sleep seconds\n";
336
+ usleep($lock_sleep * 1_000_000);
337
+ #TBD: May need to reopen $mysql_dbh here as the handle may not be clean.
338
+ }
339
+ sigaction($SIGALRM, $oldaction);
340
+ die "$Prog: ERROR: MySQL failure: $@" if $@;
341
+ }
342
+
343
+ sub xfs_freeze {
344
+ my ($xfs_filesystem) = @_;
345
+
346
+ run_command(['xfs_freeze', '-f', $xfs_filesystem]);
347
+ }
348
+
349
+ sub xfs_thaw {
350
+ my ($xfs_filesystem) = @_;
351
+
352
+ run_command(['xfs_freeze', '-u', $xfs_filesystem]);
353
+ }
354
+
355
+ # mysql defaults-file
356
+ sub read_mydefaultsfile {
357
+ my ($mysql_defaults_file) = @_;
358
+
359
+ open(MYCNF, $mysql_defaults_file)
360
+ or die "$Prog: ERROR: Couldn't open defaults-file $mysql_defaults_file\n";
361
+ while ( defined (my $line = <MYCNF>) ) {
362
+ $mysql_host = $1 if $line =~ m%^\s*host\s*=\s*"?(\S+?)"?$%
363
+ and not defined $mysql_host;
364
+ $mysql_username = $1 if $line =~ m%^\s*user\s*=\s*"?(\S+?)"?$%
365
+ and not defined $mysql_username;
366
+ $mysql_password = $1 if $line =~ m%^\s*password\s*=\s*"?(\S+?)"?$%
367
+ and not defined $mysql_password;
368
+ }
369
+ close(MYCNF);
370
+
371
+ return ($mysql_host, $mysql_username, $mysql_password);
372
+ }
373
+
374
+ # Look for the MySQL username/password in $HOME/.my.cnf
375
+ sub read_mycnf {
376
+ my ($mysql_host, $mysql_username, $mysql_password) = @_;
377
+
378
+ open(MYCNF, "$ENV{HOME}/.my.cnf")
379
+ or return($mysql_host, $mysql_username, $mysql_password);
380
+ while ( defined (my $line = <MYCNF>) ) {
381
+ $mysql_host = $1 if $line =~ m%^\s*host\s*=\s*"?(\S+?)"?$%
382
+ and not defined $mysql_host;
383
+ $mysql_username = $1 if $line =~ m%^\s*user\s*=\s*"?(\S+?)"?$%
384
+ and not defined $mysql_username;
385
+ $mysql_password = $1 if $line =~ m%^\s*password\s*=\s*"?(\S+?)"?$%
386
+ and not defined $mysql_password;
387
+ }
388
+ close(MYCNF);
389
+
390
+ return ($mysql_host, $mysql_username, $mysql_password);
391
+ }
392
+
393
+
394
+ # Figure out which AWS access keys to use
395
+ sub determine_access_keys {
396
+ my ($aws_access_key_id, $aws_secret_access_key,
397
+ $aws_access_key_id_file, $aws_secret_access_key_file,
398
+ ) = @_;
399
+
400
+ # 1. --aws-access-key-id and --aws-secret-access-key
401
+ return ($aws_access_key_id, $aws_secret_access_key)
402
+ if $aws_access_key_id;
403
+
404
+ # 2. --aws-access-key-id-file and --aws-secret-access-key-file
405
+ if ( $aws_access_key_id_file ) {
406
+ die "$Prog: Please provide both --aws-access-key-id-file and --aws-secret-access-key-file"
407
+ unless $aws_secret_access_key_file;
408
+ $aws_access_key_id = File::Slurp::read_file($aws_access_key_id_file);
409
+ $aws_secret_access_key= File::Slurp::read_file($aws_secret_access_key_file);
410
+ chomp($aws_access_key_id);
411
+ chomp($aws_secret_access_key);
412
+ return ($aws_access_key_id, $aws_secret_access_key);
413
+ }
414
+
415
+ # 3. $HOME/.awssecret
416
+ return read_awssecret();
417
+ }
418
+
419
+
420
+ # Look for the access keys in ~/.awssecret
421
+ sub read_awssecret {
422
+ my $filename = "$ENV{HOME}/.awssecret";
423
+ my ($aws_access_key_id, $aws_secret_access_key);
424
+ eval {
425
+ ($aws_access_key_id, $aws_secret_access_key) =
426
+ File::Slurp::read_file($filename);
427
+ chomp $aws_access_key_id;
428
+ chomp $aws_secret_access_key;
429
+ };
430
+ return ($aws_access_key_id, $aws_secret_access_key);
431
+ }
432
+
433
+ # Run a system command, warning if there are problems.
434
+ # Pass in reference to an array of command args.
435
+ sub run_command {
436
+ my ($command) = @_;
437
+
438
+ $Debug and warn "$Prog: ", scalar localtime, ": @$command\n";
439
+ return if $Noaction;
440
+ eval {
441
+ system(@$command) and die "failed($?)\n";
442
+ };
443
+ warn "$Prog: ERROR: @$command: $@" if $@;
444
+ }
445
+
446
+
447
+ =head1 NAME
448
+
449
+ ec2-consistent-snapshot - Create snapshot, locking db and freezing file system
450
+
451
+ =head1 SYNOPSIS
452
+
453
+ ec2-consistent-snapshot [opts] VOLUMEID...
454
+
455
+ =head1 OPTIONS
456
+
457
+ -h --help Print help and exit.
458
+ -d --debug Debug mode.
459
+ -q --quiet Quiet mode.
460
+ -n --noaction Don't do it. Just say what you would have done.
461
+
462
+ --aws-access-key-id KEY
463
+ --aws-secret-access-key SECRET
464
+
465
+ Amazon AWS access key and secret access key. Defaults to
466
+ environment variables or .awssecret file contents described below.
467
+
468
+ --aws-access-key-id-file KEYFILE
469
+ --aws-secret-access-key-file SECRETFILE
470
+
471
+ Files containing Amazon AWS access key and secret access key.
472
+ Defaults to environment variables or .awssecret file contents
473
+ described below.
474
+
475
+ --region REGION
476
+
477
+ Specify a different region like "eu-west-1". Defaults to
478
+ "us-east-1".
479
+
480
+ --description DESCRIPTION
481
+
482
+ Specify a description string for the EBS snapshot. Defaults to the
483
+ name of the program.
484
+
485
+ --xfs-filesystem MOUNTPOINT
486
+
487
+ Indicates that the volume contains an XFS file system at the specified
488
+ mount point, which will be flushed and frozen during the snapshot.
489
+
490
+ --mysql
491
+
492
+ Indicates that the volume contains data files for a running MySQL
493
+ database, which will be flushed and locked during the snapshot.
494
+
495
+ --mysql-defaults-file FILE
496
+
497
+ MySQL defaults file, containing host, username and password, this
498
+ option will ignore the --mysql-host, --mysql-username,
499
+ --mysql-password parameters
500
+
501
+ --mysql-host HOST
502
+ --mysql-username USER
503
+ --mysql-password PASS
504
+
505
+ MySQL host, username, and password used to flush logs and lock
506
+ tables. User must have appropriate permissions. Defaults to
507
+ $HOME/.my.cnf file contents.
508
+
509
+ --mysql-master-status-file FILE
510
+
511
+ Store the MASTER STATUS output in a file on the snapshot. It will
512
+ be removed after the EBS snapshot is taken. This option will be
513
+ ignored with --mysql-stop
514
+
515
+ --mysql-stop
516
+
517
+ Indicates that the volume contains data files for a running MySQL
518
+ database. The database is shutdown before the snapshot is initiated
519
+ and restarted afterwards. [EXPERIMENTAL]
520
+
521
+ --snapshot-timeout SECONDS
522
+
523
+ How many seconds to wait for the snapshot-create to return.
524
+ Defaults to 10.0
525
+
526
+ --lock-timeout SECONDS
527
+
528
+ How many seconds to wait for a database lock. Defaults to 0.5.
529
+ Making this too large can force other processes to wait while this
530
+ process waits for a lock. Better to make it small and try lots of
531
+ times.
532
+
533
+ --lock-tries COUNT
534
+
535
+ How many times to try to get a database lock before failing.
536
+ Defaults to 60.
537
+
538
+ --lock-sleep SECONDS
539
+
540
+ How many seconds to sleep between database lock tries. Defaults
541
+ to 5.0.
542
+
543
+ =head1 ARGUMENTS
544
+
545
+ VOLUMEID EBS volume id(s) for which to create a snapshot.
546
+
547
+ =head1 DESCRIPTION
548
+
549
+ This program creates an EBS snapshot for an Amazon EC2 EBS volume. To
550
+ help ensure consistent data in the snapshot, it tries to flush and
551
+ freeze the file system first as well as flushing and locking the
552
+ database, if applicable.
553
+
554
+ There are a number of timeouts to reduce the risk of interfering with
555
+ the normal database operation while improving the chances of getting a
556
+ consistent snapshot.
557
+
558
+ If you have multiple EBS volumes in a RAID configuration, you can
559
+ specify all of the volume ids on the command line and it will create
560
+ snapshots for each while the filesystem and database are locked. Note
561
+ that it is your responsibility to keep track of the resulting snapshot
562
+ ids and to figure out how to put these back together when you need to
563
+ restore the RAID setup.
564
+
565
+ If you have multiple EBS volumes which are hosting different file
566
+ systems, it might be better to simply run the command once for each
567
+ volume id.
568
+
569
+ =head1 EXAMPLES
570
+
571
+ Snapshot a volume which has a MySQL database on XFS under /vol:
572
+
573
+ ec2-consistent-snapshot --mysql --xfs-filesystem /vol vol-VOLUMEID
574
+
575
+ Snapshot a volume with XFS on /var/local but no MySQL database:
576
+
577
+ ec2-consistent-snapshot --xfs-filesystem /var/local vol-VOLUMEID
578
+
579
+ Snapshot four European volumes in a RAID configuration with MySQL on
580
+ XFS, saving the snapshots with a description marking the current time:
581
+
582
+ ec2-consistent-snapshot \
583
+ --mysql \
584
+ --xfs-filesystem /vol \
585
+ --region eu-west-1 \
586
+ --description "RAID snapshot $(date +'%Y-%m-%d %H:%M:%S')" \
587
+ vol-VOL1 vol-VOL2 vol-VOL3 vol-VOL4
588
+
589
+ =head1 ENVIRONMENT
590
+
591
+ $AWS_ACCESS_KEY_ID
592
+
593
+ Default value for access key
594
+ Can be overridden by command line options.
595
+
596
+ $AWS_SECRET_ACCESS_KEY
597
+
598
+ Default value for secret access key
599
+ Can be overridden by command line options.
600
+
601
+ =head1 FILES
602
+
603
+ $HOME/.my.cnf
604
+
605
+ Default values for MySQL user and password are sought here in the
606
+ standard format.
607
+
608
+ $HOME/.awssecret
609
+
610
+ Default values for access key and secret access keys are sought
611
+ here. Can be overridden by environment variables and command line
612
+ options.
613
+
614
+ =head1 INSTALLATION
615
+
616
+ On most Ubuntu releases, the B<ec2-consistent-snapshot> package can be
617
+ installed directly from the Alestic.com PPA using the following
618
+ commands:
619
+
620
+ code=$(lsb_release -cs)
621
+ echo "deb http://ppa.launchpad.net/alestic/ppa/ubuntu $code main"|
622
+ sudo tee /etc/apt/sources.list.d/alestic-ppa.list
623
+ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BE09C571
624
+ sudo apt-get update
625
+ sudo apt-get install -y ec2-consistent-snapshot
626
+
627
+ This program also requires the installation of the Net::Amazon::EC2
628
+ Perl package from CPAN. On an Ubuntu system this can be accomplished
629
+ with the following commands.:
630
+
631
+ sudo PERL_MM_USE_DEFAULT=1 cpan Net::Amazon::EC2
632
+
633
+ On Ubuntu 8.04 Hardy, use the following commands instead:
634
+
635
+ sudo apt-get install -y build-essential
636
+ sudo cpan Net::Amazon::EC2
637
+
638
+ The default values can be accepted for most of the prompts, though it
639
+ is necessary to select a CPAN mirror on Hardy.
640
+
641
+ =head1 SEE ALSO
642
+
643
+ Amazon EC2
644
+ Amazon EC2 EBS (Elastic Block Store)
645
+ ec2-create-snapshot
646
+
647
+ =head1 CAVEATS
648
+
649
+ This program currently supports the MySQL database and the XFS file
650
+ system. Patches are welcomed for other databases and file systems.
651
+
652
+ =head1 BUGS
653
+
654
+ Please report bugs at https://bugs.launchpad.net/ec2-consistent-snapshot
655
+
656
+ =head1 CREDITS
657
+
658
+ Thanks to the following for performing tests on early versions,
659
+ providing feedback, and patches:
660
+
661
+ David Erickson
662
+ Steve Caldwell
663
+ Gryp
664
+
665
+ =head1 AUTHOR
666
+
667
+ Eric Hammond <ehammond@thinksome.com>
668
+
669
+ =head1 LICENSE
670
+
671
+ Copyright (C) 2009 Eric Hammond <ehammond@thinksome.com>
672
+
673
+ Licensed under the Apache License, Version 2.0, see
674
+ http://www.apache.org/licenses/LICENSE-2.0
675
+
676
+ =cut