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:

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:

Create a datacenter

Create a server, and if applicable, virtual servers. Since the server is a BIG-IP, we’ll create a few virtual servers.

Create a pool

Create a wideIP

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:

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.

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:procscript::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:procscript::run {} {

35:for {set idx 1} {$idx &lt; $tmsh::argc} {incr idx} {

36:lappend ::pool_ids [lindex $tmsh::argv $idx]

37: }

38:

39:arrayset r1 {}

40:arrayset 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:arrayset r1 [array get r2]

80:arrayunset r2

81: }

82: }

83:

84:procscript::help {} {

85:tmsh::add_help"enter zero or more pool names"

86: }

87:

88:procscript::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”:

Jason Rahm

Jason is a solution architect with the DevCentral team, joining the F5 family after six years in the trenches as a customer and advocate. His experience is predominantly with design and management of the Local Traffic Manager and the Global Traffic Manager, though holds operational experience with the Application Security Manager and Acess Policy Manager products in his bag of tricks as well. Jason is quite comfortable with iRules and hacks at iControl here and there when Joe forgets to lock his cage.
Jason enjoys spending time with his beautiful wife and kids and is active with his church where he volunteers his network and (questionable) systems skills. Contact him via email at j.rahm@f5.com, or catch him on twitter at @jasonrahm.