#
# $Id: Volume.pm 419 2008-12-12 16:58:54Z pmoore $
#

package NetApp::Volume;

use version; $VERSION = version->new(qw$Revision: 419 $);

use strict;
use warnings;
use English;
use Carp;

use Class::Std;
use Params::Validate qw( :all );
use Regexp::Common;

use NetApp::Volume::Source;

{

    my %filer_of		:ATTR( get => 'filer' );

    my %name_of			:ATTR( get => 'name' );
    my %state_of		:ATTR;
    my %status_of		:ATTR;
    my %options_of		:ATTR;
    my %source_of		:ATTR( get => 'source' );

    my %plex_of			:ATTR( get => 'plex' );

    my %aggregate_name_of	:ATTR( get => 'aggregate_name' );

    my %clone_names_of		:ATTR;
    my %parent_name_of		:ATTR( get => 'parent_name' );
    my %snapshot_name_of 	:ATTR( get => 'snapshot_name' );

    my %path_of			:ATTR( get => 'path' );

    sub BUILD {

        my ($self,$ident,$args_ref) = @_;

        my @args = %$args_ref;

        my (%args) 	= validate( @args, {
            filer	=> { isa	=> 'NetApp::Filer' },
            name	=> { type	=> SCALAR },
            state	=> { type	=> HASHREF },
            status	=> { type	=> HASHREF },
            options	=> { type	=> HASHREF },
            plex	=> { type	=> HASHREF },
            aggregate_name => { type	=> SCALAR,
                                optional => 1 },
            source	=> { type	=> HASHREF,
                             optional	=> 1 },
            clone_names	=> { type	=> ARRAYREF,
                             default	=> [],
                             optional	=> 1 },
            parent_name	=> { type	=> SCALAR,
                             optional	=> 1 },
            snapshot_name => { type	=> SCALAR,
                               optional => 1 },
        });        

        $filer_of{$ident}	= $args{filer};
        $name_of{$ident}	= $args{name};
        $state_of{$ident}	= $args{state};
        $status_of{$ident}	= $args{status};
        $options_of{$ident}	= $args{options};

        $plex_of{$ident}	=
            NetApp::Aggregate::Plex->new( $args{plex} );

        if ( $args{aggregate_name} ) {
            $aggregate_name_of{$ident}	= $args{aggregate_name};
        }

        if ( $args{source} ) {
            $source_of{$ident} =
                NetApp::Volume::Source->new( $args{source} );
        }

        $clone_names_of{$ident}	= $args{clone_names};

        if ( $args{parent_name} ) {
            $parent_name_of{$ident}	= $args{parent_name};
        }

        if ( $args{snapshot_of} ) {
            $snapshot_name_of{$ident}	= $args{snapshot_name};
        }

        $path_of{$ident}	= "/vol/$args{name}";

    }

    sub get_states {
        return keys %{ $state_of{ident shift} };
    }

    sub get_state {
        
        my $self     	= shift;
        my $ident	= ident $self;
        my $state	= shift;

        return $state_of{$ident}->{$state};

    }

    sub get_statuses { # Stati?  Oh, hell no...
        return keys %{ $status_of{ident shift} };
    }

    sub get_status { 

        my $self     	= shift;
        my $ident	= ident $self;
        my $status	= shift;

        return $status_of{$ident}->{$status};

    }
    
    sub get_options {
        return keys %{ $options_of{ident shift} };
    }

    sub get_option { 

        my $self     	= shift;
        my $ident	= ident $self;
        my $option	= shift;

        if ( exists $options_of{$ident}->{$option} ) {
            return $options_of{$ident}->{$option};
        } else {
            return undef;
        }

    }

    sub set_option {

        my $self	= shift;
        my $option	= shift;
        my $value	= $option eq 'root' ? '-f' : shift;

        my $ident	= ident $self;

        my $name	= $self->get_name;

        my @command	= ( qw(vol options), $name, $option, $value );

        $self->get_filer->_run_command( command => @command );

        if ( $option eq 'root' ) {
            $options_of{$ident}->{$option} = 1;
        } else {
            $options_of{$ident}->{$option} = $value;
        }

        return 1;

    }

    sub get_aggregate {
        my $self	= shift;
        return $self->get_filer->get_aggregate( $self->get_aggregate_name );
    }

    sub get_qtree_names {
        my $self	= shift;
        return map { $_->get_name } $self->get_qtrees;
    }

    sub get_qtree {
        my $self	= shift;
        my $name	= shift || "/vol/" . $self->get_name;
        return $self->get_filer->get_qtree( $name );
    }

    sub get_qtrees {
        my $self	= shift;
        return $self->get_filer->_get_qtree_status( volume => $self );
    }

    sub get_language {

        my $self	= shift;

        my $name	= $self->get_name;
            
        $self->get_filer->_run_command(
            command	=> [qw(vol language), $name],
        );

        my @stdout	= $self->get_filer->_get_command_stdout;

        my $language	= "";

        while ( my $line = shift @stdout ) {
            if ( $line =~ /Volume language is (\S+)/ ) {
                $language	= $1;
            }
        }

        if ( not $language ) {
            croak(
                "Unable to determine language for volume $name\n",
            );
        }

        return $language;

    }

    sub set_language {

        my $self	= shift;
        my $language	= shift;

        my $name	= $self->get_name;

        return $self->get_filer->_run_command(
            command	=> [qw(vol language), $name, $language],
        );

    }

    sub get_size {

        my $self	= shift;

        my $name	= $self->get_name;

        $self->get_filer->_run_command(
            command	=> [qw(vol size), $name],
        );

        my @stdout	= $self->get_filer->_get_command_stdout;

        my $size	= "";

        while ( defined(my $line = shift @stdout) ) {
            if ( $line 	=~ /has size (\S+)\./ ) {
                $size	= $1;
            }
        }

        if ( not $size ) {
            croak("Unable to determine size of volume $name\n");
        }

        return $size;

    }

    sub set_size {

        my $self	= shift;
        my $size	= shift;

        return $self->get_filer->_run_command(
            command	=> [qw(vol size), $self->get_name, $size],
        );

    }

    sub get_maxfiles {

        my $self	= shift;

        my $name	= $self->get_name;

        $self->get_filer->_run_command(
            command	=> ['maxfiles', $name],
        );

        my @stdout	= $self->get_filer->_get_command_stdout;

        my $maxfiles	= "";

        while ( my $line = shift @stdout ) {
            if ( $line =~ /is currently (\d+)/ ) {
                $maxfiles	= $1;
            }
        }

        if ( not $maxfiles ) {
            croak("Unable to determine maxfiles of volume $name\n");
        }

        return $maxfiles;

    }

    sub set_maxfiles {

        my $self	= shift;
        my $maxfiles	= shift;

        return $self->get_filer->_run_command(
            command	=> ['maxfiles', $self->get_name, $maxfiles],
        );

    }

    sub get_clone_names {

        my $self	= shift;
        my $ident	= ident $self;

        return @{ $clone_names_of{$ident} };

    }

    sub get_clones {

        my $self	= shift;

        my @clones	= ();

        foreach my $clone_name ( $self->get_clone_names ) {
            push @clones, $self->get_filer->get_volume( $clone_name );
        }

        return @clones;

    }

    sub is_clone {
        my $self	= shift;
        return ( $self->get_parent_name ? 1 : 0 );
    }

    sub get_parent {
        my $self	= shift;
        if ( $self->is_clone ) {
            return $self->get_filer->get_volume( $self->get_parent_name );
        } else {
            return;
        }
    }

    sub get_snapmirrors {
        my $self	= shift;
        return $self->get_filer->_get_snapmirrors( volume => $self );
    }

    sub get_snapshots {
        return NetApp::Snapshot->_get_snapshots( parent => shift );
    }

    sub get_snapshot {
        my $self	= shift;
        my ($name)	= validate_pos( @_, { type => SCALAR } );
        return grep { $_->get_name eq $name } $self->get_snapshots;
    }

    sub create_snapshot {
        my $self	= shift;
        my ($name)	= validate_pos( @_, { type => SCALAR } );
        return NetApp::Snapshot->_create_snapshot(
            parent	=> $self,
            name	=> $name,
        );
    }

    sub delete_snapshot {
        my $self	= shift;
        my ($name)	= validate_pos( @_, { type => SCALAR } );
        return NetApp::Snapshot->_delete_snapshot(
            parent	=> $self,
            name	=> $name,
        );
    }

    sub delete_all_snapshots {

        croak(__PACKAGE__ . "->delete_all_snapshots not yet implemented\n");

        # XXX: This one's tricky to implement.  Should we parse the
        # output, and attempt to return a list of what was delete, and
        # what was busy?  Probably too ugly.

        my $self	= shift;

        return $self->get_filer->_run_command(
            command	=> [ qw( snap delete -a -f -q ), $self->get_name ],
        );

    }

    sub get_snapshot_deltas {
        return NetApp::Snapshot->_get_snapshot_deltas( parent => shift );
    }

    sub get_snapshot_reserved {
        return NetApp::Snapshot->_get_snapshot_reserved( parent => shift );
    }

    sub set_snapshot_reserved {
        my $self	= shift;
        my ($reserved)	= validate_pos( @_, { type => SCALAR } );
        return NetApp::Snapshot->_set_snapshot_reserved(
            parent 	=> $self,
            reserved	=> $reserved,
        );
    }

    sub get_snapshot_schedule {
        return NetApp::Snapshot->_get_snapshot_schedule(
            parent	=> shift,
            @_
        );
    }

    sub set_snapshot_schedule {
        return NetApp::Snapshot->_set_snapshot_schedule(
            parent	=> shift,
            @_
        );
    }

    sub enable_snapshot_autodelete {
        my $self	= shift;
        return $self->get_filer->_run_command(
            command	=> [ qw(snap autodelete), $self->get_name, qw(on) ],
        );
    }

    sub disable_snapshot_autodelete {
        my $self	= shift;
        return $self->get_filer->_run_command(
            command	=> [ qw(snap autodelete), $self->get_name, qw(off) ],
        );
    }

    sub reset_snapshot_autodelete {
        my $self	= shift;
        return $self->get_filer->_run_command(
            command	=> [ qw(snap autodelete), $self->get_name, qw(reset) ],
        );
    }

    sub set_snapshot_autodelete_option {

        my $self	= shift;

        my ($name,$value) = validate_pos(
            @_,
            { type	=> SCALAR },
            { type	=> SCALAR },
        );

        my @command	= (
            qw( snap autodelete ),
            $self->get_name, $name, $value,
        );

        return $self->get_filer->_run_command(
            command	=> \@command,
        );

    }

    sub get_snapshot_autodelete_option {

        my $self	= shift;

        my ($name) 	= validate_pos(
            @_,
            { type	=> SCALAR },
        );

        my @command	= ( qw( snap autodelete ), $self->get_name );

        $self->get_filer->_run_command(
            command	=> \@command,
        );

        my @stdout	= $self->get_filer->_get_command_stdout;

        my $found	= 0;
        my $value	= "";

        while ( defined (my $line = shift @stdout) ) {
            if ( $line 	=~ /^$name\s*:\s*(.*)/ ) {
                $found	= 1;
                $value	= $1;
                $value	= "" if $value eq '(not specified)';
            }
        }

        if ( not $found ) {
            croak("Invalid autodelete option name '$name'\n");
        }

        return $value;

    }

    sub get_temporary_exports {
        return grep { $_->get_type eq 'temporary' } shift->get_exports;
    }

    sub get_permanent_exports {
        return grep { $_->get_type eq 'permanent' } shift->get_exports;
    }

    sub get_active_exports {
        return grep { $_->get_active } shift->get_exports;
    }

    sub get_inactive_exports {
        return grep { not $_->get_active } shift->get_exports;
    }

    sub get_export {
        my $self	= shift;
        my ($path)	= validate_pos( @_, { type => SCALAR } );
        return grep { $_->get_path eq $path } $self->get_exports;
    }

    sub get_exports {
        my $self	= shift;
        return
            grep { $_->get_path eq $self->get_path ||
                       $_->get_actual eq $self->get_path }
                $self->get_filer->get_exports;
    }

    sub create_export {

        my $self	= shift;

        my (%args)	= validate( @_, {
            exportas	=> { type	=> SCALAR,
                             optional	=> 1 },
            type	=> { type	=> SCALAR,
                             optional	=> 1 },
            nosuid	=> { type	=> SCALAR,
                             optional	=> 1 },
            anon	=> { type	=> SCALAR,
                             optional	=> 1 },
            sec		=> { type	=> ARRAYREF,
                             optional	=> 1 },
            root	=> { type	=> ARRAYREF,
                             optional	=> 1 },
            rw		=> { type	=> ARRAYREF,
                             optional	=> 1 },
            ro		=> { type	=> ARRAYREF,
                             optional	=> 1 },
            rw_all	=> { type	=> SCALAR,
                             optional	=> 1 },
            ro_all	=> { type	=> SCALAR,
                             optional	=> 1 },
        });

        if ( $args{exportas} ) {
            $args{actual}	= $self->get_path,
            $args{path}		= delete $args{exportas},
        } else {
            $args{path}		= $self->get_path;
        }

        my $export	= NetApp::Filer::Export->new( \%args );

        return $export->update;

    }

    sub destroy_export {

        my $self     	= shift;

        # XXX: Hmm....

    }

    sub offline {

        my $self	= shift;
        my $ident	= ident $self;

        my (%args)	= validate( @_, {
            cifsdelaytime	=> { type	=> SCALAR,
                                     optional	=> 1 },
        });

        my @command	= ( qw(vol offline), $self->get_name );

        if ( $args{cifsdelaytime} ) {
            push @command, '-t', $args{cifsdelaytime};
        }

        $self->get_filer->_run_command(
            command	=> \@command,
        );

        delete $state_of{$ident}->{online};
        delete $state_of{$ident}->{restricted};
        $state_of{$ident}->{offline} = 1;
        
        return 1;

    }

    sub online {

        my $self	= shift;
        my $ident	= ident $self;

        my (%args)	= validate( @_, {
            force	=> { type	=> SCALAR,
                             optional	=> 1 },
        });

        my @command	= ( qw( vol online ), $self->get_name );

        if ( $args{force} ) {
            push @command, '-f';
        }
        
        $self->get_filer->_run_command(
            command	=> \@command,
        );

        delete $state_of{$ident}->{offline};
        delete $state_of{$ident}->{restricted};
        $state_of{$ident}->{online} = 1;
        
        return 1;

    }

    sub rename {

        my $self	= shift;
        my $ident	= ident shift;

        my (%args)	= validate( @_, {
            newname	=> { type	=> SCALAR },
        });

        my $oldname	= $self->get_name;

        $self->get_filer->_run_command(
            command	=>[qw(vol rename), $oldname, $args{newname}],
        );

        $name_of{$ident} = $args{newname};

        return 1;

    }

    sub restrict {
        
        my $self	= shift;
        my $ident	= ident shift;

        my (%args)	= validate( @_, {
            cifsdelaytime	=> { type	=> SCALAR,
                                     optional	=> 1 },
        });

        my @command	= ( qw(vol restrict), $self->get_name );

        if ( $args{cifsdelaytime} ) {
            push @command, '-t', $args{cifsdelaytime};
        }

        $self->get_filer->_run_command(
            command	=> \@command,
        );

        delete $state_of{$ident}->{offline};
        delete $state_of{$ident}->{online};
        $state_of{$ident}->{restricted} = 1;

        return 1;

    }

}

sub _parse_vol_status_headers {

    my $class		= shift;
    my $header		= shift;

    my $indices		= {};
    my $index		= 0;

    my ($volume)	= ( $header =~ /(^\s+Volume\s+)/ ) or
        croak(
            "Unable to match 'Volume' column header\n"
        );

    $indices->{volume}	= [ 0, length($volume) ];
    $index		+= length($volume);

    my ($state)		= ( $header =~ /(State\s+)/ ) or
        croak(
            "Unable to match 'State' column header\n"
        );

    $indices->{state}	= [ $index, length($state) ];
    $index		+= length($state);

    my ($status)	= ( $header =~ /(Status\s+)/ ) or
        croak(
            "Unable to match 'Status' column header\n"
        );
        
    $indices->{status}	= [ $index, length($status) ];
    $index		+= length($status);

    my ($options)	= ( $header =~ /(Options\s*)/ ) or
        croak(
            "Unable to match 'Options' column header\n"
        );
        
    $indices->{options}	= [ $index ];

    if ( $header	=~ /Source/ ) {
        $indices->{options}->[1]	= length($options);
        $index			+= length($options);
        $indices->{source}	= [ $index ];
    }

    $indices->{length}	= $index + 1;

    return $indices;

}

sub _parse_vol_status_volume {

    my $class		= shift;
    
    my %args		= validate( @_, {
        indices		=> { type	=> HASHREF },
        line		=> { type	=> SCALAR },
        volume		=> { type   	=> HASHREF,
                             default	=> {},
                             optional	=> 1 },
    });
                                      
    my $indices		= $args{indices};
    my $volume		= $args{volume};
    my $line		= $args{line};

    if ( $line =~ /Clone, backed by volume '(.*)', snapshot '(.*)'/ ) {

        $volume->{parent_name}		= $1;
        $volume->{snapshot_name}	= $2;
        return $volume;

    } elsif ( $line =~ /Volume has clones: (.*)/ ) {

        my $clones	= $1;
        $volume->{clone_names} = [ split( /[,\s]+/, $clones ) ];
        return $volume;

    } elsif ( $line =~ /Containing aggregate: (\S+)/ ) {

        my $aggrname	= $1;
        $aggrname	=~ s/'//g;
        if ( $aggrname ne '<N/A>' ) {
            $volume->{aggregate_name} = $aggrname;
        }
        return $volume;

    } 

    if ( length($line) < $indices->{length} ) {
        $line		.= " " x ( $indices->{length} - length($line) );
    }

    foreach my $column ( qw( volume state status options source ) ) {

        my $value	= "";

        next unless $indices->{$column};

        if ( defined $indices->{$column}->[1] ) {
            $value	= substr( $line,
                                  $indices->{$column}->[0],
                                  $indices->{$column}->[1] );
        } else {
            $value	= substr( $line,
                                  $indices->{$column}->[0] );
        }

        $value		=~ s/$RE{ws}{crop}//g;

        if ( $column eq 'volume' ) {

            if ( $value ) {

                $volume->{name}	= $value;

                my ($name) = split( /\s+/, $line );

                if ( length($name) > length($value) ) {
                    $volume->{name}	= $name;
                    $line =~ s/^$name/$value/;
                }

            }

        } elsif ( $column eq 'source' ) {

            my ($hostname,$source) = split( /:/, $value );

            $volume->{source} 	= {
                hostname	=> $hostname,
                volume		=> $source,
            };

            $indices->{options}->[1] = undef;

            delete $indices->{source};

        } else {

            foreach my $entry ( split( /[,\s]+/, $value ) ) {

                my ($key,$value);

                if ( $entry =~ /=/ ) {
                    ($key,$value)	= split( /=/, $entry, 2 );
                } else {
                    ($key,$value)	= ($entry,1);
                }

                $volume->{$column}->{$key} = $value;

            }

        }

    }

    return $volume;

}

1;
