diff --git a/xCAT-client/bin/mkdummyimage b/xCAT-client/bin/mkdummyimage new file mode 100644 index 000000000..facf511c9 --- /dev/null +++ b/xCAT-client/bin/mkdummyimage @@ -0,0 +1,286 @@ +#!/usr/bin/perl +############################################################################### +# IBM(c) 2015 EPL license http://www.eclipse.org/legal/epl-v10.html +############################################################################### +# COMPONENT: mkdummyimage +# +# This script creates a dummy image. +############################################################################### + +use strict; +use warnings; + +use File::Basename; +use File::Path; +use File::Spec; +use File::Temp; +use Getopt::Long; +use MIME::Base64; +use Sys::Hostname; +use Socket; + +my $version = "1.0"; + +my $defaultName = 'dummy.img'; # Default image file name +my $dest = ''; # Target destination (filespec) +my $displayHelp = 0; # Display help information +my $eckdImage = 0; # ECKD image is wanted +my $fbaImage = 0; # FBA image is wanted +my $remoteHost = ''; # Remote host transfer information +my $remoteUser = ''; # Remote user transfer information +my $verbose = 0; # Verbose flag - 0: quiet, 1: verbose +my $versionOpt = 0; # Show version information flag + +# set the usage message +my $usage_string = "Usage:\n + $0 [ --eckd | --fba ] [ -V | --verbose ] + [ -d | --destination ] [-R | --remotehost ]\n + or\n + $0 -v | --version\n + or\n + $0 -h | --help\n + The following options are supported:\n + -d | --destination + File specification of the image file to be created. + Either the filename only or a fully qualified name. + If the destination is within the xCAT MN then + it must be within the directory specified by + the installdir property in the xCAT site table. + The default file name is $defaultName.\n + --eckd Create an empty ECKD disk image. This is the + default image type if neither --eckd or --fba + is specifed.\n + --fba Create an empty FBA disk image\n + -h | --help Display help information\n + -R | --remotehost + Indicates that the destination specified on the + command invocation is the file specification on a + host other than the management node. + The 'host' operand is the IP address or DNS host + name of the target server. A user may be specified + in a similar way as it is specified with the SCP + command using the format 'user\@host' where 'user' + is the name of the user on the remote system and + 'host' operand is as previously stated. The image + is created in the directory specified by the + tmpdir property in the site table and then moved + to the remote host using SCP. After moving the + image off the xCAT MN, the image is removed + from the xCAT MN. Prior to specifying the command, + the keystore on the remote host must be set up with + the public key of the xCAT MN.\n + -v | --version Display the version of this script.\n + -V | --verbose Display verbose processing messages.\n"; + + +#------------------------------------------------------- + +=head3 createImage + + Description : Create the dummy image file. + Arguments : None + Returns : 0 - No error + non-zero - Error detected. + Example : $rc = createImage(); + +=cut + +#------------------------------------------------------- +sub createImage{ + my $rc = 0; + my $buildDir = ''; + my $header; + + # Create a temporary directory in which to create the image. + my $tmpDir = `/opt/xcat/sbin/tabdump site| grep '^"tmpdir",' | sed s/'^"tmpdir","'//g| sed s/'".*'//g`; + chomp $tmpDir; + if ( $tmpDir eq '' ) { + print "Error: The 'tmpdir' value is missing from the site table.\n"; + $rc = 11; + goto FINISH_createImage; + } + $buildDir = mkdtemp( "$tmpDir/image.$$.XXXXXX" ); + + # Create the image + my $imageFile = $defaultName; + open( my $fh, '>', "$buildDir/$imageFile" ) or die "Could not open file '$buildDir/$imageFile'"; + if ( $verbose ) { + print "Creating the image in $buildDir as $imageFile.\n"; + } + + if ( $eckdImage ) { + $header = "xCAT CKD Disk Image:"; + $header = "$header 0 CYL"; + } + elsif ( $fbaImage ) { + $header = "xCAT FBA Disk Image:"; + $header = "$header 0 BLK"; + } + $header = "$header HLen: 0055"; # Header size increased by x from 0055 + $header = "$header GZIP: 0"; + printf $fh "%-512s", "$header"; + close( $fh ); + + # Move the image to the target location. + if ( $remoteHost ne '' ) { + # Do a remote transfer + my $remoteTarget = $remoteHost . ':' . $dest; + if ( $remoteUser ne '' ) { + $remoteTarget = "$remoteUser\@$remoteTarget"; + } + my $scpVerbose = ''; + if ( $verbose ) { + print "Moving the image $imageFile to the remote system $remoteHost as $dest.\n"; + $scpVerbose = '-v' + } + $rc = system( "/usr/bin/scp $scpVerbose -B $buildDir/$imageFile $remoteTarget" ); + if ( $rc ) { + $rc = $rc >> 8; + print "Error: Unable to copy the image $buildDir/$imageFile to the remote host: $remoteHost, rc: $rc\n"; + $rc = 30; + goto FINISH_createImage; + } + } else { + # Move the file to a local directory + $rc = system( "cp $buildDir/$imageFile $dest" ); + if ( $rc ) { + $rc = $rc >> 8; + print "Error: Unable to copy the image $buildDir/$imageFile to the destination: $dest, rc: $rc\n"; + $rc = 40; + goto FINISH_createImage; + } + } + + if ( $remoteHost ne '' ) { + print "Image created on $remoteHost: $dest\n"; + } else { + print "Image created: $dest\n"; + } + +FINISH_createImage: + if ( -d $buildDir ) { + rmtree $buildDir; + } + return $rc; +} + + +#------------------------------------------------------- + +=head3 showHelp + + Description : Show the help inforamtion. + Arguments : None. + Returns : None. + Example : showHelp(); + +=cut + +#------------------------------------------------------- +sub showHelp{ + print "$0 prepares a special image file that contains no disk\ncontents.\n\n"; + print $usage_string; + return; +} + + +#***************************************************************************** +# Main routine +#***************************************************************************** +my $rc = 0; +my $out; + +# Parse the arguments +$Getopt::Long::ignorecase = 0; +Getopt::Long::Configure( "bundling" ); +if (!GetOptions( + 'eckd' => \$eckdImage, + 'd|destination=s' => \$dest, + 'fba' => \$fbaImage, + 'h|help' => \$displayHelp, + 'R|remotehost=s' => \$remoteHost, + 'v|version' => \$versionOpt, + 'V|verbose' => \$verbose )) { + print $usage_string; + goto FINISH_main; +} + +if ( $versionOpt ) { + print "Version: $version\n"; +} + +if ( $displayHelp ) { + showHelp(); +} + +if ( $displayHelp or $versionOpt ) { + goto FINISH_main; +} + +if ( !$eckdImage and !$fbaImage ) { + $eckdImage = 1; +} + +if ( $remoteHost ne '' ) { + $remoteHost =~ s/^\s+|\s+$//g; # trim blanks from both ends of the value + my $spaceCnt = scalar( () = $remoteHost =~ /\s+/g ); + if ( $spaceCnt != 0 ) { + print "Error: Remote host value is not a single word.\n"; + $rc = 1; + goto FINISH_main; + } + if ( $remoteHost =~ /@/ ) { + my @parts = split( /@/, $remoteHost ); + if ( substr( $remoteHost, -1) eq '@' or + @parts > 2 or + !defined $parts[0] or $parts[0] eq '' or + !defined $parts[1] or $parts[1] eq '' ) { + print "Error: Remote host value is not valid.\n" . + " It should be in the form of 'hostname' or 'user\@hostname'.\n"; + $rc = 2; + goto FINISH_main; + } + $remoteUser = $parts[0]; + $remoteHost = $parts[1]; + } else { + $remoteUser = 'root'; + } +} + +# Determine the destination and make certain it is valid. +my $installDir = `/opt/xcat/sbin/tabdump site| grep '^"installdir",' | sed s/'^"installdir","'//g| sed s/'".*'//g`; +chomp $installDir; +if ( $installDir eq '' ) { + print "Error: The 'installDir' value is missing from the site table.\n"; + $rc = 10; + goto FINISH_main; +} + +if ( $dest eq '' ){ + # Set default destination for either a local or remote location. + if ( $remoteHost eq '' ) { + $dest = "$installDir/$defaultName"; + } else { + $dest = "$defaultName"; + } +} else { + # Can only verify local destination here. SCP will verify the remote destination. + if ( $remoteHost eq '' ) { + if ( $dest !~ /\// ) { + $dest = "$installDir/$dest"; + } else { + if ( $dest !~ /^$installDir/ ) { + print "Error: The destination is not within the install directory specified in the site table.\n"; + $rc = 20; + goto FINISH_main; + } + } + } +} + +# Create the image file. +createImage(); + +FINISH_main: +exit $rc; + diff --git a/xCAT-client/bin/verifynode b/xCAT-client/bin/verifynode new file mode 100644 index 000000000..9d878e978 --- /dev/null +++ b/xCAT-client/bin/verifynode @@ -0,0 +1,2862 @@ +#!/usr/bin/perl +############################################################################### +# IBM (C) Copyright 2016 Eclipse Public License +# http://www.eclipse.org/org/documents/epl-v10.html +############################################################################### +# COMPONENT: verifynode +# +# Verify the capabilities of a node. +############################################################################### + +package xCAT_plugin::verifynode; +BEGIN +{ + $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; +} + +$XML::Simple::PREFERRED_PARSER='XML::Parser'; + +use strict; +use warnings; + +use Data::Dumper qw(Dumper); +use File::Basename; +use File::Path; +use File::Spec; +use File::Temp; +use Getopt::Long; +use Getopt::Long qw(GetOptionsFromString); +use lib "$::XCATROOT/lib/perl"; +use MIME::Base64; +use POSIX qw/strftime/; +use Scalar::Util qw( looks_like_number ); +use Sys::Hostname; +use Sys::Syslog qw( :DEFAULT setlogsock ); +use Socket; +use Text::Wrap; +use Time::HiRes qw( gettimeofday sleep ); +use Unicode::UCD qw(charinfo); + +use xCAT::zvmCPUtils; +use xCAT::zvmMsgs; +use xCAT::zvmUtils; + +my $version = "1"; + +my $comments = ''; # Comments for the IVP run +my $decode = ''; # String specified by the --decode operand. +my $disable = ''; # Disable scheduled IVP from running. +my $displayHelp = 0; # Display help information +my $driver = ''; # Name of driver script +my $driverLoc = '/var/opt/xcat/ivp'; # Local directory containing the driver script, if it exists +my $encode = ''; # String specified by the --encode operand. +my $enable = ''; # Enable scheduled IVP to run. +my $endTime = ''; # Human readable end time +my %hosts; # Information on host nodes known by xCAT MN +my $id = ''; # Id of the IVP run +my %ignored; # List of ignored messages +my $ignoreCnt = 0; # Number of times we ignored a message +my %localIPs; # Hash of the Local IP addresses for the xCAT MN system +my $logDir = '/var/log/xcat/ivp'; # Log location +my $logFile = ''; # Log file name and location +my $logFileHandle; # File handle for the log file. +my @ivpSummary; # Summary output lines for an IVP run. +my $locApplSystemRole = '/var/lib/sspmod/appliance_system_role'; +my %mnInfo; # Managed node environment information +my %msgsToIgnore; # Hash of messages to ignore +my $needToFinishLogFile = 0; # If 1 then finishLogFile() needs to be called +my $nodeName = ''; # Name of node to be verified +my $notify = 0; # Notify a user +my $notifyOnErrorOrWarning = 0; # Indicates a log file should be sent if the need to notify + # a user was determined. +my $openstackIP = ''; # IP address of the OpenStack node +my $openstackUser = 'nova'; # OpenStack for Nova +my $orchParms = ''; # Orchestrator parms to be stored in the zvmivp table +my $prepParms = ''; # Parameters to pass to the preparation script +my $remove = 0; # Remove a scheduled IVP +my $runBasicIVP = 0; # Run the basic xCAT IVP +my $runCron = 0; # Cron job invocation to run a possible set of IVPs + # on a periodic basis. +my $runFullIVP = 0; # Run the full end to end IVP (compute node and xCAT) +my $schedule = ''; # Hours to schedule an automated IVP +my $scheduledType = ''; # Type of IVP to schedule ('fullivp' or 'basicivp') +my $startTime = ''; # Human readable start time +my $tgtOS = ''; # Target Node's distro & version +my $todayDate = ''; # Today's date in the form 'yyyy-mm-dd' +my $verifyAccess = 0; # Verify system can be accessed +my $verifyCaptureReqs = 0; # Verify systems meets capture requirements +my $verifyCloudInit = 0; # Verify cloud-init installed +my $verifyXcatconf4z = 0; # Verify xcatconf4z +my $versionOpt = 0; # Show version information flag +my $warnErrCnt = 0; # Count of warnings and error messages for a verify set +my $zhcpTarget = ''; # ZHCP server that is related to this verification +my $zvmHost = ''; # z/VM host related to this verification +my $zxcatParms = ''; # Parmeters to pass to zxcatIVP + +# Routines to drive based on the selected test. +my %verifySets = ( + '$decode ne \'\'' => + [ '', # Do not show a function banner. + 'encodeDecodeString()', + ], + '$disable and ! $remove and $schedule eq \'\'' => + [ 'Disabling an IVP run', + 'enableDisableRun( $id )', + ], + '$enable and ! $remove and $schedule eq \'\'' => + [ 'Enabling an IVP run', + 'enableDisableRun( $id )', + ], + '$encode ne \'\'' => + [ 'Encoding a string', + 'encodeDecodeString()', + ], + '$remove' => + [ 'Remove an automated periodic IVP', + 'removeIVP( $id )', + ], + '$runBasicIVP' => + [ 'Verifying the basic general setup of xCAT', + 'setupLogFile()', + 'addLogHeader( \'basicivp\' )', + 'verifyBasicXCAT()', + 'finishLogFile()', + ], + '$runCron' => + [ 'Cron job for automated verification', + 'driveCronList()', + ], + '$runFullIVP' => + [ 'Verifying the full end-to-end setup of the xCAT and the compute node', + 'setupLogFile()', + 'addLogHeader( \'fullivp\' )', + 'checkForDefault( \'openstackIP\', \'--openstackIP was not specified\', \'xCAT_master\' )', + 'verifyAccess( $openstackIP ); $rc = 0', + 'getDistro( $openstackIP )', + 'runPrepScript()', + 'runDriverScript()', + 'finishLogFile()', + ], + '$schedule ne \'\'' => + [ 'Scheduling an automated periodic IVP', + 'scheduleIVP()', + ], + '$verifyAccess' => + [ 'Verifying access to the system', + 'verifyPower( $nodeName )', + 'verifyAccess( $nodeName )' + ], + '$verifyCaptureReqs' => + [ 'Verifying system meets the image requirements', + 'verifyPower( $nodeName )', + 'verifyAccess( $nodeName )', + 'getDistro( $nodeName )', + 'verifyDistro()', + ], + '$verifyCloudInit' => + [ 'Verifying cloud-init is installed and configured', + 'verifyPower( $nodeName )', + 'verifyAccess( $nodeName )', + 'getDistro( $nodeName )', + 'verifyService($nodeOrIP, "cloud-config,2,3,4,5 cloud-final,2,3,4,5 cloud-init,2,3,4,5 cloud-init-local,2,3,4,5")', + 'showCILevel()', + ], + '$verifyXcatconf4z' => + [ 'Verifying xcatconf4z is installed and configured', + 'verifyPower( $nodeName )', + 'verifyAccess( $nodeName )', + 'getDistro( $nodeName )', + 'verifyXcatconf4z()', + ], + '$test' => + [ 'Script test functon.', + 'verifyLogResponse()', + ], + ); + + +# set the usage message +my $usage_string = "Usage:\n + $0 -n \n + or\n + $0 \n + or\n + $0 -v\n + or\n + $0 [ -h | --help ]\n + The following options are supported:\n + -a | --access + Verify access from xCAT MN to node. + --basicivp + Run the basic xCAT IVP. + --capturereqs + Verify that node meets system capture + requirements for OpenStack. + -c | --cloudinit + Verify that node has cloud-init installed. + --comments + Comments to specify on the IVP run. + --cron + Driven as a cron job to run a set of periodic + IVPs. + --decode + Decode an encoded string. This is used to + view encoded values in the zvmivp table. + --disable Indicates that the IVP being scheduled should + be disabled from running. + --enable Indicates that the IVP being scheduled should + be enabled to run. This is the default when + neither --disable or --enable is specified. + --encode + Encode a string. This used for debug in order + to create a string to compare to an encoded + string from the zvmivp table. The must be + an ASCII string. This function does not support + encoding a UTF-8 character string. + --fullivp + Run the full end-to-end IVP. This + verification checks the compute node and builds + a driver script which is then used to check + the xCAT environment based on the values + gathered in the driver script. + -h | --help + Display help information. + -i | --id + ID to associate with the IVP run. + --ignore + Blank or comma separated list of message ids or + message severities to ignore. Ignored messages are + not counted as failures and do not produce messages. + Instead the number of ignored messages and their + message numbers are displayed at the end of + processing. Recognized message severities: + 'bypass', 'info', 'warning', 'error'. + --notify + Notification of IVP failures should be sent + to a z/VM userid. + -n | --node + Node name of system to verify. + --openstackip + IP address of the OpenStack compute node. + --openstackuser + User to use to access the compute node. + The user defaults to 'nova'. + --orchparms + Parameters to be stored in the zvmivp table. These + parameters are passed to this routine when a + scheduled IVP is driven. + --prepparms + Parameters to pass to the preparation script as + part of a full IVP. + --remove + Indicates that an IVP in the zvmivp table should be + removed. The id is specified with the --id + operand. + --schedule + List of hours (in 24 hour format) to periodically + run the IVP. Multiple hours are separated by blanks. + For example, \'0 6 12 18 20\'. + --type < basicivp | fullivp > + Type of IVP to be scheduled. + -v + Display the version of this script. + -x | --xcatconf4z + Verify that node has xcatconf4z installed. + --zxcatparms + Parameters to pass to the zxcatIVP script.\n"; + + +#------------------------------------------------------- + +=head3 addLogHeader + + Description : Add a header to the log file. + Arguments : None + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = addLogHeader(); + +=cut + +#------------------------------------------------------- +sub addLogHeader { + my ( $hType ) = @_; + + logResponse( 'DF', '*NONFORMATTED*', '*******************************************************************************' ); + + # Generate the header based on the header type. + if ( $hType eq 'basicivp' ) { + logResponse( 'DF', 'GENERIC_RESPONSE_NOINDENT', "This log file was created for a basic xCAT IVP. It consists of output from ". + "zxcatIVP that validates the xCAT management node (MN) and the ZHCP Agents that are used by ". + "the xCAT MN node to manage the z/VM hosts." ); + } elsif ( $hType eq 'fullivp' ) { + logResponse( 'DF', 'GENERIC_RESPONSE_NOINDENT', "This log file was created for a full xCAT IVP which validates the z/VM OpenStack ". + "configuration files, the xCAT management node (MN) and the ZHCP Agents that are used by ". + "the xCAT MN node to manage the z/VM host. The log file contains the following output: output ". + "from the preparation script that validated the OpenStack properties, the contents of the ". + "driver script created by the preparation script to drive the second part of the IVP, output ". + "of the zxcatIVP run that is driven by the driver script to complete the full IVP." ); + } else { + logResponse( 'DF', 'GENERIC_RESPONSE', "VerifyNode log file." ); + } + + logResponse( 'DF', '*NONFORMATTED*', '*******************************************************************************' ); + + return 0; +} + + +#------------------------------------------------------- + +=head3 checkForDefault + + Description : Determine if a default is needed for + the specified property and set it if + it is needed. Currently, this routine + is only used for the master IP address. + Arguments : Name of property + String to use in a DFLT01 message. + Default or function to perform. + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = checkForDefault( 'openStackIP', '--ip was not specified', 'xCAT_master' ); + +=cut + +#------------------------------------------------------- +sub checkForDefault { + my ( $prop, $reasonString, $default ) = @_; + my $additionalInfo = ''; + my $rc = 0; + my $val; + + eval( "\$val = \$$prop" ); + if ( $val eq '' ) { + if ( $default eq 'xCAT_master' ) { + $default = ''; + if ( exists $mnInfo{'ip'} ) { + $default = $mnInfo{'ip'}; + $additionalInfo = 'The value came from the IP address used as the xCAT MN\'s ' . + 'master IP address that was specified in the site table.'; + } else { + # Use one of the local IPv4 IPs. We don't support IPv6. + if ( keys %localIPs > 0 ) { + # Can't find the MN IP so use the first local IPv4 IP. + foreach my $ip ( keys %localIPs ) { + if ( $ip =~ /:/ ) { + next; + } else { + $default = $ip; + $additionalInfo = 'The function could not determine the master IP address for ' . + 'the xCAT MN so the first local IPv4 address was used.'; + last; + } + } + if ( $default eq '' ) { + $default = '127.0.0.1'; + $additionalInfo = 'The function could not determine the IP address for the xCAT MN ' . + 'and could not find any IPv4 addresses on the system so \'127.0.0.1\' will be used ' . + 'in the hope that it will work.'; + } + } else { + # Don't know the local IPs. + # Default to the IPv4 loopback and let it fail. + $default = '127.0.0.1'; + $additionalInfo = 'The function cound not determine the IP addresses on the ' . + 'xCAT MN system so \'127.0.0.1\' was used.'; + } + } + } + + if ( $rc == 0 ) { + # Found a default, set it in the property. + eval( "\$$prop = \'$default\'" ); + logResponse( 'DF', 'DFLT01', $reasonString, $default, $additionalInfo ); + } + } + + return $rc; +} + + +#------------------------------------------------------- + +=head3 driveCronList + + Description : Run thru the list of IVPs and run any that + are due to be run. + Arguments : None. + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = driveCronList(); + +=cut + +#------------------------------------------------------- +sub driveCronList { + my $attemptedRuns = 0; + my $disabledRuns = 0; + my @ivps; + my $out = ''; + my $rc = 0; + my $totalRunsInTable = 0; + + # Determine the timestamp and other time related information. + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time); + $year = $year + 1900; + $todayDate = sprintf( "%04d-%02d-%02d", $year, $mon + 1, $mday ); + $startTime = sprintf( "%02d:%02d:%02d on %04d-%02d-%02d", + $hour, $min, $sec, $year, $mon + 1, $mday ); + my $currTime = sprintf( "%04d-%02d-%02d_%02d:%02d:%02d", + $year, $mon + 1, $mday, $hour, $min, $sec); + my $newLastRun = "$year $yday $hour"; + + logResponse( 'DFS', '*NONFORMATTED*', "CRON job run started at $startTime." ); + + # Read the z/VM IVP table to get the list of known IVP runs. + $out = `/opt/xcat/sbin/tabdump zvmivp | grep -v "^#"`; + $rc = $?; + my @lines = split( '\n', $out ); + $totalRunsInTable = scalar( @lines ); + if ( $totalRunsInTable != 0 ) { + my $row = 0; + my $nowTotalHours = ( ( $year ) * 8784 ) + ( $yday * 24 ) + $hour; + foreach my $line ( @lines ) { + $row++; + chomp( $line ); + my @field = split( ',', $line ); + for ( my $i=0; $i < scalar( @field ); $i++ ) { + $field[$i] =~ s/^\"|\"$//g; # trim quotes from both ends of the string + } + + # Process the fields from the zvmIVP table. Each row is related to a + # scheduled run. + # Field 0 - Unique IVP ID + # 1 - IP property for the OpenStack system + # 2 - Schedule, hours to run the IVP + # 3 - Last time IVP was run, ( year day hour ) + # 4 - Type of run, e.g. basicivp, fullivp + # 5 - OpenStack user to access the config files + # 6 - Parameters to pass to the orchestrator, verifynode + # 7 - Parameters to pass to the preparator script, prep_zxcatIVP + # 8 - Parameters to pass to the main IVP script, zxcatIVP.pl + # 9 - Comments related to the IVP run + # 10 - Disabled, 1 or YES equals disabled + my %ivpRun; + if ( defined $field[0] and $field[0] ne '' ) { + $ivpRun{'id'} = $field[0]; + } else { + logResponse( 'DS', 'GENERIC_RESPONSE', "Row $row in the ivp table missing a ". + "required property, \'id\'. The row will be treated as disabled." ); + $disabledRuns++; + next; + } + + if ( defined $field[10] and ($field[10] eq '1' or uc( $field[10] ) eq 'YES') ) { + # IVP run is disabled + logResponse( 'DS', 'GENERIC_RESPONSE', "Row $row, ID($ivpRun{'id'}), in the ivp table is ". + "disabled and will not be processed." ); + $disabledRuns++; + next; + } + + if ( defined $field[1] and $field[1] ne '' ) { + $ivpRun{'ip'} = $field[1]; + } else { + # Assume this is a local xCAT IVP run + $ivpRun{'ip'} = 'xcat'; + logResponse( 'DS', 'GENERIC_RESPONSE', "Row $row, ID($ivpRun{'id'}), in the ivp table " . + "has no value specified for the \'ip\' property. " . + "The IVP run will be against the system which is " . + "running the xCAT management node." ); + } + + if ( defined $field[2] and $field[2] ne '' ) { + $ivpRun{'schedule'} = hexDecode( $field[2] ); + } else { + # Default run time is 1 AM + $ivpRun{'schedule'} = '1'; + } + + my $lRYear; + my $lRYDay; + my $lRHour; + + if ( defined $field[3] and $field[3] ne '' ) { + $ivpRun{'last_run'} = $field[3]; + my @parts = split ( '\s', $ivpRun{'last_run'} ); + if ( defined $parts[0] and $parts[0] ne '' ) { + $lRYear = $parts[0]; + } else { + # Missing year, set default to immediately, 0. + $lRYear = 0; + } + if ( defined $parts[1] and $parts[1] ne '' ) { + $lRYDay = $parts[1]; + } else { + # Missing day number, set default to immediately, 0. + $lRYDay = 0; + } + if ( defined $parts[2] and $parts[2] ne '' ) { + $lRHour = $parts[2]; + } else { + # Missing hour, set default to immediately, -1. + $lRHour = 0; + } + } else { + # Force a schedule run + $lRYear = 0; + $lRYDay = 0; + $lRHour = -1; + } + $ivpRun{'last_run'} = "$lRYear $lRYDay $lRHour"; + + # Weed out any runs that are not due to be run. + # Calculate the difference (since 2000) between the last run hour and the current hour, + # ignoring leap years by saying every year is a leap year. + my $totalHourDiff = $nowTotalHours - (( ( $lRYear ) * 8784 ) + ( $lRYDay * 24 ) + $lRHour); + + if ( $totalHourDiff == 0 ) { + # Same hour of the same day of the same year for some reason so ignore this IVP row. + logResponse( 'DS', 'GENERIC_RESPONSE', "Row $row, ID($ivpRun{'id'}), in the ivp table ". + "is being ignored because it was already run this hour." ); + next; + } else { + # Determine the range of hours that are of interest within a 24 hour time period. Anything + # that is more than 24 hours old is automatically overdue and should be run. + if ( $totalHourDiff < 24) { + my $todayStartHour; + my $yesterdayStartHour; + if ( $yday > $lRYDay ) { + # Last run yesterday + $todayStartHour = 0; + $yesterdayStartHour = $lRHour + 1; + } else { + # Last run today + $todayStartHour = $hour - $totalHourDiff; + $yesterdayStartHour = 24; + } + + # Less than a day so we need to worry about which scheduled hours were missed. + my $overdue = 0; + my @schedHours = split ( '\s', $ivpRun{'schedule'} ); + foreach my $sHour ( @schedHours ) { + if ( $sHour == 0 and $sHour ne '0' ) { + # not numeric, ignore + logResponse( 'DS', 'GENERIC_RESPONSE', "Row $row, ID($ivpRun{'id'}), in the ivp table ". + "contains as schedule hour that is non-numeric: $sHour. ". + "The value will be ignored." ); + next; + } + if (( $sHour >= $yesterdayStartHour ) or + ( $sHour > $todayStartHour and $sHour <= $hour ) ) { + logResponse( 'DS', 'GENERIC_RESPONSE', "Row $row, ID($ivpRun{'id'}), in the ivp table ". + "is due to be run. Scheduled hour: $sHour, last run: $lRHour." ); + $overdue = 1; + last; + } + } + if ( $overdue == 0 ) { + # did not find a scheduled hour that is due + logResponse( 'DS', 'GENERIC_RESPONSE', "Row $row, ID($ivpRun{'id'}), in the ivp table is not ". + "due to be run this hour." ); + next; + } + } else { + logResponse( 'DS', 'GENERIC_RESPONSE', "Row $row, ID($ivpRun{'id'}), in the ivp table ". + "is due to be run. Last run over 24 hours ago." ); + } + } + + if ( defined $field[4] and $field[4] ne '' ) { + $ivpRun{'type_of_run'} = lc( $field[4] ); + } else { + $ivpRun{'type_of_run'} = 'not specified'; + } + if ( $ivpRun{'type_of_run'} ne 'basicivp' and $ivpRun{'type_of_run'} ne 'fullivp' ) { + my $badValue = $ivpRun{'type_of_run'}; + if ( exists $mnInfo{'ip'} and ( $ivpRun{'ip'} eq $mnInfo{'ip'} )) { + if (( $mnInfo{'role'} eq 'controller' ) or + ( $mnInfo{'role'} eq 'compute' ) or + ( $mnInfo{'role'} eq 'compute_mn' )) { + $ivpRun{'type_of_run'} = 'fullivp'; + } else { + $ivpRun{'type_of_run'} = 'basicivp'; + } + } else { + $ivpRun{'type_of_run'} = 'basicivp'; + } + logResponse( 'DS', 'GENERIC_RESPONSE', "Row $row, ID($ivpRun{'id'}), in the ivp table contains ". + "an unknown run type value of \'$badValue\'. Instead ". + "a run type of \'$ivpRun{'type_of_run'}\' will be performed." ); + } + + if ( defined $field[5] and $field[5] ne '' ) { + $ivpRun{'access_user'} = hexDecode( $field[5] ); + } else { + $ivpRun{'access_user'} = ''; + } + + if ( defined $field[6] and $field[6] ne '' ) { + $ivpRun{'orch_parms'} = hexDecode( $field[6] ); + } else { + $ivpRun{'orch_parms'} = ''; + } + + if ( defined $field[7] and $field[7] ne '' ) { + $ivpRun{'prep_parms'} = hexDecode( $field[7] ); + } else { + $ivpRun{'prep_parms'} = ''; + } + + if ( defined $field[8] and $field[8] ne '' ) { + # Ensure that it is reencoded to pass along to do the actual work. + $ivpRun{'main_ivp_parms'} = hexEncode( $field[8] ); + } else { + $ivpRun{'main_ivp_parms'} = ''; + } + + if ( defined $field[9] and $field[9] ne '' ) { + $ivpRun{'comments'} = hexDecode( $field[9] ); + } else { + $ivpRun{'comments'} = ''; + } + + push @ivps, \%ivpRun; + } + } else { + # Table is empty. Create a single run for the z/VM related to the + # xCAT MN. + logResponse( 'DS', 'GENERIC_RESPONSE', "The ivp table is empty. Default IVPs will be enabled." ); + if ( exists $mnInfo{'node'} ) { + # First IVP is a full IVP for an OpenStack related xCAT at 1 am. + # Otherwise it is a basic IVP. + my %ivpRun; + $ivpRun{'id'} = 1; + if ( exists $mnInfo{'ip'} ) { + $ivpRun{'ip'} = $mnInfo{'ip'}; + } else { + $ivpRun{'ip'} = 'xcat'; + } + $ivpRun{'schedule'} = '1'; + if ( $mnInfo{'role'} eq 'controller' or $mnInfo{'role'} eq 'compute' or $mnInfo{'role'} eq 'compute_mn' ) { + $ivpRun{'type_of_run'} = 'fullivp'; + $ivpRun{'access_user'} = 'root'; + } else { + $ivpRun{'type_of_run'} = 'basicivp'; + $ivpRun{'access_user'} = ''; + } + $ivpRun{'orch_parms'} = ''; + $ivpRun{'prep_parms'} = ''; + $ivpRun{'main_ivp_parms'} = ''; + $ivpRun{'comments'} = 'Default Full IVP run'; + $ivpRun{'defaultRun'} = 'run'; + push @ivps, \%ivpRun; + $totalRunsInTable++; + + # Second IVP is a basic IVP run each hour except for 1 am when a full IVP is run. + my %ivpRun2; + $ivpRun2{'id'} = 2; + if ( exists $mnInfo{'ip'} ) { + $ivpRun2{'ip'} = $mnInfo{'ip'}; + } else { + $ivpRun2{'ip'} = 'xcat'; + } + $ivpRun2{'schedule'} = '0 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23'; + $ivpRun2{'type_of_run'} = 'basicivp'; + $ivpRun2{'access_user'} = ''; + $ivpRun2{'orch_parms'} = ''; + $ivpRun2{'prep_parms'} = ''; + $ivpRun2{'main_ivp_parms'} = ''; + $ivpRun2{'comments'} = 'Default Basic IVP run'; + $ivpRun2{'defaultRun'} = 'runLater'; + push @ivps, \%ivpRun2; + $totalRunsInTable++; + } else { + # xCAT MN does not exist as a node so we will only do a basic IVP the system. + # This is a very abnormal case. We deal in the abnormal so let's run some type + # of IVP but not define it in the table. + logResponse( 'DFS', 'MN03' ); + my %ivpRun; + $ivpRun{'id'} = 1; + if ( exists $mnInfo{'ip'} ) { + $ivpRun{'ip'} = $mnInfo{'ip'}; + } else { + $ivpRun{'ip'} = 'xcat'; + } + $ivpRun{'schedule'} = '0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23'; + $ivpRun{'type_of_run'} = 'basicivp'; + $ivpRun{'access_user'} = ''; + $ivpRun{'orch_parms'} = ''; + $ivpRun{'prep_parms'} = ''; + $ivpRun{'main_ivp_parms'} = ''; + $ivpRun{'comments'} = 'Default Basic IVP run'; + $ivpRun{'defaultRun'} = 'noTableUpdate'; + push @ivps, \%ivpRun; + $totalRunsInTable++; + } + } + + # Handle pruning, if necessary. + if ( $hour == 0 ) { + pruneLogs( $year, $mon + 1, $mday ); + } + + # Go thru the list of IVPs and drive a run for each IVP + # that is due to be run. + foreach my $ivp ( @ivps ) { + my %ivpRun = %$ivp; + + # Build the verify node command. + my $cmd = '/opt/xcat/bin/verifynode -n xcat --notify'; + + if ( $ivpRun{'type_of_run'} eq 'fullivp' ) { + $cmd = "$cmd --fullivp --openstackip \'$ivpRun{'ip'}\' --openstackuser \'" . hexEncode( $ivpRun{'access_user'} ) . "\'"; + } else { + $cmd = "$cmd --basicivp"; + } + + if ( $ivpRun{'orch_parms'} ne '' ) { + $cmd = "$cmd $ivpRun{'orch_parms'}"; # Don't hex encode orchestrator parms + } + + if ( $ivpRun{'prep_parms'} ne '' ) { + $cmd = "$cmd --prepparms \'" . hexEncode( $ivpRun{'prep_parms'} ) . "\'"; + } + + if ( $ivpRun{'main_ivp_parms'} ne '' ) { + $cmd = "$cmd --zxcatparms \'" . hexEncode( $ivpRun{'main_ivp_parms'} ) . "\'"; + } + + # Update the table to indicate the time we start the IVP run. + if ( ! exists $ivpRun{'defaultRun'} ) { + my $chtabCmd = "/opt/xcat/sbin/chtab id=\'$ivpRun{'id'}\' zvmivp.last_run=\'$newLastRun\'"; + $out = `$chtabCmd`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DS', '*NONFORMATTED*', "\'$chtabCmd\' failed for IVP ID($ivpRun{'id'}), ip: $ivpRun{'ip'}, rc: $rc, out: $out\n" ); + } + } elsif ( $ivpRun{'defaultRun'} ne 'noTableUpdate' ) { + logResponse( 'DS', '*NONFORMATTED*', "Adding IVP ID($ivpRun{'id'}) to the z/VM IVP table: $ivpRun{'comments'}\n" ); + my $chtabCmd = "/opt/xcat/sbin/chtab id=\'$ivpRun{'id'}\' ". + "zvmivp.ip=\'$ivpRun{'ip'}\' ". + "zvmivp.schedule=\'$ivpRun{'schedule'}\' ". + "zvmivp.last_run=\'$newLastRun\' ". + "zvmivp.type_of_run=\'$ivpRun{'type_of_run'}\' ". + "zvmivp.access_user=\'" . hexEncode( $ivpRun{'access_user'} ) . "\' ". + "zvmivp.comments=\'" . hexEncode( $ivpRun{'comments'} ) . "\' "; + $out = `$chtabCmd`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DS', '*NONFORMATTED*', "\'$chtabCmd\' failed for ID($ivpRun{'id'}), ip: $ivpRun{'ip'}, rc: $rc, out: $out\n" ); + } + + # Skip running this one if it is marked 'runLater'. + # A basic IVP is not needed if a full IVP is going to be run. + if ( $ivpRun{'defaultRun'} eq 'runLater' ) { + logResponse( 'DS', '*NONFORMATTED*', "Ignoring IVP ID($ivpRun{'id'}) for this hour. It will be run next hour. IVP comment: $ivpRun{'comments'}\n" ); + next; + } + } + + # Drive the run. + $attemptedRuns++; + my $logComments = ''; + if ( $ivpRun{'comments'} eq '' ) { + $logComments = "Running IVP ID($ivpRun{'id'})"; + } else { + $logComments = "Running IVP ID($ivpRun{'id'}) - $ivpRun{'comments'}"; + } + logResponse( 'DS', '*NONFORMATTED*', "$logComments" ); + $out = `$cmd`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DS', '*NONFORMATTED*', "The IVP run for ID($ivpRun{'id'}) returned a ". + "non-zero return code, rc: $rc\n" ); + } + } + + logResponse( 'DFS', '*NONFORMATTED*', "IVP CRON results: $totalRunsInTable runs considered, $disabledRuns runs disabled, $attemptedRuns runs attempted" ); + $rc = 0; + return $rc; +} + + +#------------------------------------------------------- + +=head3 enableDisableRun + + Description : Enable or Disable command line function. + Arguments : ID of the IVP to be disabled + Global input set by command operands are used + by this routine: + $disable - Disable a run. + $enable - Enable a run. + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = enableDisableRun(); + +=cut + +#------------------------------------------------------- +sub enableDisableRun { + my ( $id ) = @_; + my %ids; + my $rc = 0; + + if ( $id eq '' ) { + logResponse( 'DF', 'OPER01', '--id' ); + goto FINISH_enableDisableRun; + } + + # Get the list of IVP ids in the zvmivp table. + my $cmd = '/opt/xcat/sbin/tabdump zvmivp | grep -v "^#"'; + my $out = `$cmd`; + $rc = $?; + if ( $rc ne 0 ) { + logResponse( 'DF', 'GNRL04', $cmd, $rc, $out ); + goto FINISH_enableDisableRun; + } + + my @lines = split( '\n', $out ); + foreach my $line ( @lines ) { + chomp( $line ); + my ($ivpId, $junk) = split( ',', $line, 2 ); + $ivpId =~ s/^\"|\"$//g; # trim quotes from both ends of the string + $ivpId =~ s/^\s+|\s+$//g; # trim spaces from both ends of the string + if ( $ivpId ne '' ) { + $ids{$ivpId} = 1; + } + } + + # Validate the specified id. + if ( ! exists $ids{$id} ) { + logResponse( 'DF', 'ID03', $id ); + } + + my $action; + my $disableFlag = ''; + if ( $disable ) { + $disableFlag = 'YES'; + $action = 'disable'; + } else { + $action = 'enable'; + } + + # Update the table. + logResponse( 'DS', '*NONFORMATTED*', "Updating the z/VM IVP table (zvmivp) to $action $id" ); + my $chtabCmd = "/opt/xcat/sbin/chtab id='$id' zvmivp.disable=\'$disableFlag\'"; + $out = `$chtabCmd`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DS', '*NONFORMATTED*', "\'$chtabCmd\' failed for ID($id), rc: $rc, out: $out\n" ); + $rc = 1; + } + +FINISH_enableDisableRun: + return $rc; +} + + +#------------------------------------------------------- + +=head3 encodeDecodeString + + Description : Encode and decode command line function. + Arguments : Global input set by command operands are used + by this routine: + $decode - String to be decoded if not ''. + $encode - String to be encoded if not ''. + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = encodeDecodeString(); + +=cut + +#------------------------------------------------------- +sub encodeDecodeString { + my $newVal = ''; + if ( $decode ne '' ) { + binmode(STDOUT, ":utf8"); + print( "Value is: '" . hexDecode( $decode ) . "'\n" ); + } + + if ( $encode ne '' ) { + print( "Value is: '" . hexEncode( $encode ) . "'\n" ); + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 finishLogFile + + Description : Complete the log file for the run and + send it to the notify user if necessary. + Arguments : None + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = finishLogFile(); + +=cut + +#------------------------------------------------------- +sub finishLogFile { + my $rc = 0; + $needToFinishLogFile = 0; + + # Add the summary information to the log file. + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time); + $endTime = sprintf("%02d:%02d:%02d on %04d-%02d-%02d", + $hour, $min, $sec, $year + 1900, $mon + 1, $mday ); + + logResponse( 'DF', '*NONFORMATTED*', '*******************************************************************************' ); + logResponse( 'DF', 'GENERIC_RESPONSE', 'Results of the IVP run:\n' ); + logResponse( 'DF', 'GENERIC_RESPONSE', "Run started at $startTime and ended at $endTime." ); + foreach my $line ( @ivpSummary ) { + logResponse( 'DF', 'GENERIC_RESPONSE', $line ); + } + if ( $ignoreCnt != 0 ){ + logResponse( 'DFS', 'GENERIC_RESPONSE', "The orchestrator script (verifynode) ignored messages $ignoreCnt times." ); + my @ignoreArray = sort keys %ignored; + my $ignoreList = join ( ', ', @ignoreArray ); + logResponse( 'DFS', 'GENERIC_RESPONSE', "Message Ids of ignored messages by verifynode: $ignoreList" ); + } + logResponse( 'DF', '*NONFORMATTED*', '*******************************************************************************' ); + + # Notify the z/VM userid, if necessary. + if ( $notify and $notifyOnErrorOrWarning ) { + notifyUser(); + } + +FINISH_finishLogFile: + return $rc; +} + + +#------------------------------------------------------- + +=head3 getDistro + + Description : Create the dummy image file. + Arguments : Node name or IP address + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = getDistro(); + +=cut + +#------------------------------------------------------- +sub getDistro { + my ( $nodeOrIP ) = @_; + my $rc = 0; + + $tgtOS = xCAT::zvmUtils->getOsVersion( $::SUDOER, $nodeOrIP ); + +FINISH_getDistro: + return $rc; +} + + +#------------------------------------------------------- + +=head3 getLocalIPs + + Description : Get the IP addresses from ifconfig. + Arguments : Node name or IP address + Returns : return code (Currently always 0) + 0 - No error + non-zero - Terminating error detected. + Hash of local IP addresses + Example : $rc = getLocalIPs(); + +=cut + +#------------------------------------------------------- +sub getLocalIPs { + my $ip; + my $junk; + my $rc = 0; + + my $out = `/sbin/ip addr | grep -e '^\\s*inet' -e '^\\s*inet6'`; + my @lines = split( '\n', $out ); + foreach my $line ( @lines ) { + my @parts = split( ' ', $line ); + ($ip) = split( '/', $parts[1], 2 ); + $localIPs{$ip} = 1; + } + +FINISH_getLocalIPs: + return $rc; +} + + +#------------------------------------------------------- + +=head3 getMNInfo + + Description : Get information related to the xCAT MN + and the z/VM system where it is running. + Arguments : None + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = getMNInfo(); + +=cut + +#------------------------------------------------------- +sub getMNInfo { + my $cmd; + my $hostNode; + my @lines; + my $masterIP = ''; + my $out = ''; + my @parts; + my $rc = 0; + my $xcatMN; + + # Get the list of Local IP addresses + getLocalIPs(); + + # Get information related to all of the host nodes + $cmd = '/opt/xcat/bin/lsdef -w mgt=zvm -w hosttype=zvm -i hcp'; + $out = `$cmd`; + $rc = $?; + if ( $rc eq 0 ) { + @lines = split( '\n', $out ); + my $host = ''; + foreach my $line ( @lines ) { + $line =~ s/^\s+|\s+$//g; # trim blanks from both ends of the string + if ( $line =~ /Object name: / ) { + ($host) = $line =~ m/Object name: (.*)/; + next; + } elsif ( $line =~ /hcp\=/ ) { + ($hosts{$host}{'hcp'}) = $line =~ m/hcp\=(.*)/; + next; + } + } + } else { + $rc = logResponse( 'DFS', 'GNRL01', "$cmd", $rc, $out ); + $notify = 1; + } + + # Get key info related to the xCAT MN and the notify targets from the site table. + $mnInfo{'PruneIVP'} = 7; + $cmd = "/opt/xcat/sbin/tabdump site | grep -e '^\"master\"' -e '^\"zvmnotify\"' -e '^\"zvmpruneivp\"'"; + $out = `$cmd`; + $rc = $?; + if ( $rc == 0 ) { + @lines = split( '\n', $out ); + foreach my $line ( @lines ) { + $line =~ s/^\s+|\s+$//g; # trim blanks from both ends of the string + @parts = split( ',', $line ); + $parts[1] =~ s/\"//g; + if ( $parts[0] =~ /^\"master\"/ ) { + $masterIP = $parts[1]; + } elsif ( $parts[0] =~ /^\"zvmnotify\"/ ) { + my @notifyInfo = split( ';', $parts[1] ); + my $notifyHost = ''; + my $notifyUser = ''; + foreach my $hostUser ( @notifyInfo ) { + ($notifyHost) = $hostUser =~ m/(.*)\(/; + ($notifyUser) = $hostUser =~ m/\((.*)\)/; + if ( defined $notifyHost and $notifyHost ne '' and defined $notifyUser and $notifyUser ne '' ) { + $hosts{$notifyHost}{'notifyUser'} = uc( $notifyUser ); + } else { + $rc = logResponse( 'DFS', 'SITE01', 'zvmnotify', $parts[1], $hostUser ); + $notify = 1; + } + } + } elsif ( $parts[0] =~ /^\"zvmpruneivp\"/ ) { + $mnInfo{'PruneIVP'} = $parts[1]; + } + } + } else { + # Unable to read the site table + $rc = logResponse( 'DFS', 'GNRL01', "$cmd", $rc, $out ); + $notify = 1; + } + + # Find a node that has the same address as the master IP and then find + # the host node that relates to that node. + if ( $masterIP eq '' ) { + # Unable to get the master value + $rc = logResponse( 'DFS', 'SITE02', 'master' ); + } else { + $cmd = "/opt/xcat/bin/lsdef -w ip=$masterIP -i hcp"; + $out = `$cmd`; + $rc = $?; + if ( $rc == 0 ) { + @lines = split( '\n', $out ); + foreach my $line ( @lines ) { + $line =~ s/^\s+|\s+$//g; # trim blanks from both ends of the string + if ( $line =~ /Object name: / ) { + ($xcatMN) = $line =~ m/Object name: (.*)/; + $mnInfo{'node'} = $xcatMN; + $mnInfo{'ip'} = $masterIP; + next; + } elsif ( $line =~ /hcp\=/ ) { + ($mnInfo{'hcp'}) = $line =~ m/hcp\=(.*)/; + next; + } + } + } else { + $rc = logResponse( 'DFS', 'MN01', $masterIP, $cmd, $rc, $out ); + } + + # Find the host node which uses the hcp. + foreach $hostNode ( keys %hosts ) { + if ( exists $hosts{$hostNode}{'hcp'} and $hosts{$hostNode}{'hcp'} eq $mnInfo{'hcp'} ) { + if ( exists $hosts{$hostNode}{'notify'} ) { + $hosts{$hostNode}{'notify'} = $hosts{$hostNode}{'notify'} . 'm'; + } else { + $hosts{$hostNode}{'notify'} = 'm'; + } + $mnInfo{'host'} = $hostNode; + } + } + } + + # The following is useful in a z/VM CMA system only. + # As a safety measure, in case the site table is lost or does not have zvmnotify property, + # read the OPNCLOUD application system role file to find the value of the 'notify' property + # and remember it with the xCAT MN information. + # Also, determine the system role. + if ( -e $locApplSystemRole ) { + $out = `cat /var/lib/sspmod/appliance_system_role | grep -e '^notify\=' -e '^role\='`; + @lines = split( '\n', $out ); + foreach my $line ( @lines ) { + $line =~ s/^\s+|\s+$//g; # trim blanks from both ends of the string + my ($key, $value) = split( '=', $line, 2 ); + #($mnInfo{'notifyUser'}) = $line =~ m/^notify\=(.*)/; + if ( $key eq 'role' ) { + $mnInfo{'role'} = lc( $value ); + } + if ( $key eq 'notify' ) { + $mnInfo{'notifyUser'} = uc( $value ); + } + } + } else { + # Not CMA. Indicate role is not set. + $mnInfo{'role'} = ''; + } +} + + +#------------------------------------------------------- + +=head3 getOpenStackLevel + + Description : Get the OpenStack version level as a + name. + Arguments : IP address of OpenStack compute node. + Returns : string - OpenStack version name + (e.g. NEWTON), or + null string - error + Example : $level = getOpenStackLevel(); + +=cut + +#------------------------------------------------------- +sub getOpenStackLevel { + my ( $nodeOrIP, $openstackUser ) = @_; + my $cmd; + my $level = ''; + my $numWords = 0; + my $out = ''; + my @parts; + my $rc; + my %openStackVersion; + + # Get the list of known versions + $cmd = 'cat /opt/xcat/openstack.versions'; + $out = `$cmd`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DFS', 'GNRL01', $cmd, $rc, $out ); + $level = ''; + goto FINISH_getOpenStackLevel; + } + + my @versionLines = split ( /\n/, $out ); + foreach my $versionLine ( @versionLines ) { + if ( $versionLine =~ /^#/ ) { next; } + @parts = split( ' ', $versionLine ); + $openStackVersion{$parts[0]} = $parts[1]; + } + + # Get the version information from the OpenStack node + $cmd = "nova-manage --version 2>&1"; + ($rc, $out) = sshToNode( $openstackIP, $openstackUser, $cmd ); + if ( $rc != 0 ) { + # SSH failed, message already sent. + goto FINISH_getOpenStackLevel; + } + $out =~ s/^\s+|\s+$//g; # trim blanks from both ends of the string + ++$numWords while $out =~ /\S+/g; + if ( $numWords != 1 ) { + logResponse( 'DFS', 'GOSL01', $cmd, $openstackIP, $rc, $out ); + goto FINISH_getOpenStackLevel; + } + + @parts = split( '\.', $out ); + if ( !exists $parts[0] or !exists $parts[1] ) { + logResponse( 'DFS', 'GOSL01', $cmd, $openstackIP, $rc, $out ); + goto FINISH_getOpenStackLevel; + } elsif ( exists $openStackVersion{"$parts[0]."} ) { + $level = $openStackVersion{"$parts[0]."} + } elsif ( exists $openStackVersion{"$parts[0].$parts[1]."} ) { + $level = $openStackVersion{"$parts[0].$parts[1]."} + } else { + logResponse( 'DFS', 'GOSL02', $cmd, $openstackIP, $rc, $out ); + goto FINISH_getOpenStackLevel; + } + +FINISH_getOpenStackLevel: + return $level; +} + + +#------------------------------------------------------- + +=head3 hexDecode + + Description : Convert a string of printable hex + characters (4 hex characters per actual + character) into the actual string that + it represents. The string should + begin with 'HexEncoded:' which indicates + that it is encoded. The routine tolerates + getting called with a string that is not + not encoded and will return the string that + was passed to it instead of trying to decode + it. + Arguments : printable hex value + Returns : Perl string + Example : $rc = hexDecode(); + +=cut + +#------------------------------------------------------- +sub hexDecode { + my ( $hexVal ) = @_; + my $result = ''; + + if ( $hexVal =~ /^HexEncoded:/ ) { + ($hexVal) = $hexVal =~ m/HexEncoded:(.*)/; + my @hexes = unpack( "(a4)*", $hexVal); + for ( my $i = 0; $i < scalar(@hexes); $i++ ) { + $result .= chr( hex( $hexes[$i] ) ); + } + } else { + $result = $hexVal; + } + + return $result; +} + + +#------------------------------------------------------- + +=head3 hexEncode + + Description : Convert a string into a string of + printable hex characters in which each + character in the original string is + represented by the 2 byte (4 char) hex + code value. The string is preceded by + 'HexEncoded:' to indicate that it has + been encoded. The routine will tolerate + getting called with a string that has + already been encoded and not reencode it. + Arguments : ASCII or Unicode string + Returns : Hex string + Example : $rc = hexEncode(); + +=cut + +#------------------------------------------------------- +sub hexEncode { + my ( $str ) = @_; + my $hex; + my $result = $str; # All work done within the result variable. + + if ( $result ne '' ) { + # Encode the string if is not already encoded. Otherwise, leave it alone. + if ( $result !~ /^HexEncoded:/ ) { + $result =~ s/(.)/sprintf("%04x",ord($1))/eg; + $result = "HexEncoded:$result"; + } + } + + return $result; +} + + +#------------------------------------------------------- + +=head3 logResponse + + Description : Build and log the response. + Arguments : Target destination for the response. This is + string which can contain a charater for each + possible destination, e.g 'DSF'. The values are: + D - send to STDOUT + S - send to syslog + F - send to the log file + The default is 'DF' so that we send the output + to STDOUT and put it in the log file. + message ID or special flag: + *NONFORMATTED* indicates that the message build should not + be invoked but instead the message substitutions are + lines for the message to be produced. + *RESET* indicates that the message counter should be reset. + Array of message substitutions + Returns : 0 - No error, general response or info message detected. + 1 - Non-terminating message detected. + 2 - Terminating message detected. + Example : $rc = logResponse( 'D', 'VX01' ); + $rc = logResponse( '', 'VX03', $nodeName, $sub2); + $rc = logResponse( 'DFS', 'VX03', $nodeName, 'sub2a'); + +=cut + +#------------------------------------------------------- +sub logResponse { + my ( $dest, $msgId, @msgSubs ) = @_; + my $rc = 0; + my $extraInfo = ''; + my $msg = ''; + my @msgLines; + my $sev; + my $line; + + if ( $msgId eq '*RESET*' ) { + $warnErrCnt = 0; + goto FINISH_logResponse; + } elsif ( $msgId eq '*NONFORMATTED*' ) { + $rc = 0; + if ( @msgSubs ) { + foreach my $line ( @msgSubs ) { + $msg = "$msg$line\n"; + } + } else { + $msg = '\n'; + } + $sev = 0; + } else { + ( $rc, $sev, $msg, $extraInfo ) = xCAT::zvmMsgs->buildMsg('VERIFYNODE', $msgId, \@msgSubs); + if ( defined $msgsToIgnore{$msgId} ) { + # Ignore this message id + $ignored{$msgId} = 1; + $ignoreCnt += 1; + print( "Message $msgId is being ignored but would have occurred here.\n" ); + goto FINISH_logResponse; + } elsif ( defined $msgsToIgnore{$sev} ) { + # Ignoring all messages of this severity. + $ignored{$msgId} = 1; + $ignoreCnt += 1; + print( "Message $msgId is being ignored but would have occurred here.\n" ); + goto FINISH_logResponse; + } + } + + if ( $sev >= 4 ) { + $warnErrCnt += 1; + } + + # Send the message to the requested destination. + if ( $dest =~ 'D' ) { + print "$msg"; # Send message to STDOUT + } + if ( $dest =~ 'F' and defined $logFileHandle ) { + print $logFileHandle $msg; + } + if ( $dest =~ 'S' ) { + my $logMsg = $msg; + $logMsg =~ s/\t//g; + $logMsg =~ s/\n/ /g; + syslog( 'err', $logMsg ); + } + + # Send the extra info to the requested destination (never send it to syslog). + if ( $extraInfo ne '' ) { + if ( $dest =~ 'D' ) { + print "$extraInfo"; + } + if ( $dest =~ 'F' and defined $logFileHandle ) { + print $logFileHandle $msg; + } + } + +FINISH_logResponse: + return $rc; +} + + +#------------------------------------------------------- + +=head3 notifyUser + + Description : Notify a z/VM user. Send a message + and the log as a SPOOL file. + Arguments : Node name or IP address + Returns : 0 - Always ok. + Example : $rc = notifyUser(); + +=cut + +#------------------------------------------------------- +sub notifyUser { + my $cmd; + my $host; + my @hostOrder; + my $msg = ''; + my $out = ''; + my $rc = 0; + my $tempFile; + my $th; + + # Determine who we should notify based on the host related to the verification. + # The driver script would have set the host related to an full IVP (if we can trust it). + # If we cannot get to the host related to the run then we fall back to sending + # the notification to the user on the host in which the xCAT MN is running. + # If that fails then "tough luck, Chuck". + my $mnAdded = 0; + foreach $host ( keys %hosts ) { + if ( exists $hosts{$host}{'notify'} and exists $hosts{$host}{'hcp'} and exists $hosts{$host}{'notifyUser'} ) { + if ( $hosts{$host}{'notify'} =~ /d/ ) { + # Driver script specified hosts go on the top of the stack. + push @hostOrder, $host; + if ( $hosts{$host}{'notify'} =~ /m/ ) { + # The host related to the MN is already in the list + $mnAdded = 1; + } + } elsif ( $hosts{$host}{'notify'} =~ /m/ ) { + # Management node systems go on the bottom as a fall back plan. + unshift @hostOrder, $host; + $mnAdded = 1; + } + } + } + if ( $mnAdded == 0 and exists $mnInfo{'hcp'} and exists $mnInfo{'notifyUser'} ) { + # Did not find a host related to this management node but we have the + # necessary information to send the notification so add a dummy host + # to the hosts hash and add the dummy host to the end of the hostOrder + # stack to be used when all other notification attemps fail. + $host = '*DEFAULT_MN_HOST*'; + $hosts{$host}{'notify'} = 'm'; + $hosts{$host}{'notifyUser'} = $mnInfo{'notifyUser'}; + $hosts{$host}{'hcp'} = $mnInfo{'hcp'}; + unshift @hostOrder, $host; + } + + # Prepare the message. + $msg = "The xCAT IVP detected some possible issues. A log file is being sent to your reader."; + + # Prepare the log file by removing tabs and splitting the log file at 80 character lines. + $tempFile = mktemp( "$logDir/$logFile.XXXXXX" ); + `expand $logDir/$logFile | fold -w 80 1>$tempFile`; + + # Send the message and log file to the first entity in the host order list that lets us + # successfully do so. + my $done = 0; + my $failed = 0; + foreach $host ( @hostOrder ) { + # Check if zHCP's punch is online and online it if it is not. + ($rc, $out) = sshToNode( $hosts{$host}{'hcp'}, '', 'cat /sys/bus/ccw/drivers/vmur/0.0.000d/online' ); + chomp( $out ); + if ($rc != 0 or $out != 1) { + $cmd = '/sbin/cio_ignore -r 000d;/sbin/chccwdev -e 000d'; + ($rc, $out) = sshToNode( $hosts{$host}{'hcp'}, '', $cmd ); + chomp( $out ); + if ( $rc != 0 or !( $out =~ m/Done$/i ) ) { + $rc = logResponse( 'DFS', 'GNRL03', $hosts{$host}{'hcp'}, $cmd, $rc, $out ); + next; + } + ($rc, $out) = sshToNode( $hosts{$host}{'hcp'}, '', 'which udevadm &> /dev/null && udevadm settle || udevsettle' ); + # Don't worry about the udevadm settle. If it remains a problem then we will see it on the next command to ZHCP. + } + + # Send log file. It will exist temporarily on the ZHCP agent in the /tmp directory + # under its original log file name. + $rc = xCAT::zvmUtils->sendFile( 'root', $hosts{$host}{'hcp'}, "$tempFile", "/tmp/$logFile" ); + if ( $rc != 0 ) { + # An error is not a problem because the zhcp node could be logged off. + next; + } + + # Punch the file from the ZHCP agent to the target user. + $out = xCAT::zvmCPUtils->punch2Reader( 'root', $hosts{$host}{'hcp'}, $hosts{$host}{'notifyUser'}, "/tmp/$logFile", 'XCAT_IVP.RESULTS', '-t', 'A' ); + if ( $out ne 'Done' ) { + $rc = logResponse( 'DFS', 'GNRL02', "/tmp/$logFile", $hosts{$host}{'hcp'}, $hosts{$host}{'notifyUser'}, $host, "$logDir/$logFile", $out ); + $failed = 1; + } + + # Clean up the ZHCP node. + ($rc, $out) = sshToNode( $hosts{$host}{'hcp'}, '', "rm -f /tmp/$logFile" ); + if ( $rc != 0 ) { + $rc = logResponse( 'DFS', 'GNRL03', $hosts{$host}{'hcp'}, "rm -f /tmp/$logFile", $rc, $out ); + next; + } + + # Try another host if we failed to send the file. + if ( $failed ) { + $failed = 0; + next; + } + + # Send a message if the user is logged on the system. + ($rc, $out) = sshToNode( $hosts{$host}{'hcp'}, '', "vmcp query user $hosts{$host}{'notifyUser'}" ); + if ( $rc != 0 ) { + # User is either not logged on or we had a problem issuing the command. Not important, leave. + last; + } + $out = uc( $out ); + if ( $out =~ /^$hosts{$host}{'notifyUser'}/ ) { + my ($userStatus, $junk) = split '\s', $out, 2; + chomp( $userStatus ); + $userStatus =~ s/^\s+|\s+$//g; # trim both ends of the string + if ( $userStatus eq 'SSI' or $userStatus eq 'DSC' ) { + logResponse( 'DFS', 'MSG01', $hosts{$host}{'notifyUser'}, $host, $userStatus ); + last; + } + } + + # Don't worry if message still does not work. It is only a message. + ($rc, $out) = sshToNode( $hosts{$host}{'hcp'}, '', "vmcp message $hosts{$host}{'notifyUser'} $msg" ); + last; + } + + # Remove the temporary log file from the xCAT MN system. + $out = `rm -f $tempFile`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DFS', 'GNRL01', "rm -f $tempFile", $rc, $out ); + } + + $rc = 0; + return $rc; +} + + +#------------------------------------------------------- + + +=head3 pruneLogs + + Description : Compress and prune log files. The number of days to wait + before pruning is specified in the site table with the + zvmpruneivp property. If the property does not exist + or Management node information does not exist because of + some reason then the default is to prune logs that are + 7 days or older. + Arguments : Current year + Current month (numeric) + Current day (Julian day, numeric) + Returns : None. + Example : pruneLogs( $year, $mon + 1, $mday ); + +=cut + +#------------------------------------------------------- +sub pruneLogs{ + my ( $year, $month, $day ) = @_; + + my @lines; + my $pruneOffset = 0; + my $out; + my $pruneDate = ''; + my $rc; + my $removed = 0; + my $zipOut; + my $zipped = 0; + logResponse( 'DFS', '*NONFORMATTED*', "CRON job is pruning log files." ); + + # Determine the prune date value. + if ( ! exists $mnInfo{'PruneIVP'} or $mnInfo{'PruneIVP'} eq '' ) { + $pruneOffset = 7; + } else { + if ( looks_like_number( $mnInfo{'PruneIVP'} ) ) { + $pruneOffset = $mnInfo{'PruneIVP'}; + } else { + $pruneOffset = 7; + logResponse( 'DFS', 'TP01', 'zvmpruneivp', 'site', 'not numeric', $mnInfo{'PruneIVP'}, $pruneOffset ); + } + } + $pruneDate = strftime "%Y-%m-%d", 0, 0, 0, $day - $pruneOffset, $month-1, $year-1900; + logResponse( 'DFS', '*NONFORMATTED*', "Prune date: $pruneDate." ); + + # Zip any log files that are not for today. Current log files are similar to + # /var/log/xcat/ivp/IVP_XCAT_2016-09-19_01:01:03.log and will have .gz appended to the name + # after they have been zipped. + $out = `ls -1 /var/log/xcat/ivp/*.log`; + $rc = $?; + if ( $rc == 0 ) { + @lines = split( '\n', $out ); + foreach my $fqFile ( @lines ) { + chomp( $fqFile ); + my ($file) = $fqFile =~ /\/var\/log\/xcat\/ivp\/(.*)/; + my @parts = split( '_', $file ); + if ( ! defined $parts[0] or $parts[0] ne 'IVP' or + ! defined $parts[1] or ! defined $parts[2] or + $parts[2] eq $todayDate ) { + next; + } + #logResponse( 'DFS', 'GENERIC_RESPONSE', "ZIPPING: $file\n" ); + $zipOut = `gzip $fqFile 2>&1`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DF', 'GENERIC_RESPONSE', "Unable to gzip $fqFile, rc: $rc, out: $out" ); + } else { + $zipped++; + } + } + } + + # Prune any gzipped files that are too old. + $out = `ls -1 /var/log/xcat/ivp/*.log.gz`; + $rc = $?; + if ( $rc == 0 ) { + @lines = split( '\n', $out ); + foreach my $fqFile ( @lines ) { + chomp( $fqFile ); + my ($file) = $fqFile =~ /\/var\/log\/xcat\/ivp\/(.*)/; + my @parts = split( '_', $file ); + if ( ! defined $parts[0] or $parts[0] ne 'IVP' or + ! defined $parts[1] or ! defined $parts[2] or + $parts[2] ge $todayDate ) { + next; + } + if ( $parts[2] ge $pruneDate ) { + next; + } + + #logResponse( 'DFS', 'GENERIC_RESPONSE', "REMOVING: $file\n" ); + $zipOut = `rm $fqFile 2>&1`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DF', 'GENERIC_RESPONSE', "Unable to remove \'rm $fqFile\', rc: $rc, out: $out" ); + } else { + $removed++; + } + } + } + + logResponse( 'DFS', '*NONFORMATTED*', "IVP log pruning completed, zipped: $zipped, pruned: $removed." ); + return; +} + + +#------------------------------------------------------- + +=head3 removeIVP + + Description : Remove an automated IVP. + Arguments : ID of the IVP to be removed + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = removeIVP(); + +=cut + +#------------------------------------------------------- +sub removeIVP { + my ( $id ) = @_; + my $cmd; + my $out; + my $rc; + my $ivpId; + my %ids; + my $junk; + + # Get the list of IVP ids in the zvmivp table. + $cmd = '/opt/xcat/sbin/tabdump zvmivp | grep -v "^#"'; + $out = `$cmd`; + $rc = $?; + if ( $rc ne 0 ) { + logResponse( 'DF', 'GNRL04', $cmd, $rc, $out ); + goto FINISH_removeIVP; + } + my @lines = split( '\n', $out ); + foreach my $line ( @lines ) { + chomp( $line ); + my ($ivpId, $junk) = split( ',', $line, 2 ); + $ivpId =~ s/^\"|\"$//g; # trim quotes from both ends of the string + $ivpId =~ s/^\s+|\s+$//g; # trim spaces from both ends of the string + if ( $ivpId ne '' ) { + $ids{$ivpId} = 1; + } + } + + # Validate the specified id. + if ( $id eq '' or ! exists $ids{$id} ) { + logResponse( 'DF', 'ID03', $id ); + } + + # Update the table. + logResponse( 'DS', '*NONFORMATTED*', "Updating the z/VM IVP table (zvmivp) to remove $id" ); + my $chtabCmd = "/opt/xcat/sbin/chtab -d id=\'$id\' zvmivp"; + $out = `$chtabCmd`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DS', '*NONFORMATTED*', "\'$chtabCmd\' failed for ID($id), rc: $rc, out: $out\n" ); + $rc = 1; + } + +FINISH_removeIVP: + return $rc; +} + + +#------------------------------------------------------- + +=head3 runDriverScript + + Description : Run a downloaded driver script. + Arguments : This routine is driven from the action + table, verifySets. It takes global + input that can be set as command + input or by precursor routines. + The input variables include: + $driver - Driver script name + $driverLoc - Location of the driver script + $hosts - Infor for defined host nodes + $infoCnt - Count of warnings that were generated + $warningCnt - Count of warnings that were generated + $zxcatParms - Command line parms to + pass to zxcatIVP + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = runDriverScript(); + +=cut + +#------------------------------------------------------- +sub runDriverScript{ + my $infoCnt = 0; + my @lines; + my $out; + my $rc = 0; + my $warningCnt = 0; + + # Analyze the driver script to determine the z/VM host that it intends to support. + my $hostNodeZhcp = ''; + my $hostNode = `cat $driverLoc/$driver | grep "^export zxcatIVP_hostNode\=" | sed '/^export zxcatIVP_hostNode\=*/!d; s///; s/\"//g;q'`; + chomp( $hostNode ); + + if ( $hostNode ne '' ) { + if ( exists $hosts{$hostNode}{'hcp'} ) { + if ( exists $hosts{$hostNode}{'notify'} ) { + $hosts{$hostNode}{'notify'} = $hosts{$hostNode}{'notify'} . 'd'; + } else { + $hosts{$hostNode}{'notify'} = 'd'; + } + $zhcpTarget = $hosts{$hostNode}{'hcp'}; + $zvmHost = $hostNode; + push @ivpSummary, "The ZHCP related to the host, \'$hostNode\', identified in the driver script has been identified ". + "as \'$zhcpTarget\'."; + } else { + $rc = logResponse( 'DFS', 'DRIV03', $zvmHost ); + $notify = 1; + } + } else { + # Driver script did not specify the host node. + $rc = logResponse( 'DFS', 'DRIV02', "$driverLoc/$driver", 'zxcatIVP_hostNode' ); + $notify = 1; + } + + # Analyze the driver script to determine the ZHCP node that it intends to support. + my $zhcpNode = `cat $driverLoc/$driver | grep "^export zxcatIVP_zhcpNode\=" | sed '/^export zxcatIVP_zhcpNode\=*/!d; s///; s/\"//g;q'`; + chomp( $zhcpNode ); + my $zhcpHcp = `/opt/xcat/bin/lsdef $zhcpNode | grep "hcp\=" | sed '/hcp\=*/!d; s///; s/\"//g;q'`; + if ( $zhcpHcp ne '' ) { + $zhcpHcp =~ s/^\s+|\s+$//g; # trim both ends of the string + $zhcpTarget = $zhcpHcp; + foreach $hostNode ( keys %hosts ) { + if ( exists $hosts{$hostNode}{'hcp'} and $hosts{$hostNode}{'hcp'} eq $zhcpHcp ) { + if ( exists $hosts{$hostNode}{'notify'} ) { + $hosts{$hostNode}{'notify'} = $hosts{$hostNode}{'notify'} . 'd'; + } else { + $hosts{$hostNode}{'notify'} = 'd'; + } + } + } + } + + my $exportCmd = ''; + if ( $zxcatParms ne '' ) { + $ENV{zxcatIVP_moreCmdOps}=$zxcatParms; + } else { + delete $ENV{zxcatIVP_moreCmdOps}; + } + + logResponse( 'DF', '*NONFORMATTED*', '*******************************************************************************' ); + logResponse( 'DF', 'GENERIC_RESPONSE_NOINDENT', "Using the driver script to drive the IVP. The output from the run follows." ); + logResponse( 'DF', '*NONFORMATTED*', '*******************************************************************************' ); + $out = `chmod +x $driverLoc/$driver`; + $out = `$driverLoc/$driver`; + $rc = $?; + if ( $rc != 0 ) { + $rc = logResponse( 'DFS', 'DRIV01', "$driverLoc/$driver", $rc, $out ); + $notify = 1; + goto FINISH_runDriverScript; + } + + # Determine how many warnings and information messages were generated so that we can + # produce a summary message upon completion of the IVP. + @lines = split( '\n', $out ); + foreach my $line ( @lines ) { + if ( $line =~ /^Warning \(/ ) { $warningCnt++; } + if ( $line =~ /^Info \(/ ) { $infoCnt++; } + } + push @ivpSummary, "The driver script generated $warningCnt warnings and $infoCnt information messages."; + logResponse( 'DF', '*NONFORMATTED*', $out ); + +FINISH_runDriverScript: + if ( $warningCnt != 0 ) { + $notify = 1; + } + return $rc; +} + + +#------------------------------------------------------- + +=head3 runPrepScript + + Description : Push the appropriate level of preparation + script to a compute node and run it to + validate the system and build the driver + script. + Arguments : Global defaults set as command line parameters provide input + to this routine: + $driver - Name of driver script + $driverLoc - Location of the driver script + $infoCnt - Count of warnings that were generated + $openstackIP - IP for OpenStack compute node + $openstackUser - User for OpenStack compute node + $prepParms - Parms for preparation script + $warnErrCnt - Count of warnings and errors that were generated + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = runPrepScript(); + +=cut + +#------------------------------------------------------- +sub runPrepScript { + my $cmd = ''; + my $infoCnt = 0; + my $line; + my @lines; + my $out = ''; + my $rc = 0; + my $retRC = 0; + my $tmpDir = ''; + my $warningCnt = 0; + + # Determine the name of the driver script based on the IP address for the compute node. + $driver = "zxcatIVPDriver_$openstackIP.sh"; + + # Create the local IVP directory. + if ( !-d $driverLoc ) { + $out = `mkdir -m a=rwx,g=rx,o= -p $driverLoc`; + $rc = $?; + if ( $rc != 0 ) { + $rc = logResponse( 'DFS', 'PREP01', $driverLoc, $rc, $out ); + $notify = 1; + goto FINISH_runPrepScript; + } + } + + # Determine the OpenStack level. + my $level = getOpenStackLevel( $openstackIP, $openstackUser ); + if ( $level eq '' ) { + $notify = 1; + $rc = logResponse( 'DFS', 'PREP06', $openstackIP ); + goto FINISH_runPrepScript; + } + my $levelLetter = substr( $level, 0, 1); + push @ivpSummary, "OpenStack system at $openstackIP is running the Nova $level release."; + + # Determine the appropriate preparation script to send to the compute node. + my $prepDir = '/opt/xcat/share/xcat/tools/zvm'; + my $prepScript = "prep_zxcatIVP_$level.pl"; + logResponse( 'DF', 'GENERIC_RESPONSE', "Attempting to push $prepScript to the OpenStack system ". + "at $openstackIP. It will be used to validate the OpenStack environment and create ". + "a driver script to be used for the validation on the xCAT management node." ); + my $runPrepScript = 1; + + # Create a directory on the compute node to hold the preparation script and driver script. + $cmd = 'mktemp -d /tmp/xCAT.XXXXXXXXXX'; + ($rc, $tmpDir) = sshToNode( $openstackIP, $openstackUser, $cmd ); + if ( $rc != 0 ) { + # Unable to create the directory. Let's see if we have an old driver script that we can use. + $rc = logResponse( 'DFS', 'PREP02', $openstackIP, "$driverLoc/$driver", $cmd, $rc, $out ); + $notify = 1; + goto FINISH_runPrepScript; + } else { + # Push the preparation script to the compute node and run it. + chomp($tmpDir); + $cmd = "/usr/bin/scp $prepDir/$prepScript $openstackUser\@$openstackIP:$tmpDir"; + $out = `/usr/bin/scp "$prepDir/$prepScript" "$openstackUser\@$openstackIP:$tmpDir"`; + $rc = $?; + if ( $rc != 0 ) { + # Unable to push the preparation script. Let's see if we have an old driver script that we can use. + $rc = logResponse( 'DFS', 'PREP02', $openstackIP, "$driverLoc/$driver", $cmd, $rc, $out ); + $notify = 1; + goto FINISH_runPrepScript; + } + } + + if ( $runPrepScript ) { + # Run the preparation script. + ($rc, $out) = sshToNode( $openstackIP, $openstackUser, "chmod +x $tmpDir/$prepScript"); + ($rc, $out) = sshToNode( $openstackIP, $openstackUser, "$tmpDir/$prepScript '--driver' $tmpDir/$driver $prepParms" ); + logResponse( 'DF', '*NONFORMATTED*', '*******************************************************************************' ); + logResponse( 'DF', 'GENERIC_RESPONSE_NOINDENT', "The output from the preparation script that is run on the compute node follows. ". + "The preparation script validates the z/VM related OpenStack configuration file properties." ); + logResponse( 'DF', '*NONFORMATTED*', '*******************************************************************************' ); + logResponse( 'DF', '*NONFORMATTED*', $out ); + + # Determine how many warnings were generated so that we can produce a summary message upon completion of the IVP. + if ( $levelLetter gt 'L' ) { + @lines = split( '\n', $out ); + foreach $line ( @lines ) { + if ( $line =~ /^Warning \(/ ) { $warningCnt++; } + if ( $line =~ /^Info \(/ ) { $infoCnt++; } + } + } else { + @lines = split( '\n', $out ); + foreach $line ( @lines ) { + if ( $line =~ /^Warning:/ ) { $warningCnt++; } + if ( $line =~ /^Info:/ ) { $infoCnt++; } + } + } + push @ivpSummary, "The preparation script generated $warningCnt warnings and $infoCnt information messages."; + + # Pull back the driver script. + if ( -e "$driverLoc/$driver" ) { + $out = `mv -f $driverLoc/$driver $driverLoc/$driver.old`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'S', 'GENERIC_RESPONSE', "Unable to move $driverLoc/$driver to $driverLoc/$driver.old, rc: $rc" ); + $notify = 1; + } + } + + $cmd = "/usr/bin/scp $openstackUser\@$openstackIP:$tmpDir/$driver $driverLoc/$driver"; + $out = `/usr/bin/scp "$openstackUser\@$openstackIP:$tmpDir/$driver" "$driverLoc/$driver"`; + $rc = $?; + if ( $rc != 0 ) { + $rc = logResponse( 'DFS', 'PREP04', "$driverLoc/$driver", $openstackIP, $cmd, $rc, $out ); + $notify = 1; + goto FINISH_runPrepScript; + } + + $cmd = "chmod 660 $driverLoc/$driver 2>&1"; + $out = `$cmd`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'FS', 'PREP07', "$driverLoc/$driver", $cmd, $rc, $out ); + $notify = 1; + } + } + +FINISH_runPrepScript: + if ( !-s "$driverLoc/$driver" && -s "$driverLoc/$driver.old" ) { + # New driver does not exist but old driver exists, use the old one. + $out = `mv -f $driverLoc/$driver.old $driverLoc/$driver`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'S', 'GENERIC_RESPONSE', "Unable to move $driverLoc/$driver.old to $driverLoc/$driver, rc: $rc" ); + } + } + + if ( -s "$driverLoc/$driver" ) { + $retRC = 0; + # Add the driver script to the log file. + $out = `cat $driverLoc/$driver`; + logResponse( 'F', '*NONFORMATTED*', '*******************************************************************************' ); + logResponse( 'F', 'GENERIC_RESPONSE_NOINDENT', "The contents of the driver script used for the rest of this IVP follows. ". + "The driver script is used to run the rest of the IVP on the xCAT Management Node." ); + logResponse( 'F', 'GENERIC_RESPONSE_NOINDENT', 'Note: Any line which set a password related property has been removed.' ); + logResponse( 'F', '*NONFORMATTED*', '*******************************************************************************' ); + $out = `echo "$out" | grep -v "^export zxcatIVP_xcatUserPw"`; + logResponse( 'F', '*NONFORMATTED*', $out ); + } + + if ( $warningCnt != 0 ) { + $notify = 1; + } + + if ( $tmpDir ne '' ) { + $cmd = "rm -Rf $tmpDir 2>&1"; + ($rc, $out) = sshToNode( $openstackIP, $openstackUser, "$cmd"); + $rc = $?; + if ( $rc != 0 ) { + $rc = logResponse( 'DFS', 'CLNUP01', $openstackIP, $tmpDir, $cmd, $rc, $out ); + $notify = 1; + } + } + return $retRC; +} + + +#------------------------------------------------------- + +=head3 scheduleIVP + + Description : Schedule an automated IVP. + Arguments : Global defaults set as command line parameters provide input + to this routine: + $comments - Comments for IVP + $disable - Disable option + $id - ID to update or 'NEW' + $openstackIP - IP for OpenStack compute node + $openstackUser - User for OpenStack compute node + $orchParms - Parms for this script when IVP run + $prepParms - Parms for preparation script + $schedule - Hours to schedule + $scheduledType - Type of IVP + $zxcatParms - Parms for zxcatIVP script + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = scheduleIVP(); + +=cut + +#------------------------------------------------------- +sub scheduleIVP { + my $cmd; + my $out; + my $rc; + my $ivpId; + my %ids; + my $junk; + + # Get the list of IVP ids in the zvmivp table. + $cmd = '/opt/xcat/sbin/tabdump zvmivp | grep -v "^#"'; + $out = `$cmd`; + $rc = $?; + if ( $rc ne 0 ) { + logResponse( 'DF', 'GNRL04', $cmd, $rc, $out ); + goto FINISH_scheduleIVP; + } + my @lines = split( '\n', $out ); + foreach my $line ( @lines ) { + chomp( $line ); + my ($ivpId, $junk) = split( ',', $line, 2 ); + $ivpId =~ s/^\"|\"$//g; # trim quotes from both ends of the string + $ivpId =~ s/^\s+|\s+$//g; # trim spaces from both ends of the string + if ( $ivpId ne '' ) { + $ids{$ivpId} = 1; + } + } + + # Validate the specified id. Either generate a new ID if this is a new + # request (specified as 'new' or omitted or use the id that was passed. + if ( $id eq '' or $id eq 'NEW' ) { + # Generate the new Id + my $i; + for ( $i = 10; $i < 10010; $i++ ) { + if ( ! exists $ids{$i} ) { + last; + } + } + if ( $i <= 10010 ) { + $id = $i; + } else { + # Could not find an available number in the first 10000 IDs. + logResponse( 'DF', 'ID01' ); + goto FINISH_scheduleIVP; + } + } else { + # Validate the Id + if ( ! exists $ids{$id} ) { + logResponse( 'DF', 'ID02', $id ); + } + } + + if ( $scheduledType eq '' ) { + logResponse( 'DF', 'OPER01', '--type' ); + goto FINISH_scheduleIVP; + } + $scheduledType = lc( $scheduledType ); + if ( $scheduledType ne 'basicivp' and $scheduledType ne 'fullivp' ) { + logResponse( 'DF', 'OPER02', '--type', $scheduledType, '\'basicivp\' or \'fullivp\'' ); + goto FINISH_scheduleIVP; + } + if ( $scheduledType eq 'basicivp' ) { + # Ignore any FULLIVP parms + $openstackIP = ''; + $openstackUser = ''; + $prepParms = ''; + } + + # Normalize the schedule. + my %hours; + my @parts = split( ' ', $schedule ); + foreach my $hour ( @parts ) { + $hour =~ s/^\s+|\s+$//g; # trim spaces from both ends of the string + $hours{$hour} = 1; + } + $schedule = ''; + for ( my $hour = 0; $hour <= 23; $hour++ ) { + if ( exists $hours{$hour} ) { + $schedule = "$schedule $hour"; + delete $hours{$hour}; + } + } + $schedule =~ s/^\s+|\s+$//g; # trim spaces from both ends of the string + my (@leftOver) = keys %hours; + if ( scalar @leftOver != 0 ) { + my $badValues = join( '\' and \'', @leftOver ); + logResponse( 'DF', 'OPER02', '--schedule', $badValues, '0-23' ); + goto FINISH_scheduleIVP; + } + + if ( $comments =~ /,/ ) { + $comments =~ s/,//g; # trim commas from the string + } + + my $disableFlag = ''; + if ( $disable == 1 ) { + $disableFlag = 'YES'; + } + + # Update the table. + logResponse( 'DS', '*NONFORMATTED*', "Updating the z/VM IVP table (zvmivp) for $id" ); + my $chtabCmd = "/opt/xcat/sbin/chtab id=\'$id\' ". + "zvmivp.ip=\'$openstackIP\' ". + "zvmivp.schedule=\'$schedule\' ". + "zvmivp.last_run=\'\' ". + "zvmivp.type_of_run=\'$scheduledType\' ". + "zvmivp.access_user=\'" . hexEncode( $openstackUser ) . "\' ". + "zvmivp.orch_parms=\'" . hexEncode( $orchParms ) . "\' ". + "zvmivp.prep_parms=\'" . hexEncode( $prepParms ) . "\' ". + "zvmivp.main_ivp_parms=\'" . hexEncode( $zxcatParms ) . "\' ". + "zvmivp.comments=\'" . hexEncode( $comments ) . "\' ". + "zvmivp.disable=\'$disableFlag\'"; + $out = `$chtabCmd`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DS', '*NONFORMATTED*', "\'$chtabCmd\' failed for ID($id), rc: $rc, out: $out\n" ); + $rc = 1; + } + +FINISH_scheduleIVP: + return $rc; +} + +#------------------------------------------------------- + +=head3 setupLogFile + + Description : Set up the log file for this run. + Arguments : None + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = setupLogFile(); + +=cut + +#------------------------------------------------------- +sub setupLogFile { + my $out; + my $rc = 0; + my $ipString = ''; + + # Indicate that the log file will need to be finished. + # This is a safety in case something goes wrong during + # the IVP run and causes the program to break. + $needToFinishLogFile = 1; + + # Determine the IP address to associate with the log file. + if ( $openstackIP eq '' ) { + # Must be doing either a generic zxcatIVP or a full + # IVP for the compute node sharing the system with this + # xCAT MN. Use the string "XCAT". + $ipString = 'XCAT'; + } else { + if ( exists $localIPs{$openstackIP} ) { + $ipString = 'XCAT'; + } else { + $ipString = $openstackIP; + } + } + + # Determine the timestamp to use for the log. + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time); + $startTime = sprintf("%02d:%02d:%02d on %04d-%02d-%02d", + $hour, $min, $sec, $year + 1900, $mon + 1, $mday ); + my $currTime = sprintf("%04d-%02d-%02d_%02d:%02d:%02d", + $year + 1900, $mon + 1, $mday, $hour, $min, $sec); + + # If the log directory does not exist then create it. + if ( !-d $logDir ) { + # Directory did not exist. Create it. + $out = `mkdir -p $logDir`; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DFS', 'GENERIC_RESPONSE', 'Failed to set up the log directory. IVP will run without a log file.' ); + $rc = 0; # Continue processing. + goto FINISH_setupLogFile; + } + } + + # Create the log file. + $logFile = "IVP\_$ipString\_$currTime\.log"; + open $logFileHandle, ">", "$logDir/$logFile"; + $rc = $?; + if ( $rc != 0 ) { + logResponse( 'DFS', 'GENERIC_RESPONSE', "Failed to open the log file: $logFile. IVP will run without a log file. $!" ); + $rc = 0; # Continue processing. + goto FINISH_setupLogFile; + } + $out = `chmod 644 "$logDir/$logFile"`; + + push @ivpSummary, "The following logfile was created for this run: $logFile"; + +FINISH_setupLogFile: + return $rc; +} + + +#------------------------------------------------------- + +=head3 showCILevel + + Description : Show the cloud-init version installed + in the node. + Arguments : None + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = showCILevel(); + +=cut + +#------------------------------------------------------- +sub showCILevel { + my $ciLevel = ''; + my $out = ''; + my $rc = 0; + + # Attempted to obtain the version using the routine + # accessible through the $PATH variable. + ($rc, $out) = sshToNode( $nodeName, '', 'cloud-init --version 2>&1'); + if ( $rc == 255 ) { + logResponse( 'DF', 'GENERIC_RESPONSE', $out ); + goto FINISH_showCILevel; + } elsif ( $rc == 0 ) { + if ( $out =~ /^cloud-init / ) { + ($ciLevel) = $out =~ /^cloud-init (.*)/; + my $msgText = "Version of cloud-init: $ciLevel"; + logResponse( 'DF', 'GENERIC_RESPONSE', $msgText ); + # Add code to compare the version to what is available in xcat. + goto FINISH_showCILevel; + } + } + + # Attempt to locate the version in the various cloud-init scripts + # in the target node. + # Add support to look for versions on the node if the invocation + # using the path did not show the version. + +FINISH_showCILevel: + return $rc; +} + + +#------------------------------------------------------- + +=head3 showHelp + + Description : Show the help inforamtion. + Arguments : None. + Returns : None. + Example : showHelp(); + +=cut + +#------------------------------------------------------- +sub showHelp{ + print "$0 verify the capabilities of a node.\n\n"; + print $usage_string; + return; +} + + +#------------------------------------------------------- + +=head3 sshToNode + + Description : SSH to a node and issue a command. + Check for SSH errors. + Arguments : Node name or IP address/DNS name + user if we need to specify SUDO + Command to issue + Options (char string): + q - Quiet, Don't generate an error message on failure + Returns : Return code: + 0 - Normal Linux success + 255 - Unable to SSH to system + non-zero - command error + Output from the command or a error string on an SSH failure. + Example : ($rc, $out) = sshToNode( $nodeName, $user, $command ); + +=cut + +#------------------------------------------------------- +sub sshToNode{ + my ( $nodeName, $user, $cmd, $options ) = @_; + my $rc = 0; + my $out = ''; + + if ( ! defined $options ) { + $options = ''; + } + + if ( $user eq '' ) { + $out = `ssh $nodeName -qoBatchMode=yes '$cmd'`; + $rc = $? >> 8; + } else { + $out = `ssh $user\@$nodeName -qoBatchMode=yes '$cmd'`; + $rc = $? >> 8; + } + + if ( $rc == 255 and $options !~ /q/ ) { + logResponse( 'DFS', 'VSTN01', $nodeName ); # Keep $rc = 255. + $out = "Unable to SSH to $nodeName"; + } + + return ($rc, $out); +} + + +#------------------------------------------------------- + +=head3 verifyAccess + + Description : Verify the xCAT MN can access a system. + Arguments : Node name or IP address + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = verifyAccess(); + +=cut + +#------------------------------------------------------- +sub verifyAccess{ + my ( $nodeOrIP ) = @_; + + my $rc = 0; + my $out = ''; + + ($rc, $out) = sshToNode( $nodeOrIP, '', 'pwd 2>/dev/null' ); + goto FINISH_verifyAccess if ( $rc == 255 ); # Already generated a message + if ( $rc != 0 ) { + $rc = logResponse( 'DFS', 'VA01' ); + } + +FINISH_verifyAccess: + return $rc; +} + + +#------------------------------------------------------- + +=head3 verifyBasicXCAT + + Description : Verify the basic setup of xCAT. + Arguments : None + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = verifyBasicXCAT(); + +=cut + +#------------------------------------------------------- +sub verifyBasicXCAT{ + my $infoCnt = 0; + my @lines; + my $rc = 0; + my $warningCnt = 0; + + $rc = logResponse( 'DF', 'GENERIC_RESPONSE', 'The IVP will be invoked with BYPASS messages suppressed. '. + 'This is because a basic IVP run will produce a number of BYPASS messages due to the fact that '. + 'it is not driven with a full set of configuration operands causing it to avoid some tests. '. + 'Instead you will see an information line indicating that the message was ignored.' ); + $ENV{'zxcatIVP_bypassMsg'} = 0; # No bypass messages + $ENV{'zxcatIVP_moreCmdOps'} = $zxcatParms; + my $out = `/opt/xcat/bin/zxcatIVP.pl`; + + $rc = logResponse( 'DF', '*NONFORMATTED*', $out ); + # Determine how many warnings were generated so that we can produce a summary message upon completion of the IVP. + @lines = split( '\n', $out ); + foreach my $line ( @lines ) { + if ( $line =~ /^Warning/ ) { $warningCnt++; } + if ( $line =~ /^Info/ ) { $infoCnt++; } + } + push @ivpSummary, "The zxcatIVP.pl script generated $warningCnt warnings and $infoCnt information messages."; + +FINISH_verifyBasicXCAT: + if ( $warningCnt != 0 ) { + $notify = 1; + } + return $rc; +} + + +#------------------------------------------------------- + +=head3 verifyDistro + + Description : Verify that the distro is supported. + Arguments : None + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = verifyDistro(); + +=cut + +#------------------------------------------------------- +sub verifyDistro{ + my $rc = 0; + + my $supported = xCAT::zvmUtils->isOSVerSupported( $tgtOS ); + if ( !$supported ) { + $rc = logResponse( 'DF', 'VD01', ($tgtOS) ); + } + + return $rc; +} + + +#------------------------------------------------------- + +=head3 verifyLogResponse + + Description : Script test function to test functioning + of the logResponse() routine. + Arguments : None + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = verifyLogResponse(); + +=cut + +#------------------------------------------------------- +sub verifyLogResponse{ + my $rc = 0; + my $sub2 = 'sub2'; + + $rc = logResponse( 'DF', 'VX01' ); + print "rc: $rc\n"; + logResponse( 'DF', 'VPF01', ($nodeName, 0, 'result message') ); + $rc = logResponse( 'DF', 'VX03', $nodeName, $sub2); + print "rc: $rc\n"; + $rc = logResponse( 'DF', 'VX03', $nodeName, 'sub2a'); + print "rc: $rc\n"; + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyPower + + Description : Verify the node is powered on. + Arguments : None + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = verifyPower(); + +=cut + +#------------------------------------------------------- +sub verifyPower { + my ( $nodeName ) = @_; + my $rc = 0; + my $powerState; + + my $out = `/opt/xcat/bin/rpower $nodeName stat 2>&1`; + $rc = $?; + if ( $rc != 0 ) { + $rc = logResponse( 'DF', 'VPF01', ($nodeName, $rc, $out) ); + goto FINISH_verifyPower if ( $rc > 0 ); + } + + $out = lc( $out ); + if ( $out =~ /$nodeName: / ) { + chomp $out; + ($powerState) = $out =~ /$nodeName: (.*)/; + } + + if ( $powerState ne 'on' ) { + $rc = logResponse( 'DFS', 'VP02', ($nodeName) ); + } + +FINISH_verifyPower: + return $rc; +} + + +#------------------------------------------------------- + +=head3 verifyService + + Description : Verify that specified services are running. + Arguments : Node name or IP address + Blank delimitted list of service names + service name begins with the name and + has a comma separated list of run levels + which are to be verified as 'on' + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = verifyService(); + +=cut + +#------------------------------------------------------- +sub verifyService { + my ( $nodeOrIP, $namesAndLevels ) = @_; + my @serviceList = split ( / /, $namesAndLevels ); + + my $rc = 0; + my $serviceOut = ''; + + # Get the list of service names and run levels into an array. + + # Get the list of configured services. The output looks similar to: + # service_name 0:off 1:off 2:off 3:off 4:off 5:off 6:off + ($rc, $serviceOut) = sshToNode( $nodeOrIP, '', "chkconfig --list 2>&1" ); + goto FINISH_verifyService if ( $rc == 255 ); + + # Look for missing services + my @missingService; + my @foundService; + my @nonrunningService; + foreach my $serviceInfo ( @serviceList ) { + my @offLevels; + my $levels; + my @serviceRunLevels = split( /,/, $serviceInfo ); + my $service = shift @serviceRunLevels; + ($levels) = $serviceOut =~ /^$service(.*)\s(.*)\n/m; + $levels =~ s/^\s+|\s+$//g if ( defined $levels ); + if (( defined $levels ) and ( $levels ne '' )) { + # Verify the run levels are enabled. + foreach my $level ( @serviceRunLevels ) { + if ( $levels !~ /$level:on/ ) { + push @offLevels, $level; + } + } + if ( @offLevels ) { + $levels = join(", ", @offLevels); + $rc = logResponse( 'DF', 'VS06', $service, $levels ); + goto FINISH_verifyService if ( $rc > 0 ); + } else { + push @foundService, $service; + } + } else { + push @nonrunningService, $service; + } + } + + if ( @foundService ) { + my $list = join(", ", @foundService); + $rc = logResponse( 'DF', 'GENERIC_RESPONSE', "The following services are configured to start: $list" ); + goto FINISH_verifyService if ( $rc > 0 ); + } + if ( @nonrunningService ) { + my $list = join(", ", @nonrunningService); + $rc = logResponse( 'DF', 'VS05', $list ); + goto FINISH_verifyService if ( $rc > 0 ); + } + +FINISH_verifyService: + return $rc; +} + + +#------------------------------------------------------- + +=head3 verifyXcatconf4z + + Description : Verify xcatconf4z is properly installed + and is the correct version. + Arguments : None + Returns : 0 - No error + non-zero - Terminating error detected. + Example : $rc = verifyXcatconf4z(); + +=cut + +#------------------------------------------------------- +sub verifyXcatconf4z{ + my ( $nodeOrIP ) = @_; + my $rc = 0; + my $out; + my ($mnVersion, $tgtVersion); + + # Verify service is installed + $rc = verifyService( $nodeOrIP, 'xcatconf4z,2,3,5' ); + goto FINISH_verifyXcatconf4z if ( $rc == 255 ); + + # Get the xCAT MN's xcatconf4z version level. + $out = `/opt/xcat/share/xcat/scripts/xcatconf4z version`; + if ( $out =~ /xcatconf4z version: / ) { + chomp $out; + ($mnVersion) = $out =~ /xcatconf4z version: (.*)/; + } else { + $rc = logResponse( 'DFS', 'VX01' ); + goto FINISH_verifyXcatconf4z if ( $rc > 0 ); + } + + # Verify that the node contains xcatconf4z in the correct location and is executable. + #$out = `ssh $nodeOrIP 'ls -al /opt/xcatconf4z 2>&1'`; + ($rc, $out) = sshToNode( $nodeOrIP, '', 'ls -al /opt/xcatconf4z 2>&1'); + goto FINISH_verifyXcatconf4z if ( $rc == 255 ); + if ( $out =~ /No such file or directory/ ) { + $rc = logResponse( 'DF', 'VX06' ); + goto FINISH_verifyXcatconf4z if ( $rc > 0 ); + } else { + # Verify that it is executable + } + + # Get the node's xcatconf4z version level. + #$out = `ssh $nodeOrIP '/opt/xcatconf4z version'`; + ($rc, $out) = sshToNode( $nodeOrIP, '', '/opt/xcatconf4z version'); + goto FINISH_verifyXcatconf4z if ( $rc == 255 ); + if ( $out =~ /xcatconf4z version: / ) { + chomp $out; + ($tgtVersion) = $out =~ /xcatconf4z version: (.*)/; + } else { + $rc = logResponse( 'DF', 'VX02' ); + goto FINISH_verifyXcatconf4z if ( $rc > 0 ); + } + + # Verify the version is up to date. + if ( defined $tgtVersion ) { + if ( $mnVersion > $tgtVersion ) { + $rc = logResponse( 'DF', 'VX03', ($tgtVersion, $mnVersion) ); + goto FINISH_verifyXcatconf4z if ( $rc > 0 ); + } + } + + # Verify that authorized senders is setup. + #$out = `ssh $nodeOrIP '/opt/xcatconf4z status'`; + ($rc, $out) = sshToNode( $nodeOrIP, '', '/opt/xcatconf4z status'); + goto verifyXcatconf4z if ( $rc == 255 ); + if ( $out =~ /xcatconf4z is disabled / ) { + $rc = logResponse( 'DF', 'VX04' ); + goto FINISH_verifyXcatconf4z if ( $rc > 0 ); + } else { + # Get list of authorized users and put out an info message. + } + + # Verify that mkisofs is available + ($rc, $out) = sshToNode( $nodeOrIP, '', 'stat /opt/bin/mkisofs'); + goto verifyXcatconf4z if ( $rc == 255 ); + if ( $rc != 0 ) { + $rc = logResponse( 'DF', 'VX07' ); + goto FINISH_verifyXcatconf4z if ( $rc > 0 ); + } + +FINISH_verifyXcatconf4z: + return $rc; +} + + +#***************************************************************************** +# Main routine +#***************************************************************************** +my $ignoreOpt; +my $rc = 0; +my $thisScript = $0; +my $out; + +# Parse the arguments +$Getopt::Long::ignorecase = 0; +Getopt::Long::Configure( "bundling" ); +if (!GetOptions( + 'a|access' => \$verifyAccess, + 'basicivp' => \$runBasicIVP, + 'capturereqs' => \$verifyCaptureReqs, + 'c|cloudinit' => \$verifyCloudInit, + 'comments=s' => \$comments, + 'cron' => \$runCron, + 'decode=s' => \$decode, + 'disable' => \$disable, + 'enable' => \$enable, + 'encode=s' => \$encode, + 'fullivp' => \$runFullIVP, + 'h|help' => \$displayHelp, + 'i|id=s' => \$id, + 'ignore=s' => \$ignoreOpt, + 'n|node=s' => \$nodeName, + 'notify' => \$notifyOnErrorOrWarning, + 'openstackuser=s' => \$openstackUser, + 'openstackip=s' => \$openstackIP, + 'orchparms=s' => \$orchParms, + 'prepparms=s' => \$prepParms, + 'remove' => \$remove, + 'schedule=s' => \$schedule, + 'type=s' => \$scheduledType, + 'v|version' => \$versionOpt, + 'x|xcatconf4z' => \$verifyXcatconf4z, + 'zxcatparms=s' => \$zxcatParms, + )) { + print $usage_string; +} + +if ( $versionOpt ) { + logResponse( 'DF', 'GENERIC_RESPONSE_NOINDENT', "Version: $version\n" ); +} + +if ( $displayHelp ) { + showHelp(); +} + +if ( $displayHelp or $versionOpt ) { + goto FINISH_main; +} + +# Convert any encoded operand values back to their unencoded value. +my @convertibleParms = ( 'comments', 'nodeName', 'openstackUser', 'orchParms', 'prepParms', 'zxcatParms' ); +foreach my $parmName ( @convertibleParms ) { + eval( "\$$parmName = hexDecode( \$$parmName )" ); +} + +if ( $schedule eq '' and $orchParms ne '' ) { + # If not scheduling an IVP run and orchestrator parms are present then use them. + my $unrecognizedOps = ''; + $rc = GetOptionsFromString( + $orchParms, + 'ignore=s' => \$ignoreOpt, + ); + if ( $rc == 0 ) { + print "Unrecognized option in --orchParms. ". + "Only --ignore is allowed as an orchestrator option for an immediate run.\n"; + $rc = 2; + goto FINISH_main; + } +} + +# Handle messages to ignore. +if ( defined( $ignoreOpt ) ) { + # Make hash from the specified ignore operands + $ignoreOpt = uc( $ignoreOpt ); + my @ingoreList; + if ( $ignoreOpt =~ ',' ) { + @ingoreList = split( ',', $ignoreOpt ); + } else { + @ingoreList = split( ' ', $ignoreOpt ); + } + + %msgsToIgnore = map { $_ => 1 } @ingoreList; + + # Convert general severity type operands to their numeric value. + if ( $msgsToIgnore{'BYPASS'} ) { + delete $msgsToIgnore{'BYPASS'}; + $msgsToIgnore{'2'} = 1; + } + if ( $msgsToIgnore{'INFO'} ) { + delete $msgsToIgnore{'INFO'}; + $msgsToIgnore{'3'} = 1; + } + if ( $msgsToIgnore{'WARNING'} ) { + delete $msgsToIgnore{'WARNING'}; + $msgsToIgnore{'4'} = 1; + } + if ( $msgsToIgnore{'ERROR'} ) { + delete $msgsToIgnore{'ERROR'}; + $msgsToIgnore{'5'} = 1; + } +} + +if ( $openstackIP ne '' ) { + $openstackIP = uc( $openstackIP ); +} + +# Handle the combinations of disable and enable options. +if ( $disable eq '' ) { + $disable = 0; +} +if ( $enable eq '' ) { + $enable = 0; +} +if ( $disable == 1 and $enable == 1 ) { + $rc = logResponse( 'DFS', 'OPER03', '--disable and --enable' ); + goto FINISH_main; +} + +# Determine the information about the xCAT managed node and the z/VM +# host environment where it is running (if running on z/VM). +getMNInfo(); + +if ( $decode ne '' or + $disable or + $enable or + $encode ne '' or + $runFullIVP or + $runCron or + $runBasicIVP or + $remove or + $schedule ne '' ) { + # IVP runs do not need the node. +} elsif ( $nodeName ne '' ) { + $nodeName = lc( $nodeName ); +} else { + $rc = logResponse( 'DFS', 'OPER01', '-n or --node' ); + goto FINISH_main; +} + +($::SUDOER, $::SUDO) = xCAT::zvmUtils->getSudoer(); + +# Setup for a test run of logResponse. Normally, commented out. +#($verifyAccess, $verifyCaptureReqs, $verifyCloudInit, $verifyXcatconf4z) = 0; +#my $test = 1; + +# Run the selected tests +foreach my $verify (keys %verifySets) { + if ( eval $verify ) { + if ( $verifySets{$verify}[0] ne '' ) { + logResponse( 'D', 'GENERIC_RESPONSE_NOINDENT', "\n********************************************************************\n" ); + logResponse( 'D', 'GENERIC_RESPONSE_NOINDENT', "$verifySets{$verify}[0]\n" ); + logResponse( 'D', 'GENERIC_RESPONSE_NOINDENT', "********************************************************************\n" ); + } + my $size = @{$verifySets{$verify}}; + logResponse( 'D', '*RESET*' ); + for (my $i = 1; $i < $size; $i++) { + ($rc) = eval $verifySets{$verify}[$i]; + if ( ! defined $rc ) { + logResponse( 'DFS', 'PERL01', $thisScript, $verifySets{$verify}[$i], $@ ); + $rc = 666; + last; + } + + if ( $rc > 0 ) { + last; + } + } + + if ( $needToFinishLogFile ) { + finishLogFile(); + } + + if ( $warnErrCnt == 0 ) { + logResponse( 'DF', 'GENERIC_RESPONSE_NOINDENT', "\nProcessing completed." ); + } else { + logResponse( 'DF', 'GENERIC_RESPONSE_NOINDENT', "\nProcessing completed with warning or error messages. " . + "See previous messages for information.\n" ); + } + } +} + +FINISH_main: +exit $rc; \ No newline at end of file diff --git a/xCAT-client/bin/zvmMsg b/xCAT-client/bin/zvmMsg new file mode 100644 index 000000000..d7b451c26 --- /dev/null +++ b/xCAT-client/bin/zvmMsg @@ -0,0 +1,113 @@ +#!/usr/bin/perl +# IBM(c) 2016 EPL license http://www.eclipse.org/legal/epl-v10.html +#------------------------------------------------------- + +=head1 + + Provides more information related to an xCAT message + for z/VM. These are messages which are similar to the + following: + Warning (ZXCATIVP:VN01) Network VSW3 was not found as a network. + These message were originally added for the z/VM IVP and + are in the form: + (group:msgId) + + The information provided consists of: + Explanation of the message, + System Action, and + User Action. + +=cut + +#------------------------------------------------------- +package xCAT::zvmMsgs; +BEGIN +{ + $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; +} + +$XML::Simple::PREFERRED_PARSER='XML::Parser'; + +use Getopt::Long; +use lib "$::XCATROOT/lib/perl"; +use Text::Wrap; +use xCAT::zvmMsgs; +use strict; +use warnings; +1; + +# set the usage message +my $usage_string = "Usage:\n + $0 -h + $0 --help + $0 --group --id \n + Use this script to display extra information for messages related + to z/VM that have the format: + (:) + for example: + Warning (ZXCATIVP:VN01) Network VSW3 was not found.\n + The following options are supported: + --group + specified the identifier of the group to which the message belongs. + The group id is the first part of the identifer, e.g. + 'Warning (ZXCATIVP:VDS01)' belongs to the 'ZXCATIVP' group. + The group id is case sensitive. This operand should be specified + along with the --id operand. + -h | --help + Displays usage information. + --id + specifies the message identifier. The message id is the second part + of the identifier, e.g. 'Warning (ZXCATIVP:VDS01)' where 'VDS01' + is the message id. The message identifier is case sensitive. This + operand should be specified along with the --group operand to fully + identify the message.\n"; + +#***************************************************************************** +# Main routine +#***************************************************************************** +# Parse the arguments +my $displayHelp = 0; +my $group = ''; +my $msgId = ''; +$Getopt::Long::ignorecase = 0; +Getopt::Long::Configure( 'bundling' ); + +if ( !GetOptions( + 'h|help' => \$displayHelp, + 'group=s' => \$group, + 'id=s' => \$msgId, + )) { + print $usage_string; + goto FINISH; +} + +if ( $displayHelp ) { + print $usage_string; + goto FINISH; +} + +if ( $group eq '' ) { + print "--group operand was not specified.\n"; + print $usage_string; +} elsif ( $msgId eq '' ) { + print "--id operand was not specified.\n"; + print $usage_string; +} else { + my $extraInfo; + my $msg; + my $recAction; + my $sev; + ( $recAction, $sev, $msg, $extraInfo ) = xCAT::zvmMsgs->buildMsg( $group, $msgId ); + if ( $msg =~ ': Message was not found!' ) { + # Oops, we could not find the message. + print "$msg\n"; + } else { + if ( $extraInfo ne '' ) { + print "Additional information for message $msgId in group $group:\n"; + print "$extraInfo\n"; + } + } +} + +FINISH: +exit; \ No newline at end of file diff --git a/xCAT-client/bin/zxcatCopyCloneList.pl b/xCAT-client/bin/zxcatCopyCloneList.pl new file mode 100644 index 000000000..defab5cbc --- /dev/null +++ b/xCAT-client/bin/zxcatCopyCloneList.pl @@ -0,0 +1,353 @@ +#!/usr/bin/perl +############################################################################### +# IBM (C) Copyright 2015, 2016 Eclipse Public License +# http://www.eclipse.org/org/documents/epl-v10.html +############################################################################### +# COMPONENT: zxcatCopyCloneList.pl +# +# This is a program to copy the "DOCLONE COPY" file from the 193 disk to +# /opt/xcat/doclone.txt +############################################################################### + +use strict; +#use warnings; +use Capture::Tiny ':all'; +use Getopt::Long; + +my $file_location = '/var/opt/xcat/'; +my $source_file = 'DOCLONE.COPY'; +my $file_name = 'doclone.txt'; +my $tempPattern = 'doclone.XXXXXXXX'; +my $source_vdev = '193'; +my $version = "1.0"; +my $out; +my $err; +my $returnvalue; +my $displayHelp = 0; # Display help information +my $versionOpt = 0; # Show version information flag + +my $usage_string = "This script copies the DOCLONE COPY from the MAINT 193 +to the $file_location$file_name\n\n + Usage:\n + $0 [ -v ] + $0 [ -h | --help ]\n + The following options are supported:\n + -h | --help Display help information\n + -v Display the version of this script.\n"; + +# Copied this routine from sspmodload.pl +# This will get the Linux address of the vdev +sub get_disk($) +{ + my ($id_user) = @_; + my $id = hex $id_user; + my $hex_id = sprintf '%x', $id; + my $completed = 1; + my $dev_path = sprintf '/sys/bus/ccw/drivers/dasd-eckd/0.0.%04x', $id; + if (!-d $dev_path) { + $dev_path = sprintf '/sys/bus/ccw/drivers/dasd-fba/0.0.%04x', $id; + } + if (!-d $dev_path) { + print "(Error) Unable to find a path to the $source_vdev in /sys/bus/ccw/drivers/\n"; + } + -d $dev_path or return undef; + + #offline the disk so that a new online will pick up the current file + my @sleepTimes = ( 1, 2, 3, 5, 8, 15, 22, 34, 60); + system("echo 0 > $dev_path/online"); + + my $dev_block = "$dev_path/block"; + + #wait if the disk directory is still there + if (-d $dev_block) { + $completed = 0; + foreach (@sleepTimes) { + system("echo 0 > $dev_path/online"); + sleep $_; + if (!-d $dev_block) { + $completed = 1; + last; + } + } + } + + if (!$completed) { + print "(Error) The 193 disk failed to complete the offline!\n"; + return undef; + } + + system("echo 1 > $dev_path/online"); + # Bring the device online if offline + if (!-d $dev_block) { + $completed = 0; + foreach (@sleepTimes) { + system("echo 1 > $dev_path/online"); + sleep $_; + if (-d $dev_block) { + $completed = 1; + last; + } + } + if (!$completed) { + print "(Error) The 193 disk failed to come online!\n"; + return undef; + } + } + if (opendir(my $dir, $dev_block)) { + my $dev; + while ($dev = readdir $dir) { + last if (!( $dev eq '.' || $dev eq '..' ) ); + } + closedir $dir; + if (!defined $dev) { + print "(Error) undefined $dev\n"; + } + defined $dev ? "/dev/$dev" : undef; + } else { + print "(Error) Unable to opendir $dev_block\n"; + return undef; + } +} + +# *********************************************************** +# Mainline. Parse any arguments, usually no arguments +$Getopt::Long::ignorecase = 0; +Getopt::Long::Configure( "bundling" ); + +GetOptions( + 'h|help' => \$displayHelp, + 'v' => \$versionOpt ); + +if ( $versionOpt ) { + print "Version: $version\n"; + exit 0; +} + +if ( $displayHelp ) { + print $usage_string; + exit 0; +} + +my $tempFileName = ''; +my $rc = 0; +my $oldFileExists = 0; +my $dev = get_disk($source_vdev); +if (defined($dev)) { + # make sure directory exists + if (!-d $file_location) { + $returnvalue = mkdir "$file_location", 0755; + if (!$returnvalue) { + print "(Error) mkdir $file_location failed with errno:$!"; + $rc = 1; + goto MAIN_EXIT; + + } + } + my $oldFiletime; + # Create a temp file name to use while validating + $tempFileName = `/bin/mktemp -p $file_location $tempPattern`; + chomp($tempFileName); + # if we are overwriting an existing file, save time stamp + if (-e "$file_location$file_name") { + # stat will return results in $returnvalue + ( $out, $err, $returnvalue ) = eval { capture { `stat \'-c%y\' $file_location$file_name` } }; + chomp($out); + chomp($err); + chomp($returnvalue); + if (length($err) > 0) { + print "(Error) Cannot stat the $file_location$file_name\n$err\n"; + $rc = 1; + goto MAIN_EXIT; + } + $oldFileExists = 1; + $oldFiletime = $returnvalue; + } + + ( $out, $err, $returnvalue ) = eval { capture { `/sbin/cmsfscp -d $dev -a $source_file $tempFileName` } }; + chomp($out); + chomp($err); + chomp($returnvalue); + if (length($err) > 0) { + # skip any blksize message for other blksize + if ($err =~ 'does not match device blksize') { + } else { + print "(Error) Cannot copy $source_file\n$err\n"; + $rc = 1; + goto MAIN_EXIT; + } + } + + if ($oldFileExists == 1) { + ( $out, $err, $returnvalue ) = eval { capture { `stat \'-c%y\' $tempFileName` } }; + chomp($out); + chomp($err); + chomp($returnvalue); + if (length($err) > 0) { + print "(Error) Cannot stat the $tempFileName\n$err\n"; + $rc = 1; + goto MAIN_EXIT; + } + if ($oldFiletime eq $returnvalue) { + print "The $source_file copied to temporary file $tempFileName is the same time stamp as original.\n"; + } else { + print "$source_file copied to temporary file $tempFileName successfully\n"; + } + } else { + print "$source_file copied to temporary file $tempFileName successfully\n"; + } + + print "Validating $tempFileName contents for proper syntax...\n"; + if (-f "$tempFileName") { + $out = `cat $tempFileName`; + } else { + print "(Error) Missing temporary file: $tempFileName\n"; + $rc = 1; + goto MAIN_EXIT; + } + my @lines = split('\n',$out); + my %hash = (); + my %imagenames = (); + my $count = @lines; + if ($count < 1) { + print "(Error) $tempFileName does not have any data.\n"; + ( $out, $err, $returnvalue ) = eval { capture { `rm $tempFileName` } }; + chomp($out); + chomp($err); + chomp($returnvalue); + if (length($err) > 0) { + print "(Error) Cannot erase temporary file $tempFileName $err\n"; + } + $rc = 1; + goto MAIN_EXIT; + } + + # loop for any lines found + for (my $i=0; $i < $count; $i++) { + # skip comment lines, * or /* + if ( $lines[$i] =~ '^\s*[\*]') { + next; + } + if ( $lines[$i] =~ '^\s*/[*]') { + next; + } + # is this a blank line? if so skip it + if ($lines[$i] =~/^\s*$/) { + next; + } + my $semicolons = $lines[$i] =~ tr/\;//; + if ($semicolons < 3) { + print "(Error) Semicolons need to end each key=value on line ".($i+1)."\n"; + $rc = 1; + } + + %hash = ('IMAGE_NAME' => 0,'CLONE_FROM' => 0,'ECKD_POOL' => 0, 'FBA_POOL' => 0 ); + # IMAGE_NAME=imgBoth; CLONE_FROM=testFBA; ECKD_POOL=POOLECKD; FBA_POOL=POOLFBA + my @parms = split( ';', $lines[$i]); + my $parmcount = @parms; + # get the key and value for this item, store in hash + for (my $j=0; $j < $parmcount; $j++) { + # if this token is all blanks skip it. Could be reading blanks at the end of the line + if ($parms[$j] =~ /^\s*$/) { + next; + } + my $parmlength = length($parms[$j]); + my @keyvalue = split('=', $parms[$j]); + my $key = $keyvalue[0]; + $key =~ s/^\s+|\s+$//g; # get rid of leading and trailing blanks + + if ( length( $key ) == 0 ) { + print "(Error) Missing keyword on line ".($i+1)."\n"; + $rc = 1; + next; + } + my $value = $keyvalue[1]; + $value =~ s/^\s+|\s+$//g; + if ( length( $value ) == 0 ) { + print "(Error) Missing value for key $key on line ".($i+1)."\n"; + $rc = 1; + next + } + #uppercase both key and value; + my $UCkey = uc $key; + my $UCvalue = uc $value; + $hash{$UCkey} = $hash{$UCkey} + 1; + if ($UCkey eq "IMAGE_NAME") { + if (exists $imagenames{$UCvalue}) { + print "(Error) Duplicate IMAGE_NAME found on line ".($i+1)." with value: $value\n"; + $rc = 1; + } else { + $imagenames{$UCvalue} = 1; + } + } + if ($UCkey ne "IMAGE_NAME" && $UCkey ne "CLONE_FROM" && $UCkey ne "ECKD_POOL" && $UCkey ne "FBA_POOL") { + print "(Error) Unknown keyword $key found on line ".($i+1)."\n"; + $rc = 1; + } + } + # Check to make sure they have at least an image name, from and one pool + if ($hash{IMAGE_NAME} == 1 && $hash{CLONE_FROM} == 1 && ($hash{ECKD_POOL} ==1 || $hash{FBA_POOL} ==1 )) { + next; + } else { + if ($hash{IMAGE_NAME} == 0) { + print "(Error) Missing IMAGE_NAME key=value on line ".($i+1)."\n"; + $rc = 1; + } + if ($hash{IMAGE_NAME} > 1) { + print "(Error) Multiple IMAGE_NAME keys found on line ".($i+1)."\n"; + $rc = 1; + } + if ($hash{CLONE_FROM} == 0) { + print "(Error) Missing CLONE_FROM key=value on line ".($i+1)."\n"; + $rc = 1; + } + if ($hash{CLONE_FROM} > 1) { + print "(Error) Multiple CLONE_FROM keys found on line ".($i+1)."\n"; + $rc = 1; + } + if ($hash{ECKD_POOL} == 0 && $hash{FBA_POOL} == 0) { + print "(Error) Missing ECKD_POOL or FBA_POOL on line ".($i+1)."\n"; + $rc = 1; + } + if ($hash{ECKD_POOL} > 1) { + print "(Error) Multiple ECKD_POOL keys found on line ".($i+1)."\n"; + $rc = 1; + } + if ($hash{FBA_POOL} > 1) { + print "(Error) Multiple FBA_POOL keys found on line ".($i+1)."\n"; + $rc = 1; + } + } + } +} else { + print "(Error) Unable to access the $source_vdev disk.\n"; + $rc = 1; +} +# Main exit for this routine. Handles any necessary clean up. +MAIN_EXIT: +if (length($tempFileName) > 0 ) { + # If a good rc, Copy the temp file to the correct file + if ($rc == 0) { + ( $out, $err, $returnvalue ) = eval { capture { `/bin/cp -f $tempFileName $file_location$file_name` } }; + print $out; + print $err; + print $returnvalue; + chomp($out); + chomp($err); + chomp($returnvalue); + if (length($err) > 0) { + print "(Error) Cannot copy the temporary file $tempFileName to $file_location$file_name \n $err\n"; + $rc = 1; + } else { + print "Validation completed. Temporary file copied to $file_location$file_name.\nIt is ready to use\n"; + } + } + ( $out, $err, $returnvalue ) = eval { capture { `rm $tempFileName` } }; + chomp($out); + chomp($err); + chomp($returnvalue); + if (length($err) > 0) { + print "(Error) Cannot erase temporary file $tempFileName\n$err\n"; + $rc = 1; + } +} +exit $rc; diff --git a/xCAT-client/bin/zxcatIVP.pl b/xCAT-client/bin/zxcatIVP.pl new file mode 100644 index 000000000..5e1a79604 --- /dev/null +++ b/xCAT-client/bin/zxcatIVP.pl @@ -0,0 +1,2607 @@ +#!/usr/bin/perl +############################################################################### +# IBM (C) Copyright 2014, 2016 Eclipse Public License +# http://www.eclipse.org/org/documents/epl-v10.html +############################################################################### +# COMPONENT: zxcatIVP.pl +# +# This is an Installation Verification Program for z/VM's xCAT Management Node +# and zHCP agent. +############################################################################### +package xCAT::verifynode; +BEGIN +{ + $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; +} + +$XML::Simple::PREFERRED_PARSER='XML::Parser'; + +use strict; +#use warnings; +use Getopt::Long; +use Getopt::Long qw(GetOptionsFromString); +use MIME::Base64; +use Sys::Syslog qw( :DEFAULT setlogsock); +use Text::Wrap; +use LWP; +use JSON; +use lib "$::XCATROOT/lib/perl"; +use xCAT::zvmMsgs; +require HTTP::Request; + +# Global variables set based on input from the driver program. +my $glob_bypassMsg = 1; # Display bypass messages +my $glob_CMA = 0; # CMA appliance indicator, Assume not running in CMA +my $glob_CMARole = ''; # CMA role, CONTROLLER, COMPUTE or '' (unknown) +my $glob_cNAddress; # IP address or hostname of the compute node that + # is accessing this xCAT MN. (optional) +my $glob_defaultUserProfile; # Default profile used in creation of server instances. +my $glob_displayHelp = 0; # Display help instead of running the IVP +my @glob_diskpools; # Array of disk pools, e.g. ('POOLSCSI', 'POOL1'), + # that are expected to exist. (optional) +my $glob_expectedReposSpace; # Minimum amount of repository space. +my $glob_expUser; # User name used for importing and exporting images +my @glob_instFCPList; # Array of FCPs used by server instances +my $glob_hostNode; # Node of host being managed. If blank, + # IVP will search for the host node. (optional) +my $glob_macPrefix; # User prefix for MAC Addresses of Linux level 2 interfaces +my %glob_msgsToIgnore; # Hash of messages to be ignored +my $glob_mgtNetmask; +my $glob_mnNode; # Node name for xCAT MN (optional). +my $glob_moreCmdOps; # Additional command operands passed with an environment variable +my @glob_networks; # Array of networks and possible VLAN ranges + # eg. ( 'xcatvsw1', 'xcatvsw2:1:4999'). (optional) +my $obfuscatePw; # Obfuscate the PW in the driver file that is built +my $glob_signalTimeout; # Signal Shutdown mininum acceptable timeout +my $glob_syslogErrors; # Log errors in SYSLOG: 0 - no, 1 - yes +my %glob_vswitchOSAs; # Hash list of vswitches and their OSAs +my @glob_xcatDiskSpace; # Array information of directories in xCAT MN that should be validated for disk space +my $glob_xcatMNIp; # Expected IP address of this xCAT MN +my $glob_xcatMgtIp; # Expected IP address of this xCAT MN on the Mgt Network +my $glob_xcatUser; # User defined to communicate with xCAT MN +my $glob_xcatUserPw; # User's password defined to communicate with xCAT MN +my @glob_zhcpDiskSpace; # Array information of directories in zhcp that should be validated for disk space +my @glob_zhcpFCPList; # Array of FCPs used by zHCP +my $glob_zhcpNode; # Node name for xCAT zHCP server (optional) + +# Global IVP run time variables +my $glob_versionInfo; # Version info for xCAT MN and zHCP +my $glob_successfulTestCnt = 0; # Number of successful tests +my @glob_failedTests; # List of failed tests. +#my @glob_hostNodes; # Array of host node names +my %glob_hostNodes; # Hash of host node information +my %glob_ignored; # List of ignored messages +my $glob_ignoreCnt = 0; # Number of times we ignored a message +my %glob_localIPs; # Hash of local IP addresses for the xCAT MN's system +my $glob_localZHCP = ''; # Local ZHCP node name and hostname when ZHCP is on xCAT MN's system +my $glob_totalFailed = 0; # Number of failed tests +my $glob_testNum = 0; # Number of tests that were run + +my $glob_versionFileCMA = '/opt/ibm/cmo/version'; +my $glob_versionFileXCAT = '/opt/xcat/version'; +my $glob_applSystemRole = '/var/lib/sspmod/appliance_system_role'; + +# Tables for environment variable and command line operand processing. +my ( $cmdOp_bypassMsg, $cmdOp_cNAddress, $cmdOp_defaultUserProfile, $cmdOp_diskpools, + $cmdOp_expectedReposSpace, $cmdOp_expUser, $cmdOp_hostNode, $cmdOp_ignore, + $cmdOp_instFCPList, $cmdOp_instFCPList, $cmdOp_macPrefix, $cmdOp_mgtNetmask, + $cmdOp_mnNode, $cmdOp_moreCmdOps, $cmdOp_bypassMsg, $cmdOp_networks, $cmdOp_pw_obfuscated, + $cmdOp_syslogErrors, $cmdOp_signalTimeout, $cmdOp_vswitchOSAs, + $cmdOp_xcatDiskSpace, $cmdOp_xcatMgtIp, $cmdOp_xcatMNIp, + $cmdOp_xcatUser, $cmdOp_zhcpFCPList, $cmdOp_zhcpNode, $cmdOp_zhcpDiskSpace ); +my @cmdOps = ( + { + 'envVar' => 'zxcatIVP_bypassMsg', + 'opName' => 'bypassMsg', + 'inpVar' => 'cmdOp_bypassMsg', + 'var' => 'glob_bypassMsg', + 'type' => 'scalar', + 'desc' => "Controls whether bypass messages are produced:\n" . + "0: do not show bypass messages, or\n" . + "1: show bypass messages.\n" . + "Bypass messages indicate when a test is not run. " . + "This usually occurs when a required environment variable or command line operand is " . + "missing.", + }, + { + 'envVar' => 'zxcatIVP_cNAddress', + 'opName' => 'cNAddress', + 'inpVar' => 'cmdOp_cNAddress', + 'var' => 'glob_cNAddress', + 'type' => 'scalar', + 'desc' => 'Specifies the IP address or hostname of the OpenStack compute node that is ' . + 'accessing the xCAT MN.', + }, + { + 'envVar' => 'zxcatIVP_defaultUserProfile', + 'opName' => 'defaultUserProfile', + 'inpVar' => 'cmdOp_defaultUserProfile', + 'var' => 'glob_defaultUserProfile', + 'type' => 'scalar', + 'desc' => 'Specifies the default profile that is used in creation of server instances.', + }, + { + 'envVar' => 'zxcatIVP_diskpools', + 'opName' => 'diskpools', + 'inpVar' => 'cmdOp_diskpools', + 'var' => 'glob_diskpools', + 'type' => 'array', + 'case' => 'uc', + 'separator' => ';, ', + 'desc' => 'Specifies an array of disk pools that are expected to exist. ' . + 'The IVP will verify that space exists in those disk pools. ', + }, + { + 'envVar' => 'zxcatIVP_expectedReposSpace', + 'opName' => 'expectedReposSpace', + 'inpVar' => 'cmdOp_expectedReposSpace', + 'var' => 'glob_expectedReposSpace', + 'type' => 'scalar', + 'default' => '1G', + 'desc' => 'Specifies the expected space available in the xCAT MN image repository. ' . + 'The OpenStack compute node attempts to ensure that the space is available ' . + 'in the xCAT image repository by removing old images. This can cause ' . + 'images it be removed and added more often than desired if the value is too high.', + }, + { + 'envVar' => 'zxcatIVP_expUser', + 'opName' => 'expUser', + 'inpVar' => 'cmdOp_expUser', + 'var' => 'glob_expUser', + 'type' => 'scalar', + 'desc' => 'Specifies the name of the user under which the OpenStack Nova component runs ' . + 'on the compute node. The IVP uses this name when it attempts to verify access ' . + 'of the xCAT MN to the compute node.', + }, + { + 'envVar' => 'zxcatIVP_hostNode', + 'opName' => 'hostNode', + 'inpVar' => 'cmdOp_hostNode', + 'var' => 'glob_hostNode', + 'type' => 'scalar', + 'desc' => 'Specifies the node of host being managed by the compute node. ' . + 'The IVP will verify that the node name exists and use this to determine the ' . + 'ZHCP node that supports the host. If this value is missing or empty, the ' . + 'IVP will validate all host nodes that it detects on the xCAT MN.', + }, + { + 'envVar' => 'zxcatIVP_ignore', + 'opName' => 'ignore', + 'inpVar' => 'cmdOp_ignore', + 'var' => 'glob_msgsToIgnore', + 'type' => 'hash', + 'case' => 'uc', + 'separator' => ';, ', + 'desc' => 'Specifies a comma separated list of message Ids that should be ignored. ' . + 'Ignored messages do not generate a full message and are not counted as ' . + 'trigger to notify the monitoring userid (see XCAT_notify property in the ' . + 'DMSSICNF COPY file). Instead a line will be generated in the output indicating ' . + 'that the message was ignored.', + }, + { + 'envVar' => 'zxcatIVP_instFCPList', + 'opName' => 'instFCPList', + 'inpVar' => 'cmdOp_instFCPList', + 'var' => 'glob_instFCPList', + 'type' => 'array', + 'separator' => ';, ', + 'desc' => 'Specifies the list of FCPs used by instances.', + }, + { + 'envVar' => 'zxcatIVP_macPrefix', + 'opName' => 'macPrefix', + 'inpVar' => 'cmdOp_macPrefix', + 'var' => 'glob_macPrefix', + 'type' => 'scalar', + 'desc' => 'Specifies user prefix for MAC Addresses of Linux level 2 interfaces.', + }, + { + 'envVar' => 'zxcatIVP_mgtNetmask', + 'opName' => 'mgtNetmask', + 'inpVar' => 'cmdOp_mgtNetmask', + 'var' => 'glob_mgtNetmask', + 'type' => 'scalar', + 'desc' => 'Specifies xCat management interface netmask.', + }, + { + 'envVar' => 'zxcatIVP_mnNode', + 'opName' => 'mnNode', + 'inpVar' => 'cmdOp_mnNode', + 'var' => 'glob_mnNode', + 'type' => 'scalar', + 'desc' => 'Specifies the node name for xCAT MN.', + }, + { + 'envVar' => 'zxcatIVP_moreCmdOps', + 'opName' => 'moreCmdOps', + 'inpVar' => 'cmdOp_moreCmdOps', + 'var' => 'glob_moreCmdOps', + 'type' => 'scalar', + 'desc' => 'Specifies additional command operands to be passed to the zxcatIVP script. ' . + 'This is used interally by the IVP programs.', + }, + { + 'envVar' => 'zxcatIVP_networks', + 'opName' => 'networks', + 'inpVar' => 'cmdOp_networks', + 'var' => 'glob_networks', + 'type' => 'array', + 'separator' => ';, ', + 'desc' => "Specifies an array of networks and possible VLAN ranges. " . + "The array is a list composed of network names and optional " . + "vlan ranges in the form: vswitch:vlan_min:vlan_max where " . + "each network and vlan range components are separated by a colon.\n" . + "For example, 'vsw1,vsw2:1:4095' specifies two vswitches vsw1 without a " . + "vlan range and vsw2 with a vlan range of 1 to 4095.", + }, + { + 'envVar' => 'zxcatIVP_pw_obfuscated', + 'opName' => 'pw_obfuscated', + 'inpVar' => 'cmdOp_pw_obfuscated', + 'var' => 'obfuscatePw', + 'type' => 'scalar', + 'desc' => "Indicates whether the password zxcatIVP_xcatUserPw is obfuscated:\n" . + "1 - obfuscated,\n0 - in the clear.", + }, + { + 'envVar' => 'zxcatIVP_signalTimeout', + 'opName' => 'signalTimeout', + 'inpVar' => 'cmdOp_signalTimeout', + 'var' => 'glob_signalTimeout', + 'type' => 'scalar', + 'default' => '30', + 'desc' => "Specifies the minimum acceptable time value that should be specified ". + "in the z/VM using the SET SIGNAL SHUTDOWNTIME configuration statement. ". + "A value less than this value will generate a warning in the IVP.", + }, + { + 'envVar' => 'zxcatIVP_syslogErrors', + 'opName' => 'syslogErrors', + 'inpVar' => 'cmdOp_syslogErrors', + 'var' => 'glob_syslogErrors', + 'type' => 'scalar', + 'default' => '1', + 'desc' => "Specifies whether Warnings and Errors detected by the IVP are ". + "logged in the xCAT MN syslog:\n0: do not log,\n1: log to syslog.", + }, + { + 'envVar' => 'zxcatIVP_vswitchOSAs', + 'opName' => 'vswitchOSAs', + 'inpVar' => 'cmdOp_vswitchOSAs', + 'var' => 'glob_vswitchOSAs', + 'type' => 'hash', + 'separator' => ';, ', + 'desc' => 'Specifies vswitches and their related OSAs that are used by ' . + 'systems created by the OpenStack compute node.', + }, + { + 'envVar' => 'zxcatIVP_xcatDiskSpace', + 'opName' => 'xcatDiskSpace', + 'inpVar' => 'cmdOp_xcatDiskSpace', + 'var' => 'glob_xcatDiskSpace', + 'type' => 'array', + 'separator' => ';,', + 'default' => '/ 80 . 10M;/install 90 1g .', + 'desc' => 'Specifies a list of directories in the xCAT server that should be '. + 'verified for storage availability. Each directory consists of '. + 'a blank separated list of values:'. + "\n". + '* directory name,'. + "\n". + '* maximum in use percentage (a period indicates that the value should not be tested),'. + "\n". + '* minumum amount of available storage (period indicates that '. + 'available storage based on size should not be validated),'. + "\n". + '* minimum file size at which to generate a warning when available space tests '. + 'detect a size issue (a period indicates that the value should not be tested).'. + "\n\n". + "For example: '/ 80 5G 3M' will cause the IVP to check the space for the ". + 'root directory (/) to verify that it has at least 80% space available or '. + '5G worth of space available. If the space tests fail, a warning will be '. + 'generated for each file (that is not a jar or so file) which is 3M or larger. '. + 'Additional directories may be specified using a list separator.', + }, + { + 'envVar' => 'zxcatIVP_xcatMgtIp', + 'opName' => 'xcatMgtIp', + 'inpVar' => 'cmdOp_xcatMgtIp', + 'var' => 'glob_xcatMgtIp', + 'type' => 'scalar', + 'desc' => 'Specifies xCat MN\'s IP address on the xCAT management network.', + }, + { + 'envVar' => 'zxcatIVP_xcatMNIp', + 'opName' => 'xcatMNIp', + 'inpVar' => 'cmdOp_xcatMNIp', + 'var' => 'glob_xcatMNIp', + 'type' => 'scalar', + 'desc' => 'Specifies the expected IP address of the xcat management node.', + }, + { + 'envVar' => 'zxcatIVP_xcatUser', + 'opName' => 'xcatUser', + 'inpVar' => 'cmdOp_xcatUser', + 'var' => 'glob_xcatUser', + 'type' => 'scalar', + 'desc' => 'Specifies the user defined to communicate with xCAT management node.', + }, + { + 'envVar' => 'zxcatIVP_xcatUserPw', + 'opName' => 'xcatUserPw', + 'inpVar' => 'cmdOp_xcatUserPw', + 'var' => 'glob_xcatUserPw', + 'type' => 'scalar', + 'desc' => 'Specifies the user password defined to communicate with xCAT MN over the REST API.', + }, + { + 'envVar' => 'zxcatIVP_zhcpFCPList', + 'opName' => 'zhcpFCPList', + 'inpVar' => 'cmdOp_zhcpFCPList', + 'var' => 'glob_zhcpFCPList', + 'type' => 'array', + 'separator' => ';, ', + 'desc' => 'Specifies the list of FCPs used by zHCP.', + }, + { + 'envVar' => 'zxcatIVP_zhcpNode', + 'opName' => 'zhcpNode', + 'inpVar' => 'cmdOp_zhcpNode', + 'var' => 'glob_zhcpNode', + 'type' => 'scalar', + 'desc' => 'Specifies the expected ZHCP node name that the compute node ' . + 'expects to be used to manage the z/VM host.', + }, + { + 'envVar' => 'zxcatIVP_zhcpDiskSpace', + 'opName' => 'zhcpDiskSpace', + 'inpVar' => 'cmdOp_zhcpDiskSpace', + 'var' => 'glob_zhcpDiskSpace', + 'type' => 'array', + 'separator' => ';,', + 'default' => '/ 90 . 10M', + 'desc' => "Specifies a list of directories in the ZHCP server that should be ". + 'verified for storage availability. Each directory consists of '. + 'a blank separated list of values:'. + "\n". + '* directory name,'. + "\n". + '* maximum in use percentage (a period indicates that the value should not be tested),'. + "\n". + '* minumum amount of available storage (period indicates that '. + 'available storage based on size should not be validated),'. + "\n". + '* minimum file size at which to generate a warning when available space tests '. + 'detect a size issue (a period indicates that the value should not be tested).'. + "\n\n". + "For example: '/ 80 5G 3M' will cause the IVP to check the space for the ". + 'root directory (/) to verify that it has at least 80% space available or '. + '5G worth of space available. If the space tests fail, a warning will be '. + 'generated for each file (that is not a jar or so file) which is 3M or larger. '. + 'Additional directories may be specified using a list separator.', + }, + ); + +my $usage_string = "Usage:\n + zxcatIVP +or + zxcatIVP +or + zxcatIVP --help +or + zxcatIVP -h\n\n"; + + +#------------------------------------------------------- + +=head3 applyOverrides + + Description : Apply the overrides from either + the environment variables or command + line to the target global variables. + Arguments : None + Returns : None. + Example : applyOverrides(); + +=cut + +#------------------------------------------------------- +sub applyOverrides{ + # Handle the normal case variables and values. + foreach my $opHash ( @cmdOps ) { + my $opVarRef = eval('\$' . $opHash->{'inpVar'}); + if ( ! defined $$opVarRef) { + #print "Did not find \$$opHash->{'inpVar'}\n"; + next; + } else { + #print "key: $opHash->{'inpVar'}, value: $$opVarRef\n" + } + + # Modify the case of the value, if necessary. + if ( ! exists $opHash->{'case'} ) { + # Ignore case handling + } elsif ( $opHash->{'case'} eq 'uc' ) { + $$opVarRef = uc( $$opVarRef ); + } elsif ( $opHash->{'case'} eq 'lc' ) { + $$opVarRef = lc( $$opVarRef ); + } + + # Process the value to set the variable in this script. + if ( $opHash->{'type'} eq "scalar" ) { + my $globRef = eval('\$' . $opHash->{'var'}); + $$globRef = $$opVarRef; + } elsif ( $opHash->{'type'} eq "array" ) { + my $globRef = eval('\@' . $opHash->{'var'}); + my @array; + if ( $opHash->{'separator'} =~ /,/ and $$opVarRef =~ /,/ ) { + @array = split( ',', $$opVarRef ); + } elsif ( $opHash->{'separator'} =~ /;/ and $$opVarRef =~ /;/ ) { + @array = split( ';', $$opVarRef ); + } elsif ( $opHash->{'separator'} =~ /\s/ and $$opVarRef =~ /\s/ ) { + @array = split( '\s', $$opVarRef ); + } else { + push @array, $$opVarRef; + } + @$globRef = @array; + } elsif ( $opHash->{'type'} eq "hash" ) { + my $globRef = eval('\%' . $opHash->{'var'}); + my @array; + my %hash; + if ( $opHash->{'separator'} =~ /,/ and $$opVarRef =~ /,/ ) { + @array = split( ',', $$opVarRef ); + %hash = map { $_ => 1 } @array; + } elsif ( $opHash->{'separator'} =~ /;/ and $$opVarRef =~ /;/ ) { + @array = split( ';', $$opVarRef ); + %hash = map { $_ => 1 } @array; + } elsif ( $opHash->{'separator'} =~ /\s/ and $$opVarRef =~ /\s/ ) { + @array = split( '\s', $$opVarRef ); + %hash = map { $_ => 1 } @array; + } else { + $hash{$$opVarRef} = 1; + } + %$globRef = %hash; + } else { + print "Internal error: Unsupported \%cmdOpRef type '$opHash->{'type'}' for $opHash->{'inpVar'}\n"; + } + } +} + + +#------------------------------------------------------- + +=head3 calculateRoutingPrefix + + Description : Calculate the routing prefix for a given subnet mask. + Arguments : Subnet Mask + Returns : -1 - Unable to calculate routing prefix + zero or non-zero - Routing prefix number + Example : $rc = calculateRoutingPrefix( $subnetMask ); + +=cut + +#------------------------------------------------------- +sub calculateRoutingPrefix{ + my ( $subnetMask ) = @_; + my $routingPrefix = 0; + my @parts; + + # Determine the inet version based on the mask separator and + # calculate the routing prefix. + if ( $subnetMask =~ m/\./ ) { + # inet 4 mask + @parts = split( /\./, $subnetMask ); + foreach my $part ( @parts ) { + if (( $part =~ /\D/ ) || ( length($part) > 3 )) { + return -1; # subnet mask is not valid + } + foreach my $i ( 1, 2, 4, 8, + 16, 32, 64, 128 ) { + if ( $part & $i ) { + $routingPrefix++; + } + } + } + } elsif ( $subnetMask =~ m/\:/ ) { + # inet 6 mask + @parts = split( /:/, $subnetMask ); + foreach my $part ( @parts ) { + if (( $part =~ /[^0-9^a-f^A-F]/ ) || ( length($part) > 4 )) { + print "part failed: $part\n"; + return -1; # subnet mask is not valid + } + $part = hex $part; + foreach my $i ( 1, 2, 4, 8, + 16, 32, 64, 128, + 256, 512, 1024, 2048, + 4096, 8192, 16384, 32768 ) { + if ( $part & $i ) { + $routingPrefix++; + } + } + } + } + + return $routingPrefix +} + + +#------------------------------------------------------- + +=head3 convertDiskSize + + Description : Reduce a size with a magnitude (eg. 25G or 25M) + to a common scalar value. + Arguments : Size to convert. + Returns : non-negative - No error + -1 - Error detected. + Example : my $size = convertDiskSize( $diskType, $diskSize ); + +=cut + +#------------------------------------------------------- +sub convertDiskSize{ + my ( $diskType, $diskSize ) = @_; + my $size; + + my $bytesPer3390Cylinder = 849960; + my $bytesPer3380Cylinder = 712140; + my $bytesPer9345Cylinder = 696840; + my $bytesPerFbaBlock = 512; + my $cylindersPer3390_03 = 3339; + my $kilobyte = 1024; + my $megabyte = 1024 ** 2; + my $gigabyte = 1024 ** 3; + + $diskType = uc( $diskType ); + if ( $diskType =~ '3390-' ) { + $size = $diskSize * $bytesPer3390Cylinder / $gigabyte; + $size = sprintf("%.2f", $size); + $size = "$diskSize(cyl) -> $size" . "Gig"; + } elsif ( $diskType =~ '3380-') { + $size = $diskSize * $bytesPer3380Cylinder / $gigabyte; + $size = sprintf("%.2f", $size); + $size = "$diskSize(cyl) -> $size" . "Gig"; + } elsif ( $diskType =~ '9345-') { + $size = $diskSize * $bytesPer9345Cylinder / $gigabyte; + $size = sprintf("%.2f", $size); + $size = "$diskSize(cyl) -> $size" . "Gig"; + } elsif ( $diskType =~ '9336-') { + $size = $diskSize * $bytesPerFbaBlock / $gigabyte; + $size = sprintf("%.2f", $size); + $size = "$diskSize(block) -> $size" . "Gig"; + } elsif ( $diskType =~ '9332') { + $size = $diskSize * $bytesPerFbaBlock / $gigabyte; + $size = "$diskSize(block)"; + } else { + $size = "$diskSize"; + } + + return $size; +} + + +#------------------------------------------------------- + +=head3 convertSize + + Description : Reduce a size with a magnitude (eg. 25G or 25M) + to a common scalar value. + Arguments : Size to convert. + Returns : non-negative - No error + -1 - Error detected. + Example : my $size = convertSize( "25G" ); + +=cut + +#------------------------------------------------------- +sub convertSize{ + my ( $magSize ) = @_; + my $size; + my $numeric; + my $kilobyte = 1024; + my $megabyte = 1024 ** 2; + my $gigabyte = 1024 ** 3; + my $terabyte = 1024 ** 4; + my $petabyte = 1024 ** 5; + my $exabyte = 1024 ** 6; + + $magSize = uc( $magSize ); + $numeric = substr( $magSize, 0, -1 ); + if ( length $magSize == 0 ) { + logTest( 'misc', "size is less than expected, value: $magSize." ); + } elsif ( $magSize =~ m/K$/ ) { + $size = $numeric * $kilobyte; + } elsif ( $magSize =~ m/M$/ ) { + $size = $numeric * $megabyte; + } elsif ( $magSize =~ m/G$/ ) { + $size = $numeric * $gigabyte; + } elsif ( $magSize =~ m/T$/ ) { + $size = $numeric * $terabyte; + } elsif ( $magSize =~ m/P$/ ) { + $size = $numeric * $petabyte; + } elsif ( $magSize =~ m/E$/ ) { + $size = $numeric * $exabyte; + } else { + logTest( 'misc', "magnitude of $magSize is unknown." ); + return -1; + } + + return $size; +} + + +#------------------------------------------------------- + +=head3 driveREST + + Description : Verify the REST interface is running. + Arguments : IP address + User + Password + Rest Object ( e.g. nodes/xcat ) + Method ( GET | PUT | POST | DELETE ) + Format + Returns : Response structure + Example : my $response = driveREST( $glob_xcatMNIp, $glob_xcatUser, + $glob_xcatUserPw, "nodes/$glob_mnNode", "GET", "json", \@restOps ); + +=cut + +#------------------------------------------------------- +sub driveREST{ + my ( $addr, $user, $pw, $obj, $method, $format, $restOps) = @_; + my $url = "https://$addr/xcatws/$obj" . "?userName=$user&password=$pw" . + "&format=$format"; + + my @updatearray; + my $fieldname; + my $fieldvalue; + my @args = (); + if ( scalar( @args ) > 0 ){ + foreach my $tempstr (@args) { + push @updatearray, $tempstr; + } + } + + my $request; + + my $ua = LWP::UserAgent->new(); + my $response; + if (( $method eq 'PUT' ) or ( $method eq 'POST' )) { + my $tempstr = encode_json \@updatearray; + $request = HTTP::Request->new( $method => $url ); + $request->header('content-type' => 'text/plain'); + $request->header( 'content-length' => length( $tempstr ) ); + $request->content( $tempstr ); + } elsif (( $method eq 'GET' ) or ( $method eq 'DELETE' )) { + $request = HTTP::Request->new( $method=> $url ); + } + + $response = $ua->request( $request ); + + #print $response->content . "\n"; + #print "code: " . $response->code . "\n"; + #print "message: " .$response->message . "\n"; + return $response; +} + + +#------------------------------------------------------- +=head3 findZhcpNode + + Description : Find the object name of the zHCP node. + Arguments : Target node whose ZHCP we want to find + Returns : zHCP node name, if found + undefined, if not found + Example : my $zhcpNode = findZhcpNode(); + +=cut + +#------------------------------------------------------- +sub findZhcpNode{ + my ( $targetNode) = @_; + my $rc = 0; + my $zhcpNode; + + # Get the HCP hostname from the node + my %targetInfo = getLsdefNodeInfo( $targetNode ); + my $hcpHostname = $targetInfo{'hcp'}; + + # Find the node that owns the zHCP hostname + my @nodes = getNodeNames(); + foreach my $node (@nodes){ + my %nodeInfo = getLsdefNodeInfo( $node ); + if ( $nodeInfo{'hostnames'} =~ $hcpHostname ) { + $zhcpNode = $node; + last; + } + } + + return $zhcpNode; +} + + +#------------------------------------------------------- + +=head3 getDiskPoolNames + + Description : Obtain the list of disk pools for a + z/VM host. + Arguments : Host Node + Returns : Array of disk pool names - No error + empty array - Error detected. + Example : my @pools = getDiskPoolNames($node); + +=cut + +#------------------------------------------------------- +sub getDiskPoolNames{ + my ( $hostNode) = @_; + my @pools; + + # Find the related zHCP node + my $zhcpNode = findZhcpNode( $hostNode ); + if ( !defined $zhcpNode ) { + return @pools; + } + + my $out = `/opt/xcat/bin/lsvm $zhcpNode --diskpoolnames | awk '{print \$NF}'`; + @pools = split /\n/, $out; + return @pools; +} + + +#------------------------------------------------------- + +=head3 getHostNodeNames + + Description : Get a list of the host nodes defined to this + xCAT MN. + Arguments : none + Returns : List of host nodes + undefined - Error detected. + Example : my @hostNodes = getHostNodeNames(); + +=cut + +#------------------------------------------------------- +sub getHostNodeNames{ + my @nodes = getNodeNames(); + my @hostNodes; + + foreach my $node (@nodes){ + my %nodeInfo = getLsdefNodeInfo( $node ); + if ( ! %nodeInfo ) { + next; + } + if (( exists $nodeInfo{'hosttype'} ) and ( $nodeInfo{'hosttype'} =~ 'zvm' )) { + push( @hostNodes, $node); + } + } + + return @hostNodes; +} + + +#------------------------------------------------------- + +=head3 getLocalIPs + + Description : Get the IP addresses from ifconfig. + Arguments : Node name or IP address + Returns : Hash of local IP addresses + Example : %localIPs = getLocalIPs(); + +=cut + +#------------------------------------------------------- +sub getLocalIPs { + my $ip; + my $junk; + my %localIPs; + my $rc = 0; + + my $out = `/sbin/ip addr | grep -e '^\\s*inet' -e '^\\s*inet6'`; + my @lines = split( '\n', $out ); + foreach my $line ( @lines ) { + my @parts = split( ' ', $line ); + ($ip) = split( '/', $parts[1], 2 ); + $localIPs{$ip} = 1; + } + +FINISH_getLocalIPs: + return %localIPs; +} + + +#------------------------------------------------------- + +=head3 getLsdefNodeInfo + + Description : Obtain node info from LSDEF. + Arguments : Name of node to retrieve + Returns : Hash of node properties. + Example : my %hash = getLsdefNodeInfo($node); + +=cut + +#------------------------------------------------------- +sub getLsdefNodeInfo{ + my ( $node) = @_; + my %hash; + + my $out = `/opt/xcat/bin/lsdef $node`; + + my @list1 = split /\n/, $out; + foreach my $item (@list1) { + if ( $item !~ "Object name:" ) { + my ($i,$j) = split(/=/, $item); + $i =~ s/^\s+|\s+$//g; # trim both ends of the string + $hash{$i} = $j; + } + } + + return %hash; +} + + +#------------------------------------------------------- + +=head3 getNodeNames + + Description : Get a list of the nodes defined to this + xCAT MN. + Arguments : none + Returns : Array of nodes + undefined - Error detected. + Example : my @nodes = getNodeNames(); + +=cut + +#------------------------------------------------------- +sub getNodeNames{ + my $out = `/opt/xcat/bin/lsdef | sed "s/ (node)//g"`; + my @nodes = split( /\n/, $out ); + return @nodes; +} + + +#------------------------------------------------------- + +=head3 getUseridFromLinux + + Description : Obtain the z/VM virtual machine userid from + /proc/sysinfo file. + Arguments : Variable to receive the output + Returns : 0 - No error + non-zero - Can't get the virtual machine id. + Example : my $rc = getUseridFromLinux( \$userid ); + +=cut + +#------------------------------------------------------- +sub getUseridFromLinux{ + my ( $userid) = @_; + my $rc = 0; + + $$userid = `cat /proc/sysinfo | grep 'VM00 Name:' | awk '{print \$NF}'`; + $$userid =~ s/^\s+|\s+$//g; # trim both ends of the string + + if ( $$userid ne '' ) { + $rc = 1; + } + + return $rc; +} + + +#------------------------------------------------------- + +=head3 getVswitchInfo + + Description : Query a vswitch and produce a hash of the data. + Arguments : zHCP node + Name of switch to be queried + Returns : hash of switch data, if found. + hash contains either: + $switchInfo{'Base'}{$property} = $value; + $switchInfo{'Authorized users'}{'User'} = $value; + $switchInfo{'Connections'}{$property} = $value; + $switchInfo{'Real device xxxx'}{$property} = $value; + Example : $rc = getVswitchInfo( $zhcpNode, $switch ); + +=cut + +#------------------------------------------------------- +sub getVswitchInfo{ + my ( $zhcpNode, $switch ) = @_; + my %switchInfo; + my @word; + my $device; + + my $out = `ssh $zhcpNode smcli Virtual_Network_Vswitch_Query -T xxxx -s $switch`; + if ( $out !~ /^Failed/ ) { + # Got some information. Process it. + my @lines = split( "\n", $out ); + pop( @lines ); + my $subsection = 'Base'; + foreach my $line ( @lines ) { + #print "line: $line\n"; + my $indent = $line =~ /\S/ ? $-[0] : length $line; # Get indentation level + $line =~ s/^\s+|\s+$//g; # trim both ends of the line; + if ( $line eq '' ) { + next; + } elsif ( $indent == 0 ) { + if ( $line =~ 'VSWITCH:' ) { + $line = substr( $line, 8 ); + $line =~ s/^\s+|\s+$//g; # trim both ends of the line; + @word = split( /:/, $line ); + $word[1] =~ s/^\s+|\s+$//g; # trim both ends of the line; + $switchInfo{'Base'}{$word[0]} = $word[1]; + } + } elsif ( $indent == 2 ) { + if ( $line =~ /Devices:/ ) { + $subsection = 'Real device'; + } elsif ( $line =~ /Authorized users:/ ) { + $subsection = 'Authorized users'; + } elsif ( $line =~ /Connections:/ ) { + $subsection = 'Connections'; + } else { + $subsection = 'Base'; + @word = split( /:/, $line ); + $switchInfo{$subsection}{$word[0]} = $word[1]; + } + } elsif ( $indent == 4 ) { + if ( $subsection eq 'Real device' ) { + @word = split( ':', $line ); + if ( $line =~ /Real device:/ ) { + $device = $word[1]; + $device =~ s/^\s+|\s+$//g; # trim both ends of the string + } else { + if ( !exists $word[1] ) { + $word[1] = ''; + } + my $key = "$subsection $device"; + $switchInfo{$key}{$word[0]} = $word[1]; + } + } elsif ( $subsection eq 'Authorized users' ) { + @word = split( ':', $line ); + if ( $word[1] eq '' ) { + next; + } + if ( exists $switchInfo{$subsection} ) { + $switchInfo{$subsection} = "$switchInfo{$subsection} $word[1]"; + } else { + $switchInfo{$subsection} = "$word[1]"; + } + } elsif ( $subsection eq 'Connections' ) { + @word = split( ' ', $line ); + if ( !exists $word[2] ) { + next; + } + $switchInfo{$subsection}{$word[2]} = $word[5]; + } + } + } + } + return %switchInfo; +} + + +#------------------------------------------------------- + +=head3 getVswitchInfoExtended + + Description : Query a vswitch and produce a hash of the data + using the extended (keyword) related API. + Arguments : zHCP node + Name of switch to be queried + Returns : hash of switch data, if found. + hash contains sections and hash/value pairs: + $switchInfo{'Base'}{$property} = $value; + $switchInfo{'Real device'}{$device}{$property} = $value; + $switchInfo{'Authorized users'}{$authUser}{$property} = $value; + $switchInfo{'Connections'}{$adapter_owner}{$property} = $value; + Example : $rc = getVswitchInfoExtended( $zhcpNode, $switch ); + +=cut + +#------------------------------------------------------- +sub getVswitchInfoExtended{ + my ( $zhcpNode, $switch ) = @_; + my %switchInfo; + my @word; + my $device; + my $authUser; + + my $out = `ssh $zhcpNode smcli Virtual_Network_Vswitch_Query_Extended -T xxxx -k switch_name=$switch`; + if ( $out !~ /^Failed/ ) { + # Got some information. Process it. + my @lines = split( "\n", $out ); + pop( @lines ); + my $subsection = 'Base'; + my $authPort; + my $adapter_owner; + + foreach my $line ( @lines ) { + #print "line: $line\n"; + my $indent = $line =~ /\S/ ? $-[0] : length $line; # Get indentation level + $line =~ s/^\s+|\s+$//g; # trim both ends of the line; + if ( $line eq '' ) { + next; + } + + $line =~ s/^\s+|\s+$//g; # trim both ends of the line; + @word = split( /:/, $line ); + $word[1] =~ s/^\s+|\s+$//g; # trim both ends of the line; + if ( !exists $word[1] ) { + $word[1] = ''; + } + + if ( $word[0] eq 'switch_name' ) { + $switchInfo{'Base'}{$word[0]} = $word[1]; + $subsection = 'Base'; + next; + } elsif ( $word[0] eq 'real_device_address' ) { + $subsection = 'Real device'; + $device = $word[1]; + if ( !exists $switchInfo{$subsection}{'RDEVs'} ) { + $switchInfo{$subsection}{'RDEVs'} = $device; + } else { + $switchInfo{$subsection}{'RDEVs'} = $switchInfo{$subsection}{'RDEVs'} . ' ' . $device; + } + next; + } elsif ( $word[0] eq 'port_num' ) { + $subsection = 'Authorized users'; + $authPort = $word[1]; + next; + } elsif ( $word[0] eq 'adapter_owner' ) { + $subsection = 'Connections'; + $adapter_owner = $word[1]; + if ( !exists $switchInfo{$subsection}{'ConnectedUsers'} ) { + $switchInfo{$subsection}{'ConnectedUsers'} = $adapter_owner; + } else { + $switchInfo{$subsection}{'ConnectedUsers'} = $switchInfo{$subsection}{'ConnectedUsers'} . ' ' . $adapter_owner; + } + next; + } + + # Fill in hash based upon the subsection we are handling. + my $key; + if ( $subsection eq 'Base' ) { + $switchInfo{$subsection}{$word[0]} = $word[1]; + } elsif ( $subsection eq 'Real device' ) { + $switchInfo{$subsection}{$device}{$word[0]} = $word[1]; + } elsif ( $subsection eq 'Authorized users' ) { + if ( $word[0] eq 'grant_userid' ) { + $authUser = $word[1]; + $switchInfo{$subsection}{$authUser}{'port_num'} = $authPort; + if ( !exists $switchInfo{$subsection}{'AuthorizedUsers'} ) { + $switchInfo{$subsection}{'AuthorizedUsers'} = $authUser; + } else { + $switchInfo{$subsection}{'AuthorizedUsers'} = $switchInfo{$subsection}{'AuthorizedUsers'} . ' ' . $authUser; + } + } else { + $switchInfo{$subsection}{$authUser}{$word[0]} = $word[1]; + } + } elsif ( $subsection eq 'Connections' ) { + $switchInfo{$subsection}{$adapter_owner}{$word[0]} = $word[1]; + } + } + } + return %switchInfo; +} + + +#------------------------------------------------------- + +=head3 hexDecode + + Description : Convert a string of printable hex + characters (4 hex characters per actual + character) into the actual string that + it represents. + Arguments : printable hex value + Returns : Perl string + Example : $rc = hexDecode(); + +=cut + +#------------------------------------------------------- +sub hexDecode { + my ( $hexVal ) = @_; + my $result = ''; + + if ( $hexVal =~ /^HexEncoded:/ ) { + ($hexVal) = $hexVal =~ m/HexEncoded:(.*)/; + my @hexes = unpack( "(a4)*", $hexVal); + for ( my $i = 0; $i < scalar(@hexes); $i++ ) { + $result .= chr( hex( $hexes[$i] ) ); + } + } else { + $result = $hexVal; + } + + return $result; +} + + +#------------------------------------------------------- + +=head3 logTest + + Description : Log the start and result of a test. + Failures are added to syslog and printed as script output. + Arguments : Status of the test: + bypassed: Bypassed a test (STDOUT, optionally SYSLOGged) + failed: Failed test (STDOUT, optionally SYSLOGged) + passed: Successful test (STDOUT only) + started: Start test (STDOUT only, increments test number) + misc: Miscellaneous output (STDOUT only) + miscNF: Miscellaneous non-formatted output (STDOUT only) + info: Information output similar to miscellaneous output + but has a message number (STDOUT only) + Message ID (used for "bypassed", "failed", or "warning" messages) or + message TEXT (used for "misc", "passed", or "started" messages). + Message id should begin with the initials of the subroutine + generating the message and begin at 1. For example, the + first error message from verifyNode subroutine would be 'VN01'. + Message substitution values (used for "bypassed", "failed", or + "warning" messages) + Returns : None + Example : logTest( 'failed', "VMNI01", $name, $tgtIp ); + logTest( 'started', "xCAT MN has a virtual storage size of at least $vstorMin." ); + +=cut + +#------------------------------------------------------- +sub logTest{ + my ( $testStatus, $msgInfo, @msgSubs ) = @_; + my $extraInfo; + my $rc; + my $sev; + my $msg; + + if ( $testStatus eq 'misc' ) { + # Miscellaneous output + $msgSubs[0] = "$msgInfo\n"; + ( $rc, $sev, $msg, $extraInfo ) = xCAT::zvmMsgs->buildMsg('ZXCATIVP', 'GENERIC_RESPONSE', \@msgSubs ); + print( "$msg\n" ); + } elsif ( $testStatus eq 'miscNF' ) { + # Miscellaneous output + print("$msgInfo\n"); + } elsif ( $testStatus eq 'passed' ) { + # Test was successful. Log it as ok. + if ( $msgInfo ne '' ) { + $msgSubs[0] = "$msgInfo\n"; + ( $rc, $sev, $msg, $extraInfo ) = xCAT::zvmMsgs->buildMsg('ZXCATIVP', 'GENERIC_RESPONSE', \@msgSubs ); + print( "$msg\n" ); + } + } elsif ( $testStatus eq 'started' ) { + # Start test + $glob_testNum++; + print( "\n" ); + $msgSubs[0] = "Test $glob_testNum: Verifying $msgInfo"; + ( $rc, $sev, $msg, $extraInfo ) = xCAT::zvmMsgs->buildMsg('ZXCATIVP', 'GENERIC_RESPONSE', \@msgSubs ); + print( "$msg\n" ); + } else { + ( $rc, $sev, $msg, $extraInfo ) = xCAT::zvmMsgs->buildMsg('ZXCATIVP', $msgInfo, \@msgSubs ); + + # Determine whether we need to ignore the message or produce it and count it. + if ( defined $glob_msgsToIgnore{$msgInfo} ) { + # Ignore this message id + $glob_ignored{$msgInfo} = 1; + $glob_ignoreCnt += 1; + print( "Message $msgInfo is being ignored but would have occurred here.\n" ); + } elsif ( defined $glob_msgsToIgnore{$sev} ) { + # Ignoring all messages of this severity. + $glob_ignored{$msgInfo} = 1; + $glob_ignoreCnt += 1; + print( "Message $msgInfo is being ignored but would have occurred here.\n" ); + } else { + # Handle the failed, warning and bypassed messages + if ( $testStatus eq 'failed' ) { + # Test failed. Log it as failure and produce necessary messages + $glob_totalFailed += 1; + if ( $glob_totalFailed == 1 || $glob_failedTests[-1] != $glob_testNum ) { + push( @glob_failedTests, $glob_testNum ); + } + print( "$msg" ); + if ( $extraInfo ne '' ) { + print( "$extraInfo" ); + } + } elsif ( $testStatus eq 'warning' ) { + # Warning unrelated to a test. + print("$msg"); + if ( $extraInfo ne '' ) { + print( "$extraInfo" ); + } + } elsif ( $testStatus eq 'info' ) { + # Information message + print("$msg"); + if ( $extraInfo ne '' ) { + print( "$extraInfo" ); + } + } elsif ( $testStatus eq 'bypassed' ) { + # Bypass message + if ( $glob_bypassMsg != 0 ) { + print("$msg"); + if ( $extraInfo ne '' ) { + print( "$extraInfo" ); + } + } + } + + # Write the message to syslog + if ( $testStatus ne 'info' ) { + my $logMsg = $msg; + $logMsg =~ s/\t//g; + $logMsg =~ s/\n/ /g; + syslog( 'err', $logMsg ); + } + } + } +} + + +#------------------------------------------------------- + +=head3 setOverrides + + Description : Set global variables based on input from + an external driver perl script, the + command line or the zxcatIVP_moreCmdOps + environment variable. This allows + the script to be run standalone or overriden + by a driver perl script. + Arguments : None + Returns : None. + Example : setOverrides(); + +=cut + +#------------------------------------------------------- +sub setOverrides{ + my $rc; + my $unrecognizedOps = ''; + my $val; + + # Read the environment variables. + foreach my $opHash ( @cmdOps ) { + my $inpRef = eval('\$' . $opHash->{'inpVar'}); + + # Update the local input variable with the value from the environment + # variable or set the default. + if ( defined $ENV{ $opHash->{'envVar'} } ) { + $$inpRef = $ENV{ $opHash->{'envVar'} }; + } else { + if ( exists $opHash->{'default'} ) { + $$inpRef = $opHash->{'default'}; + } else { + next; + } + } + } + + # Apply the environent variables as overrides to the global variables in this script. + applyOverrides(); + + # Clear the input variables so that we can use them for command line operands. + foreach my $opHash ( @cmdOps ) { + my $inpRef = eval('\$' . $opHash->{'inpVar'}); + $$inpRef = undef; + } + + # Handle options from the command line. + $Getopt::Long::ignorecase = 0; + Getopt::Long::Configure( "bundling" ); + if ( !GetOptions( + 'bypassMsg=s' => \$cmdOp_bypassMsg, + 'cNAddress=s' => \$cmdOp_cNAddress, + 'defaultUserProfile=s' => \$cmdOp_defaultUserProfile, + 'diskpools=s' => \$cmdOp_diskpools, + 'expectedReposSpace=s' => \$cmdOp_expectedReposSpace, + 'expUser=s' => \$cmdOp_expUser, + 'h|help' => \$glob_displayHelp, + 'hostNode=s' => \$cmdOp_hostNode, + 'ignore=s' => \$cmdOp_ignore, + 'instFCPList=s' => \$cmdOp_instFCPList, + 'macPrefix=s' => \$cmdOp_macPrefix, + 'mgtNetmask=s' => \$cmdOp_mgtNetmask, + 'mnNode=s' => \$cmdOp_mnNode, + 'moreCmdOps=s' => \$glob_moreCmdOps, + 'networks=s' => \$cmdOp_networks, + 'pw_obfuscated' => \$cmdOp_pw_obfuscated, + 'signalTimeout=s' => \$cmdOp_signalTimeout, + 'syslogErrors' => \$cmdOp_syslogErrors, + 'vswitchOSAs=s' => \$cmdOp_vswitchOSAs, + 'xcatDiskSpace=s' => \$cmdOp_xcatDiskSpace, + 'xcatMgtIp=s' => \$cmdOp_xcatMgtIp, + 'xcatMNIp=s' => \$cmdOp_xcatMNIp, + 'xcatUser=s' => \$cmdOp_xcatUser, + 'zhcpDiskSpace=s' => \$cmdOp_zhcpDiskSpace, + 'zhcpFCPList=s' => \$cmdOp_zhcpFCPList, + 'zhcpNode=s' => \$cmdOp_zhcpNode, + )) { + print $usage_string; + } + + # Handle options passed using the environment variable. + # This will override the same value that was passed in the command line. + # Don't specify the same option on both the command line and in the environment variable. + if ( defined $glob_moreCmdOps ) { + $glob_moreCmdOps =~ hexDecode( $glob_moreCmdOps ); + ($rc, $unrecognizedOps) = GetOptionsFromString( + $glob_moreCmdOps, + 'bypassMsg=s' => \$cmdOp_bypassMsg, + 'cNAddress=s' => \$cmdOp_cNAddress, + 'defaultUserProfile=s' => \$cmdOp_defaultUserProfile, + 'diskpools=s' => \$cmdOp_diskpools, + 'expectedReposSpace=s' => \$cmdOp_expectedReposSpace, + 'expUser=s' => \$cmdOp_expUser, + 'h|help' => \$glob_displayHelp, + 'hostNode=s' => \$cmdOp_hostNode, + 'ignore=s' => \$cmdOp_ignore, + 'instFCPList=s' => \$cmdOp_instFCPList, + 'macPrefix=s' => \$cmdOp_macPrefix, + 'mgtNetmask=s' => \$cmdOp_mgtNetmask, + 'mnNode=s' => \$cmdOp_mnNode, + 'networks=s' => \$cmdOp_networks, + 'pw_obfuscated' => \$cmdOp_pw_obfuscated, + 'signalTimeout=s' => \$cmdOp_signalTimeout, + 'syslogErrors' => \$cmdOp_syslogErrors, + 'vswitchOSAs=s' => \$cmdOp_vswitchOSAs, + 'xcatDiskSpace=s' => \$cmdOp_xcatDiskSpace, + 'xcatMgtIp=s' => \$cmdOp_xcatMgtIp, + 'xcatMNIp=s' => \$cmdOp_xcatMNIp, + 'xcatUser=s' => \$cmdOp_xcatUser, + 'zhcpDiskSpace=s' => \$cmdOp_zhcpDiskSpace, + 'zhcpFCPList=s' => \$cmdOp_zhcpFCPList, + 'zhcpNode=s' => \$cmdOp_zhcpNode, + ); + if ( $rc == 0 ) { + print $usage_string; + } + } + + # Apply the command line operands as overrides to the global variables in this script. + applyOverrides(); + + # Special handling for the deobfuscation of the user pw. + if ( defined $glob_xcatUserPw and $glob_xcatUserPw ne '' and $obfuscatePw ) { + # Unobfuscate the password so that we can use it. + $glob_xcatUserPw = decode_base64($val); + } + + # Special processing for ignore messages to convert general severity type + # operands to their numeric value. + if ( $glob_msgsToIgnore{'BYPASS'} ) { + delete $glob_msgsToIgnore{'BYPASS'}; + $glob_msgsToIgnore{'2'} = 1; + } + if ( $glob_msgsToIgnore{'INFO'} ) { + delete $glob_msgsToIgnore{'INFO'}; + $glob_msgsToIgnore{'3'} = 1; + } + if ( $glob_msgsToIgnore{'WARNING'} ) { + delete $glob_msgsToIgnore{'WARNING'}; + $glob_msgsToIgnore{'4'} = 1; + } + if ( $glob_msgsToIgnore{'ERROR'} ) { + delete $glob_msgsToIgnore{'ERROR'}; + $glob_msgsToIgnore{'5'} = 1; + } + +FINISH_setOverrides: + return; +} + + +#------------------------------------------------------- + +=head3 showHelp + + Description : Show the help inforamtion. + Arguments : None. + Returns : None. + Example : showHelp(); + +=cut + +#------------------------------------------------------- +sub showHelp{ + my ($rc, $sev, $extraInfo, @array); + my $msg; + + print "$0 run tests to verify the xCAT installation.\n\n"; + print $usage_string; + print "The following environment variables (indicated by env:) ". + "and command line\noperands (indicated by cmd:) are supported.\n\n"; + foreach my $opHash ( @cmdOps ) { + if ( exists $opHash->{'desc'} ) { + if ( exists $opHash->{'envVar'} ) { + print "env: $opHash->{'envVar'}\n"; + } + if ( exists $opHash->{'opName'} ) { + print "cmd: --$opHash->{'opName'} \n"; + } + if ( exists $opHash->{'separator'} ) { + print "List separator: '$opHash->{'separator'}'\n" + } + if ( exists $opHash->{'default'} ) { + print "Default: $opHash->{'default'}\n"; + } + print wrap( '', '', "Value: $opHash->{'desc'}" ). "\n"; + print "\n"; + } + } + print ( + "Usage notes:\n" . + "1. An input value can be specified in one of three ways, either as: \n" . + " * an environment variable, \n" . + " * an operand on the command line using the -- \n" . + " operand, or \n" . + " * a --moreCmdOps operand. \n" . + " The input value in the -- operand overrides the value \n" . + " specifed by the environment variable. The --moreCmdOps operand \n" . + " overrides both the -- operand and the environment \n" . + " variable. \n" . + "2. The value for an operand that has a list value may have the \n" . + " members of the list separated by one of the indicated \n" . + " 'List separator' operands. The same separator should be used to \n" . + " separator all members of the list. For example, do NOT separate \n" . + " the first and second element by a comma and separate the second . \n" . + " and third element by a semi-colon. \n" . + "3. If blank is an allowed list separator for the operand and is used \n" . + " then the list should be enclosed in quotes or double quotes so that\n" . + " the list is interpretted as a value associated with the operand. \n" . + " You may need to escape the quotes depending on how you are invoking\n" . + " the command. For this reason, it is often better to choose a \n" . + " separator other than blank. \n" + ); + + return; +} + + +#------------------------------------------------------- + +=head3 showPoolInfo + + Description : Show available space for each disk in the pool + Arguments : Disk pool name + Array of disk information for the pool + Returns : None. + Example : showPoolInfo($node, $args); + +=cut + +#------------------------------------------------------- +sub showPoolInfo{ + my ( $diskPool, $diskLines ) = @_; + my $lines; + + $lines = "$diskPool contains the following disks that have space available:"; + foreach my $disk ( @$diskLines ) { + my @diskInfo = split( / /, $disk ); + my $size = convertDiskSize( $diskInfo[2], $diskInfo[4] ); + $lines = $lines . "\nvolid: $diskInfo[1], type: $diskInfo[2], available: $size"; + } + logTest( 'misc', $lines ); +} + + +#------------------------------------------------------- + +=head3 verifyCMAProperties + + Description : Verify key CMA properties are specified + and set the global ROLE property for + user by other functions. + Arguments : None + Returns : 0 - No error + non-zero - Error detected. + Example : $rc = verifyCMAProperties(); + +=cut + +#------------------------------------------------------- +sub verifyCMAProperties{ + logTest( "started", "some key CMA properties"); + + my $out = `cat $glob_versionFileCMA`; + chomp( $out ); + if ( $out ne '' ) { + $glob_versionInfo = "$out"; + # Determine CMA role: Controller or Compute + my $delim = "="; + open( FILE, $glob_applSystemRole ); + while ( ) { + my $line = $_; + if ( $line =~ "^role$delim" or $line =~ "^role $delim" ) { + my @array = split( /$delim/, $line, 2 ); + $array[1] =~ s/^\s+|\s+$//g; # trim both ends of the string + $glob_CMARole = uc( $array[1] ); + } + } + close(FILE); + if ( ' CONTROLLER COMPUTE COMPUTE_MN MN ' !~ / $glob_CMARole / ) { + logTest( "failed", "VCMAP01", $glob_applSystemRole ); + $glob_CMARole = ''; + } + } else { + $glob_versionInfo = "CMO Appliance version unknown"; + logTest( "failed", "VCMAP02", $glob_versionFileCMA ); + } +} + + +#------------------------------------------------------- + +=head3 verifyComputeNodeConnectivity + + Description : Verify the xCAT MN can SSH to the + Compute Node. + Arguments : Node address (IP or hostname) of the + Compute Node. + User underwhich remote exports will be performed. + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : my $rc = verifyComputeNodeConnectivity( $nodeAddress ); + +=cut + +#------------------------------------------------------- +sub verifyComputeNodeConnectivity{ + my ( $nodeAddress, $user ) = @_; + + logTest( 'started', "xCAT MN can ssh to $nodeAddress with user $user." ); + my $out = `ssh -o "NumberOfPasswordPrompts 0" $user\@$nodeAddress pwd`; + my $rc = $? >> 8; + if ( $rc != 0 ) { + logTest( 'failed', "VCNC01", $nodeAddress, $user ); + return 0; # Non-critical error detected + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyDirectorySpace + + Description : Verify disk directory space is sufficient. + Arguments : Array of directories containing: + directory name, + maximum percentage in use, + maximum file size (or empty if we should not check) + Printable node name or ZHCP host name (if remote = 1) + Remote processing flag (1 - use SSH to contact the node) + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : $rc = verifyDirectorySpace( \@dirInfo, $zhcpIP ); + +=cut + +#------------------------------------------------------- +sub verifyDirectorySpace{ + my ( $dirInfoRef, $system, $remote ) = @_; + my @dirInfo = @$dirInfoRef; + my $minAvailableSpace = '100M'; + my $largeFileSize = '30000k'; + my $out; + my $rc; + my @sizes; + + # If system is the ZHCP running on this xCAT MN's system then bypass the test + # because we would have already tested the directories when we ran the tests + # for the xCAT MN. + if ( $system eq $glob_localZHCP ) { + logTest( 'bypassed', "BPVDS01" ); + goto FINISH_verifyDirectorySpace; + } + + foreach my $line ( @dirInfo ) { + chomp( $line ); + $line =~ s/^\s+|\s+$//g; # trim both ends of the string + + my @info = split( ' ', $line ); + if ( ! defined $info[0] or $info[0] eq '' or ! defined $info[1] or $info[1] eq '' ) { + # Empty array item and/or maximum percentage is missing. + next; + } + if ( defined $info[2] and $info[2] ne '' and $info[2] ne '.' ) { + $minAvailableSpace = $info[2]; + } + if ( defined $info[3] and $info[3] ne '' and $info[3] ne '.' ) { + $largeFileSize = $info[3]; + } + + # Special case for old ZHCP servers with a memory backed / directory. + if ( $remote and $info[0] eq '/' ) { + $out = `ssh $system ls /persistent 1>/dev/null 2>/dev/null`; + $rc = $? >> 8; + if ( $rc == 255 ) { + logTest( 'failed', "STN01", $system ); + next; + } elsif ( $rc == 0 ) { + # Validate /persistent directory instead of / + $info[0] = '/persistent'; + } + } + + logTest( 'started', "the file system related to $info[0] on the $system system has sufficient space available." ); + my $sizeTestFailed = 0; + if( $remote ) { + $out = `ssh $system df -h $info[0] | sed '1d' | sed 'N;s/\\n/ /' | awk '{print \$4,\$5}'`; + $rc = $? >> 8; + if ( $rc == 255 ) { + logTest( 'failed', "STN01", $system ); + next; + } + } else { + $out = `df -h $info[0] | sed '1d' | sed 'N;s/\\n/ /' | awk '{print \$4,\$5}'`; + } + chomp( $out ); + if ( $out ) { + @sizes = split( ' ', $out, 2 ); + # Percentage In Use test + $sizes[1] =~ s/\%+$//g; # trim percent from end of the string + if ( $info[1] ne '.' and $sizes[1] > $info[1] ) { + logTest( 'failed', "VDS01", $info[0], $system, $sizes[1], $info[1] ); + $sizeTestFailed = 1; + } + # Minimum Available Size test + if ( $info[2] ne '.' and convertSize( $sizes[0] ) < convertSize( $minAvailableSpace ) ) { + logTest( 'failed', "VDS02", $info[0], $system, $sizes[0], $minAvailableSpace ); + $sizeTestFailed = 1; + } + + if ( $sizeTestFailed == 0 ) { + logTest( 'misc', "The file system related to $info[0] on the $system system is $sizes[1] percent in use with $sizes[0] available." ); + } + } else { + logTest( 'failed', "VDS03", $system, $info[0] ); + $sizeTestFailed = 1; + return 0; + } + + if ( $info[3] ne '.' and $sizeTestFailed == 1 ) { + # Show any large files in the directory space. + logTest( 'started', "the file system related to $info[0] directory on $system system has reasonable size files." ); + if( $remote ) { + $out = `ssh $system find $info[0] -mount -type f -size +$largeFileSize 2>/dev/null -exec ls -lh {} \\; | grep -v -e .so. -e .so -e .jar | awk \'{ print \$9 \": \" \$5 }\'`; + $rc = $? >> 8; + if ( $rc == 255 ) { + logTest( 'failed', "STN01", $system ); + next; + } + } else { + $out = `find $info[0] -mount -type f -size +$largeFileSize 2>/dev/null -exec ls -lh {} \\; | grep -v -e .so. -e .so -e .jar | awk \'{ print \$9 \": \" \$5 }\'`; + } + if ( $out ne '' ) { + $out =~ s/\n+$//g; # remove last newline + logTest( 'failed', "VDS04", $largeFileSize, $out ); + } else { + logTest( 'passed', "" ); + } + } + } + + if ( $system eq 'xCAT MN' and $glob_expectedReposSpace ne '' ) { + if ( convertSize( $sizes[0] ) < convertSize( $glob_expectedReposSpace ) ) { + logTest( 'failed', "VDS05", $sizes[0], $glob_expectedReposSpace ); + } else { + logTest( 'passed', "" ); + } + } + +FINISH_verifyDirectorySpace: + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyDiskPools + + Description : Verify disk pools are defined and have + at least a minimum amount of space. + Arguments : Array of expected disk pools. + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : $rc = verifyDiskPools( $hostNode, $diskpools ); + + lsvm zhcp --diskpoolnames + lsvm zhcp --diskpool pool1 free + lsvm zhcp --diskpool pool1 used + +=cut + +#------------------------------------------------------- +sub verifyDiskPools{ + my ( $hostNode, $diskPools ) = @_; + my $out; + my $zhcpNode; + + if ( exists $glob_hostNodes{$hostNode}{'zhcp'} ) { + $zhcpNode = $glob_hostNodes{$hostNode}{'zhcp'}; + } else { + return 0; + } + + logTest( 'started', "disk pools for host: $hostNode." ); + + $out = `/opt/xcat/bin/lsvm $zhcpNode --diskpoolnames | awk '{print \$NF}'`; + my @definedPools = split /\n/, $out; + + # Warn if no disk pools are defined. + if ( @definedPools == 0 ) { + logTest( 'failed', "VDP03", $hostNode ); + return 0; + } + + logTest( 'misc', "$hostNode has the following disk pools defined: " . join(', ', @$diskPools) . "." ); + + foreach my $diskPool ( @$diskPools ) { + $diskPool = uc( $diskPool ); + + # Verify pool is in the list of pools + if ( grep { $_ eq $diskPool } @definedPools ) { + } else { + logTest( 'failed', "VDP01", $diskPool ); + next; + } + + # Warn if we have very little disk space available + $out = `/opt/xcat/bin/lsvm $zhcpNode --diskpool $diskPool free | grep $zhcpNode | sed '1d'`; + my @disks = split /\n/, $out; + my $numberOfDisks = @disks; + if ( $numberOfDisks == 0 ) { + if (( $diskPool ne 'XCAT' ) and ( $diskPool ne 'XCAT1' )) { + logTest( 'failed', "VDP02", $diskPool ); + } + } else { + showPoolInfo( $diskPool, \@disks ); + } + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyHost + + Description : Verify the Host node is defined properly. + Arguments : Host node + ZHCP node + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : my $rc = verifyHost( $node ); + +=cut + +#------------------------------------------------------- +sub verifyHost{ + my ( $node) = @_; + my %hostInfo = getLsdefNodeInfo( $node ); + + # Verify node is defined + logTest( 'started', "that the host node ($node) is defined in xCAT." ); + my $count = keys %hostInfo; + if ( $count == 0 ) { + logTest( 'failed', "VHN01", $node ); + return 1; # Critical error detected. IVP should exit. + } + + # Verify the 'hcp' is defined for the node + logTest( 'started', "a zHCP is associated with the host node ($node)." ); + if ( $hostInfo{'hcp'} eq '' ) { + logTest( 'failed', "VHN02" ); + return 1; # Critical error detected. IVP should exit. + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyHostNode + + Description : Verify the Host node is defined properly. + Arguments : Host node + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : my $rc = verifyHostNode( $node ); + +=cut + +#------------------------------------------------------- +sub verifyHostNode{ + my ( $node ) = @_; + my %hostInfo = getLsdefNodeInfo( $node ); + + # Verify node is defined + logTest( 'started', "that the host node ($node) is defined in xCAT." ); + my $count = keys %hostInfo; + if ( $count == 0 ) { + logTest( 'failed', "VHN01", $node ); + return 1; # Critical error detected. IVP should exit. + } + + # Verify the 'hcp' is defined for the node + logTest( 'started', "a zHCP is associated with the host node ($node)." ); + if ( $hostInfo{'hcp'} eq '' ) { + logTest( 'failed', "VHN02" ); + return 1; # Critical error detected. IVP should exit. + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyMACUserPrefix + + Description : Verify that the specified MACADDR + user prefix matches the one on the host. + Arguments : Host node + MACADDR user prefix + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : verifyMACUserPrefix( $hostNode, $userPrefix ); + +=cut + +#------------------------------------------------------- +sub verifyMACUserPrefix{ + my ( $hostNode, $userPrefix ) = @_; + $userPrefix = uc( $userPrefix ); + logTest( 'started', "the z/VM system's MACID user prefix matches the one specified in the OpenStack configuration file." ); + + my %nodeInfo = getLsdefNodeInfo($hostNode); + + my $hostUserPrefix = `ssh $nodeInfo{'hcp'} vmcp QUERY VMLAN | sed '1,/VMLAN MAC address assignment:/d' | grep ' MACADDR Prefix:' | awk '{print \$6}'`; + chomp( $hostUserPrefix ); + + if ( $userPrefix ne $hostUserPrefix ) { + logTest( 'failed', "VMUP01", $hostNode, $hostUserPrefix, $userPrefix ); + } else { + logTest( 'passed', "" ); + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyMemorySize + + Description : Verify the virtual machine has + sufficient memory. + Arguments : None + Returns : 0 - No error + non-zero - Error detected. + Example : $rc = verifyMemorySize($node, $args); + +=cut + +#------------------------------------------------------- +sub verifyMemorySize{ + my $vstorMin = '8G'; + my ( $out, $tag, $storSize ); + + # Verify the virtual machine has the recommended virtual storage size. + logTest( 'started', "xCAT MN has a virtual storage size of at least $vstorMin." ); + + $out = `vmcp query virtual storage | grep STORAGE`; + if ( $out eq '' ) { + logTest( 'failed', "VMS01" ); + return 0; + } + + ($tag, $storSize) = split(/=/, $out, 2); + $storSize =~ s/^\s+|\s+$//g; # trim both ends of the string + my $convStorSize = convertSize( $storSize ); + my $convVStorMin = convertSize( $vstorMin ); + + if ( $convStorSize < $convVStorMin ) { + logTest( 'failed', "VMS02", $storSize, $vstorMin ); + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyMiscHostStuff + + Description : Verify miscellaneous items related to + the host. + Arguments : Host node + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : my $rc = verifyMiscHostStuff( $node ); + +=cut + +#------------------------------------------------------- +sub verifyMiscHostStuff{ + my ( $hostNode) = @_; + my $out; + my $rc; + my $zhcpNode; + + if ( exists $glob_hostNodes{$hostNode}{'zhcp'} ) { + $zhcpNode = $glob_hostNodes{$hostNode}{'zhcp'}; + } else { + return 0; + } + + # Verify the signal shutdown time is not too small + logTest( 'started', "the signal shutdown timeout on $hostNode is more than $glob_signalTimeout." ); + my $timeVal; + $out = `ssh $zhcpNode vmcp query signal shutdowntime`; + $rc = $? >> 8; + if ( $rc == 255 ) { + logTest( 'failed', 'STN01', $zhcpNode ); + } elsif ( $out !~ "System default shutdown signal timeout:" ) { + logTest( 'failed', 'VMHS01', $hostNode, $rc, $out ); + } else { + ($timeVal) = $out =~ m/System default shutdown signal timeout: (.*) seconds/; + if ( $timeVal < $glob_signalTimeout ) { + logTest( 'failed', 'VMHS02', $hostNode, $timeVal, $glob_signalTimeout ); + } + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyMnIp + + Description : Verify the xCAT MN Ip is the same one used + by this xCAT MN. + Arguments : xCAT MN IP address or host name + hostName flag, 1 - can be a hostname, 0 - must be an IP address + descriptive name string + subnet mask (optional) + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : my $rc = verifyMnIp( $ip, $possibleHostname, $name, $subnetMask ); + +=cut + +#------------------------------------------------------- +sub verifyMnIp{ + my ( $ip, $possibleHostname, $name, $subnetMask ) = @_; + my ( $out, $rest ); + my $tgtIp = $ip; + my $localRP; + my $addrType = 4; + + # Verify the IP address or hostname is defined for this machine + logTest( 'started', "xCAT MN has an interface for the $name defined as $ip." ); + + if ( $possibleHostname ) { + # Assume the input is a hostname, obtain the IP address associated with that name. + # Look for the name in /etc/hosts + $out = `grep " $ip " < /etc/hosts`; + if ( $out ne '' ) { + ($tgtIp, $rest) = split(/\s/, $out, 2); + } + } + + if ( $tgtIp =~ /:/ ) { + $addrType = 6; + } + + # Verify the IP address is defined + $out=`ip addr show to $tgtIp`; + if ( $out eq '' ) { + logTest( 'failed', "VMNI01", $name, $tgtIp ); + return 0; # Non-critical error detected + } else { + my $inetString; + if ( $addrType == 4 ) { + $inetString = " inet"; + } else { + $inetString = " inet$addrType"; + } + + my @lines= split( /\n/, $out ); + @lines= grep( /$inetString/, @lines ); + if ( @lines == 0 ) { + logTest( 'failed', "VMNI02", $name, $tgtIp ); + return 0; # Non-critical error detected + } + my @ipInfo = split( ' ', $lines[0] ); # split, ignoring leading spaces + my @parts = split( '/', $ipInfo[1] ); + $localRP = $parts[1]; + } + + # Verify the subnet mask matches what is set on the system + if ( defined $subnetMask ) { + logTest( 'started', "xCAT MN's subnet mask is $subnetMask." ); + my $rp = calculateRoutingPrefix( $subnetMask ); + if ( $rp == -1 ) { + logTest( 'failed', "VMNI03", $subnetMask ); + } + elsif ( $rp != $localRP ) { + logTest( 'failed', "VMNI04", $tgtIp, $localRP, $rp, $subnetMask, $name ); + } + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyMnNode + + Description : Verify the xCAT MN node is defined properly. + Arguments : xCAT MN node + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : my $rc = verifyMnNode( $node ); + +=cut + +#------------------------------------------------------- +sub verifyMnNode{ + my ( $node) = @_; + my %mnInfo = getLsdefNodeInfo( $node ); + + # Verify node is defined + logTest( 'started', "xCAT MN node ($node) is defined in xCAT." ); + my $count = keys %mnInfo; + if ( $count == 0 ) { + logTest( 'failed', "VMN01", $node ); + return 0; # Non-critical error detected + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyNodeExists + + Description : Verify that a named node exists in xCAT. + Arguments : Node name + Function of the node (e.g. "zHCP node") + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : verifyNodeExists($node, $function); + +=cut + +#------------------------------------------------------- +sub verifyNodeExists{ + my ( $node, $function ) = @_; + + logTest( 'started', "$function is defined and named $node." ); + my %hash = getLsdefNodeInfo($node); + if ( %hash ) { + logTest( 'passed', "" ); + } else { + logTest( 'failed', "VNE01", $node ); + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyNetworks + + Description : Verify the specified networks are defined + and have the expected VLAN settings. + Arguments : Host node name + Reference to array of networks to be verified + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : my $rc = verifyNetworks($hostNode, $network); + + lsvm zhcp --getnetworknames + lsvm zhcp --getnetwork xcatvsw2 + +=cut + +#------------------------------------------------------- +sub verifyNetworks{ + my ( $hostNode, $networks ) = @_; + my $out; + + my $zhcpNode = $glob_hostNodes{$hostNode}{'zhcp'}; + + foreach my $network ( @$networks ) { + $network = uc( $network ); + + # Split off any VLAN information from the input + my $match; + my @vlans = split( /:/, $network ); + if ( exists $vlans[1] ) { + $network = $vlans[0]; # Remove vlan info from the $network variable + $match = "VLAN Aware"; + } else { + $match = "VLAN Unaware"; + } + logTest( 'started', "$network is defined as a network to $hostNode." ); + + # Obtain the network info + my %switchInfo = getVswitchInfo( $zhcpNode, $network ); + if ( !%switchInfo ) { + logTest( 'failed', "VN01", $network ); + next; # Non-critical error detected, iterate to the next switch + } + + # Verify that the defined network matches the expectations. + logTest( 'started', "$network is $match" ); + if (( $match eq "VLAN Aware" && $switchInfo{'Base'}{'VLAN ID'} != 0 ) || + ( $match eq "VLAN Unaware" && $switchInfo{'Base'}{'VLAN ID'} == 0 )) { + logTest( 'passed', "" ); + } else { + logTest( 'failed', "VN02", $network, $match ); + } + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyProfile + + Description : Verify profile is defined in the z/VM directory. + Arguments : Host node name + Profile to be verified + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : $rc = verifyProfile($hostNode, $profile); + +=cut + +#------------------------------------------------------- +sub verifyProfile{ + my ( $hostNode, $profile ) = @_; + $profile = uc( $profile ); + + logTest( 'started', "$hostNode has the profile ($profile) in the z/VM directory." ); + my $zhcpNode = $glob_hostNodes{$hostNode}{'zhcp'}; + + my $out = `/opt/xcat/bin/chhypervisor $hostNode --smcli 'Image_Query_DM -T $profile'`; + if ( $out !~ "PROFILE $profile" ) { + logTest( 'failed', "VP01", $profile ); + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyREST + + Description : Verify the REST interface is running. + Arguments : None + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : my $rc = verifyREST(); + +=cut + +#------------------------------------------------------- +sub verifyREST{ + logTest( 'started', "REST API is accepting requests from user $glob_xcatUser." ); + my @restOps = (); + my $response = driveREST( $glob_xcatMNIp, $glob_xcatUser, $glob_xcatUserPw, "nodes/$glob_mnNode", "GET", "json", \@restOps ); + #print "Content: " . $response->content . "\n"; + #print "Code: " . $response->code . "\n"; + #print "Message: " .$response->message . "\n"; + + if ( $response->message ne "OK" or $response->code != 200 ) { + logTest( 'failed', "VR01", $response->code, $response->message, $response->content ); + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyUser + + Description : Verify a user is authorized for xCAT MN. + Arguments : User + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : $rc = verifyUser( $user ); + +=cut + +#------------------------------------------------------- +sub verifyUser{ + my ( $user ) = @_; + my $out; + + logTest( 'started', "user ($user) is in the xCAT policy table." ); + $out = `/opt/xcat/bin/gettab name=\'$user\' policy.rule`; + $out =~ s/^\s+|\s+$//g; # trim both ends of the string + if ( $out eq '' ) { + logTest( 'failed', "VU01", $user ); + } elsif ( $out ne 'allow' and $out ne 'accept' ) { + logTest( 'failed', "VU02", $user, $out ); + } else { + logTest( 'passed', "The test is successful. The user ($user) is in the policy table with the rule: \'$out\'." ); + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyVswitchOSAs + + Description : Verify the specified vswitches OSA exist. + Arguments : Hash of Vswitches and their OSAs + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : my $rc = verifyVswitchOSAs( \%vswitchOSAs ); + +=cut + +#------------------------------------------------------- +sub verifyVswitchOSAs{ + my ( $hostNode, $vswitchOSAs ) = @_; + my $out; + + logTest( 'started', "vswitches with related OSAs are valid." ); + + my $zhcpNode = $glob_hostNodes{$hostNode}{'zhcp'}; + + # For each vswitch, verify that it has the specified OSA associated + # with it and it is active or a backup. + foreach my $switch ( keys %$vswitchOSAs ) { + my %switchInfo = getVswitchInfoExtended( $zhcpNode, uc( $switch ) ); + if ( !%switchInfo ) { + logTest( 'failed', "VVO01", $switch ); + next; # Non-critical error detected, iterate to the next switch + } + + my @devices = split( ',', $$vswitchOSAs{$switch} ); + foreach my $device ( @devices ) { + $device = uc( $device ); + + my @osa = split( /\./, $device ); + + # Verify the RDEV + $device = substr( "000$osa[0]", -4 ); # pad with zeroes + if ( $switchInfo{'Real device'}{'RDEVs'} !~ $device ) { + logTest( 'failed', "VVO02", $switch, $device ); + } + } + } + + return 0; +} + + +#------------------------------------------------------- + +=head3 verifyZHCPNode + + Description : Verify the xCAT zHCP node is defined properly. + Arguments : Host node + Returns : 0 - OK, or only a non-critical error detected + non-zero - Critical error detected, IVP should exit. + Example : my $rc = verifyZHCPNode( $node ); + +=cut + +#------------------------------------------------------- +sub verifyZHCPNode{ + my ( $hostNode ) = @_; + my $out; + my $rc; + + logTest( 'started', "that a zHCP node is associated with the host: $hostNode." ); + my $zhcpNode = findZhcpNode( $hostNode ); + if ( ! defined $zhcpNode ) { + logTest( "failed", "VZN05", $hostNode ); + return 1; # Critical error detected. IVP should exit. + } + my %zhcpNodeInfo = getLsdefNodeInfo( $zhcpNode ); + + # Check if this ZHCP node is on the same system as the xCAT MN + if ( exists $zhcpNodeInfo{'ip'} and exists $glob_localIPs{$zhcpNodeInfo{'ip'}} ) { + $glob_localZHCP = $zhcpNode; + } + + # Verify that we can ping zHCP + logTest( 'started', "zHCP node ($zhcpNode) is running." ); + $out = `/opt/xcat/bin/pping $zhcpNode`; + if ( $out !~ "$zhcpNode: ping" ) { + logTest( 'failed', "VZN01", $zhcpNode ); + return 1; # Critical error detected. IVP should exit. + } + + # Obtain and zHCP version information. + $out = `ssh $zhcpNode "[ -e \"/opt/zhcp/version\" ] \&\& cat \"/opt/zhcp/version\""`; + $rc = $? >> 8; + if ( $rc == 255) { + logTest( 'failed', "STN01", $zhcpNode ); + return 1; # Critical error detected. IVP should exit. + } + if ( $out ne '' ) { + chomp( $out ); + $glob_versionInfo = "$glob_versionInfo\nOn $zhcpNode node: $out"; + } else { + $glob_versionInfo = "$glob_versionInfo\nOn $zhcpNode node: ZHCP version level is unknown."; + } + + # Drive a simple rpower request to zHCP which talks to CP + logTest( 'started', "zHCP ($zhcpNode) can handle a simple request to talk to CP." ); + $out = `/opt/xcat/bin/rpower $zhcpNode stat | grep '$zhcpNode:'`; + if ( $out !~ "$zhcpNode: on" ) { + logTest( 'failed', "VZN02", $zhcpNode ); + return 1; # Critical error detected. IVP should exit. + } + + # Drive a simple SMAPI request thru zHCP + logTest( 'started', "zHCP ($zhcpNode) can handle a simple request to SMAPI." ); + $out = `ssh $zhcpNode /opt/zhcp/bin/smcli Query_API_Functional_Level -T dummy 2>&1`; + $rc = $? >> 8; + if ( $rc == 255) { + logTest( 'failed', "STN01", $zhcpNode ); + return 1; # Critical error detected. IVP should exit. + } + if ( $out !~ "The API functional level is" ) { + chomp( $out ); + logTest( 'failed', "VZN04", $zhcpNode, $out ); + return 1; # Critical error detected. IVP should exit. + } + + # Yea, We can talk to SMAPI. Remember that we can use this ZHCP for other tests. + $glob_hostNodes{$hostNode}{'zhcp'} = $zhcpNode; + + # Drive a more complex request to zHCP, an LSVM command + logTest( 'started', "zHCP ($zhcpNode) can handle a more complex xCAT LSVM request." ); + $out = `/opt/xcat/bin/lsvm $zhcpNode | grep '$zhcpNode:'`; + $rc = $? >> 8; + if ( $rc == 255) { + logTest( 'failed', "STN01", $zhcpNode ); + return 1; # Critical error detected. IVP should exit. + } + my $zhcpUserid = uc( $zhcpNodeInfo{'userid'} ); + if ( $out !~ "USER $zhcpUserid" and $out !~ "IDENTITY $zhcpUserid" ) { + logTest( 'failed', "VZN03", $zhcpNode, $zhcpUserid ); + return 1; # Critical error detected. IVP should exit. + } + + return 0; +} + + + +#***************************************************************************** +# Main IVP routine +#***************************************************************************** + +my $rc; +my $out; +my $userid; +my $terminatingError = 0; + +# Update global variables based on overrides from an external perl script. +setOverrides(); + +# Handle help function. +if ( $glob_displayHelp == 1 ) { + showHelp(); + goto FINISH; +} + +# Establish SYSLOG logging for errors if function is desired. +if ( $glob_syslogErrors == 1 ) { + my $user = $ENV{ 'USER' }; + setlogsock( 'unix' ); + openlog( 'xcatIVP', '', 'user'); + syslog( 'info', "Began xcatIVP test" ); +} + +# Detect CMA and obtain the CMA's version information +if ( -e $glob_versionFileCMA ) { + $glob_CMA = 1; + verifyCMAProperties(); +} else { + $glob_CMA = 0; +} + +%glob_localIPs = getLocalIPs(); + +# Obtain the xCAT MN's version information +my $xcatVersion; +if ( -e $glob_versionFileXCAT ) { + $out = `cat $glob_versionFileXCAT`; + chomp( $out ); + $xcatVersion = "$out"; +} else { + $xcatVersion = "xCAT version: unknown"; +} +if ( $glob_versionInfo eq '' ) { + $glob_versionInfo = $xcatVersion; +} else { + $glob_versionInfo = "$glob_versionInfo\n$xcatVersion"; +} + +# Verify the memory size. +if ( $glob_CMA == 1 ) { + verifyMemorySize(); +} + +# Verify xCAT MN's IP address +if ( defined $glob_xcatMNIp) { + verifyMnIp( $glob_xcatMNIp, 1, "xCAT server address" ); +} + +# Verify xCAT MN mgt network IP address +if ( defined $glob_xcatMgtIp) { + verifyMnIp( $glob_xcatMgtIp, 0, "Mgt network IP address", $glob_mgtNetmask ); +} + +# Verify the management node is properly defined in xCAT +if ( defined $glob_mnNode ) { + verifyMnNode( $glob_mnNode ); +} else { + logTest( 'bypassed', "BPVMN01" ); +} + +# Create the list of host node information. +if ( defined $glob_hostNode ) { + $glob_hostNodes{$glob_hostNode}{'input'} = 1; +} else { + my @hostNodes = getHostNodeNames(); + foreach my $hostNode (@hostNodes) { + $glob_hostNodes{$hostNode}{'input'} = 0; + } + if ( keys %glob_hostNodes ) { + $glob_hostNode = split( ' ', keys( %glob_hostNodes ), 1 ); + } +} + +# Verify the host node is properly defined in xCAT and has a ZHCP agent. +foreach my $hostNode ( keys %glob_hostNodes ) {; + $terminatingError = verifyHostNode( $hostNode ); + if ( $terminatingError and scalar keys( %glob_hostNodes ) == 1 ) { + goto FINISH; + } + $terminatingError = 0; + + # Verify the zHCP node is properly defined in xCAT + $terminatingError = verifyZHCPNode( $hostNode ); + if ( $terminatingError and keys( %glob_hostNodes ) == 1 ) { + goto FINISH; + } + $terminatingError = 0; +} + +# Verify the zHCP node specified as input is accessible. +if ( defined $glob_zhcpNode ) { + # Verify the zHCP node is properly defined in xCAT + verifyNodeExists( $glob_zhcpNode, "zHCP node" ); +} + +# Verify the disk pools used to create virtual machines exist +# in the Directory Manager. +if ( @glob_diskpools ) { + verifyDiskPools( $glob_hostNode, \@glob_diskpools ); +} else { + logTest( 'bypassed', "BPVDP01" ); + foreach my $hostNode ( keys %glob_hostNodes ) { + my @pools = getDiskPoolNames( $hostNode ); + verifyDiskPools( $hostNode, \@pools ); + } +} + +# Verify the networks used by deployed virtual machines exist. +if ( @glob_networks ) { + verifyNetworks( $glob_hostNode, \@glob_networks ); +} else { + logTest( 'bypassed', "BPVN01" ); +} + +# Verify the MACADDR user prefix matches the one on the host. +if ( defined $glob_macPrefix ) { + verifyMACUserPrefix( $glob_hostNode, $glob_macPrefix ); +} + +# Verify vswitches with related OSAs are valid. +if ( %glob_vswitchOSAs ) { + verifyVswitchOSAs( $glob_hostNode, \%glob_vswitchOSAs ); +} + +# Verify file system space on the xCAT MN. +verifyDirectorySpace( \@glob_xcatDiskSpace, 'xCAT MN', 0 ); + +# Verify file system space on the zhcp server for each host. +foreach my $hostNode ( keys %glob_hostNodes ) { + verifyDirectorySpace( \@glob_zhcpDiskSpace, $glob_hostNodes{$hostNode}{'zhcp'}, 1 ); +} + +# Verify Host related items for each host in the hostNodes hash. +foreach my $hostNode ( keys %glob_hostNodes ) { + # Verify default user profile is defined to the Directory Manager. + if ( defined $glob_defaultUserProfile ) { + verifyProfile( $hostNode, $glob_defaultUserProfile ); + } + # Verify the signal shutdown interval is appropriate. + if ( $glob_signalTimeout != 0 ) { + verifyMiscHostStuff( $hostNode ); + } +} + +# Verify the xCAT user is defined in the xCAT policy table. +if ( defined $glob_xcatUser ) { + verifyUser( $glob_xcatUser ); +} + +# Verify the REST Interface is responsive. +if ( defined $glob_xcatUser and defined $glob_xcatUserPw and defined $glob_xcatMNIp and defined $glob_mnNode ) { + verifyREST(); +} + +# Verify that xCAT MN can access the compute node. +if ( defined $glob_cNAddress and defined $glob_expUser ) { + verifyComputeNodeConnectivity( $glob_cNAddress, $glob_expUser ); +} else { + logTest( "bypassed", "BPVCNC01" ); +} + +FINISH: +if ( $terminatingError ) { + logTest( "warning", "MAIN01" ); +} + +logTest( "misc", "" ); + +if ( $glob_versionInfo ) { + logTest( "miscNF", "The following versions of code were detected:\n" . $glob_versionInfo ); + logTest( "misc", "" ); +} + +if ( scalar(@glob_failedTests) != 0 ) { + logTest( "misc", "The following tests generated warning(s): " . join(", ", @glob_failedTests) . '.' ); +} +if ( $glob_displayHelp != 1 ) { + logTest( "misc", "$glob_testNum IVP tests ran, " . $glob_totalFailed . " tests generated warnings." ); +} + +if ( $glob_ignoreCnt != 0 ){ + logTest( "misc", "Ignored messages $glob_ignoreCnt times." ); + my @ignoreArray = sort keys %glob_ignored; + my $ignoreList = join ( ', ', @ignoreArray ); + logTest( "misc", "Message Ids of ignored messages: $ignoreList" ); +} + +# Close out our use of syslog +if ( $glob_syslogErrors == 1 ) { + syslog( 'info', "Ended zxcatIVP test" ); + closelog(); +} + +exit 0; diff --git a/xCAT-client/bin/zxcatexport.pl b/xCAT-client/bin/zxcatexport.pl new file mode 100644 index 000000000..1907aa313 --- /dev/null +++ b/xCAT-client/bin/zxcatexport.pl @@ -0,0 +1,333 @@ +#!/usr/bin/perl +############################################################################### +# IBM (C) Copyright 2015, 2016 Eclipse Public License +# http://www.eclipse.org/org/documents/epl-v10.html +############################################################################### +# COMPONENT: zxcatExport +# +# This is a program to save the xCAT tables to the /install/xcatmigrate directory; +# then close and export the /install LVM +# +# The reverse process on the other system can be done if the LVM disks are +# writeable and online. The zxcatmigrate script can be used for that. +# It will issue pvscan, vgimport, vgchange -ay, mkdir, then mount commands. +# +############################################################################### + +use strict; +use warnings; +use Capture::Tiny ':all'; +use Getopt::Long; +use lib '/opt/xcat/lib/perl/'; +use xCAT::TableUtils; +use xCAT::zvmUtils; +$| = 1; # turn off STDOUT buffering + +my $lvmPath = "/dev/xcat/repo"; +my $mountPoint = "/install"; +my $exportDir = "/install/xcatmigrate"; +my $exportTablesDir = "/install/xcatmigrate/xcattables"; +my $exportFcpConfigsDir = "/install/xcatmigrate/fcpconfigs"; +my $exportFcpOtherFilesDir = "/install/xcatmigrate/fcpotherfiles"; +my $exportDocloneFilesDir = "/install/xcatmigrate/doclone"; +my $lvmInfoFile = "lvminformation"; +my $lsdasdInfoFile = "lsdasdinformation"; +my $zvmVirtualDasdInfoFile = "zvmvirtualdasdinformation"; +my $vgName = "xcat"; + +# xCAT table information to be filled in +my $masterIP; +my $xcatNode; +my $hcp; +my $zhcpNode; + +my $version = "1.1"; +my $targetIP = ""; # IP address to get data from +my $skipInstallFiles = 0; # Skip copying any install files +my $skipTables = 0; # Skip copying and installing xcat tables +my $displayHelp = 0; # Display help information +my $versionOpt = 0; # Show version information flag + +my @entries; +my @propNames; +my $propVals; + +my $usage_string = "This script saves the xcat tables in /install/xcatmigrate and\n +then exports the LVM mounted at /install. This should only be used to migrate\n +the /install LVM to a new userid.\n\n + Usage:\n + $0 [ -v ]\n + $0 [ -h | --help ]\n + The following options are supported:\n + -h | --help Display help information\n + -v Display the version of this script.\n"; + + +#------------------------------------------------------- + +=head3 chompall + + Description : Issue chomp on all three input parms (pass by reference) + Arguments : arg1, arg2, arg3 + Returns : nothing + Example : chompall(\$out, \$err, \$returnvalue); + +=cut + +#------------------------------------------------------- +sub chompall{ + my ( $arg1, $arg2, $arg3 ) = @_; + chomp($$arg1); + chomp($$arg2); + chomp($$arg3); +} + +# *********************************************************** +# Mainline. Parse any arguments, usually no arguments +$Getopt::Long::ignorecase = 0; +Getopt::Long::Configure( "bundling" ); + +GetOptions( + 'h|help' => \$displayHelp, + 'v' => \$versionOpt ); + +if ( $versionOpt ) { + print "Version: $version\n"; + exit; +} + +if ( $displayHelp ) { + print $usage_string; + exit; +} + +my $out = ''; +my $err = ''; +my $returnvalue = 0; + +# This looks in the passwd table for a key = sudoer +($::SUDOER, $::SUDO) = xCAT::zvmUtils->getSudoer(); + +# Scan the xCAT tables to get the zhcp node name +# Print out a message and stop if any errors found +@entries = xCAT::TableUtils->get_site_attribute("master"); +$masterIP = $entries[0]; +if ( !$masterIP ) { + print "xCAT site table is missing a master with ip address\n"; + exit; +} + +# Get xcat node name from 'hosts' table using IP as key +@propNames = ( 'node'); +$propVals = xCAT::zvmUtils->getTabPropsByKey('hosts', 'ip', $masterIP, @propNames); +$xcatNode = $propVals->{'node'}; +if ( !$xcatNode ) { + print "xCAT hosts table is missing a node with ip address of $masterIP\n"; + exit; +} + +# Get hcp for xcat from the zvm table using xcat node name +@propNames = ( 'hcp'); +$propVals = xCAT::zvmUtils->getNodeProps( 'zvm', $xcatNode, @propNames ); +$hcp = $propVals->{'hcp'}; +if ( !$hcp ) { + print "xCAT zvm table is missing hcp value for $xcatNode\n"; + exit; +} + +# Get zhcp node name from 'hosts' table using hostname as key +@propNames = ( 'node'); +$propVals = xCAT::zvmUtils->getTabPropsByKey('hosts', 'hostnames', $hcp, @propNames); +$zhcpNode = $propVals->{'node'}; +if ( !$zhcpNode ) { + print "xCAT hosts table is missing a zhcp node with hostname of $hcp\n"; + exit; +} + +#Create the migrate directory and the xcat tables directory. This should not get error even if it exists +print "Creating directory $exportDir\n"; +( $out, $err, $returnvalue ) = eval { capture { system( "mkdir -p -m 0755 $exportDir"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to create $exportDir:\n"; + print "$err\n"; + exit; +} + +print "Creating directory $exportTablesDir\n"; +( $out, $err, $returnvalue ) = eval { capture { system( "mkdir -p -m 0755 $exportTablesDir"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to create $exportTablesDir:\n"; + print "$err\n"; + exit; +} + +print "Creating directory $exportFcpConfigsDir\n"; +( $out, $err, $returnvalue ) = eval { capture { system( "mkdir -p -m 0755 $exportFcpConfigsDir"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to create $exportFcpConfigsDir:\n"; + print "$err\n"; + exit; +} + +print "Creating directory $exportFcpOtherFilesDir\n"; +( $out, $err, $returnvalue ) = eval { capture { system( "mkdir -p -m 0755 $exportFcpOtherFilesDir"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to create $exportFcpOtherFilesDir:\n"; + print "$err\n"; + exit; +} + +print "Creating directory $exportDocloneFilesDir\n"; +( $out, $err, $returnvalue ) = eval { capture { system( "mkdir -p -m 0755 $exportDocloneFilesDir"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to create $exportDocloneFilesDir:\n"; + print "$err\n"; + exit; +} + +#Save the current LVM information +print "Saving current LVM information at $exportDir/$lvmInfoFile \n"; +( $out, $err, $returnvalue ) = eval { capture { system( "vgdisplay '-v' 2>&1 > $exportDir/$lvmInfoFile"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to display LVM information:\n"; + print "$err\n"; + exit; +} + +#Save the current Linux DASD list information +print "Saving current Linux DASD list information at $exportDir/$lsdasdInfoFile \n"; +( $out, $err, $returnvalue ) = eval { capture { system( "lsdasd 2>&1 > $exportDir/$lsdasdInfoFile"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to display Linux DASD list information:\n"; + print "$err\n"; + exit; +} + +#Save the current zVM virtual DASD list information +print "Saving current zVM virtual DASD list information at $exportDir/$zvmVirtualDasdInfoFile \n"; +( $out, $err, $returnvalue ) = eval { capture { system( "vmcp q v dasd 2>&1 > $exportDir/$zvmVirtualDasdInfoFile"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to display zVM virtual DASD list information:\n"; + print "$err\n"; + exit; +} + +#save the xcat tables +print "Dumping xCAT tables to $exportTablesDir\n"; +( $out, $err, $returnvalue ) = eval { capture { system( ". /etc/profile.d/xcat.sh; /opt/xcat/sbin/dumpxCATdb -p $exportTablesDir"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to dump the xcat tables to $exportTablesDir:\n"; + print "$err\n"; + exit; +} + +#Check for and save any zhcp FCP configuration files +print "Checking zhcp for any FCP configuration files\n"; +( $out, $err, $returnvalue ) = eval { capture { system( "ssh $zhcpNode ls /var/opt/zhcp/zfcp/*.conf"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue == 0) { + # Save any *.conf files + print "Copying /var/opt/zhcp/zfcp/*.conf files to $exportFcpConfigsDir\n"; + ( $out, $err, $returnvalue ) = eval { capture { system( "scp $::SUDOER\@$zhcpNode:/var/opt/zhcp/zfcp/*.conf $exportFcpConfigsDir"); } }; + chompall(\$out, \$err, \$returnvalue); + if ($returnvalue) { + print "Error rv:$returnvalue trying to use scp to copy files from $zhcpNode\n"; + print "$err\n"; + exit; + } +} else { + # If file not found, that is an OK error, if others then display error and exit + if (index($err, "No such file or directory")== -1) { + print "Error rv:$returnvalue trying to use ssh to list files on $zhcpNode\n"; + print "$err\n"; + exit; + } +} +# Check for any other zhcp FCP files +( $out, $err, $returnvalue ) = eval { capture { system( "ssh $zhcpNode ls /opt/zhcp/conf/*"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue == 0) { + # Save any files found + print "Copying /opt/zhcp/conf/*.conf files to $exportFcpOtherFilesDir\n"; + ( $out, $err, $returnvalue ) = eval { capture { system( "scp $::SUDOER\@$zhcpNode:/opt/zhcp/conf/* $exportFcpOtherFilesDir"); } }; + chompall(\$out, \$err, \$returnvalue); + if ($returnvalue) { + print "Error rv:$returnvalue trying to use scp to copy /opt/zhcp/conf/* files from $zhcpNode\n"; + print "$err\n"; + exit; + } +} else { + # If file not found, that is an OK error, if others then display error and exit + if (index($err, "No such file or directory")== -1) { + print "Error rv:$returnvalue trying to use ssh to list files on $zhcpNode\n"; + print "$err\n"; + exit; + } +} + +# Check for any doclone.txt file +( $out, $err, $returnvalue ) = eval { capture { system( "ls /var/opt/xcat/doclone.txt"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue == 0) { + # Save any file found + print "Copying /var/opt/xcat/doclone.txt file to $exportDocloneFilesDir\n"; + ( $out, $err, $returnvalue ) = eval { capture { system( "cp /var/opt/xcat/doclone.txt $exportDocloneFilesDir"); } }; + chompall(\$out, \$err, \$returnvalue); + if ($returnvalue) { + print "Error rv:$returnvalue trying to copy /var/opt/xcat/doclone.txt file\n"; + print "$err\n"; + exit; + } +} else { + # If file not found, that is an OK error, if others then display error and exit + if (index($err, "No such file or directory")== -1) { + print "Error rv:$returnvalue trying to copy /var/opt/xcat/doclone.txt file\n"; + print "$err\n"; + exit; + } +} + +#unmount the /install +print "Unmounting $lvmPath\n"; +( $out, $err, $returnvalue ) = eval { capture { system( "umount $lvmPath"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to umount $lvmPath:\n"; + print "$err\n"; + exit; +} + +#mark the lvm inactive +print "Making the LVM $vgName inactive\n"; +( $out, $err, $returnvalue ) = eval { capture { system( "vgchange '-an' $vgName"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to inactivate volume group $vgName:\n"; + print "$err\n"; + exit; +} + +#export the volume group +print "Exporting the volume group $vgName\n"; +( $out, $err, $returnvalue ) = eval { capture { system( "vgexport $vgName"); } }; +chompall(\$out, \$err, \$returnvalue); +if ($returnvalue) { + print "Error rv:$returnvalue trying to export volume group $vgName:\n"; + print "$err\n"; + exit; +} + +print "\nVolume group $vgName is exported, you can now signal shutdown xcat and\n"; +print "have the xcat lvm disks linked RW in the new appliance."; +exit 0; + + diff --git a/xCAT-client/bin/zxcatimport.pl b/xCAT-client/bin/zxcatimport.pl new file mode 100644 index 000000000..3bfc619fb --- /dev/null +++ b/xCAT-client/bin/zxcatimport.pl @@ -0,0 +1,758 @@ +#!/usr/bin/perl +############################################################################### +# IBM (C) Copyright 2015, 2016 Eclipse Public License +# http://www.eclipse.org/org/documents/epl-v10.html +############################################################################### +# COMPONENT: zxcatimport +# +# This is a program to copy the xCAT /install files and the xCAT tables from +# an old XCAT userid to the new appliance +# See usage string below for parameters. +# return code = 0 successful; else error. +# +############################################################################### + +use strict; +use warnings; +use Capture::Tiny ':all'; +use Getopt::Long; +use lib '/opt/xcat/lib/perl/'; +use xCAT::TableUtils; +use xCAT::Table; +use xCAT::zvmUtils; +use xCAT::MsgUtils; + +my $lvmPath = "/dev/xcat/repo"; +my $lvmMountPoint = "/install2"; +my $lvmImportDir = "/install2/xcatmigrate"; +my $lvmImportTablesDir = "/install2/xcatmigrate/xcattables"; +my $lvmImportFcpConfigsDir = "/install2/xcatmigrate/fcpconfigs"; +my $lvmImportFcpOtherFilesDir = "/install2/xcatmigrate/fcpotherfiles"; +my $lvmImportDocloneFilesDir = "/install2/xcatmigrate/doclone"; +my $lvmInfoFile = "lvminformation"; +my $lsdasdInfoFile = "lsdasdinformation"; +my $zvmVirtualDasdInfoFile = "zvmvirtualdasdinformation"; +my $vgName = "xcat"; +my $persistentMountPoint = "/persistent2"; +my @defaultTables = ("hosts", "hypervisor", "linuximage", "mac", "networks", "nodelist", + "nodetype", "policy", "zvm"); + +my $version = "1.0"; +my $out; +my $err; +my $returnValue; + +my $copyLvmFiles = 0; # Copy files under old /install to new /install +my $replaceAllXcatTables = 0; # Copy one or all xcat tables +my $addTableData = '*none*'; # Add node data to one table or default tables +my $copyFcpConfigs = 0; # copy old zhcp fcp *.conf files +my $copyDoclone = 0; # copy old doclone.txt file +my $replaceSshKeys = 0; # replace SSH keys with old xCAT SSH keys +my $displayHelp = 0; # Display help information +my $versionOpt = 0; # Show version information flag +my $rc = 0; + +my $usage_string = "This script will mount or copy the old xcat lvm and old persistent disk\n +to this appliance. The old xCAT LVM volumes and the persistent disk on XCAT userid must \n +be linked in write mode by this appliance. The LVM will be mounted at '/install2' and \n +the persistent disk will be mounted at '/persistent2'.\n +\n +The default is to just mount the persistent and LVM disks.\n +They will be mounted as /install2 and /persistent2\n\n + + Usage:\n + $0 [--addtabledata [tablename]] [--installfiles ] [--doclonefile]\n + [--fcppoolconfigs] [--replacealltables] [--replaceSSHkeys] \n + $0 [ -v ]\n + $0 [ -h | --help ]\n + The following options are supported:\n + --addtabledata [tablename] | ['tablename1 tablename2 ..']\n + Add old xCAT data to specific table(s) or\n + default to the following xCAT tables:\n + hosts, hypervisor, linuximage, mac, networks, nodelist,\n + nodetype, policy, zvm/n + --installfiles Copy any files from old /install to appliance /install\n + --doclonefile Copy any doclone.txt to appliance\n + --fcppoolconfigs Copy any zhcp FCP pool configuration files\n + --replacealltables Replace all xCAT tables with old xCAT tables\n + --replaceSSHkeys Replaces the current xCAT SSH keys with the old xCAT SSH keys\n + -h | --help Display help information\n + -v Display the version of this script.\n"; + +#------------------------------------------------------- + +=head3 chompall + + Description : Issue chomp on all three input parms (pass by reference) + Arguments : arg1, arg2, arg3 + Returns : nothing + Example : chompall(\$out, \$err, \$returnValue); + +=cut + +#------------------------------------------------------- +sub chompall{ + my ( $arg1, $arg2, $arg3 ) = @_; + chomp($$arg1); + chomp($$arg2); + chomp($$arg3); +} + +# =======unit test/debugging routine ========== +# print the return data from tiny capture return +# data for: out, err, return value +sub printreturndata{ + my ( $arg1, $arg2, $arg3 ) = @_; + print "=============================\n"; + print "Return value ($$arg3)\n"; + print "out ($$arg1)\n"; + print "err ($$arg2)\n\n"; +} + +## ---------------------------------------------- ## +## Subroutine to find device name from address + +sub get_disk($) +{ + my ($id_user) = @_; + my $id = hex $id_user; + my $hex_id = sprintf '%x', $id; + my $dev_path = sprintf '/sys/bus/ccw/drivers/dasd-eckd/0.0.%04x', $id; + unless (-d $dev_path) { + $dev_path = sprintf '/sys/bus/ccw/drivers/dasd-fba/0.0.%04x', $id; + } + -d $dev_path or return undef; + my $dev_block = "$dev_path/block"; + unless (-d $dev_block) { + # Try bringing the device online + for (1..5) { + system("echo 1 > $dev_path/online"); + last if -d $dev_block; + sleep(10); + } + } + opendir(my $dir, $dev_block) or return undef; + my $dev; + while ($dev = readdir $dir) { + last unless $dev eq '.' || $dev eq '..'; + } + closedir $dir; + defined $dev ? "/dev/$dev" : undef; +} + +#------------------------------------------------------- + +=head3 mountOldLVM + + Description : This routine will import the old LVM and mount + it at /install2 + Arguments : none + Returns : 0 - LVM mounted or already mounted + non-zero - Error detected. + Example : $rc = mountOldLVM; + +=cut + +#------------------------------------------------------- +sub mountOldLVM{ + + my $saveMsg; + my $saveErr; + my $saveReturnValue; + + #Check for /install2 If already mounted should get a return value(8192) and $err output + #Check $err for "is already mounted on", if found we are done. + print "Checking for $lvmMountPoint.\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "mount $lvmMountPoint"); } }; + chompall(\$out, \$err, \$returnValue); + if (index($err, "already mounted on $lvmMountPoint") > -1) { + print "Old xCAT LVM is already mounted at $lvmMountPoint\n"; + return 0; + } + + print "Importing $vgName\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "/sbin/vgimport $vgName"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + # There could be a case where the LVM has been imported already + # Save this error information and do the next step (vgchange) + $saveMsg = "Error rv:$returnValue trying to vgimport $vgName"; + $saveErr = "$err"; + $saveReturnValue = $returnValue; + } + + print "Activating LVM $vgName\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "/sbin/vgchange -a y $vgName"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + # If the import failed previously, put out that message instead. + if (!defined $saveMsg) { + print "$saveMsg\n"; + print "$saveErr\n"; + return $saveReturnValue; + } else { + print "Error rv:$returnValue trying to vgchange -a y $vgName\n"; + print "$err\n"; + retun $returnValue; + } + } + + print "Making $lvmMountPoint directory\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "mkdir -p $lvmMountPoint"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to mkdir -p $lvmMountPoint\n"; + print "$err\n"; + return $returnValue; + } + + print "Mounting LVM $lvmPath at $lvmMountPoint\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "mount -t ext3 $lvmPath $lvmMountPoint"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to mkdir -p $lvmMountPoint\n"; + print "$err\n"; + return $returnValue; + } + + print "Old xCAT LVM is now mounted at $lvmMountPoint\n"; + return 0; +} + +#------------------------------------------------------- + +=head3 mountOldPersistent + + Description : This routine will look for the old persistent disk and mount + it at /persistent2 + Arguments : none + Returns : 0 - /persistent2 mounted or already mounted + non-zero - Error detected. + Example : $rc = mountOldPersistent; + +=cut + +#------------------------------------------------------- +sub mountOldPersistent{ + + #Check for /persistent2 If already mounted should get a return value(8192) and $err output + #Check $err for "is already mounted on", if found we are done. + print "Checking for $persistentMountPoint.\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "mount $persistentMountPoint"); } }; + chompall(\$out, \$err, \$returnValue); + if (index($err, "already mounted on $persistentMountPoint") > -1) { + print "The old xCAT /persistent disk already mounted at $persistentMountPoint\n"; + return 0; + } + + # search the exported Linux lsdasd file to get the vdev for vdev 100 (dasda) + # should look like: 0.0.0100 active dasda 94:0 ECKD 4096 2341MB 599400 + my $dasda = `cat "$lvmImportDir/$lsdasdInfoFile" | egrep -i "dasda"`; + if (length($dasda) <= 50) { + print "Unable to find dasda information in $lvmImportDir/$lsdasdInfoFile\n"; + return 1; + } + my @tokens = split(/\s+/, $dasda); + my @vdevparts = split (/\./, $tokens[0]); + my $vdev = $vdevparts[2]; + if (!(length($vdev))) { + print "Unable to find a vdev value for dasda\n"; + return 1; + } + + # search the exported zVM virtual dasd list to get the volume id of the disk + # should look like: DASD 0100 3390 QVCD69 R/W 3330 CYL ON DASD CD69 SUBCHANNEL = 000B + my $voliddata = `cat "$lvmImportDir/$zvmVirtualDasdInfoFile" | egrep -i "DASD $vdev"`; + if (length($voliddata) <= 50) { + print "Unable to find volid information for $vdev in $lvmImportDir/$zvmVirtualDasdInfoFile\n"; + return 1; + } + @tokens = split(/\s+/, $voliddata); + my $volid = $tokens[3]; + if (!(length($volid))) { + print "Unable to find a volume id for vdev $vdev\n"; + return 1; + } + + # Now display the current zVM query v dasd to see if they have the volid listed + # and what vdev it is mounted on + ( $out, $err, $returnValue ) = eval { capture { system( "vmcp q v dasd 2>&1"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to vmcp q v dasd\n"; + print "$err\n"; + return $returnValue; + } + + # get the current VDEV the old volid is now using + # If not they they did not update the directory to link to the old classic disk + ( $out, $err, $returnValue ) = eval { capture { system( "echo \"$out\" | egrep -i $volid"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to echo $out\n"; + print "$err\n"; + return $returnValue; + } + if (!(length($out))) { + print "Unable to find a current vdev value for volume id $volid\n"; + return 1; + } + @tokens = split(/\s+/, $out); + my $currentvdev = $tokens[1]; + if (!(length($currentvdev))) { + print "Unable to find a current vdev value for volume id $volid\n"; + return 1; + } + + # Now get the Linux disk name that is being used for this vdev (/dev/dasdx) + my $devname = get_disk($currentvdev); + #print "Devname found: $devname\n"; + if (!(defined $devname)) { + print "Unable to find a Linux disk for address $currentvdev volume id $volid\n"; + return 1; + } + + # Create the directory for the mount of old persistent disk + ( $out, $err, $returnValue ) = eval { capture { system( "mkdir -p -m 0755 $persistentMountPoint"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to create $persistentMountPoint:\n"; + print "$err\n"; + return $returnValue; + } + + # Mount the old persistent disk, must be partition 1 + my $partition = 1; + ( $out, $err, $returnValue ) = eval { capture { system( "mount -t ext3 $devname$partition $persistentMountPoint"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to mount -t ext3 $devname$partition $persistentMountPoint\n"; + print "$err\n"; + return $returnValue; + } + print "The old xCAT /persistent disk is mounted at $persistentMountPoint\n"; + return 0; +} +# *********************************************************** +# Mainline. Parse any arguments +$Getopt::Long::ignorecase = 0; +Getopt::Long::Configure( "bundling" ); + +GetOptions( + 'installfiles' => \$copyLvmFiles, + 'replacealltables' => \$replaceAllXcatTables, + 'addtabledata:s' => \$addTableData, + 'fcppoolconfigs' => \$copyFcpConfigs, + 'doclonefile' => \$copyDoclone, + 'replaceSSHkeys' => \$replaceSshKeys, + 'h|help' => \$displayHelp, + 'v' => \$versionOpt ); + +if ( $versionOpt ) { + print "Version: $version\n"; + exit 0; +} + +if ( $displayHelp ) { + print $usage_string; + exit 0; +} + +# Use sudo or not +# This looks in the passwd table for a key = sudoer +($::SUDOER, $::SUDO) = xCAT::zvmUtils->getSudoer(); + +$rc = mountOldLVM(); +if ($rc != 0) { + exit 1; +} + +$rc = mountOldPersistent(); +if ($rc != 0) { + exit 1; +} + +# ***************************************************************************** +# **** Copy the LVM files from old xCAT LVM to current LVM +if ( $copyLvmFiles ) { + $rc = chdir("$lvmMountPoint"); + if (!$rc) { + print "Error rv:$rc trying to chdir $lvmMountPoint\n"; + exit 1; + } + + $out = `cp -a * /install 2>&1`; + if ($?) { + print "Error rv:$? trying to copy from $lvmMountPoint to /install. $out\n"; + exit $?; + } + print "Old LVM Files copied from $lvmMountPoint to /install\n" ; +} + +# ***************************************************************************** +# **** Replace all the current xCAT tables with the old xCAT tables +if ( $replaceAllXcatTables ) { + print "Restoring old xCAT tables from $lvmImportTablesDir\n"; + # restorexCATdb - restores the xCAT db tables from the directory -p path + ( $out, $err, $returnValue ) = eval { capture { system( ". /etc/profile.d/xcat.sh; /opt/xcat/sbin/restorexCATdb -p $lvmImportTablesDir"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to restore the xcat tables from $lvmImportTablesDir:\n"; + print "$err\n"; + exit 1; + } + # There is a chance the return value is 0, and the $out says "Restore of Database Complete."; + # Yet some of the tables had failures. That information is in $err + if (length($err)) { + print "Some tables did not restore. Error output:\n$err\n "; + exit 1; + } +} + +# ***************************************************************************** +# **** Copy the zhcp FCP config files +if ($copyFcpConfigs) { + # Check if there are any FCP config files to copy + ( $out, $err, $returnValue ) = eval { capture { system( "ls $lvmImportFcpConfigsDir/*.conf"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue == 0) { + # Save any *.conf files + print "Copying $lvmImportFcpConfigsDir/*.conf files to /var/opt/zhcp/zfcp\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "mkdir -p /var/opt/zhcp/zfcp && cp -R $lvmImportFcpConfigsDir/*.conf /var/opt/zhcp/zfcp/"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to use cp to copy files from $lvmImportFcpConfigsDir\n"; + print "$err\n"; + exit 1; + } + } else { + print "There were not any zhcp FCP *.conf files to copy\n"; + } + # Check if there are any other FCP files to copy + ( $out, $err, $returnValue ) = eval { capture { system( "ls $lvmImportFcpOtherFilesDir/*"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue == 0) { + # Save any files + print "Copying $lvmImportFcpOtherFilesDir/* files to /opt/zhcp/conf\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "mkdir -p /opt/zhcp/conf && cp -R $lvmImportFcpOtherFilesDir/* /opt/zhcp/conf/"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to use cp to copy files from $lvmImportFcpOtherFilesDir\n"; + print "$err\n"; + exit 1; + } + } else { + print "There were not any zhcp files from /opt/zhcp/conf to copy\n"; + } +} + +# ***************************************************************************** +# **** Copy the doclone.txt file if it exists +if ($copyDoclone) { + # Check if there is a doclone.txt to copy + ( $out, $err, $returnValue ) = eval { capture { system( "ls $lvmImportDocloneFilesDir/doclone.txt"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue == 0) { + # Save this file in correct location + print "Copying $lvmImportDocloneFilesDir/doclone.txt file to /var/opt/xcat/doclone.txt\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "cp -R $lvmImportDocloneFilesDir/doclone.txt /var/opt/xcat/"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to use cp to copy doclone.txt file from $lvmImportDocloneFilesDir\n"; + print "$err\n"; + exit 1; + } + } else { + print "There was not any doclone.txt file to copy\n"; + } +} + +# ***************************************************************************** +# **** Add old xCAT table data to a table +my $test = length($addTableData); +# Add old xCAT data to an existing table. Admin may need to delete out duplicates using the GUI +if ((length($addTableData)==0) || $addTableData ne "*none*") { + #defaultTables = ("hosts", "hypervisor", "linuximage", "mac", "networks", "nodelist", + # "nodetype", "policy", "zvm"); + my @tables = @defaultTables; + if (length($addTableData)>1 ) { + # use the table specified + @tables = (); + @tables = split(' ',$addTableData); + } + foreach my $atable (@tables) { + print "Adding data to table $atable\n"; + # the current xCAT code we have does not support the -a option + # use xCAT::Table functions + + my $tabledata = `cat "$lvmImportTablesDir\/$atable\.csv"`; + if (length($tabledata) <= 10) { + print "Unable to find table information for $atable in $lvmImportTablesDir\n"; + return 1; + } + # remove the hash tag from front + $tabledata =~ s/\#//; + my @rows = split('\n', $tabledata); + my @keys; + my @values; + my $tab; + # loop through all the csv rows, first are the header keys, rest is data + foreach my $i (0 .. $#rows) { + my %record; + #print "row $i data($rows[$i])\n"; + if ($i == 0) { + @keys = split(',', $rows[0]); + #print "Keys found:(@keys)\n"; + } else { + # now that we know we have data, lets create table + if (!defined $tab) { + $tab = xCAT::Table->new($atable, -create => 1, -autocommit => 0); + } + # put the data into the new table. + @values = split(',', $rows[$i]); + foreach my $v (0 .. $#values) { + # Strip off any leading and trailing double quotes + $values[$v] =~ s/"(.*?)"\z/$1/s; + $record{$keys[$v]} = $values[$v]; + #print "Row $i matches key $keys[$v] Value found:($values[$v])\n"; + } + } + # write out the row if any keys added to the hash + if (%record) { + my @dbrc = $tab->setAttribs(\%record, \%record); + if (!defined($dbrc[0])) { + print "Error ($dbrc[1]) setting database for table $atable"; + $tab->rollback(); + $tab->close; + exit 1; + } + } + } + # if we made it here and $tab is defined, commit it. + if (defined $tab) { + $tab->commit; + print "Data successfully added and committed to $atable.\n*****! Remember to check the table and remove any rows not needed\n"; + } + }#end for each table +} + +# ***************************************************************************** +# **** Replace the xCAT SSH key with the old xCAT SSH key + +# First copy the current keys and copy the old xCAT keys into unique file names +if ($replaceSshKeys) { + # Make temp file names to hold the current and old ssh public and private key + my $copySshKey= `/bin/mktemp -p /root/.ssh/ id_rsa.pub_XXXXXXXX`; + chomp($copySshKey); + my $copySshPrivateKey= `/bin/mktemp -p /root/.ssh/ id_rsa_XXXXXXXX`; + chomp($copySshPrivateKey); + + # Make temp files for the RSA backup keys in appliance + my $copyHostSshKey= `/bin/mktemp -p /etc/ssh/ ssh_host_rsa_key.pub_XXXXXXXX`; + chomp($copyHostSshKey); + my $copyHostSshPrivateKey= `/bin/mktemp -p /etc/ssh/ ssh_host_rsa_key_XXXXXXXX`; + chomp($copyHostSshPrivateKey); + + # Save old keys in unique names + my $oldSshKey= `/bin/mktemp -p /root/.ssh/ id_rsa.pub_OldMachineXXXXXXXX`; + chomp($oldSshKey); + my $oldSshPrivateKey= `/bin/mktemp -p /root/.ssh/ id_rsa_OldMachineXXXXXXXX`; + chomp($oldSshPrivateKey); + + print "Making backup copies of current xCAT SSH keys\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "cp \-p /root/.ssh/id_rsa.pub $copySshKey"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to use cp to copy /root/.ssh/id_rsa.pub to $copySshKey\n"; + print "$err\n"; + exit 1; + } + ( $out, $err, $returnValue ) = eval { capture { system( "cp \-p /root/.ssh/id_rsa $copySshPrivateKey"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to use cp to copy /root/.ssh/id_rsa to $copySshPrivateKey\n"; + print "$err\n"; + exit 1; + } + + # Save appliance backup keys + ( $out, $err, $returnValue ) = eval { capture { system( "cp \-p /etc/ssh/ssh_host_rsa_key.pub $copyHostSshKey"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to use cp to copy /etc/ssh/ssh_host_rsa_key.pub to $copyHostSshKey\n"; + print "$err\n"; + exit 1; + } + ( $out, $err, $returnValue ) = eval { capture { system( "cp \-p /etc/ssh/ssh_host_rsa_key $copyHostSshPrivateKey"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to use cp to copy /etc/ssh/ssh_host_rsa_key to $copyHostSshPrivateKey\n"; + print "$err\n"; + exit 1; + } + + # Copy the old public key and make sure the permissions are 644 + print "Copying old xCAT SSH keys (renamed) from /persistent2 to /root/.ssh\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "cp /persistent2/root/.ssh/id_rsa.pub $oldSshKey"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to use cp to copy /persistent2/root/.ssh/id_rsa.pub to $oldSshKey\n"; + print "$err\n"; + exit 1; + } + ( $out, $err, $returnValue ) = eval { capture { system( "chmod 644 $oldSshKey"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to chmod 644 $oldSshKey\n"; + print "$err\n"; + exit 1; + } + + # Copy the private key and make sure the permissions are 600 + ( $out, $err, $returnValue ) = eval { capture { system( "cp /persistent2/root/.ssh/id_rsa $oldSshPrivateKey"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to use cp to copy /persistent2/root/.ssh/id_rsa to $oldSshPrivateKey\n"; + print "$err\n"; + exit 1; + } + ( $out, $err, $returnValue ) = eval { capture { system( "chmod 600 $oldSshPrivateKey"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to chmod 600 $oldSshPrivateKey\n"; + print "$err\n"; + exit 1; + } + + # Now compare the IP of xCAT to zHCP . + # If they are the same, then only the private and public key in xCAT needs to be changed. + # If zhcp is on a different machine, then the first thing to be done is add the old xCAT key + # to the zhcp authorized_keys files, then do the switch on xcat of public and private keys + + my $xcatIp; + my $zhcpIp; + my $zhcpHostName; + my $zhcpNode; + my $xcatNodeName; + my @propNames; + my $propVals; + my @entries; + my $rc = 0; + + # Scan the xCAT tables to get the zhcp node name + # Print out a message and stop if any errors found + @entries = xCAT::TableUtils->get_site_attribute("master"); + $xcatIp = $entries[0]; + if ( !$xcatIp ) { + print "xCAT site table is missing a master with ip address\n"; + exit 1; + } + + # Get xcat node name from 'hosts' table using IP as key + @propNames = ( 'node'); + $propVals = xCAT::zvmUtils->getTabPropsByKey('hosts', 'ip', $xcatIp, @propNames); + $xcatNodeName = $propVals->{'node'}; + if ( !$xcatNodeName ) { + print "xCAT hosts table is missing a node with ip address of $xcatIp\n"; + exit 1; + } + + # Get hcp hostname for xcat from the zvm table using xcat node name + @propNames = ( 'hcp'); + $propVals = xCAT::zvmUtils->getNodeProps( 'zvm', $xcatNodeName, @propNames ); + $zhcpHostName = $propVals->{'hcp'}; + if ( !$zhcpHostName ) { + print "xCAT zvm table is missing hcp value for $xcatNodeName\n"; + exit 1; + } + + # Get zhcp IP and node from 'hosts' table using hostname as key + @propNames = ( 'ip', 'node'); + $propVals = xCAT::zvmUtils->getTabPropsByKey('hosts', 'hostnames', $zhcpHostName, @propNames); + $zhcpIp = $propVals->{'ip'}; + if ( !$zhcpIp ) { + print "xCAT hosts table is missing a zhcp node IP with hostname of $zhcpHostName\n"; + exit 1; + } + $zhcpNode = $propVals->{'node'}; + if ( !$zhcpNode ) { + print "xCAT hosts table is missing a zhcp node with hostname of $zhcpHostName\n"; + exit 1; + } + + if ($zhcpIp eq $xcatIp) { + print "xCAt and zhcp are on same IP, only need to update public and private keys\n"; + } else { + # Need to append the old SSH key to zhcp authorized_keys file + my $target = "$::SUDOER\@$zhcpHostName"; + print "Copying old SSH key to zhcp\n"; + ( $out, $err, $returnValue ) = eval { capture { system( "scp $oldSshKey $target:$oldSshKey"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to use scp to copy $oldSshKey to zhcp $oldSshKey\n"; + print "$err\n"; + exit 1; + } + # Adding the old SSH key to the authorized_keys file + # Make a copy of the old authorized_users file + my $suffix = '_' . substr($oldSshKey, -8); + ( $out, $err, $returnValue ) = eval { capture { system( "ssh $target cp \"/root/.ssh/authorized_keys /root/.ssh/authorized_keys$suffix\""); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to make a copy of the /root/.ssh/authorized_keys file\n"; + print "$err\n"; + exit 1; + } + # Add the key to zhcp authorized_keys file + ( $out, $err, $returnValue ) = eval { capture { system( "ssh $target cat \"$oldSshKey >> /root/.ssh/authorized_keys\""); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to append zhcp $oldSshKey to /root/.ssh/authorized_keys\n"; + print "$err\n"; + exit 1; + } + } + # We need to replace the xCAT public and private key with the old keys + # and add the old key to the authorized_keys on xCAT + ( $out, $err, $returnValue ) = eval { capture { system( "cat $oldSshKey >> /root/.ssh/authorized_keys"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to append xcat $oldSshKey to /root/.ssh/authorized_keys\n"; + print "$err\n"; + exit 1; + } + ( $out, $err, $returnValue ) = eval { capture { system( "cp \-f $oldSshKey /root/.ssh/id_rsa.pub"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to replace the /root/.ssh/id_rsa.pub with the $oldSshKey\n"; + print "$err\n"; + exit 1; + } + ( $out, $err, $returnValue ) = eval { capture { system( "cp \-f $oldSshPrivateKey /root/.ssh/id_rsa"); } }; + chompall(\$out, \$err, \$returnValue); + #printreturndata(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to replace the /root/.ssh/id_rsa with the $oldSshPrivateKey\n"; + print "$err\n"; + exit 1; + } + # Copy old keys into appliance saved key locations + ( $out, $err, $returnValue ) = eval { capture { system( "cp \-f $oldSshKey /etc/ssh/ssh_host_rsa_key.pub"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to replace the /etc/ssh/ssh_host_rsa_key.pub with the $oldSshKey\n"; + print "$err\n"; + exit 1; + } + ( $out, $err, $returnValue ) = eval { capture { system( "cp \-f $oldSshPrivateKey /etc/ssh/ssh_host_rsa_key"); } }; + chompall(\$out, \$err, \$returnValue); + if ($returnValue) { + print "Error rv:$returnValue trying to replace the /etc/ssh/ssh_host_rsa_key with the $oldSshPrivateKey\n"; + print "$err\n"; + exit 1; + } + + print "Old xCAT SSH keys have replaced current SSH keys. Previous key data saved in unique names.\n"; +} + +exit 0; diff --git a/xCAT-client/openstack.versions b/xCAT-client/openstack.versions new file mode 100644 index 000000000..eedfd2571 --- /dev/null +++ b/xCAT-client/openstack.versions @@ -0,0 +1,9 @@ +# This file maps the OpenStack version numbers to their OpenStack release name. +15. OCATA +14. NEWTON +13. MIKATA +12. LIBERTY +2015. KILO +2014.2. JUNO +2014.1. ICEHOUSE +2013.2. HAVANA diff --git a/xCAT-client/xCAT-client.spec b/xCAT-client/xCAT-client.spec index 44235d213..d89b9baa5 100644 --- a/xCAT-client/xCAT-client.spec +++ b/xCAT-client/xCAT-client.spec @@ -16,6 +16,9 @@ BuildRoot: /var/tmp/%{name}-%{version}-%{release}-root %define pcm %(if [ "$pcm" = "1" ];then echo 1; else echo 0; fi) %define notpcm %(if [ "$pcm" = "1" ];then echo 0; else echo 1; fi) +%define s390x %(if [ "$s390x" = "1" ];then echo 1; else echo 0; fi) +%define nots390x %(if [ "$s390x" = "1" ];then echo 0; else echo 1; fi) + # AIX will build with an arch of "ppc" %ifos linux BuildArch: noarch @@ -69,6 +72,10 @@ cp share/xcat/rvid/* $RPM_BUILD_ROOT/%{prefix}/share/xcat/rvid/ chmod 755 $RPM_BUILD_ROOT/%{prefix}/share/xcat/rvid/* %endif +%if %s390x +cp openstack.versions $RPM_BUILD_ROOT/%{prefix} +chmod 644 $RPM_BUILD_ROOT/%{prefix}/openstack.versions +%endif cp bin/* $RPM_BUILD_ROOT/%{prefix}/bin chmod 755 $RPM_BUILD_ROOT/%{prefix}/bin/* cp sbin/* $RPM_BUILD_ROOT/%{prefix}/sbin diff --git a/xCAT/xcat.conf.apach22 b/xCAT/xcat.conf.apach22 new file mode 100644 index 000000000..89fbd115b --- /dev/null +++ b/xCAT/xcat.conf.apach22 @@ -0,0 +1,29 @@ +# +# This configuration file allows a diskfull install to access the install images +# via http. It also allows the xCAT documentation to be accessed via +# http://localhost/xcat-doc/ +# Updates to xCAT/xcat.conf should also be made to xCATsn/xcat.conf +# +AliasMatch ^/install/(.*)$ "/install/$1" +AliasMatch ^/tftpboot/(.*)$ "/tftpboot/$1" + + + Options Indexes +FollowSymLinks +Includes MultiViews + AllowOverride None + Order allow,deny + Allow from all + + + Options Indexes +FollowSymLinks +Includes MultiViews + AllowOverride None + Order allow,deny + Allow from all + + +Alias /xcat-doc "/opt/xcat/share/doc" + + Options Indexes + AllowOverride None + Order allow,deny + Allow from all +