events
37 TopicsBIG-IP Logging and Reporting Toolkit - part one
Joe Malek, one of the many awesome engineers here at F5, took it upon himself to delve deeply into a very interesting but often unsung part of the BIG-IP advanced configuration world: logging and reporting. It’s my great pleasure to get to share with you his awesome study and the findings therein, along with (eventually) a toolkit to help you get started in the world of custom log manipulation. If you’ve ever questioned or been curious about your options when it comes to information gathering and reporting, this is definitely something you should read. There will be multiple parts, so stay tuned. This one is just the intro. Logging & Reporting Toolkit - Part 1 Logging & Reporting Toolkit - Part 2 Logging & Reporting Toolkit - Part 3 Logging & Reporting Toolkit - Part 4 Description F5 products occupy critical positions in application delivery infrastructure. They serve as gateways, proxies, accelerators and traffic flow arbiters. In these roles customer expectations vary for the degree and amount of event information recorded. Several opportunities exist within our current product capabilities for our customers and partners to produce and consume log messages from and via F5 products. Efforts to date include generating W3C style log messages on LTM via iRules, close integration with leading vendors and ASM (requires askf5 login), and creating relationships with leading vendors to best serve our customers. Significant capabilities exist for customers and partners to create their own logging and reporting solutions. Problems and opportunity In the many products offered by F5, there exists a variety of logging structures. The common log protocols used to emit messages by F5 products are Syslog (requires askf5 login) and SNMP (requires askf5 login), along with built-in iRulescapabilities. Though syslog-ng is commonplace, software components tend to vary in transport, verbosity, message formatting and sometimes syslog facility. This can result in a high degree of data density in our logs, and messages our systems emit can vary from version to version.[i] The combination of these factors results in a challenge that requires a coordinated solution for customers who are compelled by regulation, industry practice, or by business process, to maintain log management infrastructure that consumes messages from F5 devices.[ii] By utilizing the unique product architecture TMOS employs by sharing its knowledge about networks and applications as well as capabilities built into iRules, TMOS can provide much of this information to log management infrastructure in a simple and knowledgeable manner. In effect, we can emit messages about appliance state and offload many message logging tasks from application servers. Based on our connection knowledge we can also improve the utility and value of information obtained from vendor provided log management infrastructure.[iii] Objectives and success criteria The success criteria for including an item in the toolkit is: 1. A capability to deliver reports on select items using the leading platforms without requiring core development work on an F5 product. 2. An identified extensibility capability for future customization and report building. Assumptions and dependencies Vendors to include in the toolkit are Splunk, Q1Labs and PresiNET ASM logging and reporting is sufficient and does not need further explanation Information to be included in sample reports should begin to assist in diagnostic activities, demonstrate ROI by including ROI in an infrastructure and advise on when F5 devices are nearing capacity Vendor products must be able to accept event data emitted by F5 products. This means that some vendors might have more comprehensive support than others. Products currently supported but not in active development are not eligible for inclusion in the toolkit. Examples are older versions of BIG-IP and FirePass, and all WANJet releases. Some vendor products will require code modifications on the vendor’s side to understand the data F5 products send them. [i] As a piece of customer evidence, Microsoft implemented several logging practices around version 9.1. When they upgraded to version 9.4 their log volume increased several-fold because F5 added log messages and changed existing messages. As a result existing message taxonomy needed to be deprecated and we caused them to need to redesign filters, reports and create a new set of logging practices. [ii] Regulations such as the Sarbanes-Oxley Act, Gramm Leach Blyley Act, Federal Information Security Management Act, PCI DSS, and HIPPA. [iii] It is common for F5 products to manipulate connections via OneConnect, NATs and SNATs. These operations are unknown to external log collectors, and pose a challenge when assembling a complete view of the network connections between a client and a server via an F5 device for a single application transaction. What’s Next? In the next installment we’ll get into the details of the different vendors in question, their offerings, how they work and integrate with BIG-IP, and more. Logging and Reporting Toolkit Series: Part Two | Part Three734Views0likes1Commentv11: RDP Access via BIG-IP APM - Part 1
I wrote an article several months back on auto-launching Remote Desktop sessions with APM. With the introduction of BIG-IP APM v11, there is a new built-in capability to support a full webtop. This means that server, desktop, or other resources can be placed on the webtop for users to select once logging in. In this first example, I’ll set up a static internal resource for users to connect to after logging in. Create the Webtop After logging in to the BIG-IP, open up the Access Policy tab and select Webtops->Webtop List and then click Create (or you can hit the “+” circled to the right of the Webtop List.) Give the Webtop a meaningful name and the type needs to be Full as show in Figure 1. Create the RDP Resource Still in the Access Policy tab, click Application Access->Remote Desktops->Remote Desktops and then click Create. There are a number of fields here, but for this example the only ones that need to be set are the Type (RDP), the Destination (Server or Desktop hostname or IP), and Auto Logon (Enable). When Auto Logon is selected, the username, password, and domain source variable fields are shown. I accepted the defaults. Create the Access Policy Now that the two custom objects for the RDP Webtop are created, I’ll create the access policy (and virtuals) with the Network Access Setup Wizard for Remote Access under the Wizards tab. I create all my access policies this way, the wizard is very thorough and eliminates my tendency to overlook an object or misconfigure one, not to mention the time savings. In the first screen, I disabled AV checks (though in production I wouldn’t recommend this) as shown in Figure 3 below. Next I create a new authentication resource (you can select existing if this is not a new installation), utilizing my test active directory server. Next I configure the lease pool. It’s just me in my test lab, so I only create a single client address, but you’ll likely need to choose the IP address range. The next step is for network access configuration. Corporate policies will dictate whether all traffic is forced through the tunnel or if split-tunneling is appropriate. For this example, I stuck with forcing all traffic through the tunnel to minimize the necessary configuration to show the features. I only have one name server, my ad01 directory server, so I enter that and leave the rest blank. Next I’ll enter the VIP address and leave the http->https redirect virtual enabled. At this point, I review the configuration and click next. If there are any errors, you can return to previous steps in the wizard and make corrections. Before clicking Finished in the next screen, I need to edit the access policy. Once in the policy editor, click on the Logon Page object and set Field 3 from none to text and use domain as the post and session variable name. Then below in the Logon Page Input Field #3 text box, enter Domain. Next, click on the Resource Assign object and then click Add/Delete in the expression. I need to replace the webtop the network access wizard created and I need to select the RDP Resource I created. Close out the policy and then click Finished in the Setup Summary screen. For my configuration I need to snat the traffic, so I enabled snat-automap on the virtual created by the wizard. Because I made changes to the policy, I need to re-apply it, so in the Access Policy tab I clicked on Access Profiles and then selected my profile and clicked Apply Access Policy. That completes the configuration steps. Now it’s time to test. Testing the Configuration First I open a browser and navigate to my vip, https://10.10.20.30. After login, my RDP resource is shown on my Webtop, along with my network access. After clicking the rdptest icon, I am logged in automatically to my server. It seems like a lot of steps, but I configured this in less than five minutes, which is far more efficient and far less error-prone than the previous solution. The video below shares the same steps covered here in screen captures. BIG-IP APM RDP Webtop Conclusion This solution introduces a full Webtop environment for BIG-IP APM in version 11, which I took advantage of for statically configuring RDP resources for clients. In part 2, I’ll introduce a dynamic option for the RDP resource.552Views0likes7CommentsAdd Outlook Web Access Login Options to the APM Logon Page
Outlook Web Access is the web interface to the Microsoft Exchange environment, and many customers have secured the portal behind their BIG-IP APM architecture. In looking at the OWA logon page, however, you'll notice that there are a couple extra options than the default APM logon page supports: This article will show you how to add these options to a custom BIG-IP APM logon page, however, it assumes the portal app with SSO is already configured and working. If you need help with that, drop a comment below. Configuration Steps First, enable two additional text boxes on the APM Logon page Now that you have the variables where they will be part of the session you need to modify the logon page to display radio boxes and checkboxes for the fields we added instead of the textbox. You do this by first going to the customization section of the access policy module. Next, change the Edit Mode to Advanced. Then navigate to the logon page. Find this section of code in logon.inc: foreach( $fields_settings as $id=>$field_settings ) { if( $field_settings["type"] != "none" ) { Immediately after the opening curly brace in that section of code, add these lines (highlighted in red) so that the section of code now looks like this: foreach( $fields_settings as $id=>$field_settings ) { if( $field_settings["name"] == "pubpriv" ) { continue; } if( $field_settings["name"] == "lightversion" ) { continue; } if( $field_settings["type"] != "none" ) { The section should look like this now: Note the closing four curly braces at the bottom of the screen shot. You need to add this section of code below between the third and fourth curly brace: ?> <tr> <td colspan=2 class="credentials_table_unified_cell" > <label for="text">Security</label> <input type="radio" name=pubpriv value="public" checked> This is a public or shared computer<br> <input type="radio" name=pubpriv value="private"> This is a private computer </td> </tr> <tr> <td colspan=2 class="credentials_table_unified_cell" > <label for="text">Light Version?</label> <input type="checkbox" name=lightversion value="yes"> Use the light version of Outlook Web App </td> </tr> <? Now the section, complete, should look like this: Now click Save Draft, the click Save in the editor tool bar. Now that the customizations are done, we need to create an iRule to see what the form values are and then set the values will push into the SSO object. The values are found by looking at the post variables OWA uses. Go to "Local Traffic" section in the menu, then iRules and click "Create". I named my iRule "owa_form_values_iRule" and pasted the following code when ACCESS_POLICY_AGENT_EVENT { if {[ACCESS::session data get "session.logon.last.pubpriv"] eq "private"} { if {[ACCESS::session data get "session.logon.last.lightversion"] eq "yes"} { ACCESS::session data set "session.custom.owa.flags" 5 ACCESS::session data set "session.custom.owa.trusted" 4 } else { ACCESS::session data set "session.custom.owa.flags" 4 ACCESS::session data set "session.custom.owa.trusted" 4 } } else { if {[ACCESS::session data get "session.logon.last.lightversion"] eq "yes"} { ACCESS::session data set "session.custom.owa.flags" 1 ACCESS::session data set "session.custom.owa.trusted" 0 } else { ACCESS::session data set "session.custom.owa.flags" 0 ACCESS::session data set "session.custom.owa.trusted" 0 } } } Next go back to the visual policy editor and add an iRule Event after the logon page in the process flow but before the resource assign and enter the name of the iRule we created in the ID field. Finally, edit the SSO configuration object. In the hidden form parameters modify the values of "flags" and "trusted" to use the new session variables created in the iRule. The other variables should remain the same. flags %{session.custom.owa.flags} trusted %{session.custom.owa.trusted} Shown in the SSO object: Now apply the policy and you are good to go! OWA through APM will provide the same functions as the direct OWA logon page!2.1KViews0likes14CommentsiControl 101 - #11 - Performance Graphs
The BIG-IP stores history of certain types of data for reporting purposes. The Performance item in the Overview tab shows the report graphs for this performance data. The management GUI gives you the options to customize the graph interval but it stops short of giving you access to the raw data behind the graphs. Never fear, the System.Statistics interface contains methods to query the report types and extract the CSV data behind them. You can even select the start and end times as well as the poll intervals. This article will discuss the performance graph methods and how you can query the data behind them and build a chart of your own. Initialization This article uses PowerShell and the iControl Cmdlets for PowerShell as the client environment for querying the data. The following setup will be required for the examples contained in this article. PS> Add-PSSnapIn iControlSnapIn PS> Initialize-F5.iControl -Hostname bigip_address -Username bigip_user -Password bigip_pass PS> $SystemStats = (Get-F5.iControl).SystemStatistics Now that that is taken care of, let's dive right in. In the System.Statistics interface, there are two methods that get you to the performance graph data. The first is the get_performance_graph_list() method. This method takes no parameters and returns a list of structures containing each graph name, title, and description. PS> $SystemStats.get_performance_graph_list() graph_name graph_title graph_description ---------- ----------- ----------------- memory Memory Used Memory Used activecons Active Connections Active Connections newcons New Connections New Connections throughput Throughput Throughput httprequests HTTP Requests HTTP Requests ramcache RAM Cache Utilization RAM Cache Utilization detailactcons1 Active Connections Active Connections detailactcons2 Active PVA Connections Active PVA Connections detailactcons3 Active SSL Connections Active SSL Connections detailnewcons1 Total New Connections Total New Connections detailnewcons2 New PVA Connections New PVA Connections detailnewcons3 New ClientSSL Profile Connections New ClientSSL Profile Connections detailnewcons4 New Accepts/Connects New Accepts/Connects detailthroughput1 Client-side Throughput Client-side Throughput detailthroughput2 Server-side Throughput Server-side Throughput detailthroughput3 HTTP Compression Rate HTTP Compression Rate SSLTPSGraph SSL Transactions/Sec SSL Transactions/Sec GTMGraph GTM Performance GTM Requests and Resolutions GTMrequests GTM Requests GTM Requests GTMresolutions GTM Resolutions GTM Resolutions GTMpersisted GTM Resolutions Persisted GTM Resolutions Persisted GTMret2dns GTM Resolutions Returned to DNS GTM Resolutions Returned to DNS detailcpu0 CPU Utilization CPU Usage detailcpu1 CPU Utilization CPU Usage CPU CPU Utilization CPU Usage detailtmm0 TMM Utilization TMM Usage TMM TMM Utilization TMM CPU Utilization Creating a Report Ok, so you've now got the graph names, it's time to move on to accessing the data. The method you'll want is the get_performance_graph_csv_statistics() method. This method takes an array of PerformanceStatisticQuery structures containing the query parameters and returns an array of PerformanceGraphDataCSV structures, one for each input query. The following code illustrates how to make a simple query. The object_name corresponds to the graph_name in the get_performance_graph_list() method. The start_time and end_time allow you to control what the data range is. Values of 0 (the default) will return the entire result set. If the user specifies a start_time, end_time, and interval that does not exactly match the corresponding value used within the database, the database will attempt to use to closest time or interval as requested. The actual values used will be returned to the user on output. For querying purposes, the start_time can be specified as: 0: in which case by default, it means 24 hours ago. N: where N represents the number of seconds since Jan 1, 1970. -N: where -N represents the number of seconds before now, for example: -3600 means 3600 seconds ago, or now - 3600 seconds. For querying purposes, the end_time can be specified as: 0: in which case by default, it means now. N: where N represents the number of seconds since Jan 1, 1970. -N: where -N represents the number of seconds before now, for example: -3600 means 3600 seconds ago, or now - 3600 seconds. The interval is the suggested sampling interval in seconds. The default of 0 uses the system default. The maximum_rows value allows you to limit the returned rows. Values are started at the start_time and if the number of rows exceeds the value of maximum_rows, then the data is truncated at the maximum_rows value. A value of 0 implies the default of all rows. PS> # Allocate a new Query Object PS> $Query = New-Object -TypeName iControl.SystemStatisticsPerformanceStatisticQuery PS> $Query.object_name = "CPU" PS> $Query.start_time = 0 PS> $Query.end_time = 0 PS> $Query.interval = 0 PS> $Query.maximum_rows = 0 PS> # Make method call passing in an array of size one with the specified query PS> $ReportData = $SystemStats.get_performance_graph_csv_statistics( (,$Query) ) PS> # Look at the contents of the returned data. PS> $ReportData object_name : throughput start_time : 1208354160 end_time : 1208440800 interval : 240 statistic_data : {116, 105, 109, 101...} Processing the Data The statistic_data, much like the ConfigSync's file transfer data, is transferred as a base64 encoded string, which translates to a byte array in .Net. We will need to convert this byte array into a string and that can be done with the System.Text.ASCIIEncoding class. PS> # Allocate a new encoder and turn the byte array into a string PS> $ASCII = New-Object -TypeName System.Text.ASCIIEncoding PS> $csvdata = $ASCII.GetString($ReportData[0].statistic_data) PS> # Look at the resulting dataset PS> $csvdata timestamp,"CPU 0","CPU 1" 1208364000,4.3357230000e+00,0.0000000000e+00 1208364240,3.7098920000e+00,0.0000000000e+00 1208364480,3.7187980000e+00,0.0000000000e+00 1208364720,3.3311110000e+00,0.0000000000e+00 1208364960,3.5825310000e+00,0.0000000000e+00 1208365200,3.4826450000e+00,8.3330000000e-03 ... Building a Chart You will see the returned dataset is in the form of a comma separated value file. At this point you can take this data and import it into your favorite reporting package. But, if you want a quick and dirty way to see this visually, you can use PowerShell to control Excel into loading the data and generating a default report. The following code converts the csv format into a tab separated format, creates an instance of an Excel Application, loads the data, cleans it up, and inserts a default line graph based on the input data. PS> # Replace commas with tabs in the report data and save to c:\temp\tabdata.txt PS> $csvdata.Replace(",", "`t") > c:\temp\tabdata.txt PS> # Allocate an Excel application object and make it visible. PS> $e = New-Object -comobject "Excel.Application" PS> $e.visible = $true PS> # Load the tab delimited data into a workbook and get an instance of the worksheet it was inserted into. PS> $wb = $e.Workbooks.Open("c:\temp\tabdata.txt") PS> $ws = $wb.WorkSheets.Item(1) PS> # Let's remove the first row of timestamps. Ideally you'll want this to be the PS> # horizontal axis and I'll leave it up to you to figure that one out. PS> $ws.Columns.Item(1).EntireColumn.Select() PS> $ws.Columns.Item(1).Delete() PS> # The last row of the data is filled with NaN to indicate the end of the result set. Let's delete that row. PS> $ws.Rows.Item($ws.UsedRange.Rows.Count).Select() PS> $ws.Rows.Item($ws.UsedRange.Rows.Count).Delete() PS> # Select all the data in the worksheet, create a chart, and change it to a line graph. PS> $ws.UsedRange.Select() PS> $chart = $e.Charts.Add() PS> $chart.Type = 4 Conclusion Now you should have everything you need to get access to the data behind the performance graphs. There are many ways you could take these examples and I'll leave it to the chart gurus out there to figure out interesting ways to represent the data. If anyone does find some interesting ways to manipulate this data, please let me know! Get the Flash Player to see this player.1KViews0likes5CommentsiControl 101 - #13 - Data Groups
Data Groups can be useful when writing iRules. A data group is simply a group of related elements, such as a set of IP addresses, URI paths, or document extensions. When used in conjuction with the matchclass or findclass commands, you eliminate the need to list multiple values as arguments in an iRule expression. This article will discuss how to use the methods in the LocalLB::Class interface to manage the Data Groups for use within iRules. Terminology You will first notice a mixing up of terms. A "Class" and a "Data Group" can be used interchangeably. Class was the original development term and the marketing folks came up with Data Group later on so you will see "Class" embedded in the core configuration and iControl methods, thus the LocalLB::Class interface, and "Data Groups" will most often be how they are referenced in the administration GUI. Data Groups come in 4 flavors: Address, Integer, String, and External. Address Data Groups consist of a list of IP Addresses with optional netmasks and are useful for applying a policy based on a originating subnet. Integer Data Groups hold numeric integers and, to add more confusion, are referred as "value" types in the API. String Data Groups can hold a valid ascii-based string. All of the Data Group types have methods specific to their type (ie get_string_class_list(), add_address_class_member(), find_value_class_member()). External Data Groups are special in that they have one of the previous types but there are no direct accessor methods to add/remove elements from the file. The configuration consists of a file path and name, along with the type (Address, Integer, String). You will have to use the ConfigSync file transfer APIs to remotely manipulate External Data Groups. External Data Groups are meant for very large lists of lists that change frequently. This article will focus on String Data Groups, but the usage for Address and Integer classes will be similar in nature. Initialization This article uses PowerShell and the iControl Cmdlets for PowerShell as the client environment for querying the data. The following setup will be required for the examples contained in this article. PS> Add-PSSnapIn iControlSnapIn PS> Initialize-F5.iControl -Hostname bigip_address -Username bigip_user -Password bigip_pass PS> $Class = (Get-F5.iControl).LocalLBClass Listing data groups The first thing you'll want to do is determine which Data Groups exist. The get_string_class_list() method will return a array of strings containing the names of all of the existing String based Data Groups. PS> $Class.get_string_class_list() test_list test_class carp images Creating a Data Group You have to start from somewhere, so most likely you'll be creating a new Data Group to do your bidding. This example will create a data group of image extensions for use in a URI based filtering iRule. The create_string_class() takes as input an array of LocalLB::Class::StringClass structures, each containing the class name and an array of members. In this example, the string class "img_extensions" is created with the values ".jpg", ".gif", and ".png". Then the get_string_class_list() method is called to make sure the class was created and the get_string_class() method is called to return the values passed in the create method. PS> $StringClass = New-Object -typename iControl.LocalLBClassStringClass PS> $StringClass.name = "img_extensions" PS> $StringClass.members = (".jpg", ".gif", ".png") PS> $Class.create_string_class(,$StringClass) PS> $Class.get_string_class_list() test_list test_class carp images img_extensions PS> $Class.get_string_class((,"img_extensions")) name members ---- ------- img_extensions {.gif, .jpg, .png} Adding Data Group items Once you have an existing Data Group, you most likely will want to add something to it. The add_string_class_member() method will take as input the same LocalLB::Class::StringClass structure containing the name of the Data Group and the list of new items to add to it. The following code will add two values: ".ico" and ".bmp" to the img_extensions Data Group and then will query the values of the Data Group to make sure the call succeeded. PS> $StringClass.members = (".ico", ".bmp") PS> $Class.add_string_class_member(,$StringClass) PS> $Class.get_string_class((,"img_extensions")) name members ---- ------- img_extensions {.bmp, .gif, .ico, .jpg...} Removing Data Group Items If you want to add items, you may very well want to delete items. That's where the delete_string_class_member() method comes in. Like the previous examples, it takes the LocalLB::Class::StringClass structure containing the name of the Data Group and the values you would like to remove. The following example removes the ".gif" and ".jpg" value and queries the current value of the list. PS> $StringClass.members = (".gif", ".jpg") PS> $Class.delete_string_class_member(,$StringClass) PS> $Class.get_string_class((,"img_extensions")) name members ---- ------- img_extensions {.bmp, .ico, .png} Deleting Data Groups The interface wouldn't be complete if you couldn't delete the Data Groups you previously created. The delete_class() method takes as input a string array of class names. This example will delete the img_extensions Data Group and then call the get_string_class_list() method to verify that it was deleted. PS> $Class.delete_class(,"img_extensions") PS> $Class.get_string_class_list() ts_reputation test_list test_class carp images Conclusion That's about it! Just replace the "string" with "address" and "value" in the above methods and you should be well on your way to building any type of Data Group you need for all your iRule needs. Get the Flash Player to see this player.1.1KViews0likes6CommentsiControl 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 101 - #16 - SelfIPs
In this article, I will discuss the Networking.SelfIP interface in which you have control over the IP addresses owned by the BIG-IP system that you use to access the internal and external VLANs. Initialization This article uses PowerShell and the iControl Cmdlets for PowerShell as the client environment for querying the data. The following setup will be required for the examples contained in this article. PS C:\> add-pssnapin icontrolsnapin PS C:\> initialize-f5.icontrol -HostName theboss -Username admin -Password admin True PS C:\> $SelfIP = (Get-F5.iControl).NetworkingSelfIP Determining the list of configured Self IP Addresses As is common in most interfaces, the Networking.SelfIP interface has a get_list() method that takes as input no parameters and returns a string list of all the defined SelfIP addresses. On my system, I have two Self IP addresses: 10.10.10.1 and 20.20.20.1 PS C:\> $SelfIP.get_list() 10.10.10.1 20.20.20.1 Creating a Self IP The create() method can be used to create a Self IP address. The create() method takes as input a list of self_ip addresses, vlan_names, netmasks, unit_ids, and floating_states (for floating self-ip addresses across redundant systems). If you specify STATE_DISABLED for the floating_state, you must use a unit_id value of 0. If you are creating a floating address, you must use a unit_id of 1 or 2 and make sure that you have a fixed address on the same network as the floating address with the same netmask. This example creates a fixed Self IP on the "internal2" vlan with an address of "30.30.30.1", a netmask of "255.255.255.0". PS C:\> $SelfIP.create( (,"30.30.30.1"), (,"internal2"), (,"255.255.255.0"), (,0), (,"STATE_DISABLED") ) PS C:\> $SelfIP.get_list() 10.10.10.1 20.20.20.1 30.30.30.1 VLANs You can get and set the vlan that a Self IP address is attached to with the get_vlan() and set_vlan() methods respectively. The get_vlan() method takes as input a list of Self IP addresses and the set_vlan() method takes the same list but also a list of new VLAN names to use for each respective Self IP. The following code with modify the VLAN for the previously created 30.30.30.1 Self IP to the "internal1" VLAN. It will then query the value of the VLAN to verify that the method worked. PS C:\> $SelfIP.set_vlan((,"30.30.30.1"), (,"internal1")) PS C:\> $SelfIP.get_vlan((,"30.30.30.1")) internal1 Netmasks The netmask on the Self IP definition can be retrieved or modified with the get_netmask() and set_netmask() method. Both methods take as input a list of Self IP addresses but the set_netmask() method has an additional parameter to pass in the new netmasks for the specified Self IPs. This example will modify the netmask for the previously created 30.30.30.1 Self IP to 255.255.0.0 and query the value. I will then change the value back to it's original 255.255.255.0. PS C:\> $SelfIP.set_netmask((,"30.30.30.1"), (,"255.255.0.0")) PS C:\> $SelfIP.get_netmask((,"30.30.30.1")) 255.255.0.0 PS C:\> $SelfIP.set_netmask((,"30.30.30.1"), (,"255.255.255.0")) PS C:\> $SelfIP.get_netmask((,"30.30.30.1")) 255.255.255.0 Floating Self IP Addresses If you are running a redundant setup and would like your Self IP address to float to the active unit, you can do so by creating a floating Self IP address. This is accomplished by setting the floating_state to STATE_ENABLED and setting the unit_id to either 1 or 2 corresponding the the unit_id in your redundant setup that you would like to own the configuration for the address. The following example will create a new floating Self IP address 30.30.30.2 on the internal1 vlan configured with a unit_id value of 1. PS C:\> $SelfIP.create( (,"30.30.30.2"), (,"internal1"), (,"255.255.255.0"), (,1), (,"STATE_ENABLED") ) PS C:\> $SelfIP.get_list() 10.10.10.1 20.20.20.1 30.30.30.1 30.30.30.2 Unit IDs You can get the unit id of a Self IP address with the get_unit_id() method that takes as input a list of Self IP addresses and returns the list of unit_ids for those values. For floating Self IPs you can also call the set_unit_id() method to change the value to either 1 or 2. This example changes the Unit Id value in the previously created 30.30.30.2 Self IP from 1 to 2 and then queries the value. PS C:\> $SelfIP.set_unit_id((,"30.30.30.2"), (,2)) PS C:\> $SelfIP.get_unit_id((,"30.30.30.2")) 2 Toggling between Floating and Fixed If you decided you no longer wish to have your Self IP be floating, you can change it to fixed by calling the set_floating_state() method which takes as input a list of self ips, and a list of ENABLED_STATE enum values. In this example I'm disabling the floating state by passing in STATE_DISABLED. You'll notice that the unit_id has changed from 2 to 0. I followed that up by turning it back into a floating Self IP by passing in STATE_ENABLED. In this case, you'll see that the unit_id was set to 1 as the default. If you want it back to the value of 2 as was set above, you will have to call the set_unit_id() method again specifying a value of 2 for the unit_id. PS C:\> $SelfIP.set_floating_state((,"30.30.30.2"), (,"STATE_DISABLED")) PS C:\> $SelfIP.get_unit_id((,"30.30.30.2")) 0 PS C:\> $SelfIP.set_floating_state((,"30.30.30.2"), (,"STATE_ENABLED")) PS C:\> $SelfIP.get_unit_id((,"30.30.30.2")) 1 Deleting Self IPs If your Self IP not longer has any use to you and you would like to get rid of it for good, then the delete_self_ip() method is what you want, Just pass in a list of Self IP addresses and it will wipe them out. PS C:\> $SelfIP.delete_self_ip(("30.30.30.1", "30.30.30.2")) PS C:\> $SelfIP.get_list() 10.10.10.1 20.20.20.1 Oh, and for those who like to live on the edge, there is also a delete_all_self_ips() method that takes no input and deletes all defined Self IPs on the system. Use this method wisely... Conclusion By knowing how to use the methods in the Networking.SelfIP interface, you now have all the tools to automate the creation, management, and deletion of Self IP addresses on your BIG-IP. Get the Flash Player to see this player. 20080605-iControl101_16_SelfIPs.mp31.1KViews0likes1CommentThe Wave of Change at Tech Events
I attend a lot of technology trade shows throughout the year and still remember going to my first technology event for F5 back in 2004. Small, almost high school science fair type booths handing out glossy flyers of the latest product along with our famous squeeze balls. And for the years that followed, the events, booths, sessions and presentations got bigger and better...but the expo vendors were still almost exclusive to technology providers. The attendees came from different industries and walks of life but they were there to learn about the latest tech solutions offered by these semi-tented companies. Until now. Recently, at events like MWC, RSA, VMworld and AWS re:Invent, I've started noticing a number of traditionally non-technology specific vendors exhibiting and positioning within technology events. Granted, over the years there have been a smattering of one-offs at events and of course there is CES but these days, there seems to be more historically non-tech companies appearing and exhibiting at tech events. But it makes total sense. I really took notice during Mobile World Congress earlier this year where a number of auto manufactures had huge displays showing their software-driven connected cars and how mobile technologies are enabling these internet connected devices. Most auto manufacturers are already partnering with multiple service providers and technology companies to bring mobility, connectivity and interaction to the car. And then just recently at AWS re:Invent, amongst all the technology companies, there was an apparel, shoe and fitness manufacturer highlighting the technology within their wearables. Along with clothing, these companies are becoming software and data warehouses connecting them directly to the consumer. It is not just about the cool laces anymore, it's about measuring the impact of your foot to the ground and automatically adjusting the cushion. It's about getting instant feedback about your golf swing from the shirt you are wearing. It's about measuring your vitals to ensure your activity is healthy and productive. And that is all about the embedded technology. As more home appliances, wearables, automobiles, cameras, fitness trackers, and any other of these sensors and actuators powering the Internet of Things gets connected and generates data, I suspect we'll be seeing more ovens, autos, shoes and other stuff appearing at these industry events. I think it's an interesting trend to observe. ps Related: The Digital Dress Code What are These "Things? The IoT Ready Platform IoT Ready Infrastructure IoT Influence on Society IoT Effect on Applications Technorati Tags: iot,things,events,trade shows,technology,wearables,silva Connect with Peter: Connect with F5:231Views0likes0CommentsCreating An iControl PowerShell Monitoring Dashboard With Google Charts
PowerShell is a very extensible scripting language and the fact that it integrates so nicely with iControl means you can do all sorts of fun things with it. In this tech tip, I'll illustrate how to use just a couple of iControl method calls (3 to be exact) to create a load distribution dashboard for you desktop (with a little help from the Google Chart API). Usage The arguments for this application are the address, username, and password for your BIG-IP. param ( $g_bigip = $null, $g_uid = $null, $g_pwd = $null ); The main control flow then looks for the input parameters and if they are not present, a usage message is displayed to the console indicating the required inputs. If the connection info is specified, then the standard Do-Initialize function is called which will look to see if the iControl Snapin is installed and the Initialize-F5.iControl cmdlet is called to initialize the connection to the BIG-IP. If an error occurs during the connection, then an error is logged and the application exits. function Write-Usage() { Write-Host "Usage: iControlDashboard.ps1 host uid pwd"; exit; } 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 ) { Run-Dashboard } else { Write-Error "ERROR: iControl subsystem not initialized" Kill-Browser } Global Variables This appliction will make use of the Google Chart APIs to generate graphs and as such we need a browser to render it in. Since we will be interacting with another process (in this case Internet Explorer), it is probably a good idea to gracefully shutdown if an error occurs. A generic Exception Trap is created to log the error and shutdown the application properly. Trap [Exception] { Write-Host $("TRAPPED: " + $_.Exception.GetType().FullName); Write-Host $("TRAPPED: " + $_.Exception.Message); Kill-Browser Exit; } A few global variables are used to make the app more configurable. You can specify the title that comes up in the browsers header as well as the graph size for each report graph along with the chart type and polling interval. I opted for a pie chart but other options are available that may or may not be to your liking. At this point I go ahead and create a empty browser window and point it to the about:blank page giving us a context to manipulate the contents of the browser window. I make the window visible and set it to full screen theatermode. $g_title = "iControl PowerShell Dashboard"; $g_graphsize = "300x150"; $g_charttype = "p"; $g_interval = 5; $g_browser = New-Object -com InternetExplorer.Application; $g_browser.Navigate2("About:blank"); $g_browser.Visible = $true; $g_browser.TheaterMode = $true; Browser Control The following functions are to control the browser and the data going into it. The Refresh-Browser function takes as input the HTML to display. The Document object is then accessed from the InternetExplorer.Application object and from there we can access the DocumentElement. Then we set the InnerHTML to the input parameter $html_data and that is displayed in the browser window. #------------------------------------------------------------------------- # function Refresh-Browser #------------------------------------------------------------------------- function Refresh-Browser() { param($html_data); if ( $null -eq $g_browser ) { Write-Host "Creating new Browser" $g_browser = New-Object -com InternetExplorer.Application; $g_browser.Navigate2("About:blank"); $g_browser.Visible = $true; $g_browser.TheaterMode = $true; } $docBody = $g_browser.Document.DocumentElement.lastChild; $docBody.InnerHTML = $html_data; } #------------------------------------------------------------------------- # function Kill-Browser #------------------------------------------------------------------------- function Kill-Browser() { if ( $null -ne $g_browser ) { $g_browser.TheaterMode = $false; $g_browser.Quit(); $g_browser = $null; } } Main Application Loop The main logic for this application is a little infinite loop where we call the Get-Data function, refresh the browser with the newly acquired report, and sleep for the configured interval until the next poll occurs. function Run-Dashboard() { while($true) { #Write-Host "Requesting data..." $html_data = Get-Data; Refresh-Browser $html_data; Start-Sleep $g_interval; } } Generating the Report Here's where all the good stuff happens. The Get-Data function will make a few iControl calls (LocalLB.Pool.get_list(), LocalLB.PoolMember.get_all_statistics(), and LocalLB.PoolMember.get_object_status()) and from that generate a HTML report with charts generated with the Google Chart API. The local variable $html-data is used to store the resulting HTML data that will be sent to Internet Explorer for display and we start off the function by filling in the title and start of the report table. Then the three previously mentioned iControl calls are made and the resulting values are stored in local varables for later reference. The main loop here goes over each of the pools in the MemberStatisticsA local array variable. A few hash tables and counters are created and then we loop over each pool member for the current pool we are processing. Then entries are added to the local hash tables for total connections, current connections, bytes in, and status for later reference. Also a sum of all the values for those hash tables are stored so we can calculate percentages later on. At this point we will use the hash tables for generating the report. Each numeric value is calculated into a percent and chart variables are created to contain the data as well as the labels for the generated pie charts. Once all the number crunching has been performed the actual chart images are specified in the $chart_total, $chart_current, and $chart_bytes variables and the row in the report for the given pool is added to the $html_data variable. function Get-Data() { # TODO - get connection statistics $now = [DateTime]::Now; $html_data = "<html> <head> <title>$g_title</title> </head> <body> <center><h1>$g_title</h1><br/><h2>$now</h2></center> <center><table border='0' bgcolor='#C0C0C0'><tr><td><table border='0' cellspacing='0' bgcolor='#FFFFFF'>"; $html_data += " <tr bgcolor='#C0C0C0'><th>Pool</th><th>Total Connections</th><th>Current Connections</th><th>Bytes In</th></tr>"; $charts_total = ""; $charts_current = ""; $charts_bytes = ""; $PoolList = (Get-F5.iControl).LocalLBPool.get_list() | Sort-Object; $MemberStatisticsA = (Get-F5.iControl).LocalLBPoolMember.get_all_statistics($PoolList) $MemberObjectStatusAofA = (Get-F5.iControl).LocalLBPoolMember.get_object_status($PoolList); # loop over each pool $i = 0; foreach($MemberStatistics in $MemberStatisticsA) { $hash_total = @{}; $hash_current = @{}; $hash_bytes = @{}; $hash_status = @{}; $sum_total = 0; $sum_current = 0; $sum_bytes = 0; $PoolName = $PoolList[$i]; # loop over each member $MemberStatisticEntryA = $MemberStatistics.statistics; foreach($MemberStatisticEntry in $MemberStatisticEntryA) { $member = $MemberStatisticEntry.member; $addr = $member.address; $port = $member.port; $addrport = "${addr}:${port}"; $StatisticA = $MemberStatisticEntry.statistics; $total = Extract-Statistic $StatisticA "STATISTIC_SERVER_SIDE_TOTAL_CONNECTIONS" [long]$sum_total += $total; $hash_total.Add($addrport, $total); $current = Extract-Statistic $StatisticA "STATISTIC_SERVER_SIDE_CURRENT_CONNECTIONS" $sum_current += $current; $hash_current.Add($addrport, $current); $bytes = Extract-Statistic $StatisticA "STATISTIC_SERVER_SIDE_BYTES_IN" [long]$sum_bytes += $bytes; $hash_bytes.Add($addrport, $bytes); $color = Extract-Status $MemberObjectStatusAofA[$i] $member; $hash_status.Add($addrport, $color); } $chd_t = ""; $chd_c = ""; $chd_b = ""; $chl_t = ""; $chl_c = ""; $chl_b = ""; $chdl_t = ""; $chdl_c = ""; $chdl_b = ""; $tbl_t = ""; $tbl_c = ""; $tbl_b = ""; # enumerate the total connections foreach($k in $hash_total.Keys) { $member = $k; $v_t = $hash_total[$k]; $v_c = $hash_current[$k]; $v_b = $hash_bytes[$k]; $color = $hash_status[$k]; $div = $sum_total; if ($div -eq 0 ) { $div = 1; } $p_t = ($v_t/$div)*100; $div = $sum_current; if ($div -eq 0 ) { $div = 1; } $p_c = ($v_c/$div)*100; $div = $sum_bytes; if ($div -eq 0 ) { $div = 1; } $p_b = ($v_b/$div)*100; if ( $chd_t.Length -gt 0 ) { $chd_t += ","; $chd_c += ","; $chd_b += ","; } $chd_t += $p_t; $chd_c += $p_c; $chd_b += $p_b; if ( $chl_t.Length -gt 0 ) { $chl_t += "|"; $chl_c += "|"; $chl_b += "|"; $chdl_t += "|"; $chdl_c += "|"; $chdl_b += "|"; } $chl_t += "$member"; $chl_c += "$member"; $chl_b += "$member"; $chdl_t += "$member - $v_t"; $chdl_c += "$member - $v_c"; $chdl_b += "$member - $v_b"; #$alt_t += "($member,$v_t)"; #$alt_c += "($member,$v_c)"; #$alt_b += "($member,$v_b)"; $tbl_t += "<tr><td bgcolor='$color'>$member</td><td align='right'>$v_t</td></tr>"; $tbl_c += "<tr><td bgcolor='$color'>$member</td><td align='right'>$v_c</td></tr>"; $tbl_b += "<tr><td bgcolor='$color'>$member</td><td align='right'>$v_b</td></tr>"; } if ( $sum_total -gt 0 ) { $chart_total = "<img src='http://chart.apis.google.com/chart? chs=$g_graphsize &chd=t:$chd_t &cht=$g_charttype &chdl=$chl_t' alt='Total Connections for pool $PoolName' />"; } else { $chart_total = ""; } if ( $sum_current -gt 0 ) { $chart_current = "<img src='http://chart.apis.google.com/chart? chs=$g_graphsize &chd=t:$chd_c &cht=$g_charttype &chdl=$chl_c' alt='Current Connections for pool $PoolName' />"; } else { $chart_current = ""; } if ( $sum_bytes -gt 0 ) { $chart_bytes = "<img src='http://chart.apis.google.com/chart? chs=$g_graphsize &chd=t:$chd_b &cht=$g_charttype &chdl=$chl_b' alt='Incoming Bytes for pool $PoolName' />"; } else { $chart_current = ""; } if ( $i -gt 0 ) { $html_data += "<tr><td colspan='4'><hr/></td></tr>"; } $html_data += " <tr><th nowrap='nowrap'>$PoolName</th> <td valign='bottom'>$chart_total<br/> <center><table border='1'><tr><th>Member</th><th>Value</th></tr>$tbl_t</table> </td> <td valign='bottom'>$chart_current<br/> <center><table border='1'><tr><th>Member</th><th>Value</th></tr>$tbl_c</table> </td> <td valign='bottom'>$chart_bytes<br/> <center><table border='1'><tr><th>Member</th><th>Value</th></tr>$tbl_b</table> </td> </tr>"; $i++; } $html_data += "</table></td></tr></table></body></html>"; return $html_data; } Utility Functions It's always useful to extract common code into utility functions and this application is no exception. In here I've got a Convert-To64Bit function that takes the high and low 32 bits of a 64 bit number and does the math to convert them into a native 64 bit value. The Extract-Statistic function takes as input a Common.Statsistic Array along with a type to look for in that array. It loops over the array of Statistic values and returns the 64 bit value of the match, if one is found. And finally the Extract-Status function is used to look through the returned value from the LocalLB.PoolMember.get_object_status iControl method for a specific pool member. This function returns a color to display in the generated HTML table, green for good, red for bad. The only way a green will show up will be if both it's availability_status and enabled_status values are AVAILABILITY_STATUS_GREEN and ENABLED_STATUS_ENABLED respectively. function Convert-To64Bit() { param($high, $low); $low = [Convert]::ToString($low,2).PadLeft(32,'0') if($low.length -eq "64") { $low = $low.substring(32,32) } return [Convert]::ToUint64([Convert]::ToString($high,2).PadLeft(32,'0')+$low,2); } function Extract-Statistic() { param($StatisticA, $type); $value = -1; foreach($Statistic in $StatisticA) { if ( $Statistic.type -eq $type ) { $value = Convert-To64Bit $Statistic.value.high $Statistic.value.low; break; } } return $value; } function Extract-Status() { param($MemberObjectStatusA, $IPPortDefinition); $color = "#FF0000"; foreach($MemberObjectStatus in $MemberObjectStatusA) { if ( ($MemberObjectStatus.member.address -eq $IPPortDefinition.address) -and ($MemberObjectStatus.member.port -eq $IPPortDefinition.port) ) { $availability_status = $MemberObjectStatus.object_status.availability_status; $enabled_status = $MemberObjectStatus.object_status.enabled_status; if ( ($availability_status -eq "AVAILABILITY_STATUS_GREEN") -and ($enabled_status -eq "ENABLED_STATUS_ENABLED" ) ) { $color = "#00FF00"; } } } return $color; } Running The Application After running the application on the console, Internet Explorer will be created in Theater Mode (Full Screen) and will look something like this. My system is somewhat inactive so you'll see that some of the charts are missing. This was by design in that charts with no data are not very informative. Assuming you have traffic across all your pools, charts will be created. Extending This Application This application merely looks at load distribution and state for members within the pools. It would be trivial to change or extend the types of charts presented. iControl provides you with all the data you need to build your own monitoring dashboard regardless of the types of metrics you would like to keep an eye on. For the full application, check out the PsiControlDashboard entry in the iControl CodeShare Get the Flash Player to see this player. 20081106-iControlApps-15-iControlDashboard.mp3853Views0likes28Comments