One of the often overlooked features of iRules is the ability to use dynamic variables in assignments. This can make for some interesting iRules, but it can also get you into trouble at runtime when your variables are not defined in the current configuration. This tech tip will illustrate how to take input from an HTTP request and use that as the criteria for which pool of servers the connection is sent to, all while maintaining runtime error checking using the TCL catch command.

The pool command

This command is used to assign a connection to a specified pool name (and optionally a specific pool member). pool Specifies the pool to which you want to send the traffic. pool ?member ??? Specifies a pool member to which you want to directly send the traffic.

Typically, we see iRules written something like this (using hard-coded pool names).

when HTTP_REQUEST {
  switch -glob [HTTP::uri] {
    "*.gif" -
    "*.jpg" -
    "*.png" {
      pool images_pool
    }
    "*.asp" {
      pool ms_app_pool
    }
    "*.jsp" {
      pool java_app_pool
    }
  }
}

When this iRule is created, the pool commands are syntax checked and validated and since the pool name arguments are literals, the save logic will validate whether the given pools exist in the configuration. If they do not exist, a save error will occur and you will have to configure the pools before you save your iRule. In this case, no runtime exception will occur (unless you remove your pools out from under your iRule, that is).

But, let's say you want to build a more dynamic solution that can expand over time. For this example, let's say that you want to allow your client to determine which pool of servers it is sent to. This could be the server specifying a pool name in an HTTP cookie, or simply appending it as a GET parameter on the URI. We'll use the later scenario for this example.

For this example, we'll define the following GET parameters that can control pool direction.

pool=name

So an example URI could be:

http://somedomain.com/somefile.ext?param1=val1&pool=pool_name¶rm2=val2...

We will interrogate the URI's GET parameters in search of the "pool" parameter, extract the pool_name value and use that variable as the argument in the pool command. For newer versions of BIG-IP, one could use the URI::query command to extract values, but this implementation should work all the way back to BIG-IP v9.0.

when HTTP_REQUEST {
  set namevals [split [HTTP::query] "&"]
  set pool_name ""
  for {set i 0} {$i < [llength $namevals]} {incr i} {
    set params [split [lindex $namevals $i] "="]
    set name [lindex $params 0]
    set val [lindex $params 1]
    switch $name {
      "pool" {
         set pool_name $val
      }
    }
  }
  if { "" ne $pool_name } {
    pool $pool_name
  }
}

What's wrong with this implementation? Nothing if your configuration has the given value in the $pool_name variable. What happens if that value doesn't exist in the configuration?

catch

The answer is that you will get a runtime error and the given connection will be broken. This is not an ideal solution and the simple use of the catch command can avoid runtime connection termination and allow the request to continue on through to a default pool of servers.

The syntax for the catch command is as follows:

catch script ?varName?

The catch command may be used to prevent errors from aborting command interpretation. It calls the Tcl interpreter recursively to execute script, and always returns without raising an error, regardless of any errors that might occur while executing script.

If script raises an error, catch will return a non-zero integer value corresponding to the exceptional return code returned by evaluation of script. Tcl defines the normal return code from script evaluation to be zero (0), or TCL_OK. Tcl also defines four exceptional return codes: 1 (TCL_ERROR), 2 (TCL_RETURN), 3 (TCL_BREAK), and 4 (TCL_CONTINUE). Errors during evaluation of a script are indicated by a return code of TCL_ERROR. The other exceptional return codes are returned by the return, break, and continue commands and in other special situations as documented. Tcl packages can define new commands that return other integer values as return codes as well, and scripts that make use of the return -code command can also have return codes other than the five defined by Tcl.

If the varName argument is given, then the variable it names is set to the result of the script evaluation. When the return code from the script is 1 (TCL_ERROR), the value stored in varName is an error message. When the return code from the script is 0 (TCL_OK), the value stored in resultVarName is the value returned from script.

Solution

The following iRule is the same as above, except that it makes use of exception handling in the dynamic pool assignment.

when HTTP_REQUEST {
  set namevals [split [HTTP::query] "&"]
  set pool_name ""
  for {set i 0} {$i < [llength $namevals]} {incr i} {
    set params [split [lindex $namevals $i] "="]
    set name [lindex $params 0]
    set val [lindex $params 1]
    switch $name {
      "pool" {
         set pool_name $val
      }
    }
  }
  if { "" ne $pool_name } {
    if { [catch { pool $pool_name } ] } {
      log local0. "ERROR: Attempting to assign traffic to non-existant pool $pool_name"
      pool default_pool
    }
  }
}

Now, the control of which pool the connection is directed to is completely in the hands of the request URI. And in those rare situations where your app logic changes before the network configuration does, the connection will fallback to a default pool of servers, while logging the issue to the system log for later examination.

Conclusion

The ability to use dynamic variables greatly enhances the flexibility of what you can do with iRules commands. The catch command should be used around any iRule command that makes use of dynamic variables to catch any runtime errors that occur. In fact, if you so desire, you and nest catch commands for a multi-layered exception handling solution, but I'll leave that one for a later article.