Add ipmicap.sh: ipmitool wrapper to capture output to file

This commit adds the script /usr/local/bin/ipmicap.sh to the system
controller. ipmicap.sh is a wrapper around ipmitool to capture
serial console logs to file. It is used by dcmanager code to capture
IPMI serial console logs when the rvmc_debug_level install property is 1
or higher.

The script has a capture-only mode (--redirect) and an interactive mode.
- The capture-only mode is invoked during subcloud install/upgrade
  operations when the configured via the capture_serial_console install
  value.
- The interactive mode is intended to be used for normal lab
  or field troubleshooting operations. It can be used as a direct
  replacement of ipmitool, where the session output is automatically
  captured to file.

Note on log file:
The log file contains raw control characters.  It is best viewed using
'less' with the '-R/--RAW-CONTROL-CHARS' option.

Test Plan:

PASS:
- Test invocations of ipmicap.sh for both --redirect and interactive
  modes. Ensure output is properly captured in file.
- Test various ipmicap.sh failure modes:
    - Invalid arguments
    - Improper credentials
    - Non-existent output directory for --file param
- Ensure --kill option works as intended. ipmitool sessions are cleaned
  up properly.

TODO:
- Build package and ISO plus installation to ensure proper packaging

Story: 2010144
Task: 49147

Signed-off-by: Kyle MacLeod <kyle.macleod@windriver.com>
Change-Id: Ib2f61b47fae007b7c2f2eaabb61ad38dff7487e3
This commit is contained in:
Kyle MacLeod 2023-09-11 17:58:48 -04:00
parent 2ce860fb1a
commit 1916fba1f7
5 changed files with 324 additions and 2 deletions

View File

@ -1,5 +1,6 @@
scripts/gen-bootloader-iso.sh usr/local/bin
scripts/gen-bootloader-iso-centos.sh usr/local/bin
scripts/ipmicap.sh usr/local/bin
scripts/show-certs.sh usr/local/bin
scripts/stx-iso-utils.sh usr/local/bin
scripts/stx-iso-utils-centos.sh usr/local/bin

View File

@ -3,12 +3,12 @@ Upstream-Name: platform-util
Source: https://opendev.org/starlingx/utilities
Files: *
Copyright: (c) 2013-2021 Wind River Systems, Inc
Copyright: (c) 2013-2023 Wind River Systems, Inc
Others (See individual files for more details)
License: Apache-2
Files: debian/*
Copyright: 2021 Wind River Systems, Inc
Copyright: 2021-2023 Wind River Systems, Inc
License: Apache-2
License: Apache-2

View File

@ -1,5 +1,6 @@
/usr/local/bin/gen-bootloader-iso.sh
/usr/local/bin/gen-bootloader-iso-centos.sh
/usr/local/bin/ipmicap.sh
/usr/local/bin/show-certs.sh
/usr/local/bin/stx-iso-utils.sh
/usr/local/bin/stx-iso-utils-centos.sh

View File

@ -33,6 +33,7 @@ override_dh_auto_install:
install -m 555 scripts/update-dm.sh $(DEBIAN_BUILDDIR)/usr/local/bin/
install -m 555 scripts/gen-bootloader-iso.sh $(DEBIAN_BUILDDIR)/usr/local/bin/
install -m 555 scripts/gen-bootloader-iso-centos.sh $(DEBIAN_BUILDDIR)/usr/local/bin/
install -m 555 scripts/ipmicap.sh $(DEBIAN_BUILDDIR)/usr/local/bin/
install -m 555 scripts/stx-iso-utils.sh $(DEBIAN_BUILDDIR)/usr/local/bin/
install -m 555 scripts/stx-iso-utils-centos.sh $(DEBIAN_BUILDDIR)/usr/local/bin/
install -m 555 scripts/show-certs.sh $(DEBIAN_BUILDDIR)/usr/local/bin/

View File

@ -0,0 +1,319 @@
#!/bin/bash
# vim: filetype=sh shiftwidth=4 softtabstop=4 expandtab
#
# Copyright (c) 2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# IPMI serial console capture script.
# Captures the serial console output via underlying 'ipmitool' tool.
# Also acts as an interactive ipmitool wrapper, capturing all console input/output.
#
set -o nounset; # Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR. Same as 'set -u'
set -o pipefail; # Catch the error in case a piped command fails
set +o posix # Ensure posix mode is off. Required for process substitution
# set -o xtrace; # Turn on traces, useful while debugging (short form: on: 'set -x' off: 'set +x')
################################################################################
# Helpers
# shellcheck disable=SC2155
readonly SCRIPTNAME=$(basename "$0")
# shellcheck disable=SC2155,SC2034
readonly SCRIPTDIR=$(readlink -m "$(dirname "$0")")
BMC_USER=${BMC_USER:-}
BMC_ADDRESS=${BMC_ADDRESS:-}
BMC_PASSWORD=${BMC_PASSWORD:-}
LOG_FILE=${LOG_FILE:-$(pwd)/${SCRIPTNAME%.*}-$(date '+%Y%m%d-%H%M').log}
SOL_ACTIVE=
DEBUG=
help() {
cat<<EOF
Wrapper script to capture the output of 'ipmitool' in a log file.
This script starts up ipmitool for the given BMC session parameters, capturing all output into a log file.
Once connected, you can interact with the session normally.
To stop the capture you either exit the ipmitool session normally (via the
hotkey: '<Enter>~.'), or you can invoke the script again using the --kill
option to cleanly shut everything down.
There are two intended use-cases for this script:
1. Automatically capturing the serial console output during install via ipmitool
In this case, the --redirect flag is provided, which simply redirects all output
to the given log file. The console is not meant to be interactive in this case
2. Provide an interactive wrapper around ipmitool which captures all input and output
into the provided log file.
Note on log file: The log file is captured including raw control characters. It
is best viewed using 'less' with the '-R/--RAW-CONTROL-CHARS' option.
USAGE:
$SCRIPTNAME [-H <bmc-address] [-U <bmc-user>] [ -P <bmc-password> ] [--force-deactivate]
ARGUMENTS:
IPMI connection arguments: If these are not given you will be prompted.
You can also provide any of these as environment variables if preferred.
-H|--host <bmc-address> : BMC host address [env: BMC_ADDRESS]
-U|--user <bmc-user> : BMC user [env: BMC_USER]
-P|--password <bmc-address> : BMC password [env: BMC_PASSWORD]
-l|--log <file path> : Path to the console capture log file (directory must exist)
Default: ./${SCRIPTNAME}-YYYY-MM-DD-HHMM.log
-k|--kill : Stop the capture, cleanup the ipmitool session for this BMC address
-f|--force-deactivate : Issue a 'sol deactivate' command before connecting
-r|--redirect : Redirect all ouput to file (do not show on local console)
This option is only useful for automated/background capture.
-h|--help: print this help
EXAMPLES:
Capture:
$SCRIPTNAME -H 2620:10a:a001:df0::2 -U sysadmin -P sysadmin
$SCRIPTNAME # you will be prompted for BMC details
# You can provide any of BMC_ADDRESS, BMC_USER, BMC_PASSWORD via environment variables:
export BMC_PASSWORD=SecretPassword
$SCRIPTNAME -H 2620:10a:a001:df0::2 -U sysadmin
# Override log file
$SCRIPTNAME -l ./subcloud1-console-\$(date '+%Y%m%d-%H%M').log
Shutdown:
# Inside the ipmitool session: <Enter>~.
# Or force sol deactivate and kill ipmitool for this configuration:
$SCRIPTNAME -H 2620:10a:a001:df0::2 -U sysadmin -P sysadmin --kill
EOF
exit 1
}
# Logging: these all log to stderr
die() { >&2 colorecho red "FATAL: $*"; exit 1; }
die_with_rc() { local rc=$1; shift; >&2 colorecho red "FATAL: $*, rc=$rc"; exit "$rc"; }
check_rc_die() { local rc=$1; shift; [ "$rc" != "0" ] && die_with_rc "$rc" "$@"; return 0; }
check_rc_err() { local rc=$1; shift; [ "$rc" != "0" ] && log_error "$*, rc=$rc"; return 0; }
log_error() { >&2 colorecho red "ERROR: $*"; }
log_warn() { >&2 colorecho orange "WARN: $*"; }
log_info() { >&2 echo "$*"; }
log_debug() { if [ -n "$DEBUG" ]; then >&2 echo "DEBUG: $*"; fi; }
log_progress() { >&2 colorecho green "$*"; }
get_logdate() { date '+%Y-%m-%d %H:%M:%S'; } # eg: log_info "$(get_logdate) My log message"
_init_log() {
LOG_FILE="${LOG_FILE:-$(pwd)/${SCRIPTNAME%.*}.log}"
log_debug "$(get_logdate) Logging output to $LOG_FILE"
}
redirect_output_to_file() {
# output to file only:
_init_log
if [ -f "${LOG_FILE}" ]; then
# append
exec &>> "${LOG_FILE}"
else
exec &> "${LOG_FILE}"
fi
}
tee_output_to_file() {
# output to console and file:
_init_log
exec &> >(exec tee --append "${LOG_FILE}")
}
colorecho() { # usage: colorecho <colour> <text> or colorecho -n <colour> <text>
local echo_arg=
if [ "$1" = "-n" ]; then
echo_arg="-n"; shift;
fi
local colour="$1"; shift
case "${colour}" in
red) echo ${echo_arg} -e "$(tput setaf 1)$*$(tput sgr0)"; ;;
green) echo ${echo_arg} -e "$(tput setaf 2)$*$(tput sgr0)"; ;;
green-bold) echo ${echo_arg} -e "$(tput setaf 2; tput bold)$*$(tput sgr0)"; ;;
yellow) echo ${echo_arg} -e "$(tput setaf 3; tput bold)$*$(tput sgr0)"; ;;
orange) echo ${echo_arg} -e "$(tput setaf 3)$*$(tput sgr0)"; ;;
blue) echo ${echo_arg} -e "$(tput setaf 4)$*$(tput sgr0)"; ;;
purple) echo ${echo_arg} -e "$(tput setaf 5)$*$(tput sgr0)"; ;;
cyan) echo ${echo_arg} -e "$(tput setaf 6)$*$(tput sgr0)"; ;;
bold) echo ${echo_arg} -e "$(tput bold)$*$(tput sgr0)"; ;;
normal|*) echo ${echo_arg} -e "$*"; ;;
esac
}
################################################################################
# Script Functions
ipmitool_deactivate() {
# Forcefully deactivate via sol deactivate
# Use -E to supply password via IPMI_PASSWORD (security)
export IPMI_PASSWORD=${BMC_PASSWORD}
log_info "Disconnecting: ipmitool -I lanplus -H ${BMC_ADDRESS} -U ${BMC_USER} -E sol deactivate"
ipmitool -I lanplus -H "${BMC_ADDRESS}" -U "${BMC_USER}" -E sol deactivate
rc=$?
log_info "Exit code from sol deactivate: ${rc}"
export SOL_ACTIVE=
}
ipmitool_activate() {
# Establish remote console access via ipmi sol
# Use -E to supply password via IPMI_PASSWORD
export IPMI_PASSWORD=${BMC_PASSWORD}
export SOL_ACTIVE=1
log_info "Connecting: ipmitool -I lanplus -H ${BMC_ADDRESS} -U ${BMC_USER} -E sol activate"
ipmitool -I lanplus -H "${BMC_ADDRESS}" -U "${BMC_USER}" -E sol activate
local -i rc=$?
# We see exit code of 143 when killed
if [ ${rc} -eq 0 ] || [ ${rc} -eq 143 ]; then
log_progress "ipmitool sol activate normal exit"
else
log_error "ipmitool sol activate abnormal exit [rc: ${rc}]"
exit ${rc}
fi
export SOL_ACTIVE=
}
ipmitool_kill() {
log_progress "Shutting down IPMI capture"
ipmitool_deactivate
local pid pid2
# Now kill ipmitool for this BMC address
pid=$(pgrep -a ipmitool | awk '/'"${BMC_ADDRESS}"'/ { print $1; }')
if [ -n "${pid}" ]; then
log_info "Killing ipmitool pid: ${pid}"
kill "${pid}"
sleep 1
pid2=$(pgrep -a ipmitool | awk '/'"${BMC_ADDRESS}"'/ { print $1; }')
if [ -n "${pid2}" ]; then
log_error "FAILED to kill ipmitool pid: ${pid}, pid2: ${pid2} (retrying with -9)"
kill -9 "${pid2}"
return 1
fi
log_info "Finished. You may need to run 'reset' in the original terminal."
else
log_info "No ipmitool session found for ${BMC_ADDRESS}"
fi
return 0
}
do_cleanup() {
if [ -n "${SOL_ACTIVE}" ]; then
ipmitool_deactivate
fi
}
################################################################################
# Main
#
main() {
local arg_force_deactivate=
local arg_redirect_to_file=
local arg_rvmc_config=
local arg_kill=
while [ $# -gt 0 ] ; do
case "${1:-""}" in
-h|--help)
help
;;
-D|--debug)
DEBUG=1
;;
-f|--force-deactivate)
arg_force_deactivate=1
;;
-k|--kill)
arg_kill=1
;;
-r|--redirect)
arg_redirect_to_file=1
;;
-H|--host)
shift
BMC_ADDRESS=$1
;;
-U|--user)
shift
BMC_USER=$1
;;
-P|--password)
shift
BMC_PASSWORD=$1
;;
-l|--log*)
shift
LOG_FILE=$1
export LOG_FILE
;;
--rvmc-config)
shift
arg_rvmc_config=$1
;;
*)
die "Invalid argument '$1' [use -h/--help for help]"
;;
esac
shift
done
if ! hash ipmitool 2>&-; then
die "Cannot find 'ipmitool' in path. Is it installed?"
fi
if [ -n "${arg_rvmc_config}" ]; then
if [ ! -r "${arg_rvmc_config}" ]; then
die "RVMC config file does not exist or is not accessible: ${arg_rvmc_config}"
fi
BMC_ADDRESS=$(awk '/bmc_address:/ { print $2; }' "${arg_rvmc_config}")
[ -n "${BMC_ADDRESS}" ] || die "Could not set BMC_ADDRESS from ${arg_rvmc_config}"
BMC_USER=$(awk '/bmc_username:/ { print $2; }' "${arg_rvmc_config}")
[ -n "${BMC_USER}" ] || die "Could not set BMC_USER from ${arg_rvmc_config}"
BMC_PASSWORD=$(awk '/bmc_password:/ { print $2; }' "${arg_rvmc_config}" | base64 -d)
[ -n "${BMC_PASSWORD}" ] || die "Could not set BMC_PASSWORD from ${arg_rvmc_config}"
else
# Interactive. Prompt for BMC info:
[ -n "${BMC_ADDRESS}" ] || read -r -p "BMC address: " BMC_ADDRESS
[ -n "${BMC_USER}" ] || read -r -p "BMC user: " BMC_USER
[ -n "${BMC_PASSWORD}" ] || read -r -s -p "BMC password: " BMC_PASSWORD
fi
# Final check
[ -n "${BMC_ADDRESS}" ] || die "BMC_ADDRESS is empty"
[ -n "${BMC_USER}" ] || die "BMC_USER is empty"
[ -n "${BMC_PASSWORD}" ] || die "BMC_PASSWORD is empty"
export BMC_ADDRESS BMC_USER BMC_PASSWORD
if [ -n "${arg_kill}" ]; then
ipmitool_kill
exit $?
fi
log_progress "Capturing console output at: ${LOG_FILE}"
log_progress "To disconnect, use '<Enter>~.' or reinvoke this script with the --kill option."
# captures output in $LOG_FILE
if [ -n "${arg_redirect_to_file}" ]; then
redirect_output_to_file
else
tee_output_to_file
fi
if [ -n "${arg_force_deactivate}" ]; then
log_progress "Forcing deactivate"
ipmitool_deactivate
fi
# Ensure we disconnect on normal and abnormal termination signals:
trap do_cleanup INT QUIT TERM EXIT
ipmitool_activate
}
if [[ "${BASH_SOURCE[0]}" = "$0" ]]; then
main "$@"
fi