TMSH Scripting in v10.1

With the release of v10.1, tmsh has added a scripting language to accompany the shell introduced in v10.  To steal Yoda's linguistic skills...Powerful, this will be.  So powerful in fact that we here at DevCentral have created a wiki namespace and a forum for tmsh related content and questions.

Last Spring when v10 was released, I wrote an article on how to use the new traffic management shell to configure GTM.  In this article, we’ll build the same configuration from a questionnaire generated by a tmsh script.  A couple caveats before getting started:

  • Error checking is lacking here.  The script is for demonstration purposes.  It would need serious work for use in a production environment.
  • There are plenty of features not highlighted or optional in the script.  Those that are used outside of pool and member ratios are hardcoded.

Getting Started

You can launch the script editor in tmsh by entering “edit cli script <scriptname>.tcl”.  Once in the editor, if it’s a new script, you’ll see a create script <scriptname.tcl { } wrapper with four procedures:

  • script::init – optional.  Can initialize global variables here.
  • script::run – required.  This is the main program loop.
  • script::help – optional.  Hitting the “?” key will provide context sensitive help.
  • script::tabc – optional. Hitting the tab key will provide context sensitive help.

Our script will create a procedure of our own and utilize the script::run procedure, so you can remove the others for this effort.

 

Working with User Input

When I first started this script effort, there was an awful lot of repetition with regards to stdout.  With that in mind, I created a proc to handle the user feedback:

   1: proc getFeedback { question } {
   2:     puts -nonewline $question
   3:     flush stdout
   4:     return [gets stdin]
   5: }
   6: 
   7: script::run {
   8:     set dc_count [getFeedback "How many datacenters? "]
   9: }

 

Each time we need to get feedback, now instead of repeating the puts <string> and the stdout flush, we just set the variable to the returned data from the procedure.

GTM Configuration Tasks

To get to a resolving system, several tasks need to be accomplished:

  1. Create a datacenter
  2. Create a server, and if applicable, virtual servers.  Since the server is a BIG-IP, we’ll create a few virtual servers.
  3. Create a pool
  4. Create a wideIP
  5. Create a listener (already accomplished in our case)

In tmsh, the commands for these tasks are all under the gtm module, and we’ll need the syntax for the commands for our script.

Creating the Datacenters

This one’s really easy.  The only required information for the datacenter is the name.  In the tmsh shell, this would be create gtm datacenter <dc name>.  In script, we’ll use the tmsh::create command to achieve the same result.

   1: # Enable stateless so existing objects can be overwritten
   2: tmsh::stateless enabled
   3: 
   4: #Build Datacenters
   5: set dc_count [getFeedback "How many datacenters do you wish to create? "]
   6: for {set x 0} {$x&lt;$dc_count} {incr x} {
   7:     lappend dc_names [getFeedback "Datacenter [expr $x +1] name? "]
   8: }
   9: tmsh::create /gtm datacenter $dc_names
  10: puts "\nDatacenters created...\n\n"

Here we grab the number of datacenters desired, loop through the count number and append the datacenter names to a variable we’ll use to create the datacenters with the tmsh::create command.  One other note, since I’m using this script for demonstration purposes, running through it repeatedly would error out because these objects already exist.  Rather than deleting them all after each iteration of the script, I’m using the tmsh::stateless command to overwrite any objects already in place.

Creating the Servers

This one is slightly more difficult as there are many different options that can be applied at the server level.  To keep this short, we’ll hard code the monitor and ignore the other options.  In the tmsh shell, we’d enter create gtm server <server name> addresses add { <ip> } monitor bigip datacenter <dc name> virtual-servers add { <virtual server ip:port> }.  In script, we’ll approach it this way:

   1: #Build Servers
   2: set srv_count [getFeedback "How many servers are you adding? "]
   3: for {set x 0} {$x&lt;$srv_count} {incr x} {
   4:     set srv_name [getFeedback "Server [expr $x +1] Name? "]
   5:     set srv_ip [getFeedback "Server [expr $x +1] IP? "]
   6:     set srv_dc_loc [getFeedback "Datacenter server belongs to (tmsh::get_config /gtm datacenter)? "]
   7:     set v_count [getFeedback "How mnay virtuals for $srv_name? "]
   8:     for {set y 0} {$y&lt;$v_count} {incr y} {
   9:         lappend vmembers($srv_name) [getFeedback "Virtual Server [expr $y +1] IP:Port? "]
  10:     }
  11:     tmsh::create /gtm server $srv_name addresses add \{ \
  12:         $srv_ip \} monitor bigip datacenter $srv_dc_loc \
  13:         virtual-servers add \{ $vmembers($srv_name) \}
  14: }
  15: puts "\nServers created...\n\n"

 

This requires nested for loops, one to create the server, and the other to create the virtual servers within each server.

Creating the Pools

This code block is very similar to the previous one, with a couple exceptions.  Because the tmsh create pool command expects the ratio to be set (if being set) within the context of the { ip:port { ratio x } ip:port { ratio x} } etc, I needed to make sure that part of the string was accounted for when iterating through.  The foreach block takes each argument of the array and builds a single string so that the format is appropriate for the gtm pool command.

   1: #Build Pools
   2: set pl_count [getFeedback "How many pools are you adding? "]
   3: for {set x 0} {$x&lt;$pl_count} {incr x} {
   4:     set pl_name [getFeedback "Pool [expr $x +1] name? "]
   5:     set pm_count [getFeedback "How many pool members? "]
   6:     for {set y 0} {$y&lt;$pm_count} {incr y} {
   7:         set pm_raw [getFeedback "Pool member [expr $y +1] IP:Port and ratio (Ex. 1.1.1.1:80 1)? "]
   8:         set pm_value "[lindex [split $pm_raw " "] 0] \{ ratio [lindex [split $pm_raw " "] 1] \}"
   9:         lappend pmembers($pl_name) $pm_value
  10:     }
  11:     foreach z $pmembers($pl_name) { append pmmod "$z " }
  12:     tmsh::create /gtm pool $pl_name members add \{ $pmmod \} \
  13:         load-balancing-mode ratio verify-member-availability disabled
  14: }
  15: puts "\nPools created...\n\n"

Creating the WideIP

To create the WideIP, the block looks nearly identical to creating the pools, except for the elimination of the nested for loop and the different tmsh command in use.

   1: #Build WideIP
   2: set wip_name [getFeedback "WideIP Name? "]
   3: set wip_pl_count [getFeedback "How many pools? "]
   4: for {set x 0} {$x&lt;$wip_pl_count} {incr x} {
   5:     set pl_raw [getFeedback "Pool [expr $x +1] name a ratio (Ex. pool1 1)? "]
   6:     set pl_value "[lindex [split $pl_raw " "] 0] \{ ratio [lindex [split $pl_raw " "] 1] \}"
   7:     lappend wip_pools($wip_name) $pl_value
   8: }
   9: foreach z $wip_pools($wip_name) { append wipplmod "$z " }
  10: tmsh::create /gtm wideip $wip_name pool-lb-mode ratio pools add \{ $wipplmod \} \
  11:     persistence enabled ttl-persistence 300 rules add \{ testwip-rule \}
  12: 
  13: puts "\nWideIP created, configuration is complete.\n\n"

And that’s a wrap for the script!  Now let’s run it.

Running the Script

root@golgotha(Active)(tmos)# run cli script test1.tcl
How many datacenters do you wish to create? 2
Datacenter 1 name? dc1
Datacenter 2 name? dc2

Datacenters created...


How many servers are you adding? 2
Server 1 Name? ltm1
Server 1 IP? 10.10.100.1
Datacenter server belongs to (tmsh::get_config /gtm datacenter)? dc1
How mnay virtuals for ltm1? 3
Virtual Server 1 IP:Port? 10.10.100.10:80
Virtual Server 2 IP:Port? 10.10.100.11:80
Virtual Server 3 IP:Port? 10.10.100.12:80
Server 2 Name? ltm2
Server 2 IP? 10.10.200.1
Datacenter server belongs to (tmsh::get_config /gtm datacenter)? dc2
How mnay virtuals for ltm2? 3
Virtual Server 1 IP:Port? 10.10.200.10:80
Virtual Server 2 IP:Port? 10.10.200.11:80
Virtual Server 3 IP:Port? 10.10.200.12:80

Servers created...


How many pools are you adding? 2
Pool 1 name? gpool1
How many pool members? 3
Pool member 1 IP:Port and ratio (Ex. 1.1.1.1:80 1)? 10.10.100.10:80 1
Pool member 2 IP:Port and ratio (Ex. 1.1.1.1:80 1)? 10.10.100.11:80 2
Pool member 3 IP:Port and ratio (Ex. 1.1.1.1:80 1)? 10.10.100.12:80 3
Pool 2 name? gpool2
How many pool members? 3
Pool member 1 IP:Port and ratio (Ex. 1.1.1.1:80 1)? 10.10.200.10:80 1
Pool member 2 IP:Port and ratio (Ex. 1.1.1.1:80 1)? 10.10.200.11:80 2
Pool member 3 IP:Port and ratio (Ex. 1.1.1.1:80 1)? 10.10.200.12:80 3

Pools created...


WideIP Name? test.wip.com
How many pools? 2
Pool 1 name a ratio (Ex. pool1 1)? gpool1 1
Pool 2 name a ratio (Ex. pool1 1)? gpool2 2

WideIP created, configuration is complete.

Obviously, since there’s no error checking in this script, typos will kill, so I’d encourage you to include error checking on the data entry.  So now that our configuration is complete, I’ve modified this entry from the codeshare to watch our new pools as I run some test traffic against the new WideIP:

   1: proc script::init {} {
   2:     set ::pool_ids ""
   3: }
   4: 
   5: proc get_stats { resultsArray } {
   6: 
   7:     upvar $resultsArray results
   8: 
   9:     set idx 0
  10:     set objs [tmsh::get_status gtm pool $::pool_ids raw]
  11:     set count [llength $objs]
  12: 
  13:     while { $idx &lt; $count } {
  14: 
  15:         set obj [lindex $objs $idx]
  16:         set pool [tmsh::get_name $obj]
  17: 
  18:         lappend results($pool) preferred
  19:         lappend results($pool) \
  20:             [tmsh::get_field_value $obj "preferred"]
  21: 
  22:         lappend results($pool) alternate
  23:         lappend results($pool) \
  24:             [tmsh::get_field_value $obj "alternate"]
  25: 
  26:         lappend results($pool) dropped
  27:         lappend results($pool) \
  28:             [tmsh::get_field_value $obj "dropped"]
  29: 
  30:         incr idx
  31:     }
  32: }
  33: 
  34: proc script::run {} {
  35:     for {set idx 1} {$idx &lt; $tmsh::argc} {incr idx} {
  36:         lappend ::pool_ids [lindex $tmsh::argv $idx]
  37:     }
  38: 
  39:     array set r1 {}
  40:     array set r2 {}
  41: 
  42:     set interval 2
  43:     set delay [expr $interval * 1000]
  44: 
  45:     get_stats r1
  46: 
  47:     while { true } {
  48:         after $delay
  49:         get_stats r2
  50:         tmsh::clear_screen
  51: 
  52:         foreach { pool } [lsort [array names r1]] {
  53: 
  54:             if { [string length [array names r2 -exact $pool]] == 0 } {
  55:                 puts "$pool: no sample"
  56:                 continue
  57:             }
  58: 
  59:             set line [format "%-20s" $pool]
  60: 
  61:             set s1 $r1($pool)
  62:             set s2 $r2($pool)
  63: 
  64:             set idx 0
  65:             set count [llength $s1]
  66:             while { $idx &lt; $count } {
  67:                 append line "[lindex $s1 $idx] "
  68:                 incr idx
  69: 
  70:                 set stat \
  71:                     [expr ([lindex $s2 $idx] - [lindex $s1 $idx]) / $interval]
  72:                 append line "[format "%-12s" $stat]"
  73:                 incr idx
  74:             }
  75:             puts $line
  76:         }
  77: 
  78:         # use the most recent results as the next previous results
  79:         array set r1 [array get r2]
  80:         array unset r2
  81:     }
  82: }
  83: 
  84: proc script::help {} {
  85:     tmsh::add_help "enter zero or more pool names"
  86: }
  87: 
  88: proc script::tabc {} {
  89:     foreach {pool} [tmsh::get_config /gtm pool] {
  90:         tmsh::add_tabc [tmsh::get_name $pool]
  91:     }
  92: }

The output from the script above looks like this when running “run cli script watch_gtmPools.tcl”:

gpool1              preferred 5           alternate 0           dropped 0
gpool2              preferred 9           alternate 0           dropped 0

The complete script for the gtm configuration example above can be found here in the codeshare.  The GTM pool monitor script is also in the codeshare.  Happy scripting!

Published Jan 12, 2010
Version 1.0