# IBM(c) 2013 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT_plugin::openstack;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use xCAT::Utils;
use xCAT::TableUtils;
use xCAT::SvrUtils;
use xCAT::NetworkUtils;
use xCAT::Table;
use Data::Dumper;
use File::Path;
use File::Copy;
use Getopt::Long;
Getopt::Long::Configure("bundling");
Getopt::Long::Configure("pass_through");


sub handled_commands {
    return {
        opsaddbmnode => "openstack",    #external command
        opsaddimage  => "openstack",    #external command
        deploy_ops_bm_node => "openstack", #internal command called from the baremetal driver
        cleanup_ops_bm_node => "openstack", #internal command called from the baremetal driver
      }
}

sub process_request {
    my $request  = shift;
    my $callback = shift;
    my $doreq    = shift;
    my $command  = $request->{command}->[0];

    if ($command eq "opsaddbmnode") {
        return opsaddbmnode($request, $callback, $doreq);
    } elsif ($command eq "opsaddimage") {
        return opsaddimage($request, $callback, $doreq);
    } elsif ($command eq "deploy_ops_bm_node") {
        return deploy_ops_bm_node($request, $callback, $doreq);
    } elsif ($command eq "cleanup_ops_bm_node") {
        return cleanup_ops_bm_node($request, $callback, $doreq);
    } else {
        $callback->({ error => ["Unsupported command: $command."], errorcode => [1] });
        return 1;
    }
}


#-------------------------------------------------------------------------------

=head3  opsaddbmnode
     This function takes the xCAT nodes and register them
     as the OpenStack baremetal nodes
=cut

#-------------------------------------------------------------------------------
sub opsaddbmnode {
    my $request  = shift;
    my $callback = shift;
    my $doreq    = shift;

    @ARGV = @{ $request->{arg} };
    Getopt::Long::Configure("bundling");
    Getopt::Long::Configure("no_pass_through");

    my $help;
    my $version;
    my $verbose;
    my $host;

    if (!GetOptions(
            'h|help'    => \$help,
            'v|version' => \$version,
            'V|verbose' => \$verbose,
            's=s'       => \$host,
        ))
    {
        &opsaddbmnode_usage($callback);
        return 1;
    }

    # display the usage if -h or --help is specified
    if ($help) {
        &opsaddbmnode_usage($callback);
        return 0;
    }

    # display the version statement if -v or --verison is specified
    if ($version)
    {
        my $rsp = {};
        $rsp->{data}->[0] = xCAT::Utils->Version();
        $callback->($rsp);
        return 0;
    }

    if (!$request->{node}) {
        $callback->({ error => ["Please specify at least one node."], errorcode => [1] });
        return 1;
    }
    if (!$host) {
        $callback->({ error => ["Please specify the OpenStack compute host name with -s flag."], errorcode => [1] });
        return 1;
    }

    my $nodes = $request->{node};

    #get node mgt
    my $nodehmhash;
    my $nodehmtab = xCAT::Table->new("nodehm");
    if ($nodehmtab) {
        $nodehmhash = $nodehmtab->getNodesAttribs($nodes, [ 'power', 'mgt' ]);
    }

    #get bmc info for the nodes
    my $ipmitab = xCAT::Table->new("ipmi", -create => 0);
    my $tmp_ipmi;
    if ($ipmitab) {
        $tmp_ipmi = $ipmitab->getNodesAttribs($nodes, [ 'bmc', 'username', 'password' ], prefetchcache => 1);

        #print Dumper($tmp_ipmi);
    } else {
        $callback->({ error => ["Cannot open the ipmi table."], errorcode => [1] });
        return 1;
    }

    #get mac for the nodes
    my $mactab = xCAT::Table->new("mac", -create => 0);
    my $tmp_mac;
    if ($mactab) {
        $tmp_mac = $mactab->getNodesAttribs($nodes, ['mac'], prefetchcache => 1);

        #print Dumper($tmp_mac);
    } else {
        $callback->({ error => ["Cannot open the mac table."], errorcode => [1] });
        return 1;
    }

    #get cpu, memory and disk info for the nodes
    my $hwinvtab = xCAT::Table->new("hwinv", -create => 0);
    my $tmp_hwinv;
    if ($hwinvtab) {
        $tmp_hwinv = $hwinvtab->getNodesAttribs($nodes, [ 'cpucount', 'memory', 'disksize' ], prefetchcache => 1);

        #print Dumper($tmp_hwinv);
    } else {
        $callback->({ error => ["Cannot open the hwinv table."], errorcode => [1] });
        return 1;
    }

    #get default username and password for bmc
    my $d_bmcuser;
    my $d_bmcpasswd;
    my $passtab = xCAT::Table->new('passwd');
    if ($passtab) {
        ($tmp_passwd) = $passtab->getAttribs({ 'key' => 'ipmi' }, 'username', 'password');
        if (defined($tmp_passwd)) {
            $d_bmcuser   = $tmp_passwd->{username};
            $d_bmcpasswd = $tmp_passwd->{password};
        }
    }

    #print "d_bmcuser=$d_bmcuser, d_bmcpasswd=$d_bmcpasswd \n";
    foreach my $node (@$nodes) {

        #collect the node infomation needed for each node, some info
        my $mgt;
        my $ref_nodehm = $nodehmhash->{$node}->[0];
        if ($ref_nodehm) {
            if ($ref_nodehm->{'power'}) {
                $mgt = $ref_nodehm->{'power'};
            } elsif ($ref_nodehm->{'mgt'}) {
                $mgt = $ref_nodehm->{'mgt'};
            }
        }

        my ($bmc, $bmc_user, $bmc_password, $mac, $cpu, $memory, $disk);
        if (($mgt) && ($mgt eq 'ipmi')) {
            my $ref_ipmi = $tmp_ipmi->{$node}->[0];
            if ($ref_ipmi) {
                if (exists($ref_ipmi->{bmc})) {
                    $bmc = $ref_ipmi->{bmc};
                }
                if (exists($ref_ipmi->{username})) {
                    $bmc_user = $ref_ipmi->{username};
                    if (exists($ref_ipmi->{password})) {
                        $bmc_password = $ref_ipmi->{password};
                    }
                } else { #take the default if they cannot be found on ipmi table
                    if ($d_bmcuser)   { $bmc_user     = $d_bmcuser; }
                    if ($d_bmcpasswd) { $bmc_password = $d_bmcpasswd; }
                }
            }
        } # else { # for hardware control point other than ipmi, just fake it in OpenStack.
          #$bmc = "0.0.0.0";
          #$bmc_user = "xCAT";
          #$bmc_password = "xCAT";
          #}

        my $ref_mac = $tmp_mac->{$node}->[0];
        if ($ref_mac) {
            if (exists($ref_mac->{mac})) {
                $mac = $ref_mac->{mac};
            }
        }

        $ref_hwinv = $tmp_hwinv->{$node}->[0];
        if ($ref_hwinv) {
            if (exists($ref_hwinv->{cpucount})) {
                $cpu = $ref_hwinv->{cpucount};
            }
            if (exists($ref_hwinv->{memory})) {
                $memory = $ref_hwinv->{memory};

                #TODO: what if the unit is not in MB? We need to convert it to MB
                $memory =~ s/MB|mb//g;
            }
            if (exists($ref_hwinv->{disksize})) {

                #The format of the the disk size is: sda:250GB,sdb:250GB or just 250GB
                #We need to get the size of the first one
                $disk = $ref_hwinv->{disksize};
                my @a = split(',', $disk);
                my @b = split(':', $a[0]);
                if (@b > 1) {
                    $disk = $b[1];
                } else {
                    $disk = $b[0];
                }

                #print "a=@a, b=@b\n";
                #TODO: what if the unit is not in GB? We need to convert it to MB
                $disk =~ s/GB|gb//g;
            }
        }

        #some info are mendatory
        if (!$mac) {
            $callback->({ error => ["Mac address is not defined in the mac table for node $node."], errorcode => [1] });
            next;
        }
        if (!$cpu) {

            #default cpu count is 1
            $cpu = 1;
        }
        if (!$memory) {

            #default memory size is 1024MB=1GB
            $memory = 1024;
        }
        if (!$disk) {

            #default disk size is 1GB
            $disk = 1;
        }

        if ($verbose) {
            my $rsp;
            push @{ $rsp->{data} }, "Attributes gathered from the xCAT database:";
            push @{ $rsp->{data} }, "  bmc=$bmc";
            push @{ $rsp->{data} }, "  bmc_user=$bmc_user";
            push @{ $rsp->{data} }, "  bmc_password=$bmc_password";
            push @{ $rsp->{data} }, "  mac=$mac";
            push @{ $rsp->{data} }, "  cpu=$cpu";
            push @{ $rsp->{data} }, "  memory=$memory";
            push @{ $rsp->{data} }, "  disk=$disk";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        }

        #call OpenStack command to add the node into the OpenStack as
        #a baremetal node.
        my $cmd_tmp = "nova baremetal-node-create";
        if ($bmc) {

            #make sure it is an ip address
            if (($bmc !~ /\d+\.\d+\.\d+\.\d+/) && ($bmc !~ /:/)) {
                $bmc = xCAT::NetworkUtils->getipaddr($bmc);
            }
            $cmd_tmp .= " --pm_address=$bmc";
        }
        if ($bmc_user) {
            $cmd_tmp .= " --pm_user=$bmc_user";
        }
        if ($bmc_password) {
            $cmd_tmp .= " --pm_password=$bmc_password";
        }
        $cmd_tmp .= " $host $cpu $memory $disk $mac";

        my $cmd = qq~$cmd_tmp~;
        if ($verbose) {
            my $rsp;
            push @{ $rsp->{data} }, "The command to run on $host:";
            push @{ $rsp->{data} }, "  $cmd";
            push @{ $rsp->{data} }, "  ";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        }

        #print "cmd=$cmd\n";
        my $output =
          xCAT::InstUtils->xcmd($callback, $doreq, "xdsh", [$host], $cmd, 0);
        if ($::RUNCMD_RC != 0) {
            my $rsp;
            push @{ $rsp->{data} }, "OpenStack creating baremetal node $node:";
            push @{ $rsp->{data} }, "$output";
            push @{ $rsp->{data} }, "The command was: $cmd";
            xCAT::MsgUtils->message("E", $rsp, $callback);
        } else {
            if (($verbose) && ($output)) {
                my $rsp;
                push @{ $rsp->{data} }, "$output";
                push @{ $rsp->{data} }, "  ";
                xCAT::MsgUtils->message("I", $rsp, $callback);
            }
        }

    }
}


#-------------------------------------------------------------------------------

=head3  opsaddimage
     This function takes the xCAT nodes and register them
     as the OpenStack baremetal nodes
=cut

#-------------------------------------------------------------------------------
sub opsaddimage {
    my $request  = shift;
    my $callback = shift;
    my $doreq    = shift;

    @ARGV = @{ $request->{arg} };
    Getopt::Long::Configure("bundling");
    Getopt::Long::Configure("no_pass_through");

    my $help;
    my $version;
    my $verbose;

    #my $cloud;
    my $ops_img_names;
    my $controller;

    if (!GetOptions(
            'h|help'    => \$help,
            'v|version' => \$version,
            'V|verbose' => \$verbose,
            'c=s'       => \$controller,
            'n=s'       => \$ops_img_names,
        ))
    {
        &opsaddimage_usage($callback);
        return 1;
    }

    # display the usage if -h or --help is specified
    if ($help) {
        &opsaddimage_usage($callback);
        return 0;
    }

    # display the version statement if -v or --verison is specified
    if ($version)
    {
        my $rsp = {};
        $rsp->{data}->[0] = xCAT::Utils->Version();
        $callback->($rsp);
        return 0;
    }

    if (@ARGV == 0) {
        $callback->({ error => ["Please specify an image name or a list of image names."], errorcode => [1] });
        return 1;
    }

    #make sure the input cloud name is valid.
    #if (!$cloud) {
    #	$callback->({error=>["Please specify the name of the cloud with -c flag."],errorcode=>[1]});
    #	return 1;
    #} else {
    #	my $cloudstab = xCAT::Table->new('clouds', -create => 0);
    #	my @et = $cloudstab->getAllAttribs('name', 'controller');
    #	if(@et) {
    #		foreach my $tmp_et (@et) {
    #			if ($tmp_et->{name} eq $cloud) {
    #				if ($tmp_et->{controller}) {
    #					$controller = $tmp_et->{controller};
    #					last;
    #				} else {
    #					$callback->({error=>["Please specify the controller in the clouds table for the cloud: $cloud."],errorcode=>[1]});
    #					return 1;
    #				}
    #			}
    #		}
    #	}

    if (!$controller) {
        $callback->({ error => ["Please specify the OpenStack controller node name with -c."], errorcode => [1] });
        return 1;
    }

    #}

    #make sure that the images from the command are valid image names
    @images = split(',', $ARGV[0]);
    @new_names = ();
    if ($ops_img_names) {
        @new_names = split(',', $ops_img_names);
    }

    #print "images=@images, new image names=@new_names, controller=$controller\n";

    my $image_hash = {};
    my $osimgtab   = xCAT::Table->new('osimage', -create => 0);
    my @et         = $osimgtab->getAllAttribs('imagename');
    if (@et) {
        foreach my $tmp_et (@et) {
            $image_hash->{ $tmp_et->{imagename} }{'xCAT'} = 1;
        }
    }
    my @bad_images;
    foreach my $image (@images) {
        if (!exists($image_hash->{$image})) {
            push @bad_images, $image;
        }
    }
    if (@bad_images > 0) {
        $callback->({ error => [ "The following images cannot be found in xCAT osimage table:\n  " . join("\n  ", @bad_images) . "\n" ], errorcode => [1] });
        return 1;
    }

    my $index = 0;
    foreach my $image (@images) {
        my $new_name = shift(@new_names);
        if (!$new_name) {
            $new_name = $image;    #the default new name is xCAT image name
        }
        my $cmd_tmp = "glance image-create --name $new_name --public --disk-format qcow2 --container-format bare --property xcat_image_name=\'$image\' < /tmp/$image.qcow2";

        my $cmd = qq~touch /tmp/$image.qcow2;$cmd_tmp~;
        if ($verbose) {
            my $rsp;
            push @{ $rsp->{data} }, "The command to run on $controller:";
            push @{ $rsp->{data} }, "   $cmd";
            push @{ $rsp->{data} }, "  ";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        }
        my $output =
          xCAT::InstUtils->xcmd($callback, $doreq, "xdsh", [$controller], $cmd, 0);
        if ($::RUNCMD_RC != 0) {
            my $rsp;
            push @{ $rsp->{data} }, "OpenStack creating image $new_name:";
            push @{ $rsp->{data} }, "$output";
            push @{ $rsp->{data} }, "The command was: $cmd";
            xCAT::MsgUtils->message("E", $rsp, $callback);
        } else {
            if (($verbose) && ($output)) {
                my $rsp;
                push @{ $rsp->{data} }, "$output";
                push @{ $rsp->{data} }, "  ";
                xCAT::MsgUtils->message("I", $rsp, $callback);
            }
        }
        my $cmd1 = qq~rm /tmp/$image.qcow2~;
        if ($verbose) {
            my $rsp;
            push @{ $rsp->{data} }, "The command to run on $controller:";
            push @{ $rsp->{data} }, "  $cmd1";
            push @{ $rsp->{data} }, "  ";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        }
        my $output1 =
          xCAT::InstUtils->xcmd($callback, $doreq, "xdsh", [$controller], $cmd1, 0);
        if (($verbose) && ($output1)) {
            my $rsp;
            push @{ $rsp->{data} }, "$output1";
            push @{ $rsp->{data} }, "  ";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        }
    }
}

#-------------------------------------------------------------------------------

=head3  deploy_ops_bm_node
	This is a internel command called by OpenStack xCAT-baremetal driver. 
	It prepares the node by adding the config_ops_bm_node postbootscript 
	to the postscript table for the node, then call nodeset and then boot 
	the node up.
=cut

#-------------------------------------------------------------------------------
sub deploy_ops_bm_node {
    my $request  = shift;
    my $callback = shift;
    my $doreq    = shift;

    @ARGV = @{ $request->{arg} };
    Getopt::Long::Configure("bundling");
    Getopt::Long::Configure("no_pass_through");

    my $node = $request->{node}->[0];

    my $help;
    my $version;
    my $img_name;
    my $hostname;
    my $fixed_ip;
    my $netmask;

    if (!GetOptions(
            'h|help'    => \$help,
            'v|version' => \$version,
            'image=s'   => \$img_name,
            'host=s'    => \$hostname,
            'ip=s'      => \$fixed_ip,
            'mask=s'    => \$netmask,
        ))
    {
        &deploy_ops_bm_node_usage($callback);
        return 1;
    }

    # display the usage if -h or --help is specified
    if ($help) {
        &deploy_ops_bm_node_usage($callback);
        return 0;
    }

    # display the version statement if -v or --verison is specified
    if ($version)
    {
        my $rsp = {};
        $rsp->{data}->[0] = xCAT::Utils->Version();
        $callback->($rsp);
        return 0;
    }

    #print "node=$node, image=$img_name, host=$hostname, ip=$fixed_ip, mask=$netmask\n";

    #validate the image name
    my $osimagetab = xCAT::Table->new('osimage', -create => 1);
    my $ref = $osimagetab->getAttribs({ imagename => $img_name }, 'imagename');
    if (!$ref) {
        $callback->({ error => ["Invalid image name: $img_name."], errorcode => [1] });
        return 1;
    }

    #check if the fixed ip is within the xCAT management network.
    #get the master ip address for the node then check if the master ip and
    #the OpenStack fixed_ip are on the same subnet.
    #my $same_nw = 0;
    #my $master = xCAT::TableUtils->GetMasterNodeName($node);
    #my $master_ip = xCAT::NetworkUtils->toIP($master);
    #if (xCAT::NetworkUtils::isInSameSubnet($master_ip, $fixed_ip, $netmask, 0)) {
    #	$same_nw = 1;
    #}


    #add config_ops_bm_node to the node's postbootscript
    my $script = "config_ops_bm_node $hostname $fixed_ip $netmask";
    add_postscript($callback, $node, $script);

    #run nodeset
    my $cmd    = qq~osimage=$img_name~;
    my $output = xCAT::Utils->runxcmd(
        { command => ["nodeset"],
            node => [$node],
            arg  => [$cmd] },
        $doreq, -1, 1);
    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "nodeset:";
        push @{ $rsp->{data} }, "$output";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    #deploy the node now, supported nodehm.mgt values: ipmi, blade,fsp, hmc.
    my $hmtab = xCAT::Table->new('nodehm');
    my $hment = $hmtab->getNodeAttribs($node, ['mgt']);
    if ($hment && $hment->{'mgt'}) {
        my $mgt = $hment->{'mgt'};
        if ($mgt eq 'ipmi') {
            deploy_bmc_node($callback, $doreq, $node);
        } elsif (($mgt eq 'blade') || ($mgt eq 'fsp')) {
            deploy_blade($callback, $doreq, $node);
        } elsif ($mgt eq 'hmc') {
            deploy_hmc_node($callback, $doreq, $node);
        } else {
            my $rsp;
            push @{ $rsp->{data} }, "Node $node: nodehm.mgt=$mgt is not supported in the OpenStack cloud.";
            xCAT::MsgUtils->message("E", $rsp, $callback);
            return 1;
        }
    } else {

        #nodehm.mgt must setup for node
        my $rsp;
        push @{ $rsp->{data} }, "Node $node: nodehm.mgt cannot be empty in order to deploy.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

}

# Deploy a rack-mounted node
sub deploy_bmc_node {
    my $callback = shift;
    my $doreq    = shift;
    my $node     = shift;

    #set boot order
    my $cmd    = qq~net~;
    my $output = xCAT::Utils->runxcmd(
        { command => ["rsetboot"],
            node => [$node],
            arg  => [$cmd] },
        $doreq, -1, 1);
    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "rsetboot:";
        push @{ $rsp->{data} }, "$output";
        xCAT::MsgUtils->message("E", $rsp, $callback);
    }

    #reboot the node
    my $cmd    = qq~boot~;
    my $output = xCAT::Utils->runxcmd(
        { command => ["rpower"],
            node => [$node],
            arg  => [$cmd] },
        $doreq, -1, 1);
    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "rpower:";
        push @{ $rsp->{data} }, "$output";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }
}

# Deploy a blade or fsp controlled node
sub deploy_blade {
    my $callback = shift;
    my $doreq    = shift;
    my $node     = shift;

    #set boot order
    my $cmd    = qq~net~;
    my $output = xCAT::Utils->runxcmd(
        { command => ["rbootseq"],
            node => [$node],
            arg  => [$cmd] },
        $doreq, -1, 1);
    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "rbootseq:";
        push @{ $rsp->{data} }, "$output";
        xCAT::MsgUtils->message("E", $rsp, $callback);
    }

    #reboot the node
    my $cmd    = qq~boot~;
    my $output = xCAT::Utils->runxcmd(
        { command => ["rpower"],
            node => [$node],
            arg  => [$cmd] },
        $doreq, -1, 1);
    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "rpower:";
        push @{ $rsp->{data} }, "$output";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }
}

# Deploy a node controlled by HMC
sub deploy_hmc_node {
    my $callback = shift;
    my $doreq    = shift;
    my $node     = shift;

    my $output = xCAT::Utils->runxcmd(
        { command => ["rnetboot"],
            node => [$node] },
        $doreq, -1, 1);
    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "rnetboot:";
        push @{ $rsp->{data} }, "$output";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }
}


#-------------------------------------------------------------------------------

=head3  cleanup_ops_bm_node
	This is a internel command called by OpenStack xCAT-baremetal driver.
	It undoes all the changes made by deploy_ops_bm_node command. It removes
	the config_ops_bmn_ode postbootscript from the postscript table for the 
	node, removes the alias ip and then power off the node.
=cut

#-------------------------------------------------------------------------------
sub cleanup_ops_bm_node {
    my $request  = shift;
    my $callback = shift;
    my $doreq    = shift;

    @ARGV = @{ $request->{arg} };
    Getopt::Long::Configure("bundling");
    Getopt::Long::Configure("no_pass_through");

    my $node = $request->{node}->[0];

    my $help;
    my $version;
    my $fixed_ip;

    if (!GetOptions(
            'h|help'    => \$help,
            'v|version' => \$version,
            'ip=s'      => \$fixed_ip,
        ))
    {
        &cleanup_ops_bm_node_usage($callback);
        return 1;
    }

    # display the usage if -h or --help is specified
    if ($help) {
        &cleanup_ops_bm_node_usage($callback);
        return 0;
    }

    # display the version statement if -v or --verison is specified
    if ($version)
    {
        my $rsp = {};
        $rsp->{data}->[0] = xCAT::Utils->Version();
        $callback->($rsp);
        return 0;
    }

    #print "node=$node, ip=$fixed_ip\n";

    #removes the config_ops_bm_node postbootscript from the postscripts table
    remove_postscript($callback, $node, "config_ops_bm_node");


    #run updatenode to remove the ip alias
    my $cmd    = qq~-P deconfig_ops_bm_node $fixed_ip~;
    my $output = xCAT::Utils->runxcmd(
        { command => ["updatenode"],
            node => [$node],
            arg  => [$cmd] },
        $doreq, -1, 1);
    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "updatenode:";
        push @{ $rsp->{data} }, "$output";
        xCAT::MsgUtils->message("E", $rsp, $callback);
    }

    #turn the node power off
    $ssh_ok = 0;
    my $cmd    = qq~stat~;
    my $output = xCAT::Utils->runxcmd(
        { command => ["rpower"],
            node => [$node],
            arg  => [$cmd] },
        $doreq, -1, 1);

    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "rpower:";
        push @{ $rsp->{data} }, "$output";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    } else {
        if ($output !~ /: off/) {

            #power off the node
            my $cmd    = qq~off~;
            my $output = xCAT::Utils->runxcmd(
                { command => ["rpower"],
                    node => [$node],
                    arg  => [$cmd] },
                $doreq, -1, 1);
            if ($::RUNCMD_RC != 0) {
                my $rsp;
                push @{ $rsp->{data} }, "rpower:";
                push @{ $rsp->{data} }, "$output";
                xCAT::MsgUtils->message("E", $rsp, $callback);
                return 1;
            }
        }
    }
}

#-------------------------------------------------------

=head3  add_postscript

	It adds the 'config_ops_bm_node' postbootscript to the 
	postscript table for the given node.

=cut

#-------------------------------------------------------
sub add_postscript {
    my $callback = shift;
    my $node     = shift;
    my $script   = shift;

    #print "script=$script\n";

    my $posttab = xCAT::Table->new("postscripts", -create => 1);
    my %setup_hash;
    my $ref = $posttab->getNodeAttribs($node, [qw(postscripts postbootscripts)]);
    my $found = 0;
    if ($ref) {
        if (exists($ref->{postscripts})) {
            my @a = split(/,/, $ref->{postscripts});
            if (grep(/^config_ops_bm_node/, @a)) {
                $found = 1;
                if (!grep(/^$script$/, @a)) {

                    #not exact match, must replace it with the new script
                    for (@a) {
                        s/^config_ops_bm_node.*$/$script/;
                    }
                    my $new_post = join(',', @a);
                    $setup_hash{$node} = { postscripts => "$new_post" };
                }
            }
        }


        if (exists($ref->{postbootscripts})) {
            my $post = $ref->{postbootscripts};
            my @old_a = split(',', $post);
            if (grep(/^config_ops_bm_node/, @old_a)) {
                if (!grep(/^$script$/, @old_a)) {

                    #not exact match, will replace it with new script
                    for (@old_a) {
                        s/^config_ops_bm_node.*$/$script/;
                    }
                    my $new_postboot = join(',', @old_a);
                    $setup_hash{$node} = { postbootscripts => "$new_postboot" };
                }
            } else {
                if (!$found) {
                    $setup_hash{$node} = { postbootscripts => "$post,$script" };
                }
            }
        } else {
            if (!$found) {
                $setup_hash{$node} = { postbootscripts => "$script" };
            }
        }
    } else {
        $setup_hash{$node} = { postbootscripts => "$script" };
    }

    if (keys(%setup_hash) > 0) {
        $posttab->setNodesAttribs(\%setup_hash);
    }

    return 0;
}

#-------------------------------------------------------

=head3  remove_postscript

	It removes the 'config_ops_bm_node' postbootscript from 
	the postscript table for the given node.

=cut

#-------------------------------------------------------
sub remove_postscript {
    my $callback = shift;
    my $node     = shift;
    my $script   = shift;

    my $posttab = xCAT::Table->new("postscripts", -create => 1);
    my %setup_hash;
    my $ref = $posttab->getNodeAttribs($node, [qw(postscripts postbootscripts)]);
    my $found = 0;
    if ($ref) {
        if (exists($ref->{postscripts})) {
            my @old_a = split(/,/, $ref->{postscripts});
            my @new_a = grep(!/^$script/, @old_a);
            if (scalar(@new_a) != scalar(@old_a)) {
                my $new_post = join(',', @new_a);
                $setup_hash{$node} = { postscripts => "$new_post" };
            }
        }

        if (exists($ref->{postbootscripts})) {
            my @old_b = split(/,/, $ref->{postbootscripts});
            my @new_b = grep(!/^$script/, @old_b);
            if (scalar(@new_b) != scalar(@old_b)) {
                my $new_post = join(',', @new_b);
                $setup_hash{$node} = { postbootscripts => "$new_post" };
            }
        }

    }

    if (keys(%setup_hash) > 0) {
        $posttab->setNodesAttribs(\%setup_hash);
    }

    return 0;
}


#-------------------------------------------------------------------------------

=head3  opsaddbmnode_usage
	The usage text for opsaddbmnode command.
=cut

#-------------------------------------------------------------------------------
sub opsaddbmnode_usage {
    my $cb  = shift;
    my $rsp = {};

    $rsp->{data}->[0] = "Usage: opsaddbmnode -h";
    $rsp->{data}->[1] = "       opsaddbmnode -v";
    $rsp->{data}->[2] = "       opsaddbmnode <noderange> -s <service_host> [-V]";
    $cb->($rsp);
}


#-------------------------------------------------------------------------------

=head3  opsaddimage_usage
	The usage text for opsaddimage command.
=cut

#-------------------------------------------------------------------------------
sub opsaddimage_usage {
    my $cb  = shift;
    my $rsp = {};

    $rsp->{data}->[0] = "Usage: opsaddimage -h";
    $rsp->{data}->[1] = "       opsaddimage -v";
    $rsp->{data}->[2] = "       opsaddimage <image1,image2...> [-n <new_name1,new_name2...> -c <controller> [-V]";
    $cb->($rsp);
}

#-------------------------------------------------------------------------------

=head3   deploy_ops_bm_node_usage
	The usage text for deploy_ops_bm_node command.
=cut

#-------------------------------------------------------------------------------
sub deploy_ops_bm_node_usage {
    my $cb  = shift;
    my $rsp = {};

    $rsp->{data}->[0] = "Usage: deploy_ops_bm_node -h";
    $rsp->{data}->[1] = "       deploy_ops_bm_node -v";
    $rsp->{data}->[2] = "       deploy_ops_bm_node <node> --image <image_name> --host <ops_hostname> --ip <ops_fixed_ip> --mask <netmask>";
    $cb->($rsp);
}

#-------------------------------------------------------------------------------

=head3  cleanup_ops_bm_node_usage
	The usage text cleanup_ops_bm_node command.
=cut

#-------------------------------------------------------------------------------
sub cleanup_ops_bm_node_usage {
    my $cb  = shift;
    my $rsp = {};

    $rsp->{data}->[0] = "Usage: cleanup_ops_bm_node -h";
    $rsp->{data}->[1] = "       cleanup_ops_bm_node -v";
    $rsp->{data}->[2] = "       cleanup_ops_bm_node <node> [--ip <ops_fixed_ip>]";
    $cb->($rsp);
}

1;