SNMP - Plugin - Basics

Hello,

I’m just learning the basics of developing a plugin and have some questions after searching around.

  1. Is there better documentation ? Writing your own check plug-ins I’ve down followed the steps and have no idea how to get this to work as a service?

Would something like this be applicable?
-https://forum.checkmk.com/t/check-mk-english-snmp-checks/135942.

  1. Is snmp plugins supposed to go “~/local/lib/check_mk/base/plugins/agent_based/” seems odd to put them in the “agent_based” as it’s not, but documentation only provides this path and seems to work.

  2. Given the below plugin and output, can I get the output as
    {[OID1] = [VALUE2],
    [OID2] = [VALUE2]}

Is there something missing to get the OID information, so I can relate it to the values?
[VALUE1],
[VALUE2]

  1. Is there examples I can follow? (perhaps on the git?) Please provide all the related files and paths, as to avoid just pointing to a group of “services” is not really going to help show what is related and applicable here.

  2. Before you ask, I’m using public OIDs as it’s applicable to anyone trying to test this… The MIBs i’m going to use are custom, so sharing a more general example to get me started.

My progress so far:
~/local/lib/check_mk/base/plugins/agent_based/ANI.py

from cmk.base.plugins.agent_based.agent_based_api.v1 import *


def parse_sessions(string_table):
  parsed = {}
  print ("--", string_table, "--")
  return parsed



register.snmp_section(
  name = 'ANI',
  detect = exists('.1.3.6.1.4.1.123.0'),
  fetch = SNMPTree(
    base = '.1.3.6.1',
    oids = [
      '4.1.2021.9.1', #DISK Monitoring
      ],
     ),
  parse_function=parse_sessions,
  )

Example Output:

    Starting job...
    -- [['1'], ['2'], ['3'], ['/pl2'], ['/data'], ['/statistics/partitions/internal'], ['/dev/vda1'], ['/dev/vda2'], ['/dev/vda2'], ['-1'], ['-1'], ['-1'], ['20'], ['10'], ['10'], ['5070464'], ['20575868'], ['20575868'], ['3488988'], ['20029132'], ['20029132'], ['1581476'], ['546736'], ['546736'], ['31'], ['3'], ['3'], ['0'], ['2'], ['2'], ['5070464'], ['20575868'], ['20575868'], ['0'], ['0'], ['0'], ['3488988'], ['20029132'], ['20029132'], ['0'], ['0'], ['0'], ['1581476'], ['546736'], ['546736'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'], [''], [''], ['']] --
    Complete ...

I’m not sure how to “Group” the related /dev/vda1 volume with min / max etc … I would have thought they would be enumerated or OID Name / Value pairs?

Thank you in advance.
Mr.D

Hi Mr.D

If you enter the OID of the Table you are interested as base and a list of entries/columns you are interested in as oids you will get a list of rows back.

register.snmp_section(
  name = 'ANI',
  detect = exists('.1.3.6.1.4.1.123.0'),
  fetch = SNMPTree(
    base = '.1.3.6.1.4.1.2021.9.1',
    oids = [
      '2', # dskPath
      '3', # dskDevice
      '6', # dskTotal
      '7', # dskAvail
 
     ),
  parse_function=parse_sessions,
  )

This should result in somthing like te following as string_table

[['/pl2', '/dev/vda1', '5070464', '3488988'], ['/data', '/dev/vda2', '20575868', '20029132'], ['/statistics/partitions/internal', '/dev/vda2', '20575868', '20029132']], 

This would enable you to loop through like in the following example

for path, device, total, avail in string_table:
  pass

Regards Marius

2 Likes

Hello @Jiuka,

Sounds good. Makes sense.
I’ll give it a shot and get back as needed.

Derek

Hello @Jiuka,

Ok, So I’ve cleaned up the SNMP code to only select the relevant OIDs, output is more relatable :slight_smile:

New: Output:
Parsed: {‘VOLUME_0’: {‘Name’: ‘/dev/vda1’, ‘Size’: ‘5070464’, ‘Used’: ‘1580180’}, ‘VOLUME_1’: {‘Name’: ‘/dev/vda2’, ‘Size’: ‘20575868’, ‘Used’: ‘552232’}, ‘VOLUME_2’: {‘Name’: ‘/dev/vda2’, ‘Size’: ‘20575868’, ‘Used’: ‘552232’}}

Related Code:

def parse_sessions(string_table):

parsed = {}
summary = {}

#print ("Array: ", string_table)

for INDEX, (volume_name, volume_size, volume_used, volume_percent, volume_inode) in enumerate(string_table):
try:
print (volume_name, volume_size, volume_used, volume_percent, volume_inode )

  INDEX_VOLUME = 'VOLUME_' + str(INDEX)

  if (INDEX_VOLUME not in parsed):
    summary[INDEX_VOLUME] = {}

  summary[INDEX_VOLUME]['Name']   = volume_name
  summary[INDEX_VOLUME]['Size']   = volume_size
  summary[INDEX_VOLUME]['Used']   = volume_used
  summary[INDEX_VOLUME]['Percent] = volume_percent
  summary[INDEX_VOLUME]['iNodes'] = volume_inode

except ValueError:
  continue

print ("Parsed: ", parsed)
return parsed

Next Question:

How to get get from this data into a related “Monitored Service(s)” for example:
I’m not finding documentation or examples for this next step, any advise or direction?

‘VOLUME_0’:
— {‘Name’: ‘/dev/vda1’, ‘Size’: ‘5070464’, ‘Used’: ‘1580180’},

Into:

Hi @MrD

This is where the discovery and check functions come into play. The first one discovery the available service. The section parameter will get the parsed output and should yield one Service per discovered volume.

def discover_ani(section):
    for name section.keys():
        yield Service(item=name)

The check functions gets the same input as section but in addition the item you raised. In this example this could be something like VOLUME_0. It then should yield at least one Result.

def check_ani(item, section):
    volume = section[item]
    yield Result(state=State.OK, summary="Name %s" % volume["Name"])

    if volume["Used"] >= volume["Size"]:
        yield Result(state=State.CRIT summary="Volume is full")

However you usually the check logic is much more complex and may includes checking levels which may even are configurable with rules. However this is quiet a bit more advanced. An example which could help is the proxmox_ve_disk_usage. But keep in mind that this check expects only a single service per host. Therefore it has no item parameter in the check section but demonstrated how disk metrics and the result could be handled. The custom configuration rule for this check is found in proxmox_ve_disk_usage_params.py, the CheckParameterRulespecWithoutItem you would need to change to CheckParameterRulespecWithItem for sure as you have more the one instance of the service.

Regards Marius

Hello @Jiuka,

To help clarify my understanding: We would need these three minimum functions in order to define the service?

  • register.snmp_section(…)
    • Collect the SNMP information
  • register.check_plugin(…)
    • Define the output and raise (ok, warn, critical) events
  • rulespec_registry.register(…)
    • Define how the output should be displayed / configured

And they can all be stored in the same python script located in here?
/omd/sites//local/lib/python3/cmk/base/plugins/agent_based

Hi @MrD

  • register.snmp_section(…)
    • Collect the SNMP information
    • May parse the snmp for simpler usage of the data later on with a parse_function
  • register.check_plugin(…)
    • Defines a function to discover the services.
    • Define the output and raise (ok, warn, critical) events

This should be placed in local/lib/python3/cmk/base/plugins/agent_based/.py and is the necessary part.

  • rulespec_registry.register(…)
    • Define rules how to interpret the values. Mostly defining thresholds.

The should be places in local/share/check_mk/web/plugins/wato/.py and is only needed if the behaviour of the check needs to be configured with rules in wato.

In the the Phion Extension i have three checks but only two if them have the option to configure. The phion_service.py has no configuration as it’s only job is to translate the service state from snmp to checkmk. The other two checks have configurations in local/share/check_mk/web/plugins/wato/

Regards Marius

Hello @Jiuka,

I seem to missing a pre-requisite to using “Service.key()”.

Using:

def discovery_ani_storage(section: Service):
  print ("Discover:", Service)
  print ("Service Key:", Service.key())

And Getting Error:

Discover: <class 'cmk.base.api.agent_based.checking_classes.Service'>
  WARNING: Exception in discovery function of check plugin 'ANI_Storage': type object 'Service' has no attribute 'key'

CODE - check_plugin:

CODE - snmp_section:

section.keys() not Service.key() calling keys() on the Dictionary the parse function returned returns the Dictionary Keys from the sections.

def discovery_ani_storage(section):
  print ("Discover:", section)
  print ("Service Key:", section.keys())
 
 
  for name in section.keys():
    print ("Item/Name:", item, name)
    yield Service(item=name)
  print ("done")

Hi @Jiuka ,

Yes, just caught my mistake too. I had got that type of syntax from the examples but realized my mistake,

I’m still confused (and still trying) at the expected function “Service()” I was suspecting something like "Service (UniqueServiceLable, Name, Value) but not sure how to get the output into the formatting expected?

Code:

def discovery_ani_storage(section):
  for service in section.keys():
    print (service,section[service])
    # yield Service(item=service, name="size", value=section[Service]['Size'])
    yield Service(item=section[service])     ### <--

Current Output:

Starting job...
/dev/vda1  {'Size': '5070464', 'Used': '1576476'}
  WARNING: Exception in discovery function of check plugin 'ANI_Storage': 'item' must be a non empty string or ommited entirely, got {'Size': '5070464', 'Used': '1576476'}

Completed.

Hello,

It seems that this may be a bug, I just don’t get it yet … Even if I force name = ‘ABC’ I get the same result? If I set to 1 then i get a message saying item must be string or empty?

def discovery_ani_storage(section):
  for service in section.keys():
   name = 'ABC'    
   print ("OUT:", name, " -- ", service, " -- ", section, " -- ", section[service])
   yield Service(item=name,parameters=section[service])

From: * checkmk/checking_classes.py at f9e4e5fe5bfa0dfda1d5ed605ade5740d23683e6 · tribe29/checkmk · GitHub

    @staticmethod
        def _parse_item(item: Optional[str]) -> Optional[str]:
            if item is None:
                return None
            if (item and isinstance(item, str)):
                return item
            raise TypeError("'item' must be a non empty string or ommited entirely, got %r" % (item,))

or

Interestingly, I had no issues with getting a simpler example to work.

Quick update:

OMD[ultra]:~/local/share/check_mk/web/plugins/wato$ pwd; ls -l
/omd/sites/ultra/local/share/check_mk/web/plugins/wato
total 1
-rw-r----- 1 ultra ultra 1448 Apr 13 01:44 ANI_Connections.py


OMD[ultra]:~/local/lib/check_mk/base/plugins/agent_based$ pwd; ls -l
/omd/sites/ultra/local/lib/check_mk/base/plugins/agent_based
total 2
-rw-r----- 1 ultra ultra 2859 Apr 13 01:32 ANI_Connections.py
drwxrwx--- 2 ultra ultra  158 Apr 13 01:32 __pycache__/

Leave the above issue with me for some time i’ll go back and try and find what was needed for the more complicated example.

Next:
Why does the service say “This check is not configurable via WATO” when I have configuration for it?

You need to tell the check upon registration which configuration is for him.

If you define check_ruleset_name and check_default_parameter you also need to the check function named params. which will receive the defined parameters from the configuration or check_default_parameter if no configuration is present.