Subscriptions: Video  |  Audio  |  Tutorials  |  Tech Tips  |  Features  |  More...

Current Articles | Categories | Search | Syndication

by citizen_elah - 1656 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.
Audio
Validating Data Group (Class) References
v10.1 - Configuring GTM's DNS Security Extensions
v.10 - Remote Authorization via TACACS+
v.10 - New class features in iRules
v.10 - iRules and the after command
v.10 - FastHTTP and Cookie Persistence
v.10 - A new iRules Namespace
Unbind your LDAP servers with iRules
Ten Steps to iRules Optimization
Tech Tip: Saving Your iControl Changes
Switch Gone Wild: Using Wildcards with the Tcl "switch" command
Stacking iRules: A Modular Approach
SNMP: Capturing SSL Statistics per Virtual Server
Selective DNS Persistence on GTM
Ruby Meets iControl: Switching Policies
Ruby meets iControl: Making Wide IPs
Ruby meets iControl: Creating VIPs
Rewriting Redirects
Replacing the WebSphere Apache Plugin with iRules
RADIUS Load Balancing with iRules
Polymorphism - Making TCL operators work for you
Persisting SSL Connections
Persisting Across Virtual Servers
Passive Application Monitoring with LTM
Monitoring TCP Applications #01
Managing The System Boot Location with iControl
LTM: Per-VLAN Default Gateways
LTM: Dueling Timeouts
LTM: Configuring IP Forwarding
LTM: Action on Service Down
iRules: Disabling Event Processing
iRules Update: New options for the "log" command
iRules Optimization 101 - #05 - Evaluating iRule Performance
iRules Optimization 101 - #04 - Delimiters: Braces, Brackets, Quotes and more
iRules Optimization 101 - #03 - for vs. foreach
iRules Optimization 101 - #02 - Expressions and Variables
iRules Optimization 101 - #01 - if, elseif and switch
iRules Event Order
iRules 101 - #15 - TCL List Handling Commands
iRules 101 - #14 - TCL String Commands Part 2
iRules 101 - #13 - TCL String Commands Part 1
iRules 101 - #12 - Validating Your Logic
iRules 101 - #11 - Events
iRules 101 - #10 - Regular Expressions
iRules 101 - #09 - Debugging
iRules 101 - #08 - Classes
iRules 101 - #07 - Catch
iRules 101 - #06 - When
iRules 101 - #05 - Selecting Pools, Pool Members, and Nodes
iRules 101 - #04 - Switch
iRules 101 - #03 - Variables
iRules 101 - #02 - If and Expressions
iRules 101 - #01 - Introduction to iRules
iRule Security 101 - #09 - Command Execution
iRule Security 101 - #08 - Limiting POST Data
iRule Security 101 - #07 - FTP Proxy
iRule Security 101 - #06 - HTTP Referer
iRule Security 101 - #05 - Avoiding Path Traversal
iRule Interference: Custom Closes and Responses
Investigating the LTM TCP Profile: Windows & Buffers
Investigating the LTM TCP Profile: The Finish Line
Investigating the LTM TCP Profile: Nagle’s Algorithm
Investigating the LTM TCP Profile: ECN & LTR
Investigating the LTM TCP Profile: Congestion Control Algorithms
Investigating the LTM TCP Profile: Acknowledgements
iControl Concept to Implementation (iC2I): The Introduction.
iControl Apps - #18 - Virtual Server Reverse Lookup
iControl Apps - #14 - Global Statistics
iControl Apps - #13 - System PVA Statistics
iControl Apps - #12 - Global SSL Statistics
iControl Apps - #11 - Global GTM Statistics
iControl Apps - #10 - Bigpipe List
iControl Apps - #09 - TMM Statistics
iControl Apps - #08 - System IP Statistics
iControl Apps - #07 - System Http Statistics
iControl Apps - #06 - Configuration Archiving
iControl Apps - #05 - Rate Based Statistics
iControl Apps - #04 - Graceful Server Shutdown
iControl Apps - #03 - Local Traffic Map
iControl Apps - #02 - Local Traffic Summary
iControl Apps - #01 - Disabling Node Servers
iControl 101 - #22 - GTM Data Centers
iControl 101 - #21 - Rate Classes
iControl 101 - #20 - Port Lockdown
iControl 101 - #19 - Time Conversions
iControl 101 - #18 - Stream Profile
iControl 101 - #17 - PortMirror
iControl 101 - #16 - SelfIPs
iControl 101 - #15 - System Services
iControl 101 - #14 - License Administration
iControl 101 - #13 - Data Groups
iControl 101 - #12 - Database Variables
iControl 101 - #11 - Performance Graphs
iControl 101 - #10 - System Inet
iControl 101 - #09 - iRules
iControl 101 - #08 - Partitions
iControl 101 - #07 - User Management
iControl 101 - #06 - File Transfer APIs
iControl 101 - #05 - Exceptions
iControl 101 - #04 - Language Options
iControl 101 - #03 - iControl Taxonomy
iControl 101 - #02 - How iControl Works
iControl 101 - #01 - iControl Marketing Dissected
iC2I: Automation - Creating Virtuals Simplified.
GTM, Know Thyself
Getting Started with pyControl
FTPS Offload via iRules
Exchange Persistence Duality and iRules
Dynamic WSDL updating with iRules
Custom SNMP Traps
Creating An iControl PowerShell Monitoring Dashboard With Google Charts
Cookie LoJack vi iRules
Content-Disposition - Forced file downloads via HTTP
Concurrent iControl Programming Explained
Case Insensitive Comparisons
Can iRules fix my cert mismatch errors?
Cache in with LTM and iRules
Building a Custom WebAccelerator Policy
Automated Web Analytics iRule Style
Investigating the LTM TCP Profile: Max Syn Retransmissions & Idle Timeout