Understanding Director command arguments with DSL

Hello,

I am working on building up a new Icinga2 Setup in our Environment, in which I want to try to automatize as much as possible.

In the last days I created a Master → Satellite → Agent setup to try migrating hand written configuration from our old Icinga2 Setup into Director. Now I reached a point, where I am not able to implement a CheckCommand into director that works like expected.

The old Config:

object CheckCommand "infiniband_rate" {
        import "plugin-check-command"
        command = [ PluginDir + "/check_ib_link" ]

        arguments = {
            "-a" = {
                description = "List of active port(s)"
                value = "$ib_port$"
            }
            "-d" = {
                description = "HCA device to check"
                value = "$ib_device$"
            }
            "-r" = {
                description = "Expected rate in Gb/sec."
                value = "$ib_rate$"
            }
        }
}

apply Service "infiniband" {
        import "generic-service"

        if ( "sys/infiniband/mlx4_port1_40" in host.vars.groups) {
           // infinband device mlx4, port 1, 40GB rate

              vars.ib_port = "1"
              vars.ib_device = "mlx4_0"
              vars.ib_rate = "40"

        } else if ( "sys/infiniband/mlx4_port1_56" in host.vars.groups) {
           // infinband device mlx4, port 1, 56GB rate

              vars.ib_port = "1"
              vars.ib_device = "mlx4_0"
              vars.ib_rate = "56"

        } else if ( "sys/infiniband/mlx5_port1_40" in host.vars.groups) {
           // infinband device mlx5, port 1, 40GB rate

              vars.ib_port = "1"
              vars.ib_device = "mlx5_0"
              vars.ib_rate = "40"

        } else if ( "sys/infiniband/mlx5_port1_56" in host.vars.groups) {
           // infinband device mlx5, port 1, 56GB rate

              vars.ib_port = "1"
              vars.ib_device = "mlx5_0"
              vars.ib_rate = "56"

        } else if ( "sys/infiniband/mlx5_port1_100" in host.vars.groups) {
           // infinband device mlx5, port 1, 100GB rate

              vars.ib_port = "1"
              vars.ib_device = "mlx5_0"
              vars.ib_rate = "100"

        } else if ( "sys/infiniband/mlx5_port2_100" in host.vars.groups) {
           // infinband device mlx5, port 2, 100GB rate

              vars.ib_port = "2"
              vars.ib_device = "mlx5_2"
              vars.ib_rate = "100"

        }


        display_name = "Infiniband interface " + host.vars.ib_port
        check_command = "infiniband_rate"


        if (host.name != NodeName) {
                command_endpoint = host.name
        }


        assign where "sys/infiniband" in host.vars.groups
}

The groups like sys/infiniband have been imported from our CMDB as dicts and looks now like:

 object Host "s002.domain.net" {
    import "generic-agent"

    display_name = "s002"
    address = "xxx.xxx.240.12"
    zone = "xxx"
    vars.centos = {
        centos7 = {
            cluster = true
        }
    }
    vars.redhatel = true
    vars.redhatel7 = true
    vars.server = true
    vars.sys = {
        icinga2 = {
            client = true
            zone = {
                xxx = true
            }
        }
        infiniband = {
            mlx5_port1_100 = true
        }
        openssh = true
        platform_mpi = true
        prometheus = true
        racadm = true
        resolver = true
    }
    vars.vendor = {
        dell = {
            poweredge = {
                c6525 = true
            }
        }
    }
}

I read somewhere on the internet, that conditions on custom host variables should be done inside the CheckCommando not on the ApplyService.

So, I try to do this, but I am failing to found the right syntax for the condition. Inside the Director Command preview it shows this:

object CheckCommand "infiniband_rate" {
    import "plugin-check-command"
    command = [ PluginDir + "/check_ib_link" ]
    arguments += {
        "-a" = {
            description = "List of active port(s)"
            required = true
            value = {{
                if (host.vars.sys.infiniband.mlx5_port2_100) {
              return "2"
            } else {
              return "1"
            }
            }}
        }
        "-d" = {
            description = "HCA device to check"
            required = true
            value = {{
                if (host.vars.sys.infiniband.mlx4_port1_40) {
              return "mlx4_0"
            } else if (host.vars.sys.infiniband.mlx4_port1_56) {
              return "mlx4_0"
            } else if (host.vars.sys.infiniband.mlx5_port1_40) {
              return "mlx5_0"
            } else if (host.vars.sys.infiniband.mlx5_port1_56) {
              return "mlx5_0"
            } else if (host.vars.sys.infiniband.mlx5_port1_100) {
              return "mlx5_0"
            } else if (host.vars.sys.infiniband.mlx5_port2_100) {       
              return "mlx5_2"
            }
            }}
        }
        "-r" = {
            description = "Expected rate in Gb/sec."
            required = true
            value = {{
                if (host.vars.sys.infiniband.mlx4_port1_40) {
              return "40"
            } else if (host.vars.sys.infiniband.mlx4_port1_56) {
              return "56"
            } else if (host.vars.sys.infiniband.mlx5_port1_40) {
              return "40"
            } else if (host.vars.sys.infiniband.mlx5_port1_56) {
              return "56"
            } else if (host.vars.sys.infiniband.mlx5_port1_100) {
              return "100"
            } else if (host.vars.sys.infiniband.mlx5_port2_100) {
              return "100"
            } else {
              return false
            }
            }}
        }
    }
}

They me be other elegant ways to do this inside Director, but I really want to understand how to call the custom vars here. I am also grateful for suggestion for other solutions.

For me it looks like that inside the command condition host.vars.sys.infiniband.mlx* is not available, but I saw similar examples on the net. So, I am wondering what I am do wrong here.


  • Director version (System - About):1.9.0
  • Icinga Web 2 version and modules (System - About): 2.9.5
  • Icinga 2 version (icinga2 --version): 2.13.2-1
  • Operating System and version: RockyLinux 8
  • Webserver, PHP versions:
    • Apache 2.4.37-41
    • PHP 7.4.19

Never done something like that before tbh.
But have you tried putting what you have into the Condition (set_if) field and change the condition format to “Icinga DSL” and return a variable. And then use that variable in the value field.

I think you need to use the macro function: Library Reference - Icinga 2

I have no example for exactly this at the moment, but an example that shows the general capabilities: Director - use function for timebased threshold - #4 by dgoetz

Thank you @log1c and @dgoetz for the replays.

I did some more testing and are more confused that before :confounded:

First I try to use set_if for setting an var and use it in the value field:

object CheckCommand "infiniband_rate" {
    import "plugin-check-command"
    command = [ PluginDir + "/check_ib_link" ]
    arguments += {
        "-a" = {
            description = "List of active port(s)"
            required = true
            value = "1"
        }
        "-d" = {
            description = "HCA device to check"
            required = true
            value = "1"
        }
        "-r" = {
            description = "Expected rate in Gb/sec."
            required = true
            set_if = {{
                var speed = "40"
            return speed
            }}
            value = "$speed$"
        }
    }
}

OUTPUT: Error: Non-optional macro ‘speed’ used in argument ‘-r’ is missing.
So even in such a simple example it not worked. I am sure I do miss here something… :unamused:


Next I try to test the conditions i wanted to use. So I started the icinga2 console. Here the code runs fine with the output I expected:

# icinga2 console
Icinga 2 (version: 2.13.2-1)
Type $help to view available commands.
<1> => host.vars.sys.infiniband.mlx5_port1_100 = true
null
<2> =>
null
<3> => var speed = host.vars.sys.infiniband.keys().join("").split("_")[2]
null
<4> => speed
"100"
<5> => host.vars
{
        sys = {
                infiniband = {
                        mlx5_port1_100 = true
                }
        }
}

When I try to port this into director, it fails.

Here is the Preview of the command:

object CheckCommand "infiniband_rate" {
    import "plugin-check-command"
    command = [ PluginDir + "/check_ib_link" ]
    arguments += {
        "-a" = {
            description = "List of active port(s)"
            required = true
            value = "1"
        }
        "-d" = {
            description = "HCA device to check"
            required = true
            value = "1"
        }
        "-r" = {
            description = "Expected rate in Gb/sec."
            required = true
            set_if = {{
                var speed = host.vars.sys.infiniband.keys().join("").split("_")[2]
            return speed
            }}
            value = "$speed$"
        }
    }
}

From Icinga2 Docs I found an example of the set_if function where there are access the host.vars and use dictionary functions. In my case it does not work.

After alot of trail and error, I finally managed to get it work.

object CheckCommand "infiniband_rate" {
    import "plugin-check-command"
    command = [ PluginDir + "/check_ib_link" ]
    arguments += {
        "-a" = {
            description = "List of active port(s)"
            required = true
            value = {{
                var infiniband= macro("$host.vars.sys.infiniband$")
            return infiniband.keys().join("").split("_")[1].replace("port", "")
            
            }}
        }
        "-d" = {
            description = "HCA device to check"
            required = true
            value = {{
                var host_vars = macro("$host.vars$")
            var mlx_array = host_vars.sys.infiniband.keys().join("").split("_")
            if (mlx_array[0] == "mlx4") {
             return "mlx4_0"
            } else if (mlx_array[0] == "mlx5") {
              if (mlx_array[1] == "port1" ) {
                return "mlx5_0"
              } else {
                return "mlx5_2"
              }
            } 
            
            }}
        }
        "-r" = {
            description = "Expected rate in Gb/sec."
            required = true
            value = {{
                var host_vars = macro("$host.vars$")
            return host_vars.sys.infiniband.keys().join("").split("_")[2]
            }}
        }
    }
}

What took me the most time, was that parts of the documentation shows different examples but non of them did work from me.
Examle: https://icinga.com/docs/icinga-2/latest/doc/08-advanced-topics/#use-functions-in-command-arguments-set_if
I did a copy Paste, but I was not able to load the host_vars was always empty!

var host_vars = host.vars
log(host_vars)

When I do it with the macro function it works:

var host_vars = macro("$host.vars$")

So, can somebody explain me why I can not call the host.vars like in the example or is just the documentation here wrong?

I would classify this as bug in the documentation if it does not work like stated there. So can you do a pull request or at least open an issue? This would also allow a developer to look into and decide if it is expected to work like documented and it is an actual bug. There is also a chance that a bit of explanation is added why the one syntax works and there does not.

Someone tried to explain this to me in the past, but I am not sure if I understood and remember it correctly and so I do not try to explain it to prevent creating wrong knowledge! But this is why I pointed to the macro function initially.

Thank you @dgoetz
I opened an github issue. Lets see what the Devs will answer!

1 Like

Hi,

I suggest posting the link to the issue here as well, just for the sake of completeness :smiley:

I could imagine that the stuff from the docs not working could have something to to with the Director.
But that is just some wild guessing.

The Director is just generating DSL and if it looks like in the docs and not working, it is Icinga and not the Director to blame.

And for completeness: https://github.com/Icinga/icinga2/issues/9299

1 Like