Complex dictionaries contain a key I didn't put there

Hi all,

I just discovered a strange behaviour I can’t explain.

We have a rather complex dictionary structure in certain host objects:

vars.ibm_perfserv = {
  servers = {
    "server1" = {
      jdbc = {
        "jdbc/myjdbc1" = {
          dbconnectionpoolwaitingthreadcount.perfserv_warn = 10
          dbconnectionpoolwaitingthreadcount.perfserv_crit = 20

We are using the following apply rule to parse the mentioned data:

apply Service for (jdbc_name => jdbc_config in 
  flatten_dictionary(host.vars.ibm_perfserv.servers, "jdbc")) {
  name = "IBM WebSphere database connection pool waiting thread count " + jdbc_config.parent + "/" + jdbc_name
  import "generic-service"
  check_command = "perfserv"
  vars += jdbc_config.dbconnectionpoolwaitingthreadcount
  vars.perfserv_cellname = host.vars.ibm_perfserv.cellname
  vars.perfserv_nodename = host.vars.ibm_perfserv.nodename
  vars.perfserv_servername = jdbc_config.parent
  vars.perfserv_jdbcname = jdbc_name
  vars.perfserv_command = "show"
  vars.perfserv_metric = "DBConnectionPoolWaitingThreadCount"
  assign where typeof(host.vars.ibm_perfserv) == Dictionary && jdbc_config.contains("dbconnectionpoolwaitingthreadcount")

This is all working as intended, but when I now inspect the host object’s vars in Icingaweb2 or via API, I get the following:

"ibm_perfserv": {
  "servers": {
    "server1": {
      "jdbc": {
        "jdbc/myjdbc1": {
          "dbconnectionpoolwaitingthreadcount": {
            "perfserv_crit": 20.0,
            "perfserv_warn": 10.0
          "parent": "server1"

As you can see, there is now a key “parent” below “jdbc/myjdbc1”.
I suspect this is due to the use of jdbc_config.parent, but from my unterstanding parent should rather resolve to the parent element than creating a key by that name.
To me it seems that parent does both, since jdbc_config.parent also is being replaced properly, as my service is created with the name IBM WebSphere database connection pool waiting thread count server1/jdbc/myjdbc1

Can someone explain this behaviour?


It’s likely the dot in the variable name.

Icinga reads perfserv_warn as another key of the dictionary, so you actually have
dbconnectionpoolwaitingthreadcount["perfserv_warn"]=10 and dbconnectionpoolwaitingthreadcount["perfserv_crit"]=20
which is the same as

dbconnectionpoolwaitingthreadcount": {
  "perfserv_crit": 20.0,
  "perfserv_warn": 10.0

maybe an underscore instead of a dot would help here :wink:

Thanks for your suggestion, but I’m pretty sure that’s not it.
There is no dot in the variable name, since this is a collapsed dictionary written in the indexer format for better readability.

As you can see Icinga2 correctly expands dbconnectionpoolwaitingthreadcount.perfserv_crit and dbconnectionpoolwaitingthreadcount.perfserv_warn into this this:

In order to understand this, we also need to see the flatten_dictionary function.

1 Like

Mea culpa, I totally forgot that I created that function myself quite a while ago.
I just recently discovered that unexpected key being there and assumed that flatten_dictionary was a standard function of Icinga :roll_eyes:

For the sake of completeness, this is my function:

globals.flatten_dictionary = function(outer_dict, subkey) {
  var flattened_dict = { }
  if (typeof(outer_dict) == Dictionary) {
    for (outer_key => outer_value in outer_dict) {
      if (typeof(outer_value[subkey]) == Dictionary) {
        for (inner_key => inner_value in outer_value[subkey]) {
          flattened_dict[inner_key] = inner_value
          flattened_dict[inner_key].parent = outer_key // this line is the culprit
      } else {
        log(LogCritical, "Console", "Is no dictionary:")
        log(LogCritical, "Console", outer_value[subkey])
    return flattened_dict

I use that function to be able to loop over the inner elements of nested dictionaries.
If I remember correctly, I needed to put the parent key there in order to “remember” to which outer dictionary an inner dictionary belongs.