#!/usr/bin/env perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html package xCAT::InstUtils; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; } # if AIX - make sure we include perl 5.8.2 in INC path. # Needed to find perl dependencies shipped in deps tarball. if ($^O =~ /^aix/i) { use lib "/usr/opt/perl5/lib/5.8.2/aix-thread-multi"; use lib "/usr/opt/perl5/lib/5.8.2"; use lib "/usr/opt/perl5/lib/site_perl/5.8.2/aix-thread-multi"; use lib "/usr/opt/perl5/lib/site_perl/5.8.2"; } use lib "$::XCATROOT/lib/perl"; require xCAT::Table; use POSIX qw(ceil); use Socket; use Sys::Hostname; use File::Basename; use File::Path; use strict; require xCAT::Schema; use xCAT::NetworkUtils; #require Data::Dumper; use Data::Dumper; require xCAT::NodeRange; require DBI; our @ISA = qw(Exporter); our @EXPORT_OK = qw(genpassword); #------------------------------------------------------------------------------- =head1 xCAT::InstUtils =head2 Package Description This program module file, is a set of utilities used by xCAT install related commands. =cut #------------------------------------------------------------- #---------------------------------------------------------------------------- =head3 getnimprime Get the name of the primary AIX NIM master Returns: hostname - short hostname of primary NIM master undef - could not find primary NIM master Example: my $nimprime = xCAT::InstUtils->getnimprime(); Comments: =cut #----------------------------------------------------------------------------- sub getnimprime { # the primary NIM master is either specified in the site table # or it is the xCAT management node. my $nimprime = xCAT::Utils->get_site_Master(); my $sitetab = xCAT::Table->new('site'); (my $et) = $sitetab->getAttribs({key => "NIMprime"}, 'value'); if ($et and $et->{value}) { $nimprime = $et->{value}; } my $hostname; if ($nimprime) { if (($nimprime =~ /\d+\.\d+\.\d+\.\d+/) || ($nimprime =~ /:/)) { $hostname = xCAT::NetworkUtils->gethostname($nimprime); } else { $hostname = $nimprime; } my $shorthost; ($shorthost = $hostname) =~ s/\..*$//; chomp $shorthost; return $shorthost; } return undef; } #---------------------------------------------------------------------------- =head3 myxCATname Gets the name of the node I'm running on - as known by xCAT (Either the management node or a service node) =cut #----------------------------------------------------------------------------- sub myxCATname { my ($junk, $name); $name = hostname(); if (xCAT::Utils->isMN()) { # read the site table, master attrib my $hostname = xCAT::Utils->get_site_Master(); if (($hostname =~ /\d+\.\d+\.\d+\.\d+/) || ($hostname =~ /:/)) { $name = xCAT::NetworkUtils->gethostname($hostname); } else { $name = $hostname; } } elsif (xCAT::Utils->isServiceNode()) { # the myxcatpost_ file should exist on all nodes! my $catcmd = "cat /xcatpost/myxcatpost_* | grep '^NODE='"; my $output = xCAT::Utils->runcmd("$catcmd", -1); if ($::RUNCMD_RC == 0) { ($junk, $name) = split('=', $output); } } my $shorthost; ($shorthost = $name) =~ s/\..*$//; chomp $shorthost; return $shorthost; } #---------------------------------------------------------------------------- =head3 is_me returns 1 if the hostname is the node I am running on Gets all the interfcaes defined on this node and sees if any of them match the IP of the hostname passed in Arguments: none Returns: 1 - this is the node I am running on 0 - this is not the node I am running on Globals: none Error: none Example: if (xCAT::InstUtils->is_me(&somehostname)) { blah; } Comments: none =cut #----------------------------------------------------------------------------- sub is_me { my ($class, $name) = @_; # convert to IP my $nameIP = xCAT::NetworkUtils->getipaddr($name); chomp $nameIP; # split into octets #my ($b1, $b2, $b3, $b4) = split /\./, $nameIP; # get all the possible IPs for the node I'm running on my $ifcmd = "ifconfig -a | grep 'inet'"; my $result = xCAT::Utils->runcmd($ifcmd, 0, 1); if ($::RUNCMD_RC != 0) { my $rsp; # push @{$rsp->{data}}, "Could not run $ifcmd.\n"; # xCAT::MsgUtils->message("E", $rsp, $callback); return 0; } foreach my $int (@$result) { my ($inet, $myIP, $str) = split(" ", $int); chomp $myIP; $myIP =~ s/\/.*//; # ipv6 address 4000::99/64 $myIP =~ s/\%.*//; # ipv6 address ::1%1/128 if ($myIP eq $nameIP) { return 1; } } return 0; } #---------------------------------------------------------------------------- =head3 get_nim_attrs Use the lsnim command to get the NIM attributes and values of a resource. Arguments: Returns: hash ref - OK undef - error Globals: Error: Example: $attrvals = xCAT::InstUtils-> get_nim_attrs($res, $callback, $nimprime, $subreq); Comments: =cut #----------------------------------------------------------------------------- sub get_nim_attrs { my $class = shift; my $resname = shift; my $callback = shift; my $target = shift; my $sub_req = shift; my %attrvals = undef; if (!$target) { $target = xCAT::InstUtils->getnimprime(); } chomp $target; my $cmd = "/usr/sbin/lsnim -l $resname 2>/dev/null"; my @nout = xCAT::InstUtils->xcmd($callback, $sub_req, "xdsh", $target, $cmd, 1); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Could not run lsnim command: \'$cmd\'.\n"; xCAT::MsgUtils->message("E", $rsp, $callback); return undef; } foreach my $line (@nout) { chomp $line; my $junk; my $attrval; if ($line =~ /.*$target:(.*)/) { ($junk, $attrval) = split(/:/, $line); } else { $attrval = $line; } if ($attrval =~ /=/) { my ($attr, $val) = $attrval =~ /^\s*(\S+?)\s*=\s*(\S*.*)$/; #ndebug #my $rsp; #push @{$rsp->{data}}, "attr= $attr, val= $val.\n"; #xCAT::MsgUtils->message("I", $rsp, $callback); if ($attr && $val) { # $attrvals{$resname}{$attr} = $val; $attrvals{$attr} = $val; } } } if (%attrvals) { return \%attrvals; } else { return undef; } } #---------------------------------------------------------------------------- =head3 get_nim_attr_val Use the lsnim command to find the value of a resource attribute. Arguments: Returns: 0 - OK 1 - error Globals: Error: Example: xCAT::InstUtils->get_nim_attr_val Comments: =cut #----------------------------------------------------------------------------- sub get_nim_attr_val { my $class = shift; my $resname = shift; my $attrname = shift; my $callback = shift; my $target = shift; my $sub_req = shift; if (!$target) { $target = xCAT::InstUtils->getnimprime(); } chomp $target; my $cmd = "/usr/sbin/lsnim -a $attrname -Z $resname 2>/dev/null"; my $nout = xCAT::InstUtils->xcmd($callback, $sub_req, "xdsh", $target, $cmd, 0); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Could not run lsnim command: \'$cmd\'.\n"; xCAT::MsgUtils->message("E", $rsp, $callback); return undef; } # The command output may have the xdsh prefix "target:" #my ($junk, $junk, $junk, $loc) = split(/:/, $nout); #chomp $loc; my $loc; if ($nout =~ /.*$resname:(.*):$/) { $loc = $1; } return $loc; } #------------------------------------------------------------------------------- =head3 xcmd Run command either locally or on a remote system. Calls either runcmd or runxcmd and does either xdcp or xdsh. Arguments: Returns: Output of runcmd or runxcmd or undef. Comments: ex. xCAT::InstUtils->xcmd($callback, $sub_req, "xdcp", $nimprime, $doarray, $cmd); =cut #------------------------------------------------------------------------------- sub xcmd { my $class = shift; my $callback = shift; my $sub_req = shift; my $xdcmd = shift; # xdcp or xdsh my $target = shift; # the node to run it on my $cmd = shift; # the actual cmd to run my $doarray = shift; # should the return be a string or array ptr? my $returnformat = 0; # default is to return string my $exitcode = -1; # don't display error if ($doarray) { $returnformat = $doarray; } # runxcmd uses global $::CALLBACK = $callback; my $output; if (!ref($target)) { # must be node name if (xCAT::InstUtils->is_me($target)) { $output = xCAT::Utils->runcmd($cmd, $exitcode, $returnformat); } else { my @snodes; push(@snodes, $target); $output = xCAT::Utils->runxcmd( { command => [$xdcmd], node => \@snodes, arg => ["-s", $cmd] }, $sub_req, $exitcode, $returnformat ); } } else { # it is an array ref my @snodes; @snodes = @{$target}; $output = xCAT::Utils->runxcmd( { command => [$xdcmd], node => \@snodes, arg => ["-s", $cmd] }, $sub_req, $exitcode, $returnformat ); } if ($returnformat == 1) { return @$output; } else { return $output; } return undef; } #---------------------------------------------------------------------------- =head3 readBNDfile Get the contents of a NIM installp_bundle file based on the name of the NIM resource. =cut #----------------------------------------------------------------------------- sub readBNDfile { my ($class, $callback, $BNDname, $nimprime, $sub_req) = @_; my $junk; my @pkglist, my $pkgname; # get the location of the file from the NIM resource definition my $bnd_file_name = xCAT::InstUtils->get_nim_attr_val($BNDname, 'location', $callback, $nimprime, $sub_req); # The boundle file may be on nimprime my $ccmd = qq~cat $bnd_file_name~; my $output=xCAT::InstUtils->xcmd($callback, $sub_req, "xdsh", $nimprime, $ccmd, 0); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Command: $ccmd failed."; xCAT::MsgUtils->message("E", $rsp, $callback); } # get the names of the packages #$output =~ s/$nimprime:\s+//g; foreach my $line (split(/\n/, $output)) { #May include xdsh prefix $nimprime: $line =~ s/$nimprime:\s+//; # skip blank and comment lines next if ($line =~ /^\s*$/ || $line =~ /^\s*#/); push(@pkglist, $line); } return (0, \@pkglist, $bnd_file_name); } #---------------------------------------------------------------------------- =head3 restore_request Restores an xcatd request from a remote management server into the proper format by removing arrays that were added by XML and removing tags that were added to numeric hash keys. Arguments: Returns: ptr to hash undef Globals: Example: Comments: =cut #----------------------------------------------------------------------------- sub restore_request { my $class = shift; my $in_struct = shift; my $callback = shift; my $out_struct; if (ref($in_struct) eq "ARRAY") { # flatten the array it it has only one element # otherwise leave it alone if (scalar(@$in_struct) == 1) { return (xCAT::InstUtils->restore_request($in_struct->[0])); } else { return ($in_struct); } } if (ref($in_struct) eq "HASH") { foreach my $struct_key (keys %{$in_struct}) { my $stripped_key = $struct_key; $stripped_key =~ s/^xxXCATxx(\d)/$1/; # do not flatten the arg or node arrays if (($stripped_key =~ /^arg$/) || ($stripped_key =~ /^node$/)) { $out_struct->{$stripped_key} = $in_struct->{$struct_key}; } else { $out_struct->{$stripped_key} = xCAT::InstUtils->restore_request($in_struct->{$struct_key}); } } return $out_struct; } if ((ref($in_struct) eq "SCALAR") || (ref(\$in_struct) eq "SCALAR")) { return ($in_struct); } print "Unsupported data reference in restore_request().\n"; return undef; } #---------------------------------------------------------------------------- =head3 taghash Add a non-numeric tag to any hash keys that are numeric. Arguments: Returns: 0 - OK 1 - error Globals: Example: Comments: XML will choke on numeric values. This happens when including a hash in a request to a remote service node. =cut #----------------------------------------------------------------------- sub taghash { my ($class, $hash) = @_; if (ref($hash) eq "HASH") { foreach my $k (keys %{$hash}) { if ($k =~ /^(\d)./) { my $tagged_key = "xxXCATxx" . $k; $hash->{$tagged_key} = $hash->{$k}; delete($hash->{$k}); } } return 0; } else { return 1; } } #------------------------------------------------------------------------------- =head3 getOSnodes Split a noderange into arrays of AIX and Linux nodes. Arguments: \@noderange - reference to onde list array Returns: $rc - 1 - yes, all the nodes are AIX 0 - no, at least one node is not AIX \@aixnodes - ref to array of AIX nodes \@linuxnodes - ref to array of Linux nodes Comments: Based on "os" attr of node definition. If attr is not set, defaults to OS of current system. Example: my ($rc, $AIXnodes, $Linuxnodes) = xCAT::InstUtils->getOSnodes(\@noderange) =cut #------------------------------------------------------------------------------- sub getOSnodes { my ($class, $nodes) = @_; my @nodelist = @$nodes; my $rc = 1; # all AIX nodes my @aixnodes; my @linuxnodes; my $nodetab = xCAT::Table->new('nodetype'); my $os = $nodetab->getNodesAttribs(\@nodelist, ['node', 'os']); foreach my $n (@nodelist) { my $osname; if (defined($os->{$n}->[0]->{os})) { $osname = $os->{$n}->[0]->{os}; } else { $osname = $^O; } if (($osname ne "AIX") && ($osname ne "aix")) { push(@linuxnodes, $n); $rc = 0; } else { push(@aixnodes, $n); } } $nodetab->close; return ($rc, \@aixnodes, \@linuxnodes); } #------------------------------------------------------------------------------- =head3 get_server_nodes Determines the server node names as known by a lists of nodes. Arguments: A list of node names. Returns: A hash ref of arrays, the key is the service node pointing to an array of nodes that are serviced by that service node Example my %servernodes = &get_server_nodes($callback, \@$AIXnodes); Comments: - Code runs on MN or SNs =cut #------------------------------------------------------------------------------- sub get_server_nodes { my $class = shift; my $callback = shift; my $nodes = shift; my @nodelist; if ($nodes) { @nodelist = @$nodes; } # # get the server name for each node - as known by node # my $noderestab = xCAT::Table->new('noderes'); my $xcatmasters = $noderestab->getNodesAttribs(\@nodelist, ['node', 'xcatmaster']); $noderestab->close; my %servernodes; foreach my $node (@nodelist) { my $serv; if ($xcatmasters->{$node}->[0]->{xcatmaster}) { # get ip of node xcatmaster attribute my $xcatmaster = $xcatmasters->{$node}->[0]->{xcatmaster}; $serv = xCAT::NetworkUtils->getipaddr($xcatmaster); } else { # get ip facing node $serv = xCAT::Utils->my_ip_facing($node); } chomp $serv; if (xCAT::Utils->validate_ip($serv)) { push (@{$servernodes{$serv}}, $node); } } return \%servernodes; } #---------------------------------------------------------------------------- =head3 dolitesetup Update a spot with the statelite configuration Arguments: Returns: 0 - OK 1 - error Globals: Example: Comments: =cut #----------------------------------------------------------------------- sub dolitesetup { my $class = shift; my $imagename = shift; my $imagehash = shift; my $nodes = shift; my $callback = shift; my $subreq = shift; my @litefiles; # lists of entries in the litefile table my %imghash; if ($imagehash) { %imghash = %$imagehash; } my @nodelist; my @nl; if ($nodes) { @nl = @$nodes; foreach my $n (@nl) { push(@nodelist, xCAT::NodeRange::noderange($n)); } } # the node list is always "all" nodes. There is only one version of the # statelite, litefile and litetree files in an image and these files # must always contain all the info from the corresponding database # table. my $noderange = join(',',@nodelist); # get spot inst_root loc my $spotloc = xCAT::InstUtils->get_nim_attr_val($imghash{$imagename}{spot}, 'location', $callback, "", $subreq); my $instrootloc = $spotloc . "/lpp/bos/inst_root"; # get the statelite info - put each table into it's own file my $statelitetab = xCAT::Table->new('statelite', -create=>1); my $litefiletab = xCAT::Table->new('litefile'); my $litetreetab = xCAT::Table->new('litetree'); # these will wind up in the root dir on the node ("/") my $statelitetable = "$instrootloc/statelite.table"; my $litefiletable = "$instrootloc/litefile.table"; my $litetreetable = "$instrootloc/litetree.table"; # get rid of any old files if (-e $statelitetable) { my $rc = xCAT::Utils->runcmd("rm $statelitetable", -1); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Could not remove existing $statelitetable file."; xCAT::MsgUtils->message("E", $rsp, $callback); return 1; } } if (-e $litefiletable) { my $rc = xCAT::Utils->runcmd("rm $litefiletable", -1); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Could not remove existing $litefiletable file."; xCAT::MsgUtils->message("E", $rsp, $callback); return 1; } } if (-e $litetreetab) { my $rc = xCAT::Utils->runcmd("rm $litetreetab", -1); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Could not remove existing $litetreetab file."; xCAT::MsgUtils->message("E", $rsp, $callback); return 1; } } # # create files for each statelite table. add them to the SPOT. # use the "|" as a separator, remove all blanks from the entries. # put them in $instrootloc location. they will be available as soon # as the root dir is mounted during the boot process. my $foundstatelite=0; unless (open(STATELITE, ">$statelitetable")) { my $rsp; push @{$rsp->{data}}, "Could not open $statelitetable.\n"; xCAT::MsgUtils->message("E", $rsp, $callback); return 1; } my $stateHash = $statelitetab->getNodesAttribs(\@nodelist, ['statemnt']); foreach my $node (@nodelist) { # process statelite entry # add line to file for each node # note: if statement is xcatmn:/nodedata # /nodedata is mounted to /.statelite/persistent # then - on node - a nodename subdir is created my $statemnt=""; if (exists($stateHash->{$node})) { $statemnt = $stateHash->{$node}->[0]->{statemnt}; my ($server, $dir) = split(/:/, $statemnt); #if server is blank, then its the directory unless($dir) { $dir = $server; $server = ''; } $dir = xCAT::SvrUtils->subVars($dir, $node, 'dir', $callback); $dir =~ s/\/\//\//g; if($server) { $server = xCAT::SvrUtils->subVars($server, $node, 'server', $callback); $server =~ s/\///g; # remove "/" - bug in subVars?? my $serverIP = xCAT::NetworkUtils->getipaddr($server); $statemnt = $serverIP . "|" . $dir; } else { $statemnt = $dir; } } my $entry = qq~$node|$statemnt~; $entry =~ s/\s*//g; #remove blanks if ($statemnt) { print STATELITE $entry . "\n"; } } close(STATELITE); unless (open(LITEFILE, ">$litefiletable")) { my $rsp; push @{$rsp->{data}}, "Could not open $litefiletable.\n"; xCAT::MsgUtils->message("E", $rsp, $callback); return 1; } my @filelist = xCAT::Utils->runcmd("litefile $noderange", -1); foreach my $l (@filelist) { $l =~ s/://g; # remove ":"'s $l =~ s/\s+/|/g; # change separator to "|" print LITEFILE $l . "\n"; push (@litefiles, $l); $foundstatelite++; } close(LITEFILE); unless (open(LITETREE, ">$litetreetable")) { my $rsp; push @{$rsp->{data}}, "Could not open $litetreetable.\n"; xCAT::MsgUtils->message("E", $rsp, $callback); return 1; } my @treelist = xCAT::Utils->runcmd("litetree $noderange", -1); foreach my $l (@treelist) { my ($p, $serv, $dir) = split (/:/, $l); $p =~ s/\s*//g; $serv =~ s/\s*//g; $dir =~ s/\s*//g; my $serverIP = xCAT::NetworkUtils->getipaddr($serv); my $entry = "$p|$serverIP|$dir"; print LITETREE $entry . "\n"; $foundstatelite++; } close(LITETREE); # if there is no statelite info then just return if (!$foundstatelite) { return 1; } # # ok - do more statelite setup # # create some local directories in the SPOT # create .default, .statelite, my $mcmd = qq~/bin/mkdir -m 644 -p $instrootloc/.default; /bin/mkdir -m 644 -p $instrootloc/.statelite ~; my $output = xCAT::Utils->runcmd("$mcmd", -1); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Could not create directories."; xCAT::MsgUtils->message("E", $rsp, $callback); return 1; } # populate the .defaults dir with files and dirs from the image - if any my $default="$instrootloc/.default"; # read the litefile and try to copy into $default # everything in the litefile command output should be processed my @copiedfiles; foreach my $line (@litefiles) { # $file could be full path file name or dir name # ex. /foo/bar/ or /etc/lppcfg my ($node, $option, $file) = split (/\|/, $line); # ex. /foo or /etc my $filedir = dirname($file); # ex. .../inst_root/foo/bar/ or .../inst_root/etc/lppcfg my $instrootfile = $instrootloc . $file; my $cpcmd; if (-e $instrootfile) { if (!grep (/^$instrootfile$/, @copiedfiles)) { # don't copy same file twice push (@copiedfiles, $instrootfile); if (-d $instrootfile) { # it's a dir so copy everything in it # ex. mkdir -p ../inst_root/.default/foo/bar # ex. cp -r .../inst_root/foo/bar/ ../inst_root/.default/foo/bar $cpcmd = qq~mkdir -p $default$file; cp -r $instrootfile* $default$file~; } else { # copy file # ex. mkdir -p ../inst_root/.default/etc # ex. cp .../inst_root/etc/lppcfg ../inst_root/.default/etc $cpcmd = qq~mkdir -p $default$filedir; cp $instrootfile $default$filedir~; } my $output = xCAT::Utils->runcmd("$cpcmd", -1); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Could not copy $instrootfile to $default subdirectory."; if ($::VERBOSE) { push @{$rsp->{data}}, "$output\n"; } xCAT::MsgUtils->message("E", $rsp, $callback); } } } else { # could not find file or dir in ../inst_root (spot dir) # so create empty file or dir my $mkcmd; # check if it's a dir if(grep /\/$/, $file) { # create dir in .default $mkcmd = qq~mkdir -p $default$file~; } else { # create dir and touch file in .default my $dir = dirname($instrootfile); $mkcmd = qq~mkdir -p $dir; touch $default$file~; } my $output = xCAT::Utils->runcmd("$mkcmd", -1); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Could not create $default$file.\n"; if ($::VERBOSE) { push @{$rsp->{data}}, "$output\n"; } xCAT::MsgUtils->message("E", $rsp, $callback); } } } # add aixlitesetup to ..inst_root/aixlitesetup # this will wind up in the root dir on the node ("/") my $install_dir = xCAT::Utils->getInstallDir(); my $cpcmd = "/bin/cp $install_dir/postscripts/aixlitesetup $instrootloc/aixlitesetup; chmod +x $instrootloc/aixlitesetup"; my $out = xCAT::Utils->runcmd("$cpcmd", -1); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Could not copy aixlitesetup."; xCAT::MsgUtils->message("E", $rsp, $callback); return 1; } # if this is an update then we need to copy the new files to # the shared_root location # ??? - maybe we should try this all the time???? if (1) { # if we have a shared_root resource if ($imghash{$imagename}{shared_root} ) { my $nimprime = xCAT::InstUtils->getnimprime(); chomp $nimprime; # get the location of the shared_root directory my $SRloc = xCAT::InstUtils->get_nim_attr_val($imghash{$imagename}{shared_root}, 'location', $callback, $nimprime, $subreq); # copy the statelite table file to the shared root location # this will not effect any running nodes that are using # this shared_root resource. However the new table will # include any info need for existing nodes - for when they # need to be rebooted if (-d $SRloc) { my $ccmd = "/bin/cp $statelitetable $litefiletable $litetreetable $instrootloc/aixlitesetup $SRloc"; my $out = xCAT::Utils->runcmd("$ccmd", -1); if ($::RUNCMD_RC != 0) { my $rsp; push @{$rsp->{data}}, "Could not copy statelite files to $SRloc."; xCAT::MsgUtils->message("E", $rsp, $callback); return 1; } } } } return 0; } 1;