Subversion Repositories sysadmin_scripts

Rev

Blame | Last modification | View Log | Download | RSS feed

#! /usr/bin/env perl

use strict;
use warnings;

use Data::Dumper;
use Time::Local qw( timelocal_posix timegm_posix );
use Getopt::Long;
Getopt::Long::Configure ("bundling");

use constant SECONDS_IN_DAY => 86400;

my $config = {};

# runs a command, redirecting stderr to stdout (which it ignores)
# then returns 0 and $output on success.
# if error, returns error code and string describing error
sub run {
   my $command = shift;
   my $output = qx/$command 2>&1/;
   if ($? == -1) {
      return (-1,"failed to execute: $!");
   } elsif ($? & 127) {
      return ($?, sprintf "child died with signal %d, %s coredump",
        ($? & 127),  ($? & 128) ? 'with' : 'without' );
   } else {
      return ($? >> 8, sprintf "child exited with value %d", $? >> 8 ) if $? >> 8;
   }
   return (0,$output);
}

sub dateToUnixTime {
   my ( $date, $time ) = @_;
   my ($year,$month,$day) = split( /\D/, $date );
   my ($hour,$minute,$second) = split( /\D/, $time );
   $second = '00' unless $second;
   return timelocal_posix( $second,$minute,$hour,$day,$month-1,$year-1900);
}

sub getSnaps {
   my $config = shift;
   my %snaps;
   my ($error,$output) = &run( 
      # 'H' uses a single tab between fields
      "zfs list -H" . 
      # if they asked for recursion, add the 'r' flag
      ( $config->{'recurse'} ? 'r' : '' ) . 
      # type snap, and the dataset name
      "t snap $config->{dataset}" );
#   die $output;
   my @snaps = split( "\n", $output );
   chomp @snaps;
   while ( my $thisSnap = pop @snaps ) {
      my ($name,$used,$avail,$refer,$mountpoint) = split( "\t", $thisSnap );
      if ( $name =~ m/$config->{'filter'}/ ) {
         my $created = &dateToUnixTime( $1,$2 );
         $snaps{$name}->{'date'} = $created if ( $created < $config->{'pruneBefore'} );
      }
   }
   return \%snaps;
}

sub makeDestroyCommands {
   my @commands;
   
#   die Dumper( @_ ) . "\n";

   while ( my $snap = shift ) {
      push @commands, "zfs destroy $snap";
   }
   return \@commands;
}

sub help {
   my $message = shift;
   print "\n== $message ==\n\n" if $message;
   my $help =  <<"      EOF";
      usage: $0 [options] dataset
         --dataset - Dataset to process. May also be defined with no flags
                     REQUIRED
         --recurse - If set, will recurse through child datasets
                     default: do not recurse
         --ttl     - Time To Live, in days. Anything older will be destroyed
                     default: 90 days
         --filter  - Perl Regular Expression to match snapshots. Non-match are
                     ignored
                     default: matches YYYY.MM.DD.HH.MM where . is any character
         --dryrun  - Do not actually do the destroy. -n is short version
         --verbose - Be verbose (only one level)
   
      Simple script to prune old snapshots. Snapshots must have date/time
      stamp, which must be surrounded by parenthesis in filter.

      Examples:
      $0 storage
         destroy all snapshots older than 90 days (default) in dataset storage

      $0 -r -d 'storage'
         destroy all snapshots older than 90 days in dataset storage and all
         child datasets
   
      $0  -rvt 120 -f 'weekly_(\\d{4}-\\d{2}-\\d{2})_(\\d{2}-\\d{2})' storage
         destroy all snapshots older and 120 days from storage/ and all
         children which have the form weekly_YYYY-MM-DD_HH-MM
      EOF
   $help =~ s/^ {6}//gm;
   print $help;

   exit();
}

# set some defaults
# default time to live, in days. Everything older will be destroyed
$config->{'ttl'} = 90;
# regex to match snapshot names. Default is just YYYY?MM?DD?HH?MM, where ? is aany delimiter
$config->{'filter'} = '(\d{4}.\d{2}.\d{2}).(\d{2}.\d{2})';

GetOptions( $config,
   'dataset|d=s',
   'ttl|t=i',
   'filter|f=s',
   'dryrun|n',
   'recurse|r',
   'verbose|v',
   'help|h'
);

$config->{'dataset'} = @ARGV ? shift : '' unless $config->{'dataset'};

#print Dumper( $config );

&help() if $config->{'help'};
&help( 'No dataset defined')  unless $config->{'dataset'};

$config->{'now'} = time;
printf "Starting prune of ZFS Snapshots at %s\n", scalar localtime( $config->{'now'} ) if $config->{'verbose'};
$config->{'pruneBefore'} = $config->{'now'} - $config->{'ttl'} * SECONDS_IN_DAY;
printf "Pruning before %s \n", scalar localtime($config->{'pruneBefore'}) if $config->{'verbose'};

$config->{'snaps'} = &getSnaps( $config );

#die Dumper( $config->{'snaps'} ) . "\n";

my $commands = &makeDestroyCommands( sort keys %{ $config->{'snaps'} } );

#die Dumper( $commands ) . "\n";

if ( @$commands ) {
   for ( my $i=0; $i < @$commands; $i++ ) {
      print $commands->[$i] . "\n" if $config->{'dryrun'}  || $config->{'verbose'};
      next if $config->{'dryrun'};
      #my ($error,$output) = &run ($commands->[$i]);
      #print "Error running command\n$commands->[$i]\n$output\n" if ( $error );
   }
} else {
   print "No snapshots found to destroy\n" if $config->{'verbose'};
}

printf "Prune completed at %s\n", scalar localtime( time ) if $config->{'verbose'};   

1;

Generated by GNU Enscript 1.6.5.90.