diff --git a/xCAT-server-2.0/sbin/mkrmcresources b/xCAT-server-2.0/sbin/mkrmcresources new file mode 100755 index 000000000..f54191fd7 --- /dev/null +++ b/xCAT-server-2.0/sbin/mkrmcresources @@ -0,0 +1,833 @@ +#!/usr/bin/env perl +# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html +#------------------------------------------------------------------------------- +=head1 mkrmcresources +=head2 mkrmcresources is used to predefine RMC conditions, responses, associations, + sensors (and can be extended to support any RSCT resource + class). + To use the command, create perl modules in a directory. Each resource + should have its own perl module (so that it is easy to update a + resource without interfering with other resources), + and should be named .pm. + After the resource perl modules are installed, they will be created + by the next execution of the this command. + This command should be called by the post install scripts + of packaging files, script run after install or from the command line. +=cut +#------------------------------------------------------------------------------- +use Getopt::Long; + +$Getopt::Long::ignorecase = 0; #Checks case in GetOptions +Getopt::Long::Configure("bundling"); #allows short command line options to be grouped (e.g. -av) + +#-------------------------------------------------------------------------------- +=head3 queryResources + Queries all resources of a given class or classes. Places + results into a global hash for each resource class. + Arguments: a list of RSCT resource classes + Globals: %::EXISTS::{$resource} +=cut +#-------------------------------------------------------------------------------- +sub queryResources +{ + my @resources = @_; + + my $where = ""; + foreach my $res (@resources) + { + if ($res eq "IBM.Association") + { + #special case: run lscondresp because Associations do not have names + #cant run lsrsrc because Assoctation also does not store names of resources (just handles) + my @condresp = runcmd("LANG=C /usr/bin/lscondresp"); + my $class = $res; + $class =~ s/^IBM\.//; + splice @condresp, 0, + 2; #delete first two lines -- they are just comments + foreach my $line (@condresp) + { + my ($condition, $response, $node, $state) = split ' ', $line; + $condition = &removeQuotes($condition); + $response = &removeQuotes($response); + my $key = "${condition}:_:${response}"; + my $ActiveFlag = 0; #assume offline + if ($state =~ m/Active/) + { + $ActiveFlag = 1; + } + #currently does not checked for locked + # This \%{typeglob} syntax auto-vivifies + # the hash table for us, and gives us a reference. + my $ref = \%{$::EXISTS::{$class}}; + $ref->{$key} = {ActiveFlag => $ActiveFlag,}; + } + } + else + { + $where .= " -s ${res}::::'*p0x0020'"; + } + } + + my $output = runrmccmd("lsrsrc-api", "-i -m -n -D ':|:'", $where); + foreach my $line (@$output) + { + my @array = split(/:\|:/, $line); + my $class = shift @array; #the -m flag puts the class name in front + $class =~ s/^IBM\.//; + my %attrs = @array; + # This \%{typeglob} syntax auto-vivifies + # the hash table for us, and gives us a reference. + my $ref = \%{$::EXISTS::{$class}}; + my $key = $attrs{'Name'}; + $ref->{$key} = {%attrs}; #sets the EXISTS array with the info + } +} + +#-------------------------------------------------------------------------------- +=head3 traverseDirectories + Calls readFiles on each sub-directory of the given path. + Creates a global array with all target resource classes. + Arguments: A directory + Globals: @::DIRECTORIES (will hold all resource classes) +=cut +#-------------------------------------------------------------------------------- +sub traverseDirectories +{ + my ($dir) = @_; + my ($dir_fh, $file); + + opendir($dir_fh, $dir) + or die "Can not open directory $dir\n"; + while ($file = readdir($dir_fh)) + { + if ($file ne '.' and $file ne '..') + { + my $subdir = "$dir/$file"; + if (-d $subdir) + { #only look at directories + &readFiles($subdir); + push @::DIRECTORIES, $file; #file=just the filename + } + } + } + closedir($dir_fh) + or die "Can not close directory $dir\n"; +} + +#-------------------------------------------------------------------------------- +=head3 readFiles + Calls require on all .pm files in a given directory + Arguments: A directory +=cut +#-------------------------------------------------------------------------------- +sub readFiles +{ + my ($dir) = @_; + my ($dir_fh, $file); + opendir($dir_fh, $dir) + or die "Can not open directory $dir\n"; + while ($file = readdir($dir_fh)) + { + if ($file ne '.' and $file ne '..') + { + $file = "$dir/$file"; + if ($file =~ m/\.pm$/) + { + #its a perl module + require $file; + } + } + } + closedir($dir_fh) + or die "Can not close directory $dir\n"; +} + +#-------------------------------------------------------------------------------- +=head3 compareResources + Compares existing resources to those requiring definition. + Globals: uses %::EXISTS and %::RES and makes %::CHANGE and %::CREATE +=cut +#-------------------------------------------------------------------------------- +sub compareResources +{ + foreach my $class (@::DIRECTORIES) + { #this has all subdirectory names + $class =~ s/^IBM\.//; #the IBM prefix is not used in the hash name + local *exi = $::EXISTS::{$class}; #defined on system + local *res = $::RES::{$class}; #defined in file + foreach my $resource (keys %res) + { + if (defined $exi{$resource}) + { #exists on the system + if (defined $res{$resource}{'Locked'} + && $res{$resource}{'Locked'} == 1) + { + #only change the resource if it is supposed to be locked + foreach my $attr (keys %{$res{$resource}}) + { + if ($exi{$resource}{$attr} ne $res{$resource}{$attr}) + { + if (!($class eq "Association" && $attr eq "Locked")) + { # association locked attrs are not stored + # something has changed + if ($::VERBOSE) + { + print "Differs: Class=$class\tExists=$exi{$resource}{$attr}\tDefined=$res{$resource}{$attr}\n"; + } + $::CHANGE::{$class}{$resource} = $res{$resource}; + last; + } + } + } + } + } + else + { #resource is not defined on the system + $::CREATE::{$class}{$resource} = $res{$resource}; + } + } + } +} + +#-------------------------------------------------------------------------------- +=head3 removeQuotes + removes starting and ending quotes that are in the output of lsrsrc + Arguments: string + Returns: string with no leading or trailing quotes +=cut +#-------------------------------------------------------------------------------- +sub removeQuotes +{ + my ($string) = @_; + $string =~ s/^\"|^\'//; + $string =~ s/\"$|\'$//; + return $string; +} + +#-------------------------------------------------------------------------------- +=head3 createResources + Calls mkrsrc-api on all resources in the %::CREATE hash + Globals: %::CREATE +=cut +#-------------------------------------------------------------------------------- +sub createResources +{ + my $string; + my $counter = 0; + my @assoc_cmds; + my $sensorflg = 0; + foreach my $class (@::DIRECTORIES) + { #all the class names + local *cre = $::CREATE::{$class}; + if ($class eq "Sensor") + { + $sensorflg = 1; + } + else + { + $sensorflg = 0; + } + foreach my $resource (keys %cre) + { + if ($class eq "Association") + { #special case + my ($cond, $resp) = split ":_:", $resource; + if ($cre{$resource}{'ActiveFlag'} == 1) + { + push @assoc_cmds, "/usr/bin/startcondresp $cond $resp"; + if ($cre{$resource}{'Locked'} == 1) + { + push @assoc_cmds, "/usr/bin/startcondresp -L $cond $resp"; + } + } + else + { #not active + push @assoc_cmds, "/usr/bin/mkcondresp $cond $resp"; + #no need to lock stopped associations + } + } + else + { + $string .= " IBM.${class}::"; + foreach my $attr (keys %{$cre{$resource}}) + { + my $value = $cre{$resource}{$attr}; + $string .= "${attr}::" . quote($value) . "::"; + } + if (($sensorflg == 1) && ($::INSTALL)) + { + # make the Sensor with no userid check + $string .= "::Options::1"; + } + # + # Only build up to 10 resources at a pass + # to avoid command line limit + # + $counter = $counter + 1; + if ($counter > 10) + { + if ($string =~ m/\w+/) + { + #my $cmd = "/usr/bin/mkrsrc-api $string"; + #print "running $cmd\n"; + #system($cmd); + runrmccmd("mkrsrc-api", "", $string); + $string = ""; + $counter = 0; + } + } + } + } + } + if ($string =~ m/\w+/) # for any remaining resources + { + #my $cmd = "/usr/bin/mkrsrc-api $string"; + #print "running $cmd\n"; + #system($cmd); + runrmccmd("mkrsrc-api", "", $string); + } + foreach my $cmd (@assoc_cmds) + { + #need to make associations after conds and resps have been made + runcmd("$cmd"); + } +} + +#-------------------------------------------------------------------------------- +=head3 changeResources + Calls chrsrc-api on all resources in the %::CHANGE hash + Globals: %::CHANGE +=cut +#-------------------------------------------------------------------------------- +sub changeResources +{ + my $string; + my $ustring; #unlock + my @unlock; #unlock each class + my $where; #unlock each class + foreach my $class (@::DIRECTORIES) + { #all the class names + local *cha = $::CHANGE::{$class}; + foreach my $resource (keys %cha) + { + if ($class eq "Association") + { #special case + #code here is identical to createResource + my ($cond, $resp) = split ":_:", $resource; + if ($cre{$resource}{'ActiveFlag'} == 1) + { + runcmd("/usr/bin/startcondresp $cond $resp"); + if ($cre{$resource}{'Locked'} == 1) + { + runcmd( "/usr/bin/startcondresp -L $cond $resp"); + } + } + else + { #not active + runcmd("/usr/bin/mkcondresp $cond $resp"); + #no need to lock stopped associations + } + } + else # not class association + { + $where = qq/"Name IN ('XXX')"/; + $string .= " -s IBM.${class}::${where}::"; + push @unlock, $cha{$resource}{'Name'}; + delete $cha{$resource}{'Name'}; + foreach my $attr (keys %{$cha{$resource}}) + { + my $value = $cha{$resource}{$attr}; + $string .= "${attr}::" . quote($value) . "::"; + } + } + if (@unlock) + { + $where = qq/"Name IN ('XXX')"/; + $ustring .= " -s IBM.${class}::${where}::Locked::'0'"; + } + } # foreach resource + } # foreach key + # + # although @unlock contains the resource and not the node name + # this is a hack to use runrmccmd and the node_ref must + # be provided even though we are not really dealing with nodes + # here + + if ($ustring =~ m/\w+/) { + runrmccmd("chrsrc-api", "", $ustring, undef, \@unlock); + } + if ($string =~ m/\w+/) { + runrmccmd("chrsrc-api", "", $string, undef, \@unlock); + } +} + + +#-------------------------------------------------------------------------------- +=head3 writeAllFiles + creates all files for the given resources classes + Arguments: a array ref of class names, basedir +=cut +#-------------------------------------------------------------------------------- +sub writeAllFiles +{ + my @classes = @{shift()}; + my $basedir = shift; + print "classes=@classes, basedir=$basedir"; + foreach my $class (@classes) + { + my $output = runrmccmd("lsrsrc-api", "-i", "-s ${class}::::Name"); + foreach my $line (@$output) + { + &writeFile("${class}::$line", $basedir); + } + } +} + +#-------------------------------------------------------------------------------- +=head3 writeFile + creates a file with the resource info in + $basedir/ + Arguments: class::resource_name, basedir +=cut +#-------------------------------------------------------------------------------- +sub writeFile +{ + my $input = shift; + my $basedir= shift; + print "input=$input, basedir=$basedir\n"; + + my ($class, $resourcefilename) = split "::", $input; + if (!$resourcefilename) { + print 'mkrmcresource --mkfile requires as input.\n'; + exit 1; + } + my $resource; + push(@$resource, $resourcefilename); + + if (!-e "$basedir/$class") { + `mkdir -p "$basedir/$class"`; + } + my $file = "$basedir/$class/$resourcefilename.pm"; + my $where = qq/"Name IN ('XXX')"/; + my $string = " -s ${class}::${where}::*p0x0002"; + my $output = runrmccmd("lsrsrc-api", "-i -n -D ':|:'", + $string, undef, $resource); + $string = " -s ${class}::${where}::*p0x0008"; + my $optional = runrmccmd("lsrsrc-api", "-i -n -D ':|:'", + $string, undef, $resource); + + #my @output = runcmd("/usr/bin/lsrsrc -s $where $class"); + #uses lsrsrc instead of lsrsrc-api because format is almost right (just needs a few mods) + + my $fh; + open($fh, ">$file") + or die "Can not open this file for writing $file.\n"; + print $fh "#!/usr/bin/perl\n\n"; + $class =~ s/IBM\.//; + + print $fh '$RES::' . $class . "{" . "'" + . $resourcefilename . "'" + . "} = {\n"; + foreach my $line (@$output) + { + my %attrs = split /:\|:/, + $line; #can't go straight into a hash because -p creates extra fields + foreach my $attr (keys %attrs) + { + if ( $attr !~ m/ActivePeerDomain/ + && $attr !~ m/NodeNameList/ + && $attr !~ m/NodeIDs/) + { + my $value = $attrs{$attr}; + if ($value =~ m/\w/ || $value =~ m/\d/) + { + # print "value = |$value|\n"; + #$value = &removeQuotes($value); #quotes are not needed becaues of q() + #print "value = |$value|\n"; + my $line = "\t$attr => q($value),"; + print $fh "$line\n"; + } + } + } + } + foreach my $line (@$optional) + { + my %attrs = split /:\|:/, + $line; #can't go straight into a hash because -p creates extra fields + foreach my $attr (keys %attrs) + { + if ( $attr !~ m/ActivePeerDomain/ + && $attr !~ m/NodeNameList/ + && $attr !~ m/NodeIDs/) + { + my $value = $attrs{$attr}; + if ($value =~ m/\w/ || $value =~ m/\d/) + { + # print "value = |$value|\n"; + #$value = &removeQuotes($value); #quotes are not needed becaues of q() + #print "value = |$value|\n"; + my $line = "\t$attr => q($value),"; + print $fh "$line\n"; + } + } + } + } + + print $fh "};"; + print $fh "\n"; + print $fh "1;"; + print $fh "\n"; + close($fh) + or die "cabbit close file $file\n"; +} + +sub isHMC +{ + my $hmcfile = "/opt/hsc/data/hmcType.properties"; + if (-e $hmcfile) { return 1; } + else { return 0; } +} + +#-------------------------------------------------------------------------------- +=head3 runcmd + Run the given cmd and return the output in an array (already chopped). Alternatively, + if this function is used in a scalar context, the output is joined into a single string + with the newlines separating the lines. + Arguments: + command, exitcode and reference to output + Returns: + see below + Error: + Normally, if there is an error running the cmd, it will display the error msg + and exit with the cmds exit code, unless exitcode is given one of the + following values: + 0: display error msg, DO NOT exit on error, but set + $::RUNCMD_RC to the exit code. + -1: DO NOT display error msg and DO NOT exit on error, but set + $::RUNCMD_RC to the exit code. + -2: DO the default behavior (display error msg and exit with cmds + exit code. + number > 0: Display error msg and exit with the given code + Example: + my $outref = runcmd($cmd, -2, 1); + Comments: + If refoutput is true, then the output will be returned as a reference to + an array for efficiency. +=cut +#-------------------------------------------------------------------------------- +sub runcmd +{ + my ($cmd, $exitcode, $refoutput) = @_; + $::RUNCMD_RC = 0; + if (!$::NO_STDERR_REDIRECT) { + if (!($cmd =~ /2>&1$/)) { $cmd .= ' 2>&1'; } + } + my $outref = []; + @$outref = `$cmd`; + if ($?) + { + $::RUNCMD_RC = $? >> 8; + my $displayerror = 1; + my $rc; + if (defined($exitcode) && length($exitcode) && $exitcode != -2) + { + if ($exitcode > 0) + { + $rc = $exitcode; + } # if not zero, exit with specified code + elsif ($exitcode <= 0) + { + $rc = ''; # if zero or negative, do not exit + if ($exitcode < 0) { $displayerror = 0; } + } + } + else + { + $rc = $::RUNCMD_RC; + } # if exitcode not specified, use cmd exit code + if ($displayerror) + { + my $errmsg = ''; + if (($^O =~ /^linux/i) && $::RUNCMD_RC == 139) + { + $errmsg = "Segmentation fault $errmsg"; + } + else + { + # The error msgs from the -api cmds are pretty messy. Clean them up a little. + filterRmcApiOutput($cmd, $outref); + $errmsg = join('', @$outref); + chomp $errmsg; + } + print "Exit code $::RUNCMD_RC from command: $cmd\n\nError message from cmd: $errmsg\n" + } + } + if ($refoutput) + { + chomp(@$outref); + return $outref; + } + elsif (wantarray) + { + chomp(@$outref); + return @$outref; + } + else + { + my $line = join('', @$outref); + chomp $line; + return $line; + } +} + +#-------------------------------------------------------------------------------- +=head3 runrmccmd + Runs an RMC commmand + Arguments: + $rmccmd, $resclass, $options, $select, $exitcode, $nodelist_ref + Returns: + the output from runcmd($cmd, -2, 1) + as a ref to the output array. + Error: + none + Example: + my $outref =runrmccmd('lsrsrc-api', "-i -D ':|:'", $where); + Comments: + When $nodelist_ref is not null, break it up into smaller slices + and run RMC commands seperately for each slice. + Otherwise just run RMC commands with the arguments passed in. +=cut +#-------------------------------------------------------------------------------- +sub runrmccmd +{ + my ($rmccmd, $options, $select, $exitcode, $nodelist_ref) = @_; + + my @nodelist; + my $return_ref = []; + + if (!defined($exitcode)) + { + $exitcode = -2; + } + + if(! grep /usr\/bin/, $rmccmd) + { + # add absolute path + $rmccmd = "/usr/bin/$rmccmd"; + } + + if ($nodelist_ref) + { + # check whether to break up nodelist for better scalability. + @nodelist = @$nodelist_ref; + my $divide = 500; # max number of nodes for each division + my @sublist; + my @newarray; + my ($start_index, $end_index, $nodestring); + + my $count = 0; + my $times = int(scalar(@nodelist) / $divide); + while ($count <= $times) + { + $start_index = $count * $divide; + $end_index = + ((scalar(@nodelist) - 1) < (($count + 1) * $divide - 1)) + ? (scalar(@nodelist) - 1) + : (($count + 1) * $divide - 1); + @sublist = @nodelist[$start_index .. $end_index]; + @newarray = (); + foreach my $node (@sublist) + { + my @vals = split ',|\s', $node; + push @newarray, @vals; + } + $nodestring = join("','", @newarray); + + # replace the pattern in select string with the broken up node string + my $select_new = $select; + $select_new =~ s/XXX/$nodestring/; + my $cmd = "$rmccmd $options $select_new"; + my $outref = runcmd($cmd, $exitcode, 1); + push @$return_ref, @$outref; + $count++; + } + } + else + { + my $cmd = "$rmccmd $options $select"; + $return_ref = runcmd($cmd, $exitcode, 1); + } + + # returns a reference to the output array + return $return_ref; +} +#-------------------------------------------------------------------------------- +=head3 quote + Quote a string, taking into account embedded quotes. This function is most + useful when passing string through the shell to another cmd. It handles one + level of embedded double quotes, single quotes, and dollar signs. + Arguments: + string to quote + Returns: + quoted string + Globals: + none + Error: + none + Example: + Comments: + none +=cut +#-------------------------------------------------------------------------------- +sub quote +{ + my $str = shift; + + # if the value has imbedded double quotes, use single quotes. If it also has + # single quotes, escape the double quotes. + if (!($str =~ /\"/)) # no embedded double quotes + { + $str =~ s/\$/\\\$/sg; # escape the dollar signs + $str =~ s/\`/\\\`/sg; + $str = qq("$str"); + } + elsif (!($str =~ /\'/)) + { + $str = qq('$str'); + } # no embedded single quotes + else # has both embedded double and single quotes + { + # Escape the double quotes. (Escaping single quotes does not seem to work + # in the shells.) + $str =~ s/\"/\\\"/sg; #" this comment helps formating + $str =~ s/\$/\\\$/sg; # escape the dollar signs + $str =~ s/\`/\\\`/sg; + $str = qq("$str"); + } +} + + +#-------------------------------------------------------------------------------- +=head3 filterRmcApiOutput + filter RMC Api Output + Arguments: + RMC command + Output reference + Returns: + none + Globals: + none + Error: + none + Example: + filterRmcApiOutput($cmd, $outref); + Comments: + The error msgs from the RPM -api cmds are pretty messy. + This routine cleans them up a little bit. +=cut +#-------------------------------------------------------------------------------- +sub filterRmcApiOutput +{ + my ($cmd, $outref) = @_; + if ($::VERBOSE || !($cmd =~ m|^/usr/bin/\S+-api |)) { + return; + } # give as much info as possible, if verbose + + # Figure out the output delimiter + my ($d) = $cmd =~ / -D\s+(\S+)/; + if (length($d)) { + $d =~ s/^(\'|\")(.*)(\"|\')$/$2/; # remove any surrounding quotes + # escape any chars perl pattern matching would intepret as special chars + $d =~ s/([\|\^\*\+\?\.])/\\$1/g; + } + else + { + $d = '::'; + } # this is the default output delimiter for the -api cmds + $$outref[0] =~ s/^ERROR${d}.*${d}.*${d}.*${d}.*${d}//; +} + +#-------------------------------------------------------------------------------- +=head3 usage + Prints the command usage statement +=cut +#-------------------------------------------------------------------------------- +sub usage +{ + print "Usage: + mkrmcresources [--install|--mkfile classname::rsrcname|--mkall] \ + [-V|--verbose] directory\n\ + mkrmcresources -h|--help\n\ + directory a full path to a base directory for resurce files \ + to be created or to be read from. \ + -V|--verbose Verbose mode.\ + -h|--help shows usage information.\ + --install\ + The userid in the Sensor resource will not be verified.\n"; + +} + +####################################################################### +# main Main MAIN +####################################################################### + +# get arguments +if ( + !GetOptions( + 'h|help' => \$::HELP, + 'V|verbose' => \$::VERBOSE, + 'install' => \$::INSTALL, + 'mkfile=s' => \$::MKFILE, + 'mkall' => \$::MKALL, + ) + ) +{ + &usage; + exit 1; +} + +if ($::HELP) { &usage; exit; } +if (isHMC() && ($ENV{'DC_ENVIRONMENT'} ne 1)) +{ + print "mkresources is not supported on HMC.\n"; +} + +# any function requested +if (@ARGV < 1) { + &usage; + exit 1; +} + +my $basedir = $ARGV[0]; + +if ($::MKFILE) { + &writeFile($::MKFILE, $basedir); + exit; +} +if ($::MKALL) { + @rsrc_classes=('IBM.Condition', 'IBM.EventResponse', 'IBM.Sensor'); + &writeAllFiles(\@rsrc_classes, $basedir); + exit; +} + + + +&traverseDirectories($basedir); + +#TODO: wait for RSCT to come online + +&queryResources(@::DIRECTORIES); + +#compares whats defined in the files to the existing resources +&compareResources(); + +&createResources(); + +&changeResources(); + +END +{ + +} +exit 0; +