Tech Tips on DevCentral
   
You are here: Tutorials > Tech Tips

Current Articles | Categories | Search | Syndication

TMSH Scripting in v10.1

by Jason Rahm - 6879 views

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!



Rate This Article:

COMMENTS

There are currently no comments, be the first to post one.
Only registered users may post comments.
  
Subscriptions: Video  |  Audio  |  Tutorials  |  Tech Tips  |  Features  | 

More...

 

 

Essentials Quick Start Guides
iRules Wiki | iControl SDK | WebAccelerator Wiki iRules | iControl
FirePass Wiki | Advanced Design & Config Wiki WebAccelerator | FirePass

 

Videos

  

Audio

Cache in with LTM and iRules
Can iRules fix my cert mismatch errors?
Concurrent iControl Programming Explained
Cookie LoJack vi iRules
Creating An iControl PowerShell Monitoring Dashboard With Google Charts
Custom SNMP Traps
Exchange Persistence Duality and iRules
FTPS Offload via iRules
Getting Started with pyControl
iControl 101 - #19 - Time Conversions
iControl 101 - #20 - Port Lockdown
iControl 101 - #21 - Rate Classes
iControl 101 - #22 - GTM Data Centers
iControl Apps - #04 - Graceful Server Shutdown
iControl Apps - #05 - Rate Based Statistics
iControl Apps - #06 - Configuration Archiving
iControl Apps - #07 - System Http Statistics
iControl Apps - #08 - System IP Statistics
iControl Apps - #09 - TMM Statistics
iControl Apps - #10 - Bigpipe List
iControl Apps - #11 - Global GTM Statistics
iControl Apps - #12 - Global SSL Statistics
iControl Apps - #13 - System PVA Statistics
iControl Apps - #14 - Global Statistics
iControl Apps - #18 - Virtual Server Reverse Lookup
Investigating the LTM TCP Profile: Acknowledgements
Investigating the LTM TCP Profile: Congestion Control Algorithms
Investigating the LTM TCP Profile: ECN &amp; LTR
Investigating the LTM TCP Profile: Max Syn Retransmissions &amp; Idle Timeout
Investigating the LTM TCP Profile: Nagle’s Algorithm
Investigating the LTM TCP Profile: The Finish Line
Investigating the LTM TCP Profile: Windows &amp; Buffers
iRules 101 - #13 - TCL String Commands Part 1
iRules 101 - #14 - TCL String Commands Part 2
iRules 101 - #15 - TCL List Handling Commands
iRules Event Order
Managing The System Boot Location with iControl
Persisting SSL Connections
Replacing the WebSphere Apache Plugin with iRules
Ruby meets iControl: Creating VIPs
Ruby meets iControl: Making Wide IPs
Ruby Meets iControl: Switching Policies
Ten Steps to iRules Optimization
Unbind your LDAP servers with iRules
v.10 - A new iRules Namespace
v.10 - FastHTTP and Cookie Persistence
v.10 - iRules and the after command
v.10 - New class features in iRules
v.10 - Remote Authorization via TACACS&#43;
v10.1 - Configuring GTM's DNS Security Extensions

  

Features

  

Tutorials

  

iControl

  

iRules

  

Monitoring & Management

  

Advanced Design & Config

  

93,050 Members in 191 Countries and Growing!

Join DevCentral Today!

About DevCentral

F5 DevCentral is your source for the best technical documentation, discussion forums, blogs, media and more related to application delivery networking.

So dive in, meet your peers, and get familiar with DevCentral. We hope it makes your job easier and helps you get more from your F5 investment. If new to DevCentral, check out the Getting Started section. And if you have any problems, or think something could be easier to use, let us know.

Got It !

We've received your comment and transmitted it directly to DevCentral HQ.

Thanks for taking time to let us know what's on your mind. At DevCentral | Community Matters!

Get In Touch With Us

Have questions, suggestions or just want to get something off your chest?

Use our handy form below to Direct Connect with DevCentral Mission Control.

Send Us Feedback      or