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;
|