Dictionary based parameters and factory settings


Dieser Artikel wird nicht mehr gepflegt und ist unter Umständen nicht mehr gültig!

1. When to use dictionary based parameters

While for simple checks like a CPU check a check parameter like a pair of integers - (80, 90) - might be sufficient. More complex checks need a more flexible way of defining parameters, however.

Such an example is the filesystem check df. In recent versions of Check_MK that check offers a dictionary based way to specify parameters. In that case the parameter to a check is a dictionary with key/value pairs like:

{ "levels" : (80, 90), "trend_range" : 48, "trend_mb" : (200, 400), }

... or, if you indent this a bit more nicely:

{
    "levels"      : (80, 90),
    "trend_range" : 48,
    "trend_mb"    : (200, 400),
}

For the user this has the following advantages:

  • It is easier to write and read than a tuple like (80, 90, 48, 200, 400, None, ...).
  • It allows to specify only some of the keys and use the default settings for others.
  • It allows to override only some keys when using check_parameters or WATO rules.

As a check developer it is your task to decide, whether a simple scheme like (80, 90) is sufficient or you want to use the flexibility of a dictionary. Please have in mind, however, that it is hard to change the format of the check parameters later while being compatible with older existing configurations.

2. How to write dictionary based checks

Check_MK supports you in writing dictionary based checks. The version 1.1.11i2 introduces a new concept called factory settings. Let's assume your check is named foo. When defining your default variable foo_default_levels you do not create that variable in your check, but instead add its name to a builtin dictionary called check_default_levels:

checks/foo
check_info["foo"] = {
    ...
    "default_levels_variable" : "foo_default_levels",
    ...
}

The default values of that variable are then defined in the dictionary factory_settings:

checks/foo
factory_settings["foo_default_levels"] = {
    "heat_levels"    : (600, 800),
    "trend"          : 0.5,
    "allowed_states" : [ 0, 2, 3, 6, 8 ],
}

It is also possible to leave some keys out. The default value for those is is "unset": the particular aspect is not being checked in that case.

That's all! When writing your check function, you can always rely on Check_MK making sure that params is a valid dictionary containing all of the keys that have been defined in factory_settings, with at least some setting. Some might have been set by inventory, some by the user, some might still have the factory settings. Using the parameters is as simply as doing dictionary lookups where needed:

checks/foo
def check_foo(item, params, info):
    ...
    if state not in params["allowed_states"]:
        ...

Note (1): The code of the check must never access factory_settings directly. Believe me! Check_MK will make sure they are reflected in params.

Note (2): It is not uncommon that several checks share the same default variable. This makes sense if the checks do practically the same, but just parse different types of input. In that case check_default_levels needs to be defined for each check, but the factory_settings definition needs only to be done once. You can put it into an include file commonly used by all those checks.

Note (3): Keys that are not set in factory_settings[...] might be unset in params. Your check could needs to tolerance their absence. Either check if they are present or work with params.get("foo"), which returns None in case the key is not set.

2.1. Inventory

When using dictionary based checks, the inventory function does not longer need to insert the name of the defaults variable as check parameter. Instead it simply inserts an empty dictionary ({}):

checks/foo
def inventory_foo(info):
    for line in info:
        # some other stuff...
        yield some_item, {}

Some checks determine the current state of something and code that into the check parameters at inventory time. When using factory_settings this is easy. You can simply specify a (usually partially filled) parameter dictionary as check parameter. Check_MK will make sure that:

  • this is correctly merged with the factory settings,
  • the user can partly override keys of the parameters without losing the values of the inventory.

An example code could look like this:

checks/foo
def inventory_foo(info):
    for line in info:
        # some other stuff...
        yield line[1] { "state": line[8], "heat" : line[7] }

3. Dictionary based parameters without default values

If you cannot think of any sensible default parameters for your check then you can make use of dictionary based parameters without defining factory settings. Simply let the inventory put the empty dictionary {} as check parameter.

The check then treat params as a dictionary with optional entries. The merging of the dictionary by the rule algorithm works nevertheless.

4. Converting existing checks to dictionary parameters

In some cases, when an existing check gets new features, you would like to convert its parameters from something static like a pair of integers to a dictionary. The tricky thing when doing this is to keep compatiblity with existing Check_MK setups. Here is a short checklist for how to do this. As an example we assume that the check currently uses a pair of two integers, for example (50, 100).

Design the new parameter structure and move the current parameter into one dictionary key. For example the parameter could then be { "levels": (50, 100) }

Make the check function accept both the old and the new style parameter. Do this by using the body of the check function the new style parameter. And at the beginning convert the paramter into a dictionary, if it is not yet one:

mycheck
def check_mycheck(item, params, info):
    if type(params) != dict:
        params = { "levels" : params }

    # ...
    if "levels" in params:
        warn, crit = params["levels"]
        # ...

WATO rule: In order to make WATO read legacy and new-style parameters it needs to transform legacy parameters into dictionary form on the fly. This is done with a Transform-Valuespec that is tranforming only in forth direction.

web/plugins/wato/check_parameters.mk
Transform(
    Dictionary(
        elements = [
            ( "levels",
               Tuple(
                   title = _("Set levels on foo bar"),
                   elements = [
                         Integer(title = _("Warning at"), default_value = 30),
                         Integer(title = _("Critical at"), default_value = 80)],
            ),
        ]
    ),
    forth = lambda old: type(old) != dict and { "levels" : old } or old,
)

After these changes the check should work without any difference to the user for existing inventorized checks. Also the user should be able to change the configuration of the check via WATO.

Fix the inventory function for new checks: return {} as check parameter.

Change the default levels variable if any and use factory_settings if the check must rely on certain elements in the dictionary to always exist. This includes defining the key default_levels_variable in the check_info of the check. Please note: the new format of this variable - if the user defines it in main.mk is dict. This is not automatically converted and theus this is an incompatible change. Users using WATO for the configuration or not affected.

Now you are able to add new optional parameters to the dictionary - beginning with the WATO rule and the implementing that in the check function.