ospackage/bin/saptune_check
#!/bin/bash
# ------------------------------------------------------------------------------
# Copyright (c) 2019 SUSE LLC
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of version 3 of the GNU General Public License as published by the
# Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, contact SUSE Linux GmbH.
#
# ------------------------------------------------------------------------------
# Author: Sören Schmidt <soeren.schmidt@suse.com>
#
# This tool checks if saptune is set up correctly.
# It will not dig deeper to check if the tuning itself is working.
#
# exit codes: 0 All checks ok. Saptune have been set up correctly.
# 1 Some warnings occurred. Saptune should work, but better check manually.
# 2 Some errors occurred. Saptune will not work.
# 3 Wrong parameters given to the tool on commandline.
# 5 Error in JSON string (bug in program!)
#
# Changelog:
#
# 08.01.2021 v0.1 First release. (Split of sapconf_saptune_check v1.2.1)
# 19.08.2021 v0.2 supports (only) saptune v3
# tests system status and lists failed services
# 26.08.2021 v0.2.1 added missing os_release to global arrays
# 09.11.2021 v0.2.2 degraded system is no longer considered an error
# 26.09.2022 v0.2.3 degraded systemd state gets explained in more detail (TEAM-6584)
# 26.09.2022 v0.3 reactivate unused function compile_filelists() as file_check() and add detection of sapconf remains (TEAM-6275)
# 03.04.2023 v0.3.1 removed error regarding tuned sapconf profile (TEAM-7529)
# 28.08.2024 v0.4 a little rework and added JSON support (TEAM-8959)
# 31.10.2024 v0.4.1 added --force-color to support forced colored output by saptune
declare -r VERSION="0.4.1"
# We use these global arrays through out the program:
#
# package_version - contains package version (string)
# os_version - contains os version and service pack
# system_status - contains system status and failed units
# unit_state_active - contains systemd unit state (systemctl is-active)
# unit_state_enabled - contains systemd unit state (systemctl is-enabled)
# tool_profile - contains actual profile (string) for each tool
declare -A package_version os_version system_status unit_state_active unit_state_enabled tool_profile
color=1 # we like it colorful
function header() {
[ ${DO_JSON} -ne 0 ] && return
local len=${#1}
echo -e "\n${1}"
printf '=%.s' $(eval "echo {1.."$((${len}))"}")
echo
}
function print_ok() {
[ ${DO_JSON} -ne 0 ] && return
local col_on col_off
[ -t 1 ] || color=0 # Disable color if we run in a pipe
[ ${FORCE_COLOR} -eq 1 ] && color=1 # force colored output
if [ ${color} -eq 1 ] ; then
col_on="\033[0;32m"
col_off="\033[0m"
else
col_on=""
col_off=""
fi
echo -e "[ ${col_on}OK${col_off} ] ${1}"
}
function print_fail() {
[ ${DO_JSON} -ne 0 ] && return
local col_on col_off bold_on
[ -t 1 ] || color=0 # Disable color if we run in a pipe
[ ${FORCE_COLOR} -eq 1 ] && color=1 # force colored output
if [ ${color} -eq 1 ] ; then
col_on="\033[0;31m"
col_off="\033[0m"
bold_on="\033[1m"
else
col_on=""
col_off=""
bold_on=""
fi
echo -e "[${col_on}FAIL${col_off}] ${1}${bold_on}\t-> ${2}${col_off}"
}
function print_warn() {
[ ${DO_JSON} -ne 0 ] && return
local col_on col_off bold_on
[ -t 1 ] || color=0 # Disable color if we run in a pipe
[ ${FORCE_COLOR} -eq 1 ] && color=1 # force colored output
if [ ${color} -eq 1 ] ; then
col_on="\033[0;33m"
col_off="\033[0m"
bold_on="\033[1m"
else
col_on=""
col_off=""
bold_on=""
fi
echo -e "[${col_on}WARN${col_off}] ${1}${bold_on}\t-> ${2}${col_off}"
}
function print_note() {
[ ${DO_JSON} -ne 0 ] && return
local col_on col_off
[ -t 1 ] || color=0 # Disable color if we run in a pipe
[ ${FORCE_COLOR} -eq 1 ] && color=1 # force colored output
if [ ${color} -eq 1 ] ; then
col_on="\033[0;37m"
col_off="\033[0m"
else
col_on=""
col_off=""
fi
echo -e "[${col_on}NOTE${col_off}] ${1}"
}
function _json_must_be() {
# Params: OBJECT_TYPE...
# Output: -
# Returncode: -
#
# Checks if one of the given object types is the last character
# of_JSON_NESTING. Terminates with exit code 5 an an error nmessage
# if not, otherwise returns.
#
# Requires: _JSON_NESTING
local current_object="${_JSON_NESTING: -1:1}"
local object_type
for object_type in ${@} ; do
[ "${object_type}" == "${current_object}" ] && return
done
echo "Current object type \"${current_object}\" is not in the allowed types: ${@}" >&2
exit 5
}
function _json_must_be_number() {
# Params: VALUE
# Output: -
# Returncode: -
#
# Checks if one of the given parameter is a number.
# Terminates with exit code 5 if not, otherwise returns.
#
# Requires: -
if [[ ! ${1} =~ ^[+-]?([0-9]+([.][0-9]*)?|\.[0-9]+)$ ]] ; then
echo "JSON value is not a number: ${1}" >&2
exit 5
fi
}
function _json_must_be_bool() {
# Params: VALUE
# Output: -
# Returncode: -
#
# Checks if one of the given parameter is a 'true'
# or 'false'.
# Terminates with exit code 5 if not, otherwise returns.
#
# Requires: -
if [[ ! "${1}" =~ ^(true|false)$ ]] ; then
echo "JSON value is no neither 'true' nor 'false: ${1}" >&2
exit 5
fi
}
function _json_key_must_exist() {
# Params: ARG...
# Output: -
# Returncode: -
#
# Checks if the second parameter exists.
# The function normaly will be called by `add2json` with
# its arguments (`add2json REG_TYPE [KEY [VALUE]]`).
# Terminates with exit code 5 if not, otherwise returns.
#
# Requires: -
if [ -z "${2}" ] ; then
echo "JSON key is missing! Command: ${*}" >&2
exit 5
fi
}
function _json_value_must_exist() {
# Params: ARG...
# Output: -
# Returncode: -
#
# Checks if the third parameter exists.
# The function normaly will be called by `add2json` with
# its arguments (`add2json REG_TYPE [KEY [VALUE]]`).
# Terminates with exit code 5 if not, otherwise returns.
#
# Requires: -
if [ -z "${3}" ] ; then
echo "JSON value is missing! Command: ${*}" >&2
exit 5
fi
}
function _json_nesting_add() {
# Params: DATATYPE
# Output: -
# Returncode: -
#
# Appends the argument (typically D or L) to _JSON_NESTING.
#
# Requires: _JSON_NESTING
_JSON_NESTING="${_JSON_NESTING}${1}"
}
function _json_nesting_pop() {
# Params: -
# Output: -
# Returncode: -
#
# Removes the last character from _JSON_NESTING.
#
# Requires: _JSON_NESTING
_JSON_NESTING="${_JSON_NESTING:0: -1}"
}
function start_json() {
# Params: -
# Output: -
# Returncode: -
#
# Initializes JSON_STRING and _JSON_NESTING to start
# a new JSON string.
# JSON_STRING starts with a { for a new dict.
# If DO_JSON is 0, the function returns immediately.
#
# Requires: DO_JSON, JSON_STRING, _JSON_NESTING
[ ${DO_JSON} -eq 0 ] && return
shopt -s extglob
JSON_STRING='{'
_JSON_NESTING='D'
}
function end_json() {
# Params: -
# Output: -
# Returncode: -
#
# Finalizes JSON_STRING and _JSON_NESTING to finish
# a JSON string or terminates with exit code 5 if
# there is a problem.
# JSON_STRING ends with a } to close the overall dict.
# If DO_JSON is 0, the function returns immediately.
#
# Requires: DO_JSON, JSON_STRING, _JSON_NESTING
[ ${DO_JSON} -eq 0 ] && return
if [ "${_JSON_NESTING}" != 'D' ] ; then
echo "Cannot terminate JSON string with nesting: ${_JSON_NESTING}" >&2
exit 5
fi
JSON_STRING="${JSON_STRING%,*( )}}"
_JSON_NESTING='E'
}
function print_json() {
# Params: -
# Output: JSON_STRING
# Returncode: -
#
# Prints JSON_STRING to stdout or terminates with
# exit code 5 if there is a problem.
# If DO_JSON is 0, the function returns immediately.
#
# Requires: DO_JSON, JSON_STRING, _JSON_NESTING
[ ${DO_JSON} -eq 0 ] && return
if [ "${_JSON_NESTING}" != 'E' ] ; then
echo "Cannot print incomplete JSON string with nesting: ${_JSON_NESTING}" >&2
exit 5
fi
echo "${JSON_STRING}"
}
function add2json() {
# Params: REQ_TYPE [KEY [VALUE]]
# Output: -
# Returncode: -
#
# Extends JSON_STRING with the given entry and
# updates _JSON_NESTING accordingly.
# Terminates with exit code 5 if there is a problem.
# If DO_JSON is 0, the function returns immediately.
#
# Implemented commands are:
# add2json dict_start NAME
# add2json dict_end
# add2json list_start NAME
# add2json list_entry_string VALUE
# add2json list_entry_number VALUE
# add2json list_entry_bool true|false
# add2json list_entry_null
# add2json list_entry_dict_start
# add2json list_entry_dict_end
# add2json list_end
# add2json string NAME VALUE
# add2json number NAME VALUE
# add2json bool NAME true|false
# add2json nul NAME
#
# Requires: DO_JSON, JSON_STRING, _JSON_NESTING
[ ${DO_JSON} -eq 0 ] && return
local req_type="${1}"
local key="${2}"
local value="${3}"
# Escape quotes in keys and values.
key="${key//\"/\\\"}"
value="${value//\"/\\\"}"
# Act on the request type.
case "${req_type}" in
dict_start)
_json_must_be L D
_json_key_must_exist "$@"
JSON_STRING="${JSON_STRING}\"${key}\":{"
_json_nesting_add D
;;
dict_end)
_json_must_be D
JSON_STRING="${JSON_STRING%,*( )}},"
_json_nesting_pop
;;
list_start)
_json_must_be L D
_json_key_must_exist "$@"
JSON_STRING="${JSON_STRING}\"${key}\":["
_json_nesting_add L
;;
list_entry_string)
_json_must_be L
_json_key_must_exist "$@" # key is really a value (list entry)
JSON_STRING="${JSON_STRING}\"${key}\", "
;;
list_entry_number)
_json_must_be L
_json_key_must_exist "$@" # key is really a value (list entry)
_json_must_be_number ${key} # key is really a value (list entry)
JSON_STRING="${JSON_STRING}${key}, "
;;
list_entry_bool)
_json_must_be L
_json_key_must_exist "$@" # key is really a value (list entry)
_json_must_be_bool ${key} # key is really a value (list entry)
JSON_STRING="${JSON_STRING}${key}, "
;;
list_entry_null)
_json_must_be L
JSON_STRING="${JSON_STRING}null, "
;;
list_entry_dict_start)
_json_must_be L
JSON_STRING="${JSON_STRING}{"
_json_nesting_add D
;;
list_entry_dict_end)
_json_must_be D
JSON_STRING="${JSON_STRING%,*( )}},"
_json_nesting_pop
;;
list_end)
_json_must_be L
JSON_STRING="${JSON_STRING%,*( )}],"
_json_nesting_pop
;;
string)
_json_must_be D
_json_key_must_exist "$@"
_json_value_must_exist "$@"
JSON_STRING="${JSON_STRING}\"${key}\":\"${value}\", "
;;
number)
_json_must_be D
_json_key_must_exist "$@"
_json_value_must_exist "$@"
_json_must_be_number ${value}
JSON_STRING="${JSON_STRING}\"${key}\":${value}, "
;;
bool)
_json_must_be D
_json_key_must_exist "$@"
_json_must_be_bool ${value}
JSON_STRING="${JSON_STRING}\"${key}\":${value}, "
;;
null)
_json_must_be D
_json_key_must_exist "$@"
JSON_STRING="${JSON_STRING}\"${key}\":null, "
;;
*)
echo "Unknown request type: ${req_type}" >&2
exit 5
;;
esac
}
# MARKED FOR REMOVAL
function add_msg_json() {
# Params: -
# Output: -
# Returncode: -
#
# Adds a note, warning or fail message to the arrays
# JSON_NOTE_MESSAGES, JSON_WARN_MESSAGES, JSON_FAIL_MESSAGES
# or JSON_REMEDIATION_MESSAGES which are added to JSON_STRING
# by update_messages_json().
# Terminates with exit code 5 if there is a problem.
# If DO_JSON is 0, the function returns immediately.
#
# Requires: JSON_NOTE_MESSAGES JSON_WARN_MESSAGES
# JSON_FAIL_MESSAGES JSON_REMEDIATION_MESSAGES
[ ${DO_JSON} -eq 0 ] && return
case "${1}" in
note)
JSON_NOTE_MESSAGES+=("${2}")
;;
warn)
JSON_WARN_MESSAGES+=("${2}")
;;
fail)
JSON_FAIL_MESSAGES+=("${2}")
;;
remediation)
JSON_REMEDIATION_MESSAGES+=("${2}")
;;
*)
echo "Unknown JSON message type: ${1}" >&2
exit 5
;;
esac
}
# MARKED FOR REMOVAL
function update_messages_json() {
# Params: -
# Output: -
# Returncode: -
#
# Adds a 'messages' dictionary with lists for 'notes'
# (JSON_NOTE_MESSAGES), 'warnings' (JSON_WARN_MESSAGES)
# 'remediations' (JSON_REMEDIATION_MESSAGES) and 'errors'
# (JSON_FAIL_MESSAGES) or terminates with exit code 5 if
# there is a problem.
# If DO_JSON is 0, the function returns immediately.
#
# Requires: DO_JSON, JSON_STRING, _JSON_NESTING
# JSON_NOTE_MESSAGES JSON_WARN_MESSAGES
# JSON_FAIL_MESSAGES JSON_REMEDIATION_MESSAGES
[ ${DO_JSON} -eq 0 ] && return
add2json dict_start 'messages'
add2json list_start 'notes'
for msg in "${JSON_NOTE_MESSAGES[@]}" ; do
add2json list_entry_string "${msg}"
done
add2json list_end
add2json list_start 'warnings'
for msg in "${JSON_WARN_MESSAGES[@]}" ; do
add2json list_entry_string "${msg}"
done
add2json list_end
add2json list_start 'errors'
for msg in "${JSON_FAIL_MESSAGES[@]}" ; do
add2json list_entry_string "${msg}"
done
add2json list_end
add2json list_start 'remediations'
for msg in "${JSON_REMEDIATION_MESSAGES[@]}" ; do
add2json list_entry_string "${msg}"
done
add2json list_end
add2json dict_end
}
function add_message_json() {
# Params: TYPE TEXT REMEDIATION
# Output: -
# Returncode: -
#
# Adds dictionary to a list ()'messages'), which must
# be present, with the elements 'type' (OK|WARN|FAIL|NOTE),
# 'text' and 'remediation' (optional) or terminates with
# exit code 5 if there is a problem.
# The remediation is left out, except the type is FAIL or
# WARN.
# If DO_JSON is 0, the function returns immediately.
#
# Requires: DO_JSON, JSON_STRING, _JSON_NESTING
[ ${DO_JSON} -eq 0 ] && return
local parameter_failure=0
[ $# -lt 2 -o $# -gt 3 ] && parameter_failure=1
[[ "${1}" =~ ^(OK|WARN|FAIL|NOTE)$ ]] || parameter_failure=2
[ "${1}" == 'FAIL' -a $# -ne 3 ] && parameter_failure=3
[ "${1}" == 'WARN' -a $# -ne 3 ] && parameter_failure=3
if [ ${parameter_failure} -ne 0 ] ; then
echo "${FUNCNAME@Q} wrongly called (code=${parameter_failure}) with: ${@@Q} " >&2
exit 5
fi
add2json list_entry_dict_start
add2json string 'type' "${1}"
add2json string 'text' "${2}"
[ -n "${3:-}" ] && add2json string 'remediation' "${3}"
add2json list_entry_dict_end
}
function is_in() {
# Params: NAME LIST
# Output: -
# Returncode: 0 (true), 1 (false)
#
# Checks if NAME is in the space-separated string LIST
# and returns 0 (true) or 1 (false).
#
# Requires: -
local name="${1}"
local list=( ${2} )
for elem in "${list[@]}" ; do
if [ "${name}" == "${elem}" ] ; then
return 1
fi
done
return 1
}
function get_os_version() {
# Params: -
# Output: -
# Exitcode: -
#
# Determines the OS version as string for each PACKAGE.
#
# The function updates the associative array "os_version".
#
# Requires:-
local VERSION_ID
eval "$(grep ^VERSION_ID= /etc/os-release)"
os_version['release']="${VERSION_ID%.*}"
os_version['servicepack']="${VERSION_ID#*.}"
}
function get_package_versions() {
# Params: PACKAGE...
# Output: -
# Exitcode: -
#
# Determines package version as string for each PACKAGE.
# Not installed packages will have an empty string as version.
#
# The function updates the associative array "package_version".
#
# Requires:-
local package version
for package in "${@}" ; do
if version=$(rpm -q --qf '%{version}' "${package}" 2>&1) ; then
package_version["${package}"]=${version}
else
package_version["${package}"]=''
fi
done
}
function get_system_status() {
# Params: -
# Output: -
# Exitcode: -
#
# Collect data about system status and failed services.
#
# The function updates the associative arrays "system_status".
#
# Requires: -
system_status["status"]=$(systemctl is-system-running 2> /dev/null)
system_status["failed_units"]=$(systemctl list-units --state=failed --plain --no-legend --no-pager | cut -d ' ' -f 1 | tr '\n' ' ' 2> /dev/null)
}
function get_unit_states() {
# Params: UNIT...
# Output: -
# Exitcode: -
#
# Determines the state (is-active/is-enabled) for each UNIT.
# A missing state is reported as "missing".
#
# The function updates the associative arrays "unit_state_active" and "unit_state_enabled".
#
# Requires: -
local unit state_active state_enabled
for unit in "${@}" ; do
state_active=$(systemctl is-active "${unit}" 2> /dev/null)
state_enabled=$(systemctl is-enabled "${unit}" 2> /dev/null)
unit_state_active["${unit}"]=${state_active:-missing}
unit_state_enabled["${unit}"]=${state_enabled:-missing}
done
}
function get_tool_profiles() {
# Params: -
# Output: -
# Exitcode: -
#
# Determines the current profile of tuned and saptune (profile==Notes/Solution).
# A missing profile (file) is reported as "missing".
#
# The function updates the associative array "tool_profile".
#
# Requires: -
local active_profile TUNE_FOR_NOTES TUNE_FOR_SOLUTIONS
active_profile=''
[ -e /etc/tuned/active_profile ] && active_profile=$(< /etc/tuned/active_profile)
tool_profile['tuned']="${active_profile:-missing}"
if [ -e /etc/sysconfig/saptune ] ; then
eval $(grep ^TUNE_FOR_NOTES= /etc/sysconfig/saptune)
eval $(grep ^TUNE_FOR_SOLUTIONS= /etc/sysconfig/saptune)
if [ -z "${TUNE_FOR_NOTES}" -a -z "${TUNE_FOR_SOLUTIONS}" ] ; then
tool_profile['saptune']='missing'
else
tool_profile['saptune']="solutions: ${TUNE_FOR_SOLUTIONS:=-} notes: ${TUNE_FOR_NOTES:=-}"
fi
else
tool_profile['saptune']='missing'
fi
}
function configured_saptune_version() {
# Params: -
# Output: -
# Exitcode: -
#
# Checks the configured saptune version.
# A missing saptune is reported as "missing".
#
# The function updates the variable "configured_saptune_version".
#
# Requires: -
local SAPTUNE_VERSION
[ -e /etc/sysconfig/saptune ] && eval $(grep ^SAPTUNE_VERSION= /etc/sysconfig/saptune)
configured_saptune_version="${SAPTUNE_VERSION:-missing}"
}
function collect_data() {
# Params: -
# Output: -
# Exitcode: -
#
# Calls various functions to collect data.
#
# Requires: get_os_version()
# get_package_versions()
# get_system_status()
# get_unit_states()
# get_tool_profiles()
# configured_saptune_version()
# Collect OS version.
get_os_version
# Collect data about some packages.
get_package_versions sapconf saptune tuned
# Collect data about system status and failed services.
get_system_status
# Collect data about some systemd services.
get_unit_states sapconf.service tuned.service saptune.service
# Collect the profiles of various tools.
get_tool_profiles
# Get configured saptune version.
configured_saptune_version
}
function file_check() {
# Params: VERSIONTAG
# Output: warnings, fails and notes with print_warn(), print_fail() and print_note()
# Exitcode: -
#
# Checks the existence of mandatory and invalid files for sapconf and saptune
# (depending on SLES release and VERSIONTAG) and prints warnings or fails.
#
# The following strings for VERSIONTAG are allowed: "saptune-3"
#
# Also for all mandatory and invalid files, we search for RPM leftovers (.rpmnew/.rpmsave).
#
# IMPORTANT:
# When adding new files every file must be listed in either of the arrays mandatory_files"
# or "invalid_files" but in *each* SLES release and tag section!
#
# The function updates the variables "warnings" and "fails" used in saptune_check().
#
# Requires: print_warn(), print_fail(), print_note() and add_message_json()
local VERSION_ID tag="${1}" mandatory_files invalid_files rpm_leftovers sapconf_leftovers critical_sapconf_leftovers
declare -a mandatory_files invalid_files rpm_leftovers
eval $(grep ^VERSION_ID= /etc/os-release)
case ${VERSION_ID} in
12*)
case ${tag} in
saptune-3)
mandatory_files=( '/etc/sysconfig/saptune' )
invalid_files=( '/etc/saptune/extra/SAP_ASE-SAP_Adaptive_Server_Enterprise.conf' '/etc/saptune/extra/SAP_BOBJ-SAP_Business_OBJects.conf' '/etc/sysconfig/saptune-note-1275776' '/etc/sysconfig/saptune-note-1557506' '/etc/sysconfig/saptune-note-SUSE-GUIDE-01' '/etc/sysconfig/saptune-note-SUSE-GUIDE-02' '/etc/tuned/saptune' )
;;
esac
;;
15*)
case ${tag} in
saptune-3)
mandatory_files=( '/etc/sysconfig/saptune' )
invalid_files=( '/etc/saptune/extra/SAP_ASE-SAP_Adaptive_Server_Enterprise.conf' '/etc/saptune/extra/SAP_BOBJ-SAP_Business_OBJects.conf' '/etc/sysconfig/saptune-note-1275776' '/etc/sysconfig/saptune-note-1557506' '/etc/sysconfig/saptune-note-SUSE-GUIDE-01' '/etc/sysconfig/saptune-note-SUSE-GUIDE-02' '/etc/tuned/saptune' )
;;
esac
;;
esac
# Leftovers from a damaged sapconf update/removal, which do not pervent saptune from starting.
sapconf_leftovers=( '/var/lib/sapconf' '/run/sapconf/active' '/run/sapconf_act_profile' )
# Critical leftovers from a damaged sapconf update/removal, which prevent saptune from starting.
critical_sapconf_leftovers=( '/var/lib/sapconf/act_profile' '/run/sapconf/active' )
# Now check the existence of mandatory and invalid files and print warnings and fails.
for ((i=0;i<${#mandatory_files[@]};i++)) ; do
if [ ! -e "${mandatory_files[i]}" ] ; then
msg="${mandatory_files[i]} is missing, but a mandatory file."
remediation="Check your installation!"
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
((fails++))
fi
rpm_leftovers+=("${mandatory_files[i]}.rpmsave" "${mandatory_files[i]}.rpmnew" )
done
for ((i=0;i<${#invalid_files[@]};i++)) ; do
if [ -e "${invalid_files[i]}" ] ; then
msg="${invalid_files[i]} is not used by this version. Maybe a leftover from an update?"
remediation="Check the content and remove it."
print_warn "${msg}" "${remediation}"
add_message_json WARN "${msg}" "${remediation}"
((warnings++))
fi
rpm_leftovers+=("${invalid_files[i]}.rpmsave" "${invalid_files[i]}.rpmnew" )
done
# Print a warning if we have found RPM leftovers!
for ((i=0;i<${#rpm_leftovers[@]};i++)) ; do
if [ -e "${rpm_leftovers[i]}" ] ; then
msg="${rpm_leftovers[i]} found. This is a leftover from a package update!"
remediation="Check the content of \"${rpm_leftovers[i]}\" and remove it."
print_warn "${msg}" "${remediation}"
add_message_json WARN "${msg}" "${remediation}"
((warnings++))
fi
done
# Print a warning and recommend a deletion, if sapconf is not intalled and we found some files.
if [ -z ${package_version['sapconf']} ] ; then
for ((i=0;i<${#sapconf_leftovers[@]};i++)) ; do
if [ -e "${sapconf_leftovers[i]}" ] ; then
msg="${sapconf_leftovers[i]} found. This is a leftover from a sapconf package upgrade or removal!"
remediation="Delete ${critical_sapconf_leftovers[i]}. If this happens regularly, please report a bug."
add_message_json WARN "${msg}" "${remediation}"
((warnings++))
fi
done
fi
# Print a fail and recommend a deletion, if sapconf.service is stopped and we find these files.
if [ "${unit_state_active['sapconf.service']}" == 'inactive' ] ; then
for ((i=0;i<${#critical_sapconf_leftovers[@]};i++)) ; do
if [ -e "${critical_sapconf_leftovers[i]}" ] ; then
msg="${critical_sapconf_leftovers[i]} found. This is an unintended leftover from sapconf!"
remediation="Delete ${critical_sapconf_leftovers[i]}. If this happens regularly, please report a bug."
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
((fails++))
fi
done
fi
}
function check_saptune() {
# Checks if saptune is installed correctly.
local fails=0 warnings=0 version_tag SAPTUNE_VERSION TUNE_FOR_SOLUTIONS TUNE_FOR_NOTES
start_json # open new JSON document
add2json list_start messages # open messages dilistct
# We can stop, if saptune is not installed.
if [ -z "${package_version['saptune']}" ] ; then
msg="saptune is not installed"
remediation="Install saptune with: zypper install saptune"
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
add2json list_end # close messages list
add2json number warnings 0
add2json number errors 1
end_json
print_json
return 2
fi
case "${package_version['saptune']}" in
3.*)
version_tag='saptune-3'
;;
*)
msg="The saptune version ${package_version['saptune']} is unknown to this script! Exiting."
remediation="Install a supported saptune version."
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
add2json list_end # close messages list
add2json number warnings 0
add2json number errors 1
end_json
print_json
return 2
;;
esac
# Let's test.
header "Checking saptune"
msg="saptune package has version ${package_version['saptune']}"
print_note "${msg}"
add_message_json NOTE "${msg}"
# Check if leftover files still in place.
file_check saptune-3
# Checking if system is "running" and has no failed units."
case "${system_status['status']}" in
running)
msg="systemd reports status \"running\""
print_ok "${msg}"
add_message_json OK "${msg}"
;;
degraded)
msg="systemd reports status \"${system_status['status']}\". Failed units: ${system_status['failed_units']}"
remediation="Check the cause and reset the state with 'systemctl reset-failed'!"
print_warn "${msg}" "${remediation}"
add_message_json WARN "${msg}" "${remediation}"
msg="A degraded systemd system status means, that one or more systemd units failed. The system is still operational! Tuning might not be affected, please run 'saptune verfiy' for detailed information."
print_note "${msg}"
add_message_json NOTE "${msg}"
((warnings++))
;;
*) msg="systemd reports status \"${system_status['status']}\"."
remediation="Check systemd to see what is wrong!"
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
((fails++))
;;
esac
# Checking if the correct version has been configured.
#add2json string configured_saptune_version "${configured_saptune_version}"
case ${configured_saptune_version} in
3) msg="configured saptune version is 3"
print_ok "${msg}"
add_message_json OK "${msg}"
;;
*) msg="Configured saptune version is ${configured_saptune_version}"
remediation="Misconfiguration happened or an update went wrong! This needs to be investigated."
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
((fails++))
;;
esac
# Checking status of sapconf.service.
if [ -n "${package_version['sapconf']}" ] ; then
case "${unit_state_active['sapconf.service']}" in
inactive)
msg="sapconf.service is inactive"
print_ok "${msg}"
add_message_json OK "${msg}"
;;
*)
msg="sapconf.service is ${unit_state_active['sapconf.service']}"
remediation="Run 'systemctl stop sapconf.service' or 'saptune service takeover'."
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
((fails++))
;;
esac
case "${unit_state_enabled['sapconf.service']}" in
disabled)
msg="sapconf.service is disabled"
print_ok "${msg}"
add_message_json OK "${msg}"
;;
*)
msg="sapconf.service is ${unit_state_enabled['sapconf.service']}"
remediation="Run 'systemctl disable sapconf.service' or 'saptune service takeover'."
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
((fails++))
;;
esac
fi
# Checking if saptune.service is enabled and started.
#add2json list_start 'saptune.service'
#add2json list_entry_string "${unit_state_active['saptune.service']}"
#add2json list_entry_string "${unit_state_enabled['saptune.service']}"
#add2json list_end
case "${unit_state_active['saptune.service']}" in
active)
msg="saptune.service is active"
print_ok "${msg}"
add_message_json OK "${msg}"
;;
*)
msg="saptune.service is ${unit_state_active['saptune.service']}"
remediation="Run 'systemctl start saptune.service', 'saptune service start' or 'saptune service takeover'."
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
((fails++))
;;
esac
case "${unit_state_enabled['saptune.service']}" in
enabled)
msg="saptune.service is enabled"
print_ok "${msg}"
add_message_json OK "${msg}"
;;
*)
msg="saptune.service is ${unit_state_enabled['saptune.service']}"
remediation="Run 'systemctl enable saptune.service', 'saptune service enable' or 'saptune service takeover'."
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
((fails++))
;;
esac
# Checking status of tuned.service. and the profile.
if [ -n "${package_version['tuned']}" ] ; then
case "${tool_profile['tuned']}" in
saptune)
msg="tuned.service is ${unit_state_active['tuned.service']}/${unit_state_enabled['tuned.service']} with profile ('${tool_profile['tuned']}')"
remediation="This profile should not exist anymore! This needs to be investigated."
print_fail "${msg}" "${remediation}"
add_message_json FAIL "${msg}" "${remediation}"
((fails++))
;;
*)
msg="tuned profile is '${tool_profile['tuned']}'"
print_note "${msg}"
add_message_json NOTE "${msg}"
case "${unit_state_active['tuned.service']}" in
inactive)
msg="tuned.service is inactive"
print_ok "${msg}"
add_message_json OK "${msg}"
;;
*)
msg="tuned.service is ${unit_state_active['tuned.service']}"
remediation="Verify that tuning does not conflict with saptune or run 'systemctl stop tuned.service'!"
print_warn "${msg}" "${remediation}"
add_message_json WARN "${msg}" "${remediation}"
((warnings++))
;;
esac
case "${unit_state_enabled['tuned.service']}" in
disabled)
msg="tuned.service is disabled"
print_ok "${msg}"
add_message_json OK "${msg}"
;;
*)
msg="tuned.service is ${unit_state_enabled['tuned.service']}"
remediation="Verify that tuning does not conflict with saptune or run 'systemctl disable tuned.service'!"
print_warn "${msg}" "${remediation}"
add_message_json WARN "${msg}" "${remediation}"
((warnings++))
;;
esac
;;
esac
fi
# Print JSON output.
add2json list_end # close messages list
add2json number warnings ${warnings}
add2json number errors ${fails}
#update_messages_json
end_json # finialize JSON string
print_json
# Summary.
if [ ${DO_JSON} -eq 0 ] ; then
echo
[ ${warnings} -gt 0 ] && echo "${warnings} warning(s) have been found."
[ ${fails} -gt 0 ] && echo "${fails} error(s) have been found."
if [ ${fails} -gt 0 ] ; then
echo "Saptune will not work properly!"
else
if [ ${warnings} -gt 0 ] ; then
echo "Saptune should work properly, but better investigate!"
else
echo "Saptune is set up correctly."
fi
fi
fi
[ ${fails} -gt 0 ] && return 1
return 0
}
function help() {
echo "Usage: ${0##*/} [--json] [--force-color]"
}
function intro() {
[ ${DO_JSON} -ne 0 ] && return
echo -e "\nThis is ${0##*/} v${VERSION}.\n"
echo -e "It verifies if saptune is set up correctly."
echo -e "Please keep in mind:"
echo -e " - This tool does not check, if the tuning itself works correctly."
echo -e " - Follow the hints from top to down to minimize side effects.\n"
}
# --- MAIN ---
declare -a JSON_NOTE_MESSAGES JSON_WARN_MESSAGES JSON_FAIL_MESSAGES JSON_REMEDIATION_MESSAGES
unset JSON_STRING _JSON_NESTING
DO_JSON=0
FORCE_COLOR=0
# Check parameters.
for param in ${@} ; do
case "${param}" in
--json)
DO_JSON=1
;;
--force-color)
FORCE_COLOR=1
;;
*)
echo "Unknown parameter: ${param}" >&2
help
exit 3
;;
esac
done
# Determine if we are running a SLES.
eval $(grep ^ID= /etc/os-release)
[ "${ID}" != "sles" ] && { echo "Only SLES is supported! Your OS ID is ${ID}! Exiting." ; exit 2 ; }
# Introduction.
intro
collect_data
check_saptune
# Bye.
exit $?