package xCAT_plugin::iscsi;
use strict;
use xCAT::Table;
use xCAT::TableUtils;
use Socket;
use File::Path;
use File::Basename;
use Getopt::Long;
Getopt::Long::Configure("bundling");
Getopt::Long::Configure("pass_through");


sub handled_commands {
    return {
        "setupiscsidev" => "iscsi",
    };
}

sub get_tid {

    #generate a unique tid given a node for tgtadm to use
    my $node = shift;
    my $tid = unpack("N", inet_aton($node));
    $tid = $tid & ((2**31) - 1);
    return $tid;
}

sub preprocess_request {
    my $request  = shift;
    my $callback = shift;
    my @requests = ();
    my %iscsiserverhash;
    if ($request->{_xcatpreprocessed}->[0] == 1) { return [$request]; }
    my $iscsitab = xCAT::Table->new('iscsi');
    foreach my $node (@{ $request->{node} }) {
        my $tent = $iscsitab->getNodeAttribs($node, ['server']);
        if ($tent and $tent->{server}) {
            $iscsiserverhash{ $tent->{server} }->{$node} = 1;
        } else {
            $callback->({ error => ["No iscsi.server for $node, aborting request"] });
            return [];
        }
    }
    foreach my $iscsis (keys %iscsiserverhash) {
        my $reqcopy = {%$request};
        $reqcopy->{'_xcatdest'} = $iscsis;
        $reqcopy->{_xcatpreprocessed}->[0] = 1;
        $reqcopy->{node} = [ keys %{ $iscsiserverhash{$iscsis} } ];
        push @requests, $reqcopy;
    }
    return \@requests;
}

sub process_request {
    my $request  = shift;
    my $callback = shift;
    unless (-x "/usr/sbin/tgtadm") {
        $callback->({ error => "/usr/sbin/tgtadm does not exist, iSCSI plugin currently requires it, please install scsi-target-utils package under CentOS, RHEL, or Fedora.  SLES support is not yet implemented", errorcode => [1] });
        return;
    }
    my $lunsize = 4096;
    if ($request->{arg}) {
        @ARGV = @{ $request->{arg} };
        GetOptions(
            "size|s=i" => \$lunsize,
        );
    }
    my $iscsitab = xCAT::Table->new('iscsi');
    my @nodes    = @{ $request->{node} };

    my $nd          = xCAT::NetworkUtils->getNodeDomains(\@nodes);
    my %nodedomains = %{$nd};

    my $iscsiprefix;
    my @entries = xCAT::TableUtils->get_site_attribute("iscsidir");
    my $t_entry = $entries[0];
    if (defined($t_entry)) {
        $iscsiprefix = $t_entry;
    }
    foreach my $node (@nodes) {
        my $fileloc;
        my %rsp;
        %rsp = (name => [$node]);
        my $iscsient = $iscsitab->getNodeAttribs($node, ['file']);
        if ($iscsient and $iscsient->{file}) {
            $fileloc = $iscsient->{file};
            unless ($fileloc =~ /^\//) {
                unless ($iscsiprefix) {
                    $rsp{error} = ["$node: Unable to identify file to back iSCSI LUN, no iscsidir in site table and iscsi.file entry for node is a relative path"];
                    $rsp{errorcode} = [1];
                    $callback->({ node => [ \%rsp ] });
                    %rsp = (name => [$node]);
                    next;
                }
                $fileloc = $iscsiprefix . "/" . $iscsient->{file};
            }
        } else {
            unless ($iscsiprefix) {
                $rsp{error} = ["$node: Unable to identify file to back iSCSI LUN, no iscsidir in site table nor iscsi.file entry for node  (define at least either)"];
                $rsp{errorcode} = [1];
                $callback->({ node => [ \%rsp ] });
                %rsp = (name => [$node]);
                next;
            }
            $fileloc = "$iscsiprefix/$node";
            $iscsitab->setNodeAttribs($node, { file => $fileloc });
        }
        unless (-d dirname($fileloc)) {
            mkpath dirname($fileloc);
        }
        unless (-f $fileloc) {
            $rsp{name} = [$node];
            $rsp{data} = ["Creating $fileloc ($lunsize MB)"];
            $callback->({ node => [ \%rsp ] });
            %rsp = (name => [$node]);
            $lunsize -= 1;
            my $rc = system("dd if=/dev/zero of=$fileloc bs=1M count=1 seek=$lunsize");
            $lunsize += 1;
            if ($rc) {
                $rsp{error}     = ["dd process exited with return code $rc"];
                $rsp{errorcode} = [1];
                $callback->({ node => [ \%rsp ] });
                %rsp = (name => [$node]);
                next;
            }
        }
        my $targname;
        my $lun;
        $iscsient = $iscsitab->getNodeAttribs($node, [ 'target', 'lun' ]);
        if ($iscsient and $iscsient->{target}) {
            $targname = $iscsient->{target};
        }
        if ($iscsient and defined($iscsient->{lun})) {
            $lun = $iscsient->{lun};
        } else {
            $lun = '1';
            $iscsitab->setNodeAttribs($node, { lun => $lun });
        }
        unless ($targname) {
            my @date   = localtime;
            my $year   = 1900 + $date[5];
            my $month  = $date[4];
            my $domain = $nodedomains{$node};
            $targname = "iqn.$year-$month.$domain:$node";
            $iscsitab->setNodeAttribs($node, { target => $targname });
        }
        system("tgtadm --lld iscsi --mode target --op delete --tid " . get_tid($node) . " -T $targname");
        my $rc = system("tgtadm --lld iscsi --mode target --op new --tid " . get_tid($node) . " -T $targname");
        if ($rc) {
            $rsp{error} = [ "tgtadm --lld iscsi --mode target --op new --tid " . get_tid($node) . " -T $targname returned $rc" ];
            if ($rc == 27392) {
                push @{ $rsp{error} }, "This likely indicates the need to do /etc/init.d/tgtd start";
            }
            $rsp{errorcode} = [1];
            $callback->({ node => [ \%rsp ] });
            %rsp = (name => [$node]);
            next;
        }
        $rc = system("tgtadm --lld iscsi --mode logicalunit --op new --tid " . get_tid($node) . " --lun 1 --backing-store $fileloc --device-type disk");
        if ($rc) {
            $rsp{error} = [ "tgtadm --lld iscsi mode logicalunit --op new --tid " . get_tid($node) . " --lun 1 --backing-store $fileloc returned $rc" ];
            $rsp{errorcode} = [1];
            $callback->({ node => [ \%rsp ] });
            %rsp = (name => [$node]);
            next;
        }
        $rc = system("tgtadm --lld iscsi --mode target --op bind --tid " . get_tid($node) . " -I " . inet_ntoa(inet_aton($node)));
        if ($rc) {
            $rsp{error} = [ "tgtadm --lld iscsi --mode target --op bind --tid " . get_tid($node) . " -I " . inet_ntoa(inet_aton($node)) . " returned $rc" ];
            $rsp{errorcode} = [1];
            $callback->({ node => [ \%rsp ] });
            %rsp = (name => [$node]);
        } else {
            $rsp{data} = ["iSCSI LUN configured"];
            $callback->({ node => [ \%rsp ] });
            %rsp = (name => [$node]);
        }
    }
}

1;