#!/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 Xeon Phi (mic) support
#

package xCAT_plugin::mic;
BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}

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

use xCAT::Utils;
use xCAT::MsgUtils;
use xCAT::TableUtils;
use xCAT::Table;

sub handled_commands {
    return {
        rpower => 'nodehm:mgt',
        nodeset => "nodehm:mgt", # generate the osimage for mic on the host
        rflash => 'nodehm:mgt',     # update the firmware of mics
        rinv => 'nodehm:mgt',
        rvitals => 'nodehm:mgt',
        copytar => 'mic',
        getcons => 'nodehm:mgt',        
    }
}

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

# since the mic is attached to the host node and management of mic needs to be 
# done via host node, the host will be used as the target to get the service node
sub preprocess_request { 
    my $request = shift;
    my $callback = shift;
   
    if ($request->{command}->[0] eq 'copytar')
    {
        # don't handle copytar (copycds)
        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 ();
        }
    }

    # Get the host for the mic nodes
    my %hosts;
    my $mictab = xCAT::Table->new("mic");
    unless ($mictab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the mic table."], errorcode=>["1"]}, $callback);
        return;
    }
    my $mictabhash = $mictab->getNodesAttribs($nodes,['host']);
    foreach my $node (@$nodes) {
        if (!defined ($mictabhash->{$node}->[0]->{'host'})) {
            xCAT::MsgUtils->message("E", {error=>["The michost attribute was not set for $node"], errorcode=>["1"]}, $callback);
            return;
        }
        push @{$hosts{$mictabhash->{$node}->[0]->{'host'}}}, $node;
    }

    my @requests;
    # get the service nodes of hosts instread of mic nodes
    my @hosts=keys(%hosts);
    my $sn = xCAT::ServiceNodeUtils->get_ServiceNode(\@hosts, 'xcat', "MN");
    foreach my $snkey (keys %$sn){
        my $reqcopy = {%$request};
        my @nodes4sn;
        foreach (@{$sn->{$snkey}}) {
            push @nodes4sn, @{$hosts{$_}};
        }
        $reqcopy->{node} = \@nodes4sn;   # the node attribute will have the real mic nodes
        unless ($snkey eq '!xcatlocal!') {
            $reqcopy->{'_xcatdest'} = $snkey;
        }
        $reqcopy->{_xcatpreprocessed}->[0] = 1;
        push @requests, $reqcopy;
    }
    return \@requests;
}

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};

    # get the mapping between host and mic
    my %hosts;
    my $mictab = xCAT::Table->new("mic");
    unless ($mictab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the mic table."], errorcode=>["1"]}, $callback);
        return;
    }
    my $mictabhash = $mictab->getNodesAttribs($nodes,['host', 'id']);
    foreach my $node (@$nodes) {
        $hosts{$mictabhash->{$node}->[0]->{'host'}}{$mictabhash->{$node}->[0]->{'id'}} = $node;    # $hosts{host}{micid} = micname
        $hosts{$mictabhash->{$node}->[0]->{'host'}}{'ids'} .= " $mictabhash->{$node}->[0]->{'id'}";  # $hosts{host}{ids} = " id0 id1 id2 ..."
    }

    if ($command eq "rvitals"){
        rinv($request, $callback, $subreq, \%hosts);
    } elsif ($command eq "rmicctrl") {
        rmicctrl($callback, $args);
    } elsif ($command eq "rinv") {
        rinv($request, $callback, $subreq, \%hosts);
    } elsif ($command eq "rpower") {
        rpower($request, $callback, $subreq, \%hosts);
    } elsif ($command eq "copytar") {
        copytar($request, $callback);
    } elsif ($command eq "getcons") {
        getcons($request, $callback);
    } elsif ($command eq "rflash") {
        rflash($request, $callback, $subreq, \%hosts);
    } elsif ($command eq "nodeset") {
        nodeset($request, $callback, $subreq, \%hosts);
    }

}

# handle the rpower command for the mic node
sub rpower {
    my $request = shift;
    my $callback = shift;
    my $subreq = shift;
    my $host2mic = shift;

    my $usage_string = "rpower noderange [stat|state|on|off|reset|boot]";

    my $args = $request->{arg};
    my ($wait, $timeout, $action);
    if ($args) {
        @ARGV=@{$args};
        GetOptions('w' => \$wait,
                   't=s' => \$timeout);
        foreach (@ARGV) {
            if (/^stat/) {
                $action = "--status";
            } elsif (/^on$/) {
                $action = "--boot";
            } elsif (/^off$/) {
                $action = "--shutdown";
            } elsif (/^reset$/) {
                $action = "--reset";
            } elsif (/^boot$/) {
                $action = "--reboot";
            } else {
                my $rsp;
                push @{$rsp->{data}}, $usage_string;
                xCAT::MsgUtils->message("E", $rsp, $callback);
                return;
            }
        }
    } else {
        my $rsp;
        push @{$rsp->{data}}, $usage_string;
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return;
    }

    # get all the hosts
    # use the id list as the key to classify the host, since the handle the host against
    # same mic list will be run in a batch
    my %hostclass;  
    foreach (keys %$host2mic) {
        push @{$hostclass{$host2mic->{$_}{'ids'}}}, $_;
    }

    foreach (keys %hostclass) {
        my $idlist = $_;
        my @hosts = @{$hostclass{$idlist}};
        my $miclist = $idlist;  # currently ,it's " 0 1 2 .."
        $miclist =~ s/(\d+)/mic$1/g;  # then, it should be "mic0 mic1 mic2 ..."

        my @cmd = ("/usr/sbin/micctrl", $action, $miclist);
        if ($wait) {
            if ($timeout) {
                push @cmd, (" --wait", "--timeout=$timeout");
            } else {
                push @cmd, " --wait";
            }
        }
    
        my $output = xCAT::Utils->runxcmd({ command => ['xdsh'],
                                           node => \@hosts,
                                           arg => \@cmd }, $subreq, 0, 1);
    
        # parse the output
        # replace the mic name from the host like mic0:xxx, mic1:xxx to the real mic name
        # which defined in xCAT
        foreach (@$output) {
            foreach my $line (split /\n/, $_) {
                if ($line =~ /([^:]*):\s*([^:]*):(.*)/) {
                    my $host = $1;
                    my $mic = $2;
                    my $msg = $3;
                    chomp ($host);
                    chomp ($mic);
                    chomp ($msg);
                    if ($mic =~ /^mic\d+/) {
                        my $micid = $mic;
                        $micid =~ s/[^\d]*//g;
                        my $micname = $host2mic->{$host}{$micid};
                        xCAT::MsgUtils->message("I", {data=>["$micname: $msg"]}, $callback);
                    } else {
                        xCAT::MsgUtils->message("E", {data=>[$line]}, $callback);
                    }
                }
            }
        }
    } # end of foreach host class with same mic list
}

# display the inventory information for mic
# this subroutine will handle the both rinv and rvitals commands
sub rinv {
    my $request = shift;
    my $callback = shift;
    my $subreq = shift;
    my $host2mic = shift;

    my $command = $request->{command}->[0];
    my $usage_string;
    my %validargs;

    # set the valid argurments and correspoding groups which can be recognized by 
    # micinfo command
    if ($command eq "rinv") {
        $usage_string = "rinv noderange {system|ver|board|core|gddr|all}";
        %validargs = ("system" => "System", "ver" => "Version", "board" => "Board", 
                      "core" => "Core", "gddr" => "GDDR", "all" => "all");
    } elsif ($command eq "rvitals") {
        $usage_string = "rvitals noderange {thermal|all}";
        %validargs = ("thermal" => "Thermal", "all" => "all");
    }

    my @args;
    if (defined ($request->{arg})) {
        @args = @{$request->{arg}};
        unless (@args) {
            push @args, "all";
        }
    } else {
        push @args, "all";
    }
    my @groups; # the groups name which could be displayed by micinfo 
    foreach my $arg (@args) {
        if ($validargs{$arg}) {
            if ($arg eq "all") {
                if ($command eq "rinv") {
                    push @groups, ("System", "Version", "Board", "Core", "GDDR");
                } elsif ($command eq "rvitals") {
                    push @groups, ("Thermal");
                }
            } else {
                push @groups, $validargs{$arg};
            }
        } else {
            my $rsp;
            push @{$rsp->{data}}, $usage_string;
            xCAT::MsgUtils->message("E", $rsp, $callback);
            return;
        }
    }

    my %groupflag;
    foreach (@groups) {
        $groupflag{$_} = 1;
    }

    # run micinfo on the host to get all the mic information first
    my @hosts = (keys  %$host2mic);
    my $output = xCAT::Utils->runxcmd({ command => ['xdsh'],
                                           node => \@hosts,
                                           arg => ["/usr/bin/micinfo"]}, $subreq, 0, 1);

    # classify all the output with the host name
    my %outofhost;
    foreach (@$output) {
        if (/\@\@\@/) { next; } # remove the part of ssh connection warning
        foreach my $line (split /\n/, $_) {
            $line =~ s/(\s)+/ /g;
            if ($line =~ /([^:]*):(.*)/) {
                push @{$outofhost{$1}}, $2;
            }
        }
    }

    foreach my $host (keys %outofhost) {
        my $micid;
        my $micname; # which is the node name of the mic
        my $curgroup;
        my @sysinfo;
        my $rsp;
        foreach (@{$outofhost{$host}}) {
            if (/^\s*Device No:\s*(\d+)/) {
                # get the mic name
                $micid = $1;
                $micname = $host2mic->{$host}->{$micid};

                # display the System infor first
                foreach (@sysinfo) {
                    if ($groupflag{'System'} == 1 && $micname) {
                        push @{$rsp->{data}}, "$micname: $_";
                    }
                }
            } elsif (/^\s*System Info$/) {
                $curgroup = "System";
            } elsif (/^\s*Version$/) {
                $curgroup = "Version";
            } elsif (/^\s*Board$/) {
                $curgroup = "Board";
            } elsif (/^\s*Cores$/) {
                $curgroup = "Core";
            } elsif (/^\s*Thermal$/) {
                $curgroup = "Thermal";
            }  elsif (/^\s*GDDR$/) {
                $curgroup = "GDDR";
            } else {
                my $msg = $_;
                if ($msg =~ /^\s*$/) { next;}
                $msg =~ s/^\s*//;
                if ($curgroup eq "System") {
                    push @sysinfo, $msg;
                } elsif ($groupflag{$curgroup} == 1 && $micname) {
                    push @{$rsp->{data}}, "$micname: $msg";
                }
            }
        }
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }
}

# do the copy of metarials (root file system, flash image, kernel) for mic support from mpss tar 
sub copytar {
    my $request = shift;
    my $callback = shift;

    my $args = $request->{arg};
    my ($osname, $file);
    if ($args) {
        @ARGV=@{$args};
        GetOptions('n=s' => \$osname,
                   'f=s' => \$file);
    }

    # get the mpss version and target host os version
    my ($mpssver, $targetos, $targetosver);
    my $tarfilename = basename ($file);
    if ($tarfilename =~ /mpss-([\d\.-]+)-(rhel|suse)-([\d\.-]+)\.tar/) {
        $mpssver = $1;
        $targetos = $2;
        $targetosver = $3;
    } elsif ($tarfilename =~ /mpss/) {
        if ($osname =~ /mpss-([\d\.-]+)-(rhel|suse)-([\d\.-]+)/) {
            $mpssver = $1;
            $targetos = $2;
            $targetosver = $3;
        } else {
            xCAT::MsgUtils->message("E", {error=>["The flag -n needs be specified to identify the version of mpss and target os. The value format for -n must be mpss-[mpss_versoin]-[rhel/suse]-[os_version]."], errorcode=>["1"]}, $callback);
            return;
        }
    } else {
        # not for Xeon Phi
        return;
    }
    
    my $installroot = "/install";
    my @entries =  xCAT::TableUtils->get_site_attribute("installdir");
    my $t_entry = $entries[0];
    if ( defined($t_entry) ) {
        $installroot = $t_entry;
    }
    
    my $destdir = "$installroot/mpss$mpssver";
    mkpath ($destdir);

    # creae the default dirs in the mpss image dir
    mkpath ("$destdir/common");
    mkpath ("$destdir/overlay/package");
    mkpath ("$destdir/overlay/rpm");
    `/bin/touch "$destdir/common.filelist"`;

    # extract the files from the mpss tar file
    #my $cmd = "tar xvf $file -C $tmpdir";
    #my @output = xCAT::Utils->runcmd($cmd, -1);
    #if ($::RUNCMD_RC != 0) {
    #    xCAT::MsgUtils->message("E", {error=>["Error when run [$cmd], @output"], errorcode=>["1"]}, $callback);
    #    return 1;
    #}

    # get the rpm packages intel-mic-gpl and intel-mic-flash which include the files for root file system, flash ...
    #my @micgpl = <$tmpdir/*/intel-mic-gpl*>;
    #my @micflash = <$tmpdir/*/intel-mic-flash*>;
    #unless (-r $micgpl[0] && -r $micflash[0]) {
    #    xCAT::MsgUtils->message("E", {error=>["Error: Cannot get the rpm files intel-mic-gpl or intel-mic-flash from the tar file."], errorcode=>["1"]}, $callback);
    #    return 1;
    #}

    # extract the files from rpm packages
    #$cmd = "cd $destdir; rpm2cpio $micgpl[0] | cpio -idum; rpm2cpio $micflash[0] | cpio -idum";
    #@output = xCAT::Utils->runcmd($cmd, -1);
    #if ($::RUNCMD_RC != 0) {
    #    xCAT::MsgUtils->message("E", {error=>["Error when run [$cmd], @output"], errorcode=>["1"]}, $callback);
    #    return 1;
    #}

    

    # 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 %values;
    $values{'imagetype'} = "linux";
    $values{'provmethod'} = "netboot";
    $values{'description'} = "Linux for Intel mic";
    $values{'osname'} = "Linux";
    $values{'osvers'} = "mic$mpssver";
    $values{'osarch'} = "x86_64";
    $values{'profile'} = "compute";

    # set the osdistroname attr which will be used to get the repo path for the rpm install in osimage
    my $osver;
    if ($targetos =~ /rhel/) {
        $osver = "rhels".$targetosver;
    } elsif ($targetos =~ /suse/) {
        $osver = "sles".$targetosver;
    }
    $values{'osdistroname'} = $osver."-x86_64";

    my $imagename = "mpss$mpssver-$osver-compute";
    $oitab->setAttribs({'imagename' => $imagename}, \%values);

    my $litab = xCAT::Table->new('linuximage');
    unless ($litab) {
        xCAT::MsgUtils->message("E", {error=>["Error: Cannot open table linuximage."], errorcode=>["1"]}, $callback);
        return 1;
    }

    my $otherpkgdir = "$installroot/post/otherpkgs/mic$mpssver/x86_64";
    
    # set a default package list
    my $pkglist = "$::XCATROOT/share/xcat/netboot/mic/compute.pkglist";
    $litab->setAttribs({'imagename' => $imagename}, {'pkgdir' => $destdir, 'pkglist' => $pkglist, 'otherpkgdir' => $otherpkgdir});

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

# get the console configuration for rcons: 
# see /opt/xcat/share/xcat/cons/mic
sub getcons {
    my $request = shift;
    my $callback = shift;

    my $node = $request->{node}->[0];
    my $mictab = xCAT::Table->new("mic");
    unless ($mictab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the mic table."], errorcode=>["1"]}, $callback);
        return;
    }

    # get the console parameters
    my $sconsparms = {node=>[{name=>[$node]}]};
    my $mictabhash = $mictab->getNodeAttribs($node,['host', 'id']);
    if (defined ($mictabhash->{'host'})) {
        $sconsparms->{node}->[0]->{sshhost} = [$mictabhash->{'host'}];
    }
    if (defined ($mictabhash->{'id'})) {
        $sconsparms->{node}->[0]->{psuedotty} = ["/dev/ttyMIC".$mictabhash->{'id'}];
    }
    $sconsparms->{node}->[0]->{baudrate}=["115200"];

    $callback->($sconsparms);
}

# do the flash of firmware for mic
sub rflash {
    my $request = shift;
    my $callback = shift;
    my $subreq = shift;
    my $host2mic = shift;

    my $usage_string = "rflash noderange";

    my $nodes = $request->{'node'};
    
    # get the provision method for all the nodes
    my $nttab = xCAT::Table->new("nodetype");
    unless ($nttab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the nodetype table."], errorcode=>["1"]}, $callback);
        return;
    }
    
    my $nthash = $nttab->getNodesAttribs($nodes,['provmethod']);
    foreach my $node (@$nodes) {
        unless (defined ($nthash->{$node}->[0]->{'provmethod'})) {
            xCAT::MsgUtils->message("E", {error=>["The provmethod for the node $node should be set before the rflash."], errorcode=>["1"]}, $callback);
            return;
        }
    }

    # get pkgdir for the osimage
    my $litab = xCAT::Table->new("linuximage");
    unless ($litab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the linuximage table."], errorcode=>["1"]}, $callback);
        return;
    }
    my @osimages = $litab->getAllAttribs("imagename", "pkgdir");
    my %osimage;
    foreach (@osimages) {
        $osimage{$_->{'imagename'}} = $_->{'pkgdir'};
    }

    # get the tftp dir and create the path for the mic configuration files
    my $tftpdir = "/tftpboot";
    my @entries =  xCAT::TableUtils->get_site_attribute("$tftpdir");
    my $t_entry = $entries[0];
    if ( defined($t_entry) ) {
        $tftpdir = $t_entry;
    }
    mkpath ("$tftpdir/xcat/miccfg/");

    # generate the rflash configuration files for each host
    # the configureation file should have the following format
    #miclist=mic0
    #0:name=host1-mic0
    #imgpath=/install/mpss3.new
    my @hosts = (keys %$host2mic);
    foreach my $host (@hosts) {
        my @cfgfile;
        push @cfgfile, "#XCAT MIC FLASH CONFIGURATION FILE#";
        my $miclist = $host2mic->{$host}{'ids'};
        my @micids = split (/ /, $miclist);
        $miclist =~ s/(\d+)/mic$1/g;
        $miclist =~ s/ /,/g;
        $miclist =~ s/^,//;
        
        push @cfgfile, "miclist=$miclist";

        my $osimg;
        foreach my $micid (@micids) {
            if ($micid eq '') { next;}
            my $micname = $host2mic->{$host}{$micid};
            # get the pkgdir of the osimage which set to the mic node.
            # and make sure the osimage which set to the mic shold be same for all the mics on one host
            if ($osimg) {
                if ($osimg ne $nthash->{$micname}->[0]->{'provmethod'}) {
                    xCAT::MsgUtils->message("E", {error=>["The provmethod for the nodes in the same host should be same."], errorcode=>["1"]}, $callback);
                    return;
                }
            } else {
                $osimg = $nthash->{$micname}->[0]->{'provmethod'};
            }
            push @cfgfile, "$micid:name=$micname";
        }
        push @cfgfile, "imgpath=$osimage{$osimg}";
        
        if (open (CFG, ">$tftpdir/xcat/miccfg/micflash.$host")) {
            foreach (@cfgfile) {
                print CFG $_."\n";
            }
            close (CFG);
        } else {
            xCAT::MsgUtils->message("E", {error=>["Cannot open the file $tftpdir/xcat/miccfg/micflash.$host to write."], errorcode=>["1"]}, $callback);
            return;
        }
    }

    # run the cmd on the host to flash the mic
    my @args = ("-s", "-v", "-e");
    push @args, "$::XCATROOT/sbin/flashmic";
    my $master = $request->{'_xcatdest'};
    push @args, ("-m", "$master");
    push @args, ("-p", "$tftpdir/xcat/miccfg");

    $CALLBACK = $callback;
    $subreq->({ command => ['xdsh'],
                      node => \@hosts,
                      arg => \@args }, \&michost_cb);
}

# run the nodeset to genimage the osimage for each mic
# it gets on host first and mount the root file system from MN:/install/mpssxxx
# to host, and then configure and generate the mic specific osimage for each mic.
#
# If running 'odeset noderange osimage=imagename', the imagename will be used to generate
# image for mic and imagename will be set to the provmethod attr for the mic. If no 'osimage=xx' 
# is specified, the osimage will be get from provmethod attr.
sub nodeset {
    my $request = shift;
    my $callback = shift;
    my $subreq = shift;
    my $host2mic = shift;

    my $usage_string = "nodeset noderange osimage[=imagename]";

    my $nodes = $request->{'node'};
    my $args = $request->{arg};
    my $setosimg;
    foreach (@$args) {
        if (/osimage=(.*)/) {
            $setosimg = $1;
        }
    }
    
    # get the provision method for all the nodes
    my $nttab = xCAT::Table->new("nodetype");
    unless ($nttab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the nodetype table."], errorcode=>["1"]}, $callback);
        return;
    }

    # if the osimage=xxx has been specified, then set it to the provmethod attr for the mic.
    if ($setosimg) {
        my %setpmethod;
        foreach (@$nodes) {
            $setpmethod{$_}{'provmethod'} = $setosimg;
        }
        $nttab->setNodesAttribs(\%setpmethod);
    }

    # get the provision method from nodetype table
    my $nthash = $nttab->getNodesAttribs($nodes,['provmethod']);
    foreach my $node (@$nodes) {
        unless (defined ($nthash->{$node}->[0]->{'provmethod'})) {
            xCAT::MsgUtils->message("E", {error=>["The <image name> must be specified in [nodeset <node> osimage=<image name>] or it must be set in the provmethod attribute before running nodeset command."], errorcode=>["1"]}, $callback);
            return;
        }
    }

    # get the virtual bridge, onboot, vlog information for the mic nodes
    my $mictab = xCAT::Table->new("mic");
    unless ($mictab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the mic table."], errorcode=>["1"]}, $callback);
        return;
    }
    my $michash = $mictab->getNodesAttribs($nodes, ['bridge', 'onboot', 'vlog', 'powermgt']);

    # get ip for the mic nodes from hosts table
    my $hosttab = xCAT::Table->new("hosts");
    unless ($hosttab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the host table."], errorcode=>["1"]}, $callback);
        return;
    }
    my $hosthash = $hosttab->getNodesAttribs($nodes, ['ip']);

    # get pkgdir from the osimage
    my $litab = xCAT::Table->new("linuximage");
    unless ($litab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the linuximage table."], errorcode=>["1"]}, $callback);
        return;
    }
    my @osimages = $litab->getAllAttribs("imagename", "pkgdir");
    my %osimage;
    foreach (@osimages) {
        $osimage{$_->{'imagename'}} = $_->{'pkgdir'};
    }

    # get the bridge configuration from nics table
    my $nicstab = xCAT::Table->new("nics");
    unless ($nicstab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the nics table."], errorcode=>["1"]}, $callback);
        return;
    }

    # get mic mount entries from litefile table
    my $lftab = xCAT::Table->new("litefile");
    unless ($lftab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the litefile table."], errorcode=>["1"]}, $callback);
        return;
    }
    my @lfentall = $lftab->getAttribs({'image'=>'ALL'}, 'file', 'options');

    # get the nfs server from statelite table
    my $sltab = xCAT::Table->new('statelite');
    unless ($sltab) {
        xCAT::MsgUtils->message("E", {error=>["Cannot open the statelite table."], errorcode=>["1"]}, $callback);
        return;
    }
    my $slhash = $sltab->getNodesAttribs($nodes, ['statemnt']);

    # get the tftp dir and create the path for mic configuration
    my $tftpdir = "/tftpboot";
    my @entries =  xCAT::TableUtils->get_site_attribute("$tftpdir");
    my $t_entry = $entries[0];
    if ( defined($t_entry) ) {
        $tftpdir = $t_entry;
    }
    mkpath ("$tftpdir/xcat/miccfg/");

    # generate the configuration file for each host
    # the configureation file should have the following format
    #miclist=mic0
    #0:ip=10.10.10.1|br=mybr0|name=host1-mic0|onboot=yes|vlog=no
    #imgpath=/install/mpss3.1
    #overlay=ol1
    my %imghash; # cache of osimage information
    my @hosts = (keys %$host2mic);
    # get the bridge configuration from nics table, use host as key
    my $nicshash = $nicstab->getNodesAttribs(\@hosts, ['nicips', 'nictypes']);
    foreach my $host (@hosts) {
        my @cfgfile;
        push @cfgfile, "#XCAT MIC CONFIGURATION FILE#";
        my $miclist = $host2mic->{$host}{'ids'};
        my @micids = split (/ /, $miclist);
        $miclist =~ s/(\d+)/mic$1/g;
        $miclist =~ s/ /,/g;
        $miclist =~ s/^,//;
        
        push @cfgfile, "miclist=$miclist";

        my $osimg;
        foreach my $micid (@micids) {
            if ($micid eq '') { next;}
            my $micname = $host2mic->{$host}{$micid};
            # get the pkgdir of the osimage which set to the mic node,
            # and make sure the osimage which set to the mic shold be same for all the mics on one host
            if ($osimg) {
                if ($osimg ne $nthash->{$micname}->[0]->{'provmethod'}) {
                    xCAT::MsgUtils->message("E", {error=>["The provmethod for the mic nodes which located in the same host should be same."], errorcode=>["1"]}, $callback);
                    return;
                }
            } else {
                $osimg = $nthash->{$micname}->[0]->{'provmethod'};
            }

            # get the ip of the mic node
            # get the ip from system resolution first, if failed, get from host table
            my $micip = xCAT::NetworkUtils->getipaddr($micname);
            unless ($micip) {
                $micip = $hosthash->{$micname}->[0]->{'ip'};
                unless ($micip) {
                    xCAT::MsgUtils->message("E", {error=>["Cannot get the IP from hosts table or system resolution for the $micname."], errorcode=>["1"]}, $callback);
                    return;
                }
            }

            # get the virtual bridge for the mic node
            my $micbrg = $michash->{$micname}->[0]->{'bridge'};
            unless ($micbrg) {
                xCAT::MsgUtils->message("E", {error=>["Cannot get the micbridge for the $micname."], errorcode=>["1"]}, $callback);
                return;
            }

            # get the bridge configuration
            my ($brgip, $brgtype);
            if (defined ($nicshash->{$host}) && defined ($nicshash->{$host}->[0]->{'nicips'})) {
                foreach (split(/,/, $nicshash->{$host}->[0]->{'nicips'})) {
                    if (/$micbrg!(.+)/) {
                        $brgip = $1;
                    }
                }
                push @cfgfile, "brgip=$brgip";

                foreach (split(/,/, $nicshash->{$host}->[0]->{'nictypes'})) {
                    if (/$micbrg!(.+)/) {
                        $brgtype = $1;
                    }
                }
                push @cfgfile, "brgtype=$brgtype";
            }

            # generate the mic specific entry in the configuration file
            my $micattrs = "$micid:ip=$micip|br=$micbrg|name=$micname";
            if (defined ($michash->{$micname}->[0]->{'onboot'})) {
                $micattrs .= "|onboot=$michash->{$micname}->[0]->{'onboot'}";
            }
            if (defined ($michash->{$micname}->[0]->{'vlog'})) {
                $micattrs .= "|vlog=$michash->{$micname}->[0]->{'vlog'}";
            }
            if (defined ($michash->{$micname}->[0]->{'powermgt'})) {
                $micattrs .= "|powermgt=$michash->{$micname}->[0]->{'powermgt'}";
            }
            if (defined ($slhash->{$micname}->[0]->{'statemnt'})) {
                $micattrs .= "|statemnt=$slhash->{$micname}->[0]->{statemnt}";
            }
            push @cfgfile, $micattrs;

        }
        push @cfgfile, "imgpath=$osimage{$osimg}";

        # get all the overlay entries for the osimage and do the cache for image
        # search all the dir in the /install/<image>/overlay dir, the dir tree in 'overlay' should be following:
        # |--mnt
        # |  `--system (files for system boot)
        # |  |--common.filelist
        # |  `--common
        # |  `--overlay
        # |       `--overlay
        # |           `--rpm
        # |           `--simple
        # |               |--simple.cfg  (the file must be multiple lines of 'a->b' format; 'a' is dir name in simple/, 'b' is the path on mic for 'a'
        # |           `--package
        # |                |--the base file for fs
        # |                `--opt/mic
        # |                     |--yy.filelist
        # |                     `--yy
        # |           |--xx.filelist
        # |           `--xx
        #the system dir (system dir includes the files
        # which generated by genimage command, and will be copied to mic osimage separated)
            if (! -d "$osimage{$osimg}/system") {
                xCAT::MsgUtils->message("E", {error=>["Missed system directory in $osimage{$osimg}. Did you miss to run genimage command?"], errorcode=>["1"]}, $callback);
                return;
            }
        if (defined ($imghash{$osimg}{'ollist'})) {
            push @cfgfile, "overlay=$imghash{$osimg}{'ollist'}";
        } else {
            my @items = <$osimage{$osimg}/overlay/*>;
            my $ollist; # overlay list
            foreach my $obj (@items) {
                my $objname = basename($obj);
                if (-d $obj) {
                    if ($objname eq "rpm") {
                        $ollist .= ",rpm:$objname";
                    } elsif ($objname eq "simple") {
                        if (-f "$osimage{$osimg}/overlay/simple/simple.cfg") {
                            open (SIMPLE, "<$osimage{$osimg}/overlay/simple/simple.cfg");
                            while (<SIMPLE>) {
                                if (/(.+)->(.+)/) {
                                    $ollist .= ",simple:$_";
                                }
                            }
                        }
                    } elsif ($objname eq "package") {
                        my @pfl = <$osimage{$osimg}/overlay/package/opt/mic/*.filelist>;
                        foreach my $filelist (@pfl) {
                            $filelist = basename($filelist);
                            if ($filelist =~ /(.+)\.filelist/) {
                                $ollist .= ",pfilelist:$1";
                            }
                        }
                    }
                } else {
                    if ($objname =~ /(.+)\.filelist/) {
                        $ollist .= ",filelist:$1";
                    }
                }
            }
            $ollist =~ s/^,//;

            $imghash{$osimg}{'ollist'} = $ollist;
            push @cfgfile, "overlay=$ollist";
        }

        # generate the nfs mount entries for mic node
        my @lfentimg = $lftab->getAttribs({'image'=>$osimg}, 'file', 'options');
        foreach my $ent (@lfentall, @lfentimg) {
            if (defined ($ent->{'options'}) && $ent->{'options'} eq "micmount") {
               if (defined ($ent->{'file'})) {
                   push @cfgfile, "micmount=$ent->{file}";
               }
            }
        }

        if (open (CFG, ">$tftpdir/xcat/miccfg/miccfg.$host")) {
            foreach (@cfgfile) {
                print CFG $_."\n";
            }
            close (CFG);
        } else {
            xCAT::MsgUtils->message("E", {error=>["Cannot open the file $tftpdir/xcat/miccfg/miccfg.$host to write."], errorcode=>["1"]}, $callback);
            return;
        }

    }

    # run the cmd on the host to configure the mic
    my @args = ("-s", "-v", "-e");
    push @args, "$::XCATROOT/sbin/configmic";
    my $master = $request->{'_xcatdest'};
    push @args, ("-m", "$master");
    push @args, ("-p", "$tftpdir/xcat/miccfg");

    $CALLBACK = $callback;
    $subreq->({ command => ['xdsh'],
                      node => \@hosts,
                      #arg => \@args }, $callback);
                      arg => \@args }, \&michost_cb);
}

# Handle the return message from xdsh command for 'configmic' and 'flashmic' scripts to
# replace the message with correct mic name at head. And remove the unnecessary messages 
sub michost_cb {
    no strict;
    my $response = shift;
    my $rsp;
    foreach my $type (keys %$response)
    {
        my @newop;
        foreach my $output (@{$response->{$type}})
        {
            # since the remote run of mic configuration will be closed by force in the configmic
            # script, remove the error message from xdsh
            if ($type eq "error" && $output =~ /(remote shell had error code|remote Command had return code)/) {
                delete $response->{error};
                delete $rsp->{error};
            } elsif ($type eq "data" && $output =~ /Connection to(.*)closed by remote host/) {
                delete $response->{error};
                delete $response->{errorcode};
                delete $rsp->{error};
                delete $rsp->{errorcode};
            } else {
                $output =~ s/^[^:]+:\s*MICMSG://g;
                $output =~ s/\n[^:]+:\s*MICMSG:/\n/g;
                push @newop, $output;
            }
        }
        $rsp->{$type} = \@newop;
    }
    $CALLBACK->($rsp);
}

1;