#!/usr/bin/perl -w

###########################################################################
#
# Baracus build and boot management framework
#
# Copyright (C) 2010 Novell, Inc, 404 Wyman Street, Waltham, MA 02451, USA.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the Artistic License 2.0, as published
# by the Perl Foundation, or the GNU General Public License 2.0
# as published by the Free Software Foundation; your choice.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  Both the Artistic
# Licesnse and the GPL License referenced have clauses with more details.
#
# You should have received a copy of the licenses mentioned
# along with this program; if not, write to:
#
# FSF, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110, USA.
# The Perl Foundation, 6832 Mulderstraat, Grand Ledge, MI 48837, USA.
#
###########################################################################

use strict;
use warnings;

use Getopt::Long qw( :config pass_through );
use Pod::Usage;

use lib "/usr/share/baracus/perl";

use SqlFS;

use BaracusDB;
use BaracusCore qw( :subs  );
use BaracusConfig qw( :vars :subs );
use BaracusAux qw( :subs  );
use BaracusSql qw( :vars :subs );
use BaracusStorage qw( :vars :subs );


#     update    <...>  Update a storage name or description

=pod

=head1 NAME

B<bastorage> - Baracus identification and handling for SAN, NAS and images

=head1 SYNOPSIS

B<bastorage> E<lt>commandE<gt> [options and arguments]

Where E<lt>commandE<gt> is

    add       <...>  Add an identifier, name and description for media or share
    remove    <...>  Remove Baracus knowledge of media or share
    list      <...>  List the known non-local storage shares and media available
    detail    <...>  Show all known information about storage

    help    This 'Usage' summary message.
    man     Detailed man page.

Use 'man' or 'help <command>' for more details.

=head1 DESCRIPTION

This tool allows for the identification of disserperate network storage (Network Attached Storage or Storage Area Networks) for potential use with baracus hosts as available disks for network boot targets (e.g. nfsroot, ibft/iscsi).

=head1 OPTIONS

=over 4

=item -v --verbose  Be verbose with output

=back

=cut

my $man      = 0;
my $help     = 0;
my $tbl      = "storage";

# no add / remove here - all cmds add or update as needed
# to 'mac', 'build', and 'templateid' tables

my $cmds = {
            'add'           => \&add,
            'remove'        => \&remove,
            'list'          => \&list,
            'detail'        => \&detail,

            'help'          => \&help,
            'man'           => \&man,
            };

my $opts = {
            verbose    => 0,
            quiet      => 0,
#            all        => 0,
#            nolabels   => 0,
            debug      => 0,
            execname   => "",
            LASTERROR  => "",
            };

# Build command line for help, historical reference and point-in-time rebuilding
$opts->{execname} = $0;
$opts->{execname} = $1 if ( $0 =~ m|^.*/([^/].+)| );
my $cmdline = join '::', $opts->{execname}, @ARGV;

GetOptions(
           'verbose'   => \$opts->{verbose},
           'quiet'     => \$opts->{quiet},
#           'all'       => \$opts->{all},
#           'nolabels'  => \$opts->{nolabels},
           'debug+'    => \$opts->{debug},
           'man'       => \$man,
           'help|?'    => \$help,
           );

&man()  if $man;
&help( $opts, $cmds ) if $help;
&help( $opts, $cmds ) unless ( scalar @ARGV );
if ($ARGV[0] eq "man"  ) { &man(); }
if ($ARGV[0] eq "help" ) { shift @ARGV; &help( $opts, $cmds, @ARGV ); }

my $dbname = "baracus";
my $dbrole = $dbname;

print "setting uid to $dbrole\n" if ($opts->{debug} > 2);

my $uid = BaracusDB::su_user( $dbrole );
die BaracusDB::errstr unless ( defined $uid );

my $dbh = BaracusDB::connect_db( $dbname, $dbrole );
die BaracusDB::errstr unless( $dbh );

my $status = &main( $opts, $cmds, @ARGV );

die BaracusDB::errstr unless BaracusDB::disconnect_db( $dbh );

print $opts->{LASTERROR} if $status;

exit $status;

die "DOES NOT EXECUTE";


###############################################################################
##
## commands
##

=head1 COMMANDS

=head2 add --name <storageid> --type <type> --storage <path_or_uri> [options]

With arguments

  --name <storageid>       Unique identifying name.
  --type <sharetype>       Storage protocol [iscsi|nfs|aoe|image]
  --storage <path_or_uri>  Path, unique resource id, or iqn identifier.

For --type other than 'image' or 'clone'

  --ip <server>            Address of network storage server.

And options

  --description <"text">   Quoted descriptive text for the share.
  --size <"text">          User description for the storage size.
  --username <username>    Username for storage authentication
  --passwd <password>      Password for storage authentication

=cut

sub add
{
    my $command = "add";

    unless ( scalar @_ ) {
        &help( $opts, $cmds, $command );
    }

    my $sref = {};
    my $cols = lc get_cols( $baTbls{ $tbl });
    foreach my $key (split(/[,\s*]/, $cols )) {
        $sref->{$key} = '';
    }

    my $chkref;

    @ARGV = @_;

    GetOptions
        (
         'name=s'        => \$sref->{'storageid'},
         'ip=s'          => \$sref->{'storageip'},
         'storage=s'     => \$sref->{'storage'},
         'type=s'        => \$sref->{'type'},     # in as string & store as int
         'username=s'    => \$sref->{'username'},
         'passwd=s'      => \$sref->{'passwd'},
         'description=s' => \$sref->{'description'},
         'size=s'        => \$sref->{'size'},
         );

    if ( scalar @ARGV ) {
        $opts->{LASTERROR} = sprintf "\nUnknown arg(s): %s\n", join(" ", @ARGV);
        return 1;
    }

    if ( defined $baStorageType{ $sref->{type} } ) {
        $sref->{type} = $baStorageType{ $sref->{type} };
    } else {
        $opts->{LASTERROR} = "Incorrect --type: $sref->{type}\n";
        return 1;
    }

    ## Verify Mandatory parameters
    ##
    if ( $sref->{'storageid'} eq "" ) {
        $opts->{LASTERROR} = "Missing  --name\n";
        return 1;
    }
    if ( ( $sref->{storageip} eq "" ) and
         ( $sref->{type} != BA_STORAGE_IMAGE ) and
         ( $sref->{type} != BA_STORAGE_CLONE ) ) {
        $opts->{LASTERROR} = "Missing  --ip\n";
        return 1;
    }
    if ( $sref->{storage} eq "" ) {
        $opts->{LASTERROR} = "Missing --storage\n";
        return 1;
    }

    # exits on error
    &check_ip( $sref->{storageip} ) if ( $sref->{storageip} ne "" );

    # check for pre-exsisting name
    $chkref = &get_db_data( $dbh, $tbl, $sref->{storageid} );
    if ( defined $chkref ) {
        $opts->{LASTERROR} = "storage name already exists: $sref->{storageid}\n";
        return 1;
    }

    if ( $sref->{type} eq BA_STORAGE_IMAGE ) {
        # make sure either storage or storage.gz exists
        # and generate md5sum for tamper checking
        if ( -f "$baDir{images}/$sref->{storage}" ) {
            ; # no-op
        } elsif ( $sref->{storage} !~ m/^.*\.gz$/ and
                  -f "$baDir{images}/$sref->{storage}.gz" ) {
            # user didn't spec .gz but it's there so use it
            $sref->{storage} = "$sref->{storage}.gz";
        } else {
            $opts->{LASTERROR} = "file not found in $baDir{images}: $sref->{storage}\n";
            return 1;
        }
        print "md5sum $sref->{storage} ...\n" if $opts->{verbose};
        $sref->{md5sum} = &get_md5sum( "$baDir{images}/$sref->{storage}" );

    } elsif ( $sref->{type} eq BA_STORAGE_CLONE ) {
        # make sure neither storage nor storage.gz exists
        if ( -f "$baDir{images}/$sref->{storage}" ) {
            $opts->{LASTERROR} = "\t$baDir{images}/$sref->{storage}\n";
        }
        if ( $sref->{storage} !~ m/^.*\.gz$/ ) {
            # clone generated .gz images so use the extension
            $sref->{storage} = "$sref->{storage}.gz";
            if ( -f "$baDir{images}/$sref->{storage}" ) {
                $opts->{LASTERROR} .= "\t$baDir{images}/$sref->{storage}\n";
            }
        }
        if ( $opts->{LASTERROR} ne "" ) {
            $opts->{LASTERROR} = "file(s) already found in $baDir{images}:\n" . $opts->{LASTERROR};
            return 1;
        }
    }

    # done with all checking
    &add_db_data( $dbh, $tbl, $sref );
    printf "storage added: $sref->{storageid}\n" if ( $opts->{verbose} );

    return 0;
}


=head2 remove --name <storageid>

This command does not remove any data from the network storage device. Instead this only removes the info from Baracus so that it cannot easily be assigned to a host.

Also note this does not remove any existing relations of this share with existing host entries.

=cut

sub remove
{
    my $command = "remove";

    unless ( scalar @_ ) {
        &help( $opts, $cmds, $command );
    }

    my $name;
    my $chkref;

    @ARGV = @_;

    GetOptions(
               'name=s' => \$name,
               );

    if ( scalar @ARGV ) {
        $opts->{LASTERROR} = sprintf "\nUnknown arg(s): %s\n", join(" ", @ARGV);
        return 1;
    }

    $chkref = &get_db_data( $dbh, $tbl, $name );
    if ( defined $chkref ) {
        &remove_db_data( $dbh, $tbl, $name );
    }
    return 0;
}

=head2 list [ --filter <name|storage>::<partial*|specific> ] [ --uri ]

List the network storage for which Baracus has information.

Option

     --uri                    list with uri output

     --filter name::'partial*'   filter results based on wildcard
     --filter storage::specific  filter for a single match

=cut

sub list
{
    my $command = "list";

    my $filter = "";
    my $uri = "";

    @ARGV = @_;

    GetOptions(
               'filter=s'   => \$filter,
               'uri'        => \$uri,
              );

    if ( scalar @ARGV ) {
        $opts->{LASTERROR} = sprintf "\nUnknown arg(s): %s\n", join(" ", @ARGV);
        return 1;
    }

    # manipulate filter before calling list_start_data
    if ( $filter =~ m|::| ) {
        my ( $fkey, $fval ) = split( /::/, $filter, 2 );
        if ( $fkey eq "name" ) {
            $fkey = "storageid";
        }
        elsif( $fkey eq "ip" ) {
            $fkey = "storageip";
        }
        elsif ( $fkey eq "type" ) {
            unless ( $baStorageType{ $fval } ) {
                $opts->{LASTERROR} = "Invalid type: $fval\n";
                return 1;
            }
            $fval = $baStorageType{ $fval };
        }
        $filter = "${fkey}::${fval}";
    }

    my $sth = &list_start_data ( $opts, $dbh, 'storage', $filter );

    unless( defined $sth ) {
        # list_start error
        return 1;
    }

    if ( $uri ) {
        unless ($opts->{quiet}) {
        print "----------------------------------------------------------------------------------------------\n";
        printf "%-26s %-56s\n", "name", "uri";
        print "----------------------------------------------------------------------------------------------\n";
        }

        my $dbref;
        my $curi;
        while ( $dbref = &list_next_data( $sth ) ) {
            $curi = &get_db_storage_uri( $dbh, $dbref->{storageid} );
            printf "%-26s %-56s\n",
                $dbref->{storageid}, $curi;
        }
    } else {
        unless ($opts->{quiet}) {
        print "----------------------------------------------------------------------------------------------\n";
        printf "%-26s %-7s %-18s %-30s\n", "name", "type", "ip", "storage";
        print "----------------------------------------------------------------------------------------------\n";
        }

        my $dbref;
        while ( $dbref = &list_next_data( $sth ) ) {
            printf "%-26s %-7s %-18s %-30s\n",
                $dbref->{storageid}, $baStorageType{ $dbref->{type} }, $dbref->{storageip}, $dbref->{storage};
        }
    }

    return 0;
}

=head2 detail --name <storageid>

Show details for specified network storage device

=cut

sub detail
{
    my $command = "detail";

    unless ( scalar @_ ) {
        &help( $opts, $cmds, $command );
    }

    my $sref;
    my $name = "";

    @ARGV = @_;

    GetOptions(
               'name=s' => \$name,
               );

    if ( scalar @ARGV ) {
        $opts->{LASTERROR} = sprintf "\nUnknown arg(s): %s\n", join(" ", @ARGV);
        return 1;
    }

    $sref = &get_db_data( $dbh, $tbl, $name );
    unless ( defined $sref ) {
        $opts->{LASTERROR} = "No storage found with storage id: $name\n";
        return 1;
    }

    printf "%-12s %s\n", "Storage ID",   $sref->{'storageid'} ;
    printf "%-12s %s\n", "Storage Type", $baStorageType{ $sref->{'type'} } ;
    printf "%-12s %s\n", "Storage IP",   $sref->{'storageip'} ;
    printf "%-12s %s\n", "Storage",      $sref->{'storage'} ;
    printf "%-12s %s\n", "Storage URI",  &get_db_storage_uri( $dbh, $sref->{'storageid'} ) ;
    if ( defined $sref->{'size'} and $sref->{'size'} ne "" ) {
        printf "%-12s %s\n", "Size", $sref->{'size'} ;
    }
    if ( defined $sref->{'description'} and $sref->{'description'} ne "" ) {
        printf "%-12s %s\n", "Description", $sref->{'description'} ;
    }
    if ( defined $sref->{'username'} and $sref->{'username'} ne "" ) {
        printf "%-12s %s\n", "Username", $sref->{'username'} ;
    }
    if ( defined $sref->{'passwd'} and $sref->{'passwd'} ne "" ) {
        printf "%-12s %s\n", "Password", "*****" ;
    }
    return 0;
}


die "ABSOLUTELY DOES NOT EXECUTE";

__END__
