Node management sprint2 works, reviewed by xCAT beijing team

git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/trunk@13724 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd
This commit is contained in:
leiaibj 2012-09-07 08:19:36 +00:00
parent 6961c84384
commit bef2a2a82e
4 changed files with 1577 additions and 0 deletions

View File

@ -2080,4 +2080,179 @@ sub pingNodeStatus {
return %status;
}
#-------------------------------------------------------------------------------
=head3 isReservedIP
Description : Validate whether specified string is a reseved IPv4 string.
Arguments : ipstr - the string to be validated.
Returns : 1 - valid reserved String.
0 - invalid reserved String.
=cut
#-------------------------------------------------------------------------------
sub isReservedIP
{
my ($class, $ipstr) = @_;
my @ipnums = split('\.', $ipstr);
if ($ipnums[3] eq "0" || $ipnums[3] eq "255"){
return 1;
}
return 0;
}
#-------------------------------------------------------------------------------
=head3 isValidMAC
Description : Validate whether specified string is a MAC string.
Arguments : macstr - the string to be validated.
Returns : 1 - valid MAC String.
0 - invalid MAC String.
=cut
#-------------------------------------------------------------------------------
sub isValidMAC
{
my ($class, $macstr) = @_;
if ($macstr =~ /^[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}$/){
return 1;
}
return 0;
}
#-------------------------------------------------------------------------------
=head3 isValidHostname
Description : Validate whether specified string is a valid hostname.
Arguments : hostname - the string to be validated.
Returns : 1 - valid hostname String.
0 - invalid hostname String.
=cut
#-------------------------------------------------------------------------------
sub isValidHostname
{
my ($class, $hostname) = @_;
if ($hostname =~ /^[\-a-zA-Z0-9]+$/){
return 1;
}
return 0;
}
#-------------------------------------------------------------------------------
=head3 ip_to_int
Description : convert an IPv4 string into int.
Arguments : ipstr - the IPv4 string.
Returns : ipint - int number
=cut
#-------------------------------------------------------------------------------
sub ip_to_int
{
my ($class, $ipstr) = @_;
my $ipint = 0;
my @ipnums = split('\.', $ipstr);
$ipint += $ipnums[0] << 24;
$ipint += $ipnums[1] << 16;
$ipint += $ipnums[2] << 8;
$ipint += $ipnums[3];
return $ipint;
}
#-------------------------------------------------------------------------------
=head3 int_to_ip
Description : convert an int into IPv4 String.
Arguments : ipnit - the input int number.
Returns : ipstr - IPv4 String.
=cut
#-------------------------------------------------------------------------------
sub int_to_ip
{
my ($class, $ipint) = @_;
return inet_ntoa(inet_aton($ipint));
}
#-------------------------------------------------------------------------------
=head3 get_allips_in_range
Description : Get all IPs in a IP range, return in a list.
Arguments : $startip - start IP address
$endip - end IP address
$increment - increment factor
$reservflag - A flag for whether we exclude reserved ips or not
Returns : IP list in this range.
Example :
my $startip = "192.168.0.1";
my $endip = "192.168.0.100";
xCAT::NetworkUtils->get_allips_in_range($startip, $endip, 1, 1);
=cut
#-------------------------------------------------------------------------------
sub get_allips_in_range
{
my $class = shift;
my $startip = shift;
my $endip = shift;
my $increment = shift;
my $reservflag = shift;
my @iplist;
my $startipnum = xCAT::NetworkUtils->ip_to_int($startip);
my $endipnum = xCAT::NetworkUtils->ip_to_int($endip);
while ($startipnum <= $endipnum){
my $ip = xCAT::NetworkUtils->int_to_ip($startipnum);
$startipnum += $increment;
# Not return reserved IPs
if ($reservflag){
if(xCAT::NetworkUtils->isReservedIP($ip)){
next;
}
}
push (@iplist, $ip);
}
return \@iplist;
}
#-------------------------------------------------------------------------------
=head3 get_all_ips
Description : Get all IP addresses from table nics, column nicips.
Arguments : hashref - if not set, will return a reference of list,
if set, will return a reference of hash.
Returns : All IPs reference.
=cut
#-------------------------------------------------------------------------------
sub get_all_nicips{
my ($class, $hashref) = @_;
my %allipshash;
my @allipslist;
my $table = xCAT::Table->new('nics');
my @entries = $table->getAllNodeAttribs(['nicips']);
foreach (@entries){
# $_->{nicips} looks like "eth0:ip1,eth1:ip2,bmc:ip3..."
if($_->{nicips}){
my @nicandiplist = split(',', $_->{nicips});
# Each record in @nicandiplist looks like "eth0:ip1"
foreach (@nicandiplist){
my @nicandip = split(':', $_);
if ($hashref){
$allipshash{$nicandip[1]} = 0;
} else{
push (@allipslist, $nicandip[1]);
}
}
}
}
if ($hashref){
return \%allipshash;
} else{
return \@allipslist;
}
}
1;

View File

@ -0,0 +1,527 @@
# IBM(c) 2012 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT::PCMNodeMgmtUtils;
use strict;
use warnings;
use Socket;
use File::Path qw/mkpath/;
use File::Temp qw/tempfile/;
require xCAT::Table;
require xCAT::TableUtils;
require xCAT::NodeRange;
require xCAT::NetworkUtils;
#--------------------------------------------------------------------------------
=head1 xCAT::PCMNodeMgmtkUtils
=head2 Package Description
This program module file, is a set of PCM node management utilities.
=cut
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
=head3 get_allocable_staticips_innet
Description : Get allocable IPs from a network.
Arguments : $netname - network name
$exclude_ips_ref - excluded IPs list reference.
Returns : Reference of allocable IPs list
=cut
#-------------------------------------------------------------------------------
sub get_allocable_staticips_innet
{
my $class = shift;
my $netname = shift;
my $exclude_ips_ref = shift;
my %iphash;
my @allocableips;
foreach (@$exclude_ips_ref){
$iphash{$_} = 0;
}
my $networkstab = xCAT::Table->new('networks');
my $netentry = ($networkstab->getAllAttribsWhere("netname = '$netname'", 'ALL'))[0];
my ($startip, $endip) = split('-', $netentry->{'staticrange'});
my $incremental = $netentry->{'staticrangeincrement'};
my $validipsref;
if ($incremental and $startip and $endip){
$validipsref = xCAT::NetworkUtils->get_allips_in_range($startip, $endip, $incremental);
}
foreach (@$validipsref){
if (! exists($iphash{$_})){
push @allocableips, $_;
}
}
return \@allocableips;
}
#-------------------------------------------------------------------------------
=head3 genhosts_with_numric_tmpl
Description : Generate numric hostnames using numric template name.
Arguments : $format - The hostname format string..
Returns : numric hostname list
Example :
calling genhosts_with_numric_tmpl("compute#NNnode") will return a list like:
("compute00node", "compute01node", ..."compute98node", "compute99node")
=cut
#-------------------------------------------------------------------------------
sub genhosts_with_numric_tmpl
{
my ($class, $format) = @_;
my ($prefix, $appendix, $len) = xCAT::PCMNodeMgmtUtils->split_hostname($format, 'N');
return xCAT::PCMNodeMgmtUtils->gen_numric_hostnames($prefix, $appendix, $len);
}
#-------------------------------------------------------------------------------
=head3 split_hostname
Description : Split hostname format as prefix, appendix and number length.
Arguments : $format - hostname format
$patt_char - pattern char, we always use "N" to indicate numric pattern
Returns : ($prefix, $appendix, $numlen)
$prefix - the prefix string of hostname format.
$appendix - the appendix string of hostname format
$numlen - The number length in hostname format.
Example :
calling split_hostname("compute#NNnode") will return a list like:
("compute", "node", 2)
=cut
#-------------------------------------------------------------------------------
sub split_hostname
{
my ($class, $format, $patt_char) = @_;
my $idx = index $format, "#$patt_char";
my @array_format = split(//, $format);
my $pos = $idx+2;
while ( $pos <= (scalar(@array_format) - 1)){
if ($array_format[$pos] eq "$patt_char"){
$pos++;
}else{
last;
}
}
my $ridx = $pos - 1;
my $prefix = "";
$prefix = substr $format, 0, $idx;
my $appendix = "";
if (($ridx + 1) != scalar(@array_format)){
$appendix = substr $format, $ridx + 1;
}
return $prefix, $appendix, ($ridx - $idx);
}
#-------------------------------------------------------------------------------
=head3 gen_numric_hostnames
Description : Generate numric hostnames.
Arguments : $prefix - The prefix string of the hostname.
$appendix - The appendix string of the hostname.
$len - the numric number length in hostname.
Returns : numric hostname list
Example :
calling gen_numric_hostnames("compute", "node",2) will return a list like:
("compute00node", "compute01node", ..."compute98node", "compute99node")
=cut
#-------------------------------------------------------------------------------
sub gen_numric_hostnames
{
my ($class, $prefix, $appendix, $len) = @_;
my @hostnames;
my $cnt=0;
my $maxnum = 10 ** $len;
while($cnt < $maxnum)
{
my $fullnum = $maxnum + $cnt;
my $hostname = $prefix.(substr $fullnum, 1).$appendix;
push (@hostnames, $hostname);
$cnt++;
}
return \@hostnames;
}
#-------------------------------------------------------------------------------
=head3 get_hostname_format_type
Description : Get hostname format type.
Arguments : $format - hostname format
Returns : hostname format type value:
"numric" - numric hostname format.
"rack" - rack info hostname format.
Example :
calling get_hostname_format_type("compute#NNnode") will return "numric"
calling get_hostname_format_type("compute-#RR-#NN") will return "rack"
=cut
#-------------------------------------------------------------------------------
sub get_hostname_format_type{
my ($class, $format) = @_;
my $type;
my $ridx = index $format, "#R";
my $nidx = index $format, "#N";
if ($ridx >= 0){
$type = "rack";
} elsif ($nidx >= 0){
$type = "numric";
}
return $type;
}
#-------------------------------------------------------------------------------
=head3 rackformat_to_numricformat
Description : convert rack hostname format into numric hostname format.
Arguments : $format - rack hostname format
$racknum - rack number.
Returns : numric hostname format.
Example :
calling rackformat_to_numricformat("compute-#RR-#NN", 1) will return "compute-01-#NN"
=cut
#-------------------------------------------------------------------------------
sub rackformat_to_numricformat{
my ($class, $format, $racknum) = @_;
my ($prefix, $appendix, $len) = xCAT::PCMNodeMgmtUtils->split_hostname($format, 'R');
my $maxnum = 10 ** $len;
my $fullnum = $maxnum + $racknum;
return $prefix.(substr $fullnum, 1).$appendix;
}
#-------------------------------------------------------------------------------
=head3 get_netprofile_nic_attrs
Description : Get networkprofile's NIC attributes and return a dict.
Arguments : $netprofilename - network profile name.
Returns : A hash %netprofileattrs for network profile attributes.
keys of %netprofileattrs are nics names, like: ib0, eth0, bmc...
values of %netprofileattrs are attributes of a specific nic, like:
type : nic type
hostnamesuffix: hostname suffix
customscript: custom script for this nic
network: network name for this nic
=cut
#-------------------------------------------------------------------------------
sub get_netprofile_nic_attrs{
my $class = shift;
my $netprofilename = shift;
my $nicstab = xCAT::Table->new( 'nics');
my $entry = $nicstab->getNodeAttribs("$netprofilename", ['nictypes', 'nichostnamesuffixes', 'niccustomscripts', 'nicnetworks']);
my %netprofileattrs;
my @nicattrslist;
if ($entry->{'nictypes'}){
@nicattrslist = split(",", $entry->{'nictypes'});
foreach (@nicattrslist){
my @nicattrs = split(":", $_);
$netprofileattrs{$nicattrs[0]}{'type'} = $nicattrs[1];
}
}
if($entry->{'nichostnamesuffixes'}){
@nicattrslist = split(",", $entry->{'nichostnamesuffixes'});
foreach (@nicattrslist){
my @nicattrs = split(":", $_);
$netprofileattrs{$nicattrs[0]}{'hostnamesuffix'} = $nicattrs[1];
}
}
if($entry->{'niccustomscripts'}){
@nicattrslist = split(",", $entry->{'niccustomscripts'});
foreach (@nicattrslist){
my @nicattrs = split(":", $_);
$netprofileattrs{$nicattrs[0]}{'customscript'} = $nicattrs[1];
}
}
if($entry->{'nicnetworks'}){
@nicattrslist = split(",", $entry->{'nicnetworks'});
foreach (@nicattrslist){
my @nicattrs = split(":", $_);
$netprofileattrs{$nicattrs[0]}{'network'} = $nicattrs[1];
}
}
return \%netprofileattrs;
}
#-------------------------------------------------------------------------------
=head3 get_netprofile_bmcnet
Description : Get bmc network name of a network profile.
Arguments : $nettmpl - network profile name
Returns : bmc network name of this network profile.
=cut
#-------------------------------------------------------------------------------
sub get_netprofile_bmcnet{
my ($class, $netprofilename) = @_;
my $netprofile_nicshash_ref = xCAT::PCMNodeMgmtUtils->get_netprofile_nic_attrs($netprofilename);
my %netprofile_nicshash = %$netprofile_nicshash_ref;
if (exists $netprofile_nicshash{'bmc'}{"network"}){
return $netprofile_nicshash{'bmc'}{"network"}
}else{
return undef;
}
}
#-------------------------------------------------------------------------------
=head3 get_netprofile_provisionnet
Description : Get deployment network of a network profile.
Arguments : $nettmpl - network profile name
Returns : deployment network name of this network profile.
=cut
#-------------------------------------------------------------------------------
sub get_netprofile_provisionnet{
my ($class, $netprofilename) = @_;
my $netprofile_nicshash_ref = xCAT::PCMNodeMgmtUtils->get_netprofile_nic_attrs($netprofilename);
my %netprofile_nicshash = %$netprofile_nicshash_ref;
my $restab = xCAT::Table->new('noderes');
my $installnicattr = $restab->getNodeAttribs($netprofilename, ['installnic']);
my $installnic = $installnicattr->{'installnic'};
if ($installnic){
if (exists $netprofile_nicshash{$installnic}{"network"}){
return $netprofile_nicshash{$installnic}{"network"}
}
}
return undef;
}
#-------------------------------------------------------------------------------
=head3 get_output_filename
Description : Generate a temp file name for placing output details for PCM node management operations.
We make this file generated under /install/ so that clients can access it through http.
Arguments : N/A
Returns : A temp filename placed under /install/pcm/work/
=cut
#-------------------------------------------------------------------------------
sub get_output_filename
{
my $installdir = xCAT::TableUtils->getInstallDir();
my $pcmworkdir = $installdir."/pcm/work/";
if (! -d $pcmworkdir)
{
mkpath($pcmworkdir);
}
return tempfile("hostinfo_result_XXXXXXX", DIR=>$pcmworkdir);
}
#-------------------------------------------------------------------------------
=head3 get_all_chassis
Description : Get all chassis in system.
Arguments : hashref: if not set, return a array ref.
if set, return a hash ref.
Returns : ref for node list.
Example :
my $arrayref = xCAT::PCMNodeMgmtUtils->get_all_chassis();
my $hashref = xCAT::PCMNodeMgmtUtils->get_all_chassis(1);
=cut
#-------------------------------------------------------------------------------
sub get_all_chassis
{
my $class = shift;
my $hashref = shift;
my %chassishash;
my @chassis = xCAT::NodeRange::noderange('__Chassis');
if ($hashref){
foreach (@chassis){
$chassishash{$_} = 1;
}
return \%chassishash;
} else{
return \@chassis;
}
}
#-------------------------------------------------------------------------------
=head3 get_allnode_singleattrib_hash
Description : Get all records of a column from a table, then return a hash.
The return hash's keys are the records of this attribute
and values are all set as 1.
Arguments : $tabname - the table name.
$attr - the attribute name.
Returns : Reference of the records hash.
=cut
#-------------------------------------------------------------------------------
sub get_allnode_singleattrib_hash
{
my $class = shift;
my $tabname = shift;
my $attr = shift;
my $table = xCAT::Table->new($tabname);
my @entries = $table->getAllNodeAttribs([$attr]);
my %allrecords;
foreach (@entries) {
if ($_->{$attr}){
$allrecords{$_->{$attr}} = 0;
}
}
return \%allrecords;
}
#-------------------------------------------------------------------------------
=head3 acquire_lock
Description : Create lock file for node management plugin so that there is
no multi node mamangement plugins running.
The file content will be the pid of the plugin running process.
Once the plugin process terminated abnormally and lock file not removed,
next time node management plugin runs, it will read the lock file
and check whether this process is running or not. If not,
just remove this lock file and create a new one.
Arguments : N/A
Returns : 1 - create lock success.
0 - failed, there may be a node management process running.
=cut
#-------------------------------------------------------------------------------
sub acquire_lock
{
my $lockfile = "/var/lock/pcm/nodemgmt";
my $lockdir = "/var/lock/pcm";
mkdir "$lockdir", 0755 unless -d "$lockdir";
if (-e $lockfile) {
open LOCKFILE, "<$lockfile";
my @lines = <LOCKFILE>;
my $pid = $lines[0];
if (-d "/proc/$pid") {
return 0;
} else{
# the process already not exists, remove lock file.
File::Path->rmtree($lockfile);
}
}
open LOCKFILE, ">$lockfile";
print LOCKFILE $$;
close LOCKFILE;
return 1;
}
#-------------------------------------------------------------------------------
=head3 release_lock
Description : Release lock for node management process.
Arguments : N/A
Returns : N/A
=cut
#-------------------------------------------------------------------------------
sub release_lock
{
my $lockfile = "/var/lock/pcm/nodemgmt";
if (-e $lockfile){
open LOCKFILE, "<$lockfile";
my @lines = <LOCKFILE>;
my $pid = $lines[0];
close LOCKFILE;
if ($pid ne $$){
File::Path->rmtree($lockfile);
}
}
}
#-------------------------------------------------------------------------------
=head3 get_node_profiles
Description : Get nodelist's profile and return a hash ref.
Arguments : node list.
Returns : nodelist's profile hash.
keys are node names.
values are hash ref. This hash ref is placing the node's profile information:
keys can be followings: "NetworkProfile", "ImageProfile", "HardwareProfile"
values are the profile names.
=cut
#-------------------------------------------------------------------------------
sub get_nodes_profiles
{
my $class = shift;
my $nodelistref = shift;
my %profile_dict;
my $nodelisttab = xCAT::Table->new('nodelist');
my $groupshashref = $nodelisttab->getNodesAttribs($nodelistref, ['groups']);
my %groupshash = %$groupshashref;
foreach (keys %groupshash){
my $value = $groupshash{$_};
my $groups = $value->[0]->{'groups'};
# groups looks like "__Managed,__NetworkProfile_default_cn,__ImageProfile_rhels6.3-x86_64-install-compute"
my @grouplist = split(',', $groups);
my @profilelist = ("NetworkProfile", "ImageProfile", "HardwareProfile");
foreach my $group (@grouplist){
foreach my $profile (@profilelist){
my $idx = index ($group, $profile);
# The Group starts with __, so index will be 2.
if ( $idx == 2 ){
# The group string will like @NetworkProfile_<profile name>
# So, index should +3, 2 for '__', 1 for _.
my $append_index = length($profile) + 3;
$profile_dict{$_}{$profile} = substr $group, $append_index;
last;
}
}
}
}
return \%profile_dict;
}
#-------------------------------------------------------------------------------
=head3 get_imageprofile_prov_method
Description : Get A node's provisioning method from its imageprofile attribute.
Arguments : $imgprofilename - imageprofile name
Returns : node's provisioning method: install, netboot...etc
=cut
#-------------------------------------------------------------------------------
sub get_imageprofile_prov_method
{
# For imageprofile, we can get node's provisioning method through:
# nodetype table: node (imageprofile name), provmethod (osimage name)
# osimage table: imagename (osimage name), provmethod (node deploy method: install, netboot...)
my $class = shift;
my $imgprofilename = shift;
my $nodetypestab = xCAT::Table->new('nodetype');
my $entry = ($nodetypestab->getAllAttribsWhere("node = '$imgprofilename'", 'ALL' ))[0];
my $osimgname = $entry->{'provmethod'};
my $osimgtab = xCAT::Table->new('osimage');
my $osimgentry = ($osimgtab->getAllAttribsWhere("imagename = '$osimgname'", 'ALL' ))[0];
return $osimgentry->{'provmethod'};
}

View File

@ -0,0 +1,117 @@
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT_plugin::00pcmkitbase;
use strict;
use warnings;
require xCAT::Utils;
require xCAT::Table;
require xCAT::PCMNodeMgmtUtils;
#-------------------------------------------------------
=head1
xCAT plugin, which is also the default PCM kit plugin.
These commands are called by PCM node management commands,
should not be called directly by external.
=cut
#-------------------------------------------------------
#-------------------------------------------------------
=head3 handled_commands
Return list of commands handled by this plugin
=cut
#-------------------------------------------------------
sub handled_commands {
return {
kitcmd_nodemgmt_add => '00pcmkitbase',
kitcmd_nodemgmt_remove => '00pcmkitbase',
kitcmd_nodemgmt_update => '00pcmkitbase',
kitcmd_nodemgmt_refresh => '00pcmkitbase',
kitcmd_nodemgmt_finished => '00pcmkitbase',
};
}
#-------------------------------------------------------
=head3 process_request
Process the command. This is the main call.
=cut
#-------------------------------------------------------
sub process_request {
my $request = shift;
my $callback = shift;
my $request_command = shift;
my $command = $request->{command}->[0];
my $argsref = $request->{arg};
my $nodelist = $request->{node};
if($command eq 'kitcmd_nodemgmt_add')
{
$request_command->({command=>["makehosts"], node=>$nodelist});
$request_command->({command=>["makedns"], node=>$nodelist}, arg=>['-n']);
# Work around for makedns bug, it will set umask to 0007.
umask(0022);
$request_command->({command=>["makedhcp"], node=>$nodelist});
$request_command->({command=>["makeknownhosts"], node=>$nodelist});
my $firstnode = (@$nodelist)[0];
my $profileref = xCAT::PCMNodeMgmtUtils->get_nodes_profiles([$firstnode]);
my %profilehash = %$profileref;
if (exists $profilehash{$firstnode}{"ImageProfile"}){
$request_command->({command=>["nodeset"], node=>$nodelist, arg=>['osimage='.$profilehash{$firstnode}{"ImageProfile"}]});
}
}
elsif ($command eq 'kitcmd_nodemgmt_remove'){
$request_command->({command=>["nodeset"], node=>$nodelist, arg=>['offline']});
$request_command->({command=>["makeknownhosts"], node=>$nodelist, arg=>['-r']});
$request_command->({command=>["makedhcp"], node=>$nodelist, arg=>['-d']});
$request_command->({command=>["makedns"], node=>$nodelist, arg=>['-d']});
# Work around for makedns bug, it will set umask to 0007.
umask(0022);
$request_command->({command=>["makehosts"], node=>$nodelist, arg=>['-d']});
}
elsif ($command eq 'kitcmd_nodemgmt_update'){
$request_command->({command=>["makehosts"], node=>$nodelist});
$request_command->({command=>["makedns"], node=>$nodelist}, arg=>['-n']);
# Work around for makedns bug, it will set umask to 0007.
umask(0022);
$request_command->({command=>["makedhcp"], node=>$nodelist});
$request_command->({command=>["makeknownhosts"], node=>$nodelist});
my $firstnode = (@$nodelist)[0];
my $profileref = xCAT::PCMNodeMgmtUtils->get_nodes_profiles([$firstnode]);
my %profilehash = %$profileref;
if (exists $profilehash{$firstnode}{"ImageProfile"}){
$request_command->({command=>["nodeset"], node=>$nodelist, arg=>['osimage='.$profilehash{$firstnode}{"ImageProfile"}]});
}
}
elsif ($command eq 'kitcmd_nodemgmt_refresh'){
$request_command->({command=>["makehosts"], node=>$nodelist});
$request_command->({command=>["makedns"], node=>$nodelist}, arg=>['-n']);
# Work around for makedns bug, it will set umask to 0007.
umask(0022);
$request_command->({command=>["makedhcp"], node=>$nodelist});
$request_command->({command=>["makeknownhosts"], node=>$nodelist});
}
elsif ($command eq 'kitcmd_nodemgmt_finished')
{
$request_command->({command=>["makeconservercf"]});
}
else
{
}
}
1;

View File

@ -0,0 +1,758 @@
# IBM(c) 2012 EPL license http://www.eclipse.org/legal/epl-v10.html
#-------------------------------------------------------
=head1
xCAT plugin to support PCM node management
These commands are designed to be called by PCM GUI.
=cut
#-------------------------------------------------------
package xCAT_plugin::pcmnodes;
use strict;
use warnings;
require xCAT::Table;
require xCAT::DBobjUtils;
require xCAT::Utils;
require xCAT::TableUtils;
require xCAT::NetworkUtils;
require xCAT::MsgUtils;
require xCAT::PCMNodeMgmtUtils;
# Globals.
# These 2 global variables are for storing the parse result of hostinfo file.
# These 2 global varialbes are set in lib xCAT::DBobjUtils->readFileInput.
#%::FILEATTRS;
#@::fileobjnames;
# All database records.
my %allhostnames;
my %allbmcips;
my %allmacs;
my %allips;
my %allinstallips;
my %allnicips;
my %allracks;
my %allchassis;
# Define parameters for xcat requests.
my $request;
my $callback;
my $request_command;
my $command;
my $args;
# Put arguments in a hash.
my %args_dict;
#-------------------------------------------------------
=head3 handled_commands
Return list of commands handled by this plugin
=cut
#-------------------------------------------------------
sub handled_commands {
return {
addhost_hostfile => 'pcmnodes',
addhost_discover => 'pcmnodes',
removehost => 'pcmnodes',
updatehost => 'pcmnodes',
};
}
#-------------------------------------------------------
=head3 process_request
Process the command. This is the main call.
=cut
#-------------------------------------------------------
sub process_request {
my $lock = xCAT::PCMNodeMgmtUtils->acquire_lock();
unless ($lock){
setrsp_errormsg("Can not acquire lock, some process is operating node related actions.");
return;
}
$request = shift;
$callback = shift;
$request_command = shift;
$command = $request->{command}->[0];
$args = $request->{arg};
if ($command eq "addhost_hostfile"){
addhost_hostfile()
} elsif ($command eq "removehost"){
removehost();
} elsif ($command eq "updatehost"){
updatehost();
}
xCAT::PCMNodeMgmtUtils->release_lock($lock);
}
#-------------------------------------------------------
=head3 parse_args
Description : Parse arguments. We placed arguments into a directory %args_dict
Arguments : args - args of xCAT requests.
Returns : undef - parse succeed.
A string - parse arguments failed, the return value is error message.
=cut
#-----------------------------------------------------
sub parse_args{
foreach my $arg (@$args){
my @argarray = split(/=/,$arg);
my $arglen = @argarray;
if ($arglen > 2){
return "Illegal arg $arg specified.";
}
# translate the profile names into real group names in db.
if($argarray[0] eq "networkprofile"){
$args_dict{$argarray[0]} = "__NetworkProfile_".$argarray[1];
} elsif ($argarray[0] eq "imageprofile"){
$args_dict{$argarray[0]} = "__ImageProfile_".$argarray[1];
} elsif ($argarray[0] eq "hardwareprofile"){
$args_dict{$argarray[0]} = "__HardwareProfile_".$argarray[1];
} else{
$args_dict{$argarray[0]} = $argarray[1];
}
}
return undef;
}
#-------------------------------------------------------
=head3 addhost_hostfile
Description : Create nodes by import hostinfo file.
Arguments : N/A
=cut
#-------------------------------------------------------
sub addhost_hostfile {
# Parse arges.
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]Import PCM nodes through hostinfo file.\n");
my $retstr = parse_args();
if ($retstr){
setrsp_errormsg($retstr);
return;
}
# Make sure the specified parameters are valid ones.
# TODO: support privisioning template.
my @enabledparams = ('file', 'groups', 'networkprofile', 'hardwareprofile', 'imageprofile');
foreach my $argname (keys %args_dict){
if (! grep{ $_ eq $argname} @enabledparams){
setrsp_errormsg("Illegal attribute $argname specified.");
return;
}
}
# validate hostinfo file.
if (! exists $args_dict{'file'}){
setrsp_errormsg("No hostinfo file specified.");
return;
}
elsif(! (-e $args_dict{'file'})){
setrsp_errormsg("The hostinfo file not exists.");
return;
}
# Get database records: all hostnames, all ips, all racks...
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]Getting database records.\n");
my $recordsref = xCAT::PCMNodeMgmtUtils->get_allnode_singleattrib_hash('nodelist', 'node');
%allhostnames = %$recordsref;
$recordsref = xCAT::PCMNodeMgmtUtils->get_allnode_singleattrib_hash('ipmi', 'bmc');
%allbmcips = %$recordsref;
$recordsref = xCAT::PCMNodeMgmtUtils->get_allnode_singleattrib_hash('mac', 'mac');
%allmacs = %$recordsref;
$recordsref = xCAT::PCMNodeMgmtUtils->get_allnode_singleattrib_hash('hosts', 'ip');
%allinstallips = %$recordsref;
$recordsref = xCAT::NetworkUtils->get_all_nicips(1);
%allips = %$recordsref;
# Merge all BMC IPs and install IPs into allips.
%allips = (%allips, %allbmcips, %allinstallips);
#my $recordsref = xCAT::PCMNodeMgmtUtils->get_allnode_singleattrib_hash('rack', 'rackname');
#%allracks = %$recordsref;
#my $recordsref = xCAT::PCMNodeMgmtUtils->get_allchassis(1);
#%allchassis = %$recordsref;
# Generate temporary hostnames for hosts entries in hostfile.
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]Generate temporary hostnames.\n");
my ($retcode_read, $retstr_read) = read_and_generate_hostnames($args_dict{'file'});
if ($retcode_read != 0){
setrsp_errormsg($retstr_read);
return;
}
# Parse and validate the hostinfo string. The real hostnames will be generated here.
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]Parsing hostinfo string and validate it.\n");
my ($hostinfo_dict_ref, $invalid_records_ref) = parse_hosts_string($retstr_read);
my %hostinfo_dict = %$hostinfo_dict_ref;
my @invalid_records = @$invalid_records_ref;
if (@invalid_records){
setrsp_invalidrecords(\@invalid_records);
return;
}
unless (%hostinfo_dict){
setrsp_errormsg("No valid host records found in hostinfo file.");
return;
}
# Create the real hostinfo string in stanza file format.
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]Generating new hostinfo string.\n");
my ($retcode_gen, $retstr_gen) = gen_new_hostinfo_string(\%hostinfo_dict);
unless ($retcode_gen){
setrsp_errormsg($retstr_gen);
return;
}
# call mkdef to create hosts and then call nodemgmt for node management plugins.
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]call mkdef to create pcm nodes.\n");
$request_command->({command=>["mkdef"], stdin=>[$retstr_gen], arg=>['-z']});
my @nodelist = keys %hostinfo_dict;
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]call nodemgmt plugins.\n");
$request_command->({command=>["kitcmd_nodemgmt_add"], node=>\@nodelist});
$request_command->({command=>["kitcmd_nodemgmt_finished"], node=>\@nodelist});
setrsp_success(\@nodelist);
}
#-------------------------------------------------------
=head3 removehost
Description : Remove nodes.
Arguments : N/A
=cut
#-------------------------------------------------------
sub removehost{
my $nodes = $request->{node};
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]Remove PCM nodes.\n");
# For remove nodes, we should call 'nodemgmt' in front of 'noderm'
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]call nodemgmt plugins.\n");
$request_command->({command=>["kitcmd_nodemgmt_remove"], node=>$nodes});
$request_command->({command=>["kitcmd_nodemgmt_finished"], node=>$nodes});
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]call noderm to remove nodes.\n");
$request_command->({command=>["noderm"], node=>$nodes});
setrsp_success($nodes);
}
#-------------------------------------------------------
=head3 updatehost
Description : Update host profiles.
Arguments : N/A
=cut
#-------------------------------------------------------
sub updatehost{
my $nodes = $request->{node};
my %updated_groups;
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]Update PCM nodes settings.\n");
# Parse arges.
my $retstr = parse_args();
if ($retstr){
setrsp_errormsg($retstr);
return;
}
# Make sure the specified parameters are valid ones.
# TODO: support privisioning template.
my @enabledparams = ('networkprofile', 'hardwareprofile', 'imageprofile');
foreach my $argname (keys %args_dict){
if (! grep{ $_ eq $argname} @enabledparams){
setrsp_errormsg("Illegal attribute $argname specified.");
return;
}
}
# Get current templates for all nodes.
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]Read database to get groups for all nodes.\n");
my %groupdict;
my $nodelstab = xCAT::Table->new('nodelist');
my $nodeshashref = $nodelstab->getNodesAttribs($nodes, ['groups']);
my %nodeshash = %$nodeshashref;
my %updatenodeshash;
foreach (keys %nodeshash){
my @groups;
my $attrshashref = $nodeshash{$_}[0];
my %attrshash = %$attrshashref;
if ($attrshash{'groups'}){
@groups = split(/,/, $attrshash{'groups'});
my $groupsref;
# Replace the old template name with new specified ones in args_dict
if(exists $args_dict{'networkprofile'}){
$groupsref = replace_item_in_array(\@groups, "NetworkProfile", $args_dict{'networkprofile'});
}
if(exists $args_dict{'hardwareprofile'}){
$groupsref = replace_item_in_array(\@groups, "HardwareProfile", $args_dict{'hardwareprofile'});
}
if(exists $args_dict{'imageprofile'}){
$groupsref = replace_item_in_array(\@groups, "ImageProfile", $args_dict{'imageprofile'});
}
$updatenodeshash{$_}{'groups'} = join (',', @$groupsref);
}
}
#update DataBase.
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]Update database records.\n");
my $nodetab = xCAT::Table->new('nodelist',-create=>1);
$nodetab->setNodesAttribs(\%updatenodeshash);
$nodetab->close();
# call plugins
xCAT::MsgUtils->message('S', "[PCM nodes mgmt]call nodemgmt plugins.\n");
$request_command->({command=>["kitcmd_nodemgmt_update"], node=>$nodes});
$request_command->({command=>["kitcmd_nodemgmt_finished"], node=>$nodes});
setrsp_success($nodes);
}
#-------------------------------------------------------
=head3 replace_item_in_array
Description : Replace an item in a list with new value. This item should match specified pattern.
Arguments : arrayref - the list.
pattern - the pattern which the old item must match.
newitem - the updated value.
=cut
#-------------------------------------------------------
sub replace_item_in_array{
my $arrayref = shift;
my $pattern = shift;
my $newitem = shift;
my @newarray;
foreach (@$arrayref){
if ($_ =~ /__$pattern/){
next;
}
push (@newarray, $_);
}
push(@newarray, $newitem);
return \@newarray;
}
#-------------------------------------------------------
=head3 gen_new_hostinfo_string
Description : Generate a stanza file format string used for 'mkdef' to create nodes.
Arguments : hostinfo_dict_ref - The reference of hostinfo dict.
Returns : (returnvalue, returnmsg)
returnvalue - 0, stands for generate new hostinfo string failed.
1, stands for generate new hostinfo string OK.
returnnmsg - error messages if generate failed.
- the new hostinfo string if generate OK.
=cut
#-------------------------------------------------------
sub gen_new_hostinfo_string{
my $hostinfo_dict_ref = shift;
my %hostinfo_dict = %$hostinfo_dict_ref;
# Get free ips list for all networks in network profile.
my @allknownips = keys %allips;
my $netprofileattrsref = xCAT::PCMNodeMgmtUtils->get_netprofile_nic_attrs($args_dict{'networkprofile'});
my %netprofileattr = %$netprofileattrsref;
my %freeipshash;
foreach (keys %netprofileattr){
my $netname = $netprofileattr{$_}{'network'};
if($netname and (! exists $freeipshash{$netname})) {
$freeipshash{$netname} = xCAT::PCMNodeMgmtUtils->get_allocable_staticips_innet($netname, \@allknownips);
}
}
# Get networkprofile's installip
my $noderestab = xCAT::Table->new('noderes');
my $networkprofile = $args_dict{'networkprofile'};
my $nodereshashref = $noderestab->getNodeAttribs($networkprofile, ['installnic']);
my %nodereshash = %$nodereshashref;
my $installnic = $nodereshash{'installnic'};
# Get node's provisioning method
my $provmethod = xCAT::PCMNodeMgmtUtils->get_imageprofile_prov_method($args_dict{'imageprofile'});
# compose the stanza string for hostinfo file.
my $hostsinfostr = "";
foreach my $item (keys %hostinfo_dict){
# Generate IPs for all interfaces.
my %ipshash;
foreach (keys %netprofileattr){
my $netname = $netprofileattr{$_}{'network'};
my $freeipsref;
if ($netname){
$freeipsref = $freeipshash{$netname};
}
my $nextip = shift @$freeipsref;
if (!$nextip){
return 0, "No sufficient IP address in network $netname for interface $_";
}else{
$ipshash{$_} = $nextip;
$allips{$nextip} = 0;
}
}
my $nicips = "";
foreach(keys %ipshash){
$nicips = "$_:$ipshash{$_},$nicips";
}
$hostinfo_dict{$item}{"nicips"} = $nicips;
# Generate IP address if no IP specified.
if (! exists $hostinfo_dict{$item}{"ip"}) {
if (exists $ipshash{$installnic}){
$hostinfo_dict{$item}{"ip"} = $ipshash{$installnic};
}else{
return 0, "No sufficient IP address for interface $installnic";
}
}
$hostinfo_dict{$item}{"objtype"} = "node";
$hostinfo_dict{$item}{"groups"} = "__Managed";
if (exists $args_dict{'networkprofile'}){$hostinfo_dict{$item}{"groups"} .= ",".$args_dict{'networkprofile'}}
if (exists $args_dict{'imageprofile'}){$hostinfo_dict{$item}{"groups"} .= ",".$args_dict{'imageprofile'}}
if (exists $args_dict{'hardwareprofile'}){$hostinfo_dict{$item}{"groups"} .= ",".$args_dict{'hardwareprofile'}}
# Update BMC records.
if (exists $netprofileattr{"bmc"}){
$hostinfo_dict{$item}{"mgt"} = "ipmi";
$hostinfo_dict{$item}{"chain"} = 'runcmd=bmcsetup,'.$provmethod;
if (exists $ipshash{"bmc"}){
$hostinfo_dict{$item}{"bmc"} = $ipshash{"bmc"};
} else{
return 0, "No sufficient IP addresses for BMC";
}
} else{
$hostinfo_dict{$item}{"chain"} = $provmethod;
}
# Generate the hostinfo string.
$hostsinfostr = "$hostsinfostr$item:\n";
my $itemdictref = $hostinfo_dict{$item};
my %itemdict = %$itemdictref;
foreach (keys %itemdict){
$hostsinfostr = "$hostsinfostr $_=\"$itemdict{$_}\"\n";
}
}
return 1, $hostsinfostr;
}
#-------------------------------------------------------
=head3 read_and_generate_hostnames
Description : Read hostinfo file and generate temporary hostnames for no-hostname specified ones.
Arguments : hostfile - the location of hostinfo file.
Returns : (returnvalue, returnmsg)
returnvalue - 0, stands for a failed return
1, stands for a success return
returnnmsg - error messages for failed return.
- the contents of the hostinfo string.
=cut
#-------------------------------------------------------
sub read_and_generate_hostnames{
my $hostfile = shift;
# Get 10000 temprary hostnames.
my $freehostnamesref = xCAT::PCMNodeMgmtUtils->gen_numric_hostnames("TMPHOSTS","", 4);
# Auto generate hostnames for "__hostname__" entries.
open(HOSTFILE, $hostfile);
my $filecontent = join("", <HOSTFILE>);
while ((index $filecontent, "__hostname__:") >= 0){
my $nexthost = shift @$freehostnamesref;
# no more valid hostnames to assign.
if (! $nexthost){
return 1, "Can not generate hostname automatically: No more valid hostnames available .";
}
# This hostname already specified in hostinfo file.
if ((index $filecontent, "$nexthost:") >= 0){
next;
}
# This hostname should not in database.
if (exists $allhostnames{$nexthost}){
next;
}
$filecontent =~ s/__hostname__/$nexthost/;
}
close(HOSTFILE);
return 0, $filecontent;
}
#-------------------------------------------------------
=head3 parse_hosts_string
Description : Parse the hostinfo string and validate it.
Arguments : filecontent - The content of hostinfo file.
Returns : (hostinfo_dict, invalid_records)
hostinfo_dict - Reference of hostinfo dict. Key are hostnames and values is an attributes dict.
invalid_records - Reference of invalid records list.
=cut
#-------------------------------------------------------
sub parse_hosts_string{
my $filecontent = shift;
my %hostinfo_dict;
my @invalid_records;
my $nicstab = xCAT::Table->new('nics');
my $nodehashref = $nicstab->getNodeAttribs($args_dict{'networkprofile'}, ['hostnameformat']);
my $nameformat = $nodehashref->{'hostnameformat'};
my $nameformattype = xCAT::PCMNodeMgmtUtils->get_hostname_format_type($nameformat);
my %freehostnames;
# Parse hostinfo file string.
xCAT::DBobjUtils->readFileInput($filecontent);
# Record duplicated items.
# We should go through list @::fileobjnames first as %::FILEATTRS is just a hash,
# it not tells whether there are some duplicated hostnames in the hostinfo string.
my %hostnamedict;
foreach my $hostname (@::fileobjnames){
if (exists $hostnamedict{$hostname}){
push @invalid_records, [$hostname, "Duplicated hostname defined"];
} else{
$hostnamedict{$hostname} = 0;
}
}
# Verify each node entry.
foreach (keys %::FILEATTRS){
my $errmsg = validate_node_entry($_, $::FILEATTRS{$_});
if ($errmsg) {
if ($_=~ /^TMPHOSTS/){
push @invalid_records, ["__hostname__", $errmsg];
} else{
push @invalid_records, [$_, $errmsg];
}
next;
}
# We need generate hostnames for this entry.
if ($_=~ /^TMPHOSTS/)
{
# rack + numric hostname format, we must specify rack in node's definition.
my $numricformat;
# Need convert hostname format into numric format first.
if ($nameformattype eq "rack"){
if (! exists $::FILEATTRS{$_}{"rack"}){
push @invalid_records, ["__hostname__", "No rack info specified. Do specify it because the nameformat contains rack info."];
next;
}
$numricformat = xCAT::PCMNodeMgmtUtils->rackformat_to_numricformat($nameformat, $::FILEATTRS{$_}{"rack"});
} else{
# pure numric hostname format
$numricformat = $nameformat;
}
# Generate hostnames based on numric hostname format.
if (! exists $freehostnames{$numricformat}){
$freehostnames{$numricformat} = xCAT::PCMNodeMgmtUtils->genhosts_with_numric_tmpl($numricformat);
}
my $hostnamelistref = $freehostnames{$numricformat};
my $nexthostname = shift @$hostnamelistref;
while (exists $allhostnames{$nexthostname}){
$nexthostname = shift @$hostnamelistref;
}
$hostinfo_dict{$nexthostname} = $::FILEATTRS{$_};
} else{
$hostinfo_dict{$_} = $::FILEATTRS{$_};
}
}
return (\%hostinfo_dict, \@invalid_records);
}
#-------------------------------------------------------
=head3 validate_node_entry
Description : Validate a node info hash.
Arguments : node_name - node hostname.
node_entry_ref - Reference of the node info hash.
Returns : errormsg
- undef: stands for no errror.
- valid string: stands for the error message of validation.
=cut
#-------------------------------------------------------
sub validate_node_entry{
my $node_name = shift;
my $node_entry_ref = shift;
my %node_entry = %$node_entry_ref;
# duplicate hostname found in hostinfo file.
if (exists $allhostnames{$node_name}) {
return "Specified hostname $node_name conflicts with database records.";
}
# Must specify either MAC or switch + port.
if (exists $node_entry{"mac"} ||
exists $node_entry{"switch"} && exists $node_entry{"port"}){
} else{
return "Neither MAC nor switch + port specified";
}
if (! xCAT::NetworkUtils->isValidHostname($node_name)){
return "Specified hostname: $node_name is invalid";
}
# validate each single value.
foreach (keys %node_entry){
if ($_ eq "mac"){
if (exists $allmacs{$node_entry{$_}}){
return "Specified MAC address $node_entry{$_} conflicts with MACs in database or hostinfo file";
}elsif(! xCAT::NetworkUtils->isValidMAC($node_entry{$_})){
return "Specified MAC address $node_entry{$_} is invalid";
}else{
$allmacs{$node_entry{$_}} = 0;
}
}elsif ($_ eq "ip"){
if (exists $allips{$node_entry{$_}}){
return "Specified IP address $node_entry{$_} conflicts with IPs in database or hostinfo file";
}elsif((xCAT::NetworkUtils->validate_ip($node_entry{$_}))[0][0] ){
return "Specified IP address $node_entry{$_} is invalid";
}elsif(xCAT::NetworkUtils->isReservedIP($node_entry{$_})){
return "Specified IP address $node_entry{$_} is invalid";
}else {
#push the IP into allips list.
$allips{$node_entry{$_}} = 0;
}
}elsif ($_ eq "switch"){
#TODO: xCAT switch discovery enhance: verify whether switch exists.
}elsif ($_ eq "port"){
#TODO: xCAT switch discovery enhance: verify whether port exists.
}elsif ($_ eq "rack"){
if (not exists $allracks{$node_entry{$_}}){
return "Specified rack $node_entry{$_} not defined";
}
}elsif ($_ eq "chassis"){
if (not exists $allchassis{$node_entry{$_}}){
return "Specified chassis $node_entry{$_} not defined";
}
}elsif ($_ eq "unit"){
# Not a valid number.
if (!($node_entry{$_} =~ /^\d+$/)){
return "Specified unit $node_entry{$_} is a invalid number";
}
}elsif ($_ eq "height"){
# Not a valid number.
if (!($node_entry{$_} =~ /^\d+$/)){
return "Specified height $node_entry{$_} is a invalid number";
}
}else{
return "Invalid attribute $_ specified";
}
}
# For blades, don't support specify unit and height.
if(exists $node_entry{"chassis"} ){
if(exists $node_entry{"unit"}){
return "Can not specify 'unit' together with 'chassis'";
}
if(exists $node_entry{"height"}){
return "can not specify 'height' together with 'chassis'";
}
}
# push hostinfo into global dicts.
$allhostnames{$node_name} = 0;
return undef;
}
#-------------------------------------------------------
=head3 setrsp_invalidrecords
Description : Set response for processing invalid host records.
Arguments : recordsref - Refrence of invalid nodes list.
=cut
#-------------------------------------------------------
sub setrsp_invalidrecords
{
my $recordsref = shift;
my $rsp;
my $master=xCAT::TableUtils->get_site_Master();
# The total number of invalid records.
$rsp->{invalid_records_num} = scalar @$recordsref;
# We write details of invalid records into a file.
my ($fh, $filename) = xCAT::PCMNodeMgmtUtils->get_output_filename();
foreach (@$recordsref){
my @erroritem = @$_;
print $fh "nodename $erroritem[0], error: $erroritem[1]\n";
}
close $fh;
# Tells the URL of the details file.
xCAT::MsgUtils->message('S', "Detailed response info placed in file: http://$master/$filename\n");
$rsp->{details} = "http://$master/$filename";
$callback->($rsp);
}
#-------------------------------------------------------
=head3 setrsp_errormsg
Description : Set response for error messages.
Arguments : errormsg - Error messages.
=cut
#-------------------------------------------------------
sub setrsp_errormsg
{
my $errormsg = shift;
my $rsp;
xCAT::MsgUtils->message('S', "$errormsg\n");
$rsp->{error}->[0] = $errormsg;
$callback->($rsp);
}
#-------------------------------------------------------
=head3 setrsp_success
Description : Set response for successfully processed nodes.
Arguments : recordsref - Refrence of nodes list.
=cut
#-------------------------------------------------------
sub setrsp_success
{
my $recordsref = shift;
my $rsp;
my $master=xCAT::TableUtils->get_site_Master();
# The total number of success nodes.
$rsp->{success_nodes_num} = scalar @$recordsref;
my ($fh, $filename) = xCAT::PCMNodeMgmtUtils->get_output_filename();
foreach (@$recordsref){
print $fh "success: $_\n";
}
close $fh;
# Tells the URL of the details file.
xCAT::MsgUtils->message('S', "Detailed response info placed in file: http://$master/$filename\n");
$rsp->{details} = "http://$master/$filename";
$callback->($rsp);
}
1;