/usr/local/CPAN/NetApp/NetApp/Snapshot.pm
#
# $Id: Qtree.pm 275 2008-07-25 18:50:49Z pmoore $
#
package NetApp::Snapshot;
use version; $VERSION = version->new(qw$Revision: 275 $);
use strict;
use warnings;
use English;
use Carp;
use Class::Std;
use Params::Validate qw( :all );
use Regexp::Common;
use NetApp::Snapshot::Delta;
use NetApp::Snapshot::Schedule;
{
my %parent_of :ATTR( get => 'parent' );
my %name_of :ATTR( get => 'name' );
my %date_of :ATTR( get => 'date' );
my %used_of :ATTR( get => 'used' );
my %total_of :ATTR( get => 'total' );
sub BUILD {
my ($self,$ident,$args_ref) = @_;
my @args = %$args_ref;
my (%args) = validate( @args, {
parent => { type => OBJECT },
name => { type => SCALAR },
date => { type => SCALAR },
used => { type => SCALAR },
total => { type => SCALAR },
});
$parent_of{$ident} = $args{parent};
$name_of{$ident} = $args{name};
$date_of{$ident} = $args{date};
$used_of{$ident} = $args{used};
$total_of{$ident} = $args{total};
}
sub get_filer {
return shift->get_parent->get_filer;
}
sub get_snapshot_deltas {
my $self = shift;
return $self->_get_snapshot_deltas(
parent => $self->get_parent,
from => $self,
);
}
sub get_reclaimable {
my $self = shift;
if ( $self->get_parent->isa("NetApp::Aggregate") ) {
croak("Aggregate snapshots do not support 'snap reclaimable'\n");
}
$self->get_filer->_run_command(
command => [ qw( snap reclaimable ),
$self->get_parent->get_name,
$self->get_name ],
nonfatal => 1,
);
my @stdout = $self->get_filer->_get_command_stdout;
my @stderr = $self->get_filer->_get_command_stderr;
while ( defined (my $line = shift @stdout) ) {
if ( $line =~ /Approximately (\d+)/ ) {
return $1;
}
}
carp(
"Unable to determine reclaimable space for ",
$self->get_parent->get_name, ":",
$self->get_name, "\n",
@stderr,
);
return undef;
}
sub restore {
my $self = shift;
my (%args) = validate( @_, {
type => { type => SCALAR,
regexp => qr{^(vol|file)$},
default => 'vol',
optional => 1 },
from_path => { type => SCALAR,
optional => 1 },
to_path => { type => SCALAR,
optional => 1 },
});
if ( $args{type} eq 'file' && ! $args{from_path} ) {
croak(
"Missing required argment 'from_path'\n",
"File restores must specify the from_path\n",
);
}
if ( $args{type} eq 'vol' && $args{to_path} ) {
croak(
"Invalid argument 'to_path'\n",
"Volume restores can not specify to_path\n",
);
}
my @command = qw( snap restore );
if ( $self->get_parent->isa("NetApp::Aggregate" ) ) {
push @command, '-A';
}
if ( $args{to_path} ) {
push @command, '-r', $args{to_path};
}
push @command, (
qw( -f -s ), $self->get_name,
qw( -t ), $args{type},
);
if ( $args{type} eq 'vol' ) {
push @command, $self->get_parent->get_name;
} else {
push @command, $args{from_path};
}
return $self->get_filer->_run_command(
command => \@command,
);
}
sub rename {
my $self = shift;
my $ident = ident $self;
my ($newname) = validate_pos(
@_,
{ type => SCALAR },
);
my @command = qw( snap rename );
if ( $self->get_parent->isa("NetApp::Aggregate") ) {
push @command, '-A';
}
push @command, $self->get_name, $newname;
$self->_run_command(
command => \@command,
);
$name_of{$ident} = $newname;
return 1;
}
}
# NOTE: These are class methods, since you can request snapshots,
# deltas, etc from an aggregate, volume, or a specific snapshot.
sub _get_snapshots {
my $class = shift;
my (%args) = validate( @_, {
parent => { type => OBJECT },
});
my $parent = $args{parent};
my @command = qw( snap list );
if ( $parent->isa("NetApp::Aggregate") ) {
push @command, '-A';
}
push @command, $parent->get_name;
$parent->get_filer->_run_command(
command => \@command,
);
my @stdout = $parent->get_filer->_get_command_stdout;
my @snapshots = ();
while ( defined (my $line = shift @stdout) ) {
next if $line =~ /^(Volume|Aggregate)/;
next if $line =~ /^working/;
next if $line =~ /^\s*$/;
last if $line =~ /No snapshots exist/;
next if $line =~ m:^\s*%/used:;
next if $line =~ /^-+/;
my $snapshot = $class->_parse_snap_list( $line );
push @snapshots, NetApp::Snapshot->new({
parent => $parent,
%$snapshot,
});
}
return @snapshots;
}
sub _create_snapshot {
my $class = shift;
my (%args) = validate( @_, {
parent => { type => OBJECT },
name => { type => SCALAR },
});
my $parent = $args{parent};
my @command = qw( snap create );
if ( $parent->isa("NetApp::Aggregate") ) {
push @command, '-A';
}
push @command, $args{name};
return $parent->get_filer->_run_command(
command => \@command,
);
}
sub _delete_snapshot {
my $class = shift;
my (%args) = validate( @_, {
parent => { type => OBJECT },
name => { type => SCALAR },
});
my $parent = $args{parent};
my @command = qw( snap delete );
if ( $parent->isa("NetApp::Aggregate") ) {
push @command, '-A';
}
push @command, $args{name};
return $parent->get_filer->_run_command(
command => \@command,
);
}
sub _set_snapshot_schedule {
my $class = shift;
my (%args) = validate( @_, {
parent => { type => OBJECT },
weekly => { type => SCALAR },
daily => { type => SCALAR },
hourly => { type => SCALAR },
hourlist => { type => ARRAYREF,
optional => 1 },
});
my $parent = $args{parent};
my @command = qw( snap sched );
if ( $parent->isa("NetApp::Aggregate") ) {
push @command, '-A';
}
push @command, $parent->get_name, $args{weekly}, $args{daily};
my $hourly = $args{hourly};
if ( $args{hourlist} ) {
$hourly .= '@' . join( ',', @{ $args{hourlist} } );
}
push @command, $args{hourly};
return $parent->get_filer->_run_command(
command => \@command,
);
}
sub _get_snapshot_schedule {
my $class = shift;
my (%args) = validate( @_, {
parent => { type => OBJECT },
});
my $parent = $args{parent};
my @command = qw( snap sched );
if ( $parent->isa("NetApp::Aggregate") ) {
push @command, '-A';
}
push @command, $parent->get_name;
$parent->get_filer->_run_command(
command => \@command,
);
my @stdout = $parent->get_filer->_get_command_stdout;
my $schedule =
NetApp::Snapshot::Schedule->_parse_snap_sched( shift @stdout );
return NetApp::Snapshot::Schedule->new({
parent => $parent,
%$schedule,
});
}
sub _set_snapshot_reserved {
my $class = shift;
my (%args) = validate( @_, {
parent => { type => OBJECT },
reserved => { type => SCALAR },
});
my $parent = $args{parent};
my $reserved = $args{reserved};
my @command = qw( snap reserve );
if ( $parent->isa("NetApp::Aggregate") ) {
push @command, '-A';
}
push @command, $parent->get_name, $reserved;
return $parent->get_filer->_run_command(
command => \@command,
);
}
sub _get_snapshot_reserved {
my $class = shift;
my (%args) = validate( @_, {
parent => { type => OBJECT },
});
my $parent = $args{parent};
my $parent_class = ref $parent;
my @command = qw( snap reserve );
if ( $parent->isa("NetApp::Aggregate") ) {
push @command, '-A';
}
push @command, $parent->get_name;
$parent->get_filer->_run_command(
command => \@command,
);
my @stdout = $parent->get_filer->_get_command_stdout;
my $line = shift @stdout;
if ( $line =~ /reserve is (\d+)%/ ) {
return $1;
} else {
croak(
"Unable to determine snapshot reserve for $parent_class",
$parent->get_name, "\n",
);
}
}
sub _get_snapshot_deltas {
my $class = shift;
my (%args) = validate( @_, {
parent => { type => OBJECT },
from => { isa => 'NetApp::Snapshot',
optional => 1 },
to => { isa => 'NetApp::Snapshot',
depends => [qw( from )],
optional => 1 },
});
my $parent = $args{parent};
my @command = qw( snap delta );
if ( $parent->isa("NetApp::Aggregate") ) {
push @command, '-A';
}
push @command, $parent->get_name;
if ( $args{from} ) {
push @command, $args{from}->get_name;
}
if ( $args{to} ) {
push @command, $args{to}->get_name;
}
$parent->get_filer->_run_command(
command => \@command,
);
my @stdout = $parent->get_filer->_get_command_stdout;
my @deltas = ();
my $summary = 0;
while ( defined( my $line = shift @stdout) ) {
next if $line =~ /^\s*$/;
next if $line =~ /^(Volume|Aggregate|working|From)/;
next if $line =~ /^[-\s]+$/;
last if $line =~ /No snapshots exist/;
if ( $line =~ /^Summary/ ) {
$summary = 1;
next;
}
my $delta = NetApp::Snapshot::Delta->_parse_snap_delta( $line );
push @deltas, NetApp::Snapshot::Delta->new({
summary => $summary,
%$delta,
});
}
return @deltas;
}
sub _parse_snap_list {
my $class = shift;
my $line = shift;
$line =~ m{ ^ \s* \d+% \s+ \( \s*
(\d+)
% \) \s+ \d+% \s+ \( \s*
(\d+)
% \) \s+
( \w+ \s+ \d+ \s+ \d+ : \d+ )
\s+
(\S+) }x;
return {
used => $1,
total => $2,
date => $3,
name => $4,
};
}
1;