Help writing a snmp plugin with new API

Check_MK Version: 2.2.0p31
OS version: CentOS Linux 7

Could anybody recommend me another documentation for writing own snmp plugins? Or any example for following with a check witch retrieve branches that contain multiple leaves. I’m following the guide on: Writing SNMP-based check plug-ins

why they never cover this case?? :face_with_symbols_over_mouth: :rage: :face_with_symbols_over_mouth: :rage:

But in the example is only for one service, I’m trying to do it for more than just a service and I’m becoming nut, because when trying to discover services, it says missing %s on service name and the guide doesn’t cover that part.

Trying to retrieve branches that contain multiple leaves, and services are not discovered :face_with_symbols_over_mouth: :rage: :face_with_symbols_over_mouth:
Why do they change it?? Why documentation isn’t good enough?? I’m really frustrated I couldn’t make it work right now.

Thanks in advance for any help or sugestion.

here is a sample for the CMK 2.3 Check API

check_plugin_cisco_meraki_org_appliance_vpns = CheckPlugin(
    name='cisco_meraki_org_appliance_vpns',
    service_name='VPN peer %s',
    discovery_function=discover_appliance_vpns,
    check_function=check_appliance_vpns,
    check_default_parameters={},
    check_ruleset_name='cisco_meraki_org_appliance_vpns',
)

for the 2.0 to 2.2 check API it looks like this

register.check_plugin(
    name="cisco_meraki_org_licenses_overview",
    service_name="Cisco Meraki Licenses %s",
    discovery_function=discover_licenses_overview,
    check_function=check_licenses_overview,
    check_ruleset_name="cisco_meraki_org_licenses_overview",
    check_default_parameters={},
)

I guess what you are missing is the %s in the service_name

These samples are form the Cisco Meraki special agent.

They cover this here 4.2. Service discovery. In the SNMP part they concentrate on how to get the SNMP data…

I added the %s at the end of the service_name, but it complains is missing, I think is not getting it on discovery function. Because of that I want an example for try to do it.

The string table I get is like this:
[[‘HUB_WAN1_0’, ‘190.24.132.38’, ‘1’], [‘HUB_LAN_0’, ‘172.20.90.66’, ‘2’], [‘HUB_WAN2_0’, ‘201.234.246.167’, ‘1’]]

Then I convert it to a dictionary with a function like this:

def parse_tunel_vpn(string_table):
    print(string_table)
    parsed = {}
    column_names = [
        "nombreTunel",
        "ipTunel",
        "estadoTunel",
    ]
    for line in string_table:
        parsed[line[0]] = {}
        for n in range(1, len(column_names)):
            parsed[line[0]][column_names[n]] = line[n]

    print("Parseada la info")
    print(parsed)
    return parsed

And discovery fails, it complain name is missing

Discovery method:

def discover_tunel_vpn(section):
    for tunel in section:
        yield Service(item=tunel)

I made so many changes in my code that sure I broke something, but I couldn’t get it work. I got the metrics from snmp without problem, but I cannot build services.

Full check code:

#!/usr/bin/env python3


# Ayuda: https://github.com/Checkmk/checkmk-docs/blob/master/examples/devel_check_plugins/check_plugin_advanced_myhostgroups.py
# + Ayuda: https://docs.checkmk.com/latest/es/devel_check_plugins_snmp.html#simulation
# Test: cmk -v --detect-plugins=tunel_vpn_setup_check 172.28.76.128

from .agent_based_api.v1 import register, Result, Service, startswith, SNMPTree, State

def parse_tunel_vpn(string_table):
    print(string_table)
    parsed = {}
    column_names = [
        "nombreTunel",
        "ipTunel",
        "estadoTunel",
    ]
    for line in string_table:
        parsed[line[0]] = {}
        for n in range(1, len(column_names)):
            parsed[line[0]][column_names[n]] = line[n]

    print("Parseada la info")
    print(parsed)
    return parsed

def discover_tunel_vpn(section):
    for tunel in section:
        yield Service(item=tunel)

def check_tunel_vpn(section):
    yield Result(state=State.OK, summary="Everything is fine")

register.snmp_section(
    name = "tunel_vpn_base_config",
    parse_function = parse_tunel_vpn,
    detect = exists(".1.3.6.1.4.1.12356.101.12.2.2.1"),
    fetch = SNMPTree(
        base = '.1.3.6.1.4.1.12356.101.12.2.2.1',
        oids = [
            '2',  #Nombre Tunel
            '4',  #IP
            '20', #Estado
        ]
    ),
)

register.check_plugin(
    name = "tunel_vpn_setup_check",
    sections = ["tunel_vpn_base_config"],
    service_name = "tunel_vpn_%s",
    discovery_function = discover_tunel_vpn,
    check_function = check_tunel_vpn,
)

I made some small modifications in your code.

#!/usr/bin/env python3
from .agent_based_api.v1 import register, Result, Service, exists, SNMPTree, State


def parse_tunel_vpn(string_table):
    parsed = {}
    for line in string_table:
        if len(line) != 3:
            continue
        parsed.setdefault(line[0], {"ipTunel": line[1], "estadoTunel": line[2]})
    return parsed


def discover_tunel_vpn(section):
    for item in section.keys():
        yield Service(item=item)


def check_tunel_vpn(item, section):
    data = section.get(item)
    if not data:
        return
    yield Result(state=State.OK, summary="Everything is fine")


register.snmp_section(
    name="tunel_vpn_base_config",
    parse_function=parse_tunel_vpn,
    detect=exists(".1.3.6.1.4.1.12356.101.12.2.2.1"),
    fetch=SNMPTree(
        base=".1.3.6.1.4.1.12356.101.12.2.2.1",
        oids=[
            "2",  # Nombre Tunel
            "4",  # IP
            "20",  # Estado
        ],
    ),
)

register.check_plugin(
    name="tunel_vpn_setup_check",
    sections=["tunel_vpn_base_config"],
    service_name="tunel_vpn_%s",
    discovery_function=discover_tunel_vpn,
    check_function=check_tunel_vpn,
)

One problem was that your check function had no item but you discover items.
The parse function is a little bit simplified.

2 Likes

Thank you very much @andreas-doehler . Really appreciate your help and time. More after writing to check_mk support (paid support) and they sent me to this free forum looking for help. What a good business they made selling the support to our company. I own you a couple of beers!! I’m going to try the code you provide and let you know. Thanks again.

Hi Martin,

we support the stuff we build, but exclude third party things and self-written stuff. This is also very clearly stated in our support description and terms.

If you or any other Checkmk user in your company ever have a problem with any of the built-in features of Checkmk, our support will help you solve the problem competently and quickly. As they have done for you many times in the past…

It’s one of the beauties of Checkmk that you can take it into a million different directions. But you do understand that it would be impossible for us to support each of these million different directions.

Best
Elias

I understand your point @elias.voelker . You can’t take responsibility or support for third party develops. Fair enough.

But what I can’t understand why don’t you provide help for fixing any trouble developing new checks or fixing old plugins after an API update, or even improving the documentation if it’s not clear enough for a silly customer like me witch can’t afford the task to make a new check. This is something that improves your product, getting more services monitored available for all the community.

Sorry for not been agree with you in terms of how a support service should be provided. This is my point of view like a customer witch pay for something that is not what I expected when I paid for that. This is not a support service witch I wouldn’t recommend for other customers.

Thank you so much @andreas-doehler , with your help I could finish the plugin I was working on.

I’m going to clean de code, make some comments to make it more self explained and share it with the community and over here.

Again Thanks!! So grateful Own you a couple of beers (or coffes) :slight_smile: :slight_smile: !!

1 Like

Final code working. Sure it can be improved, but it works.

#!/usr/bin/env python3
import datetime
from .agent_based_api.v1 import register, Result, Service, exists, SNMPTree, State, Metric


def parse_fortigate_ipsec(string_table):
    # Information comes in this format: 
    # [['HUB_WAN1_10', '190.24.13.38', '1'], ['HUB_LAN_10', '172.20.9.66', '2'], ['HUB_WAN2_10', '201.234.24.167', '1']]

    parsed = {}
    for line in string_table:
        if len(line) != 3:
            continue
        parsed.setdefault(line[0], {"ipTunel": line[1], "estadoTunel": line[2]})
    # With the last loop we convert it to a dictionary in this format
    # {'HUB_WAN1_10': {'ipTunel': '190.24.13.38', 'estadoTunel': '1'}, 'HUB_LAN_10': {'ipTunel': '172.20.9.66', 'estadoTunel': '2'}, 'HUB_WAN2_10': {'ipTunel': '201.234.24.167', 'estadoTunel': '1'}}
    return parsed


def discover_ipsec(section):
    # In this method we parse the dict returned by parse_fortigate_ipsec function and we save all dict keys like Services names. 
    for item in section.keys():
        yield Service(item=item)


def check_tunel_ipsec(item, section):
    fecha_hora_actual = datetime.datetime.now()
    fecha_hora_formateada = fecha_hora_actual.strftime("%Y-%m-%d %H:%M:%S")
    
    data = section.get(item)
    if not data:
        return

    estado_tunel = int(data.get('estadoTunel', -1))  # Usamos -1 como valor por defecto si no existe

    if estado_tunel == 2:
        yield Metric("estadoTunel", estado_tunel)
        yield Result(state=State.OK, summary=f"OK - IP: {data['ipTunel']} | Estado: {estado_tunel} | Chequeado: {fecha_hora_formateada}")
    elif estado_tunel == 1:
        yield Metric("estadoTunel", estado_tunel)
        yield Result(state=State.CRIT, summary=f"CRITICAL - IP: {data['ipTunel']} | Estado: {estado_tunel} | Chequeado: {fecha_hora_formateada}")
    else:
        yield Metric("estadoTunel", estado_tunel)
        yield Result(state=State.UNKNOWN, summary=f"UNKNOWN - IP: {data['ipTunel']} | Estado: {estado_tunel} | Chequeado: {fecha_hora_formateada}")
    


register.snmp_section(
    name="fortigate_ipsec_data",
    parse_function=parse_fortigate_ipsec,
    detect=exists(".1.3.6.1.4.1.12356.101.12.2.2.1"),
    fetch=SNMPTree(
        base=".1.3.6.1.4.1.12356.101.12.2.2.1",
        oids=[
            "2",  # Nombre Tunel
            "4",  # IP
            "20",  # Estado
        ],
    ),
)

register.check_plugin(
    name="fortigate_ipsec",
    sections=["fortigate_ipsec_data"],
    service_name="fortigate_ipsec_%s",
    discovery_function=discover_ipsec,
    check_function=check_tunel_ipsec,
)

Hope this could help other people developing their own plugins.

1 Like

Is there a step I would be missing for doing this services discovered automatically when I run a Service Discovery??

If I do a service discovery on a host, this services are not detected. The only way that this services would be detected is from command line runing:

cmk -Iv --detect-plugins=fortigate_ipsec hostname && cmk -R

Normal service discovery not detecting new services:

Only after running cmk -Iv --detect-plugins=fortigate_ipsec hostname && cmk -R services are shown.


I think I forgot to enable something but cant remember where. Any help?

Thanks in advance

This part of the command line forces the detection of a plugin.
If it is not detected without this, like inside the web GUI then your “detect” inside the snmp_section is not working.

Normally i would prefer here something like this.

    detect=startswith(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.14823.1.2.111"),

Here you should enter the value your device has inside the sysObjectID (.1.3.6.1.2.1.1.2.0).

1 Like

Thank you @andreas-doehler Doing what you suggested me fixed the problem. I had to do it with a regex, because I have different devices with this metric I’m getting.

I changed to this:

detect=matches(".1.3.6.1.2.1.1.2.0", "^\.1\.3\.6\.1\.4\.1\.(12356\.101\.1|9\.1)\.[0-9]+$"),

You can also use

detect = startswith(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.12356.101.1"),

This will match to all Fortigate Models.

.1.3.6.1.4.1.12356.101.9 is for IPS Infos and will never occur behind the OID .1.3.6.1.2.1.1.2.0

Your regex matches two complete different OID trees.
Here these will match.

.1.3.6.1.4.1.12356.101.1.1
.1.3.6.1.4.1.12356.101.1.51
.1.3.6.1.4.1.9.1.1
.1.3.6.1.4.1.9.1.3

Is this what you want?
If yes i would write the detect a little bit different and without regex.

detect=any_of(startswith(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.12356.101.1"),
              startswith(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.9.1")),

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.