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

=head1

    This is a utility plugin for z/VM.

=cut

#-------------------------------------------------------
package xCAT::zvmUtils;
use xCAT::MsgUtils;
use xCAT::Utils;
use xCAT::Table;
use xCAT::NetworkUtils;
use File::Basename;
use strict;
use warnings;
1;

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

=head3   getNodeProps
    Description : Get node properties
    Arguments   :   Table
                    Node
                    Properties
    Returns     : Node properties from given table
    Example     : my $propVals = xCAT::zvmUtils->getNodeProps($tabName, $node, $propNames);
    
=cut

#-------------------------------------------------------
sub getNodeProps {

    # Get inputs
    my ( $class, $tabName, $node, @propNames ) = @_;

    # Get table
    my $tab = xCAT::Table->new($tabName);

    # Get property values
    my $propVals = $tab->getNodeAttribs( $node, [@propNames] );

    return ($propVals);
}

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

=head3   getTabPropsByKey
    Description : Get table entry properties by key
    Arguments   :   Table
                    Key name
                    Key value
                    Requested properties
    Returns     : Table entry properties
    Example     : my $propVals = xCAT::zvmUtils->getTabPropsByKey($tabName, $key, $keyValue, @reqProps);
    
=cut

#-------------------------------------------------------
sub getTabPropsByKey {

    # Get inputs
    my ( $class, $tabName, $key, $keyVal, @propNames ) = @_;

    # Get table
    my $tab = xCAT::Table->new($tabName);
    my $propVals;

    # Get table attributes matching given key
    $propVals = $tab->getAttribs( { $key => $keyVal }, @propNames );
    return ($propVals);
}

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

=head3   getAllTabEntries
    Description : Get all entries within given table
    Arguments   : Table name
    Returns     : All table entries
    Example     : my $entries = xCAT::zvmUtils->getAllTabEntries($tabName);
    
=cut

#-------------------------------------------------------
sub getAllTabEntries {

    # Get inputs
    my ( $class, $tabName ) = @_;

    # Get table
    my $tab = xCAT::Table->new($tabName);
    my $entries;

    # Get all entries within given table
    $entries = $tab->getAllEntries();
    return ($entries);
}

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

=head3   setNodeProp

    Description : Set a node property in a given table
    Arguments   :   Table
                    Node
                    Property name
                    Property value
    Returns     : Nothing
    Example     : xCAT::zvmUtils->setNodeProp($tabName, $node, $propName, $propVal);
    
=cut

#-------------------------------------------------------
sub setNodeProp {

    # Get inputs
    my ( $class, $tabName, $node, $propName, $propVal ) = @_;

    # Get table
    my $tab = xCAT::Table->new( $tabName, -create => 1, -autocommit => 0 );

    # Set property
    $tab->setAttribs( { 'node' => $node }, { $propName => $propVal } );

    # Save table
    $tab->commit;

    return;
}

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

=head3   setNodeProps

    Description : Set node properties in a given table
    Arguments   :   Table
                    Node
                    Reference to property name/value hash
    Returns     : Nothing
    Example     : xCAT::zvmUtils->setNodeProps($tabName, $node, \%propHash);
    
=cut

#-------------------------------------------------------
sub setNodeProps {

    # Get inputs
    my ( $class, $tabName, $node, $propHash ) = @_;

    # Get table
    my $tab = xCAT::Table->new( $tabName, -create => 1, -autocommit => 0 );

    # Set property
    $tab->setAttribs( { 'node' => $node }, $propHash );

    # Save table
    $tab->commit;

    return;
}

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

=head3   delTabEntry

    Description : Delete a table entry
    Arguments   :   Table
                    Key name
                    Key value 
    Returns     : Nothing
    Example     : xCAT::zvmUtils->delTabEntry($tabName, $keyName, $keyVal);
    
=cut

#-------------------------------------------------------
sub delTabEntry {

    # Get inputs
    my ( $class, $tabName, $keyName, $keyVal ) = @_;

    # Get table
    my $tab = xCAT::Table->new( $tabName, -create => 1, -autocommit => 0 );

    # Delete entry from table
    my %key = ( $keyName => $keyVal );
    $tab->delEntries( \%key );

    # Save table
    $tab->commit;

    return;
}

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

=head3   tabStr

    Description : Tab a string (4 spaces)
    Arguments   : String
    Returns     : Tabbed string
    Example     : my $str = xCAT::zvmUtils->tabStr($str);
    
=cut

#-------------------------------------------------------
sub tabStr {

    # Get inputs
    my ( $class, $inStr ) = @_;
    my @lines = split( "\n", $inStr );

    # Tab output
    my $outStr;
    foreach (@lines) {
        $outStr .= "    $_\n";
    }

    return ($outStr);
}

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

=head3   trimStr

    Description : Trim the whitespaces in a string
    Arguments   : String
    Returns     : Trimmed string
    Example     : my $str = xCAT::zvmUtils->trimStr($str);
    
=cut

#-------------------------------------------------------
sub trimStr {

    # Get string
    my ( $class, $str ) = @_;

    # Trim right
    $str =~ s/\s*$//;

    # Trim left
    $str =~ s/^\s*//;

    return ($str);
}

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

=head3   replaceStr

    Description : Replace a given pattern in a string
    Arguments   : String
    Returns     : New string
    Example     : my $str = xCAT::zvmUtils->replaceStr($str, $pattern, $replacement);
    
=cut

#-------------------------------------------------------
sub replaceStr {

    # Get string
    my ( $class, $str, $pattern, $replacement ) = @_;

    # Replace string
    $str =~ s/$pattern/$replacement/g;

    return ($str);
}

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

=head3   printLn

    Description : Print a string to stdout
    Arguments   : String
    Returns     : Nothing
    Example     : xCAT::zvmUtils->printLn($callback, $str);
    
=cut

#-------------------------------------------------------
sub printLn {

    # Get inputs
    my ( $class, $callback, $str ) = @_;

    # Print string
    my $rsp;
    my $type = "I";
    if ($str =~ m/(\(error\)|\sfailed)/i) {  # Set to print error if the string contains error
        $type = "E";
    }
    
    $rsp->{data}->[0] = "$str";
    xCAT::MsgUtils->message( $type, $rsp, $callback );
    # xCAT::MsgUtils->message( "S", $str );  # Print to syslog

    return;
}

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

=head3   printSyslog

    Description : Print a string to syslog
    Arguments   : String
    Returns     : Nothing
    Example     : xCAT::zvmUtils->printSyslog($str);
    
=cut

#-------------------------------------------------------
sub printSyslog {

    # Get inputs
    my ( $class, $str ) = @_;

    # Prepend where this message came from
    $str = $class . "  " . $str;

    # Print string
    xCAT::MsgUtils->message( "S", $str );

    return;
}

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

=head3   isZvmNode

    Description : Determines if a given node is in the 'zvm' table
    Arguments   : Node
    Returns     :   TRUE    Node exists
                    FALSE    Node does not exists
    Example     : my $out = xCAT::zvmUtils->isZvmNode($node);
    
=cut

#-------------------------------------------------------
sub isZvmNode {

    # Get inputs
    my ( $class, $node ) = @_;

    # Look in 'zvm' table
    my $tab = xCAT::Table->new( 'zvm', -create => 1, -autocommit => 0 );

    my @results = $tab->getAllAttribsWhere( "node like '%" . $node . "%'", 'userid' );
    foreach (@results) {

        # Return 'TRUE' if given node is in the table
        if ($_->{'userid'}) {
            return 1;
        }
    }

    return 0;
}

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

=head3   getHwcfg

    Description : Get the hardware configuration file path (SUSE only)
                  e.g. /etc/sysconfig/hardwarehwcfg-qeth-bus-ccw-0.0.0600
    Arguments   :   User (root or non-root)
                    Node
    Returns     : Hardware configuration file path
    Example     : my $hwcfg = xCAT::zvmUtils->getHwcfg($user, $node);
    
=cut

#-------------------------------------------------------
sub getHwcfg {

    # Get inputs
    my ( $class, $user, $node ) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Get OS
    my $os = xCAT::zvmUtils->getOs($user, $node);

    # Get network configuration file path
    my $out;
    my @parms;

    # If it is SUSE - hwcfg-qeth file is in /etc/sysconfig/hardware
    if ( $os =~ m/SUSE/i ) {
        $out = `ssh -o ConnectTimeout=5 $user\@$node "$sudo ls /etc/sysconfig/hardware/hwcfg-qeth*"`;
        @parms = split( '\n', $out );
        return ( $parms[0] );
    }

    # If no file is found - Return nothing
    return;
}

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

=head3   getIp

    Description : Get the IP address of a given node
    Arguments   : Node
    Returns     : IP address of given node
    Example     : my $ip = xCAT::zvmUtils->getIp($node);
    
=cut

#-------------------------------------------------------
sub getIp {

    # Get inputs
    my ( $class, $node ) = @_;

    # Get IP address
    # You need the extra space in the pattern,
    # else it will confuse gpok2 with gpok21
    my $out   = `cat /etc/hosts | egrep -i "$node | $node."`;
    my @parms = split( ' ', $out );

    return $parms[0];
}

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

=head3   getIfcfg

    Description : Get the network configuration file path of a given node
                    * Red Hat - /etc/sysconfig/network-scripts/ifcfg-eth
                    * SUSE    - /etc/sysconfig/network/ifcfg-qeth
    Arguments   :   User (root or non-root)
                    Node
    Returns     : Network configuration file path
    Example     : my $ifcfg = xCAT::zvmUtils->getIfcfg($user, $node);
    
=cut

#-------------------------------------------------------
sub getIfcfg {

    # Get inputs
    my ( $class, $user, $node ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    # Get OS
    my $os = xCAT::zvmUtils->getOs($user, $node);

    # Get network configuration file path
    my $out;
    my @parms;

    # If it is Red Hat - ifcfg-qeth file is in /etc/sysconfig/network-scripts
    if ( $os =~ m/Red Hat/i ) {
        $out = `ssh -o ConnectTimeout=5 $user\@$node "$sudo ls /etc/sysconfig/network-scripts/ifcfg-eth*"`;
        @parms = split( '\n', $out );
        return ( $parms[0] );
    }

    # If it is SUSE - ifcfg-qeth file is in /etc/sysconfig/network
    elsif ( $os =~ m/SUSE/i ) {
        $out = `ssh -o ConnectTimeout=5 $user\@$node "$sudo ls /etc/sysconfig/network/ifcfg-qeth*"`;
        @parms = split( '\n', $out );
        return ( $parms[0] );
    }

    # If no file is found - Return nothing
    return;
}

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

=head3   getIfcfgByNic

    Description : Get the network configuration file path of a given node
    Arguments   :   User (root or non-root)
                    Node
                    NIC address
    Returns     : Network configuration file path
    Example     : my $ifcfg = xCAT::zvmUtils->getIfcfgByNic($user, $node, $nic);
    
=cut

#-------------------------------------------------------
sub getIfcfgByNic {

    # Get inputs
    my ( $class, $user, $node, $nic ) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Get OS
    my $os = xCAT::zvmUtils->getOs($user, $node);

    # Get network configuration file path
    my $out;
    my @parms;

    # If it is Red Hat - ifcfg-qeth file is in /etc/sysconfig/network-scripts
    if ( $os =~ m/Red Hat/i ) {
        $out   = `ssh -o ConnectTimeout=5 $user\@$node "$sudo ls /etc/sysconfig/network-scripts/ifcfg-eth*"`;
        @parms = split( '\n', $out );

        # Go through each line
        foreach (@parms) {

            # If the network file contains the NIC address
            $out = `ssh -o ConnectTimeout=5 $user\@$node "$sudo cat $_" | egrep -i "$nic"`;
            if ($out) {

                # Return network file path
                return ($_);
            }
        }
    }

    # If it is SLES 10 - ifcfg-qeth file is in /etc/sysconfig/network
    elsif ( $os =~ m/SUSE Linux Enterprise Server 10/i ) {
        $out = `ssh -o ConnectTimeout=5 $user\@$node "$sudo ls /etc/sysconfig/network/ifcfg-qeth*" | grep -i "$nic"`;
        @parms = split( '\n', $out );
        return ( $parms[0] );
    }

    # If it is SLES 11 - ifcfg-qeth file is in /etc/sysconfig/network
    elsif ( $os =~ m/SUSE Linux Enterprise Server 11/i ) {

        # Get a list of ifcfg-eth files found
        $out = `ssh -o ConnectTimeout=5 $user\@$node "$sudo ls /etc/sysconfig/network/ifcfg-eth*"`;
        my @file = split( '\n', $out );

        # Go through each ifcfg-eth file
        foreach (@file) {

            # If the network file contains the NIC address
            $out = `ssh -o ConnectTimeout=5 $user\@$node "$sudo cat $_" | grep -i "$nic"`;
            if ($out) {

                # Return ifcfg-eth file path
                return ($_);
            }
        }
    }

    # If no file is found - Return nothing
    return;
}

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

=head3   sendFile

    Description : SCP a file to a given node
    Arguments   :   User (root or non-root)
                    Node
                    Source file
                    Target file
    Returns     : Nothing
    Example     : xCAT::zvmUtils->sendFile($user, $node, $srcFile, $trgtFile);
    
=cut

#-------------------------------------------------------
sub sendFile {

    # Get inputs
    my ( $class, $user, $node, $srcFile, $trgtFile ) = @_;

    # Create destination string
    my $dest = "$user\@$node";

    # SCP directory entry file over to HCP
    my $out = `/usr/bin/scp $srcFile $dest:$trgtFile`;

    return;
}

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

=head3   getRootDeviceAddr

    Description : Get the root device address of a given node
    Arguments   :   User (root or non-root)
                    Node
    Returns     : Root device address
    Example     : my $deviceAddr = xCAT::zvmUtils->getRootDeviceAddr($user, $node);
    
=cut

#-------------------------------------------------------
sub getRootDeviceAddr {

    # Get inputs
    my ( $class, $user, $node ) = @_;

    # Get the root device node
    # LVM is not supported
    my $out = `ssh $user\@$node  "mount" | grep "/ type" | sed 's/1//'`;
    my @parms = split( " ", $out );
    @parms = split( "/", xCAT::zvmUtils->trimStr( $parms[0] ) );
    my $devNode = $parms[0];

    # Get disk address
    $out = `ssh $user\@$node "cat /proc/dasd/devices" | grep "$devNode" | sed 's/(ECKD)//' | sed 's/(FBA )//' | sed 's/0.0.//'`;
    @parms = split( " ", $out );
    return ( $parms[0] );
}

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

=head3   disableEnableDisk

    Description : Disable/enable a disk for a given node
    Arguments   :   User (root or non-root)
                    Node
                    Device address
                    Option (-d|-e)
    Returns     : Nothing
    Example     : my $out = xCAT::zvmUtils->disableEnableDisk($callback, $user, $node, $option, $devAddr);
    
=cut

#-------------------------------------------------------
sub disableEnableDisk {

    # Get inputs
    my ( $class, $user, $node, $option, $devAddr ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    # Disable/enable disk
    my $out;
    if ( $option eq "-d" || $option eq "-e" ) {
        $out = `ssh $user\@$node "$sudo /sbin/chccwdev $option $devAddr"`;
    }

    return ($out);
}

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

=head3   getMdisks

    Description : Get the MDISK statements in the user entry of a given node
    Arguments   :   User (root or non-root)
                    Node
    Returns     : MDISK statements
    Example     : my @mdisks = xCAT::zvmUtils->getMdisks($callback, $user, $node);
    
=cut

#-------------------------------------------------------
sub getMdisks {

    # Get inputs
    my ( $class, $callback, $user, $node ) = @_;

    # Directory where executables are
    my $dir = '/opt/zhcp/bin';

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Get HCP
    my @propNames = ( 'hcp', 'userid' );
    my $propVals = xCAT::zvmUtils->getNodeProps( 'zvm', $node, @propNames );
    my $hcp = $propVals->{'hcp'};

    # Get node userID
    my $userId = $propVals->{'userid'};

    my $out = `ssh $user\@$hcp "$sudo $dir/getuserentry $userId" | grep "MDISK"`;

    # Get MDISK statements
    my @lines = split( '\n', $out );
    my @disks;
    foreach (@lines) {
        $_ = xCAT::zvmUtils->trimStr($_);

        # Save MDISK statements
        push( @disks, $_ );
    }

    return (@disks);
}

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

=head3   getDedicates

    Description : Get the DEDICATE statements in the user entry of a given node
    Arguments   :   User (root or non-root)
                    Node
    Returns     : DEDICATE statements
    Example     : my @dedicates = xCAT::zvmUtils->getDedicates($callback, $user, $node);
    
=cut

#-------------------------------------------------------
sub getDedicates {

    # Get inputs
    my ( $class, $callback, $user, $node ) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    # Directory where executables are
    my $dir = '/opt/zhcp/bin';
    
    # Get zHCP
    my @propNames = ( 'hcp', 'userid' );
    my $propVals = xCAT::zvmUtils->getNodeProps( 'zvm', $node, @propNames );
    my $hcp = $propVals->{'hcp'};

    # Get node userId
    my $userId = $propVals->{'userid'};

    my $out = `ssh $user\@$hcp "$sudo $dir/smcli Image_Query_DM -T $userId" | egrep -i "DEDICATE"`;
    
    # Get DEDICATE statements
    my @lines = split( '\n', $out );
    my @dedicates;
    foreach (@lines) {
        $_ = xCAT::zvmUtils->trimStr($_);

        # Save statements
        push( @dedicates, $_ );
    }

    return (@dedicates);
}

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

=head3   getUserEntryWODisk

    Description : Get the user entry of a given node without MDISK statments, 
                  and save it to a file
    Arguments   :   User (root or non-root)
                    Node
                    File name to save user entry under
    Returns     : Nothing
    Example     : my $out = xCAT::zvmUtils->getUserEntryWODisk($callback, $user, $node, $file);
    
=cut

#-------------------------------------------------------
sub getUserEntryWODisk {

    # Get inputs
    my ( $class, $callback, $user, $node, $file ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    # Directory where executables are
    my $dir = '/opt/zhcp/bin';
        
    # Get node properties from 'zvm' table
    my @propNames = ( 'hcp', 'userid' );
    my $propVals = xCAT::zvmUtils->getNodeProps( 'zvm', $node, @propNames );

    # Get HCP
    my $hcp = $propVals->{'hcp'};
    if ( !$hcp ) {
        xCAT::zvmUtils->printLn( $callback, "Error: Missing node HCP" );
        return;
    }

    # Get node userID
    my $userId = $propVals->{'userid'};
    if ( !$userId ) {
        xCAT::zvmUtils->printLn( $callback, "Error: Missing node ID" );
        return;
    }

    my $out = `ssh $user\@$hcp "$sudo $dir/smcli Image_Query_DM -T $userId" | sed '\$d' | grep -v "MDISK"`;

    # Create a file to save output
    open( DIRENTRY, ">$file" );

    # Save output
    my @lines = split( '\n', $out );
    foreach (@lines) {

        # Trim line
        $_ = xCAT::zvmUtils->trimStr($_);

        # Write directory entry into file
        print DIRENTRY "$_\n";
    }
    close(DIRENTRY);

    return;
}

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

=head3   appendHostname

    Description : Append a hostname in front of a given string
    Arguments   :   Hostname
                    String
    Returns     : String appended with hostname
    Example     : my $str = xCAT::zvmUtils->appendHostname($hostname, $str);
    
=cut

#-------------------------------------------------------
sub appendHostname {
    my ( $class, $hostname, $str ) = @_;

    # Append hostname to every line
    my @outLn = split( "\n", $str );
    $str = "";
    foreach (@outLn) {
        $str .= "$hostname: " . $_ . "\n";
    }

    return $str;
}

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

=head3   checkOutput

    Description : Check the return of given output
    Arguments   : Output string
    Returns     :    0  Good output
                    -1  Bad output
    Example     : my $rtn = xCAT::zvmUtils->checkOutput($callback, $out);
    
=cut

#-------------------------------------------------------
sub checkOutput {
    my ( $class, $callback, $out ) = @_;

    # Check output string
    my @outLn = split( "\n", $out );
    foreach (@outLn) {

        # If output contains 'Failed', return -1
        if ( $_ =~ m/Failed/i || $_ =~ m/Error/i ) {
            return -1;
        }
    }

    return 0;
}

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

=head3   checkOutputExtractReason

    Description : Check the return of given output. If bad, extract the reason.
    Arguments   : Output string
                  Reason (passed as a reference)
    Returns     :    0  Good output
                    -1  Bad output
    Example     : my $rtn = xCAT::zvmUtils->checkOutput($callback, $out, \$reason);
    
=cut

#-------------------------------------------------------
sub checkOutputExtractReason {
    my ( $class, $callback, $out, $reason ) = @_;

    # Check output string
    my @outLn = split("\n", $out);
    foreach (@outLn) {
        # If output contains 'ERROR: ', return -1 and pass back the reason.
        if ($_ =~ /(.*?ERROR: )/) {
            $$reason = substr($_, index($_, "ERROR: ") + length("ERROR: "));
            return -1;
        }
    }

    return 0;
}

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

=head3   getDeviceNode

    Description : Get the device node for a given address
    Arguments   :   User (root or non-root)
                    Node
                    Disk address
    Returns     : Device node
    Example     : my $devNode = xCAT::zvmUtils->getDeviceNode($user, $node, $tgtAddr);
    
=cut

#-------------------------------------------------------
sub getDeviceNode {
    my ( $class, $user, $node, $tgtAddr ) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Determine device node
    my $out = `ssh $user\@$node "$sudo cat /proc/dasd/devices" | grep ".$tgtAddr("`;
    my @words = split(' ', $out);
    my $tgtDevNode;

    # /proc/dasd/devices look similar to this:
    # 0.0.0100(ECKD) at ( 94: 0) is dasda : active at blocksize: 4096, 1802880 blocks, 7042 MB
    # Look for the string 'is'
    my $i = 0;
    while ($tgtDevNode ne 'is') {
        $tgtDevNode = $words[$i];
        $i++;
    }

    return $words[$i];
}

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

=head3   getDeviceNodeAddr

    Description : Get the virtual device address for a given device node
    Arguments   :   User (root or non-root)
                    Node
                    Device node
    Returns     : Virtual device address
    Example     : my $addr = xCAT::zvmUtils->getDeviceNodeAddr($user, $node, $deviceNode);
    
=cut

#-------------------------------------------------------
sub getDeviceNodeAddr {
    my ( $class, $user, $node, $deviceNode ) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Find device node and determine virtual address
    #   /proc/dasd/devices look similar to this:
    #   0.0.0100(ECKD) at ( 94: 0) is dasda : active at blocksize: 4096, 1802880 blocks, 7042 MB
    my $addr = `ssh $user\@$node "$sudo cat /proc/dasd/devices" | grep -i "is $deviceNode"`;
    $addr =~ s/ +/ /g;
    $addr =~ s/^0.0.([0-9a-f]*).*/$1/;
    chomp($addr);

    return $addr;
}

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

=head3   isAddressUsed

    Description : Check if a given address is used
    Arguments   :   User (root or non-root)
                    Node
                    Disk address
    Returns     :  0  Address used
                  -1  Address not used
    Example     : my $ans = xCAT::zvmUtils->isAddressUsed($user, $node, $address);
    
=cut

#-------------------------------------------------------
sub isAddressUsed {
    my ( $class, $user, $node, $address ) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    # Search for disk address
    my $out = `ssh -o ConnectTimeout=5 $user\@$node "$sudo /sbin/vmcp q v dasd" | grep "DASD $address"`;
    if ($out) {
        return 0;
    }

    return -1;
}

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

=head3   getMacID

    Description : Get the MACID from /opt/zhcp/conf/next_macid on the HCP
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : MACID
    Example     : my $macId = xCAT::zvmUtils->getMacID($user, $hcp);
    
=cut

#-------------------------------------------------------
sub getMacID {
    my ( $class, $user, $hcp ) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Check /opt/zhcp/conf directory on HCP
    my $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo test -d /opt/zhcp/conf && echo 'Directory exists'"`;
    if ( $out =~ m/Directory exists/i ) {

        # Check next_macid file
        $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo test -e /opt/zhcp/conf/next_macid && echo 'File exists'"`;
        if ( $out =~ m/File exists/i ) {

            # Do nothing
        } else {

            # Create next_macid file
            $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo echo 'FFFFF0' > /opt/zhcp/conf/next_macid"`;
        }
    } else {

        # Create /opt/zhcp/conf directory
        # Create next_mac - Contains next MAC address to use
        $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo mkdir /opt/zhcp/conf"`;
        $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo echo 'FFFFF0' > /opt/zhcp/conf/next_macid"`;
    }

    # Read /opt/zhcp/conf/next_macid file
    $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo cat /opt/zhcp/conf/next_macid"`;
    my $macId = xCAT::zvmUtils->trimStr($out);

    return $macId;
}

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

=head3   generateMacId

    Description : Generate a new MACID 
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : Nothing
    Example     : my $macId = xCAT::zvmUtils->generateMacId($user, $hcp);
    
=cut

#-------------------------------------------------------
sub generateMacId {
    my ( $class, $user, $hcp ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    # Check /opt/zhcp/conf directory on HCP
    my $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo test -d /opt/zhcp/conf && echo 'Directory exists'"`;
    if ( $out =~ m/Directory exists/i ) {

        # Check next_macid file
        $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo test -e /opt/zhcp/conf/next_macid && echo 'File exists'"`;
        if ( $out =~ m/File exists/i ) {

            # Do nothing
        } else {

            # Create next_macid file
            $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo echo 'FFFFF0' > /opt/zhcp/conf/next_macid"`;
            $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo /bin/chmod 666 /opt/zhcp/conf/next_macid"`;
        }
    } else {

        # Create /opt/zhcp/conf directory
        # Create next_mac - Contains next MAC address to use
        $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo mkdir /opt/zhcp/conf"`;
        $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo echo 'FFFFF0' > /opt/zhcp/conf/next_macid"`;
        $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo /bin/chmod 666 /opt/zhcp/conf/next_macid"`;
    }

    # Read /opt/zhcp/conf/next_macid file
    $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo cat /opt/zhcp/conf/next_macid"`;
    my $macId = xCAT::zvmUtils->trimStr($out);
    my $int;

    if ($macId) {

        # Convert hexadecimal - decimal
        $int   = hex($macId);
        $macId = sprintf( "%d", $int );

        # Generate new MAC suffix
        $macId = $macId - 1;

        # Convert decimal - hexadecimal
        $macId = sprintf( "%X", $macId );

        # Save new MACID
        $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo echo $macId > /opt/zhcp/conf/next_macid"`;
    }

    return;
}

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

=head3   createMacAddr

    Description : Create a MAC address using the HCP MAC prefix and a given MAC suffix
    Arguments   :   User (root or non-root)
                    Node
                    MAC suffix
    Returns     : MAC address
    Example     : my $mac = xCAT::zvmUtils->createMacAddr($user, $node, $suffix);
    
=cut

#-------------------------------------------------------
sub createMacAddr {
    my ( $class, $user, $node, $suffix ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Get node properties from 'zvm' table
    my @propNames = ('hcp');
    my $propVals  = xCAT::zvmUtils->getNodeProps( 'zvm', $node, @propNames );

    # Get HCP
    my $hcp = $propVals->{'hcp'};
    if ( !$hcp ) {
        return -1;
    }

    # Get USER Prefix
    my $prefix = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo /sbin/vmcp q vmlan" | egrep -i "USER Prefix:"`;
    $prefix =~ s/(.*?)USER Prefix:(.*)/$2/;
    $prefix =~ s/^\s+//;
    $prefix =~ s/\s+$//;
                        
    # Get MACADDR Prefix instead if USER Prefix is not defined
    if (!$prefix) {
        $prefix = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo /sbin/vmcp q vmlan" | egrep -i "MACADDR Prefix:"`;
        $prefix =~ s/(.*?)MACADDR Prefix:(.*)/$2/;
        $prefix =~ s/^\s+//;
        $prefix =~ s/\s+$//;
        
        if (!$prefix) {
            return -1;
        }
    }

    # Generate MAC address of source node
    my $mac = $prefix . $suffix;

    # If length is less than 12, append a zero
    if ( length($mac) != 12 ) {
        $mac = "0" . $mac;
    }

    # Format MAC address
    $mac =
        substr( $mac, 0, 2 ) . ":"
      . substr( $mac, 2,  2 ) . ":"
      . substr( $mac, 4,  2 ) . ":"
      . substr( $mac, 6,  2 ) . ":"
      . substr( $mac, 8,  2 ) . ":"
      . substr( $mac, 10, 2 );

    return $mac;
}

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

=head3   getOs

    Description : Get the operating system of a given node
    Arguments   :   User (root or non-root)
                    Node
    Returns     : Operating system name
    Example     : my $osName = xCAT::zvmUtils->getOs($user, $node);
    
=cut

#-------------------------------------------------------
sub getOs {

    # Get inputs
    my ( $class, $user, $node ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    # Get operating system
    my $out = `ssh -o ConnectTimeout=10 $user\@$node "$sudo cat /etc/*release" | egrep -v "LSB_VERSION"`;
    my @results = split( '\n', $out );
    return ( xCAT::zvmUtils->trimStr( $results[0] ) );
}

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

=head3   getArch

    Description : Get the architecture of a given node
    Arguments   :   User (root or non-root)
                    Node
    Returns     : Architecture of node
    Example     : my $arch = xCAT::zvmUtils->getArch($user, $node);
    
=cut

#-------------------------------------------------------
sub getArch {

    # Get inputs
    my ( $class, $user, $node ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    # Get host using VMCP
    my $arch = `ssh $user\@$node "$sudo uname -m"`;

    return ( xCAT::zvmUtils->trimStr($arch) );
}

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

=head3   getUserProfile

    Description : Get the user profile
    Arguments   :   User (root or non-root)
                    Profile name
    Returns     : User profile
    Example     : my $profile = xCAT::zvmUtils->getUserProfile($user, $hcp, $name);
    
=cut

#-------------------------------------------------------
sub getUserProfile {

    # Get inputs
    my ( $class, $user, $hcp, $profile ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    # Set directory where executables are on zHCP
    my $hcpDir = "/opt/zhcp/bin";
        
    my $out;

    # Set directory for cache
    my $cache = '/var/opt/zhcp/cache';
    # If the cache directory does not exist
    if (!(`ssh $user\@$hcp "$sudo test -d $cache && echo Exists"`)) {
        # Create cache directory
        $out = `ssh $user\@$hcp "$sudo mkdir -p $cache"`;
    }

    # Set output file name
    my $file = "$cache/$profile.profile";

    # If a cache for the user profile exists
    if (`ssh $user\@$hcp "$sudo ls $file"`) {

        # Get current Epoch
        my $curTime = time();

        # Get time of last change as seconds since Epoch
        my $fileTime = xCAT::zvmUtils->trimStr(`ssh $user\@$hcp "$sudo stat -c %Z $file"`);

        # If the current time is greater than 5 minutes of the file timestamp
        my $interval = 300;    # 300 seconds = 5 minutes * 60 seconds/minute
        if ( $curTime > $fileTime + $interval ) {

            # Get user profiles and save it in a file
            $out = `ssh $user\@$hcp "$sudo $hcpDir/smcli Profile_Query_DM -T $profile > $file"`;
        }
    } else {

        # Get user profiles and save it in a file
        $out = `ssh $user\@$hcp "$sudo $hcpDir/smcli Profile_Query_DM -T $profile > $file"`;
    }

    # Return the file contents
    $out = `ssh $user\@$hcp "$sudo cat $file"`;
    return $out;
}

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

=head3   inArray

    Description : Checks if a value exists in an array
    Arguments   :   Search value
                    Search array
    Returns     : The searched expression
    Example     : my $rtn = xCAT::zvmUtils->inArray($needle, @haystack);
    
=cut

#-------------------------------------------------------
sub inArray {

    # Get inputs
    my ( $class, $needle, @haystack ) = @_;
    return grep{ $_ eq $needle } @haystack;
}

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

=head3   getOsVersion

    Description : Get the operating system of a given node
    Arguments   :   User (root or non-root)
                    Node
    Returns     : Operating system name
    Example     : my $os = xCAT::zvmUtils->getOsVersion($user, $node);
    
=cut

#-------------------------------------------------------
sub getOsVersion {
    
    # Get inputs
    my ( $class, $user, $node ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    my $os = '';
    my $version = '';

    # Get operating system
    my $release = `ssh -o ConnectTimeout=2 $user\@$node "$sudo cat /etc/*release"`;
    my @lines = split('\n', $release);
    if (grep(/SUSE Linux Enterprise Server/, @lines)) {
        $os = 'sles';
        $version = `echo "$release" | grep "VERSION ="`;
        $version =~ s/\s*$//;
        $version =~ s/^\s*//;
        $version =~ tr/\.//;
        $version =~ s/[^0-9]*([0-9]+).*/$1/;
        $os = $os . $version;
        
        # Append service level
        $version = `echo "$release" | grep "LEVEL ="`;
        $version =~ s/\s*$//;
        $version =~ s/^\s*//;
        $version =~ tr/\.//;
        $version =~ s/[^0-9]*([0-9]+).*/$1/;
        $os = $os . 'sp' . $version;
    } elsif (grep(/Red Hat Enterprise Linux Server/, @lines)) {
        $os = 'rhel';
        $version = $lines[0];
        $version =~ tr/\.//;
        $version =~ s/([A-Za-z\s\(\)]+)//g;
        $os = $os . $version;
    }

    return xCAT::zvmUtils->trimStr($os);
}

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

=head3   getZfcpInfo

    Description : Get the zFCP device info
    Arguments   :   User (root or non-root)
                    Node
    Returns     : zFCP device info
    Example     : my $info = xCAT::zvmUtils->getZfcpInfo($user, $node);
    
=cut

#-------------------------------------------------------
sub getZfcpInfo {
    # Get inputs
    my ( $class, $user, $node ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Get zFCP device info
    my $info = `ssh -o ConnectTimeout=5 $user\@$node "$sudo /sbin/lszfcp -D"`;
    my @zfcp = split("\n", $info);
    if (!$info || $info =~ m/No zfcp support/i || $info =~ m/No fcp devices found/i) {
        return;
    }
    
    # Get SCSI device and their attributes
    my $scsi = `ssh -o ConnectTimeout=5 $user\@$node "$sudo /usr/bin/lsscsi"`;
    $info = "";
        
    my @args;
    my $tmp;
    my $id;
    my $device;
    my $wwpn;
    my $lun;
    my $size;
    
    foreach (@zfcp) {
        @args = split(" ", $_);
        $id = $args[1];
        @args = split("/", $args[0]);
        
        $device = $args[0];
        $wwpn = $args[1];
        $lun = $args[2];
        
        # Make sure WWPN and LUN do not have 0x prefix
        $wwpn = xCAT::zvmUtils->replaceStr($wwpn, "0x", "");
        $lun = xCAT::zvmUtils->replaceStr($lun, "0x", "");
        
        # Find the device name
        $tmp = `echo "$scsi" | egrep -i $id`;
        $tmp = substr($tmp, index($tmp, "/dev/"));
        chomp($tmp);
        
        # Find the size in MiB
        $size = `ssh -o ConnectTimeout=5 $user\@$node "$sudo /usr/bin/sg_readcap $tmp" | egrep -i "Device size:"`;
        $size =~ s/Device size: //g;
        @args = split(",", $size);
        $size = xCAT::zvmUtils->trimStr($args[1]);

        $info .= "Device: $device  WWPN: 0x$wwpn  LUN: 0x$lun  Size: $size\n";
    }

    $info = xCAT::zvmUtils->tabStr($info);
    return ($info);
}

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

=head3   isHypervisor

    Description : Determines if a given node is in the 'hypervisor' table
    Arguments   : Node
    Returns     :   1   Node exists
                    0   Node does not exists
    Example     : my $out = xCAT::zvmUtils->isHypervisor($node);
    
=cut

#-------------------------------------------------------
sub isHypervisor {

    # Get inputs
    my ( $class, $node ) = @_;
    
    # Look in 'zvm' table
    my $tab = xCAT::Table->new( "hypervisor", -create => 1, -autocommit => 0 );

    my @results = $tab->getAllAttribsWhere( "node like '%" . $node . "%'", 'type' );
    foreach (@results) {
        
        # Return 'TRUE' if given node is in the table
        if ($_->{"type"} eq "zvm") {
            return 1;
        }
    }

    return 0;
}

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

=head3   getSudoer

    Description : Retrieve sudoer user name
    Arguments   : Node
    Returns     :   Sudoer user name
                    Sudo keyword
    Example     : my ($sudoer, $sudo) = xCAT::zvmUtils->getSudoer();
    
=cut

#-------------------------------------------------------
sub getSudoer {
    # Get inputs
    my ( $class ) = @_;
    
    # Use sudo or not on zHCP
    my @propNames = ('username');
    my $propVals = xCAT::zvmUtils->getTabPropsByKey( 'passwd', 'key', 'sudoer', @propNames );
    my $sudo = "sudo";
    my $user = $propVals->{'username'};
    
    if (!$user) {
        $user = "root";
    }
    
    if ($user eq "root") {
        $sudo = "";
    }
    
    return ($user, $sudo);
}

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

=head3   getFreeAddress

    Description : Get a free(unused) virtual address
    Arguments   :   User (root or non-root)
                    Node
                    Type (vmcp or non-vmcp)
    Returns     : vdev  An address which is free to use
                  -1    No free address is left
    Example     : my $vdev = xCAT::zvmUtils->getFreeAddress($user, $node, $type);
    
=cut

#-------------------------------------------------------
sub getFreeAddress {
    my ( $class, $user, $node, $type ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Although 0000 maybe is free, we do not use it
    my $freeAddress = 1;
    my $freeAddressHex = sprintf('%04X', $freeAddress);
    
    # All device type names in VM, do not contain CPU
    my $deviceTypesVm = 'CONS|CTCA|DASD|FCP|GRAF|LINE|MSGD|OSA|PRT|PUN|RDR|SWCH|TAPE';
    # All device type names in user directory, do not contain CPU
    my $deviceTypesUserDir = 'CONSOLE|MDISK|NICDEF|SPOOL|RDEVICE'; 

    # Search for all address that is in use
    my $allUsedAddr;
    if ($type eq 'vmcp') {
        # When the node is up, vmcp can be used
        $allUsedAddr = `ssh -o ConnectTimeout=5 $user\@$node "$sudo /sbin/vmcp q v all | awk '$1 ~/^($deviceTypesVm)/ {print $2}' | sort"`;
    } else {
        # When the node is down, use zHCP to get its user directory entry
        # Get HCP
        my @propNames = ('hcp', 'userid');
        my $propVals = xCAT::zvmUtils->getNodeProps( 'zvm', $node, @propNames );
        my $hcp = $propVals->{'hcp'};

        # Get node userID
        my $userId = $propVals->{'userid'};
        
        # Get user directory entry
        my $userDirEntry = `ssh $::SUDOER\@$hcp "$::SUDO $::DIR/smcli Image_Query_DM -T $userId"'`;
        
        # Get profile if user directory entry include a profile
        if ($userDirEntry =~ "INCLUDE ") {
            my $profileName = `cat $userDirEntry | awk '$1 ~/^(INCLUDE)/ {print $2}`;    
            $profileName = xCAT::zvmUtils->trimStr($profileName);        
            $userDirEntry .= `ssh $::SUDOER\@$hcp "$::SUDO $::DIR/smcli Image_Query_DM -T $profileName"`;
        }
        
        # Get all defined device address
        $allUsedAddr = `cat $userDirEntry | awk '$1 ~/^($deviceTypesUserDir)/ {print $2}' | sort`;
        # Get all linked device address
        $allUsedAddr .= `cat $userDirEntry | awk '$1 ~/^(LINK)/ {print $4}' | sort`;
    }
    
    # Loop to get the lowest free address
    while ($freeAddress < 65536 && $allUsedAddr =~ $freeAddressHex) {
        $freeAddress++;
        $freeAddressHex = sprintf('%04X', $freeAddress);
    }   
    
    if ($freeAddress < 65536) {
        return $freeAddressHex;
    }

    return -1;
}

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

=head3   getUsedCpuTime

    Description : Get used CPU time of instance
    Arguments   :   User (root or non-root)
                    zHCP (to query on)
                    node
    Returns     : In nanoseconds for used CPU time
    Example     : my $out = xCAT::zvmUtils->getUsedCpuTime($hcp, $node);
    
=cut

#-------------------------------------------------------
sub getUsedCpuTime {
    my ( $class, $user, $hcp , $node ) = @_;

    # Directory where executables are
    my $dir = '/opt/zhcp/bin';
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    my $userId = xCAT::zvmCPUtils->getUserId($user, $node);
    
    # Call IUO function to query CPU used time
    my $time = `ssh $user\@$hcp "$sudo $dir/smcli Image_Performance_Query -T $userId -c 1" | egrep -i "Used CPU time:"`;
    $time =~ s/^Used CPU time:(.*)/$1/;
    $time =~ s/"//g;
    $time =~ s/^\s+//;
    $time =~ s/\s+$//;
    if (!$time) {
        $time = 0;
    }
    
    # Not found, return 0
    return $time;
}


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

=head3   getUpTime

    Description : Get running time of an instance
    Arguments   :   User (root or non-root)
                    Node
    Returns     : Running time
    Example     : my $out = xCAT::zvmUtils->getUpTime($user, $node);
    
=cut

#-------------------------------------------------------
sub getUpTime {
    my ( $class, $user, $node ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
 
    my $out = `ssh -o ConnectTimeout=5 $user\@$node "$sudo uptime"`;
    $out = xCAT::zvmUtils->trimStr($out);
    $out =~ /.*up +(?:(\d+) days?,? +)?(\d+):(\d+),.*/;
    my $uptime;
    
    if (!$1 && !$2) {
        # Special case for less than 1 hour, will display X min
        $out =~ /.*up +(\d+) min,.*/;
        $uptime = "0 days $3 min";
    } elsif (!$1) {
        # Special case for less than 1 day, will display X hr X min
        $uptime = "0 days $2 hr $3 min";
    } else {
        $uptime = "$1 days $2 hr $3 min";
    }
    
    return ($uptime);
}

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

=head3   getSizeFromByte

    Description : Return disk size (G or M) from given bytes
    Arguments   : Bytes
    Returns     : Size string
    Example     : my $out = xCAT::zvmUtils->getSizeFromByte($bytes);
    
=cut

#-------------------------------------------------------
sub getSizeFromByte {
    my ( $class, $bytes ) = @_;

    my $size = ($bytes)/(1024*1024);
    if ($size > (1024*5)) {
        $size = ($size / 1024);
        # If the size > 5G, will use G to represent 
        $size = sprintf("%.1f",$size);
        $size = $size . 'G';
    } else {
        # If the size < 5G, will use M to represent
        $size = sprintf("%d",$size);
        $size = $size . 'M';
    }

    return ($size);
}


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

=head3   getSizeFromCyl

    Description : Return disk size (G or M) from given cylinders
    Arguments   : Node
    Returns     : Size string
    Example     : my $out = xCAT::zvmUtils->getSizeFromCyl($cyl);
    
=cut

#-------------------------------------------------------
sub getSizeFromCyl {
    my ($class, $cyl) = @_;

    my $bytes = ($cyl * 737280);
    my $size = xCAT::zvmUtils->getSizeFromByte($bytes);

    return ($size);
}

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

=head3   getSizeFromPage

    Description : Return disk size (G or M) from given pages
    Arguments   : Page
    Returns     : Size string
    Example     : my $out = xCAT::zvmUtils->getSizeFromPage($page);
    
=cut

#-------------------------------------------------------
sub getSizeFromPage {
    my ( $class, $page ) = @_;

    my $bytes = ($page * 4096);
    my $size = xCAT::zvmUtils->getSizeFromByte($bytes);

    return ($size);
}


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

=head3   getLparCpuTotal

    Description : Get total count of logical CPUs in the LPAR
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : Total CPU count
    Example     : my $out = xCAT::zvmCPUtils->getLparCpuTotal($user, $hcp);
    
=cut

#-------------------------------------------------------
sub getLparCpuTotal {
    my ($class, $user, $hcp) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    my $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo cat /proc/sysinfo" | grep "LPAR CPUs Total"`;

    my @results = split(' ', $out);
    return ($results[3]);
}

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

=head3   getLparCpuUsed

    Description : Get count of used logical CPUs in the LPAR
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : Used CPU count
    Example     : my $out = xCAT::zvmCPUtils->getLparCpuUsed($user, $hcp);
    
=cut

#-------------------------------------------------------
sub getLparCpuUsed {
    my ($class, $user, $hcp) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    my $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo cat /proc/sysinfo" | grep "LPAR CPUs Configured"`;

    my @results = split(' ', $out);
    return ($results[3]);
}

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

=head3   getCecModel

    Description : Get the model of this CEC (LPAR)
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : Model of this CEC
    Example     : my $out = xCAT::zvmCPUtils->getCecModel($user, $hcp);
    
=cut

#-------------------------------------------------------
sub getCecModel {
    my ($class, $user, $hcp) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    my $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo cat /proc/sysinfo" | grep "^Type:"`;
    my @results = split(' ', $out);

    return ($results[1]);
}

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

=head3   getCecVendor

    Description : Get the vendor of this CEC (LPAR)
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : Vendor of this CEC
    Example     : my $out = xCAT::zvmCPUtils->getCecVendor($user, $hcp);
    
=cut

#-------------------------------------------------------
sub getCecVendor {
    my ( $class, $user, $hcp ) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    my $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo cat /proc/sysinfo" | grep "Manufacturer"`;
    my @results = split(' ', $out);

    return ($results[1]);
}

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

=head3   getHypervisorInfo

    Description : Get the info(name & version) for this hypervisor
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : Name & version of this hypervisor
    Example     : my $out = xCAT::zvmCPUtils->getHypervisorInfo($user, $hcp);
    
=cut

#-------------------------------------------------------
sub getHypervisorInfo {
    my ($class, $user, $hcp) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    my $out = `ssh -o ConnectTimeout=5 $user\@$hcp "$sudo cat /proc/sysinfo" | grep "VM00 Control Program"`;
    my @results = split(' ', $out);

    my $str = "$results[3] $results[4]";

    return ($str);
}

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

=head3   getLparMemoryTotal

    Description : Get the total physical memory of this LPAR
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : Total physical memory 
    Example     : my $out = xCAT::zvmCPUtils->getLparMemoryTotal($user, $hcp);
    
=cut

#-------------------------------------------------------
sub getLparMemoryTotal {
    my ($class, $user, $hcp) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    my $out = `ssh $user\@$hcp "$sudo /opt/zhcp/bin/smcli System_Info_Query" | grep "real storage"`;
    my @results = split(' ', $out);

    return ($results[5]);
}

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

=head3   getLparMemoryOffline

    Description : Get the offline physical memory of this LPAR
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : Offline physical memory 
    Example     : my $out = xCAT::zvmCPUtils->getLparMemoryOffline($user, $hcp);
    
=cut

#-------------------------------------------------------
sub getLparMemoryOffline {
    my ($class, $user, $hcp) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    my $out = `ssh $user\@$hcp "$sudo /opt/zhcp/bin/smcli System_Info_Query" | grep "real storage"`;
    my @results = split(' ', $out);

    return ($results[14]);
}

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

=head3   getLparMemoryUsed

    Description : Get the used physical memory of this LPAR
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : Used physical memory 
    Example     : my $out = xCAT::zvmCPUtils->getLparMemoryUsed($user, $hcp);
    
=cut

#-------------------------------------------------------
sub getLparMemoryUsed {
    my ($class, $user, $hcp) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    my $out = `ssh $user\@$hcp "$sudo /opt/zhcp/bin/smcli System_Performance_Info_Query " | grep "Used memory pages:"`;
    my @results = split(':', $out);
    
    my $page = xCAT::zvmUtils->trimStr( $results[1] );
    my $size = xCAT::zvmUtils->getSizeFromPage( $page );
    
    return ($size);
}

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

=head3   getDiskPoolUsed

    Description : Get the used size of specified disk pool 
    Arguments   :   User (root or non-root)
                    zHCP
                    Disk pool
    Returns     : Used size of specified disk pool 
    Example     : my $out = xCAT::zvmCPUtils->getDiskPoolUsed($user, $hcp, $diskpool);
    
=cut

#-------------------------------------------------------
sub getDiskPoolUsed {
    my ($class, $user, $hcp, $diskpool) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    my $hcpUserId = xCAT::zvmCPUtils->getUserId($user, $hcp);

    my $out = `ssh $user\@$hcp "$sudo /opt/zhcp/bin/smcli Image_Volume_Space_Query_DM -q 3 -e 3 -n $diskpool -T $hcpUserId"`;
    my @lines = split('\n', $out);
    my @results;
    my $used = 0;

    foreach (@lines) {
        @results = split(' ', $_);
        if ($results[1] =~ '^9336') {
            # Change the format from blocks (512 byte) to cylinder (737280)
            my $cyls = ($results[3] * 512)/(737280);
            $used += $cyls;
        } elsif ($results[1] =~ '^3390') {
            $used += $results[3];
        }
    }
    
    return ($used);
}

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

=head3   getDiskPoolFree

    Description : Get the free size of specified disk pool 
    Arguments   :   User (root or non-root)
                    zHCP 
                    Disk pool
    Returns     : Free size of specified disk pool 
    Example     : my $out = xCAT::zvmCPUtils->getDiskPoolFree($user, $hcp, $diskpool);
    
=cut

#-------------------------------------------------------
sub getDiskPoolFree {
    my ($class, $user, $hcp, $diskpool) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    my $hcpUserId = xCAT::zvmCPUtils->getUserId($user, $hcp);

    my $out = `ssh $user\@$hcp "$sudo /opt/zhcp/bin/smcli Image_Volume_Space_Query_DM -q 2 -e 3 -n $diskpool -T $hcpUserId"`;
    my @lines = split('\n', $out);
    my @results;
    my $free = 0;

    foreach (@lines) {
        @results = split(' ', $_);
        if ($results[1] =~ '^9336') {
            # Change the format from blocks (512 byte) to cylinder (737280)
            my $cyls = ( $results[3] * 512 ) / ( 737280 );
            $free += $cyls;
        } elsif ($results[1] =~ '^3390') {
            $free += $results[3];
        }
    }
    
    return ($free);
}

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

=head3   getMaxMemory

    Description : Get the max memory of a given node
    Arguments   :   User (root or non-root)
                    zHCP 
                    Node
    Returns     : Max memory
    Example     : my $maxMemory = xCAT::zvmCPUtils->getMaxMemory($user, $hcp, $node);
    
=cut

#-------------------------------------------------------
sub getMaxMemory {
    my ($class, $user, $hcp , $node) = @_;

    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }

    my $userId = xCAT::zvmCPUtils->getUserId( $user, $node );

    # Query the maximum memory allowed in user directory entry
    my $out = `ssh $user\@$hcp "$sudo /opt/zhcp/bin/smcli Image_Definition_Query_DM -T $userId -k STORAGE_MAXIMUM"`;
    my @results = split('=', $out);

    return ($results[1]);
}

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

=head3   smapi4xcat

    Description : Verify if SMAPI EXEC (xCAT_Commands_IUO) exists
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     :   0  EXEC not found
                    1  EXEC found
    Example     : my $out = xCAT::zvmUtils->smapi4xcat($user, $hcp);
    
=cut

#-------------------------------------------------------
sub smapi4xcat {
    my ( $class, $user, $hcp ) = @_;
    
    # Directory where executables are
    my $dir = '/opt/zhcp/bin';
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Get zHCP user ID
    my $hcpUserId = xCAT::zvmCPUtils->getUserId($user, $hcp);
    $hcpUserId =~ tr/a-z/A-Z/;
    
    # Check SMAPI level
    # Levels 621 and greater support SMAPI EXEC
    my $out = `ssh $user\@$hcp "$sudo $dir/smcli Query_API_Functional_Level -T $hcpUserId"`;
    $out = xCAT::zvmUtils->trimStr($out);
    if ( !($out =~ m/V6.2/i || $out =~ m/V6.1/i || $out =~ m/V5.4/i) ) {
        return 1;
    }
    
    # Check if SMAPI EXEC exists
    # EXEC found if RC = 8 and RS = 3002
    $out = `ssh $user\@$hcp "$sudo $dir/smcli xCAT_Commands_IUO -T $hcpUserId -c ''"`;
    $out = xCAT::zvmUtils->trimStr($out);
    if ( $out =~ m/Return Code: 8/i && $out =~ m/Reason Code: 3002/i ) {
        return 1;
    } 

    return 0;
}

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

=head3   generateUserEntryFile

    Description : Generate a user entry file without Mdisk
    Arguments   :   UserId
                    Password
                    Memory
                    Privilege
                    Profile
                    Cpu
    Returns     : If successful, return file path. Otherwise, return -1
    Example     : my $out = xCAT::zvmUtils->generateUserEntryFile($userId, $password, $memorySize, $privilege, $profileName, $cpuCount);
    
=cut

#-------------------------------------------------------
sub generateUserEntryFile {
    my ( $class, $userId, $password, $memorySize, $privilege, $profileName, $cpuCount ) = @_;

    # If a file of this name already exists, just override it
    my $file = "/tmp/$userId.txt";
    my $content = "USER $userId $password $memorySize $memorySize $privilege\nINCLUDE $profileName\nCPU 00 BASE\n";
    
    # Add additional CPUs
    my $i;
    for ( $i = 1; $i < $cpuCount; $i++ ) {
        $content = $content.sprintf("CPU %02X\n", $i)
    }
    
    unless (open(FILE, ">$file")) {
        return -1;
    }
    
    print FILE $content;    
    close(FILE);
      
    return $file;    
}

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

=head3   querySSI

    Description : Obtain the SSI and system status
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : SSI cluster name
    Example     : my $out = xCAT::zvmUtils->querySSI($user, $hcp);
    
=cut

#-------------------------------------------------------
sub querySSI {
    my ( $class, $user, $hcp ) = @_;
    
    # Directory where executables are
    my $dir = '/opt/zhcp/bin';
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    my $ssi = `ssh -o ConnectTimeout=10 $user\@$hcp "$sudo $dir/smcli SSI_Query" | egrep -i "ssi_name"`;
    $ssi =~ s/ssi_name = //;
    $ssi =~ s/\s*$//;
    $ssi =~ s/^\s*//;
    
    return $ssi;
}

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

=head3   rExecute

    Description : Execute a remote command
    Arguments   :   User (root or non-root)
                    Node
                    Command to execute
    Returns     : Output returned from executing command
    Example     : my $out = xCAT::zvmUtils->rExecute($user, $node, $cmd);
    
=cut

#-------------------------------------------------------
sub rExecute {
    my ( $class, $user, $node, $cmd ) = @_;
    
    my $out;
    my $sudo = "sudo";
    if ($user eq "root") {
        # Just execute the command if root        
        $out = `ssh $user\@$node "$cmd"`;
        return $out;
    }
    
    # Encapsulate command in single quotes
    $cmd = "'" . $cmd . "'";
    $out = `ssh $user\@$node "$sudo sh -c $cmd"`;
    return $out;
}

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

=head3   getUsedFcpDevices

    Description : Get a list of used FCP devices in the zFCP pools
    Arguments   :   User (root or non-root)
                    zHCP
    Returns     : List of known FCP devices
    Example     : my %devices = xCAT::zvmUtils->getUsedFcpDevices($user, $zhcp);
    
=cut

#-------------------------------------------------------
sub getUsedFcpDevices {
    my ( $class, $user, $hcp ) = @_;
    
    # Directory where zFCP pools are
    my $pool = "/var/opt/zhcp/zfcp";
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Grep the pools for used or allocated zFCP devices
    my %usedDevices;
    my @args;
    my @devices = split("\n", `ssh $user\@$hcp "$sudo cat $pool/*.conf" | egrep -i "used|allocated"`);
    foreach (@devices) {
        @args = split(",", $_);
        
        # Sample pool configuration file:
        #   #status,wwpn,lun,size,range,owner,channel,tag
        #     used,1000000000000000,2000000000000110,8g,3B00-3B3F,ihost1,1a23,$root_device$
        #     free,1000000000000000,2000000000000111,,3B00-3B3F,,,
        #     free,1230000000000000,2000000000000112,,3B00-3B3F,,,
        $args[6] = xCAT::zvmUtils->trimStr($args[6]);
        
        # Push used or allocated devices into hash 
        if ($args[6]) {
            $usedDevices{uc($args[6])} = 1;
        }
    }
    
    return %usedDevices;
}

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

=head3   establishMount

    Description : Establish an NFS mount point on a zHCP system.
    Arguments   : Sudoer user name
                  Sudo keyword
                  zHCP hostname
                  Install root directory
                  Local directory to remotely mount
                  Mount access ('ro' for read only, 'rw' for read write)
                  Directory as known to zHCP (out)
    Returns     : 0 - Mounted, or zHCP and MN are on the same system
                  1 - Mount failed
    Example     : establishMount( $callback, $::SUDOER, $::SUDO, $hcp, $installRoot, $provMethod, "ro", \$remoteDeployDir );
    
=cut

#-------------------------------------------------------
sub establishMount {
    # Get inputs
    my ($class, $callback, $sudoer, $sudo, $hcp, $installRoot, $localDir, $access, $mountedPt) = @_;    
    my $out;

    # If the target system is not on this system then establish the NFS mount point.
    my $hcpIP = xCAT::NetworkUtils->getipaddr( $hcp );
    if (! defined $hcpIP) {
        xCAT::zvmUtils->printLn( $callback, "(Error) Unable to obtain the IP address of the hcp node" );
        return 1;
    }
    
    my $masterIp = xCAT::TableUtils->get_site_Master();
    if (! defined $masterIp) {
        xCAT::zvmUtils->printLn( $callback, "$hcp: (Error) Unable to obtain the management node IP address from the site table" );
        return 1;
    }
    
    if ($masterIp eq $hcpIP) {
        # xCAT MN and zHCP are on the same box and will use the same directory without the need for an NFS mount.
        $$mountedPt = "$installRoot/$localDir";
    } else {
        # Determine the hostname for this management node
        my $masterHostname = Sys::Hostname::hostname();
        if (! defined $masterHostname) {
            # For some reason, the xCAT MN's hostname is not known.  We pass along the IP address instead.
            $masterHostname = $masterIp;
        }
        
        $$mountedPt = "/mnt/$masterHostname$installRoot/$localDir";
        
        # If the mount point already exists then return because we are done.
        my $rc = `ssh $sudoer\@$hcp "$sudo mount | grep $$mountedPt > /dev/null; echo \\\$?"`;
        if ($rc == 0) {
            return 0;
        }
        
        xCAT::zvmUtils->printSyslog( "establishMount() Preparing the NFS mount point on zHCP ($hcpIP) to xCAT MN $masterHostname($masterIp) for $localDir" );
        
        # Prepare the staging mount point on zHCP, if they need to be established
        $rc = `ssh $sudoer\@$hcp "$sudo mkdir -p $$mountedPt && mount -t nfs -o $access $masterIp:/$localDir $$mountedPt; echo \\\$?"`;
        
        # Return code = 0 (mount succeeded)
        if ($rc != '0') {
            xCAT::zvmUtils->printLn( $callback, "$hcp: (Error) Unable to establish zHCP mount point: $$mountedPt" );
            xCAT::zvmUtils->printSyslog( "establishMount() Unable to establish zHCP mount point: $$mountedPt, rc: $rc" );
            return 1;
        }
    }
    
    return 0;
}

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

=head3   getFreeRepoSpace

    Description : Get the free space of image repository under /install.
    Arguments   : Node
    Returns     : The available space for /install (e.g. "2.1G ").  
                  The value is returned as a perl string (e.g. "0 ") to 
                  avoid perl returning null instead of "0" in the case
                  of no space available.
    Example     : my $free = getFreeRepoSpace($callback, $node);
    
=cut

#-------------------------------------------------------
sub getFreeRepoSpace {
    # Get inputs
    my ($class, $user, $node) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Check if node is the management node
    my @entries = xCAT::TableUtils->get_site_attribute("master");
    my $master = xCAT::zvmUtils->trimStr($entries[0]);
    my $ip = xCAT::NetworkUtils->getipaddr($node);
    $ip = xCAT::zvmUtils->trimStr($ip);
    my $mn = 0;
    if ($master eq $ip) {
        # If the master IP and node IP match, then it is the management node
        my $out = `$sudo /bin/df -h /install | sed 1d`;
        # causing problems on other platforms $out =~ s/\h+/ /g;
        my @results = split(' ', $out);
        if ( $results[3] eq "0" ) {
            $results[3] = "0M";
        }
        return $results[3];
    } 

    return;
}

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

=head3   findAndUpdatezFcpPool

    Description : Find and update a SCSI/FCP device in a given storage pool.
                  xCAT will find and update the SCSI/FCP device in all known pools based on the unique WWPN/LUN combo.
    Arguments   :   Message header
                    User (root or non-root)
                    zHCP
                    Storage pool
                    Criteria hash including:
                        - Status (free, reserved, or used)
                        - zFCP channel
                        - WWPN
                        - LUN
                        - Size requested
                        - Owner
                        - Tag                                      
    Returns     :   Results hash including:
                        - Return code (0 = Success, -1 = Failure)
                        - zFCP device (if one is requested)
                        - WWPN
                        - LUN
    Example     : my $resultsRef = xCAT::zvmUtils->findAndUpdatezFcpPool($callback, $header, $user, $hcp, $pool, $criteriaRef);
    
=cut

#-------------------------------------------------------
sub findAndUpdatezFcpPool {
    # Get inputs
    my ($class, $callback, $header, $user, $hcp, $pool, $criteriaRef) = @_;
        
    # Determine if sudo is used
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Directory where executables are on zHCP
    my $dir = "/opt/zhcp/bin";
        
    # Directory where FCP disk pools are on zHCP
    my $zfcpDir = "/var/opt/zhcp/zfcp";
    
    my %results = ('rc' => -1);  # Default to error

    # Extract criteria
    my %criteria = %$criteriaRef;
    my $status = defined($criteria{status}) ? $criteria{status} : "";
    my $fcpDevice = defined($criteria{fcp}) ? $criteria{fcp} : "";
    my $wwpn = defined($criteria{wwpn}) ? $criteria{wwpn} : "";
    my $lun = defined($criteria{lun}) ? $criteria{lun} : "";
    my $size = defined($criteria{size}) ? $criteria{size} : "";
    my $owner = defined($criteria{owner}) ? $criteria{owner} : "";
    my $tag = defined($criteria{tag}) ? $criteria{tag} : "";
    
    # Check required arguments: pool, status
    # If you do not know what to update, why update!
    if (!$pool && !$status) {
       return \%results;
    }
        
    # Check status
    if ($status !~ m/^(free|used|reserved)$/i) {
        xCAT::zvmUtils->printLn($callback, "$header: (Error) Status not recognized. Status can be free, used, or reserved.");
        return \%results;
    }
    
    # Check FCP device syntax
    if ($fcpDevice && ($fcpDevice !~ /^auto/i) && ($fcpDevice =~ /[^0-9a-f]/i)) {
        xCAT::zvmUtils->printLn($callback, "$header: (Error) Invalid FCP channel address $fcpDevice.");
        return \%results;
    }
    
    # Owner must be specified if status is used
    if ($status =~ m/used/i && !$owner) {
        xCAT::zvmUtils->printLn( $callback, "$header: (Error) Owner must be specified if status is used." );
        return \%results;
    } elsif ($status =~ m/free/i && $owner) {
        xCAT::zvmUtils->printLn( $callback, "$header: (Error) Owner must not be specified if status is free." );
        return \%results;
    }
        
    # Size can be M(egabytes) or G(igabytes). Convert size into MB.
    my $originSize = $size;
    if ($size) {
        if ($size =~ m/G/i) {
            # Convert to MegaBytes
            $size =~ s/\D//g;
            $size = int($size) * 1024
        } elsif ($size =~ m/M/i || !$size) {
            # Do nothing
        } else {
            xCAT::zvmUtils->printLn( $callback, "$header: (Error) Size not recognized. Size can be M(egabytes) or G(igabytes)." );
            return \%results;
        }
    }
        
    # Check if WWPN and LUN are given
    # WWPN can be given as a semi-colon separated list (multipathing)
    my $useWwpnLun = 0;
    if ($wwpn && $lun) {
        xCAT::zvmUtils->printLn($callback, "$header: Using given WWPN and LUN");
        $useWwpnLun = 1;
        
        # Make sure WWPN and LUN do not have 0x prefix
        $wwpn = xCAT::zvmUtils->replaceStr($wwpn, "0x", "");
        $lun = xCAT::zvmUtils->replaceStr($lun, "0x", "");
        
        # Check WWPN and LUN syntax
        if ( $wwpn && ($wwpn =~ /[^0-9a-f;"]/i) ) {
            xCAT::zvmUtils->printLn( $callback, "$header: (Error) Invalid world wide portname $wwpn." );
            return \%results;
        } if ( $lun && ($lun =~ /[^0-9a-f]/i) ) {
            xCAT::zvmUtils->printLn( $callback, "$header: (Error) Invalid logical unit number $lun." );
            return \%results;
        }
    }
    
    # Find disk pool (create one if non-existent)
    my $out;
    if (!(`ssh $user\@$hcp "$sudo test -d $zfcpDir && echo Exists"`)) {
        # Create pool directory
        $out = `ssh $user\@$hcp "$sudo mkdir -p $zfcpDir"`;
    }
    
    # Find if disk pool exists
    if (!(`ssh $user\@$hcp "$sudo test -e $zfcpDir/$pool.conf && echo Exists"`)) {
        # Return if xCAT is expected to find a FCP device, but no disk pool exists.
        xCAT::zvmUtils->printLn($callback, "$header: (Error) FCP storage pool does not exist");
        return \%results;
    }
        
    # Find a free disk in the pool
    # FCP devices are contained in /var/opt/zhcp/zfcp/<pool-name>.conf   
    my $range = "";
    my $sizeFound = "*";
    my @info;
    if (!$useWwpnLun) {
        # Find a suitable pair of WWPN and LUN in device pool based on requested size
        # Sample pool configuration file:
        #   #status,wwpn,lun,size,range,owner,channel,tag
        #     used,1000000000000000,2000000000000110,8g,3B00-3B3F,ihost1,1a23,$root_device$
        #     free,1000000000000000,2000000000000111,,3B00-3B3F,,,
        #     free,1230000000000000;4560000000000000,2000000000000112,,3B00-3B3F,,,
        my @devices = split("\n", `ssh $user\@$hcp "$sudo cat $zfcpDir/$pool.conf" | egrep -i ^free`);            
        $sizeFound = 0;
        foreach (@devices) {
            @info = split(',', $_);
                    
            # Check if the size is sufficient. Convert size into MB.
            if ($info[3] =~ m/G/i) {
                # Convert to MegaBytes
                $info[3] =~ s/\D//g;
                $info[3] = int($info[3]) * 1024
            } elsif ($info[3] =~ m/M/i) {
                # Do nothing
                $info[3] =~ s/\D//g;
            } else {
                next;
            }
            
            # Find optimal disk based on requested size
            if ($sizeFound && $info[3] >= $size && $info[3] < $sizeFound) {
                $sizeFound = $info[3];
                $wwpn = $info[1];                    
                $lun = $info[2];
                $range = $info[4];
            } elsif (!$sizeFound && $info[3] >= $size) {
                $sizeFound = $info[3];
                $wwpn = $info[1];
                $lun = $info[2];   
                $range = $info[4];       
            }
        }
        
        # Do not continue if no devices can be found
        if (!$wwpn || !$lun) {
            xCAT::zvmUtils->printLn($callback, "$header: (Error) A suitable device of $size" . "M or larger could not be found");
            return \%results;
        }
    } else {
        # Find given WWPN and LUN. Do not continue if device is used
        my $select = `ssh $user\@$hcp "$sudo cat $zfcpDir/$pool.conf" | grep -i "$wwpn,$lun"`;
        chomp($select);
        if (!$select) {
            xCAT::zvmUtils->printLn($callback, "$header: (Error) zFCP device 0x$wwpn/0x$lun could not be found in zFCP pool $pool");
            return \%results;
        }

        @info = split(',', $select);
        
        if ($size) {
            if ($info[3] =~ m/G/i) {
                # Convert to MegaBytes
                $info[3] =~ s/\D//g;
                $info[3] = int($info[3]) * 1024
            } else {
                # Do nothing
                $info[3] =~ s/\D//g;
            }
            
            # Do not continue if specified device does not have enough capacity
            if ($info[3] < $size) {
                xCAT::zvmUtils->printLn($callback, "$header: (Error) FCP device $wwpn/$lun is not large enough");
                return \%results;
            }
        }

        # Find range of the specified disk
        $range = $info[4];
    }
    
    # If there are multiple paths, take the 1st one
    # Handle multi-pathing in postscript because autoyast/kickstart does not support it.
    my $origWwpn = $wwpn;
    if ($wwpn =~ m/;/i) {
        @info = split(';', $wwpn);
        $wwpn = xCAT::zvmUtils->trimStr($info[0]);
    }
        
    xCAT::zvmUtils->printLn($callback, "$header: Found FCP device 0x$wwpn/0x$lun");
    
    if ( ($status =~ m/used/i) && ($fcpDevice =~ /^auto/i) ) {
        # select an eligible FCP device
        $fcpDevice = xCAT::zvmUtils->selectFcpDevice($callback, $header, $user, $hcp, $fcpDevice, $range, $owner);
        if (!$fcpDevice) {
            return \%results;
        }
    } elsif ($status =~ m/free/i) {
        # Owner and FCP channel make no sense when status is free
        $fcpDevice = "";
        $owner = "";
    }
            
    # Mark WWPN and LUN as used, free, or reserved and set the owner/channel appropriately
    # This config file keeps track of the owner of each device, which is useful in nodeset
    $size = $size . "M";
    my $select = `ssh $user\@$hcp "$sudo cat $zfcpDir/$pool.conf" | grep -i "$lun"`;
    chomp($select);
    if ($select) {
        @info = split(',', $select);
        
        if (!$info[3]) {
            $info[3] = $size;
        }
            
        # Do not update if WWPN/LUN pair is specified but the pair does not exist
        if (!($info[1] =~ m/$wwpn/i)) {
            xCAT::zvmUtils->printLn($callback, "$header: (Error) FCP device $wwpn/$lun does not exists");
            return \%results;
        }
                    
        # Entry order: status,wwpn,lun,size,range,owner,channel,tag
        # The following are never updated: wwpn, lun, size, and range
        my $update = "$status,$info[1],$info[2],$info[3],$info[4],$owner,$fcpDevice,$tag";
        my $expression = "'s#" . $select . "#" .$update . "#i'";
        $out = `ssh $user\@$hcp "$sudo sed --in-place -e $expression $zfcpDir/$pool.conf"`;
    } else {
        # Insert device entry into file
        $out = `ssh $user\@$hcp "$sudo echo \"$status,$origWwpn,$lun,$size,,$owner,$fcpDevice,$tag\" >> $zfcpDir/$pool.conf"`;
    }

    # Generate results hash
    %results = (
        'rc' => 0,
        'fcp' => $fcpDevice,
        'wwpn' => $wwpn,
        'lun' => $lun
    );
    return \%results;
}

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

=head3   selectFcpDevice

    Description : Select an eligible FCP device for attaching a zFCP device to a node
    Arguments   :   Message header
                    User (root or non-root)
                    zHCP
                    candidate FCP devices or auto
                    FCP device range
                    zFCP device owner
    Returns     : selected FCP device or empty if no one is selected
    Example     : my $fcpDevice = xCAT::zvmUtils->selectFcpDevice($callback, $header, $user, $hcp, $fcpDevice, $range, $owner);
    
=cut

#-------------------------------------------------------
sub selectFcpDevice {
    # Get inputs
    my ($class, $callback, $header, $user, $hcp, $fcpDevice, $range, $owner) = @_;
        
    # Determine if sudo is used
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    # Directory where executables are on zHCP
    my $dir = "/opt/zhcp/bin";
        
    # Directory where FCP disk pools are on zHCP
    my $zfcpDir = "/var/opt/zhcp/zfcp";
    
    my %results = ('rc' => -1);  # Default to error
    
    # Check FCP device syntax
    if ($fcpDevice && ($fcpDevice !~ /^auto/i) && ($fcpDevice =~ /[^0-9a-f]/i)) {
        xCAT::zvmUtils->printLn($callback, "$header: (Error) Invalid FCP channel address $fcpDevice.");
        return;
    }
    
    # Find a free FCP device based on the given range
    if ($fcpDevice =~ m/^auto/i) {
        my @ranges;
        my $min;
        my $max;
        my $found = 0;
                
        if ($range =~ m/;/i) {
            @ranges = split(';', $range);
        } else {
            push(@ranges, $range);
        }
                    
        if (!$found) {
            # If the node has an eligible FCP device, use it
            my @deviceList = xCAT::zvmUtils->getDedicates($callback, $user, $owner);
            foreach (@deviceList) {
                # Check if this devide is eligible (among the range specified for disk $lun)
                my @info = split(' ', $_);
                my $candidate = $info[2];
                foreach (@ranges) {
                    ($min, $max) = split('-', $_);
                    if (hex($candidate) >= hex($min) && hex($candidate) <= hex($max)) {
                        $found = 1;
                        $fcpDevice = uc($candidate);
                        
                        last;
                    }
                }
                
                if ($found) {
                    xCAT::zvmUtils->printLn($callback, "$header: Found eligible FCP channel $fcpDevice");
                    last;
                }       
            }
        }
        
        if (!$found) {
            # If the node has no eligible FCP device, find a free one for it.
            my %usedDevices = xCAT::zvmUtils->getUsedFcpDevices($user, $hcp);
            
            my $hcpUserId = xCAT::zvmCPUtils->getUserId($user, $hcp);
            $hcpUserId =~ tr/a-z/A-Z/;
        
            # Find a free FCP channel
            my $out = `ssh $user\@$hcp "$sudo $dir/smcli System_WWPN_Query -T $hcpUserId" | egrep -i "FCP device number|Status"`;
            my @devices = split( "\n", $out );
            for (my $i = 0; $i < @devices; $i++) {
                # Extract the device number and status
                $fcpDevice = $devices[$i];
                $fcpDevice =~ s/^FCP device number:(.*)/$1/;
                $fcpDevice =~ s/^\s+//;
                $fcpDevice =~ s/\s+$//;
                        
                $i++;
                my $fcpStatus = $devices[$i];
                $fcpStatus =~ s/^Status:(.*)/$1/;
                $fcpStatus =~ s/^\s+//;
                $fcpStatus =~ s/\s+$//;                    
                        
                # Only look at free FCP devices
                if ($fcpStatus =~ m/free/i) {                    
                    # If the device number is within the specified range, exit out of loop
                    # Range: 3B00-3C00;4B00-4C00;5E12-5E12
                    foreach (@ranges) {
                        ($min, $max) = split('-', $_);
                        if (hex($fcpDevice) >= hex($min) && hex($fcpDevice) <= hex($max)) {
                            $fcpDevice = uc($fcpDevice);
                
                            # Use found FCP channel if not in use or allocated                        
                            if (!$usedDevices{$fcpDevice}) {
                                $found = 1;
                                last;
                            }
                        }
                    }
                }
                
                # Break out of loop if FCP channel is found
                if ($found) {
                    xCAT::zvmUtils->printLn($callback, "$header: Found FCP channel within acceptable range $fcpDevice");
                    last;
                }
            }
        }
            
        # Do not continue if no FCP channel is found
        if (!$found) {
            xCAT::zvmUtils->printLn($callback, "$header: (Error) A suitable FCP channel could not be found");
            return;
        }
    }
    
    # If there are multiple devices (multipathing), take the 1st one
    if ($fcpDevice) {
        if ($fcpDevice =~ m/;/i) {
            my @info = split(';', $fcpDevice);
            $fcpDevice = xCAT::zvmUtils->trimStr($info[0]);
        }
                    
        # Make sure channel has a length of 4
        while (length($fcpDevice) < 4) {
            $fcpDevice = "0" . $fcpDevice;
        }
    }
    
    return $fcpDevice;
}

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

=head3   findzFcpDevicePool

    Description : Find the zFCP storage pool that contains the given zFCP device
    Arguments   :   User (root or non-root)
                    zHCP
                    WWPN
                    LUN
    Returns     : Storage pool where zFCP device resides
    Example     : my $pool = xCAT::zvmUtils->findzFcpDevicePool($user, $hcp, $wwpn, $lun);
    
=cut

#-------------------------------------------------------
sub findzFcpDevicePool {

    # Get inputs
    my ( $class, $user, $hcp, $wwpn, $lun ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
            
    # Directory where FCP disk pools are on zHCP
    my $zfcpDir = "/var/opt/zhcp/zfcp";

    # Find the pool that contains the SCSI/FCP device
    my @pools = split("\n", `ssh $user\@$hcp "$sudo grep -i -l \"$wwpn,$lun\" $zfcpDir/*.conf"`);
    my $pool = ""; 
    if (scalar(@pools)) {
        $pool = basename($pools[0]);
        $pool =~ s/\.[^.]+$//;  # Do not use extension
    }

    return $pool;
}

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

=head3   findzFcpDeviceAttr

    Description : Find the zFCP device attributes
    Arguments   :   User (root or non-root)
                    zHCP
                    Storage pool
                    WWPN
                    LUN
    Returns     : Architecture of node
    Example     : my $deviceRef = xCAT::zvmUtils->findzFcpDeviceAttr($user, $hcp, $pool, $wwpn, $lun);
    
=cut

#-------------------------------------------------------
sub findzFcpDeviceAttr {

    # Get inputs
    my ( $class, $user, $hcp, $pool, $wwpn, $lun ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
            
    # Directory where FCP disk pools are on zHCP
    my $zfcpDir = "/var/opt/zhcp/zfcp";

    # Find the SCSI/FCP device
    # Entry order: status,wwpn,lun,size,range,owner,channel,tag
    my @info = split("\n", `ssh $user\@$hcp "$sudo grep -i \"$wwpn,$lun\" $zfcpDir/$pool.conf"`);
    my $entry = $info[0];
    chomp($entry);
    
    # Do not continue if no device is found
    my %attrs = ();
    if (!$entry) {
        return \%attrs;
    }
    
    @info = split(',', $entry);    
    %attrs = (
        'status' => $info[0],
        'wwpn' => $info[1],
        'lun' => $info[2],
        'size' => $info[3],
        'range' => $info[4],
        'owner' => $info[5],
        'fcp' => $info[6],
        'tag' => $info[7]        
    );
    
    return \%attrs;
}

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

=head3   findUsablezHcpNetwork

    Description : Find a useable NIC shared with the zHCP for a given user Id
    Arguments   :   User (root or non-root)
                    zHCP
                    User Id to find a useable NIC on
                    DHCP is used or not (0 or 1)
    Returns     : NIC, device channel, and layer (2 or 3)
    Example     : my ($nic, $channel, $layer) = xCAT::zvmUtils->findUsablezHcpNetwork($user, $hcp, $userId, $dhcp);
    
=cut

#-------------------------------------------------------
sub findUsablezHcpNetwork {
	# Get inputs
    my ( $class, $user, $hcp, $userId, $dhcp ) = @_;
    
    my $sudo = "sudo";
    if ($user eq "root") {
        $sudo = "";
    }
    
    my $nic = '';  # Usuable NIC on zHCP
    my $channel = '';  # Device channel where NIC is attached
    my $layer;
    my $i;
    my @words;
    
    # Get the networks used by the zHCP
    my @hcpNetworks = xCAT::zvmCPUtils->getNetworkNamesArray($user, $hcp);        

    # Search directory entry for network name
    my $userEntry = `ssh $user\@$hcp "$sudo $::DIR/smcli Image_Query_DM -T $userId" | sed '\$d'`;    
    xCAT::zvmUtils->printSyslog("findUsablezHcpNetwork() smcli Image_Query_DM -T $userId");
    xCAT::zvmUtils->printSyslog("findUsablezHcpNetwork() $userEntry");
    
    my $out = `echo "$userEntry" | grep "NICDEF"`;
    my @lines = split('\n', $out);
        
    # Go through each line
    for ($i = 0; $i < @lines; $i++) {
        # Go through each network device attached to zHCP
        foreach (@hcpNetworks) {
                
            # If network device is found
            if ($lines[$i] =~ m/ $_/i) {
                # Get network layer
                $layer = xCAT::zvmCPUtils->getNetworkLayer($user, $hcp, $_);
                xCAT::zvmUtils->printSyslog("findUsablezHcpNetwork() NIC:$_ layer:$layer");
                    
                # If template using DHCP, layer must be 2
                if ((!$dhcp && $layer != 2) || (!$dhcp && $layer == 2) || ($dhcp && $layer == 2)) {
                    # Save network name
                    $nic = $_;
                        
                    # Get network virtual address
                    @words = split(' ',  $lines[$i]);
                        
                    # Get virtual address (channel)
                    # Convert subchannel to decimal
                    $channel = sprintf('%d', hex($words[1]));
                    
                    xCAT::zvmUtils->printSyslog("findUsablezHcpNetwork() Candidate found NIC:$nic channel:$channel layer:$layer"); 
                    return ($nic, $channel, $layer);
                } else {
                    # Go to next network available
                    $nic = '';
                }
            }
        }
    }

    # If network device is not found
    if (!$nic) {
        # Check for user profile
        my $profileName = `echo "$userEntry" | grep "INCLUDE"`;
        if ($profileName) {
            @words = split(' ', xCAT::zvmUtils->trimStr($profileName));
                
            # Get user profile
            my $userProfile = xCAT::zvmUtils->getUserProfile($user, $hcp, $words[1]);
            xCAT::zvmUtils->printSyslog("findUsablezHcpNetwork() $userProfile");
                
            # Get the NICDEF statement containing the HCP network
            $out = `echo "$userProfile" | grep "NICDEF"`;
            @lines = split('\n', $out);
                
            # Go through each line
            for ($i = 0; $i < @lines; $i++) {
                # Go through each network device attached to zHCP
                foreach (@hcpNetworks) {
                        
                    # If network device is found
                    if ($lines[$i] =~ m/ $_/i) {
                        # Get network layer
                        $layer = xCAT::zvmCPUtils->getNetworkLayer($user, $hcp, $_);
                        xCAT::zvmUtils->printSyslog("findUsablezHcpNetwork() NIC:$_ layer:$layer");
                    
                        # If template using DHCP, layer must be 2
                        if ((!$dhcp && $layer != 2) || (!$dhcp && $layer == 2) || ($dhcp && $layer == 2)) {
                            # Save network name
                            $nic = $_;
                                
                            # Get network virtual address
                            @words = split(' ',  $lines[$i]);
                            
                            # Get virtual address (channel)
                            # Convert subchannel to decimal
                            $channel = sprintf('%d', hex($words[1]));
                            
                            xCAT::zvmUtils->printSyslog("findUsablezHcpNetwork() Candidate found NIC:$nic channel:$channel layer:$layer");
                            return ($nic, $channel, $layer);
                        } else {
                            # Go to next network available
                            $nic = '';
                        }
                    }
                } # End of foreach
            } # End of for
        } # End of if
    }
    
    return;
}