From 1e7b62e20d29ea382c35a23b78f12ad434d501b1 Mon Sep 17 00:00:00 2001
From: "Hahn Axel (hahn)" <axel.hahn@iml.unibe.ch>
Date: Thu, 10 Sep 2020 18:39:09 +0200
Subject: [PATCH] add mysqlserver checks (work in progress so far)

---
 check_mysqlserver      | 300 +++++++++++++++++++++++++++++++++++++++++
 inc_pluginfunctions    |  99 +++++++++-----
 inc_pluginfunctions.md |  15 +++
 3 files changed, 378 insertions(+), 36 deletions(-)
 create mode 100755 check_mysqlserver

diff --git a/check_mysqlserver b/check_mysqlserver
new file mode 100755
index 0000000..45e7d65
--- /dev/null
+++ b/check_mysqlserver
@@ -0,0 +1,300 @@
+#!/bin/bash
+# ======================================================================
+#
+# !!! WORK IN PROGRESS !!! DO NOT USE YET !!!
+#
+# Check MYSQL / MARIADB SERVER
+#
+# requirements:
+# - mysql client
+#
+# installation:
+# - execute check_mysqlserver -i
+#
+# ----------------------------------------------------------------------
+# 2020-08-xx  v0.0  <axel.hahn@iml.unibe.ch>
+# ======================================================================
+
+
+. `dirname $0`/inc_pluginfunctions
+_version="0.1"
+
+# --- set HOME
+HOME=/etc/icinga2-passive-client
+# cd $( dirname $0)
+# cd ..
+# HOME=$( pwd ); export HOME
+# cd - >/dev/null
+
+# --- other vars...
+cfgfile=$HOME/.my.cnf
+myuser=icingamonitor
+datafile=/tmp/mysqlvars.out
+
+# new line
+NL="
+"
+
+out=
+
+# ----------------------------------------------------------------------
+# FUNCTIONS
+# ----------------------------------------------------------------------
+
+# uninstall database user
+function _uninstall(){
+    echo UNINSTALL ...
+    mysql -e "DROP user $myuser@localhost ;"
+}
+
+# (re)install database user for monitoring
+function _install(){
+    echo INSTALLING ...
+    local pwlength=64
+    
+    echo "- check mysql connection..."
+    mysql -e ";"
+    if [ $? -ne 0 ]; then
+        echo "ERROR: mysql connect without password failed."
+        exit 1
+    fi
+
+    echo "- creating mysql user $myuser@localhost with random password ($pwlength chars)..."
+    mypw=$( head /dev/urandom | tr -dc A-Za-z0-9 | head -c $pwlength )
+
+    mysql -e "CREATE USER $myuser@localhost IDENTIFIED BY '$mypw';"
+    if [ $? -ne 0 ]; then
+        echo "ERROR: mysql command to create user failed."
+        exit 1
+    fi
+    echo "- grant SELECT on mysql tables ..."
+    mysql -e "GRANT SELECT ON mysql.* TO $myuser@localhost;"
+    if [ $? -ne 0 ]; then
+        echo "ERROR: mysql command to grant permissions failed."
+        exit 1
+    fi
+
+    echo "- flush privileges ..."
+    mysql -e "FLUSH PRIVILEGES;"
+
+    echo "- creating config file $cfgfile ... "
+    cat  >$cfgfile <<EOF
+#
+# generated on `date`
+#
+[client]
+user=$myuser
+host=localhost
+password=$mypw
+
+EOF
+    ls -l $cfgfile
+    if [ $? -ne 0 ]; then
+        echo "ERROR: creation of config file failed."
+        exit 1
+    fi
+}
+
+function _usage(){
+    cat <<EOH
+______________________________________________________________________
+
+CHECK MYSQL SERVER :: v${_version}
+
+(c) Institute for Medical Education - Univerity of Bern
+Licence: GNU GPL 3
+______________________________________________________________________
+
+USAGE:
+  `basename $0` [OPTIONS] -m METHOD
+
+OPTIONS:
+  -h  this help
+  -i  install monitoring user (must be executed as root)
+  -u  uninstall monitoring user (must be executed as root)
+
+PARAMETERS:
+  -m  method; valid methods are:
+      connections  current/ max connections
+      connects     connects per min and aborted connections/ clients
+      commands     current running statements insert, select, ...
+
+EXAMPLES:
+  `basename $0` -i
+  `basename $0` -m commands
+
+EOH
+}
+
+function _mysqlreadvars(){
+    mysql -e "SHOW GLOBAL VARIABLES ;" --skip-column-names >$datafile
+    mysql -e "SHOW STATUS ;"           --skip-column-names >>$datafile
+}
+
+function _mysqlgetvar() {
+    local sVarname=$1
+    grep "^$sVarname[^_a-z]" ${datafile} | awk '{ print $2 }'
+}
+# get a value from mysql status output
+# param  string  variable name
+# param  string  prefix to remove from beginning of variable
+# param  (set)   flag if it is a delta value
+function _mysqlrendervar() {
+    local sVarname=$1
+    local sRemove=$2
+    local sDeltaUnit=$3
+    local sFloat=$4
+
+    # local iValue=`grep "^$sVarname[^_a-z]" ${datafile} | awk '{ print $2 }'`
+    local iValue=`_mysqlgetvar "$sVarname"`
+    if [ "$iValue" = "" ]; then
+            ph.abort "no value for ${sVarname}"
+    fi
+
+    # get label for perfdata
+    local sLabel=`echo ${sVarname} | sed "s#^${sRemove}##g"`
+    
+    if [ ! -z $sDeltaUnit ]; then
+        local iSpeed=` ph.perfdeltaspeed "mysql-${sVarname}" ${iValue} $sDeltaUnit $sFloat`
+        out="${out}${sLabel}: ${iValue} ... delta = ${iSpeed} per $sDeltaUnit${NL}"
+        ph.perfadd "${sLabel}"   "${iSpeed}"
+    else
+        out="${out}${sLabel}: ${iValue}${NL}"
+        ph.perfadd "${sLabel}" "${iValue}"
+    fi
+
+}
+
+function _mysqlrenderdelta() {
+    local deltaUnit=$3
+    test -z $deltaUnit && deltaUnit="sec"
+    _mysqlrendervar "$1" "$2" $deltaUnit $4
+}
+# ----------------------------------------------------------------------
+# MAIN
+# ----------------------------------------------------------------------
+
+bOptInstall=`   ph.hasParamoption "i" "$@"`
+bOptUninstall=` ph.hasParamoption "u" "$@"`
+bOptHelp=`      ph.hasParamoption "h" "$@"`
+
+if [ $bOptHelp -eq 1 -o $# -lt 1 ]; then
+    _usage
+    exit 0
+fi
+
+# --- check required tools
+ph.reqire mysql
+
+# --- install
+if [ $bOptInstall -eq 1 -a "$( whoami )" = "root" ]; then
+    if [ -f $cfgfile ]; then
+        ph.status "SKIP installation. config file already exists: $cfgfile."
+        ph.exit
+    fi
+    HOME=/root
+    export HOME
+
+    _uninstall
+    _install
+
+    ph.status "Installation was done"
+    ph.exit
+fi
+
+# --- uninstall
+if [ $bOptUninstall -eq 1 -a "$( whoami )" = "root" ]; then
+    HOME=/root
+
+    _uninstall
+    rm -f $cfgfile
+
+    ph.status "Uninstalled."
+    ph.exit
+fi
+
+
+# --- check installation
+grep $myuser $cfgfile >/dev/null 2>/dev/null 
+if [ $? -ne 0 ]; then
+    ph.abort "MYSQL access not possible yet. You need to install the monitoring user first: as root execute `basename $0` -i"
+fi
+
+# ----------------------------------------------------------------------
+
+# --- get data
+_mysqlreadvars
+if [ $? -ne 0 ]; then
+    rm -f $datafile
+    ph.abort "MYSQL access not possible. Maybe Mysql is not running??"
+fi
+
+# --- set optional limits
+# typeset -i iWarnLimit=`     ph.getValueWithParam 0 w "$@"`
+# typeset -i iCriticalLimit=` ph.getValueWithParam 0 c "$@"`
+
+sMode=`ph.getValueWithParam '' m "$@"`
+
+case "${sMode}" in
+
+    "connections")
+        descr="current/ max connections"
+
+        _mysqlrendervar    max_connections
+        _mysqlrendervar    Max_used_connections
+        _mysqlrendervar    Threads_connected
+        ;;
+
+    "connects")
+        descr="connects per min and aborted connections/ clients"
+        _mysqlrenderdelta  Connections            "" min float
+        _mysqlrenderdelta  Aborted_clients        "" min float
+        _mysqlrenderdelta  Aborted_connects       "" min float
+        ;;
+
+    "commands")
+        descr="currently executed commands"
+        _mysqlrendervar Com_delete  "Com_"
+        _mysqlrendervar Com_insert  "Com_"
+        _mysqlrendervar Com_replace "Com_"
+        _mysqlrendervar Com_select  "Com_"
+        _mysqlrendervar Com_update  "Com_"
+        ;;
+
+    "qcache-blocks")
+        descr="query cache blocks"
+        _mysqlgetvar "have_query_cache" | grep "YES" >/dev/null || ph.abort "Query cache is not active"
+        _mysqlrendervar Qcache_total_blocks "Qcache_"
+        _mysqlrendervar Qcache_free_blocks  "Qcache_"
+        ;;
+
+    "qcache-queries")
+        descr="query cache data"
+        _mysqlgetvar "have_query_cache" | grep "YES" >/dev/null || ph.abort "Query cache is not active"
+        _mysqlrendervar    Qcache_queries_in_cache  "Qcache_"
+        _mysqlrenderdelta  Qcache_not_cached        "Qcache_"  min  float
+        _mysqlrenderdelta  Qcache_lowmem_prunes     "Qcache_"  min  float
+        ;;
+    "qcache-hits")
+        descr="query cache hits"
+        _mysqlgetvar "have_query_cache" | grep "YES" >/dev/null || ph.abort "Query cache is not active"
+        _mysqlrendervar    Qcache_hits        "Qcache_"
+        _mysqlrendervar    Qcache_inserts     "Qcache_"
+        _mysqlrendervar    Qcache_not_cached  "Qcache_"
+
+        ;;
+
+    *)
+        echo ERRROR: [${sMode}] is an INVALID mode
+        _usage
+        ph.abort
+
+esac
+
+ph.status "Mysql $sMode :: $descr"
+echo "$out"
+
+rm -f $datafile
+ph.exit
+
+# ----------------------------------------------------------------------
\ No newline at end of file
diff --git a/inc_pluginfunctions b/inc_pluginfunctions
index 35d6321..36cf81e 100644
--- a/inc_pluginfunctions
+++ b/inc_pluginfunctions
@@ -6,20 +6,29 @@
 # ----------------------------------------------------------------------
 #
 # Functions 
-#   ph.abort [TEXT]     shows error message and exit with status unknown 
-#   ph.exit             exit plugin (with set statuscode)
+#   execIfReady COMMAND [SLEEPTIME] [MAXTRIES]
+#                       execute a command max MAXTRIES times until it returns
+#                       exitcode 0 (or reaches limit)
 #   ph.getOS            get operating system as lowercase - centos|debian|ubuntu|...
 #   ph.getOSMajor       get OS Major version as integer, i.e. 7 on a CentOS7
-#   ph.getValueWithParam VALUE  PARAMNAME "$@"
+#   ph.getValueWithParam VALUE PARAMNAME "$@"
 #                       return default value or its override from command line
+#   ph.hasParamoption PARAMNAME "$@"
+#                       check if a letter was used as command line option
+#   ph.toUnit VALUE TO-UNIT
+#                       convert value i.e. "12M" into other unit i.e. "K"
 #   ph.setStatus VALUE  set a status
 #   ph.setStatusByLimit VALUE WARNLIMIT CRITLIMIT
 #   ph.status [TEXT]    show status as Text
 #
+#   ph.abort [TEXT]     shows error message and exit with status unknown 
+#   ph.exit             exit plugin (with set statuscode)
+#
 # ----------------------------------------------------------------------
 # 2016-09-23  added getOS
 # 2019-10-29  added setExitcode
 # 2020-03-05  v1.2  <axel.hahn@iml.unibe.ch> switch to ph.* helper functions
+# 2020-09-01  v1.3  <axel.hahn@iml.unibe.ch> added ph.hasParamoption
 # ======================================================================
 
 
@@ -30,10 +39,11 @@ typeset -i ph_cfg__EXIT_CRITICAL=2
 typeset -i ph_cfg__EXIT_UNKNOWN=3
 
 declare ph_perfdatafile=
-declare -a ph_perfdata
+# declare -a ph_perfdata
 typeset -i ph_perfcounter=0
 
-
+# save command line params
+ph_cmd__params="$@"
 
 
 # abort a check and exit with status "unknown"
@@ -129,6 +139,37 @@ function ph.getValueWithParam(){
   echo $_value
 }
 
+# check if a letter was used as command line option and return as 0 (=no) or 1 (=yes)
+# ph.hasParamoption "h" "$@"
+# param  string  parameter(letter) to test
+# param  string  "$@"
+function ph.hasParamoption(){
+  local _sParam=$1
+  local _opt
+  shift 1
+
+  # trick I: allows ${_sParam} in case .. esac section
+  shopt -s extglob 
+
+  # trick II: allows usage of getopts multiple times
+  OPTIND=1
+
+  # trick III: changing getops params with a single param do not work ... 
+  # while getopts ":$_sParam:" opt; do
+  while getopts "abcdefghijklmnopqrstuvwxyz" _opt; do
+    # echo "DEBUG: testing $_sParam in ${_opt} ..."
+    case "${_opt}" in
+      $_sParam)
+        echo "1"
+        return 1
+        ;;
+    esac
+  done
+  echo "0"
+  return 0
+}
+
+
 # set an exitcode for the plugin
 #
 # example:
@@ -325,7 +366,20 @@ function ph._getperflabel(){
 function ph.perfdeltaspeed(){
         local varName=$1
         local value=$2
+        local deltaUnit=$3
+        local isFloat=$4
+
+        local bcParam=
+
         typeset -i newvalue=0
+        typeset -i deltaFactor=1
+
+        test "$deltaUnit" = "s"   && deltaFactor=1
+        test "$deltaUnit" = "sec" && deltaFactor=1
+        test "$deltaUnit" = "m"   && deltaFactor=60
+        test "$deltaUnit" = "min" && deltaFactor=60
+
+        test "$isFloat" = "float" && bcParam="-l"
 
         # get last value
         local lastvalue=`ph._readlastvalue "${varName}"`
@@ -344,9 +398,9 @@ function ph.perfdeltaspeed(){
                         local iage=`ph._getageoflastvalue "${varName}"`
                         test $iage = 0 && iage=1
 
-                        local delta=$(( ${value}-$lastvalue ))
-                        local newvalue=$((  ${delta}/(${iage})  ))
-                        retvalue=$newvalue
+                        local delta=`echo "${value}-$lastvalue" | bc $bcParam `
+                        local deltaspeed=`echo "${delta}*${deltaFactor}/(${iage})" | bc $bcParam`
+                        retvalue=$deltaspeed
                 fi
         fi
         ph._savecounter "${varName}" "${value}"
@@ -359,32 +413,6 @@ function ph.perfdeltaspeed(){
         # gdAddLabel "... ${varName} absolute: ${value} -> delta: ${delta} -> derive: ${retvalue} ($iage sec)"
 }
 
-# W.I.P.
-# add performance data with total counters and store change speed of it
-# i.e. network byte or package counter in /proc/net/dev
-#
-# OUTPUT-SYNTAX
-# 'label'=value[UOM];[warn];[crit];[min];[max]
-# procs=401;200;300;0;
-#
-# example
-# ph.perfadd  
-function ph.perfaddtotal(){
-
-  local _label=`ph._getperflabel "$1"`
-  
-  local _value=$2
-  
-
-  local _w=$3
-  local _c=$4
-  local _min=$5
-  local _max=$6
-
-  ph.perfadd 
-  echo "${_label}=${_value};${_w};${_c};${_min};${_max}" >>${ph_perfdatafile}
-} 
-
 # add performance data
 #
 # OUTPUT-SYNTAX
@@ -430,7 +458,7 @@ function ph.perfshow(){
 # param  string    command line to execute
 # param  integer   sleeptime in sec before repeating again; default: 5
 # param  integer   max number of tries; default: 3
-ph.execIfReady(){
+function ph.execIfReady(){
   local _cmd2run=$1
   local _iSleeptime=${2:-5}
   local _iMaxTry=${3:-3}
@@ -464,6 +492,5 @@ ph.execIfReady(){
 # init
 ph.setStatus "ok"
 
-
 # ----------------------------------------------------------------------
 
diff --git a/inc_pluginfunctions.md b/inc_pluginfunctions.md
index 8a66525..e489a03 100644
--- a/inc_pluginfunctions.md
+++ b/inc_pluginfunctions.md
@@ -48,6 +48,21 @@ Example:
 
 This will set variable iWarnLimit based on CLI parameter -w [value] ... if it does not exist it gets the default 75.
 
+**ph.hasParamoption** PARAMNAME "$@"
+
+check if a letter was used as command line option and return as 0 (=no) or 1 (=yes)
+
+Example:
+
+```bash
+bOptHelp=`ph.hasParamoption "h" "$@"`
+
+if [ $bOptHelp -eq 1 -o $# -lt 1 ]; then
+    _usage
+    exit 0
+fi
+```
+
 **ph.setStatus** [STATUS]
 
 Set a status with keyword ok, warning, critical, unknown
-- 
GitLab