Question
Get all hosts which belong to a specific host group inside the Icinga 2 DSL. This should be used for clustered checks combining multiple location based results.
You can either write a custom function which loops over all host objects, fetching all of them with an if condition. Or you’ll abstract that in existing function calls like shown below.
At first, we need the groupname as string, or fetched from an existing object like this:
var myhost = macro("$host.name$")
log(myhost)
var myobj = get_host(myhost)
var mygroup = myobj.vars.aggregate_group
log(mygroup)
Then a filter should be applied, which takes a callback function, checking for hosts which belong to the mygroup
hostgroup.
var nodes = get_objects(Host).filter(node => mygroup in node.groups)
This doesn’t work as described here: Monitoring same hosts from multiple locations - #9 by thoomas
Introduction into Lambdas and Scopes
The following construct looks legit in the first place.
var mygroup = "linux-servers"
var nodes = get_objects(Host).filter(node => mygroup in node.groups)
- get_objects() returns a list of objects, each element represents a Host object
- filter() loops over each element and calls the function callback provided
- the function callback uses an abbreviated lambda function with
x => inner function body
Try that on your fresh installation where NodeName as host, and linux-servers
as hostgroup exists.
icinga2 console --connect 'https:/root:icinga@localhost:5665/'
<1> => var mygroup = "linux-servers"
<2> => var nodes = get_objects(Host).filter(node => mygroup in node.groups)
It fails, but why?
Lambda functions and scoped variables
node => mygroup in node.groups
can also be written as
f = function(node) { mygroup in node.groups }
f
as function name is ignored here, since the definition and call happens in one place inside filter() making this an anonymous function.
A call to this function will fail since mygroup
isn’t defined in the local function scope.
Example from icinga2 console --connect ...
:
<25>: var nodes = get_objects(Host).filter(node => mygroup in node.groups)
^^^^^^^
Error while evaluating expression: Tried to access undefined script variable 'mygroup'
Let’s try this again with the function we’ve defined before:
<26> => f = function(node) { mygroup in node.groups }
null
<27> => get_objects(Host).filter(f)
<26>: f = function(node) { mygroup in node.groups }
^^^^^^^
Error while evaluating expression: Tried to access undefined script variable 'mygroup'
So, how can we solve this problem?
Bind scopes into functions: Closures
It’s possible to bind specific variables from the caller’s scope into the called function with closures.
This requires the use
keyword followed after the function parameter list. Hint: Therefore it cannot be used in combination with lambda expressions.
Take the example from above, and bind the mygroup
variable into the function’s scope:
f = function(node) use(mygroup) { mygroup in node.groups }
Now call this function in the filter()
function.
get_objects(Host).filter(f)
It works!
Solution
You can combine this into a runtime function like this:
vars.dummy_text = {{
var myhost = macro("$host.name$")
log(myhost)
var myobj = get_host(myhost)
var mygroup = myobj.vars.aggregate_group
log(mygroup)
var f = function(node) use(mygroup) { mygroup in node.groups }
var nodes = get_objects(Host).filter(f)
//cluster checks
//...
}}
Continue here for a deep-dive into clustered checks: Advanced Topics - Icinga 2
References
- Functions: Language Reference - Icinga 2
- Lambda Expressions: Language Reference - Icinga 2
- Variable Scopes: Language Reference - Icinga 2
- Closures: Language Reference - Icinga 2
- Object Accessor Functions: Library Reference - Icinga 2
- Array#filter: Library Reference - Icinga 2