File Coverage

blib/lib/DBA/Backup.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package DBA::Backup;
2              
3              
4             =head1 NAME
5              
6             DBA::Backup - Core module for managing automated database backups.
7              
8             =head1 SYNOPSIS
9              
10             NOTICE! This is currently a broken partial port from the origal working
11             MySQL specific module. I hope to have the port finished and a functional
12             version uploaded soon. Email me or the list for more information.
13              
14             The mailing list for the DBA modules is perl-dba@fini.net. See
15             http://lists.fini.net/mailman/listinfo/perl-dba to subscribe.
16              
17             use DBA::Backup;
18            
19             my $dba = new DBA::Backup(%params);
20             die "Can't initiate backups: $dba" unless ref $dba;
21            
22             $dba->run(%conf_overides);
23             $dba->log_messages();
24             $dba->send_email_notification();
25              
26             =begin dev
27              
28             Required methods (can be void if appropriate):
29             _flush_logs # so all logs are current
30             _rotate_logs # rotate specified log types
31             _dump_databases # as server optimized SQL file
32             _stop_server
33             _start_server
34             _lock_database # (s)?
35              
36              
37             =end dev
38              
39             =cut
40              
41 1     1   53538 use 5.006001;
  1         5  
  1         51  
42 1     1   5 use strict;
  1         2  
  1         41  
43 1     1   4 use warnings;
  1         15  
  1         50  
44              
45 1     1   2396 use Sys::Hostname; # provides hostname()
  1         3834  
  1         225  
46 1     1   6791 use File::Copy qw/move/;
  1         12751  
  1         98  
47 1     1   10 use File::Path qw/rmtree/;
  1         3  
  1         65  
48 1     1   863 use Mail::Sendmail; # for sending mail notifications
  0            
  0            
49             use YAML qw(LoadFile); # config file processing
50              
51              
52             our $VERSION = '0.2_1';
53              
54             # prevent this module from granting any privilege to all (other users)
55             umask(0117);
56              
57             =head1 new()
58              
59             Create new DBA::Backup object. Use this object to initiate backups.
60              
61             OPTIONS:
62              
63             CONF_FILE: Location of configuration file to use. Default is
64             /etc/dba-backup.yml. Please keep in mind that conf files for any specific
65             servers to be backup will need to be in the same location.
66              
67             LOG_FILE: Location to write process log file.
68              
69             BACKUP: If true will force full database backups.
70              
71             ADD_DATABASES: Specify additional databases to be backed up. ** broken
72              
73             =cut
74              
75             sub new {
76             my $class = shift;
77             my %params = ref($_[0]) eq 'HASH' ? %{$_[0]} : @_;
78             $params{CONF_FILE} ||= '/etc/dba-backup.yml';
79            
80             # exits with usage statement if the config file is not valid
81             _is_config_file_valid($params{CONF_FILE}) or usage($params{CONF_FILE});
82            
83             # Read the YAML formatted configuration file
84             my $HR_conf = LoadFile($params{CONF_FILE})
85             or return ("Problem reading conf file $params{CONF_FILE}");
86            
87             # now lets modify for certain passed parameters
88             $HR_conf->{LOG_FILE} = $params{LOG_FILE} if $params{LOG_FILE};
89             my $cur_day = substr(localtime,0,3);
90             $HR_conf->{backup}{days} = $cur_day if $params{BACKUP};
91             if ($params{ADD_DATABASES}) {
92             my $AR_dbs = $HR_conf->{backup}{databases};
93             foreach my $db (split(/ ?, ?/,$params{ADD_DATABASES})) {
94             push(@{$AR_dbs},$db) unless grep (/$db/, @{$AR_dbs});
95             } # for each db to add
96             } # if backing up additional databases
97            
98             # this opens the log file for writing, turns off the buffer and redirects
99             # STDERR to the log
100             $HR_conf->{LOG} = _open_log_file($HR_conf->{LOG_FILE});
101            
102             # Stores the name of the current config file in the object
103             $HR_conf->{backup_params}{CONF_FILE} = $params{CONF_FILE};
104             $HR_conf->{HOSTNAME} = Sys::Hostname::hostname();
105            
106             # list of servers to back up
107             my @servers = grep(/^server_/, keys %{$HR_conf});
108             $HR_conf->{backup_servers} = \@servers;
109             # generate list of unique server types
110             my %extensions = ();
111             foreach my $server (@servers) {
112             my $type = lc($HR_conf->{$server}{server_type});
113             $extensions{$type} = 1;
114             } # foreach server
115            
116             # load extension for each server type
117             foreach my $ext (keys %extensions) {
118             my $mod = "DBA/Backup/$ext.pm";
119             require $mod && import $mod;
120             push(@ISA,"DBA::Backup::$ext");
121             } # foreach class to load
122            
123             # Yes, I'm currently being very lazy and using the configuration
124             # hash as self and letting my methods access directly.
125             return bless $HR_conf, $class;
126             } # end new()
127              
128              
129             sub _open_log_file {
130             my $logfile = shift;
131            
132             open(my $LOG,"> $logfile") or die "Can't write logfile $logfile: $!";
133            
134             # sig warn and die handlers to write errors to log before sending to STDERR
135             sub catch_sig {
136             my $signame = shift;
137             my $msg = shift;
138             print $LOG "$signame: $msg\n";
139             die "$signame: $msg\n";
140             } # catch_sig
141            
142             $SIG{INT} = \&catch_sig;
143             $SIG{QUIT} = \&catch_sig;
144             $SIG{warn} = \&catch_sig;
145             $SIG{die} = \&catch_sig;
146            
147             $self->_debug( "Testing sig handlers");
148            
149             $|++;
150             return $LOG;
151             } # _open_log_file
152              
153             =head1 usage()
154              
155             Prints an usage message for the program on the screen and then exits.
156              
157             =cut
158              
159             sub usage {
160             my $file = shift;
161             die "Usage: $0 /path/backup.cnf\n"
162             . "Please make sure you specified a config file with proper format."
163             . "$file provided.\n";
164             }
165              
166              
167             sub _is_config_file_valid {
168             my $file = shift;
169             return 0 unless $file;
170            
171             # if file name does not start with ./ or /, then append ./
172             # unless ($file =~ m{^[./]} ) {
173             # $_[0] = "./$_[0]";
174             # }
175            
176             unless (-f $file && -r $file) {
177             return 0;
178             } # unless file exists and is reabable
179            
180             return 1;
181             }
182              
183              
184             ##
185             ## The above section sets up and handles configuration parsing
186             ## The section below actually manages the backups
187             ##
188              
189              
190             =head1 run()
191              
192             This is where most of the work in the program is done.
193             It logs some messages to the log file and invokes the subroutines
194             for database backup and log backup and rotation.
195              
196             =cut
197              
198             sub run {
199             my $self = shift;
200             my $time = localtime();
201            
202             my $conf_file = $self->{backup_params}{CONF_FILE};
203             $self->_debug("Starting log");
204            
205             # Greeting message
206             print $self->{LOG} <
207            
208             *** DBA::Backup process started [$time] ***
209             Backup config file: $conf_file
210              
211             LOG
212            
213             # manage backups for each server
214             foreach my $server (@{$HR_conf->{backup_servers}}) {
215             # set server params to global vals if undefined
216             foreach my $param (keys %{$self->{backup_params}}) {
217             $self->_debug("Setting $server $param to default if value false",3);
218             $self->{$server}{backup_params}{$param}
219             ||= $self->{backup_params}{$param};
220             } # for each backup param global
221            
222             my $server_type = $self->{$server}{server_type};
223             my $host = $self->{$server}{connect}{RDBMS_HOST};
224             my $log_dir = $self->{$server}{backup_params}{LOG_DIR};
225             my $dump_dir = $self->{$server}{backup_params}{DUMP_DIR}
226             || $self->{backup_params}{DUMP_DIR};
227             my $dump_copies = $self->{$server}{backup_params}{DUMP_COPIES}
228             || $self->{backup_params}{DUMP_COPIES};
229             $time = localtime();
230            
231             # Greeting message
232             print $self->{LOG} <
233            
234             $server backup parameters:
235             RDBMS: $server_type
236             Host: $host
237             Log dir: $log_dir
238             Dump dir: $dump_dir
239             Dumps to keep: $dump_copies
240            
241             *Tidying up dump dirs*
242             LOG
243            
244             # the only arg usually passed to extension?
245             my $HR_server_conf = $self->{$server};
246            
247             $self->_debug( "_tidy_dump_dirs");
248             # clean up dump dirs
249             $self->_tidy_dump_dirs();
250            
251             # check the disk space on the dump directory
252             # for now, use the unix df command, later use perl equivalent
253             my $disk_usage = `df '$dump_dir'`;
254             print $self->{LOG} "\n\n*Disk space report for $dump_dir*\n$disk_usage";
255            
256             ##
257             ## example server method call
258             ##
259            
260             my $flush_logs = "$server_flush_logs";
261             $self->$flush_logs($HR_server_conf);
262            
263             ##
264             ## Below here is unmodified from port
265             ##
266            
267            
268             # check the list of currently running mysql queries
269             print $self->{LOG} "\n\n*Current processlist*\n";
270             $self->_debug( "_get_process_list");
271             print $self->{LOG} $self->_get_process_list();
272            
273             # rotate the logs. most text log files need to be renamed manually
274             # before the "flush logs" command is issued to mysqld
275             # we rotate logs daily (well, every time script is run)
276             print $self->{LOG} "\n\n*Rotating logs*\n";
277             $self->_debug("_rotate_general_query_log");
278             $self->_rotate_general_query_log();
279             $self->_debug("_rotate_slow_query_log");
280             $self->_rotate_slow_query_log();
281             $self->_debug("_cycle_bin_logs");
282             $self->_cycle_bin_logs();
283             $self->_debug("_rotate_error_log");
284             $self->_rotate_error_log();
285            
286             # Backup the databases only if today is the day which is
287             # specified in the config file
288             my $cur_day = substr(localtime,0,3);
289             my @backup_days = split(/ ?, ?/,$self->{backup}{days});
290            
291             if (grep(/$cur_day/i, @backup_days)) {
292             print $self->{LOG} "\n\n*Starting dumps*\n";
293             $self->_debug( '_backup_databases');
294             my $b_errs = $self->_backup_databases();
295            
296             # if there were no problems backing up dbs, rotate the dump dirs
297             if (not $b_errs) {
298             print $self->{LOG} "\n\n*Rotating dump dirs*\n";
299             $self->_debug( '_rotate_dump_dirs');
300             $self->_rotate_dump_dirs();
301             } # if no backup errors
302             } # if today is a database backup day
303            
304             } # for each server to backup
305            
306             # Goodbye message
307             $time = localtime();
308             print $self->{LOG} "\n\n*** DBA::Backup finished [$time] ***\n\n";
309            
310             } # end run()
311              
312              
313             =head1 _test_create_dirs
314              
315             Test for the existence and writeability of specified directories.
316             If the directories do not exist, attempt to create them. If unable
317             to create writeable directories, fail with error.
318              
319             =cut
320              
321             sub _test_create_dirs {
322             my $self = shift;
323            
324             # check the existence and proper permissions on all dirs
325             foreach my $dir (@_) {
326             # if it doesn't exist, create it
327             unless (-d $dir) {
328             print $self->{LOG} "Directory $dir does not exist, creating it...\n";
329             my $mask = umask;
330             umask(0007);
331             unless (mkdir($dir) and -d $dir) {
332             $self->error("Cannot create $dir.\n");
333             } # unless dir created
334             umask($mask);
335             } # if directory doesn't exist
336             # check that we can write to it
337             unless (-w $dir) {
338             $self->error("Directory $dir is not writable by the user running $0\n");
339             } # if directory isn't writable
340             } # foreach directory to be tested
341              
342             } # end _test_create_dirs()
343              
344              
345             =head1 _rotate_dump_dirs()
346              
347             The dump directories contain output from both the full, weekly mysql
348             dump as well as the incremental binary update logs that follow the
349             dump (possibly multiple binlogs per day). Rotate these directory
350             names to conform to convention:
351              
352             [dump_dir]/00/ - most recent dump
353             [dump_dir]/01/ - next most recent
354             ...
355             [dump_dir]/_NN/ - oldest
356              
357             Where N is [dump_copies] - 1 (in the config file). [dump_dir]/new/
358             is a temporary directory created from _backup_databases. This will
359             be renamed 00/, 00/ will be renamed 01/, and so on.
360              
361             =cut
362              
363             sub _rotate_dump_dirs {
364             my $self = shift;
365            
366             my $dump_root = $self->{backup_params}{DUMP_DIR};
367             my $max_old = $self->{backup_params}{DUMP_COPIES} -1;
368             $max_old = 0 if $max_old < 0;
369              
370             # grab list of files/dirs within dump_root - @dump_root_files
371             # create hash of dirs we care about w/in dump_root - %dump_root_hash
372             opendir(DIR, $dump_root)
373             or $self->error("Cannot get listing of files in $dump_root\n");
374             my @dump_dirs = grep {-d "$dump_root/$_"} readdir(DIR);
375             closedir(DIR);
376             my %dump_root_hash = ();
377             foreach my $dir (@dump_dirs) {
378             $dump_root_hash{$dir} = 1 if $dir =~ /^\d+$/;
379             $dump_root_hash{$dir} = 1 if $dir =~ /^00$/;
380             $dump_root_hash{$dir} = 1 if $dir =~ /^new$/;
381             } # for each dump root file
382              
383             # prepare instructions on how to rename directories, and in what order
384             # do not rename a dir unless it "needs" to be renamed
385            
386             # this seems wicked kludgy, but I want to understand reasoning before I
387             # throw away or improve - spq
388             my @dir_order;
389             my %dir_map;
390             if (exists $dump_root_hash{'new'}) {
391             push @dir_order, 'new';
392             $dir_map{'new'} = '00';
393            
394             if (exists $dump_root_hash{'00'}) {
395             push @dir_order, '00';
396             $dir_map{'00'} = '01';
397            
398             if ($max_old) {
399             foreach my $idx (1 .. $max_old) {
400             my $name = sprintf("%02d", $idx);
401             last unless exists $dump_root_hash{$name};
402             push @dir_order, $name;
403             $dir_map{$name} = sprintf("%02d", $idx+1);
404             } # foreach archival iteration
405             } # if we're keeping archival copies
406             } # if there is a current directory as well
407             } # if there is a new directory
408            
409             if (@dir_order) {
410             print $self->{LOG} "The following dump dirs will be renamed: "
411             . join(", ", @dir_order) . ".\n";
412             } # if there are directories to rename
413             else {
414             print $self->{LOG} "No dump dirs will be renamed.\n";
415             } # else note there are none
416              
417             # rotate names of the dump dirs we want to keep
418             foreach my $old_dname (reverse @dir_order) {
419             my $new_dname = $dir_map{$old_dname};
420             next if $old_dname eq $new_dname;
421             next unless (-d "$dump_root/$old_dname");
422             next if (-f "$dump_root/$new_dname");
423             print $self->{LOG} "Renaming dump dir $old_dname/ to $new_dname/ in $dump_root/ ...\n";
424             File::Copy::move("$dump_root/$old_dname", "$dump_root/$new_dname")
425             or $self->error("Cannot rename $old_dname/ to $new_dname/\n");
426             } # for each directory to rename
427              
428             # delete oldest dump dir if it exceeds the specified number of copies
429             # can't this just be made a delete above if last or such?
430             my $oldest_dir = $dump_root . '/' . sprintf("%02d", $max_old+1);
431             if (-d $oldest_dir) {
432             print $self->{LOG} "Deleting $oldest_dir/ ...\n";
433             eval { File::Path::rmtree($oldest_dir) };
434             $self->error("Cannot delete $oldest_dir/ - $@\n") if ($@);
435             $self->error("$oldest_dir/ deleted, but still exists!\n") if (-d $oldest_dir);
436             } # delete past-max oldest archive
437              
438             } # end _rotate_dump_dirs
439              
440              
441             =head1 _tidy_dump_dirs()
442              
443             The dump directories contain output from both the full, weekly mysql
444             dump as well as the incremental binary update logs that follow the
445             dump (possibly multiple binlogs per day). Sometimes a user might
446             delete a directory between backup runs (particularly if it has bad
447             dumps).
448              
449             This function is intended to be run before backups start. It will
450             Attempt to make directory names to conform to convention:
451              
452             [dump_dir]/00/ - most recent dump
453             [dump_dir]/01/ - next most recent
454             ...
455             [dump_dir]/NN/ - oldest
456              
457             If there are missing directories, _tidy_dump_dirs will create a
458             directory to take its place, such that 00/ should always exist
459             and there should be no gaps in the numbering of old directories. In
460             other words, N+1 should be the total number of directories in [dump_dir].
461              
462             If there are no gaps to begin with, _tidy_dump_dirs does not rename
463             anything.
464              
465             This function will also delete any xx directories that exceed the
466             [dump_copies] config variable.
467              
468             It will never touch [dump_dir]/new/. It will never modify the contents
469             of any of these subdirectories (unless its deleting the whole subdir).
470              
471             It will create [dump_dir] and [dump_dir]/00/ if they do not exist.
472              
473             =cut
474              
475             # is this routine doing redundant work with above?!?!
476             sub _tidy_dump_dirs {
477             my $self = shift;
478            
479             my $dump_copies = $self->{backup_params}{DUMP_COPIES};
480             my $dump_root = $self->{backup_params}{DUMP_DIR};
481             $self->_test_create_dirs($dump_root);
482            
483             # grab list of files/dirs within dump_root - @dump_root_files
484             # create hash of dirs we care about w/in dump_root - %dump_root_hash
485             opendir(DIR, $dump_root)
486             or $self->error("Cannot get listing of files in $dump_root\n");
487             my @dump_dirs = grep {-d "$dump_root/$_"} readdir(DIR);
488             closedir(DIR);
489             my %dump_root_hash;
490             foreach my $dump_dir (@dump_dirs) {
491             $dump_root_hash{$dump_dir} = 1 if $dump_dir =~ /^\d+$/;
492             $dump_root_hash{$dump_dir} = 1 if $dump_dir =~ /^00$/;
493             } # for each dump directory
494             # (the next line requires that [dump_copies] is <= 100) # huh?! why? - spq
495             my @dump_root_dirs = sort keys %dump_root_hash;
496            
497             # prepare instructions on how to rename directories, and in what order
498             # also prep instructions on which directories to delete (in case user
499             # has reduced [dump_copies] in the config file since the last time
500             # this script was run)
501             my %dir_map;
502             my @ren_queue;
503             my @del_queue;
504             my $idx=0;
505             foreach my $dir (@dump_root_dirs) {
506             if ($idx < $dump_copies) {
507             $dir_map{$dir} = sprintf("%02d", $idx);
508             push @ren_queue, $dir
509             unless ($dir eq $dir_map{$dir}) or ($dir eq '00');
510             } # if dump dir < max copies
511             else {
512             push @del_queue, $dir;
513             } # else prepare delete queue
514             $idx++;
515             } # foreach dump dir
516            
517             $dir_map{$dump_root_dirs[0]} = '00' if @dump_root_dirs;
518             print $self->{LOG} "The following dump dirs will be renamed: "
519             . join(", ", @ren_queue) . ".\n" if @ren_queue;
520             print $self->{LOG} "The following dump dirs will be deleted: "
521             . join(", ", @del_queue) . ".\n" if @del_queue;
522             print $self->{LOG} "Dump dirs look good, not much to tidy up\n"
523             if not @ren_queue and not @del_queue;
524            
525             # shuffle names of the dump dirs
526             foreach my $old_dname (@ren_queue) {
527             my $new_dname = $dir_map{$old_dname};
528             next if $old_dname eq $new_dname;
529             next unless (-d "$dump_root/$old_dname");
530             next if (-f "$dump_root/$new_dname");
531             print $self->{LOG} "Renaming dump dir $old_dname/ to $new_dname/ in "
532             . "$dump_root/ ...\n";
533             File::Copy::move("$dump_root/$old_dname", "$dump_root/$new_dname")
534             or $self->error("Cannot rename $old_dname/ to $new_dname/\n");
535             } # foreach old name?
536            
537             # delete excess dump dirs
538             foreach my $dname (@del_queue) {
539             $dname = "$dump_root/$dname";
540             print $self->{LOG} "Deleting $dname/ (exceeds dump_copies=$dump_copies) ...\n";
541             eval { File::Path::rmtree($dname) };
542             $self->error("Cannot delete $dname/ - $@\n") if ($@);
543             $self->error("$dname/ deleted, but still exists!\n") if (-d $dname);
544             } # for each excess dir to delete
545            
546             # if not @dump_root_files, create a 00/ dir
547             $self->_test_create_dirs("$dump_root/00");
548            
549             } # end _tidy_dump_dirs
550              
551              
552             =head1 send_email_notification()
553              
554             Sends the data from the 00 run of the program
555             which gets stored in the log file by email. The exact
556             behaviour for this subroutine is controlled by the
557             varibles in [mail-setup] section in the config file
558              
559             =cut
560              
561             sub send_email_notification {
562             my $self = shift;
563             $self->_debug("self? $self");
564            
565             # Email notifications can be turned off although this
566             # is usually not a good idea
567             my $notify = $self->{mail_setup}{mail_notification};
568             return unless $notify =~ /yes/i;
569            
570             # send LOG by mail
571             # get the varibles below from a config file
572             my $hostname = $self->{db_connect}{HOSTNAME};
573             my $subject = "MySQL dump log from $hostname at " . localtime;
574            
575             my $to = join(', ', @{$self->{mail_setup}{mail_to}});
576             my $cc = join(', ', @{$self->{mail_setup}{mail_cc}});
577            
578             sendmail(To => $to,
579             CC => $cc,
580             Subject => $subject,
581             From => $self->{mail_setup}{mail_from},
582             Message => (join '', ''), # we no longer store @LOG - what here?
583             Server => $self->{mail_setup}{mail_server},
584             delay => 1,
585             retries => 3 );
586              
587             } # end send_email_notification()
588              
589              
590             # Log debug messages. Supress die on _gripe at high debug by
591             # setting die to -1
592             sub _debug {
593             my $self = shift;
594             my $msg = shift;
595             my $level = shift || 1;
596            
597             $self->_gripe($msg,-1) if $self->{DEBUG} >= $level;
598             } # _debug
599              
600              
601             # uses gripes message controls, but instructs exit
602             sub _error {
603             my $self = shift;
604             $self->_gripe(shift,1);
605             } # _error
606              
607             # debug level modified warn replacement using Carp
608             sub _gripe {
609             my $self = shift;
610             my $msg = shift || confess("gripe without message");
611             my $die = shift || 0;
612            
613             my @call = caller;
614             @call = caller(1) if $die; # -1 from debug should be true here as well
615             my $pkg = $call[0];
616             $call[1] =~ s{.+/}{};
617             $msg = "$call[1]" . "[$call[2]]: $msg";
618            
619            
620             my $debug = $self->{DEBUG};
621             $die++ if $debug > 3;
622            
623             $self->log_messages();
624             $self->send_email_notification();
625             exit 1;
626            
627            
628             print $self->{LOG} $msg;
629            
630             if ($die && $debug) {
631             print $self->{LOG} "FATAL: $msg\n";
632             confess("$msg\n");
633             } # if we're dying and debug is on
634             elsif ($die) {
635             print $self->{LOG} "FATAL: $msg\n";
636             croak("$msg\n");
637             } # or die with just the message
638             elsif ($debug > 1) {
639             print $self->{LOG} "$msg\n";
640             cluck("$msg\n");
641             } # verbose warn
642             else {
643             print $self->{LOG} "$msg\n";
644             carp("$msg\n");
645             } # just let em know the basics
646             } # _gripe
647              
648              
649             1;
650              
651              
652             =head1 INSTALLATION
653              
654             To install this module type the following:
655              
656             perl Makefile.PL
657             make
658             make test
659             make install
660              
661             =head1 DEPENDENCIES
662              
663             This module requires these other modules and libraries:
664              
665             Mail::Sendmail # if you want email reports
666             YAML
667             Sys::Hostname
668             File::Copy
669             File::Path
670              
671              
672             =head1 DESCRIPTION
673              
674             Manages rotation of mysql database logs and database backups. Reads information
675             on which databases to back up on what days for the week from the configuration
676             file. If no file is specified, it will look for one at /etc/mysql-backup.conf.
677             If no configuration file is found program will exit with a failure.
678              
679             This program assumes a MySQL 4.0.x server that is at least 4.0.10.
680             It will likely work with current 1.23.xx server, but that has not been tested.
681             Please let the maintainers know if you use this tool succesfully with other
682             versions of MySQL or Perl so we can note what systems it works with.
683              
684             The expected usage of this program is for it to be run daily by a cron job,
685             at a time of day convienient to have the backups occur. This program uses the
686             administrative tools provided with MySQL (mysqladmin and mysqldump) as well
687             as gzip for compression of backups.
688              
689             Every time this program is run it will flush the MySQL logs. The binary update
690             log will be moved into /path/to/dump/dir/00. Error log and slow query log files
691             are rotated only if they exceeded the size limit specified in the confguration
692             file.
693              
694             If it is run on a day when full database backups are specified, then
695             all databases specified in the config file are dumped and
696             written to the directory specified in dump_dir variable in the config
697             file. If there are no problems with this operation, previous full backups
698             from dump_dir/00 are moved to directory dump_dir/01 and all the
699             files in dump_dir/01 (full database backups and log files) are deleted
700             from it or moved to dump_dir/02 etc. to the archival depth specified in the
701             config file. This way there always [dump_copies] full database backups -
702             one in 00/ and [dump_copies]-1 in the xx directories.
703              
704             Detailed information about the different configuration parameters
705             can be found in the comments in the configuration file
706              
707             log-slow-queries
708             log-long-format
709             log-bin # required
710             log-error # should be required?
711              
712             =head1 OPTIONS
713              
714              
715             =over 4
716              
717             =item B
718              
719             Filename for logging backup proceedure. Overrides conf file.
720              
721             =item B
722              
723             Additional databases to back up. These will be backed up I to any
724             databases specified in the conf file. B - this adds databases to the list
725             of those to be backed up. If the program is being run on a day when database
726             backups are not scheduled, the extra databases specified will B be backed
727             up.
728              
729             =item B
730              
731             If present this option forces full database backups to be done, even if not
732             normally scheduled.
733              
734             =item B
735              
736             Outputs this help file.
737              
738             =item B
739              
740             ** NOT IMPLIMENTED **
741              
742             Turn on debugging. Optionally takes a filename to store debugging and any error
743             messages to.
744              
745             =item B
746              
747             ** NOT IMPLIMENTED **
748              
749             Increases debugging vebosity. If three or more v's provided (-v -v -v)
750             than program will exit on warnings.
751              
752             =back
753              
754             =head1 TO DO
755              
756             Impliment debugging output options.
757              
758             Streamline config process - can we avoid using multiple config files?
759              
760             Support multiple servers of the same type.
761              
762             =head1 HISTORY
763              
764             =over 8
765              
766             =item 0.1
767              
768             Partial port from original MySQL specific version.
769              
770             =item 0.1.1
771              
772             Some more work on getting port working and some comments. Early release
773             for boston.pm mailing list.
774              
775             =item 0.1.2
776              
777             Improved configuration handling allowing multiple servers per RDBMS type.
778             Draft example of calling extension method and providing only server specific
779             conf hash explicitly instead of making extension find it.
780              
781             =back
782              
783              
784              
785             =head1 SEE ALSO
786              
787             The mailing list for the DBA modules is perl-dba@fini.net. See
788             http://lists.fini.net/mailman/listinfo/perl-dba to subscribe.
789              
790             dba-backup.yml
791             dba-backup
792              
793             =head1 AUTHOR
794              
795             Sean P. Quinlan, Egilant@gmail.comE
796              
797             =head1 COPYRIGHT AND LICENSE
798              
799             Copyright (C) 2004 by Sean P. Quinlan
800              
801             This library is free software; you can redistribute it and/or modify
802             it under the same terms as Perl itself, either Perl version 5.8.3 or,
803             at your option, any later version of Perl 5 you may have available.
804              
805             =cut
806