Subversion Repositories sysadmin_scripts

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
172 rodolico 1
#! /usr/bin/env perl
2
 
3
use strict;
4
use warnings;
5
 
6
use Data::Dumper;
7
use Time::Local qw( timelocal_posix timegm_posix );
8
use Getopt::Long;
9
Getopt::Long::Configure ("bundling");
10
 
11
use constant SECONDS_IN_DAY => 86400;
12
 
13
my $config = {};
14
 
15
# runs a command, redirecting stderr to stdout (which it ignores)
16
# then returns 0 and $output on success.
17
# if error, returns error code and string describing error
18
sub run {
19
   my $command = shift;
20
   my $output = qx/$command 2>&1/;
21
   if ($? == -1) {
22
      return (-1,"failed to execute: $!");
23
   } elsif ($? & 127) {
24
      return ($?, sprintf "child died with signal %d, %s coredump",
25
        ($? & 127),  ($? & 128) ? 'with' : 'without' );
26
   } else {
27
      return ($? >> 8, sprintf "child exited with value %d", $? >> 8 ) if $? >> 8;
28
   }
29
   return (0,$output);
30
}
31
 
32
sub dateToUnixTime {
33
   my ( $date, $time ) = @_;
34
   my ($year,$month,$day) = split( /\D/, $date );
35
   my ($hour,$minute,$second) = split( /\D/, $time );
36
   $second = '00' unless $second;
37
   return timelocal_posix( $second,$minute,$hour,$day,$month-1,$year-1900);
38
}
39
 
40
sub getSnaps {
41
   my $config = shift;
42
   my %snaps;
43
   my ($error,$output) = &run( 
44
      # 'H' uses a single tab between fields
45
      "zfs list -H" . 
46
      # if they asked for recursion, add the 'r' flag
47
      ( $config->{'recurse'} ? 'r' : '' ) . 
48
      # type snap, and the dataset name
49
      "t snap $config->{dataset}" );
50
#   die $output;
51
   my @snaps = split( "\n", $output );
52
   chomp @snaps;
53
   while ( my $thisSnap = pop @snaps ) {
54
      my ($name,$used,$avail,$refer,$mountpoint) = split( "\t", $thisSnap );
55
      if ( $name =~ m/$config->{'filter'}/ ) {
56
         my $created = &dateToUnixTime( $1,$2 );
57
         $snaps{$name}->{'date'} = $created if ( $created < $config->{'pruneBefore'} );
58
      }
59
   }
60
   return \%snaps;
61
}
62
 
63
sub makeDestroyCommands {
64
   my @commands;
65
 
66
#   die Dumper( @_ ) . "\n";
67
 
68
   while ( my $snap = shift ) {
69
      push @commands, "zfs destroy $snap";
70
   }
71
   return \@commands;
72
}
73
 
74
sub help {
75
   my $message = shift;
76
   print "\n== $message ==\n\n" if $message;
77
   my $help =  <<"      EOF";
78
      usage: $0 [options] dataset
79
         --dataset - Dataset to process. May also be defined with no flags
80
                     REQUIRED
81
         --recurse - If set, will recurse through child datasets
82
                     default: do not recurse
83
         --ttl     - Time To Live, in days. Anything older will be destroyed
84
                     default: 90 days
85
         --filter  - Perl Regular Expression to match snapshots. Non-match are
86
                     ignored
87
                     default: matches YYYY.MM.DD.HH.MM where . is any character
88
         --dryrun  - Do not actually do the destroy. -n is short version
89
         --verbose - Be verbose (only one level)
90
 
91
      Simple script to prune old snapshots. Snapshots must have date/time
92
      stamp, which must be surrounded by parenthesis in filter.
93
 
94
      Examples:
95
      $0 storage
96
         destroy all snapshots older than 90 days (default) in dataset storage
97
 
98
      $0 -r -d 'storage'
99
         destroy all snapshots older than 90 days in dataset storage and all
100
         child datasets
101
 
102
      $0  -rvt 120 -f 'weekly_(\\d{4}-\\d{2}-\\d{2})_(\\d{2}-\\d{2})' storage
103
         destroy all snapshots older and 120 days from storage/ and all
104
         children which have the form weekly_YYYY-MM-DD_HH-MM
105
      EOF
106
   $help =~ s/^ {6}//gm;
107
   print $help;
108
 
109
   exit();
110
}
111
 
112
# set some defaults
113
# default time to live, in days. Everything older will be destroyed
114
$config->{'ttl'} = 90;
115
# regex to match snapshot names. Default is just YYYY?MM?DD?HH?MM, where ? is aany delimiter
116
$config->{'filter'} = '(\d{4}.\d{2}.\d{2}).(\d{2}.\d{2})';
117
 
118
GetOptions( $config,
119
   'dataset|d=s',
120
   'ttl|t=i',
121
   'filter|f=s',
122
   'dryrun|n',
123
   'recurse|r',
124
   'verbose|v',
125
   'help|h'
126
);
127
 
128
$config->{'dataset'} = @ARGV ? shift : '' unless $config->{'dataset'};
129
 
130
#print Dumper( $config );
131
 
132
&help() if $config->{'help'};
133
&help( 'No dataset defined')  unless $config->{'dataset'};
134
 
135
$config->{'now'} = time;
136
printf "Starting prune of ZFS Snapshots at %s\n", scalar localtime( $config->{'now'} ) if $config->{'verbose'};
137
$config->{'pruneBefore'} = $config->{'now'} - $config->{'ttl'} * SECONDS_IN_DAY;
138
printf "Pruning before %s \n", scalar localtime($config->{'pruneBefore'}) if $config->{'verbose'};
139
 
140
$config->{'snaps'} = &getSnaps( $config );
141
 
142
#die Dumper( $config->{'snaps'} ) . "\n";
143
 
144
my $commands = &makeDestroyCommands( sort keys %{ $config->{'snaps'} } );
145
 
146
#die Dumper( $commands ) . "\n";
147
 
148
if ( @$commands ) {
149
   for ( my $i=0; $i < @$commands; $i++ ) {
150
      print $commands->[$i] . "\n" if $config->{'dryrun'}  || $config->{'verbose'};
151
      next if $config->{'dryrun'};
152
      #my ($error,$output) = &run ($commands->[$i]);
153
      #print "Error running command\n$commands->[$i]\n$output\n" if ( $error );
154
   }
155
} else {
156
   print "No snapshots found to destroy\n" if $config->{'verbose'};
157
}
158
 
159
printf "Prune completed at %s\n", scalar localtime( time ) if $config->{'verbose'};   
160
 
161
1;