Passing Arguments to iCall Scripts

iCall is a control-plane automation tool for the BIG-IP platform. There are several articles on overview and implementation details, but lost among them is being clear about how to pass arguments to iCall scripts. A post in the technical forum on disabling multiple other interfaces if one should fail highlighted the fact that the configuration can get pretty bloated if one does not pass data to the script. Here are two ways to do it:

Setting variables in user_alert.conf events

The alert definition supports variable names and values, here are a few examples:

 

alert local-http-10-2-80-1-80-DOWN "Pool /Common/my_pool member /Common/10.2.80.1:80 monitor status down" {
   exec command="tmsh generate sys icall event tcpdump context { { name ip value 10.2.80.1 } { name port value 80 } { name vlan value internal } { name count value 20 } }"
}
alert interface_1_1_down "Link: 1.1 is DOWN" {
    exec command="tmsh generate sys icall event interface_manager context { { name action value disabled } { name interface value 1.1 } }"
}

 

The key/value pair arguments are set in the context of the exec command like so:

 

{ { name k1 value v1 } { name k2 value v2 } { name k3 value v3 } }

 

Setting variables in iCall handlers

A second method of setting variables is to do so in the handler definition. Note, however, this only is supported on the periodic handler.

 

sys icall handler periodic myPeriodicTestHandlerWithArguments {
    arguments {
        {
            name k1
            value v1
        }
        {
            name k2
            value v2
        }
        {
            name k3
            value v3
        }
    }
    interval 30
    script myTestScriptWithArguments
}

 

Reading the variables in the iCall script

This is where the magic happens! In the iCall script, there's a little snippet to gain access to all that goodness you set in the alerts and/or handlers:

 

sys icall script myTestScriptWithArguments {
    app-service none
    definition {
        foreach var { k1 k2 k3 } {
            set $var $EVENT::context($var)
        } 
        tmsh::log "k1: ${k1}"
        tmsh::log "k2: ${k2}"
        tmsh::log "k3: ${k3}"
    }
    description none
    events none
}

 

The for loop iterates through the names you established in the alert/handler (specified also in the script) and then sets each variable to the context you provided. In this dummy example, I'm just logging it. But let's look at a real example to close the loop.

Use Case: Disable other interfaces when one fails

In the forum thread, the ask was to validate the alerts, handlers, and scripts they had assembled to accomplish disabling multiple interfaces when one fails. Totally possible without passing arguments, but think about how many objects you need to accomplish this. It's a lot! The only number of objects that doesn't change is how many alert definitions you need in user_alert.conf. But...the size of that definition shrinks considerably. Let's start with the user_alert.conf file, and I'm limiting to two interfaces (one failure triggering the other to be disabled) for brevity.

 

alert interface_1_1_down "Link: 1.1 is DOWN" {
    exec command="tmsh generate sys icall event interface_manager context { { name action value disabled } { name interface value 1.1 } }"
}
alert interface_1_3_down "Link: 1.3 is DOWN" {
    exec command="tmsh generate sys icall event interface_manager context { { name action value disabled } { name interface value 1.3 } }"
}
alert interface_1_1_up "Link: 1.1 is UP" {
    exec command="tmsh generate sys icall event interface_manager context { { name action value enabled } { name interface value 1.1 } }"
}
alert interface_1_3_up "Link: 1.3 is UP" {
    exec command="tmsh generate sys icall event interface_manager context { { name action value enabled } { name interface value 1.3 } }"
}

 

Pretty simple here. Notice there is only one exec command, and I only need to pass the action desired (enabled, disabled) and the failing interface. Now let's look at the handler. This is the easier piece of the puzzle. We only need one triggered handler to call the script.

 

sys icall handler triggered interface_manager {
    script interface_manager
    subscriptions {
        interface_manager {
            event-name interface_manager
        }
    }
}

 

So here in the handler, the event-name matches the event specified in the alert, and for consistency, I've named the script that as well. And now the script.

 

sys icall script interface_manager {
    app-service none
    definition {
        foreach var { action interface } {
            set $var $EVENT::context($var)
        }
        switch ${interface} {
            "1.1" {
                tmsh::modify /net interface 1.3 ${action}
            }
            "1.3" {
                tmsh::modify /net interface 1.1 ${action}
            }
        }
    }
    description none
    events none
}

 

You can see at the top of the script definition is our little snippet to extract and set the variables for use, and then I'm using a switch statement to then modify the interfaces I want disabled or enabled based on the source interface failure. By passing the action along with the interface, I don't have to have two handlers and two scripts, one for each interface state. You could further optimize by passing with each source interface failure a list of the interfaces that should be disabled, then execute a tmsh::modify in a foreach loop, but the script is easier to modify programmatically than the user_alert.conf file.

Testing the solution

My first attempt to test the script failed because I disabled the interface administratively rather than having it fail. I had to look up how to "fail" an interface in my BIG-IP VE running on VMWare Fusion. Turns out I just have to deactivate the appropriate NIC in the VM settings. Here's a quick one minute video showing the results of the alert, handler, and script above.

 

Published Oct 04, 2022
Version 1.0
No CommentsBe the first to comment