No word about CheckMK’s supplied documentation :expressionless:
In this example we add some features for the included SNMP-check kemp_loadmaster_services
. For whatever reason, the included check only has hardcoded thresholds, and only takes "Active Connections" into account. Besides it is not yet migrated to CMK v2.0 which we will do in this course.
Plugin Structure
SNMP-plugins only need about three files
└── local
├── bin
├── lib
│ ├── check_mk -> python3/cmk
│ └── python3
│ └── cmk
│ └── base
│ └── plugins
│ └── agent_based
│ └── kemp_loadmaster_services_extended.py # Check-File: file where the output of the agent is analyzed
└── share
└── check_mk
├── checkman
│ └── kemp_loadmaster_services_extended # Checkman-File: file with info about the check
└── web
└── plugins
└── wato
└── kemp_loadmaster_services_extended_parameters.py # Check-Parameters-UI-File: file which create the WATO-UI page for configuring the parameters of the check
You may notice that the new check-plugin is placed in the agent_based
-folder – yes, looks like migrated/new plugins should be placed in there. Looking at ~/lib/check_mk/base/plugins/agent_based/
you’ll see that only a handful of SNMP-checks and normal agent-plugin checks are in there.
For the plugin we create here, I orientated on the
netscaler_vserver.py
plugin.
Check-File
The check-file got a little bit more complicated in my eyes. Parsing the input uses similar functions like the netscaler_vserver.py
– a in my eyes "helper"-function to create some dictionary. In the old CMK v1.6 this was a little bit easier achieved – but well, here we go with the new way.
In older CMK-versions it was possible to override included checks by only copying the built-in one and adding additional stuff. CMKv2 seems to complain about that – not sure if it’s only because of "overriding" a legacy-check.
#!/usr/bin/env python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# 2021-10-11 c.steinkogler[at]cashpoint.com
#
# Check_MK kemp_loadmaster_services_extended
# an extended SNMP CheckPlugin with parts reused from the included check
#
# This is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation in version 2. This file is distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU General Public License for more de-
# ails. You should have received a copy of the GNU General Public
# License along with GNU Make; see the file COPYING. If not, write
# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
# Boston, MA 02110-1301 USA.
# I use pycharm so that I can use the "Go To" -> "Declaration or Usages"
# Not sure if it's smart to keep those in productive checks else comment
# those if they create problems
import time
from typing import TypedDict, Mapping, List, Iterable, Tuple
from cmk.base.plugins.agent_based.agent_based_api.v1 import *
from cmk.base.item_state import set_item_state, get_item_state
# As documentation about SNMP-checks is scarce I orientated at the code from
# ~/lib/check_mk/base/plugins/agent_based/netscaler_vserver.py which gets
# the normal "string_table" and creates a dictionary from it
# It seems we cannot "overload" the included legacy kemp_loadmaster_service
# check - so we have to disable the check via rule and create this "extended" one
map_states = {
1: (0, 'in service'),
2: (2, 'out of service'),
3: (2, 'failed'),
4: (0, 'disabled'),
5: (1, 'sorry'),
6: (0, 'redirect'),
7: (2, 'error message'),
}
map_summary_states = {
0: "(.)",
1: "(!)",
2: "(!!)",
3: "(?)",
}
class KempService(TypedDict, total=False):
service_name: str
device_state: int
conns: int
active_conns: int
# endclass
Section = Mapping[str, KempService]
# a helper function for parsing the input
def _to_kemp_service_extended(line: Iterable[str]) -> Tuple[str, KempService]:
# >>> import pprint
# >>> pprint.pprint(_to_kemp_service_extended(['someservice_http', '1', '38885', '0']))
# ('someservice_http',
# {'device_state': '1',
# 'conns': 38885,
# 'active_conns': '0'})
(service_name, device_state, conns, active_conns) = line
kempservice: KempService = {
'device_state': int(device_state),
'conns': int(conns),
'active_conns': int(active_conns)
}
return service_name or service_name, kempservice
# enddef
def parse_kemp_loadmaster_services_extended(string_table: List[type_defs.StringTable]) -> Section:
"""
The normal "string_table" looks like:
[['a_dummy', '1', '420', '0'],
['b_dummy', '4', '0', '0']]
with the parse-function it should create:
{'a_dummy': {'active_conns': 0, 'conns': 0, 'device_state': '4'},
'b_dummy': {'active_conns': 0, 'conns': 14, 'device_state': '1'}}
"""
return dict(_to_kemp_service_extended(line) for line in string_table[0])
# enddef
register.snmp_section(
name="snmp_kemp_loadmaster_services_extended_info",
parse_function=parse_kemp_loadmaster_services_extended,
fetch=[
SNMPTree(
base=".1.3.6.1.4.1.12196.13.1.1",
oids=[
"13", # B100-MIB::vSname
"14", # B100-MIB::vSstate
"16", # B100-MIB::vSConns - Total number of connections in the lifetime of Virtual Services
"21" # B100-MIB::vSactive_conns - Total number of current active connections of Virtual Services
],
),
],
detect=exists(".1.3.6.1.2.1.1.2.0"),
)
def discover_kemp_loadmaster_services_extended(section: Section) -> type_defs.DiscoveryResult:
for service_name in section:
if section[service_name]['device_state'] not in [4, ""]:
yield Service(item=service_name)
# endif
# endfor
# enddef
def check_kemp_loadmaster_services_extended(item, params, section: Section):
warn_active_conns, crit_active_conns = params['active_conns']
warn_connspersec, crit_connspersec = params['connspersec']
summary = ""
if item in section:
data = section[item]
dev_state = data['device_state']
# we read the state from the mapping table - if there is an completely unknown one, default to 3 and return the state-number received
state, state_readable = map_states.get(dev_state, (3, 'unknown[%s]' % dev_state))
summary += "Status: %s %s" % (state_readable, map_summary_states.get(int(state)))
active_conns_state = State.OK
active_conns = data['active_conns']
if active_conns is not None:
if active_conns > warn_active_conns and active_conns >= crit_active_conns:
active_conns_state = State.worst(State.CRIT, active_conns_state)
elif warn_active_conns <= active_conns < crit_active_conns:
active_conns_state = State.worst(State.WARN, active_conns_state)
else:
active_conns_state = State.worst(State.OK, active_conns_state)
# endif
# not completely sure why we have to explicitly tell with `int(state)` to resolve e.g. State.OK to the corresponding number value
summary += ", Active connections approximately: %s %s" % (active_conns, map_summary_states.get(int(active_conns_state)))
yield Metric('active_conns', active_conns, levels=(warn_active_conns, crit_active_conns))
# endif
conns_state = State.OK
conns = data['conns']
# conns value is a counter-value which only gets incremented
if conns is not None:
# set_item_state & get_item_state: writes and saves values into some file. We have to do that to later calculate
# a rough connections/sec value, where conns is showing us a estimation about how many new connections/sec are created
kemp_loadmaster_service_total_conns = get_item_state('kemp_loadmaster_service_total_conns')
kemp_loadmaster_service_total_conns_timestamp = get_item_state('kemp_loadmaster_service_total_conns_timestamp')
if kemp_loadmaster_service_total_conns is None:
epoch_timestamp_now = int(time.time())
set_item_state('kemp_loadmaster_service_total_conns', conns)
set_item_state('kemp_loadmaster_service_total_conns_timestamp', epoch_timestamp_now)
conns_state = State.worst(State.UNKNOWN, state)
summary += ", Connections approximately per second: %s %s" % (0, map_summary_states.get(int(conns_state)))
yield Metric('connspersec', 0, levels=(warn_active_conns, crit_active_conns))
else:
epoch_timestamp_now = int(time.time())
set_item_state('kemp_loadmaster_service_total_conns', conns)
set_item_state('kemp_loadmaster_service_total_conns_timestamp', epoch_timestamp_now)
if kemp_loadmaster_service_total_conns <= conns:
time_diff = epoch_timestamp_now - kemp_loadmaster_service_total_conns_timestamp
conns_diff = conns - kemp_loadmaster_service_total_conns
conns_per_second = int(conns_diff / time_diff)
if conns_per_second > warn_connspersec and conns_per_second >= crit_connspersec:
conns_state = State.worst(State.CRIT, conns_state)
elif warn_connspersec <= conns_per_second < crit_connspersec:
conns_state = State.worst(State.WARN, conns_state)
else:
conns_state = State.worst(State.OK, conns_state)
# endif
summary += ", Connections approximately per second: %s %s" % (conns_per_second, map_summary_states.get(int(conns_state)))
yield Metric('connspersec', conns_per_second, levels=(warn_active_conns, crit_active_conns))
else:
conns_state = State.worst(State.UNKNOWN, conns_state)
summary += ", Connections approximately per second: %s %s" % (0, map_summary_states.get(int(conns_state)))
yield Metric('connspersec', 0, levels=(warn_active_conns, crit_active_conns))
# endif
# endif
# endif
# we get the worst state from all
state = State.worst(state, active_conns_state, conns_state)
yield Result(state=state, summary=summary)
# endif
# enddef
register.check_plugin(
name="kemp_loadmaster_services_extended",
sections=["snmp_kemp_loadmaster_services_extended_info"],
service_name="Service %s",
discovery_function=discover_kemp_loadmaster_services_extended,
check_function=check_kemp_loadmaster_services_extended,
check_ruleset_name="kemp_loadmaster_services_extended",
check_default_parameters={
'active_conns': (2200, 3000),
'connspersec': (300, 500),
},
)
The def check_kemp_loadmaster_services_extended
didn’t change a lot in comparison to the old-version for CMK v1.6. Only the way how the result and metrics are yielded changed. In earlier versions it virtually was possible in on line – now you need two it seems.
Check-Parameters-UI-File
The parameters-file also doesn’t look "a lot" different. Only a few lines changed/were added.
#!/usr/env/bin python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
#
# 2021-10-11 c.steinkogler[at]cashpoint.com
#
# Check_MK kemp_loadmaster_services_extended
# an extended SNMP CheckPlugin with parts reused from the included check
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 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, see <http://www.gnu.org/licenses/>.
# As documentation about these parameter-files is scarce I orientated on the code from
# ~/lib/python3/cmk/gui/plugins/wato/check_parameters/netscaler_vserver.py
# for autotranslating stuff it seems
from cmk.gui.i18n import _
# we need Dictionary, Tuple and Integer as we use those later
from cmk.gui.valuespec import (
Dictionary,
Tuple,
Integer,
TextAscii,
)
from cmk.gui.plugins.wato import (
CheckParameterRulespecWithItem,
rulespec_registry,
RulespecGroupCheckParametersOperatingSystem,
)
def _parameter_valuespec_kemp_loadmaster_services_extended():
return Dictionary(
elements=[
("active_conns",
Tuple(
title=_("Levels for Active Connections"),
help=_("The check detects the active connections"),
elements=[
Integer(title=_("Warning at"), default_value=1500),
Integer(title=_("Critical at"), default_value=2000),
]
),
),
("connspersec",
Tuple(
title=_("Levels for Connections per second"),
help=_("The check detects Connections per second"),
elements=[
Integer(title=_("Warning at"), default_value=1500),
Integer(title=_("Critical at"), default_value=2000),
]
),
)
]
)
# enddef
rulespec_registry.register(
CheckParameterRulespecWithItem(
check_group_name="kemp_loadmaster_services_extended",
group=RulespecGroupCheckParametersOperatingSystem,
item_spec=lambda: TextAscii(title=_("Name of Service")),
match_type="dict",
parameter_valuespec=_parameter_valuespec_kemp_loadmaster_services_extended,
title=lambda: _("Kemp Service Connection Thresholds"),
)
)
As mentioned in the code – if you want to have a look at the built-in parameter-files, you have to go to
~/lib/python3/cmk/gui/plugins/wato/check_parameters/
Checkman-File
And again the least important file, the checkman file.
You can find the built-in checkman-files in
./share/check_mk/checkman/
title: Kemp Loadmaster: Active Connections (extended)
agents: snmp
catalog: hw/network/kemp
license: GPL
distribution: check_mk
description:
This check monitors the state and number of active connections of the services (VS)
running on a Kemp Loadmaster. It is known to run on Kemp Loadmaster LM-2600 and LM-3600.
It will return OK if the service is in state 'in Service' or 'redirect', WARN if the
service is in state 'sorry', CRIT if in state 'out of Service',
'failed' or 'errormsg', 'disabled' services will not be inventorized or if later disabled
they will be shown as OK - Otherwise it will result in UNKNOWN.
Additionally thresholds can be set for current active and "new" connections.
inventory:
One service is created for each VS.
item:
The name of the VS as found in the oid .1.3.6.1.4.1.12196.13.1.1.13
Kommentare