series-icontrol-apps
16 TopicsiControl Apps - #03 - Local Traffic Map
The BIG-IP Management GUI has a feature called the Network Map that includes a hierarchical summary of the objects on the system and their current statuses. This tech tip follows on to my previous Local Traffic Summary article and will take that GUI component and break it down into the underlying iControl method calls and present an alternative implementation from within Windows PowerShell. Introduction The Network Map component of the BIG-IP Management GUI has two components. This article focuses on the second component which is a Local Traffic Map that lists the a hierarchical tree of Virtual Servers, Pools, Pool Members, and iRules with their respective statuses. An example of the Network map can be seen below: This application will replicate that tree of objects and show you how to use client side caching techniques to make the application run faster. I will be using Windows PowerShell with the iControl Cmdlets for PowerShell available from the DevCentral Labs website. The code listed below can be consolidated into a single script (PsLocalTrafficMap.ps1) to be run directly from the command line. First, I'm going to define the parameters for the application. This can be done with the param statement. We'll be needing the BIG-IP address, username, and password. I'll also include here the declarations for a couple of global variables we'll be using for caching. param ( $g_bigip = $null, $g_uid = $null, $g_pwd = $null ) Set-PSDebug -strict; $g_vs_list = $null; $g_vs_pool_list = $null; $g_vs_rule_list = $null; Initialization The entry code checks to see if the parameters were passed in correctly. If not, a usage message will be displayed. If the parameters were passed in correctly, it will attempt to initialize the iControl SnapIn and authenticate with the BIG-IP and then make a call to the local Print-LocalTrafficMap function. function Show-Usage() { Write-Host "Usage: LocalTrafficSummary.ps1 host uid pwd"; exit; } function Do-Initialize() { $snapin = Get-PSSnapin | Where-Object { $_.Name -eq "iControlSnapIn" } if ( $null -eq $snapin ) { Add-PSSnapIn iControlSnapIn } $success = Initialize-F5.iControl -HostName $g_bigip -Username $g_uid -Password $g_pwd; return $success; } if ( ($g_bigip -eq $null) -or ($g_uid -eq $null) -or ($g_pwd -eq $null) ) { Show-Usage; } if ( Do-Initialize ) { Print-LocalTrafficMap } else { Write-Error "ERROR: iControl subsystem not initialized" } Let the fun begin The Print-LocalTrafficMap is where all the fun happens. The function queries all the data up front and makes use of client side caching by using the Get-VSList, Get-VSPoolList, and GET-VSRuleList functions that return cached objects to eliminate the need for multiple requests for the same data. The function then iterates over the virtual server list and generates a status value with the local Get-ObjectStatus function described below. The Name of the virtual server is written to the console along with the current availability and enabled status and the availability description. Next, if there was a pool defined for the current virtual server, as indicated by the "i"th index in the pool_list array, the pool and pool member details will be analyzed. The user friendly pool status is then generated by the Get-ObjectStatus function and the default pool along with it's status are printed to the console. Next, we use the current pool and look for members within that pool. If there are members present, as defined by the "i"th element of the MemberStatAofA 2-D array, we iterate over those members and extract the members address and port and generate it's status with the Get-ObjectStatus function. Then the pool member is written to the console along with it's status string. Lastly, the iRules are iterated and written to the console for the specific virtual server. There is no status associated with iRules, so only the name is sent to output. function Print-LocalTrafficMap() { $vs_list = Get-VSList; $pool_list = Get-VSPoolList; $vs_statuses = (Get-F5.iControl).LocalLBVirtualServer.get_object_status( $vs_list ); $pool_statuses = (Get-F5.iControl).LocalLBPool.get_object_status( (Sanitize-Array $pool_list) ); $MemberStatAofA = (Get-F5.iControl).LocalLBPoolMember.get_object_status( $pool_list ); $VSRuleAofA = Get-VSRuleList; Write-Host "***** SITE MAP FOR BIG-IP $g_bigip ******" for($i=0; $i -lt $vs_list.Length; $i++) { $vs = $vs_list[$i]; $stat_str = Get-ObjectStatus $vs_statuses[$i]; Write-Host "$vs (VS)"; Write-Host "| $stat_str" if ( $pool_list[$i].Length -gt 0 ) { $pool = $pool_list[$i]; $stat_str = Get-ObjectStatus $pool_statuses[$i]; Write-Host "+-> $pool (P)" Write-Host " | $stat_str" for($j=0; $j -lt $MemberStatAofA[$i].Length; $j++) { $MemberStat = $MemberStatAofA[$i][$j]; $member = $MemberStat.member; $addr = $member.address; $port = $member.port; $stat_str = Get-ObjectStatus $MemberStat.object_status; Write-Host " +-> ${addr}:${port} (PM)" Write-Host " | $stat_str" } } if ( $VSRuleAofA[$i].Length -gt 0 ) { for ($j=0; $j -lt $VSRuleAofA[$i].Length; $j++) { $RuleDef = $VSRuleAofA[$i][$j]; $rule_name = $RuleDef.rule_name; Write-Host "+-> $rule_name (R)" } } } } As mentioned above, the Get-ObjectStatus function will take as input a LocalLB.ObjectStatus structure along with the name of the type of object the status is for. The availability status and enabled_status are then extracted along with the status string. From the Availability status and Enabled status, user friendly strings are generated and a resulting status string is returned from the function. function Get-ObjectStatus() { param ( $object_status ); $avail = $object_status.availability_status; $enabled = $object_status.enabled_status; $s_avail = ""; $s_enabled = ""; $s_desc = $object_status.status_description; switch($avail) { "AVAILABILITY_STATUS_GREEN" { $s_avail = "Available"; } "AVAILABILITY_STATUS_YELLOW" { $s_avail = "Unavailable"; } "AVAILABILITY_STATUS_RED" { $s_avail = "Unavailable"; } "AVAILABILITY_STATUS_BLUE" { $s_avail = "Unknown"; } "AVAILABILITY_STATUS_GRAY" { $s_avail = "Unavailable"; } default { $s_avail = "Unknown"; } } switch($enabled) { "ENABLED_STATUS_ENABLED" { $s_enabled = "Enabled"; } "ENABLED_STATUS_DISABLED" { $s_enabled = "Disabled"; } "ENABLED_STATUS_DISABLED_BY_PARENT" { $s_enabled = "Disabled"; } default { } } return "$s_avail ($s_enabled) $s_desc"; } Utility Functions There are a set of helper methods are wrappers around the global cached objects for the virtual server list, virtual server pool list, and virtual server iRule list. If the values are null, they are queried. If non-null, the cached values are returned. The benefit of doing this allows for a reduction in iControl requests to be made for duplication information. function Get-VSList() { if ( $null -eq $g_vs_list ) { $g_vs_list = (Get-F5.iControl).LocalLBVirtualServer.get_list(); } return $g_vs_list; } function Get-VSPoolList() { if ( $null -eq $g_vs_pool_list ) { $g_vs_pool_list = (Get-F5.iControl).LocalLBVirtualServer.get_default_pool_name( (Get-VSList) ); } return $g_vs_pool_list; } function Get-VSRuleList() { if ( $null -eq $g_vs_rule_list ) { $g_vs_rule_list = (Get-F5.iControl).LocalLBVirtualServer.get_rule( (Get-VSList) ); } return $g_vs_rule_list; } And lastly is the Sanitize-Array method that takes as input a string array and removes elements of zero size. This is for the case where a default pool list is returned from the list of virtual servers and one or more of the virtual servers does not have a default pool in it's configuration. If the originally returned list was passed into one of the Pool interfaces methods, it would return an error as an undefined pool. This method helps by eliminating the empty elements. function Sanitize-Array() { param ( [string[]]$in ); $out = $null; foreach ($item in $in) { if ( $item.Length -gt 0 ) { if ( $null -eq $out ) { $out = (, $item) } else { $out += $item } } } return $out; } Running the Script Running this script with query all the Virtual Server, Pool, Pool Members, and iRules states and report them in a table that is similar to the one in the Management GUI illustrated above. PS C:\> D:\dev\iControl\CodeShare\iControl\LocalTrafficMap.ps1 bigip_address username password ***** SITE MAP FOR BIG-IP bigip_address ****** xpbert-telnet (VS) | Unknown (Enabled) The virtual server is has no availability status xpbert-http (VS) | Available (Enabled) The virtual server is available +-> xpbert-http (P) | Available (Enabled) The pool is available +-> 10.10.10.149:80 (PM) | Available (Enabled) The pool member is available xpbert-ssh (VS) | Available (Enabled) The virtual server is available +-> xpbert-ssh (P) | Unknown (Enabled) The pool is has no availability status +-> 10.10.10.149:22 (PM) | Available (Enabled) The pool member is available xpbert-ftp (VS) | Unknown (Enabled) The virtual server is has no availability status +-> xpbert-ftp (P) | Unknown () The pool is not available +-> 10.10.10.149:21 (PM) | Unknown (Enabled) The pool member is has no availability status +-> hunt_the_wumpus (R) The full appilcation can be found in the iControl CodeShare under PsLocalTrafficMap Get the Flash Player to see this player. 20080703-iControlApps_3_LocalTrafficMap.mp3799Views0likes7CommentsiControl Apps - #04 - Graceful Server Shutdown
This question comes up quite often here on DevCentral: "How can I gracefully shut down my servers for maintenance without disrupting current user sessions?". In fact, I answered this question just the other day again in the iControl forum and figured I'd throw out an application that accomplished this. So I went about writing this application to allow for the graceful shutdown of a given pool member. Of course, the application wouldn't be complete without listing the pools and members for a specified pool as well as allowing for enabling and disabling of the server so I went ahead and included those pieces as a bonus. Usage The arguments for this application are the address, username, and password of the BIG-IP along with optional arguments for the specified pool, pool member and enabled state. This is declared in the top of the script with the following param statement. There is also a Write-Usage function to display the arguments to the user. param ( $g_bigip = $null, $g_uid = $null, $g_pwd = $null, $g_pool = $null, $g_member = $null, $g_mode = $null ); Set-PSDebug -strict; function Write-Usage() { Write-Host "Usage: PoolMemberControl.ps1 host uid pwd [pool [member:ip [mode = "enable"|"disable"]]]"; exit; } Initialization As is with all of my PowerShell scripts, the initialization component will look to see if the iControlSnapIn is loaded into the current PowerShell session. If not, the Add-PSSnapIn Cmdlet is called to add the snapin into the runtime. Then a call to the Initialize-F5.iControl cmdlet to setup the connection to the BIG-IP. If this succeeds then success, then the rest of the optional arguments are processed. If a pool name is not specified the Get-Pools function will be called to list out all of the available pools on the BIG-IP. If a pool is specified but a member is not, then the Get-PoolMembers function will be called passing in the pool name and this function will then list the currently configured members within that pool. If a pool as well as the pool member are specified but no enabled state is specified, the Get-PoolMemberStatus function is called in which the enabled and availablility status of the specified pool member is displayed. If a pool, pool member, and state are specified, the Enable-Member or Disable-Member method will be called depending on whether the state is "enabled" or "disabled". The code for this process is listed below. #------------------------------------------------------------------------- # Do-Initialize #------------------------------------------------------------------------- function Do-Initialize() { if ( (Get-PSSnapin | Where-Object { $_.Name -eq "iControlSnapIn"}) -eq $null ) { Add-PSSnapIn iControlSnapIn } $success = Initialize-F5.iControl -HostName $g_bigip -Username $g_uid -Password $g_pwd; return $success; } #------------------------------------------------------------------------- # Main Application Logic #------------------------------------------------------------------------- if ( ($g_bigip -eq $null) -or ($g_uid -eq $null) -or ($g_pwd -eq $null) ) { Write-Usage; } if ( Do-Initialize ) { if ( $g_pool -eq $null ) { Get-Pools; } elseif ( $g_member -eq $null ) { Get-PoolMembers $g_pool; } elseif ( $g_mode -eq $null ) { Get-PoolMemberStatus $g_pool $g_member; } else { switch ($g_mode.ToLower()) { "enable" { Enable-Member $g_pool $g_member; } "disable" { Disable-Member $g_pool $g_member; } default { Write-Usage; } } } } else { Write-Error "ERROR: iControl subsystem not initialized" } Listing Pools As mentioned above, if no pool is specified to the program, the Get-Pools function is called in which the LocalLB.Pool.get_list() method is called. This method returns a list of pool names and the function will loop through this list and write them to the console. function Get-Pools() { $pool_list = (Get-F5.iControl).LocalLBPool.get_list(); Write-Host "Available Pools:"; foreach ($pool in $pool_list) { Write-Host " $pool"; } } An example usage of the Get-Pools function is illustrated here: PS C:\> .\PoolMemberControl.ps1 theboss admin admin Available Pools: xpbert-ftp pool_1 catbert-http catbert-ssh pool_2 test-pool xpbert-http xpbert-ssh Listing Pool Members If you don't happen the know the specific addresses of the pool members in your given pool, the application will call the Get-PoolMembers function which in turn calls the iControl LocalLB.Pool.get_member() method passing in the supplied pool name. Returned is a 2-d array of Common.IPPortDefinition structures with the first dimension corresponding to each pool name passed in (in this case just one) and the second dimension for the list of pool members for that specified pool. The function will then iterate through that array and print the results to the console. Function Get-PoolMembers() { param($pool_name); $member_lists = (Get-F5.iControl).LocalLBPool.get_member((, $pool_name)); Write-Host "Members for Pool ${pool_name}:" foreach($member in $member_lists[0]) { $addr = $member.address; $port = $member.port; Write-Host " ${addr}:${port}" } } The following example will list the pool members for pool "xpbert-http". PS C:\> .\PoolMemberControl.ps1 theboss admin admin xpbert-http Members for Pool xpbert-http 10.10.10.149:80 Querying Pool Member Status If your ultimate goal is to take a server out of provisioning, it's likely a good idea to know what it's current state is. By not passing in a state while passing in a pool name and member details, the application will call the local Get-PoolMemberStatus method which then calls the iControl LocalLB.PoolMember.get_object_status() method which returns a 2-D array of MemberObjectStatus structures. Packed inside there are the availability statuses, enabled statuses, and status descriptions for each of the pool members for the specified pool. This method will loop through that returned list and if it finds a match for the specified pool member, it will display the status values. If no match is found, an error is displayed. Function Get-PoolMemberStatus() { param($pool_name, $member); $vals = $member.Split( (, ':')); $member_addr = $vals[0]; $member_port = $vals[1]; $bFound = 0; $MemberObjectStatusAofA = (Get-F5.iControl).LocalLBPoolMember.get_object_status((, $pool_name)); foreach($MemberObjectStatus in $MemberObjectStatusAofA[0]) { $a2 = $MemberObjectStatus.member.address; $p2 = $MemberObjectStatus.member.port; if ( "${member_addr}:${member_port}" -eq "${a2}:${p2}" ) { $Availability = $MemberObjectStatus.object_status.availability_status; $Enabled = $MemberObjectStatus.object_status.enabled_status; $Description = $MemberObjectStatus.object_status.status_description; Write-Host "Pool $pool_name, Member ${member_addr}:${member_port} status:" Write-Host " Availability : $Availability" Write-Host " Enabled : $Enabled" Write-Host " Description : $Description" $bFound = 1; } } if ( $bFound -eq 0 ) { Write-Host "Member ${member_addr}:${member_port} could not be found in pool $pool_name!" } } The following command will query the state of the 10.10.10.149:80 pool member in pool xpbert-http. PS C:\> .\PoolMemberControl.ps1 theboss admin admin xpbert-http 10.10.10.149:80 Pool xpbert-http, Member 10.10.10.149:80 status: Availability : Enabled : ENABLED_STATUS_ENABLED Description : Pool member is available Gracefully disabling a Pool Member Now for the meat of this article: how to gracefully shutdown a pool member. If a pool name, member, and state of "disable" is passed into the application, the local Delete-Member function is called to take down the pool member. Remember that we want to "gracefully" take down the pool member so we'll want to make sure that all current sessions are allowed to run their course before the pool member is disabled for good. So, the first step is to disable any new sessions to that pool member with the LocalLB.PoolMember.set_session_enabled_state() iControl method passing in the pool and member details along with a session_state of "STATE_DISABLED". Remember, that this will not fully disable the pool member but will just stop new connections from being established. The function then queries the current connections statistic with the LocalLB.PoolMember.get_statistics() method. The value of "STATISTIC_SERVER_SIDE_CURRENT_CONNECTIONS" is then extracted into the cur_connections variable. A loop is performed until this value is down to zero. As long as the value is greater than zero, the function sleeps for 1 second and then queries the statistic again. Once the current connections statistic reaches zero, all current connections to the pool member have been removed so it's safe to disable it. The LocalLB.PoolMember.set_monitor_state() method is used to forcefully take down a pool member. Since there are no connections to the pool member any more a forceful take down is actually graceful to the users... Once the pool member is offline, the local Get-PoolMemberStatus function is called to query and display the current status of the pool member to make sure that it's really down. function Disable-Member() { param($pool_name, $member); $vals = $member.Split( (, ':')); $member_addr = $vals[0]; $member_port = $vals[1]; Write-Host "Disabling Session Enabled State..."; $MemberSessionState = New-Object -TypeName iControl.LocalLBPoolMemberMemberSessionState; $MemberSessionState.member = New-Object -TypeName iControl.CommonIPPortDefinition; $MemberSessionState.member.address = $member_addr; $MemberSessionState.member.port = $member_port; $MemberSessionState.session_state = "STATE_DISABLED"; $MemberSessionStateAofA = New-Object -TypeName "iControl.LocalLBPoolMemberMemberSessionState[][]" 1,1 $MemberSessionStateAofA[0][0] = $MemberSessionState; (Get-F5.iControl).LocalLBPoolMember.set_session_enabled_state( (, $pool_name), $MemberSessionStateAofA); Write-Host "Waiting for current connections to drop to zero..." $MemberDef = New-Object -TypeName iControl.CommonIPPortDefinition; $MemberDef.address = $member_addr; $MemberDef.port = $member_port; $MemberDefAofA = New-Object -TypeName "iControl.CommonIPPortDefinition[][]" 1,1 $MemberDefAofA[0][0] = $MemberDef; $cur_connections = 1; while ( $cur_connections -gt 0 ) { $MemberStatisticsA = (Get-F5.iControl).LocalLBPoolMember.get_statistics( (, $pool_name), $MemberDefAofA); $MemberStatisticEntry = $MemberStatisticsA[0].statistics[0]; $Statistics = $MemberStatisticEntry.statistics; foreach ($Statistic in $Statistics) { $type = $Statistic.type; $value = $Statistic.value; if ( $type -eq "STATISTIC_SERVER_SIDE_CURRENT_CONNECTIONS" ) { # just use the low value. Odds are there aren't over 2^32 current connections. # If your site is this big, you'll have to convert this to a 64 bit number. $cur_connections = $value.low; Write-Host "Current Connections: $cur_connections" } } Start-Sleep -s 1 } Write-Host "Disabling Monitor State..."; $MemberMonitorState = New-Object -TypeName iControl.LocalLBPoolMemberMemberMonitorState; $MemberMonitorState.member = New-Object -TypeName iControl.CommonIPPortDefinition; $MemberMonitorState.member.address = $member_addr; $MemberMonitorState.member.port = $member_port; $MemberMonitorState.monitor_state = "STATE_DISABLED"; $MemberMonitorStateAofA = New-Object -TypeName "iControl.LocalLBPoolMemberMemberMonitorState[][]" 1,1 $MemberMonitorStateAofA[0][0] = $MemberMonitorState; (Get-F5.iControl).LocalLBPoolMember.set_monitor_state( (, $pool_name), $MemberMonitorStateAofA); Get-PoolMemberStatus $pool_name $member } The following command will disable pool member 10.10.10.149:80 from pool xpbert-http gracefully by first stopping new connections, then waiting until the connection count drops to zero, and then fully disabling the pool member. PS C:\> .\PoolMemberControl.ps1 theboss admin admin xpbert-http 10.10.10.149:80 disable Disabling Session Enabled State... Waiting for current connections to drop to zero... Current Connections: 50 Current Connections: 47 Current Connections: 47 Current Connections: 0 Disabling Monitor State... Pool xpbert-http, Member 10.10.10.149:80 status: Availability : AVAILABILITY_STATUS_RED Enabled : ENABLED_STATUS_DISABLED Description : Forced down Enabling a Pool Member Now that your server maintenance is performed, you'll likely want to put the pool member back into rotation. This can be done by passing the application the pool name, member name, and a state of "enable". When this is done, the local Enable-Member function is called that basically reverses the Disable-Member functions actions. It first set's the monitor state to "STATE_ENABLED" and then enables new connections by calling the LocalLB.PoolMember.set_session_enabled_state() iControl method with the "STATE_ENABLED" value as well. After the pool is enabled, the Get-PoolMemberStatus function is called to make sure that it is in fact re-enabled. function Enable-Member() { param($pool_name, $member); $vals = $member.Split( (, ':')); $member_addr = $vals[0]; $member_port = $vals[1]; $MemberMonitorState = New-Object -TypeName iControl.LocalLBPoolMemberMemberMonitorState; $MemberMonitorState.member = New-Object -TypeName iControl.CommonIPPortDefinition; $MemberMonitorState.member.address = $member_addr; $MemberMonitorState.member.port = $member_port; $MemberMonitorState.monitor_state = "STATE_ENABLED"; $MemberMonitorStateAofA = New-Object -TypeName "iControl.LocalLBPoolMemberMemberMonitorState[][]" 1,1 $MemberMonitorStateAofA[0][0] = $MemberMonitorState; Write-Host "Setting Montior State to Enabled"; (Get-F5.iControl).LocalLBPoolMember.set_monitor_state( (, $pool_name), $MemberMonitorStateAofA); $MemberSessionState = New-Object -TypeName iControl.LocalLBPoolMemberMemberSessionState; $MemberSessionState.member = New-Object -TypeName iControl.CommonIPPortDefinition; $MemberSessionState.member.address = $member_addr; $MemberSessionState.member.port = $member_port; $MemberSessionState.session_state = "STATE_ENABLED"; $MemberSessionStateAofA = New-Object -TypeName "iControl.LocalLBPoolMemberMemberSessionState[][]" 1,1 $MemberSessionStateAofA[0][0] = $MemberSessionState; Write-Host "Setting Session Enabled State to Enabled"; (Get-F5.iControl).LocalLBPoolMember.set_session_enabled_state( (, $pool_name), $MemberSessionStateAofA); Get-PoolMemberStatus $pool_name $member } The following command will re-enable the 10.10.10.149:80 pool member from pool xpbert-http by enabling the monitor state and allowing new connections. PS C:\> .\PoolMemberControl.ps1 theboss admin admin xpbert-http 10.10.10.149:80 enable Setting Montior State to Enabled Setting Session Enabled State to Enabled Pool xpbert-http, Member 10.10.10.149:80 status: Availability : AVAILABILITY_STATUS_GREEN Enabled : ENABLED_STATUS_ENABLED Description : Pool member is available Conclusion While this application was written in PowerShell, the logic should transfer to any development language you choose to use. You now have the tools you need to automate the maintenance of your application servers. The code for this application can be found in the iControl CodeShare wiki under PsPoolMemberControl. Get the Flash Player to see this player. 20080716-iControlApps_4_GracefulServerShutdown.mp31.1KViews0likes15CommentsiControl Apps - #02 - Local Traffic Summary
The BIG-IP Management GUI has a feature called the Network Map that includes a summary of the objects on the system and their current statuses. This tech tip will take that GUI component and break it down into the underlying iControl method calls and present an alternative implementation from within Windows PowerShell. Introduction The Network Map component of the BIG-IP Management GUI has two components. The first component is a Local Traffic Summary table that lists the Virtual Servers, Pools, Pool Members, Nodes, and iRules with their respective statuses. An example of this table can be seen in the image below: This application will replicate that table and show you how to use client side caching techniques to make the application run faster. As I mentioned above, we will be using Windows PowerShell with the iControl CmdLets for PowerShell available from the PowerShell DevCentral Labs website. The code listed below can be consolidated into a single script file (LocalTrafficSummary.ps1) to be run directly from the command line. First, I'm going to define the parameters for the application. This can be done with the param statement. We'll be needing the BIG-IP address, username, and password. I'll also include here the declarations for a couple of global variables we'll be using for caching. param ( $g_bigip = $null, $g_uid = $null, $g_pwd = $null ) # Set strict level debugging. Set-PSDebug -strict; # A few global variables to cache to avoid multiple duplicate iControl requests. $g_vs_list = $null; $g_vs_pool_list = $null; $g_vs_rule_list = $null; Initialization The entry code checks to see if the parameters were passed in correctly. If not, a usage message will be displayed. If the parameters were passed in correctly, it will attempt to initialize the iControl SnapIn and authenticate with the BIG-IP and then make a call to the local Print-LocalTrafficSummary function. #------------------------------------------------------------------------- # function Show-Usage #------------------------------------------------------------------------- function Show-Usage() { Write-Host "Usage: LocalTrafficSummary.ps1 host uid pwd"; exit; } #------------------------------------------------------------------------- # #------------------------------------------------------------------------- function Do-Initialize() { $snapin = Get-PSSnapin | Where-Object { $_.Name -eq "iControlSnapIn" } if ( $null -eq $snapin ) { Add-PSSnapIn iControlSnapIn } $success = Initialize-F5.iControl -HostName $g_bigip -Username $g_uid -Password $g_pwd; return $success; } #------------------------------------------------------------------------- # Main Application Logic #------------------------------------------------------------------------- if ( ($g_bigip -eq $null) -or ($g_uid -eq $null) -or ($g_pwd -eq $null) ) { Show-Usage; } if ( Do-Initialize ) { Print-LocalTrafficSummary } else { Write-Error "ERROR: iControl subsystem not initialized" } PowerShell has a handy CmdLet that helps with the displaying of tables. Objects can be passed to the Format-Table Cmdlet and the table will be displayed on the console spaced out correctly with column headers and values. The following code will pass an array of values returned from various internal function calls to Format-Table. The magic here is that each of the objects should have the same properties so that the table will be displayed properly. PowerShell has another nifty CmdLet that allows you to create custom objects and then define custom properties in that object. The Add-Member CmdLet will do just this and later in this article, you will see the New-StatusObject function that returns an object to simulate the columns in each row of the generated table. function Print-LocalTrafficSummary() { ( (Get-VSStatus), (Get-PoolStatus), (Get-PoolMemberStatus), (Get-NodeStatus), (Get-iRuleStatus) ) | Format-Table } Virtual Server Status The following function is called by the Print-LocalTrafficSummary function to return the object containing the Virtual Server Summary. In this function a new status object is created by the New-StatusObject function. A list of Virtual Server statuses is then returned from the iControl LocalLB.VirtualServer.get_object_status() method. The cached virtual server list, retrieved from the Get-VSList function, is passed in as a parameter to this method and a list of statuses for the virtual servers are returned. This list is iterated upon and passed into the Update-ObjectStatus function to update the counters on the status object for the supplied object status availability and enabled statuses. The filled object is then returned to the calling function. function Get-VSStatus() { $obj = New-StatusObject -name "Virtual Servers"; $vs_statuses = (Get-F5.iControl).LocalLBVirtualServer.get_object_status( (Get-VSList) ); foreach ($status in $vs_statuses) { Update-ObjectStatus $obj $status; } return $obj; } Pool Status the Pool Status values are retreived with the following Get-PoolStatus function. It generates a status object named "Pools" and then requests a list of pool statuses from all the pools specified as default pools from within the virtual server list. The Get-VSPoolList does some trickery to query the list of default pools from all the Virtual Servers and truncate the list for the virtual servers that do not have a default pool associated with them. This list of pools associated to virtual servers is then iterated upon and their status is added to the status object which is then returned to the calling program. function Get-PoolStatus() { $obj = New-StatusObject -name "Pools"; $pool_statuses = (Get-F5.iControl).LocalLBPool.get_object_status( (Get-VSPoolList) ); foreach ($status in $pool_statuses) { Update-ObjectStatus $obj $status; } return $obj; } Pool Member Status The Pool member status values are retrieved with the Get-PoolMemberStatus function. This function creates a new status object named "Pool members" and then makes a call to the LocalLB.PoolMember.get_object_status() iControl method to query the status of all pool members for the list of Pools that are associated with Virtual Servers that is retreieved from the Get-VSPoolList method. This list of object statuses is interated upon and the statuses are added to the status object with the Update-ObjectStatus function and then the status object is returned to the calling function. function Get-PoolMemberStatus() { $obj = New-StatusObject -name "Pool Members"; $MemberStatAofA = (Get-F5.iControl).LocalLBPoolMember.get_object_status( (Get-VSPoolList) ); foreach ($MemberStatA in $MemberStatAofA) { foreach ($MemberStat in $MemberStatA) { Update-ObjectStatus $obj $MemberStat.object_status; } } return $obj; } Node Status The List of Nodes in the Local Traffic Summary table are the nodes that are referenced in at least one of the pools that are default pools for a virtual server. This function will generate a new status object named "Nodes" and then get a list of pool members from the globally cached virtual server pools returned from the Get-VSPoolList function. Since Pool members are full node servers (meaning address:port pairs), and we are only concerned with unique node addresses, a little busy work is needed to loop over all of the member definitions and make an array that contains only the unique IP addresses for the pool members. This will constitute the list of Nodes we will be reporting on. This list is stored in the local "node_list" variable. This list is then passed into the LocalLB.NodeAddress.get_object_status() method and we go through the same procedure of iterating through that list and updating the status objects values. Then the status object is returned to the calling function. function Get-NodeStatus() { $obj = New-StatusObject -name "Nodes"; $IPPortDefAofA = (Get-F5.iControl).LocalLBPool.get_member( (Get-VSPoolList) ); $node_list = $null; foreach($IPPortDefA in $IPPortDefAofA) { foreach($IPPortDef in $IPPortDefA) { if ( !(Is-InArray $node_list $IPPortDef.address) ) { if ( $null -eq $node_list ) { $node_list = (, $IPPortDef.address) } else { $node_list += $IPPortDef.address; } } } } if ( $node_list.Length -gt 0 ) { $NodeStatusA = (Get-F5.iControl).LocalLBNodeAddress.get_object_status($node_list); foreach ($status in $NodeStatusA) { Update-ObjectStatus $obj $status; } } return $obj; } iRule Status For the iRule Status values, we are only interested in the iRules that are associated with a Virtual Server. This can be done by calling the Get-VSRuleList function which returns the cached list of iRules that are assocated with the cached list of Virtual Servers. For this type of object, there is no status outside of the total number of iRules, so we will iterate through the list and increment the totals by the number of iRules per virtual. All the other statuses are set to dashes as they are in the GUI. function Get-iRuleStatus() { $obj = New-StatusObject -name "iRules"; $vs_list = Get-VSList; $VSRuleAofA = Get-VSRuleList; foreach ($VSRuleA in $VSRuleAofA) { $obj.Total += $VSRuleA.Length; } $obj.Available = "-"; $obj.Unavailable = "-"; $obj.Offline = "-"; $obj.Unknown = "-"; return $obj; } Utility functions As I mentioned above, PowerShell has the Add-Member CmdLet that will allow dynamic creation of properties within an object. Essentially this function will create a structure with an "ObjectType", "Total", "Available", "Unavailable", "Offline", and "Unknown" members. The default value for the ObjectType is the passed in name value and all other statistics are set to 0. function New-StatusObject() { param( $name = $null ); $obj = New-Object -TypeName System.Object; $obj | Add-Member -Type noteProperty -Name "ObjectType" $name; $obj | Add-Member -Type noteProperty -Name "Total" 0; $obj | Add-Member -Type noteProperty -Name "Available" 0; $obj | Add-Member -Type noteProperty -Name "Unavailable" 0; $obj | Add-Member -Type noteProperty -Name "Offline" 0; $obj | Add-Member -Type noteProperty -Name "Unknown" 0; return $obj; } The following function is used to update the status values within an object created with the New-StatusObject function. It is expecting a parameter of type iControl.Common.ObjectStatus to determine the status from. Since this structure has two values of availability_status and enabled_status, I've concatenated the values together in the switch statement and built cases for each combination. Depending on what this combination is, the relevant member of the status object is incremented. function Update-ObjectStatus() { param ( $obj, $object_status ); $obj.Total++; $avail = $object_status.availability_status; $enabled = $object_status.enabled_status; switch("$avail,$enabled") { # Available in some capacity "AVAILABILITY_STATUS_GREEN,ENABLED_STATUS_ENABLED" { $obj.Available++; } "AVAILABILITY_STATUS_GREEN,ENABLED_STATUS_DISABLED" { $obj.Unavailable++; } "AVAILABILITY_STATUS_GREEN,ENABLED_STATUS_DISABLED_BY_PARENT" { $obj.Unavailable++; } # Object Not Available at the current moment "AVAILABILITY_STATUS_YELLOW,ENABLED_STATUS_ENABLED" { $obj.Unavailable++; } "AVAILABILITY_STATUS_YELLOW,ENABLED_STATUS_DISABLED" { $obj.Unavailable++; } "AVAILABILITY_STATUS_YELLOW,ENABLED_STATUS_DISABLED_BY_PARENT" { $obj.Unavailable++; } # Object is not available "AVAILABILITY_STATUS_RED,ENABLED_STATUS_ENABLED" { $obj.Offline++; } "AVAILABILITY_STATUS_RED,ENABLED_STATUS_DISABLED" { $obj.Unavailable++; } "AVAILABILITY_STATUS_RED,ENABLED_STATUS_DISABLED_BY_PARENT" { $obj.Unavailable++; } # Object's availability status is unknown "AVAILABILITY_STATUS_BLUE,ENABLED_STATUS_ENABLED" { $obj.Unknown++; } "AVAILABILITY_STATUS_BLUE,ENABLED_STATUS_DISABLED" { $obj.Unavailable++; } "AVAILABILITY_STATUS_BLUE,ENABLED_STATUS_DISABLED_BY_PARENT" { $obj.Unavailable++; } # Object is unlicensed "AVAILABILITY_STATUS_GRAY,ENABLED_STATUS_ENABLED" { $obj.Unavailable++; } "AVAILABILITY_STATUS_GRAY,ENABLED_STATUS_DISABLED" { $obj.Unavailable++; } "AVAILABILITY_STATUS_GRAY,ENABLED_STATUS_DISABLED_BY_PARENT" { $obj.Unavailable++; } default { Write-Host "$_";} } } These functions are helpers around array management. The Sanitize-Array will take as input a string array and return a string array minus any empty string elements. The Is-InArray function will look through an array for a value and return whether or not the value exists in the specified array. function Sanitize-Array() { param ( [string[]]$in ); $out = $null; foreach ($item in $in) { if ( $item.Length -gt 0 ) { if ( $null -eq $out ) { $out = (, $item) } else { $out += $item } } } return $out; } function Is-InArray() { param( [string[]]$in, [string]$val ); $bFound = $false; foreach($v in $in) { if ( $v -eq $val ) { $bFound = $true; } } return $bFound; } And the last set of helper methods are wrappers around the global cached objects for the virtual server list, virtual server pool list, and virtual server iRule list. If the values are null, they are queried. If non-null, the cached values are returned. The benefit of doing this allows for a reduction in iControl requests to be made for duplication information. function Get-VSList() { if ( $null -eq $g_vs_list ) { $g_vs_list = (Get-F5.iControl).LocalLBVirtualServer.get_list(); } return $g_vs_list; } function Get-VSPoolList() { if ( $null -eq $g_vs_pool_list ) { $g_vs_pool_list = Sanitize-Array (Get-F5.iControl).LocalLBVirtualServer.get_default_pool_name( (Get-VSList) ); } return $g_vs_pool_list; } function Get-VSRuleList() { if ( $null -eq $g_vs_rule_list ) { $g_vs_rule_list = (Get-F5.iControl).LocalLBVirtualServer.get_rule( (Get-VSList) ); } return $g_vs_rule_list; } Running the Script Running this script with query all the Virtual Server, Pool, Pool Member, Nodes, and iRules states and report them in a table that is similar to the one in the Management GUI illustrated above. PS C:\> .\LocalTrafficSummary.ps1 bigip username password ObjectType Total Available Unavailable Offline Unknown ---------- ----- --------- ----------- ------- ------- Virtual Servers 4 2 0 0 2 Pools 3 2 0 0 1 Pool Members 3 2 0 0 1 Nodes 1 0 0 0 1 iRules 1 - - - - With a handful of iControl calls, key functionality can be repurposed for your own needs. Be on the lookout for a future article discussing building a console based network map as available in the management GUI. The full application can be found in the iControl CodeShare under PsLocalTrafficSummary. Get the Flash Player to see this player. 20080626-iControlApps_2_LocalTrafficSummary.mp3592Views0likes1CommentiControl Apps - #07 - System Http Statistics
I was digging through the iControl interfaces the other day and it's still amazing to me, after being around since the beginning of the API, to find new and cool stuff hiding in there. Today's find was lurking within the System.Statistics interface. There are literally dozens of method calls to pull out all sorts of useful statistics and the one I picked for today is the System HTTP Statistics which are a rollup of all HTTP Profiles' statistics. This application will build a report on everything you wanted to know from an HTTP level such as how many 5xx responses have been served to the number of bytes of compressed video and the RAM cache hits and misses. Usage The arguments for this application are the address, username, and password of the BIG-IP along with optional argument for the subset of the statistitcs to present. This is declared in the top of the script with the following param statement. There is also a Write-Usage function to display the arguments to the user. param ( $g_bigip = $null, $g_uid = $null, $g_pwd = $null, $g_subset = $null ); Set-PSDebug -strict; function Write-Usage() { Write-Host "Usage: GlobalHttpStats.ps1 host uid pwd [all|http|compression|cache]"; exit; } Initialization As is with all of my PowerShell scripts, the initialization component will look to see if the iControlSnapIn is loaded into the current PowerShell session. If not, the Add-PSSnapIn Cmdlet is called to add the snapin into the runtime. Then a call to the Initialize-F5.iControl cmdlet to setup the connection to the BIG-IP. If this succeeds, then a call to the Get-GlobalHttpStatistics function is called with the optional subset argument. function Do-Initialize() { if ( (Get-PSSnapin | Where-Object { $_.Name -eq "iControlSnapIn"}) -eq $null ) { Add-PSSnapIn iControlSnapIn } $success = Initialize-F5.iControl -HostName $g_bigip -Username $g_uid -Password $g_pwd; return $success; } if ( ($g_bigip -eq $null) -or ($g_uid -eq $null) -or ($g_pwd -eq $null) ) { Write-Usage; } if ( Do-Initialize ) { Get-GlobalHttpStatistics $g_subset; } else { Write-Error "ERROR: iControl subsystem not initialized" } Global HTTP Statistics The System.Statistics.get_http_statistics() is the only method that's called here. It takes no arguements and returns a System.Statistics structure containing the timestamp when the statistics were taken, along with a list of Common.Statistics structures containing the statistic type along with the 64bit value. To pretty up the presentation, an empty System.Object was created named stat_obj. The statistics list is iterated upon and each of the statistics that match the subset are added as a property to the stats_obj object using Add-Member Cmdlet. This way I can use the PowerShell Format-List CmdLet to make a nice columnar display on the console. You'll notice that all statistics are returned from the method call so the subset feature is purely cosmetic in allowing you to have a more concise output if that is what you desire. Once all of the statistics have been iterated through, they are written to the console by passing the stats_obj object through the PowerShell pipeline to the Format-List Cmdlet. function Get-GlobalHttpStatistics() { param($subset = $null); if ( $subset -eq $null ) { $subset = "all"; } Write-Host "Global HTTP Statistics" $stat_obj = New-Object -TypeName System.Object; $SystemStatistics = (Get-F5.iControl).SystemStatistics.get_http_statistics(); $t = Get-TimeFromTimeStamp $SystemStatistics.time_stamp; $Statistics = $SystemStatistics.statistics; $stat_obj | Add-Member -Type noteProperty -Name "Time Stamp" $t; $stat_obj | Add-Member -Type noteProperty -Name "--------------------------------" ""; foreach($Statistic in $Statistics) { $val = Convert-To64Bit $Statistic.value.high $Statistic.value.low; $bDisplay = 0; switch ($Statistic.type) { "STATISTIC_HTTP_COOKIE_PERSIST_INSERTS" { $label = "Cookie Persist Header Inserts"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_2XX_RESPONSES" { $label = "Responses - 2xx"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_3XX_RESPONSES" { $label = "Responses - 3xx"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_4XX_RESPONSES" { $label = "Responses - 4xx"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_5XX_RESPONSES" { $label = "Responses - 5xx"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_TOTAL_REQUESTS" { $label = "Requests - Total"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_GET_REQUESTS" { $label = "Requests - HTTP GET"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_POST_REQUESTS" { $label = "Requests - HTTP POST"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_V9_REQUESTS" { $label = "Requests - v0.9"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_V10_REQUESTS" { $label = "Requests - v1.0"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_V11_REQUESTS" { $label = "Requests - v1.1"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_V9_RESPONSES" { $label = "Responses - v0.9"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_V10_RESPONSES" { $label = "Responses - v1.0"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_V11_RESPONSES" { $label = "Responses - v1.1"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_MAXIMUM_KEEPALIVE_REQUESTS" { $label = "Requests - Maximum Keepalive"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_BUCKET_1K_RESPONSES" { $label = "Responses - under 1k"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_BUCKET_4K_RESPONSES" { $label = "Responses - From 1-4k"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_BUCKET_16K_RESPONSES" { $label = "Responses - From 4-16k"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_BUCKET_32K_RESPONSES" { $label = "Responses - From 16-32k"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_BUCKET_64K_RESPONSES" { $label = "Responses - From 32-64k"; $bDisplay = (($subset -eq "all") -or ($subset -eq "http")) } "STATISTIC_HTTP_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_NULL_COMPRESSION_BYTES" { $label = " Compression Bytes - Null"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_HTML_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - HTML"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_HTML_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - HTML"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_CSS_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - CSS"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_CSS_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - CSS"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_JS_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - JavaScript"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_JS_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - JavaScript"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_XML_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - XML"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_XML_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - XML"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_SGML_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - SGML"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_SGML_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - SGML"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_PLAIN_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - Plain"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_PLAIN_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - Plain"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_OCTET_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - Octet"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_OCTET_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - Octet"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_IMAGE_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - Image"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_IMAGE_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - Image"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_VIDEO_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - Video"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_VIDEO_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - Video"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_AUDIO_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - Audio"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_AUDIO_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - Audio"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_OTHER_PRE_COMPRESSION_BYTES" { $label = " Pre Compression Bytes - Other"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_OTHER_POST_COMPRESSION_BYTES" { $label = "Post Compression Bytes - Other"; $bDisplay = (($subset -eq "all") -or ($subset -eq "compression")) } "STATISTIC_HTTP_RAM_CACHE_HITS" { $label = "RAM Cache - Hits"; $bDisplay = (($subset -eq "all") -or ($subset -eq "cache")) } "STATISTIC_HTTP_RAM_CACHE_MISSES" { $label = "RAM Cache - Misses"; $bDisplay = (($subset -eq "all") -or ($subset -eq "cache")) } "STATISTIC_HTTP_RAM_CACHE_TOTAL_MISSES" { $label = "RAM Cache - Total Misses"; $bDisplay = (($subset -eq "all") -or ($subset -eq "cache")) } "STATISTIC_HTTP_RAM_CACHE_HIT_BYTES" { $label = "RAM Cache - Hit Bytes"; $bDisplay = (($subset -eq "all") -or ($subset -eq "cache")) } "STATISTIC_HTTP_RAM_CACHE_MISS_BYTES" { $label = "RAM Cache - Miss Bytes"; $bDisplay = (($subset -eq "all") -or ($subset -eq "cache")) } "STATISTIC_HTTP_RAM_CACHE_TOTAL_MISS_BYTES" { $label = "RAM Cache - Total Miss Bytes"; $bDisplay = (($subset -eq "all") -or ($subset -eq "cache")) } "STATISTIC_HTTP_RAM_CACHE_SIZE" { $label = "RAM Cache - Size"; $bDisplay = (($subset -eq "all") -or ($subset -eq "cache")) } "STATISTIC_HTTP_RAM_CACHE_COUNT" { $label = "RAM Cache - Count"; $bDisplay = (($subset -eq "all") -or ($subset -eq "cache")) } "STATISTIC_HTTP_RAM_CACHE_EVICTIONS" { $label = "RAM Cache - Evictions"; $bDisplay = (($subset -eq "all") -or ($subset -eq "cache")) } default { $label = $Statistic.type $bDisplay = 1; } } if ( $bDisplay -ne 0 ) { $stat_obj | Add-Member -Type noteProperty -Name $label $val; } } $stat_obj | Format-List } Utility functions Several utility functions are included as well that will do the 64 bit math conversion as well as converting the iControl TimeStamp structure to a .Net DateTime. See the full example in the iControl CodeShare for the content of the Invoke-Inline function. function Convert-To64Bit() { param($high, $low); return Invoke-Inline "result.Add((Convert.ToUInt64($high)<<32) | (Convert.ToUInt64($low)));" } function Get-TimeFromTimeStamp() { param ($TimeStamp); $dt = new-object -typename System.DateTime $dt = $dt.AddYears($TimeStamp.year-1).AddMonths($TimeStamp.month-1).AddDays($TimeStamp.day-1).AddHours($TimeStamp.hour).AddMinutes($TimeStamp.minute).AddSeconds($TimeStamp.second); return $dt; } Running the Application The following example shows my somewhat idle BIG-IP's statistics. While these are not very exciting, the numbers you are likely to see will be more interesting depending on the activity of your HTTP applicationsand whether you are using compression and/or caching. PS C:\> .\GlobalHttpStats.ps1 bigip_address username password Global HTTP Statistics Time Stamp : 8/6/2008 12:57:01 PM -------------------------------- : Cookie Persist Header Inserts : 0 Responses - 2xx : 80000 Responses - 3xx : 0 Responses - 4xx : 0 Responses - 5xx : 0 Requests - Total : 80000 Requests - HTTP GET : 80000 Requests - HTTP POST : 0 Requests - v0.9 : 0 Requests - v1.0 : 80000 Requests - v1.1 : 0 Responses - v0.9 : 0 Responses - v1.0 : 0 Responses - v1.1 : 80000 Requests - Maximum Keepalive : 1 Responses - under 1k : 0 Responses - From 1-4k : 80000 Responses - From 4-16k : 0 Responses - From 16-32k : 0 Responses - From 32-64k : 0 Pre Compression Bytes : 0 Post Compression Bytes : 0 Compression Bytes - Null : 0 Pre Compression Bytes - HTML : 0 Post Compression Bytes - HTML : 0 Pre Compression Bytes - CSS : 0 Post Compression Bytes - CSS : 0 Pre Compression Bytes - JavaScript : 0 Post Compression Bytes - JavaScript : 0 Pre Compression Bytes - XML : 0 Post Compression Bytes - XML : 0 Pre Compression Bytes - SGML : 0 Post Compression Bytes - SGML : 0 Pre Compression Bytes - Plain : 0 Post Compression Bytes - Plain : 0 Pre Compression Bytes - Octet : 0 Post Compression Bytes - Octet : 0 Pre Compression Bytes - Image : 0 Post Compression Bytes - Image : 0 Pre Compression Bytes - Video : 0 Post Compression Bytes - Video : 0 Pre Compression Bytes - Audio : 0 Post Compression Bytes - Audio : 0 Pre Compression Bytes - Other : 0 Post Compression Bytes - Other : 0 RAM Cache - Hits : 0 RAM Cache - Misses : 0 RAM Cache - Total Misses : 0 RAM Cache - Hit Bytes : 0 RAM Cache - Miss Bytes : 0 RAM Cache - Total Miss Bytes : 0 RAM Cache - Size : 0 RAM Cache - Count : 0 RAM Cache - Evictions : 0 Conclusion This article illustrated how to pull out HTTP statistics that you probably didn't know were available to you. There are several other interesting statistics available in the System.Statistics interface that I'll illustrate in future articles so be on the look out! The entire script can be found in the iControl CodeShare under PsSystemHttpStats. Get the Flash Player to see this player. 20080807-iControlApps_7_SystemHttpStats.mp3424Views0likes1CommentiControl Apps - #06 - Configuration Archiving
Let's face it, we like to believe that disaster will never happen, but how many times have you accidentally deleted a document you have been working on for a presentation without a working backup? Odds are more than once. If it can happen on your desktop, then it can happen on the network and imagine the situation where your network configuration is running the bread and butter for your company. Wouldn't it be nice to have the knowledge that your network configuration is securely archived for easy recovery? Well, this article will illustrate an application that performs configuration archiving for a BIG-IP device by allowing for the creation of configuration restore points as well as remote file upload/download capabilities. Read this article, and fear no more! Usage The arguments for this application are the address, username, and password of the BIG-IP along with optional argument for the operation to be performed along with an optional configuration file name. This is declared in the top of the script with the following param statement. There is also a Write-Usage function to display the arguments to the user. param ( $g_bigip = $null, $g_uid = $null, $g_pwd = $null, $g_cmd = $null, $g_name = $null ); $DEFAULT_CHUNK_SIZE = (64*1024); Set-PSDebug -strict; #------------------------------------------------------------------------- # function Write-Usage #------------------------------------------------------------------------- function Write-Usage() { Write-Host "Usage: ConfigArchive.ps1 host uid pwd [list|save|delete|download|install|rollback|upload [name]]"; exit; } Initialization As is with all of my PowerShell scripts, the initialization component will look to see if the iControlSnapIn is loaded into the current PowerShell session. If not, the Add-PSSnapIn Cmdlet is called to add the snapin into the runtime. Then a call to the Initialize-F5.iControl cmdlet to setup the connection to the BIG-IP. If this succeeds, then the rest of the optional arguments are processed. The application will then look for the "list", "save", "delete", "download", "install", "rollback", or "upload" commands. For those the associated functions will be called, otherwise the usage message will be presented to the user. function Do-Initialize() { if ( (Get-PSSnapin | Where-Object { $_.Name -eq "iControlSnapIn"}) -eq $null ) { Add-PSSnapIn iControlSnapIn } $success = Initialize-F5.iControl -HostName $g_bigip -Username $g_uid -Password $g_pwd; return $success; } if ( ($g_bigip -eq $null) -or ($g_uid -eq $null) -or ($g_pwd -eq $null) -or ($g_cmd -eq $null) ) { Write-Usage; } if ( Do-Initialize ) { switch ($g_cmd.ToLower()) { "list" { Get-ConfigList; } "save" { Save-Configuration $g_name; } "delete" { Delete-Configuration $g_name; } "download" { Download-Configuration $g_name; } "install" { Install-Configuration $g_name; } "rollback" { Rollback-Configuration; } "upload" { Upload-Configuration $g_name; } default { Write-Usage; } } } else { Write-Error "ERROR: iControl subsystem not initialized" } Querying the list of configuration files In case you didn't know what the existing configurations are on your BIG-IP, this function will query that list and write the configuration files along with their associated timestamp. The System.ConfigSync.get_configuration_list() iControl method returns a list of ConfigFileEntry structures containing these values and they are then output to the console with the Write-Host cmdlet. function Get-ConfigList() { $ConfigFileEntryList = (Get-F5.iControl).SystemConfigSync.get_configuration_list(); Write-Host "Available Configuration Files"; Write-Host "-----------------------------"; foreach ($ConfigFileEntry in $ConfigFileEntryList) { $file_name = $ConfigFileEntry.file_name; $file_datetime = $ConfigFileEntry.file_datetime; Write-Host "-> $file_name ($file_datetime)"; } } Saving the Configuration Ok, it's time to archive. At this point, the first step you'll want to take is to save the current configuration. the Save-Configuration function will use the iControl System.ConfigSync.save_configuration() method to save the entire configuration to a configuration archive (.ucs file) on the BIG-IP with the specified name. If a name is not given, then the included Generate-ConfigName function will do it based on the current BIG-IP hostname appended with the date and time. function Save-Configuration() { param($config_name = $null); if ( $config_name -eq $null ) { $config_name = Generate-ConfigName; } Write-Host "Saving Configuration to file $config_name" (Get-F5.iControl).SystemConfigSync.save_configuration($config_name, "SAVE_FULL"); Get-ConfigList; } function Generate-ConfigName() { $now = [DateTime]::Now; $year = $now.year; $month = $now.month; if ($month -lt 10) { $month = "0${month}" } $day = $now.day; if ( $day -lt 10 ) { $day = "0{$day}" } $hour = $now.hour; if ( $hour -lt 10 ) { $hour = "0{$hour}" } $minute = $now.minute; if ( $minute -lt 10 ) { $minute = "0{$minute}" } $second = $now.second; if ( $second -lt 10 ) { $second = "0{$second}" } $config_name = "${g_bigip}-${year}${month}${day}-${hour}${minute}${second}.ucs" return $config_name; } Deleting a configuration Now, you may not wish to keep all the configurations on your BIG-IP since the whole idea of archiving is getting them off the device and onto a secured resiliant storage of some sort. Thus the Delete-Configuration function will call the iControl System.ConfigSync.delete_configuration() method with the supplied configuration name and will delete that file from the filesystem on the BIG-IP. function Delete-Configuration() { param($config_name); if ( $config_name -eq $null ) { Write-Usage; } else { Write-Host "Deleting configuration $config_name"; (Get-F5.iControl).SystemConfigSync.delete_configuration($config_name); Get-ConfigList; } } Downloading a Configuration Since archiving is the main goal of this application, it wouldn't be much use without the ability to download a configuration after you've saved it on the BIG-IP. The Download-Configuration function takes as input a configuration name, and uses the iControl System.ConfigSync.download_configuration() method to download the file. Now, configurations can be quite large at times so that is why we've implemented the download_configuration, as well as the more generic download_file, methods to operate in a chunking mode. The use is illustrated in the Download-Configuration function. I'm using the native .Net FileStream and BinaryWriter classes to handle output to local disk. I tried a pure native Set-Content and Add-Content implementation but the performance was much slower than accessing the .Net File IO classed directly. Essentially this function sits in a loop requesting $chunk_size (64k) bytes of data from the file at the given file_offset. The file_offset is incremented on the server for the next request. Then the downloaded data is written to the BinaryWriter class to append the data to the local file in the current working directory with the same name as the downloaded configuration. When the chain_type of FILE_LAST, or FILE_FIRST_AND_LAST is returned, signifying the end of the data, the loop is exited and the local files are closed. function Download-Configuration() { param($config_name); Write-Host "Downloading Configuration file $config_name" $loc = Get-Location $local_file = "$loc\$config_name"; $ctx = New-Object -TypeName iControl.SystemConfigSyncFileTransferContext; $chunk_size = $DEFAULT_CHUNK_SIZE; $file_offset = 0; $bContinue = 1; $mode = [System.IO.FileMode]::CreateNew; if ( Test-Path $local_file ) { $mode = [System.IO.FileMode]::Truncate; } $fs = New-Object -TypeName System.IO.FileStream -argumentList ($local_file, $mode); $w = New-Object -TypeName System.IO.BinaryWriter -argumentList ($fs); while($bContinue -eq 1) { $ctx = (Get-F5.iControl).SystemConfigSync.download_configuration($config_name, $chunk_size, [ref]$file_offset); $w.Write($ctx.file_data, 0, $ctx.file_data.Length); Write-Host "Bytes Transferred: $file_offset"; if ( ($ctx.chain_type -eq "FILE_LAST") -or ($ctx.chain_type -eq "FILE_FIRST_AND_LAST") ) { $bContinue = 0; } } $w.Close() $fs.Close() } Uploading an Archived Configuration Now that you find yourself in the situation of wanting to restore a configuration from your local archive store, you'll first need to upload the configuration to the BIG-IP. The Upload-Configuration function basically does the opposite as the Download-Configuration function. It sits in a loop reading $chunk_size bytes from a BinaryReader object based on the locale file requested. As long as there are more bytes to read, they are passed up to the BIG-IP to be appended to the specified configuration file with the iControl System.ConfigSync.upload_configuration() method. Once all of the bytes have been uploaded, the local file handles are closed and the function is exited. function Upload-Configuration() { param($config_name); $loc = Get-Location $local_file = "$loc\$config_name"; Write-Host "Uploading file $local_file"; $ctx = New-Object -TypeName iControl.SystemConfigSyncFileTransferContext; $bContinue = 1; $ctx.chain_type = "FILE_FIRST"; $chunk_size = $DEFAULT_CHUNK_SIZE; $total_bytes = 0; $fs = New-Object -TypeName System.IO.FileStream -argumentList ($local_file, [System.IO.FileMode]::Open); $r = New-Object -TypeName System.IO.BinaryReader -argumentList ($fs) while ($bContinue -eq 1) { $ctx.file_data = $r.ReadBytes($chunk_size); if ( $ctx.file_data.Length -ne $chunk_size ) { # At the end, check to see if it is the first request if ( $total_bytes -eq 0 ) { $ctx.chain_type = "FILE_FIRST_AND_LAST"; } else { $ctx.chain_type = "FILE_LAST"; } $bContinue = 0; } $total_bytes = $total_bytes + $ctx.file_data.Length; # Upload bytes (Get-F5.iControl).SystemConfigSync.upload_configuration($config_name, $ctx); # move to middle $ctx.chain_type = "FILE_MIDDLE"; Write-Host "Uploaded $total_bytes bytes"; } $r.Close(); $fs.Close(); } Installing a Configuration Once you have a configuration uploaded, you'll want to install that configuration into the running configuration system. This is done with the Install-Configuration function that in turn calls the iControl System.ConfigSync.install_configuration() method with the specified configuration name. When the installation of the configuration is complete, a message will be written to the console indicating a success. function Install-Configuration() { param($config_name); if ( $config_name -eq $null ) { Write-Usage; } else { Write-Host "Installing Configuration $config_name"; (Get-F5.iControl).SystemConfigSync.install_configuration($config_name); Write-Host "Configuration successfully installed."; } } Rolling Back to the Last Configuration Now, let's say you accidentally installed the wrong configuration and want to undo your operation? Never fear, the Rollback-Configuration function calls the iControl System.ConfigSync.rollback_configuration() method that will roll back the running configuration to the previous settings before the last "load" operation. function Rollback-Configuration() { Write-Host "Rolling back configuration" (Get-F5.iControl).SystemConfigSync.rollback_configuration(); Write-Host "Configuration Rollback successful" } Conclusion So, next time you find yourself in the situation where you have a hard drive malfunction, or even something as rare as an operator accidentally deleting configuration objects (which never happens - right???), if you were smart enough to implement a solution illustrated by this application, you'll just be a few clicks away from a full restore of your configuration and your job will be secure for a little while longer. The entire script can be found in the iControl CodeShare under PsConfigArchiving. Get the Flash Player to see this player. 20080731-iControlApps_6_ConfigArchiving.mp3645Views0likes1CommentiControl Apps - #01 - Disabling Node Servers
In version 4.x of BIG-IP, there was a concept of a Node Server (a top level object that consisted of a node's address and port). Basically it was a top level rollup of all instances of a specific node address and port across all pools of servers. In version 9 of BIG-IP, the top level Node object is a Node Address (minus the port). You can still enable/disable a node address across all pool instances, it is not as simple to enable/disable all occurances of a node address AND port across all pools. This article will discuss how to write an iControl implementation that will allow you to relive some of those good memories from 4.x time. Introduction This article will make use of the SOAP::Lite perl library for the iControl client toolkit. See the perl getting started guide for details on setting up your environment for using this script (or just drop it on your BIG-IP and it will work there). Now that you've got the configuration to run this script, then we can move on to the logic behind it. But first, we need to get rid of some of the busy work including the SOAP::Lite initialization code and parameter input. This script is going to take 5 parameters with 2 being optional. The first 3 parameters are the address, username, and password for the BIG-IP. The optional parameters are a node_port combination in ip:port format and the state of enable or disable to set the node server to. You'll see why these are optional later. NodeServer.pl host uid pwd [[node_port] [enable|disable]] So we'll need to parse those parameters and setup the SOAP::Lite objects for the Pool and PoolMember interfaces. This is accomplished with the code below. #!/usr/bin/perl #---------------------------------------------------------------------------- # The contents of this file are subject to the "END USER LICENSE AGREEMENT FOR F5 # Software Development Kit for iControl"; you may not use this file except in # compliance with the License. The License is included in the iControl # Software Development Kit. # # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and limitations # under the License. # # The Original Code is iControl Code and related documentation # distributed by F5. # # The Initial Developer of the Original Code is F5 Networks, # Inc. Seattle, WA, USA. Portions created by F5 are Copyright (C) 1996-2004 F5 Networks, # Inc. All Rights Reserved. iControl (TM) is a registered trademark of F5 Networks, Inc. # # Alternatively, the contents of this file may be used under the terms # of the GNU General Public License (the "GPL"), in which case the # provisions of GPL are applicable instead of those above. If you wish # to allow use of your version of this file only under the terms of the # GPL and not to allow others to use your version of this file under the # License, indicate your decision by deleting the provisions above and # replace them with the notice and other provisions required by the GPL. # If you do not delete the provisions above, a recipient may use your # version of this file under either the License or the GPL. #---------------------------------------------------------------------------- #use SOAP::Lite + trace => qw(method debug); use SOAP::Lite; use MIME::Base64; #---------------------------------------------------------------------------- # Validate Arguments #---------------------------------------------------------------------------- my $sHost = $ARGV[0]; my $sPort = $ARGV[1]; my $sUID = $ARGV[2]; my $sPWD = $ARGV[3]; my $sNodePort = $ARGV[4]; my $sEnable = $ARGV[5]; my $sProtocol = "https"; my $sPort = 443; if ( ("80" eq $sPort) or ("8080" eq $sPort) ) { $sProtocol = "http"; } if ( ($sHost eq "") or ($sUID eq "") or ($sPWD eq "") ) { die ("Usage: NodeServer.pl host uid pwd [[node_port] [enable|disable]]\n"); } #---------------------------------------------------------------------------- # support for custom enum types #---------------------------------------------------------------------------- sub SOAP::Deserializer::typecast { my ($self, $value, $name, $attrs, $children, $type) = @_; my $retval = undef; if ( "{urn:iControl}LocalLB.AvailabilityStatus" == $type ) { $retval = $value; } return $retval; } #---------------------------------------------------------------------------- # Transport Information #---------------------------------------------------------------------------- sub SOAP::Transport::HTTP::Client::get_basic_credentials { return "$sUID" => "$sPWD"; } $Pool = SOAP::Lite -> uri('urn:iControl:LocalLB/Pool') -> proxy("$sProtocol://$sHost:$sPort/iControl/iControlPortal.cgi"); eval { $Pool->transport->http_request->header ( 'Authorization' => 'Basic ' . MIME::Base64::encode("$sUID:$sPWD", '') ); }; $PoolMember = SOAP::Lite -> uri('urn:iControl:LocalLB/PoolMember') -> proxy("$sProtocol://$sHost:$sPort/iControl/iControlPortal.cgi"); eval { $PoolMember->transport->http_request->header ( 'Authorization' => 'Basic ' . MIME::Base64::encode("$sUID:$sPWD", '') ); }; Now we can move on to the program logic. The problem Since there is no top level node server object to set state on at a global level, you will have to loop through all the pools and it's pool members to look for matches. Once you've found the pools that a node server is a member of (a pool member), then you can use the PoolMember.set_monitor_state() method to mark all instances of that node server across all the pools it belongs to. But, your first question might be what node servers are defined on the system. No good bit of code would be complete without the ability to verify your changes, so I've made the node_port and state parameters optional to allow for querying values. The main application logic looks like this #---------------------------------------------------------------------------- # main app logic #---------------------------------------------------------------------------- if ( ($sNodePort ne "") && ($sEnable ne "") ) { &SetNodeServer($sNodePort, $sEnable) } else { &ListPoolsAndMembers($sNodePort) } Listing Node Servers For listing out all node servers, you will have to loop over all of the pools and query the pool members. I've opted to use the get_object_status() in the PoolMember interface as it not only returns the pool members but also the objects status (enabled and availability states). This also came in useful in that the listing can also include the state to verify your future state changes. This routine takes an optional node_addr_port member so if you don't specify the node_port on the command line, it will list all of the pools and all pool members. If you do specify a node_port, it will only display the pools that it is a member in. #---------------------------------------------------------------------------- # sub ListPoolsAndMembers #---------------------------------------------------------------------------- sub ListPoolsAndMembers() { my ($node_addr_port) = (@_); my ($node_addr, $node_port) = split(/:/, $node_addr_port, 2); my @pool_list = &getPoolList(); my @object_status_lists = &getObjectStatusLists(@pool_list); # Loop over pools for $i (0 .. $#pool_list) { $bFound = 0; $pool = @pool_list[$i]; if ( "" == $node_addr ) { # if no node given, print out full list print "Pool $pool\n"; foreach $status (@{@object_status_lists[$i]}) { $member = $status->{"member"}; $addr = $member->{"address"}; $port = $member->{"port"}; $ostat = $status->{"object_status"}; $astat = $ostat->{"availability_status"}; $estat = $ostat->{"enabled_status"}; print " $addr:$port ($astat, $estat)\n"; } #print "\n"; } else { # else, only print out where matches are found. foreach $status (@{@object_status_lists[$i]}) { if ( !$bFound ) { $member = $status->{"member"}; $addr = $member->{"address"}; $port = $member->{"port"}; $ostat = $status->{"object_status"}; $astat = $ostat->{"availability_status"}; $estat = $ostat->{"enabled_status"}; if ( ($node_addr eq $addr) && ($node_port eq $port) ) { $bFound = 1; } } } if ( $bFound ) { print "Pool $pool : $node_addr:$node_port ($astat, $estat)\n"; } } } } Setting Node Server State Now, listing the objects fun, but the issue here is setting the state across all of the pools. First, we'll need a method that takes as input a node address/port pair. It will then loop through all the pool members and build a list of pools where that node server is defined in. It will finally return the pool list to the calling routine. This calling routine is the one that set's the state with the PoolMember.set_monitor_state() method effectively marking the pool member down through the monitoring system. The set_monitor_state() method takes an array of pools, along with a 2-d array of MemberMonitorState structures, an array for each pool in the first parameter. #---------------------------------------------------------------------------- # sub findPoolsFromMember #---------------------------------------------------------------------------- sub findPoolsFromMember() { my ($node_addr_port) = (@_); my ($node_addr, $node_port) = split(/:/, $node_addr_port, 2); my @pool_match_list; my @pool_list = &getPoolList(); my @member_lists = &getMemberLists(@pool_list); for $i (0 .. $#pool_list) { $pool = @pool_list[$i]; foreach $member (@{@member_lists[$i]}) { $addr = $member->{"address"}; $port = $member->{"port"}; if ( ($node_addr eq $addr) && ($node_port eq $port) ) { push @pool_match_list, $pool; } } } return @pool_match_list; } #---------------------------------------------------------------------------- # sub setNodeServer #---------------------------------------------------------------------------- sub SetNodeServer() { my ($node_addr_port, $state) = (@_); my ($node_addr, $node_port) = split(/:/, $node_addr_port, 2); my @pool_list = &findPoolsFromMember($node_addr_port); my $member = { address => $node_addr, port => $node_port }; my $ENABLED_STATE = "STATE_ENABLED"; if ( $state eq "disable" ) { $ENABLED_STATE = "STATE_DISABLED"; } my $MemberMonitorState = { member => $member, monitor_state => $ENABLED_STATE }; my @MemberMonitorStateList; push @MemberMonitorStateList, $MemberMonitorState; my @MemberMonitorStateLists; for $i (0 .. $#pool_list) { push @MemberMonitorStateLists, [@MemberMonitorStateList]; } # Make call to set_monitor_state $soapResponse = $PoolMember->set_monitor_state( SOAP::Data->name(pool_names => [@pool_list]), SOAP::Data->name(monitor_states => [@MemberMonitorStateLists]) ); &checkResponse($soapResponse); print "Node Server $node_addr_port set to $ENABLED_STATE in pools: "; foreach $pool (@pool_list) { print "$pool, "; } print "\n"; } Helper Routines You may have noticed some routines in the listing and state sections of code. I'm all about conserving space, so I've consolidated some common code into helper routines. Things like retrieving lists of pools, pool members, object status, and validating SOAP responses are included below: #---------------------------------------------------------------------------- # sub getPoolList #---------------------------------------------------------------------------- sub getPoolList() { # Get the list of pools $soapResponse = $Pool->get_list(); &checkResponse($soapResponse); my @pool_list = @{$soapResponse->result}; return @pool_list; } #---------------------------------------------------------------------------- # sub getMemberLists #---------------------------------------------------------------------------- sub getMemberLists() { my (@pool_list) = (@_); # Get the list of pool members for all the pools $soapResponse = $Pool->get_member ( SOAP::Data->name(pool_names => [@pool_list]) ); &checkResponse($soapResponse); @member_lists = @{$soapResponse->result}; return @member_lists; } #---------------------------------------------------------------------------- # sub getObjectStatus #---------------------------------------------------------------------------- sub getObjectStatusLists() { my (@pool_list) = (@_); # Get the list of pool members for all the pools $soapResponse = $PoolMember->get_object_status ( SOAP::Data->name(pool_names => [@pool_list]) ); &checkResponse($soapResponse); @object_status_lists = @{$soapResponse->result}; return @object_status_lists; } #---------------------------------------------------------------------------- # checkResponse #---------------------------------------------------------------------------- sub checkResponse() { my ($soapResponse) = (@_); if ( $soapResponse->fault ) { print $soapResponse->faultcode, " ", $soapResponse->faultstring, "\n"; exit(); } } Sample Usage PS > .\NodeServer.pl bigip username password Pool pool_1 10.10.10.149:80 (AVAILABILITY_STATUS_GREEN, ENABLED_STATUS_ENABLED) 20.20.20.101:80 (AVAILABILITY_STATUS_RED, ENABLED_STATUS_ENABLED) Pool pool_2 20.20.20.102:80 (AVAILABILITY_STATUS_RED, ENABLED_STATUS_ENABLED) Pool xpbert-http 10.10.10.149:80 (AVAILABILITY_STATUS_GREEN, ENABLED_STATUS_ENABLED) PS> .\NodeServer.pl bigip username password 10.10.10.149:80 Pool pool_1 : 10.10.10.149:80 (AVAILABILITY_STATUS_GREEN, ENABLED_STATUS_ENABLED) Pool xpbert-http : 10.10.10.149:80 (AVAILABILITY_STATUS_GREEN, ENABLED_STATUS_ENABLED) PS> .\NodeServer.pl bigip username password 10.10.10.149:80 disable Node Server 10.10.10.149:80 set to STATE_DISABLED in pools: pool_1, xpbert-http, PS> .\NodeServer.pl bigip username password 10.10.10.149:80 Pool pool_1 : 10.10.10.149:80 (AVAILABILITY_STATUS_RED, ENABLED_STATUS_ENABLED) Pool xpbert-http : 10.10.10.149:80 (AVAILABILITY_STATUS_RED, ENABLED_STATUS_ENABLED) PS> .\NodeServer.pl bigip username password 10.10.10.149:80 enable Node Server 10.10.10.149:80 set to STATE_ENABLED in pools: pool_1, xpbert-http, PS> .\NodeServer.pl bigip username password 10.10.10.149:80 Pool pool_1 : 10.10.10.149:80 (AVAILABILITY_STATUS_GREEN, ENABLED_STATUS_ENABLED) Pool xpbert-http : 10.10.10.149:80 (AVAILABILITY_STATUS_GREEN, ENABLED_STATUS_ENABLED) The one question you may have is what's up with the AVAILABILITY_STATUS and what does GREEN mean versus RED. It's all defined in the iControl API for that method but, in short, GREEN means UP, RED means DOWN. Conclusion This sample application was written in Perl but the logic behind it could easily be implemented in Java, .Net, PowerShell, or whatever language your heart desires. With this little bit of code, you can relive your 4.x days and enable/disable node servers to your hearts content. The entire script is located in the iControl Codeshare under DisablingNodeServers Get the Flash Player to see this player. 20080515-iControlApps_1_DisablingNodeServers.mp31.3KViews0likes6CommentsiControl Apps #22 - System Software Status
Through the iControl interfaces, we've exposed many of the features on the BIG-IP. In this example, I'll illustrate the new System SoftwareStatus method in version BIG-IP version 10.1. This information can be found on the "System.Software Management.Image List" menu item on the BIG-IP management GUI. From this information you can determine the installed images on the BIG-IP as well as their active state and status of live software installs. Usage The arguments for this application are the bigip address and a username/hostname for the connection. param ( $bigip = $null, $uid = $null, $pwd = $null ) function Show-Usage() { Write-Host "Usage: SystemSoftwareStatus.ps1 host uid pwd"; exit; } Initialization The initialization component of this script will check for the required parameters, attempt to load the iControl Snapin into the current PowerShell runspace, and then call the local Get-SystemSWStatus function that queries the system software status information. function Do-Initialize() { if ( (Get-PSSnapin | Where-Object { $_.Name -eq "iControlSnapIn"}) -eq $null ) { Add-PSSnapIn iControlSnapIn } $success = Initialize-F5.iControl -HostName $bigip -Username $uid -Password $pwd; return $success; } if ( ($bigip -eq $null) -or ($uid -eq $null) -or ($pwd -eq $null) ) { Show-Usage; } if ( Do-Initialize ) { Get-SystemSWStatus } else { Write-Error "ERROR: iControl subsystem not initialized" } Querying Software Status The heart of this script lies around the get_all_software_status() method in the System.SoftwareManagement interface. There is also a get_software_status() method that allows you to pass in a list of installation IDs, but we'll make it simple by querying the entire list. The method returns a structure containing the installation id (chassis slot id and install volume) along with the product, version, build base build, active status, edition, and live install status. Member Description installation_id The blade location and hard drive slot the installation is targeted for. product The installed product (ie. BIG-IP) version The version of the product (ie. 10.1.0) build The build number installed (ie. 3327.0) base_build The base build (used for hotfixes) active Whether the boot location is active. edition The edition for the given software install (used for hotfixes). status The status of the live install. (ie. "none", "audited", "retry", "upgrade needed", "waiting for image", installing nn.mmm pct", "complete", "cancelling", "cancelled", or "failed" function Get-SystemSWStatus() { $SoftwareStatusA = (Get-F5.iControl).SystemSoftwareManagement.get_all_software_status(); "===============================================" " SYSTEM SOFTWARE STATUS "; "-----------------------------------------------" "{0,10} : {1}" -f "Host", $bigip; foreach ($SoftwareStatus in $SoftwareStatusA) { "-----------------------------------------------" $installation_id = $SoftwareStatus.installation_id; Write-Entry "Chassis Slot Id" $installation_id.chassis_slot_id; Write-Entry "Install Volume" $installation_id.install_volume; Write-Entry "Product" $SoftwareStatus.product; Write-Entry "Version" $SoftwareStatus.version; Write-Entry "Build" $SoftwareStatus.build; Write-Entry "Base Build" $SoftwareStatus.base_build; Write-Entry "Active" $SoftwareStatus.active; Write-Entry "Edition" $SoftwareStatus.edition; Write-Entry "Status" $SoftwareStatus.status; "-----------------------------------------------" } "===============================================" } Utilitiy Functions To help with formatting, I included the Write-Entry utility function to help space the output into columns. function Write-Entry() { param([string]$name, [string]$value, [int]$width = 15); if ( $name.Length -gt 0 ) { $fmt = "{0,$width}"; if ( $value.Length -gt 0 ) { $fmt += " : {1}"; } } $fmt -f $name, $value; } Usage text PSC:\> .\SystemSoftwareStatus.ps1 bigip user pass =============================================== SYSTEM SOFTWARE STATUS ----------------------------------------------- Host : theboss ----------------------------------------------- Chassis Slot Id : 0 Install Volume : HD1.2 Product : BIG-IP Version : 10.1.0 Build : 3341.0 Base Build : 3341.0 Active : True Edition Status : complete ----------------------------------------------- ----------------------------------------------- Chassis Slot Id : 0 Install Volume : HD1.1 Product : BIG-IP Version : 10.1.0 Build : 3327.0 Base Build : 3327.0 Active : False Edition Status : complete ----------------------------------------------- ----------------------------------------------- Chassis Slot Id : 0 Install Volume : CF1.1 Product : BIG-IP Version : 9.2.0 Build : 119.0 Base Build Active : False Edition Status : complete ----------------------------------------------- =============================================== You'll see for my system, Ihave three installations. The active installation is running on install volumn HD1.2 with version 10.1.0, version 3341.0. I've also got an older version of 10.1 on installation IDHD1.1 and an ancient v 9.2 on my CF1.1 volume. You can see the full script under PowerShellSoftwareStatus in the iControl wiki.521Views0likes0CommentsiControl Apps - #05 - Rate Based Statistics
One of the key features of iControl is the ability to monitor statistics of the various objects on the system. Whether it's for capacity planning or auditing and billing purposes, the numbers behind how your applications are being utilized on the network are very important. The iControl API allows for querying of statistics for most of the major objects including Virtual Servers, Pools, Pool Members, and Node Addresses. This data is returned as counters but most often the desired presentation of this data is in the form of a rate such as bits or connections per second. This article will show how to query the counter values and calculate rate based statistics for a given virtual server. Usage The arguments for this application are the address, username, and password of the BIG-IP along with optional argument for the specified virtual server. This is declared in the top of the script with the following param statement. There is also a Write-Usage function to display the arguments to the user. param ( $g_bigip = $null, $g_uid = $null, $g_pwd = $null, $g_virtual = $null ); $g_gmt_offset = ""; Set-PSDebug -strict; function Write-Usage() { Write-Host "Usage: VirtualRates.ps1 host uid pwd [virtual_server]"; exit; } Initialization As is with all of my PowerShell scripts, the initialization component will look to see if the iControlSnapIn is loaded into the current PowerShell session. If not, the Add-PSSnapIn Cmdlet is called to add the snapin into the runtime. Then a call to the Initialize-F5.iControl cmdlet to setup the connection to the BIG-IP. If this succeeds then success, then the rest of the optional arguments are processed. If a virtual server name is not specifed, the Get-Virtuals function is called to list out all available virtual servers on the BIG-IP. If a virtual server name is specified, the Get-VirtualRates function is called which continuously polls the BIG-IP in one second intervals. During each interval it extracts the total bits, packets, and connections and then calculates rates based off of those numbers. The code for this process is listed below. function Do-Initialize() { if ( (Get-PSSnapin | Where-Object { $_.Name -eq "iControlSnapIn"}) -eq $null ) { Add-PSSnapIn iControlSnapIn } $success = Initialize-F5.iControl -HostName $g_bigip -Username $g_uid -Password $g_pwd; return $success; } if ( ($g_bigip -eq $null) -or ($g_uid -eq $null) -or ($g_pwd -eq $null) ) { Write-Usage; } if ( Do-Initialize ) { if ( $g_virtual -eq $null ) { Get-Virtuals; } else { Get-VirtualRates $g_virtual; } } else { Write-Error "ERROR: iControl subsystem not initialized" } Listing the Virtuals The Get-Virtuals function will call the LocalLB.VirtualServer.get_list() method to query the list of virtual servers on the BIG-IP. It will then loop over that list and write the results to the console. function Get-Virtuals() { $vs_list = (Get-F5.iControl).LocalLBVirtualServer.get_list(); Write-Host "Available Virtual Servers:"; foreach ($vs in $vs_list) { Write-Host " $vs"; } } Reporting Rates The Get-VirtualRates function does most of the grunt work in this application. It takes as a parameter a virtual server name and then enters a continuous loop. In this loop, the LocalLB.VirtualServer.get_statistics() method is called. The Common.TimeStamp structure from the result of that call is then converted to a .Net DateTime with the local Get-TimeFromTimeStamp function. After the time conversion, the returned statistics for the specified virtual server and looks for the STATISTIC_CLIENT_SIDE_BYTES_IN, STATISTIC_CLIENT_SIDE_PACKETS_IN, and STATISTIC_CLIENT_SIDE_TOTAL_CONNECTIONS to use for calculations. If this is the first time through the loop, there is no reference for a rate, so the values returned are stored in the b0, p0, and c0 variables respectively. Note the Convert-To64Bit function used here. We'll talk about that later but it basically takes the Common.UInt64 structure's high and low 32 bit values and turns it into a 64 bit number. For all subsequent trips through the loop, a difference is calculated between the new and old counter values and these are stored in the b2, p2, and c2 variables. The difference between the first and current poll is calculated by subtracting the t1 and t0 variables and this results in a .Net TimeSpan object that we extract the total seconds from and store that value in the sec variable. For sanity, if the time difference rounds to zero, we'll set it to one to avoid a divide by zero error. At this point another sanity check is performed to make sure that the difference in counters is not negative. If it's negative, meaning that the last value is smaller than the first, that would mean that either the statistics have rolled over the 2^64 boundary or that they were manually reset. If this is the case, then we'll throw this poll away and treat it as if the polling started over again. Finally if we have a valid poll, the rates are calculated by total number over the interval by the time difference resulting in a X/sec rate. The values for bits per second, packets per second, and connections per second are then presented to the console. Function Get-VirtualRates() { param($virtual_server); $bFirst = 1; $b0 = 0; $p0 = 0; $c0 = 0; $t0 = [DateTime]::Now; Write-Host "Rates for Virtual Server: $virtual_server" while (1) { $VirtualServerStatistics = (Get-F5.iControl).LocalLBVirtualServer.get_statistics( (, $virtual_server) ); $t1 = Get-TimeFromTimeStamp $VirtualServerStatistics.time_stamp; $VirtualServerStatisticEntry = $VirtualServerStatistics.statistics[0]; $Statistics = $VirtualServerStatisticEntry.statistics; foreach ($Statistic in $Statistics) { switch ($Statistic.type) { "STATISTIC_CLIENT_SIDE_BYTES_IN" { $b1 = Convert-To64Bit $Statistic.value.high $Statistic.value.low; } "STATISTIC_CLIENT_SIDE_PACKETS_IN" { $p1 = Convert-To64Bit $Statistic.value.high $Statistic.value.low; } "STATISTIC_CLIENT_SIDE_TOTAL_CONNECTIONS" { $c1 = Convert-To64Bit $Statistic.value.high $Statistic.value.low; } } } if ($bFirst -eq 1 ) { $bFirst = 0; $b0 = $b1; $p0 = $p1; $c0 = $c1; $t0 = $t1; } else { $b2 = $b1 - $b0; $p2 = $p1 - $p0; $c2 = $c1 - $c0; $t2 = $t1 - $t0; $sec = $t2.Seconds; if ( $sec -eq 0 ) { $sec = 1 } if ( ($b2 -lt 0) -or ($p2 -lt 0) -or ($c2 -lt 0) ) { # either the counters increased past 2^64 or they were reset so start over. $bFirst = 1; } else { # Calculate rates $br = $b2/$sec; $pr = $p2/$sec; $cr = $c2/$sec; Write-Host "$br bps; $pr pps; $cr cps" } } Start-Sleep -s 1 } } Converting Time The Get-TimeFromTimeStamp function takes as input an iControl Common.TimeStamp structure and then creates a native .Net TimeStamp structure from those values and returns it to the calling function. function Get-TimeFromTimeStamp() { param ($TimeStamp); $dt = new-object -typename System.DateTime $dt = $dt.AddYears($TimeStamp.year-1) .AddMonths($TimeStamp.month-1) .AddDays($TimeStamp.day-1) .AddHours($TimeStamp.hour) .AddMinutes($TimeStamp.minute) .AddSeconds($TimeStamp.second); return $dt; } Converting to 64 bit The iControl value returned for statistics is the Common.UInt64 structure which has two 32 bit values for the low and high 32 bits of the resulting 64 bit number. Unfortunately, at the time of this article, PowerShell does not support bitwise shifting so we can't create the 64 bit number natively in PowerShell. Never fear! Since PowerShell supports .Net and .Net supports inline compiling and running of .Net code, we can inline compile the C# needed to do the conversion. I found a handy PowerShell script by Lee Holmes/Joel Bennett providing support for inline C# in PowerShell. I converted this script to a powershell function. The Convert-To64Bit function builds the C# 32 bit parts to 64 bit conversion code, calls the Invoke-Inline function with it, and returns the result. function Convert-To64Bit() { param($high, $low); return Invoke-Inline "result.Add((Convert.ToUInt64($high)<<32) | (Convert.ToUInt64($low)));" } Running C# inline in PowerShell As mentioned above, I took the Invoke-Inline powershell script from Lee Holmes/Joel Bennett and converted it into a inline function for this application. I'm not going to go into the details of this function but in short it takes as input some C# code, builds a cached compiled version of it, executes the supplied code, and then returns the results - if there are any. Very cool! function Invoke-Inline() { param( [string[]] $code, [object[]] $arguments, [string[]] $reference = @() ) ## Stores a cache of generated inline objects. If this library is dot-sourced ## from a script, these objects go away when the script exits. if(-not (Test-Path Variable:\inlineCode.Cache)) { ${GLOBAL:inlineCode.Cache} = @{} } $using = $null; $source = $null; if($code.length -eq 1) { $source = $code[0] } elseif($code.Length -eq 2){ $using = $code[0] $source = $code[1] } else { Write-Error "You have to pass some code, or this won't do anything ..." } ## un-nesting magic (why do I need this?) $params = @() foreach($arg in $arguments) { $params += $arg } $arguments = $params ## The main function to execute inline C#. ## Pass the argument to the function as a strongly-typed variable. They will ## be available from C# code as the Object variable, "arg". ## Any values assigned to the "returnValue" object by the C# code will be ## returned to MSH as a return value. ## See if the code has already been compiled and cached $cachedObject = ${inlineCode.Cache}[$source] #Write-Verbose "Type: $($arguments[0].GetType())" ## The code has not been compiled or cached if($cachedObject -eq $null) { $codeToCompile = @" using System; using System.Collections.Generic; $using public class InlineRunner { public List<object> Invoke(Object[] args) { List<object> result = new List<object>(); $source if( result.Count > 0 ) { return result; } else { return null; } } } "@ Write-Verbose $codeToCompile ## Obtains an ICodeCompiler from a CodeDomProvider class. $provider = New-Object Microsoft.CSharp.CSharpCodeProvider ## Get the location for System.Management.Automation DLL $dllName = [PsObject].Assembly.Location ## Configure the compiler parameters $compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters $assemblies = @("System.dll", $dllName) $compilerParameters.ReferencedAssemblies.AddRange($assemblies) $compilerParameters.ReferencedAssemblies.AddRange($reference) $compilerParameters.IncludeDebugInformation = $true $compilerParameters.GenerateInMemory = $true ## Invokes compilation. $compilerResults = $provider.CompileAssemblyFromSource($compilerParameters, $codeToCompile) ## Write any errors if generated. if($compilerResults.Errors.Count -gt 0) { $errorLines = "" foreach($error in $compilerResults.Errors) { $errorLines += "`n`t" + $error.Line + ":`t" + $error.ErrorText } Write-Error $errorLines } ## There were no errors. Store the resulting object in the object ## cache. else { ${inlineCode.Cache}[$source] = $compilerResults.CompiledAssembly.CreateInstance("InlineRunner") } $cachedObject = ${inlineCode.Cache}[$source] } Write-Verbose "Argument $arguments`n`n$cachedObject" ## Finally invoke the C# code if($cachedObject -ne $null) { return $cachedObject.Invoke($arguments) } } Running the program The following command will not supply the virtual server name so a list of available virtual servers will be returned. PS C:\> .\VirtualRates.ps1 theboss admin admin Available Virtual Servers: xpbert-ftp xpbert-http xpbert-ssh xpbert-telnet For this example, I'll monitor the xpbert-http virtual server. For this test, the virtual is initially idle and then I ran apachebench against the virtual with 10000 total/50 concurrent connections to the default page on the webserver. You'll see starting at poll #3 the cps starts increasing and then when the bit counts start kicking in the bits per second (bps) and packets per second (pps) start increasing. When the apachebench process completes, you will see that the rates will start decreasing over time. Since there is no traffic on the virtual it's fairly obvious that since the time is still increasing but the statistic over that interval remains the same, then the rate will decrease. PS C:\> .\VirtualRates.ps1 theboss admin admin xpbert-http Rates for Virtual Server: xpbert-http 0 bps; 0 pps; 0 cps 0 bps; 0 pps; 0 cps 0 bps; 0 pps; 77 cps 0 bps; 0 pps; 232 cps 32398.8 bps; 411.6 pps; 350.8 cps 207088.166666667 bps; 2625.5 pps; 453.5 cps 177504.142857143 bps; 2250.42857142857 pps; 528.285714285714 cps 203525.625 bps; 2585.625 pps; 570.5 cps 216543 bps; 2751 pps; 554.9 cps 246497.363636364 bps; 3131.54545454545 pps; 550 cps 298047.083333333 bps; 3783.75 pps; 554.083333333333 cps 255468.928571429 bps; 3243.21428571429 pps; 545.357142857143 cps 253460 bps; 3220 pps; 567.2 cps 271884.0625 bps; 3454.0625 pps; 593.75 cps 287913.705882353 bps; 3657.70588235294 pps; 588.235294117647 cps 301427.611111111 bps; 3829.38888888889 pps; 555.555555555556 cps 275500 bps; 3500 pps; 500 cps 262380.952380952 bps; 3333.33333333333 pps; 476.190476190476 cps 250454.545454545 bps; 3181.81818181818 pps; 454.545454545455 cps 239565.217391304 bps; 3043.47826086957 pps; 434.782608695652 cps 229583.333333333 bps; 2916.66666666667 pps; 416.666666666667 cps 220400 bps; 2800 pps; 400 cps 211923.076923077 bps; 2692.30769230769 pps; 384.615384615385 cps 204074.074074074 bps; 2592.59259259259 pps; 370.37037037037 cps 196785.714285714 bps; 2500 pps; 357.142857142857 cps 190000 bps; 2413.79310344828 pps; 344.827586206897 cps 183666.666666667 bps; 2333.33333333333 pps; 333.333333333333 cps 177741.935483871 bps; 2258.06451612903 pps; 322.58064516129 cps 172187.5 bps; 2187.5 pps; 312.5 cps 166969.696969697 bps; 2121.21212121212 pps; 303.030303030303 cps Conclusion In this article, I've presented a way to query a virtual server for it's throughput and connection statistics and use that data to calculate rates based on the time interval between polls. This logic can easily be extended to any other object on the BIG-IP such as pools, pool members, or node addresses. For a full version of this script, check out the wiki entry for PsRateBasedStatistics in the iControl CodeShare. Get the Flash Player to see this player. 20080724-iControlApps_5_RateBasedStatistics.mp3638Views0likes3CommentsiControl Apps - #20 - Server Control
The issue of server state continues to come up in the DevCentral iControl forums so I figured I'd write an application that illustrates how to get and set the state of a server object and in doing so show how the iControl API methods map to the three way toggle state of enabled, disabled, forced offline in the BIG-IP admin GUI. Usage The arguments for this application are the BIG-IP address, username and password along with optional arguments for the object identifier and the toggle state. I've created a few hash tables to help with converting from the state parameter's values of "enabled", "disabled", and "offline" to the appropriate monitor and session state values. param ( $bigip = $null, $user = $null, $pass = $null, $object = $null, $state = $null ); Set-PSDebug -strict; $MONITOR_STATE_HASH = @{}; $MONITOR_STATE_HASH.Add("enabled", "STATE_ENABLED"); $MONITOR_STATE_HASH.Add("disabled", "STATE_ENABLED"); $MONITOR_STATE_HASH.Add("offline", "STATE_DISABLED"); $SESSION_STATE_HASH = @{}; $SESSION_STATE_HASH.Add("enabled", "STATE_ENABLED"); $SESSION_STATE_HASH.Add("disabled", "STATE_DISABLED"); $SESSION_STATE_HASH.Add("offline", "STATE_DISABLED"); #------------------------------------------------------------------------- # function Write-Usage #------------------------------------------------------------------------- function Write-Usage() { Write-Host "Usage: ServerControl.ps1 host uid pwd [object [state]]"; Write-Host " object: address (Node) | address:port (Pool Member)"; Write-Host " state: enabled | disabled | offline"; exit; } Initialization The main app logic checks for whether the required BIG-IP connections parameters are supplied. If they are, it performs the initialization to load the iControl PowerShell SnapIn into the current Runspace so that the iControl calls are available to the script. At this point if the object parameter was not supplied, it will call the local Get-ObjectState function with null as an argument. This will return results for all objects on the system. If a object is supplied but a state is not, then the Get-ObjectState function will be called for that object. Finally, if both an object and state are supplied, the script will attempt to set the object's state to the specified value. function Do-Initialize() { if ( (Get-PSSnapin | Where-Object { $_.Name -eq "iControlSnapIn"}) -eq $null ) { Add-PSSnapIn iControlSnapIn } $success = Initialize-F5.iControl -HostName $bigip -Username $user -Password $pass; return $success; } if ( ($bigip -eq $null) -or ($user -eq $null) -or ($pass -eq $null) ) { Write-Usage; } if ( Do-Initialize ) { if ( ! $object ) { # List all Node Addresses and Pool Members Get-ObjectState -objects $null; } elseif ( ! $state ) { # List toggle state of provided object Get-ObjectState -objects (,$object); } else { if ( ($state -eq "enabled") -or ($state -eq "disabled") -or ($state -eq "offline") ) { # Set the specified object's toggle state Set-ObjectState -object $object -state $state; } else { Write-Usage; } } } else { Write-Error "ERROR: iControl subsystem not initialized" } Determining The Toggle State When querying the Toggle state (enabled, disabled, or offline), one must look at the monitor and session enabled states. The three possible combinations of these two values will yield the appropriate toggle states. Monitor State Session Enabled State Toggle State STATE_ENABLED STATE_ENABLED Enabled (All traffic allowed) STATE_ENABLED STATE_DISABLED Disabled (Only persistent or active connections allowed) STATE_DISABLED STATE_DISABLED Forced Offline (Only active connections allowed) The local Get-ToggleState function will take as input the monitor and session enabled state of an object and return the toggle state. function Get-ToggleState() { param( [string]$monitor_state = $null, [string]$session_enabled_state = $null ); $state = $null; if ( $monitor_state -and $session_enabled_state ) { if ( ($monitor_state -eq "STATE_ENABLED") -and ($session_enabled_state -eq "STATE_ENABLED") ) { $state = "enabled"; } elseif ( ($monitor_state -eq "STATE_ENABLED") -and ($session_enabled_state -eq "STATE_DISABLED") ) { $state = "disabled"; } elseif ( ($monitor_state -eq "STATE_DISABLED") -and ($session_enabled_state -eq "STATE_DISABLED") ) { $state = "offline"; } } $state; } Querying The Server State The application was designed to allow either an address (node address) or address:port (pool member) for the input object. The Get-ObjectState function will determine whether we are looking for a node address or pool member and call the appropriate method depending on the object type. The Get-NodeAddressState function will call the LocalLB.NodeAddress.get_monitor_status() and LocalLB.NodeAddresss.get_session_enabled_state() functions for the specified node address and display the determined toggle status value. The Get-PoolMemberState function is a bit more complex because it needs to do a reverse lookup for all pools that have the specified pool member in it's configuration. When a match is found, the pool member along with it's associated pool and status are displayed. function Get-ObjectState() { param([string[]]$objects = $null); if ( ! $objects ) { $objects = Get-AllObjects; } foreach ($object in $objects) { $tokens = $object.Split((, ":")); if ( $tokens.Length -eq 1 ) { # Node Address Get-NodeAddressState -address $tokens[0]; } elseif ( $tokens.Length -eq 2 ) { # Pool Member Get-PoolMemberState -address $tokens[0] -port $tokens[1]; } else { Write-Host "Invalid object '$object'"; } } } function Get-NodeAddressState() { param([string]$address = $null); $state = $null; if ( $address ) { $MonitorStatusA = (Get-F5.iControl).LocalLBNodeAddress.get_monitor_status( (,$address)); $monitor_state = "STATE_ENABLED"; if ( $MonitorStatusA[0] -eq "MONITOR_STATUS_FORCED_DOWN" ) { $monitor_state = "STATE_DISABLED"; } $EnabledStateA = (Get-F5.iControl).LocalLBNodeAddress.get_session_enabled_state( (,$address)); $session_enabled_state = $EnabledStateA[0]; $state = Get-ToggleState -monitor_state $monitor_state -session_enabled_state $session_enabled_state; New-ObjectStatus -object $address -state $state; } } function Get-PoolMemberState() { param([string]$address = $null, [string]$port = $null); if ( $address -and $port ) { $state = $null; $pool_list = (Get-F5.iControl).LocalLBPool.get_list(); $memberSessionStateAofA = (Get-F5.iControl).LocalLBPoolMember.get_session_enabled_state($pool_list); $monitorStatusAofA = (Get-F5.iControl).LocalLBPoolMember.get_monitor_status($pool_list); for ($i=0; $i-lt$pool_list.Length; $i++) { for($j=0; $j-lt$memberSessionStateAofA[$i].Length; $j++) { $memberSessionState = $memberSessionStateAofA[$i][$j]; $monitorStatus = $monitorStatusAofA[$i][$j]; if ( ($monitorStatus.member.address -eq $address) -and ($monitorStatus.member.port -eq $port) ) { # found a match $session_enabled_state = $memberSessionState.session_state; $monitor_state = "STATE_ENABLED"; if ( $monitorStatus.monitor_status -eq "MONITOR_STATUS_FORCED_DOWN" ) { $monitor_state = "STATE_DISABLED"; } $state = Get-ToggleState -monitor_state $monitor_state -session_enabled_state $session_enabled_state; New-ObjectStatus -object "${address}:${port}" -parent "$($pool_list[$i])" -state $state; } } } } } Setting The Server State Simliar to the Get-ObjectState function, the Set-ObjectState function will determine whether the object is a node address or pool member and call the appropriate function. The Set-NodeAddressState function will call the LocalLB.NodeAddress.set_monitor_state() and LocalLB.NodeAddress.set_session_enabled_state() functions with the appopriate monitor and session state as described in the above table. The Set-PoolMemberState function again will do a reverse lookup to find all containing pools and set the values accordingly for all pools that contain that pool member. function Set-ObjectState() { param( [string]$object = $null, [string]$state = $null ); if ( $object -and $state ) { $tokens = $object.Split((,":")); if ( $tokens.Length -eq 1 ) { Set-NodeAddressState -address $tokens[0] -state $state; } elseif ( $tokens.Length -eq 2 ) { Set-PoolMemberState -address $tokens[0] -port $tokens[1] -state $state; } } } function Set-NodeAddressState() { param( [string]$address = $null, [string]$state = $null ); if ( $address -and $state ) { $monitor_state = $MONITOR_STATE_HASH[$state]; $session_state = $SESSION_STATE_HASH[$state]; if ( $monitor_state -and $session_state ) { (Get-F5.iControl).LocalLBNodeAddress.set_monitor_state( (,$address), (,$monitor_state)); (Get-F5.iControl).LocalLBNodeAddress.set_session_enabled_state( (,$address), (,$session_state)); Get-NodeAddressState -address $address; } } } function Set-PoolMemberState() { param( [string]$address = $null, [string]$port = $null, [string]$state = $null ); if ( $address -and $port -and $state ) { $pool_list = (Get-F5.iControl).LocalLBPool.get_list(); $memberDefAofA = (Get-F5.iControl).LocalLBPool.get_member($pool_list); $monitor_state = $MONITOR_STATE_HASH[$state]; $session_state = $SESSION_STATE_HASH[$state]; Write-Host "state: $state; monitor_state $monitor_state; session_state: $session_state"; for ($i=0; $i-lt$pool_list.Length; $i++) { for($j=0; $j-lt$memberDefAofA[$i].Length; $j++) { $member = $memberDefAofA[$i][$j]; if ( ($member.address -eq $address) -and ($member.port -eq $port) ) { # found a match $memberMonitorState = New-Object -TypeName iControl.LocalLBPoolMemberMemberMonitorState; $memberMonitorState.member = $member; $memberMonitorState.monitor_state = $monitor_state; (Get-F5.iControl).LocalLBPoolMember.set_monitor_state( (,$pool_list[$i]), (,(,$memberMonitorState)) ); $memberSessionState = New-Object -TypeName iControl.LocalLBPoolMemberMemberSessionState; $memberSessionState.member = $member; $memberSessionState.session_state = $session_state; (Get-F5.iControl).LocalLBPoolMember.set_session_enabled_state( (,$pool_list[$i]), (,(,$memberSessionState)) ) Get-PoolMemberState -address $address -port $port; } } } } } Utilitiy Functions What's an app without a bit of utility? The New-ObjectStatus function is mainly to allow PowerShell to build a nicely formatted table of the results. An object is created with the properties of Object, Parent, and State and those property values are populated with the input to the function and the resulting object is passed out through the pipeline. function New-ObjectStatus() { param( [string]$object = $null, [string]$parent = $null, [string]$state = $null ); $o = $null; if ( $object -and $state ) { $o = 1 | select "Object", "Parent", "State"; $o.Object = $object; $o.State = $state; if ($parent) { $o.Parent = $parent; } } $o; } Example session foo PS D:\> .\ServerControl.ps1 bigip user pass Object Parent State ------ ------ ----- 1.1.1.1 enabled 1.1.1.2 disabled 1.1.1.3 enabled 1.2.3.4 enabled 10.10.10.148 enabled 10.10.10.148:22 catbert-ssh enabled 10.10.10.148:80 catbert-http enabled 10.10.10.149 enabled 10.10.10.149:22 xpbert-ssh enabled 10.10.10.149:80 xpbert-http enabled 10.10.10.149:80 pool_1 enabled 10.10.10.149:80 xpbert-http enabled 10.10.10.149:80 pool_1 enabled 10.10.10.149:81 xpbert-http enabled 10.10.10.201 enabled 10.10.10.201:80 dc-sea-web enabled 10.10.10.202 enabled 10.10.10.202:80 dc-sea-web enabled 10.10.10.203 enabled 10.10.10.203:80 dc-sea-media enabled 10.10.10.204 enabled 10.10.10.211 enabled 10.10.10.211:80 dc-llix-web enabled 10.10.10.212 enabled 10.10.10.212:80 dc-llix-web enabled 10.10.10.213 enabled 10.10.10.213:80 dc-llix-media enabled 20.20.20.101 enabled 20.20.20.101:80 pool_1 enabled 20.20.20.102 enabled 20.20.20.102:80 pool_2 enabled 30.30.30.149 enabled PS D:\> .\ServerControl.ps1 bigip user pass 10.10.10.149:80 Object Parent State ------ ------ ----- 10.10.10.149:80 xpbert-http enabled 10.10.10.149:80 pool_1 enabled PS D:\> .\ServerControl.ps1 bigip user pass 10.10.10.149:80 disabled Object Parent State ------ ------ ----- 10.10.10.149:80 xpbert-http disabled 10.10.10.149:80 pool_1 disabled PS D:\> .\ServerControl.ps1 bigip user pass 10.10.10.149:80 offline Object Parent State ------ ------ ----- 10.10.10.149:80 xpbert-http offline 10.10.10.149:80 pool_1 offline PS D:\> .\ServerControl.ps1 bigip user pass 10.10.10.149 enabled Object Parent State ------ ------ ----- 10.10.10.149 enabled Conclusion This appliation illustrates the logic in emulating the functionality of the three-way toggle in the BIG-IP admin GUI with the approrpriate methods in the iControl API and gives you a tool to easily enable/disable servers. You can download the full code for this script in the iControl CodeShare under PsServerControl.540Views0likes0CommentsiControl Apps - #14 - Global Statistics
Continuing on with my series of applications on system level statistics, this application will look into the insides of the global system level statistics for the device. The global statistics contain information on Auth, Bytes, Connections, CPU, Denials, Errors, Hardware, HTTP, Memory, Packets, and the TMM. Usage The arguments for this application are the address, username, and password of the BIG-IP. This is declared in the top of the script with the following param statement. There is also a Write-Usage function to display the arguments to the user. param ( $g_bigip = $null, $g_uid = $null, $g_pwd = $null ); Set-PSDebug -strict; function Write-Usage() { Write-Host "Usage: PsSystemStats.ps1 host uid pwd"; exit; } Initialization As is with all of my PowerShell scripts, the initialization component will look to see if the iControlSnapIn is loaded into the current PowerShell session. If not, the Add-PSSnapIn Cmdlet is called to add the snapin into the runtime. Then a call to the Initialize-F5.iControl cmdlet to setup the connection to the BIG-IP. If this succeeds, then a call to the Get-GlobalStatistics function is called to query the system statistics and output them to the console. function Do-Initialize() { if ( (Get-PSSnapin | Where-Object { $_.Name -eq "iControlSnapIn"}) -eq $null ) { Add-PSSnapIn iControlSnapIn } $success = Initialize-F5.iControl -HostName $g_bigip -Username $g_uid -Password $g_pwd; return $success; } #------------------------------------------------------------------------- # Main Application Logic #------------------------------------------------------------------------- if ( ($g_bigip -eq $null) -or ($g_uid -eq $null) -or ($g_pwd -eq $null) ) { Write-Usage; } if ( Do-Initialize ) { Get-GlobalStatistics; } else { Write-Error "ERROR: iControl subsystem not initialized" } Querying Global Statistics The global system statistics can be retrieved with a single call to the get_global_statistics method located in the System.Statistics interface. This method takes no arguments and returns a System.Statistics structure containing the timestamp for the statistics along with an array of Common.Statistics structures which in turn contain the type and value for each individual statistic. In the Get-GlobalStatistics function, a call is made to the get_global_statistics() method and the returned value is stored in the GlobalStats variable. A hash is then created to store all the statistics values. Next, a foreach loop is run over all of the stats in the GlobalStats variable and for each stat the 64 bit value is calculated, a label is created, and that name/value pair is added to the hash table. When all of the statistics have been processed, the hash's enumerator is accessed and that is passed through the PowerShell pipeline to the Sort-Object Cmdlet and then the Format-Table Cmdlet to display the results to the console. function Get-GlobalStatistics() { $GlobalStats = (Get-F5.iControl).SystemStatistics.get_global_statistics(); $t = Get-TimeFromTimeStamp $GlobalStats.time_stamp; $hash = @{}; $hash.Add("* Time Stamp", $t); $Statistics = $GlobalStats.statistics; foreach($Statistic in $Statistics) { $val = Convert-To64Bit $Statistic.value.high $Statistic.value.low; $label = Get-StatisticLabel $Statistic.type; $hash.Add($label, $val); } $hash.GetEnumerator() | Sort-Object -Property Name | Format-Table -autosize } function Get-StatisticLabel() { param($type); $label = ""; switch($type) { "STATISTIC_CLIENT_SIDE_BYTES_IN" { $label = "Bytes - Client In"; } "STATISTIC_CLIENT_SIDE_BYTES_OUT" { $label = "Bytes - Client Out"; } "STATISTIC_CLIENT_SIDE_PACKETS_IN" { $label = "Packets - Client In"; } "STATISTIC_CLIENT_SIDE_PACKETS_OUT" { $label = "Packets - Client Out"; } "STATISTIC_CLIENT_SIDE_CURRENT_CONNECTIONS" { $label = "Connections - Client Current"; } "STATISTIC_CLIENT_SIDE_MAXIMUM_CONNECTIONS" { $label = "Connections - Client Maximum"; } "STATISTIC_CLIENT_SIDE_TOTAL_CONNECTIONS" { $label = "Connections - Client Total"; } "STATISTIC_SERVER_SIDE_BYTES_IN" { $label = "Bytes - Server In"; } "STATISTIC_SERVER_SIDE_BYTES_OUT" { $label = "Bytes - Server Out"; } "STATISTIC_SERVER_SIDE_PACKETS_IN" { $label = "Packets - Server In"; } "STATISTIC_SERVER_SIDE_PACKETS_OUT" { $label = "Packets - Server Out"; } "STATISTIC_SERVER_SIDE_CURRENT_CONNECTIONS" { $label = "Connections - Server Current"; } "STATISTIC_SERVER_SIDE_MAXIMUM_CONNECTIONS" { $label = "Connections - Server Maximum"; } "STATISTIC_SERVER_SIDE_TOTAL_CONNECTIONS" { $label = "Connections - Server Total"; } "STATISTIC_PVA_CLIENT_SIDE_BYTES_IN" { $label = "Bytes - PVA Client In"; } "STATISTIC_PVA_CLIENT_SIDE_BYTES_OUT" { $label = "Bytes - PVA Client Out"; } "STATISTIC_PVA_CLIENT_SIDE_PACKETS_IN" { $label = "Packets - PVA Client In"; } "STATISTIC_PVA_CLIENT_SIDE_PACKETS_OUT" { $label = "Packets - PVA Client Out"; } "STATISTIC_PVA_CLIENT_SIDE_CURRENT_CONNECTIONS" { $label = "Connections - PVA Client Current"; } "STATISTIC_PVA_CLIENT_SIDE_MAXIMUM_CONNECTIONS" { $label = "Connections - PVA Client Maximum"; } "STATISTIC_PVA_CLIENT_SIDE_TOTAL_CONNECTIONS" { $label = "Connections - PVA Client Total"; } "STATISTIC_PVA_SERVER_SIDE_BYTES_IN" { $label = "Bytes - PVA Server In"; } "STATISTIC_PVA_SERVER_SIDE_BYTES_OUT" { $label = "Bytes - PVA Server Out"; } "STATISTIC_PVA_SERVER_SIDE_PACKETS_IN" { $label = "Packets - PVA Server In"; } "STATISTIC_PVA_SERVER_SIDE_PACKETS_OUT" { $label = "Packets - PVA Server Out"; } "STATISTIC_PVA_SERVER_SIDE_CURRENT_CONNECTIONS" { $label = "Connections - PVA Server Current"; } "STATISTIC_PVA_SERVER_SIDE_MAXIMUM_CONNECTIONS" { $label = "Connections - PVA Server Maximum"; } "STATISTIC_PVA_SERVER_SIDE_TOTAL_CONNECTIONS" { $label = "Connections - PVA Server Total"; } "STATISTIC_TOTAL_PVA_ASSISTED_CONNECTIONS" { $label = "Connections - PVA Assisted Total"; } "STATISTIC_CURRENT_PVA_ASSISTED_CONNECTIONS" { $label = "Connections - PVA Assisted Current"; } "STATISTIC_TM_TOTAL_CYCLES" { $label = "TMM - Total Cycles"; } "STATISTIC_TM_IDLE_CYCLES" { $label = "TMM - Idle Cycles"; } "STATISTIC_TM_SLEEP_CYCLES" { $label = "TMM - Sleep Cycles"; } "STATISTIC_MAINTENANCE_MODE_DENIALS" { $label = "Denials - Maintence Mode"; } "STATISTIC_VIRTUAL_ADDRESS_MAXIMUM_CONNECTION_DENIALS" { $label = "Denials - VA Maximum Connection"; } "STATISTIC_VIRTUAL_SERVER_MAXIMUM_CONNECTION_DENIALS" { $label = "Denials - VS Maximum Connection"; } "STATISTIC_VIRTUAL_SERVER_NON_SYN_DENIALS" { $label = "Denials - VS Non SYN"; } "STATISTIC_NO_HANDLER_DENIALS" { $label = "Denials - No Handler"; } "STATISTIC_LICENSE_DENIALS" { $label = "Denials - License"; } "STATISTIC_CONNECTION_FAILED_MEMORY_ERRORS" { $label = "Errors - Connection Failed Memory"; } "STATISTIC_CPU_COUNT" { $label = "CPU - Count"; } "STATISTIC_ACTIVE_CPU_COUNT" { $label = "CPU - Active Count"; } "STATISTIC_MULTI_PROCESSOR_MODE" { $label = "CPU - Multi Processor Mode"; } "STATISTIC_MEMORY_TOTAL_BYTES" { $label = "Memory - Total Bytes"; } "STATISTIC_MEMORY_USED_BYTES" { $label = "Memory - Used Bytes"; } "STATISTIC_DROPPED_PACKETS_TOTAL" { $label = "Packets - Total Dropped"; } "STATISTIC_ERRORS_IN" { $label = "Errors - In"; } "STATISTIC_ERRORS_OUT" { $label = "Errors - Out"; } "STATISTIC_AUTH_TOTAL_SESSIONS" { $label = "Auth - Total Sessions"; } "STATISTIC_AUTH_CURRENT_SESSIONS" { $label = "Auth - Current Sessions"; } "STATISTIC_AUTH_MAXIMUM_SESSIONS" { $label = "Auth - Maximum Sessions"; } "STATISTIC_AUTH_SUCCESS_RESULTS" { $label = "Auth - Success Results"; } "STATISTIC_AUTH_FAILURE_RESULTS" { $label = "Auth - Failure Results"; } "STATISTIC_AUTH_WANT_CREDENTIAL_RESULTS" { $label = "Auth - Want Credentials Results"; } "STATISTIC_AUTH_ERROR_RESULTS" { $label = "Auth - Error Results"; } "STATISTIC_HTTP_TOTAL_REQUESTS" { $label = "HTTP - Total Request"; } "STATISTIC_HARDWARE_SYN_COOKIES_GENERATED" { $label = "Hardware - SYN Cookies Generated"; } "STATISTIC_HARDWARE_SYN_COOKIES_DETECTED" { $label = "Hardware - SYN Cookies Detected"; } default { Write-Error "Unknown Label $type" $label = "***UNKNOWN***"; } } return $label; } Running the code The following command line will execute the code and display the output. My device is not very active so there are not that many statistics to display but you will see that even for an idle machine Since I do not have PVA enabled on my test system, the data here is pretty boring. But, give it a shot on your active site and you'll see the data presented. PS C:\> .\PsGlobalStatistics.ps1 bigip_address username password Name Value ---- ----- * Time Stamp 10/16/2008 12:37:36 PM Auth - Current Sessions 0 Auth - Error Results 0 Auth - Failure Results 0 Auth - Maximum Sessions 0 Auth - Success Results 0 Auth - Total Sessions 0 Auth - Want Credentials Results 0 Bytes - Client In 212559442 Bytes - Client Out 6372270 Bytes - PVA Client In 0 Bytes - PVA Client Out 0 Bytes - PVA Server In 0 Bytes - PVA Server Out 0 Bytes - Server In 11975422 Bytes - Server Out 6372270 Connections - Client Current 63 Connections - Client Maximum 64 Connections - Client Total 117795 Connections - PVA Assisted Current 0 Connections - PVA Assisted Total 0 Connections - PVA Client Current 0 Connections - PVA Client Maximum 0 Connections - PVA Client Total 0 Connections - PVA Server Current 0 Connections - PVA Server Maximum 0 Connections - PVA Server Total 0 Connections - Server Current 62 Connections - Server Maximum 63 Connections - Server Total 117794 CPU - Active Count 2 CPU - Count 2 CPU - Multi Processor Mode 1 Denials - License 0 Denials - VS Non SYN 0 Denials - Maintence Mode 0 Denials - No Handler 2020 Denials - VA Maximum Connection 0 Denials - VS Maximum Connection 0 Errors - Connection Failed Memory 0 Errors - In 0 Errors - Out 0 Hardware - SYN Cookies Detected 0 Hardware - SYN Cookies Generated 0 HTTP - Total Request 0 Memory - Total Bytes 931135488 Memory - Used Bytes 24899536 Packets - Client In 1357519 Packets - Client Out 118005 Packets - PVA Client In 0 Packets - PVA Client Out 0 Packets - PVA Server In 0 Packets - PVA Server Out 0 Packets - Server In 177613 Packets - Server Out 118005 Packets - Total Dropped 0 TMM - Idle Cycles 939236910286802 TMM - Sleep Cycles 0 TMM - Total Cycles 940529569438863 Conclusion As with all the other methods in the System.Statistics interface, you'll find a lot of hidden data that you may or may not be aware of. Explore these global statistics and all the others to help you monitor and manage your systems. Get the Flash Player to see this player. 20081016-iControlApps-14-GlobalStatistics.mp3476Views0likes0Comments