#!/p/sy/subsys/scripts/wermit + #!/p/kd/fdc/solaris9/wermit + #!/p/kd/fdc/RHEL4/wermit + # # IMPORTANT: The first line must indicate the path to C-Kermit 8.0.212 or 9.0 # and there must be a + sign after the path, separated by a space. # # ilosetup: C-Kermit Script to configure an HP server through its # iLO management interface. # # author: Frank da Cruz, Columbia University, 2009 # .version = 1.07 .date = 20091113 # Created: # 1.01 20090428 # Modified: # 1.02 20090916: Prompt for username if not in environment. # 1.03 20091028: Remove IF WRITEABLE check for log directory # 1.04 20091103: Handle console login failure more gracefully; # Allow empty password in serials file. # 1.05 20091104: Add 'usepassword' and 'nopassword' options; # Try both in specified order. # 1.06 20091104: Handle login failure on Telnet connections. # 1.07 20091113: Enable remote console acquire # # Usage: [ ilo_username=x ] [ ilo_userpass=y ] ilosetup hostname [ keywords ] # # After the hostname you can put zero or more of these keywords: # # connect - make an interactive connection after logging in # debug - create a debug log (very big, only for Frank) # usepassword - try to log in first with a serials file password # nopassword - try to log in first with a blank password # nopoweroff - don't power the host machine off at the end # noreset - no reset at end # poweroff - power the host machine off at the end (default) # quiet - only show error, warning, or status messages on the screen # reset - reset at end (default) # verbose - watch the script play on the screen (default) # # The keywords can be abbreviated to any unique left substring. # # Examples for host fillmore: # ilosetup fillmore # ilosetup fillmore poweroff # ilosetup fillmore p (same as poweroff) # ilosetup fillmore p q (poweroff and run quietly) # # The hostname should match (case independently) a hostname in the HP # serials file, which is just the first part of the first field of the # IP hostname, e.g. fillmore instead of fillmore-console.cc.columbia.edu. # But if you do include the extra fields, the script allows for it. # # Exit status: # 0 on success; 1 on failure. # # Creates in the log directory (logdirectory defined below): # A session log file xxx-session.log (xxx is the hostname) # (the log directory itself is created if it doesn't exist) # # Requires: C-Kermit 8.0.212 or later because it uses some recent features. # Version 8.0.211 or earlier lack at least the following, which are used in # this script: # . FSEEK /FIND:string # . The \fkwval() function # (The script could be recoded at some effort to use older ways of doing # the same things.) # # The binary used to execute the script is the one shown above in the # "kerbang line". Of course you can move the binary to anywhere you want, # or rename it, but then you have to change the kerbang line. # # Other dependencies: # . The HP serial numbers file (path defined a few lines down) # . That the first field in a serials file record is the simple hostname # (e.g. FILLMORE, not FILLMORE-CONSOLE or FILLMORE-CONSOLE.CC.COLUMBIA.EDU) # . That the corresponding iLO console password is the 6th field # (if there is no password, the 6th field should be empty). # ############################################################################### if < \v(version) 800212 { # Check Kermit version exit 1 "\m(myname): C-Kermit 8.0.212 or later required" } # Operating parameters and defaults (that can be changed)... .dodebug = 0 if def \$(DEBUG) .dodebug = 1 if \m(dodebug) { echo DEBUGGING ON echo ILO_USERID=[\$(ilo_userid)] echo ILO_USERPASS=[\$(ilo_userpass)] } set quiet off # Allow messages (for foreground use) # "Constant" definitions (that can be changed)... .iloprompt = ">hpiLO-> " # HP iLO console prompt .logdirectory = /var/log/ilosetup # Directory for session log .outputpacing = 30 # Intercharter pacing for OUTPUT (msec) .poweroff = 0 # Whether to power off at end .connect = 0 # Make in interactive connection .reset = 1 # Reset at end .dopassword = 1 # Use password from serials file .serialsfile = /p/sy/subsys/HP/serials # Path of blade serial numbers file .hostsuffix = -console.cc.columbia.edu # Hostname suffix .loginname = Administrator # iLO login name if equ "\v(user)" "fdc" { # Development / testing .logdirectory = ~/net/shrimp # (dev/test) # if \m(dodebug) .serialsfile = ./serials } .myname := \fbasename(\v(cmdfile)) # Name of this script without path if \m(dodebug) { # dev/test show args } # Define command-line option keywords dcl \&k[] = poweroff nopoweroff verbose quiet - connect debug reset noreset usepassword nopassword array sort &k # Alphabetize them for table lookup # If command line is invalid give usage message and exit def usage { # Give usage message and exit .verbose = "verbose" .turnoff = "nopoweroff" .doreset = "noreset" .usepass = "nopassword" echo - "Usage: [ ilo_username=x ] [ ilo_userpass=y ] \m(myname) hostname [ options ]" echo "Options:" show array k if quiet .verbose = "quiet" if \m(poweroff) .turnoff = "poweroff" if \m(reset) .doreset = "reset" if \m(dopassword) .usepass = "usepassword" echo "Default options: \m(verbose) \m(turnoff) \m(doreset) \m(usepass)" echo "Optional environment variables:" echo " ilo_userid: user ID to create" echo " ilo_userpass: password for user ID" echo " DEBUG=1: turn on debugging" echo "If created user ID and password are not found in the environment," echo "you are prompted for them." echo exit 1 } # Parse and validate command line if not def \%1 { echo "FATAL - A hostname is required" usage # A hostname is required } .hostname := \flower(\fword(\%1,1,.)) # First field of hostname argument .\%t := \findex(-console,\m(hostname)) # If "-console" found in hostname... if \%t .hostname := \s(hostname[1:\%t-1]) # remove it. .fullhost := \m(hostname)\m(hostsuffix) # Make full IP hostname # Parse optional command-line keywords... set flag off # No debugging unless requested if def \m(dodebug) set flag on # Can request with environment var while defined \%2 { # While we still have trailing args... .tmp := \%2 .\%2 := \ftablelook(\%2,&k) # Look up this keyword and get index if < \%2 1 { # Not found or ambiguous echo "Invalid command-line option: '\m(tmp)'" usage } .\%2 := \&k[\%2] # Resolve index to keyword itself switch \%2 { # Handle the keyword :debug # Debug set flag on break :connect # Connect (interactive session) .connect = 1 break :poweroff # Leave power off at end .poweroff = 1 break :nopoweroff # Leave power on at end .poweroff = 0 break :quiet # Silent running (don't watch session) set quiet on set input echo off break :verbose # Show session on screen set quiet off set input echo on :reset # Reset at end (default) .reset = 1 break :noreset # No reset at end .reset = 0 break :usepassword # Use password from serials file .dopassword = 1 break :nopassword # Use empty password .dopassword = 0 } shift # Shift argument list to the left } # Macro definitions... define logit { # Write a message to session log if not open session-log end 0 .\%1 := \frpad(\%1,7,.) # Make fields line up writeln session # Make sure we're on a new line writeln session "%% \%1 - \m(myname): \v(timestamp) [\m(hostname)] - \%2" } define message { # Print and log a progress message if not quiet echo # To avoid mixing up screen text echo "MESSAGE - \%1" # Write message to screen (stdout) logit MESSAGE "\%1" # and to session log } define warn { # Print and log warning message if not quiet echo # To avoid mixing up screen text echo "WARNING - \%1" # Write it to to stdout logit WARNING "\%1" # and to session log } define fatal { # Fatal error .rc = 1 # Failure exit code echo "FATAL - \%1" # Write error message to screen if \m(haveconnection) { # If we have a connection if not equ "\m(state)" "iLO" { # but w're not in iLO... output \{27}\{40} # ESC+( - Escape back to iLO input 8 \m(iloprompt) # and wait 8 sec for the prompt } } logit FATAL "\%1" # write message to session log logit INFO "Most recent error: \v(errstring)" goto DONE } define getiloprompt { # Get iLO prompt if not def \%1 .\%1 = 30 # Default timeout (secs) if not numeric \%1 fatal "Non-numeric INPUT timeout" input \%1 \m(iloprompt) # Wait for the prompt if fail fatal "Timed out waiting for iLO CLI prompt." } define ilocommand { # Send an iLO command and get response local cmd # Local (stack) variable .cmd := \fword(\%1,1) # First word of command msleep 10 # Pause 10 msec just in case lineout \%1 # Send the full command if fail fatal "Output failed" # Check for i/o error clear input # Empty the INPUT result buffer getiloprompt \%2 # Get response and next iLO prompt # The "power on/off" and "vsp" commands give no response... if not equ "\%1" "power" { # But "power" by itself does... if ( equ "\m(cmd)" "power" || equ "\m(cmd)" "vsp" ) end 0 } .status = 9 # Dummy inital status value .tmp := \freplace(\v(input),\13,) # Strip carriage returns (but not LFs) .lines := \fsplit(\m(tmp),&a,\10) # Split captured text into lines for i 1 lines 1 { # Loop through lines void \fkwval(\&a[i]) # to get status and message values... } if not numeric \m(status) .status = 9 # Just in case if == \m(status) 0 end 0 # If status was 0 then OK if ( equ "\m(cmd)" "create" && equ "\&a[5]" "Duplicate login name." ) { end 0 # This is not an error } fatal "ILO COMMAND \%1: \m(error_tag)" # Fail with nonzero status } define rbsucommand { # Send a command to RBSU and get next rbsu> prompt. local i sleep pacing max msleep 10 # Pause 10 msec just in case lineout \%1 # Send the command # Check for error response or next rbsu> prompt. # Note: RBSU commands have no consistent error indication; # There may be some error responses that are not caught here... minput 30 {rbsu>} {Invalid Command} {Incomplete Command} {Invalid Option} if fail fatal "RBSU: Timeout after [\%1]" if == \v(minput) 1 end 0 # No error message - done. warn "RBSU COMMAND FAILURE [\%1]" .max = 10 for i 1 \m(max) 1 { # Try again this many times. increment pacing 40 # Slower pacing each time. warn "RETRYING WITH PACING = \m(pacing) msec..." msleep 10*i # Pause a little longer each time lineout \%1 # Send the command minput 30 {rbsu>} - {Invalid Command} {Incomplete Command} {Invalid Option} if fail fatal "RBSU: Timeout [\%1]" if == \v(minput) 1 break # No error message - done. } if > \m(i) \m(max) fatal "RBSU command error" warn "RBSU COMMAND RECOVERY OK" } def xsleep { # (for debugging display) if not def \%1 .\%1 = 1 if quiet { sleep \%1 } else { local i for i 1 \%1 1 { sleep 1, xecho . } echo } } # Execution begins here: Get host password from HP serial numbers file... .haveconnection = 0 # No connection yet set input cancellation off # Essential because of Esc sequences set exit warning off # Allow exit without warning if \m(dodebug) directory \m(serialsfile) # Check and open serials file if not exist \m(serialsfile) exit 1 "\m(serialsfile) not found" if not readable \m(serialsfile) exit 1 "\m(serialsfile) not readable" fopen /read \%c \m(serialsfile) # Open serials file if fail fatal # Check for failure # Seek to line with this hostname fseek /find:"^\fupper(\m(hostname))[\9\32]" \%c 0 if fail fatal "Hostname [\m(hostname)] not found in HP serials file" fread /line \%c line # Read the line fclose \%c # Close the file .\%n := \fsplit(\m(line),&a,) # Split the line into words .pass := \&a[6] # The sixth word is the host password if \m(dopassword) { # 2-element password array dcl \&p[2] = \m(pass) "" # Try serials file password first } else { dcl \&p[2] = "" \m(pass) # Try blank password first } if \m(dodebug) show array p # If this is a configuration run open the log file if not \m(connect) { if not directory \m(logdirectory) { # Check if log directory exists fatal "Log directory \m(logdirectory) not found or not a directory" } cd \m(logdirectory) # cd to the log directory if fail fatal "cd \m(logdirectory)" # failure is fatal .sessionlog := \m(logdirectory)/\m(hostname)-session.log # Session log name log session \m(sessionlog) # Open and start the session log } else { # Make session log in current directory log session \m(hostname)-connect.log } if fail warn "FAILURE TO OPEN \m(sessionlog)" message "BEGIN ilosetup \m(version) \m(date)" # Open the connection and log in .protocol = "none" # No protocol yet message "TRYING TELNET..." set host \m(fullhost) # Try Telnet (port 23) first if success { # Telnet OK - send user name input 10 "Login Name: " if fail fatal "Telnet Login Name timeout" lineout \m(loginname) input 10 "password: " if fail fatal "Telnet password timeout" .protocol = telnet message "PROTOCOL TELNET" } else { # No telnet - try SSH message "TELNET FAILED - TRYING SSH..." set host /pty ssh -e none -l \m(loginname) \m(fullhost) if fail fatal "SSH \m(fullhost) failed." # Watch out for RSA key fingerprint complaint and "are you sure?" prompt... minput 20 "password: " "continue connecting (yes/no)? " if fail fatal "No response on SSH connection to \m(fullhost)" if == \v(minput) 2 { lineout "yes" input 20 "password: " if fail fatal "SSH password timeout" } .protocol = "ssh" message "PROTOCOL SSH" } # If we arrive here we should have the password prompt. # NOTE: This logic will need to be changed if it is possible to log in # via SSH (or some secure form of Telnet) without a password. .haveiloprompt = 0 for i 1 2 1 { lineout \&p[i] # Send console password if fail fatal "Output password to \m(fullhost) failed." minput 30 \m(iloprompt) "Permission denied" "Invalid login." if fail fatal "Timed out waiting for iLO CLI prompt." if == \v(minput) 1 { .haveiloprompt = 1 break } if \m(dopassword) { message "Password from serials file failed - trying empty password..." } else { message "Empty password failed - trying password from serials file..." } # After bad password Telnet prompts again for the username - ssh does not. if eq "\m(protocol)" "telnet" { input 10 "Login Name: " if fail fatal "Telnet Login Name timeout" lineout \m(loginname) } input 2 "password: " if not quiet echo if == \m(i) 2 fatal "Password not accepted" if quiet continue } if not \m(haveiloprompt) fatal "iLO console login failure" # CONNECT - Go interactive and do not execute the rest of the script. .haveconnection = 1 # Have connection if \m(connect) { # Interactive session requested lineout # Send a carriage return connect # Connect to remote if not open connection exit # Exit from Kermit if connection closed stop # Otherwise don't exit } # Automated configuration run... # Prompt for created-user ID if it wasn't in the environment .userid := \$(ilo_userid) .userpass := \$(ilo_userpass) while not defined userid { # Prompt for created user ID askq userid "User ID for created account: " } # Prompt for created-user password if it wasn't in the environment while not defined userpass { askq /echo:* userpass "Password for created account: " } # Send iLO commands... .state = iLO ilocommand "create /map1/accounts1 username=\m(userid) - password=\m(userpass) group=admin,config,oemhp_vm,oemhp_rc,oemhp_power" ilocommand "set /map1/config1 oemhp_telnetenable=yes" ilocommand "set /system1 oemhp_server_name=\m(hostname)" ilocommand "set /map1/config1 oemhp_rc_acquire=yes" # 1.07 lineout "power" # Check power state minput 20 {server power is currently: On} {server power is currently: Off} if fail fatal "No response to power command" switch \v(minput) { :1, getiloprompt, lineout "reset /system1 hard", break :2, getiloprompt, lineout "power on", break } .max = 10 for i 1 \m(max) 1 { # Wait for power to come on lineout "power" minput 20 {server power is currently: On} {server power is currently: Off} if fail fatal "No response to power command" if == \v(minput) 1 break xsleep 10 getiloprompt lineout "power on" getiloprompt } if >= \m(i) \m(max) fatal "POWER ON DIDN'T TURN POWER ON" # Enter VSP [virtual serial port]... message "Attempting to enter VSP..." if flag { set debug timestamps on log debug \m(hostname)-debug.log } ilocommand "vsp" # Here we have to allow for many prompts and responses # including a Device Status Request escape sequence from the host # (ESC [ 5 n) which must be replied to. while true { minput 300 - {Press "ESC+0" for System Maintenance Menu} - {\27[5n} - {Requested service is unavailable\, it is already in use} - {The server is not powered on} - {The Virtual Serial Port is not available} if fail { # If INPUT fails output \{27}\{40} # Escape back to iLO prompt message "INPUT: \v(inmessage)" # and back out from there. if open connection { # Is the connection still open? fatal "INPUT FAILED WITH CONNECTION STILL OPEN" } else { fatal "CONNECTION CLOSED BY SERVER" } } if == 1 \v(minput) break # What we want... if == 2 \v(minput) { # Device Status Report Request output \27[0n # Send Device Status Report (I'm OK) continue } getiloprompt # Soak up rest of message lineout "exit" # Exit from iLO fatal "VSP service unavailable" .rc = 1 forward DONE } if flag if open debug-log close debug-log output \{27}0 # Send ESC and 0 input 60 "MMenu> " # Get MMenu> prompt if fail fatal "Timed out waiting for MMenu> prompt" .state = MMenu lineout RBSU # Enter RBSU input 30 "rbsu> " # Wait for rbsu> prompt if fail fatal "Timed out waiting for first rbsu> prompt" .state = rbsu set output pacing \m(outputpacing) # Throttle output speed # Send the RBSU commands. # Here we could give a SHOW CONFIG VIRTUAL SERIAL PORT command and parse the # BIOS Serial Console Port menu choices in case the menu ever changes. rbsucommand "SET CONFIG VIRTUAL SERIAL PORT 2" rbsucommand "SET CONFIG BIOS SERIAL CONSOLE PORT 4" rbsucommand "SET CONFIG BIOS SERIAL CONSOLE BAUD RATE 4" rbsucommand "SET CONFIG EMS CONSOLE 3" lineout EXIT # Exit from RBSU pause 1 # Wait a sec and... output \27( # Escape back to iLO getiloprompt # Get iLO prompt .state = iLO .rc = 0 # Our return code - assume success. if not \m(poweroff) { # Turn power off now? if not \m(reset) message "LEAVING POWER ON" # No... forward DONE } message "TURNING POWER OFF" # Yes... .max = 10 # How many times to try. for i 1 \m(max) 1 { # Loop for each try. lineout "power off" # Send power off command. sleep 15 # Give it a little time getiloprompt # Get iLO prompt lineout "power" # Send "power" command to check minput 30 {server power is currently: Off} {server power is currently: On} if == \v(minput) 1 { # Power is off getiloprompt # Read next iLO prompt forward DONE # Exit loop and skip ahead } sleep 2*i # Sleep more each time warn "RETRYING POWER OFF [\m(i)]..." getiloprompt } warn "WARNING: POWER OFF FAILED AFTER \m(max) TRIES" .rc = 1 :DONE define fatal end 0 # No fatal errors from now on if \m(haveconnection) { # If we have a connection... if \m(reset) { # Reset the machine .reset = 0 # Prevent recursion in case of error message "HARD RESET AND CLOSE" ilocommand "cd /Map1" lineout "reset" sleep 5 close connection # Close our end of the connection } else { if not \m(rc) { message "EXIT WITHOUT RESET" lineout "exit" # Exit from iLO input 30 "CLI session stopped" # Wait for final gasp } if open connection hangup # Close our end too just in case } } .haveconnection = 0 message "END: STATUS: \m(rc)" if open session-log close session-log # Close log exit \m(rc) # Exit from script and Kermit ; Local Variables: ; comment-column:40 ; comment-start:"# " ; End: .