#!/usr/bin/env perl
## IBM(c) 20013 EPL license http://www.eclipse.org/legal/epl-v10.html
#
# This plugin is used to handle the command requests for cumulus OS support
#


package xCAT_plugin::onie;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";

use strict;
use Getopt::Long;
use Expect;
use File::Path;
use File::Basename;

use xCAT::Utils;
use xCAT::MsgUtils;
use xCAT::TableUtils;
use xCAT::SvrUtils;
use xCAT::Table;
my $xcatdebugmode = 0;
$::VERBOSE        = 0;

sub handled_commands {
    return {
        nodeset => 'nodehm:mgt=switch',
        copydata => 'onie',
        rspconfig => 'switches:switchtype',
      }
}

my $CALLBACK;                       # used to hanel the output from xdsh

sub preprocess_request {
    my $request  = shift;
    my $callback = shift;

    if ($request->{command}->[0] eq 'copydata')
    {
        return [$request];
    }

    # if already preprocessed, go straight to request
    if ((defined($request->{_xcatpreprocessed}->[0]))
        && ($request->{_xcatpreprocessed}->[0] == 1)) {
        return [$request];
    }

    my $nodes     = $request->{node};
    my $command   = $request->{command}->[0];
    my $extraargs = $request->{arg};

    if ($extraargs) {
        @ARGV = @{$extraargs};
        my ($verbose, $help, $ver);
        GetOptions("V" => \$verbose, 'h|help' => \$help, 'v|version' => \$ver);
        if ($help) {
            my $usage_string = xCAT::Usage->getUsage($command);
            my $rsp;
            push @{ $rsp->{data} }, $usage_string;
            xCAT::MsgUtils->message("I", $rsp, $callback);
            return ();
        }
        if ($ver) {
            my $ver_string = xCAT::Usage->getVersion($command);
            my $rsp;
            push @{ $rsp->{data} }, $ver_string;
            xCAT::MsgUtils->message("I", $rsp, $callback);
            return ();
        }

        if ($verbose) {
            $::VERBOSE = 1;
        }
    }

    return [$request];
}

sub process_request {
    my $request  = shift;
    my $callback = shift;
    my $subreq   = shift;

    my $nodes   = $request->{node};
    my $command = $request->{command}->[0];
    my $args    = $request->{arg};
    my @exargs    = ($request->{arg});
    if (ref($args)) {
        @exargs = @$args;
    }


    if ($::XCATSITEVALS{xcatdebugmode} != 0) { $::VERBOSE = 1}

    if ($command eq "copydata") {
        copydata($request, $callback);
    } elsif ($command eq "nodeset") {
        nodeset($request, $callback, $subreq);
    } elsif ($command eq "rspconfig") {
        my $subcmd = $exargs[0];
        if ($subcmd eq 'sshcfg') {
            process_sshcfg($nodes, $callback);
        } else {
            xCAT::MsgUtils->message("I", { data => ["The rspconfig command $subcmd is not supported"] }, $callback);
        }
    }

}

# build cumulus OS image
sub copydata {
    my $request  = shift;
    my $callback = shift;
    my $file;
    my $inspection   = undef;
    my $noosimage    = undef;
    my $nooverwrite = undef;

    # get arguments
    my $args = $request->{arg};
    if ($args) {
        @ARGV = @{$args};
        GetOptions(
            'w'   => \$nooverwrite,
            'o'   => \$noosimage,
            'i'   => \$inspection,
            'f=s' => \$file
        );
    }

    if (!(-x $file)) {
        xCAT::MsgUtils->message("E", { error => ["$file is not executable, will not process"], errorcode => ["1"] }, $callback);
        return;
    }

    #get install dir
    my $installroot = "/install";
    my $sitetab     = xCAT::Table->new('site');
    my @ents     = xCAT::TableUtils->get_site_attribute("installdir");
    my $site_ent = $ents[0];
    if (defined($site_ent))
    {
        $installroot = $site_ent;
    }

    my $arch;
    my $desc;
    my $release;
    my $osname;
    my $filename = basename($file);
    my $output = `$file`;
    if ($inspection) {
        $callback->({ data => "file output: $output" });
        return;
    }
    foreach my $line (split /[\r\n]+/, $output) {
        if ($line =~ /^Architecture/) {
            ($desc, $arch) = split /: /, $line ;
            chomp $arch;
        }
        if ($line =~ /^Release/) {
            ($desc, $release) = split /: /, $line ;
            chomp $release;
        }
        if ($line =~ /cumulus/) {
            $osname = "cumulus" ;
        }
    }
    unless ($osname) {
        $osname="image";
    }

    my $distname = $osname . $release;
    my $imagename = $distname . "-" . $arch;
    my $defaultpath = "$installroot/$distname/$arch";

    #check if file exists
    if ( (-e "$defaultpath/$filename") && ($nooverwrite)){
        $callback->({ data => "$defaultpath/$filename is already exists, will not overwrite" });
    } else {
        $callback->({ data => "Copying media to $defaultpath" });
        mkpath ("$defaultpath");
        system("cp $file $defaultpath");
        $callback->({ data => "Media copy operation successful" });
    }

    if ($noosimage) {
        $callback->({ data => "Option noosimage is specified, will not create osimage definition" });
        return;
    }

    # generate the image objects
    my $oitab = xCAT::Table->new('osimage');
    unless ($oitab) {
        xCAT::MsgUtils->message("E", { error => ["Error: Cannot open table osimage."], errorcode => ["1"] }, $callback);
        return 1;
    }
    my $litab = xCAT::Table->new('linuximage');
    unless ($litab) {
        xCAT::MsgUtils->message("E", { error => ["Error: Cannot open table linuximage."], errorcode => ["1"] }, $callback);
        return 1;
    }
    my $pkgdir = "$defaultpath/$filename";
    my $imgdir = $litab->getAttribs({ 'imagename' => $imagename }, 'pkgdir');

    if ($::VERBOSE) {
        $callback->({ data => "creating image $imagename with osarch=$arch, osvers=$distname" });
    }

    my %values;
    $values{'imagetype'}   = "linux";
    $values{'provmethod'}  = "install";
    $values{'description'} = "Cumulus Linux";
    $values{'osname'}      = "$osname";
    $values{'osvers'}      = "$distname";
    $values{'osarch'}      = "$arch";

    $oitab->setAttribs({ 'imagename' => $imagename }, \%values);

    # set a default package list
    $litab->setAttribs({ 'imagename' => $imagename }, { 'pkgdir' => $pkgdir });
    if ($::VERBOSE) {
        $callback->({ data => "setting pkgdir=$pkgdir for image $imagename" });
    }

    #Need to update osdistro table?
    my @ret = xCAT::SvrUtils->update_osdistro_table($distname, $arch, $defaultpath, $imagename);
    if ($ret[0] != 0) {
        xCAT::MsgUtils->message("E", { error => ["Error when updating the osdistro tables."], errorcode => ["1"] }, $callback);
    }

    xCAT::MsgUtils->message("I", { data => ["The image $imagename is created."] }, $callback);
}


# run the nodeset to updatenode provmethod
sub nodeset {
    my $request  = shift;
    my $callback = shift;
    my $subreq   = shift;

    my $switches = $request->{'node'};
    my $args  = $request->{arg};
    my $provmethod;
    my $image_pkgdir;

    my $setosimg;
    foreach (@$args) {
        if (/osimage=(.*)/) {
            $setosimg = $1;
        }
    }

    my $switchestab = xCAT::Table->new('switches');
    my $switcheshash = $switchestab->getNodesAttribs($switches, ['switchtype']);

    my $nodetab  = xCAT::Table->new('nodetype');
    my $nodehash = $nodetab->getNodesAttribs($switches, [ 'provmethod' ]);

    foreach my $switch (@$switches) {
        if ($switcheshash->{$switch}->[0]->{switchtype} ne "onie") {
            xCAT::MsgUtils->message("E", { error => ["nodeset command is not processed for $switch, only supports switchtype=onie"], errorcode => ["1"] }, $callback);
            next;
        }


        if ($setosimg) {
            $provmethod = $setosimg;
        } else {
            $provmethod = $nodehash->{$switch}->[0]->{provmethod};
        }
        if ($::VERBOSE) {
            if (!defined($::DISABLENODESETWARNING)) { # set by AAsn.pm
                xCAT::MsgUtils->message("I", { data => [ "$switch has provmethod=$provmethod" ] }, $callback);
            }
        }

        #get pkgdir from osimage
        my $linuximagetab = xCAT::Table->new('linuximage');
        my $osimagetab = xCAT::Table->new('osimage');
        my $imagetab = $linuximagetab->getAttribs({ imagename => $provmethod },'pkgdir');
        my $osimghash = $osimagetab->getAttribs({ imagename => $provmethod },'osvers','osarch');
        unless($imagetab and $osimghash){
            if (!defined($::DISABLENODESETWARNING)) {    # set by AAsn.pm
                xCAT::MsgUtils->message("E", { error => ["cannot find osimage \"$provmethod\" for $switch, please make sure the osimage specified in command line or node.provmethod exists!"], errorcode => ["1"] }, $callback);
            }
            next;
        }


        my %attribs=('provmethod' => $provmethod,'os'=>$osimghash->{'osvers'},'arch'=>$osimghash->{'osarch'} );
        $nodetab->setAttribs({ 'node' => $switch }, \%attribs);
        $image_pkgdir = $imagetab->{'pkgdir'};

        #validate the image pkgdir
        my $flag=0;
        if (-r $image_pkgdir) {
            my @filestat = `file $image_pkgdir`;
            if (grep /$image_pkgdir: data/, @filestat) {
                $flag=1;
            }
        }
        unless ($flag) {
            xCAT::MsgUtils->message("E", { error => ["The image '$image_pkgdir' is invalid"], errorcode => ["1"] }, $callback);
            next;
        }
        if ($::VERBOSE) {
            xCAT::MsgUtils->message("I", { data => ["osimage=$provmethod, pkgdir=$image_pkgdir"] }, $callback);
        }

        #updateing DHCP entries
        my $ret = xCAT::Utils->runxcmd({ command => ["makedhcp"], node => [$switch] }, $subreq, 0, 1);
        if ($::RUNCMD_RC) {
            xCAT::MsgUtils->message("E", { error => ["Failed to run 'makedhcp' command"], errorcode => ["$::RUNCMD_RC"] }, $callback);
        }

        xCAT::MsgUtils->message("I", { data => ["$switch: install $provmethod"] }, $callback);
    }
    return;
}


sub process_sshcfg {
    my $noderange = shift;
    my $callback = shift;

    my $password = "CumulusLinux!";
    my $userid = "cumulus";
    my $timeout = 30;
    my $keyfile = "/root/.ssh/id_rsa.pub";
    my $rootkey = `cat /root/.ssh/id_rsa.pub`;

    foreach my $switch (@$noderange) {
        my $ip = xCAT::NetworkUtils->getipaddr($switch);

        #remove old host key from /root/.ssh/known_hosts
        my $cmd = "ssh-keygen -R $switch";
        xCAT::Utils->runcmd($cmd, 0);
        $cmd = "ssh-keygen -R $ip";
        xCAT::Utils->runcmd($cmd, 0);

        my ($exp, $errstr) = cumulus_connect($ip, $userid, $password, $timeout);
        if (!defined $exp) {
            xCAT::MsgUtils->message("E", { data => ["Failed to connect to $switch"] }, $callback);
            next;
        }

        my $ret;
        my $err;

        ($ret, $err) = cumulus_exec($exp, "mkdir -p /root/.ssh");
        ($ret, $err) = cumulus_exec($exp, "chmod 700 /root/.ssh");
        ($ret, $err) = cumulus_exec($exp, "echo \"$rootkey\" >/root/.ssh/authorized_keys");
        ($ret, $err) = cumulus_exec($exp, "chmod 644 /root/.ssh/authorized_keys");
        if (!defined $ret) {
            xCAT::MsgUtils->message("E", { data => ["Failed to run command on $switch"] }, $callback);
            next;
        }
        xCAT::MsgUtils->message("I", { data => ["$switch: SSH enabled"] }, $callback);
    }
}

sub cumulus_connect {
     my $server   = shift;
     my $userid   = shift;
     my $password = shift;
     my $timeout  = shift;

     my $ssh      = Expect->new;
     my $command     = 'ssh';
     my @parameters  = ($userid . "@" . $server);

     $ssh->debug(0);
     $ssh->log_stdout(0);    # suppress stdout output..
     $ssh->slave->stty(qw(sane -echo));

     unless ($ssh->spawn($command, @parameters))
     {
         my $err = $!;
         $ssh->soft_close();
         my $rsp;
         return(undef, "unable to run command $command $err\n");
     }

     $ssh->expect($timeout,
                   [ "-re", qr/WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED/, sub {die "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!\n"; } ],
                   [ "-re", qr/\(yes\/no\)\?\s*$/, sub { $ssh->send("yes\n");  exp_continue; } ],
                   [ "-re", qr/ password:/,        sub {$ssh->send("$password\n"); exp_continue; } ],
                   [ "-re", qr/:~\$/,              sub { $ssh->send("sudo su\n"); exp_continue; } ],
                   [ "-re", qr/ password for cumulus:/, sub { $ssh->send("$password\n"); exp_continue; } ],
                   [ "-re", qr/.*\/home\/cumulus#/, sub { $ssh->clear_accum(); } ],
                   [ timeout => sub { die "No login.\n"; } ]
                  );
     $ssh->clear_accum();
     return ($ssh);
}

sub cumulus_exec {
     my $exp = shift;
     my $cmd = shift;
     my $timeout    = shift;
     my $prompt =  shift;

     $timeout = 10 unless defined $timeout;
     $prompt = qr/.*\/home\/cumulus#/ unless defined $prompt;


     $exp->clear_accum();
     $exp->send("$cmd\n");
     my ($mpos, $merr, $mstr, $mbmatch, $mamatch) = $exp->expect(6,  "-re", $prompt);

     if (defined $merr) {
         return(undef,$merr);
     }
     return($mbmatch);
}



1;
