CMK version: Production 2.0.p20 OS version: CentOS 7
Working on a custom special agent plugin and have something functional.
I would like to know if there’s a way to include a ‘details’ for the check in addition to the standard state and summary. I see on the Writing your own check plug-ins page references to yielding details but that seems specific to the installed client with a local plugin. Is there a specific import that can be used to do something similar for a special agent?
My goal is to include some sub-site information in the details if the status is not ‘operational’.
I attempted to use the from .agent_based_api.v1 import * coupled with register.check_plugin() instead of a check_info[]={} and yield Result(state=State.OK, summary="summary info", details="detailed info").
This resulted in errors so my assumption is I need to import some alternate module or I’m misunderstanding the layout. Looking through examples is slightly confusing to me with the transitions to 2.0 and greater.
My code is below
#!/usr/bin/env python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
"""
Example output from special agent:
<<<cloudflare_status_api>>>
id name status group_id group components(if any)
38w9dn5m8n4p Tianjin,_China_-_(TSN) operational 77867vxkttgw False None
Example of string_table input for parsing
[['38w9dn5m8n4p','Tianjin,_China_-_(TSN)','operational','77867vxkttgw','False','None'],]
"""
def parse_cloudflare_status_api(string_table):
parsed = []
for site in string_table:
site_dict = {}
site_dict["id"] = site[0]
site_dict["name"] = modify_strings(site[1])
site_dict["status"] = site[2]
site_dict["group_id"] = site[3]
site_dict["group"] = site[4]
site_dict["components"] = site[5]
parsed.append(site_dict)
return parsed
def modify_strings(data):
if isinstance(data, str):
mod_data = data.replace("!", "’")
return mod_data.replace("_"," ")
else:
return data
def discovery_cloudflare_status_api(parsed):
for site in parsed:
# filter for the group
if site["group_id"] == "1km35smx8p41" or site["group"] == 'True':
yield Service(site["name"])
def check_cloudflare_status_api(item, params, parsed):
# default to Unknown
exit_code = 2
# Notify if empty parsed input
if parsed == []:
yield 3, 'No pool data available'
# Filter on the item passed and exit_code
else:
for site in parsed:
if site["name"] == item:
output = f'{site["name"]}'
if site["status"] == "operational":
output += ' is fully operational.'
exit_code = 0
elif site["status"] == "partial_outage":
output += ' is in a partial outage.'
exit_code = 1
elif site["status"] == "degraded_performance":
output += ' is experiencing degraded performance.'
exit_code = 1
else:
output += ' is in an unidentified or critical state.'
exit_code = 2
yield exit_code, output
check_info['cloudflare_status_api'] = {
'check_function': check_cloudflare_status_api,
'inventory_function': discovery_cloudflare_status_api,
'parse_function': parse_cloudflare_status_api,
'service_description': 'Cloudflare Service %s',
'has_perfdata': False,
'group': 'cloudflare_status_api_group',
}
Any guidance or reference to documents for a 2.0+ special agent specifically or an example code would be greatly appreciated.
the examples in Writing your own check plug-ins are actually pretty good and for the check logic, it really doesn’t matter if the section being parsed and checked comes from a special agent or a check plugin.
would you mind sharing the code you wrote that uses the register.check_plugin(), yield Result() and other 2.0 API compatible parts incl. the error message, then I suppose me (and others) can try to fix this with you :).
Good to know. Likely it’s my inexperience because I felt like the documentation was switching gears between check types as it presented examples.
Certainly, and I’m sure it’s something I’ve misinterpreted.
With the code below I see the error Error in plugin file /omd/sites/custnms01/local/share/check_mk/checks/cloudflare_status_api: "'__name__' not in globals"
#!/usr/bin/env python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
"""
Example output from special agent:
<<<cloudflare_status_api>>>
id name status group_id group components(if any)
38w9dn5m8n4p Tianjin,_China_-_(TSN) operational 77867vxkttgw False None
Example of string_table input for parsing
[['38w9dn5m8n4p','Tianjin,_China_-_(TSN)','operational','77867vxkttgw','False','None'],]
"""
from .agent_based_api.v1 import *
def parse_cloudflare_status_api(string_table):
parsed = []
for site in string_table:
site_dict = {}
site_dict["id"] = site[0]
site_dict["name"] = modify_strings(site[1])
site_dict["status"] = site[2]
site_dict["group_id"] = site[3]
site_dict["group"] = site[4]
site_dict["components"] = site[5]
parsed.append(site_dict)
return parsed
def modify_strings(data):
if isinstance(data, str):
mod_data = data.replace("!", "’")
return mod_data.replace("_"," ")
else:
return data
def discovery_cloudflare_status_api(parsed):
for site in parsed:
# filter for the group
if site["group_id"] == "1km35smx8p41" or site["group"] == 'True':
yield Service(item=site["name"])
def check_cloudflare_status_api(item, params, parsed):
# default to Unknown
exit_code = state.OK
# Notify if empty parsed input
if parsed == []:
yield Result(
state.CRIT,
summary='No pool data available'
)
# Filter on the item passed and exit_code
else:
for site in parsed:
if site["name"] == item:
output = f'{site["name"]}'
if site["status"] == "operational":
output += ' is fully operational.'
exit_code = state.OK
elif site["status"] == "partial_outage":
output += ' is in a partial outage.'
exit_code = state.WARN
elif site["status"] == "degraded_performance":
output += ' is experiencing degraded performance.'
exit_code = state.WARN
else:
output += ' is in an unidentified or critical state.'
exit_code = state.CRIT
yield Result(
state=exit_code,
summary=output,
details='detailed output'
)
register.check_plugin(
name = "cloudflare_status_api",
service_name = "Cloudflare Service %s",
discovery_function = discovery_cloudflare_status_api,
check_function = check_cloudflare_status_api,
parse_function = parse_cloudflare_status_api,
)
I appreciate the links to the repository and will look through those. If you spot something glaringly obvious please point it out. The time pressure for this isn’t as intense for now since I have a working check but I’d love to improve by providing subsite information on non-operational service states.
I think the parse_function is registered for the agent_section, not for the check_plugin:
register.agent_section(
name = "linux_usbstick",
parse_function = parse_linux_usbstick,
)
Other than that, I’d suggest running “cmk --debug -v <host_with_your_special_agent>” as debug will give you a little more detail on where/why python or checkmk are unhappy with your code.
And removed parse_function line from the register.check_plugin.
Running with debug provides a traceback that I’m not sure how to interpret but still shows the same bottom line error message.
$ cmk --debug -v Cloudflare_Services
Error in plugin file /omd/sites/custnms01/local/share/check_mk/checks/cloudflare_status_api.py: "'__name__' not in globals"
Traceback (most recent call last):
File "/omd/sites/custnms01/bin/cmk", line 79, in <module>
errors = config.load_all_agent_based_plugins(check_api.get_check_api_context)
File "/omd/sites/custnms01/lib/python3/cmk/base/config.py", line 1528, in load_all_agent_based_plugins
errors.extend(load_checks(get_check_api_context, filelist))
File "/omd/sites/custnms01/lib/python3/cmk/base/config.py", line 1609, in load_checks
did_compile |= load_precompiled_plugin(f, check_context)
File "/omd/sites/custnms01/lib/python3/cmk/base/config.py", line 1901, in load_precompiled_plugin
exec(marshal.loads(Path(precompiled_path).read_bytes()[_PYCHeader.SIZE :]), check_context)
File "/omd/sites/custnms01/local/share/check_mk/checks/cloudflare_status_api.py", line 14, in <module>
from .agent_based_api.v1 import *
KeyError: "'__name__' not in globals"
The error is unclear on the cause. It’s pretty generic and google searches indicate including the parent module but that an import using “.agent_based_api.v1” should work. Any thoughts on this?
Hi Scott,
not sure how the import can already fail, especially as Writing your own check plug-ins section 2.5 uses the exactly same import line.
But. wait… I think your file is in the wrong location - nor sure if this could be the cause though:
old api: ~/local/share/check_mk/checks/ ← currently your path
new api checks: ~/local/lib/check_mk/base/plugins/agent_based/ ← where checks with the new api should go
Gerd,
Thanks for catching that. I think my trial/error approach and using existing special agent checks as examples and templates may have led me to what I had. I also noted that it mentioned “The agent_based is for all plug-ins that relate to the Checkmk agent (so not alerting plug-ins, for example).” which I considered my check a ‘special agent’ and wasn’t sure if it applied.
I tried moving the files from the checks folder into ~/local/lib/check_mk/base/plugins/agent_based/. The errors and tracebacks stopped however so did any output, discovery information or other data.
I’m not quite sure where I’ve gone wrong but I’ll keep trying and may start fresh to make sure I haven’t left behind any artifacts from the previous working check (without details).
Took some time to make it back to this script and topic but finally reserved some time to review.
Posting this to update those involved and for any future searches.
Should also note that an MKP for this updated code v1.0.2 was uploaded to the exchange this morning and I created a github repository for public review and feedback.
This was indeed correct for the file containing the parse, discover and checks.
For the special agent I was attempting the proper files were:
The reason for the lack of output at all after relocating the file turned out to be state=exit_code not being something Results() understood as valid.
Once corrected, started seeing output error.
$ cmk -I Cloudflare_Services
Error in agent based plugin cloudflare_status_api: discovery_function: expected arguments: 'section', actual arguments: 'parsed'
This was a holdover from the previous check and seemingly required using the name ‘section’ specifically for the method’s parameter. Also updated other instances of parsed to section for readability.
Next error was regarding params in the check function.
$ cmk -I Cloudflare_Services
Error in agent based plugin cloudflare_status_api: check_function: 'params' argument expected if and only if default parameters are not None
For now, removed params altogether but potentially explore putting it back later on when I incorporate useful parameters.
At this point the plugin was running but crashing in the webUI leading to this output which helpfully pointed to the exact issue.
OMD[cloudflaretest]:~/local/lib/check_mk/base/plugins/agent_based$ cmk --detect-plugins=cloudflare_status_api --debug -v Cloudflare_Services
+ FETCHING DATA
[ProgramFetcher] Execute data source
[PiggybackFetcher] Execute data source
No piggyback files for 'Cloudflare_Services'. Skip processing.
Traceback (most recent call last):
File "/omd/sites/cloudflaretest/bin/cmk", line 98, in <module>
exit_status = modes.call("--check", None, opts, args)
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/modes/__init__.py", line 69, in call
return handler(*handler_args)
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/modes/check_mk.py", line 1794, in mode_check
checking.commandline_checking(
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/agent_based/decorator.py", line 43, in wrapped_check_func
status, output_text = _combine_texts(check_func(hostname, *args, **kwargs))
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/agent_based/checking/__init__.py", line 121, in commandline_checking
return _execute_checkmk_checks(
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/agent_based/checking/__init__.py", line 174, in _execute_checkmk_checks
num_success, plugins_missing_data = check_host_services(
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/agent_based/checking/__init__.py", line 322, in check_host_services
success = _execute_check(
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/agent_based/checking/__init__.py", line 382, in _execute_check
submittable = get_aggregated_result(
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/agent_based/checking/__init__.py", line 470, in get_aggregated_result
result = _aggregate_results(
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/agent_based/checking/__init__.py", line 578, in _aggregate_results
perfdata, results = _consume_and_dispatch_result_types(subresults)
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/agent_based/checking/__init__.py", line 622, in _consume_and_dispatch_result_types
for subr in subresults:
File "/omd/sites/cloudflaretest/lib/python3/cmk/base/api/agent_based/register/check_plugins.py", line 94, in filtered_generator
for element in generator(*args, **kwargs):
File "/omd/sites/cloudflaretest/local/lib/python3/cmk/base/plugins/agent_based/cloudflare_status_api.py", line 73, in check_cloudflare_status_api
yield Result(state=state.OK,summary=f"fully operational.",details="detailed\nmultiline\noutput.")
NameError: name 'state' is not defined
Updating the offending state.OK to State.OK cleared up this issue and displayed both a summary and details. From that point I could begin to manipulate the checks and behavior and updated my testing to a more production style setup.
Full check code as of this morning (posted here as github will change over time).
#!/usr/bin/env python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
"""
Example output from special agent:
<<<cloudflare_status_api>>>
id name status group_id group components(if any)
38w9dn5m8n4p Tianjin,_China_-_(TSN) operational 77867vxkttgw False None
Example of string_table input for parsing
[['38w9dn5m8n4p','Tianjin,_China_-_(TSN)','operational','77867vxkttgw','False','None'],]
Example of data after parsing
[{'components': 'None',
'group': 'False',
'group_id': '77867vxkttgw',
'id': '38w9dn5m8n4p',
'name': 'Tianjin, China - (TSN)',
'status': 'operational'}]
"""
from .agent_based_api.v1 import IgnoreResultsError, register, Result, Service, State
from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult
import pprint
def parse_cloudflare_status_api(string_table):
parsed = []
for site in string_table:
site_dict = {}
site_dict["id"] = site[0]
site_dict["name"] = modify_strings(site[1])
site_dict["status"] = site[2]
site_dict["group_id"] = site[3]
site_dict["group"] = site[4]
site_dict["components"] = site[5]
parsed.append(site_dict)
return parsed
register.agent_section(
name = "cloudflare_status_api",
parse_function = parse_cloudflare_status_api,
)
def modify_strings(data):
if isinstance(data, str):
mod_data = data.replace("!", "’")
return mod_data.replace("_"," ")
else:
return data
def discover_cloudflare_status_api(section) -> DiscoveryResult:
if section is None:
return
else:
for site in section:
# filter for the group or site and yield name as the item.
if site["group_id"] == "1km35smx8p41" or site["group"] == "True":
yield Service(item=site["name"])
def check_cloudflare_status_api(item, section) -> CheckResult:
# Notify if empty section input
if section is None:
raise IgnoreResultsError("No API status data returned.")
# Filter on the item passed and exit_code
else:
for site in section:
if site["name"] == item:
output = f'{site["name"]}'
detail = None
if site["components"] != "None":
detail = f"{output} subcomponent-status:\n"
# iterate through the subcomponents of the site and
# add them as details if they exist.
for subcomponent in site["components"].split(","):
res = list(filter(lambda section: section["id"] == subcomponent, section))
#if res[0]["status"] != "operational":
#detail += f'{res[0]["name"]}-{res[0]["status"]}\\n'
detail += f'{res[0]["name"]}-{res[0]["status"]}\\n'
# Results if operational
if site["status"] == "operational":
yield Result(
state = State.OK,
summary = f"{output} is fully operational.",
details = detail,
)
# results if partial outage
elif site["status"] == "partial_outage":
yield Result(
state = State.WARN,
summary = f"{output} is in a partial outage.",
details = detail,
)
# results if degraded
elif site["status"] == "degraded_performance":
yield Result(
state = State.WARN,
summary = f"{output} is experiencing degraded performance.",
details = detail,
)
# anything currently not observed in status
# outage or other status.
else:
yield Result(
state = State.CRIT,
summary = f"{output} is in an unidentified or critical state.",
details = detail,
)
register.check_plugin(
name = "cloudflare_status_api",
service_name = "Cloudflare Service %s",
discovery_function = discover_cloudflare_status_api,
check_function = check_cloudflare_status_api,
)
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed. Contact an admin if you think this should be re-opened.