#!/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 BaracusDB;
use BaracusCore   qw( :subs  );
use BaracusConfig qw( :vars :subs );
use BaracusAux    qw( :subs  );
use BaracusMcast  qw( :subs );
use BaracusStorage qw( :vars );


=pod

=head1 NAME

B<bamcast> - Baracus "multicast" for registering and maintaining multicast image channels

=head1 SYNOPSIS

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

Where E<lt>commandE<gt> is

    add       <...>  Add a multicast channel identifier, name and description for media or share
    remove    <...>  Remove a multicast channel 
    list      <...>  List all registered multicast channels
    detail    <...>  Provide details of specified multicast channel
  
    enable    Enable multicast channel
    disable   Disables multicast channel

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

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

=head1 DESCRIPTION

This tool registeres and maintains Baracus multicast channels for use in multicast image deployments.

=head1 OPTIONS

=over 4

=item -v --verbose  Be verbose with output

=back

=cut

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


my $cmds = {
            'add'           => \&add,
            'remove'        => \&remove,
            'list'          => \&list,
            'detail'        => \&detail,
            'enable'        => \&enable,
            'disable'       => \&disable,
            'init'          => \&init,
            'destroy'       => \&destroy,

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

my $opts = {
            verbose    => 0,
            quiet      => 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},
           '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 <channel id> --storageid <storage id> --dataip <dataip> --rdvip <rdvip> --interface <interface> --ratemx <max rate> --mrecv <min recievers>

Where

  --name <unique channel id>    globally unique share locator URI.
  --storageid <storage id>         storage id of storage image to use.
  --dataip <ip>
  --rdvip <ip>
  --interface <network interface>
  --ratemx <max bit rate>
  --mrecv <min # receivers>

=cut

sub add
{
    my $command = "add";

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

    my $mcastref;
    my $imgref;

    @ARGV = @_;

    GetOptions(
               'name=s'        => \$mcastref->{'mcastid'},
               'storageid=s'   => \$mcastref->{'storageid'},
               'dataip=s'      => \$mcastref->{'dataip'},
               'rdvip=s'       => \$mcastref->{'rdvip'},
               'interface=s'   => \$mcastref->{'interface'},
               'ratemx=s'      => \$mcastref->{'ratemx'},
               'mrecv=s'       => \$mcastref->{'mrecv'},
              );

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

    ## Verify Mandatory parameters
    ##
    for my $key ( keys %$mcastref ) {
        if ( $mcastref->{$key} eq "" ) {
            $opts->{LASTERROR} = "Missing  --$key\n";
            return 1;
        }
    }

    my $chkref = &get_db_data( $dbh, $tbl, $mcastref->{mcastid} );
    if ( defined $chkref ) {
        $opts->{LASTERROR} = "$mcastref->{mcastid} already exists\n";
        return 1;
    }
    
    &check_ip( $mcastref->{dataip} );  # exits on error
    &check_ip( $mcastref->{rdvip} ); # exits on error

    # check for pre-exsisting id
    $imgref = &get_db_data( $dbh, 'storage', $mcastref->{storageid} );
    unless ( defined $imgref ) {
        $opts->{LASTERROR} = "$mcastref->{storageid} does not exist\n";
        return 1;
    }
    if ( $imgref->{type} != BA_STORAGE_IMAGE ) {
        $opts->{LASTERROR} = "$mcastref->{storageid} not of type image\n";
        return 1;
    }

    # test if image exists 
    unless ( -e "$baDir{images}/$imgref->{storage}" ) {
        $opts->{LASTERROR} = "image: $imgref->{target} does not exist in image directory\n";
        return 1;
    }

    # Get next available port
    $mcastref->{port} = &get_free_mcast_port( $opts, $dbh );

    # done with all checking
    # begin loading of hash used for remainder of processing command

    # set status to enabled
    &add_db_data( $dbh, $tbl, $mcastref );
    my $pid = &start_mchannel( $dbh, $mcastref->{mcastid} );
    if ( $pid == 1 ) { 
        $mcastref->{status} = 0;
        $mcastref->{pid} = "0";
    } else {
        $mcastref->{status} = 1;
        $mcastref->{pid} = $pid;
    }
    &update_db_data( $dbh, $tbl, $mcastref );

    if ( $opts->{verbose} )
    {
        printf "mcast: $mcastref->{targetid} added\n";
    }

    return 0;
}

=head2 remove --name <name>

Removes the multicast channel associated with specified mcastid.

=cut

sub remove
{
    my $command = "remove";

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

    my $mcastid="";

    my $chkref;

    @ARGV = @_;

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

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

    $chkref = &get_db_data( $dbh, $tbl, $mcastid );
    if ( defined $chkref ) {
       if ( &stop_mchannel( $dbh, $mcastid ) == 0 ) {
            &remove_db_data( $dbh, $tbl, $mcastid );
       } else {
           $opts->{LASTERROR} = print "unable to remove mchannel, error stopping $mcastid\n";
           return 1;     
       }
    } else {
        $opts->{LASTERROR} = print "mcastid: $mcastid no registered\n";
        return 1;
    }

    return 0;
}

=head2 enable --name <name>

Enable the multicast channel associated with specified mcastid.

=cut

sub enable
{
    my $command = "enable";

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

    my $mcastid="";
    my $chkref;

    @ARGV = @_;

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

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

    my $mcastref = &get_db_data( $dbh, $tbl, $mcastid );
    if ( defined $mcastref ) {
        if ( $mcastref->{status} == 0 ) {
            ## start channel
            my $pid = &start_mchannel( $dbh, $mcastid );
            if ( $pid != 1 ) {
                $mcastref->{status} = 1;
                $mcastref->{pid} = $pid;
                &update_db_data( $dbh, $tbl, $mcastref);
            } else {
                print "status not updated \n";
            }
        } elsif (  $mcastref->{status} == 0 ) {
            $opts->{LASTERROR} = print "mcastid: $mcastid already enabled\n";
            return 1;
        } else {
            # something is screwed up with status
            $opts->{LASTERROR} = print "mcastid: $mcastid status error\n";
            return 1;
        }
     } else {
        $opts->{LASTERROR} = print "mcastid: $mcastid no registered\n";
        return 1;
    }

}

=head2 disable --name <name>

Enable the multicast channel associated with specified mcastid.

=cut
sub disable
{
    my $command = "disable";

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

    my $mcastid="";
    my $chkref;

    @ARGV = @_;

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

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

    my $mcastref = &get_db_data( $dbh, $tbl, $mcastid );
    if ( defined $mcastref ) {
        if ( $mcastref->{status} == 1 ) {
            if ( &stop_mchannel( $dbh, $mcastid ) == 0 ) {
                $mcastref->{status} = 0;
                $mcastref->{pid} = "0";
                &update_db_data( $dbh, $tbl, $mcastref);
            }
        } elsif ( $mcastref->{status} == 0 ) {
            $opts->{LASTERROR} = print "mcastid: $mcastid already disabled\n";
            return 1;
        } else {
            # something is screwed up with status
            $opts->{LASTERROR} = print "mcastid: $mcastid status error\n";
            return 1;
        }
     } else {
        $opts->{LASTERROR} = print "mcastid: $mcastid no registered\n";
        return 1;
    }

}

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

List the registered multicast channels.

Option

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

=cut

sub list
{
    my $command = "list";

    my $filter = "";
    my %mcastStatus = (
                        0   => 'disabled',
                        1   => 'enabled',
                      );
                 

    @ARGV = @_;

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

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

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

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

    unless ($opts->{quiet}) {
        print "----------------------------------------------------------------------\n";
        printf "%-26s %-26s %8s\n", "mcastid", "image", "status";
        print "----------------------------------------------------------------------\n";
    }

    my $dbref;
    while ( $dbref = &list_next_data( $sth ) ) {
        printf "%-26s %-26s %-8s\n",
            $dbref->{mcastid}, $dbref->{storageid}, $mcastStatus{ $dbref->{status} };
    }

    return 0;
}

=head2 detail --name <name>

Show details for specified multicast channels

=cut

sub detail
{
    my $command = "detail";

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

    my $mcastref;
    my $mcastid = "";

    @ARGV = @_;

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

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

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

    printf "%-12s %s\n", "mcastID:",     $mcastref->{mcastid} ;
    printf "%-12s %s\n", "storageID:",   $mcastref->{storageid} ;
    printf "%-12s %s\n", "dataIP:",      $mcastref->{dataip} ;
    printf "%-12s %s\n", "rdvIP:",       $mcastref->{rdvip} ;
    printf "%-12s %s\n", "interface:",   $mcastref->{interface} ;
    printf "%-12s %s\n", "ratemx:",      $mcastref->{ratemx} ;
    printf "%-12s %s\n", "mrecv:",       $mcastref->{mrecv} ;
    printf "%-12s %s\n", "status:",      $mcastref->{status} ;
    if ( $mcastref->{status} == 1 ) { printf "%-12s %s\n", "pid:",         $mcastref->{pid}; }

    return 0;
}

sub init {
    my $sth = &list_start_data ( $opts, $dbh, $tbl, "" );
    while ( my $dbref = &list_next_data( $sth ) ) {
        if ( $dbref->{status} ) {
            ## start the mchannel
            if ( $opts->{verbose} ) {
                print "Starting mchannel id: $dbref->{mcastid} \n";
            }
            my $mcastref = &get_db_data( $dbh, $tbl, $dbref->{mcastid} );
            $mcastref->{pid} = &start_mchannel( $dbh, $dbref->{mcastid} );
            &update_db_data( $dbh, $tbl, $mcastref);
        }
    }

    return 0;
}

sub destroy {
    my $sth = &list_start_data ( $opts, $dbh, $tbl, "" );
    while ( my $dbref = &list_next_data( $sth ) ) {
        if ( $dbref->{status} ) {
            ## start the mchannel
            if ( $opts->{verbose} ) {
                print "Stopping mchannel id: $dbref->{mcastid} \n";
            }
            my $ret = &stop_mchannel( $dbh, $dbref->{mcastid} );
            if ( $ret == 1 ) {
                $opts->{LASTERROR} = "error stopping $dbref->{mcastid}\n";
                return 1;
             }
        }
    }

    return 0;
}


die "ABSOLUTELY DOES NOT EXECUTE";

__END__
