series-icontrol-101
20 TopicsiControl 101 - #05 - Exceptions
When designing the iControl API, we had two choices with regards to API design. The first option was to build our methods to return status codes as return values and outbound data as "out" parameters. The other option was to make use of exception handling as a way to return non-success results allowing the use of the return value for outbound data. This article will discuss why we went with the later and how you can build exception handling in your client application to handle the cases where your method calls fail. Camp 1: return codes As I mentioned above, there are two camps for API design. The first are the ones that return status codes as return values and external error methods to return error details for a given error code. For you developers out there who still remember your "C" programming days, this may look familiar: struct stat sb; char [] dirname = "c:\somefile.txt"; if ( 0 != stat(dirname, &sb) ) { printf("Problem with file '%s'; error: %s\n", dirname, strerror(errno)); } You'll notice that the "stat" method to determine the file status returns an integer that is zero for success. When it's non, zero a global variable is set (errno) indicating the error number, and the "strerror" method can then be called with that error number to determine the user readable error string. There is a problem with this approach, as illustrated by the "Semipredicate problem", in which users of the method need to write extra code to distinguish normal return values from erroneous ones. Camp 2: Exceptions The other option for status returns is to make use of exception handling. Exception handling makes use of the fact that when error conditions occur, the method call will not return via it's standard return logic but rather the information on the exception will be stored and the call stack is unwound until a handler for that exception is found. This code sample in C# is an example of making use of exceptions to track errors: try { Microsoft.Win32.RegistryKey cu = Microsoft.Win32.Registry.CurrentUser; Microsoft.Win32.RegistryKey subKey = cu.OpenSubKey("some_bogus_path"); } catch(Exception ex) { Console.WriteLine("Exception: " + ex.Message.ToString()); } iControl works with Exceptions Luckily for us, the SOAP specification takes into account the exception model by adding an alternate to the SOAP Response. A SOAPFault can be used to return error information for those cases where the method calls cannot be completed due to invalid arguments or other system configuration issues. A SOAPFault for an invalid parameter to Networking::VLAN::get_vlan_id() looks like this: <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap-env:body> <soap-env:fault> <faultcode xsi:type="xsd:string">SOAP-ENV:Server</faultcode> <faultstring xsi:type="xsd:string">Exception caught in Networking::VLAN::get_vlan_id() Exception: Common::OperationFailed primary_error_code : 16908342 (0x01020036) secondary_error_code : 0 error_string : 01020036:3: The requested VLAN (foo) was not found.</faultstring> </soap-env:fault> </soap-env:body> </SOAP-ENV:Envelope> The faultcode element indicates that the fault occurred on the server (ie, it wasn't a client connectivity issue) and the faultstring contains the details. You may ask why we include all our fault data in the return string and not in the new SOAPFault elements defined in SOAP v1.2? Well, when we first released our iControl interfaces, SOAP v1.0 was just coming out and they were not defined yet. At this point we cannot change our fault format for risk of breaking backward compatibility in existing iControl applications. An added benefit of using exceptions is that it makes client code much cleaner as opposed to using "out" type parameters. Wouldn't you much your code look like this: String [] pool_list = m_interfaces.LocalLBPool.get_list() As opposed to this: String [] pool_list = null; int rc = m_interfaces.LocalLBPool.get_list(pool_list); Types of Exceptions If you look in the Common module in the SDK, you'll find a list of the exceptions supported in the iControl methods. The most common of them is "OperationFailed" but in some cases you'll see AccessDenied, InvalidArgument, InvalidUser, NoSuchInterface, NotImplemented, and OutOfMemory crop up. The SDK documentation for each method lists the Exceptions that can be raised by each method if you need to narrow down what each method will give you. Processing Faults In almost all cases, it is sufficient to just know that an exception occurred. The use of the method will likely give you the reason for a possible fault. If you are trying to create a pool and it fails, odds are you passed in an existing pool name as an input parameter. But for those situations where you need to get detailed info on why an exception happened how do you go about it? Given that the Exceptions we return are all encoded as text in the faultstring field, it would be handy to have some tools to help you decipher that data. Good thing you are reading this tech tip! Here is a sample C# class to parse and identify iControl exceptions. This could easily be ported to another language of your choice. using System; using System.Collections.Generic; using System.Text; namespace iControlProgram { public class ExceptionInfo { #region Private Member Variables private Exception m_ex = null; private Type m_exceptionType = null; private String m_message = null; private String m_location = null; private String m_exception = null; private long m_primaryErrorCode = -1; private String m_primaryErrorCodeHex = null; private long m_secondaryErrorCode = -1; private String m_errorString = null; private bool m_IsiControlException = false; #endregion #region Public Member Accessors public System.Type ExceptionType { get { return m_exceptionType; } set { m_exceptionType = value; } } public String Message { get { return m_message; } set { m_message = value; } } public String Location { get { return m_location; } set { m_location = value; } } public String Exception { get { return m_exception; } set { m_exception = value; } } public long PrimaryErrorCode { get { return m_primaryErrorCode; } set { m_primaryErrorCode = value; } } public String PrimaryErrorCodeHex { get { return m_primaryErrorCodeHex; } set { m_primaryErrorCodeHex = value; } } public long SecondaryErrorCode { get { return m_secondaryErrorCode; } set { m_secondaryErrorCode = value; } } public String ErrorString { get { return m_errorString; } set { m_errorString = value; } } public bool IsiControlException { get { return m_IsiControlException; } set { m_IsiControlException = value; } } #endregion #region Constructors public ExceptionInfo() { } public ExceptionInfo(Exception ex) { parse(ex); } #endregion #region Public Methods public void parse(Exception ex) { m_ex = ex; ExceptionType = ex.GetType(); Message = ex.Message.ToString(); System.IO.StringReader sr = new System.IO.StringReader(Message); String line = null; try { while (null != (line = sr.ReadLine().Trim())) { if (line.StartsWith("Exception caught in")) { Location = line.Replace("Exception caught in ", ""); } else if (line.StartsWith("Exception:")) { Exception = line.Replace("Exception: ", ""); } else if (line.StartsWith("primary_error_code")) { line = line.Replace("primary_error_code : ", ""); String[] sSplit = line.Split(new char[] { ' ' }); PrimaryErrorCode = Convert.ToInt32(sSplit[0]); PrimaryErrorCodeHex = sSplit[1]; } else if (line.StartsWith("secondary_error_code")) { SecondaryErrorCode = Convert.ToInt32(line.Replace("secondary_error_code : ", "")); } else if (line.StartsWith("error_string")) { ErrorString = line.Replace("error_string : ", ""); } } IsiControlException = (null != Location) && (null != Exception); } catch (Exception) { } } #endregion } } And here's a usage of the above ExceptionInfo class in a snippet of code that is making use of the iControl Assembly for .NET. ... try { m_interfaces.NetworkingVLAN.get_vlan_id(new string[] { "foobar" }); } catch (Exception ex) { ExceptionInfo exi = new ExceptionInfo(ex); if (exi.IsiControlException) { Console.WriteLine("Exception: " + exi.Exception); Console.WriteLine("Location : " + exi.Location); Console.WriteLine("Primary Error : " + exi.PrimaryErrorCode + "(" + exi.PrimaryErrorCodeHex + ")"); Console.WriteLine("Seconary Error : " + exi.SecondaryErrorCode); Console.WriteLine("Description : " + exi.ErrorString); } } Conclusion The flexibility in our Exception implementation in iControl, along with some utilities to help process that information, you should help you well on your way to building a rock solid iControl application. Get the Flash Player to see this player.1.8KViews0likes0CommentsiControl 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 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.mp31KViews0likes1CommentiControl 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 - #06 - File Transfer APIs
The main use we see for iControl applications is with the automation of control features such as adding, removing, enabling, and disabling objects. Another key step in multi-device management is the automation of applying hotfixes and other software updates as well as the downloading of configurations for archival and disaster recovery purposes. iControl has a set of methods that enable the uploading and downloading of files for these purposes. This article will discuss these "file transfer" APIs and how you can use them for various management purposes. The File Transfer APIs The API methods uses to transfer files to and from the device can be found in the System::ConfigSync interface. You may ask: Why are they ConfigSync interface? The answer is quite simple actually. In our first version of iControl, we had the need to transfer configurations across devices in a HA pair as part of the Configuration Sync process. So in doing so, we introduced the upload_configuration() and download_configuration() methods. When we later added more generic file transfer APIs, it seemed logical to place them next to the pre-existing configuration transfer methods. So, that's the reason... The following methods are used to download content: FileTransferContext System::ConfigSync::download_configuration( in String config_name, in long chunk_size, inout long file_offset ); FileTransferContext System::ConfigSync::download_file( in String file_name, in long chunk_size, inout long file_offset ); And the following two methods are used to upload content: void System::ConfigSync::upload_configuration( in String config_name, in System::ConfigSync::FileTransferContext file_context ); void System::ConfigSync::upload_file( in String file_name, in System::ConfigSync::FileTransferContext file_context ); The above methods use the following enum and structure as part of the control enum Common::FileChainType { FILE_UNDEFINED = 0 FILE_FIRST = 1, FILE_MIDDLE = 2, FILE_UNUSED = 3, FILE_LAST = 4, FILE_FIRST_AND_LAST }; struct System.::ConfigSync::FileTransferContext { char [] file_data, Common::FileChainType chain_type }; Chunks Due to the limitations with SOAP's with regards to payload and processing of large messages, we designed the file transfer APIs to work in "chunks". This means that for a multi-megabyte file, you a loop in your code to send up "chunks" in whatever chunk sizes you wish from 1 byte to 100's of Ks. We recommend not getting too extreme on either end of the chunk size ranges. Typically we recommend 64 to 256k per chunk. How to use the download methods. The process for downloading content is fairly straightforward. In this example, we'll use the download_configuration method. 1. Make a call to the download_configuration method with a given configuration name (a list of existing configurations can be returned from ConfigSync::get_configuration_list) with the requested chunk_size (ie 64k) and the starting file_offset of 0. 2. The first response will come back in the FileTransferContext with the data and the FileChainType describing whether this was the first chunk, a middle chunk, the last chunk, or the first and last chunk. 3. take the encoded data stored in the FileTransferContext.file_data array and save it locally. 4. If the FileChainType is FILE_LAST or FILE_FIRST_AND_LAST, you are done. 5. Otherwise, use the incremented file_offset go to step #1. The following snippet of code taken from the iControl SDK's ConfigSync C# sample application illustrates how to deal with downloading a file with an unknown size. void handle_download(string config_name, string local_file) { ConfigSync.SystemConfigSyncFileTransferContext ctx; long chunk_size = (64*1024); long file_offset = 0; bool bContinue = true; FileMode fm = FileMode.CreateNew; if ( File.Exists(local_file) ) { fm = FileMode.Truncate; } FileStream fs = new FileStream(local_file, fm); BinaryWriter w = new BinaryWriter(fs); while ( bContinue ) { ctx = ConfigSync.download_configuration(config_name, chunk_size, ref file_offset); // Append data to file w.Write(ctx.file_data, 0, ctx.file_data.Length); Console.WriteLine("Bytes Transferred: " + file_offset); if ( (CommonFileChainType.FILE_LAST == ctx.chain_type) || (CommonFileChainType.FILE_FIRST_AND_LAST == ctx.chain_type) ) { bContinue = false; } } w.Close(); } How to use the upload methods The upload methods work in a similar way but in the opposite direction. To use the upload_configuration configuration method, you would use the following logic. 1. Determine the chunk size you are going to use (64k - 256k recommended) and fill the file_data array with the first chunk of data. 2. Set the FileChainType to FILE_TYPE_FIRST_AND_LAST if all your data can fit in the first chunk size. 3. Set the FileChainType to FILE_FIRST if you fill up the data with your chunk size and there is more data to follow. 4. Make a call to upload_configuration with the configuration name and the FileTransferContext with the data and the FileChainType. 5. If the data has all been sent, stop processing. 6. Else if the remaining data will able to fit in the given chunk size, set the FileChainType to FILE_LAST, otherwise set it to FILE_MIDDLE. 7. Fill the file_data with the next chunk of data. 8. Goto Step #4. The following example taken from the iControl SDK ConfigSync perl sample and illustrates taking a local configuration file and uploading it to the device. sub uploadConfiguration() { my ($localFile, $configName) = (@_); $success = 0; $bContinue = 1; $chain_type = $FILE_FIRST; $preferred_chunk_size = 65536; $chunk_size = 65536; $total_bytes = 0; open(LOCAL_FILE, "<$localFile") or die("Can't open $localFile for input: $!"); binmode(LOCAL_FILE); while (1 == $bContinue ) { $file_data = ""; $bytes_read = read(LOCAL_FILE, $file_data, $chunk_size); if ( $preferred_chunk_size != $bytes_read ) { if ( $total_bytes == 0 ) { $chain_type = $FILE_FIRST_AND_LAST; } else { $chain_type = $FILE_LAST; } $bContinue = 0; } $total_bytes += $bytes_read; $FileTransferContext = { file_data => SOAP::Data->type(base64 => $file_data), chain_type => $chain_type }; $soap_response = $ConfigSync->upload_configuration ( SOAP::Data->name(config_name => $configName), SOAP::Data->name(file_context => $FileTransferContext) ); if ( $soap_response->fault ) { print $soap_response->faultcode, " ", $soap_response->faultstring, "\n"; $success = 0; $bContinue = 0; } else { print "Uploaded $total_bytes bytes\n"; $success = 1; } $chain_type = $FILE_MIDDLE; } print "\n"; close(LOCAL_FILE); return $success; } Other methods of data transfer The two methods illustrated above are specific to system configurations. The more generic upload_file() and download_file() commands may be used to do things like backing up other system files as well as, but not limited to, uploading hotfixes to be later installed with the System::SoftwareManagement::install_hotfix() method. The usage of those methods is identical to the configuration transfer methods except in the fact that the config_name parameter is now replaced with a fully qualified file system name. Note that there are some restrictions as to the file system locations that are readable and writable. You wouldn't want to accidentally overwrite the system kernel with your latest hotfix would you? Conclusion Hopefully this article gave you some insights on how to use the various file transfer APIs to manipulate file system content on your F5 devices. Get the Flash Player to see this player.849Views0likes2CommentsiControl 101 - #08 - Partitions
In a previous article, I discussed user management and the concepts of user roles. User roles form half of what we refer to as Administrative Domains. The second half of Administrative Domains are Configuration Partitions. Configuration Partitions allow you to control access to BIG-IP system resources by BIG-IP administrative users. This article will discuss how to manage Configuration Partitions with the iControl programming interfaces. Configuration partitions are essentially logical containers that you can use to group local traffic management objects. Think of it as different "buckets" you can place your virtuals, pools, rules, etc into. You can then control who can peek into those buckets by the user name and, for those lucky enough to be able to peek in, what they can do by their user roles. There is always at least one partition. For new installs, or upgrades, a special partition called "Common" is created that is the default for all traffic management objects. You can create many partitions with zero or more users assigned access to them. The following examples will illustrate how to interact with the partitions. The samples use the iControl CmdLets for PowerShell so check out the PowerShell Labs Project if you want to test them out for yourself. Initializing the iControl PowerShell SnapIn For a new PowerShell instance, you will need to add the iControl SnapIn and initialize it for your BIG-IP. PS> Add-PSSnapIn iControlSnapIn PS> Initialize-F5.iControl -hostname bigip_address -username bigip_username -password bigip_password PS> $ic = Get-F5.iControl Querying Partitions The Management::Partition::get_partition_list() method will return the list of all Configuration partitions along with the corresponding description attribute. PS> $ic.ManagementPartition.get_list() partition_name description -------------- ----------- Common Repository for system objects and shared objects. Accounting Partition for Accounting Department DevCentral DevCentral administrative partition Finance Partition for Finance Departmen Creating Partitions So, let's say you've got a new department that needs a separate configuration space on your BIG-IP. Creating a new Partition is as easy as this: PS > $partition = New-Object -TypeName iControl.ManagementPartitionAuthZPartition PS > $partition.partition_name = "NewDept" PS > $partition.description = "New Department" PS > $ic.ManagementPartition.create_partition( (,$partition) ) PS> $ic.ManagementPartition.get_list() partition_name description -------------- ----------- Common Repository for system objects and shared objects. NewDept New Department Accounting Partition for Accounting Department DevCentral DevCentral administrative partition Finance Partition for Finance Departmen Deleting Partitions Now, let's suppose that the NewDept group that insisted on a separate configuration space, has gone 2 years without adding a single object and they are now no longer anywhere to be seen. Well, it's very simple to delete that partition you created for them with the delete_partition() method. PS> $ic.ManagementPartition.delete_partition( (,"NewDept") ) PS> $ic.ManagementPartition.get_list() partition_name description -------------- ----------- Common Repository for system objects and shared objects. DevCentral DevCentral administrative partition Accounting Partition for Accounting Department Finance Partition for Finance Departmen Active Partitions So, you've found out how to query, create, and delete partitions. The last piece of info you need to know is how you specify which partition a object is going into when you are using all of the other 2500+ iControl methods that deal directly with the configuration objects. When you initialize an iControl connection, you authenticate with a given username. How do you determine which partition this user context is operating in? Well, it's all right there with the other Partition methods. You can use the get_active_partition() method to query the current active partition for a given user and the set_active_partition() method to change the current active partition. A reason why you might want to do this is so that when you are logging in as admin, you can create and manage objects in various partitions buckets. Here's an example of querying the current partition and then changing it. PS> $ic.ManagementPartition.get_active_partition() Common PS> $ic.ManagementPartition.set_active_partition( (,"DevCentral") ) PS> $ic.ManagementPartition.get_active_partition() DevCentral Conclusion By combining the User Roles aspects of the Management::UserManagement interface and the Partition methods in the Management::Partition interface, you have all the tools you need to implement and manage Administrative Domains programmatically. Get the Flash Player to see this player.804Views0likes2CommentsiControl 101 - #09 - iRules
iRules are the internal scripting language of the BIG-IP and can be used to attain full control of traffic flowing through your network. But, did you know that you can automate the management of iRules across your Virtual Servers with iControl? This article will discuss the iControl methods that can be used to query, create, delete iRules as well as how to apply them to Virtual Servers. The Interfaces There are two interfaces that I will highlight in this article. For the management of the iRules themselves such as querying, creating, and deleting, you will want to look in the LocalLB::Rule interface. This fairly small interface (11 methods) gives you the power to have full control of the management of the iRules on the system. Once you have created your iRules and are ready to put them into production, you'll want to head over to the LocalLB::VirtualServer interface. iRules are attributes of a Virtual Server so, consequently, there are methods in there to add and remove configured rules to the specified Virtuals. The following examples will use the iControl CmdLet's for Microsoft PowerShell as the iControl client. If you are using one of the many other languages that are supported with iControl, the logic will be the same. For these examples, the following setup is required for the iControl Cmdlets. PS> Add-PSSnapIn iControlSnapIn PS> Initialize-F5.iControl -Hostname bigip_address -Username username -Password password PS> $ic = Get-F5.iControl Determining the iRules on your system. The LocalLB::Rule::query_all_rules() method will return an array of RuleDefinition structures, one for each iRule. The RuleDefinition Structure contains the "rule_name" and "rule_definition". The following example will call the query_all_rules() method and list out all of the Rule names followed by the actual iRule content (rule_definition). PS> $RuleDefinitions = $ic.LocalLBRule.query_all_rules() PS> $RuleDefinitions | Format-List rule_name : _sys_auth_ldap rule_definition : when HTTP_REQUEST { if {not [info exists tmm_auth_http_sids(ldap)]} { set tmm_auth_sid [AUTH::start pam default_ldap] set tmm_auth_http_sids(ldap) $tmm_auth_sid if {[info exists tmm_auth_subscription]} { ... The LocalLB::Rule::query_rule() method is also available if you want to specify a specific list of iRules to query. Creating an iRule The LocalLB::Rule::create() method takes in an array of RuleDefinition structures defining the new iRules to be created. For this example, I am allocating a single RuleDefinition structure for a single iRule and passing in an array size of one to the create method. I will then call the query_rule() method to show you the creation took effect. PS> $RuleDefinition = New-Object -TypeName iControl.LocalLBRuleRuleDefinition PS> $RuleDefinition.rule_name = "MyCoolRule" PS> $RuleDefinition.rule_definition = @" >> when HTTP_REQUEST { >> log local0. {URI: [HTTP::uri]} >> } >> "@ >> PS> $ic.LocalLBRule.create( (,$RuleDefinition) ) PS> $ic.LocalLBRule.query_rule( (,"MyCoolRule") ) | Format-List rule_name : MyCoolRule rule_definition : when HTTP_REQUEST { log local0. {URI: [HTTP::uri]} } Deleting an iRule Deleting an iRule is as simple as passing the rule name to the LocalLB::Rule::delete_rule() method. The following sample will delete the previously created "MyCoolRule" iRule and then attempt to query it again and show the exception that is thrown as a result of the requested iRule name not existing on the system. PS> $ic.LocalLBRule.delete_rule( (,"MyCoolRule") ) PS> $ic.LocalLBRule.query_rule( (,"MyCoolRule") ) | format-list Exception calling "query_rule" with "1" argument(s): "Exception caught in LocalLB::Rule::query_rule() Exception: Common::OperationFailed primary_error_code : 16908342 (0x01020036) secondary_error_code : 0 error_string : 01020036:3: The requested rule (MyCoolRule) was not found." At line:1 char:27 + $ic.LocalLBRule.query_rule( <<<< (,"MyCoolRule") ) | format-list Applying the iRule to a Virtual Server Now, go ahead and recreate that iRule by calling the create() method again. Once you've got the iRule in place, You'll want to jump over to the LocalLB::VirtualServer interface and it's add_rule() method. This method takes an array of virtual servers and a 2-d array for the iRules (1-n iRules for each virtual server passed in). PS> # Create the iRule PS> $ic.LocalLBRule.create( (,$RuleDefinition) ) PS> PS> # Allocate and populate parameters PS> $VirtualServerRule = New-Object -TypeName iControl.LocalLBVirtualServerVirtualServerRule PS> $VirtualServerRule.rule_name = "MyCoolRule" PS> $VirtualServerRule.priority = 500 PS> $VirtualServerRules = New-Object -TypeName "iControl.LocalLBVirtualServerVirtualServerRule[][]" 1,1 PS> $VirtualServerRules[0][0] = $VirtualServerRule PS> PS> # Call add_rule to add the iRule to the specified virtual servers resource list. PS> $ic.LocalLBVirtualServer.add_rule( (,"virtual_name"), $VirtualServerRules ) PS> PS> # Call get_rule to make sure the add_rule call worked. PS> $ic.LocalLBVirtualServer.get_rule( (,"virtual_name") ) | format-list rule_name : MyCoolRule priority : 500 Conclusion While the iRule Editor makes this easy to do on a single system, there may be a time when you need to deploy an iRule to multiple systems and an automated approach sounds more appealing than the manual effort required for mass deployment. In this case, whip out your favorite programming language, spend a couple of minutes building a solution, click "GO" and grab a cup of coffee and relax. Get the Flash Player to see this player.799Views0likes1CommentiControl 101 - #20 - Port Lockdown
A Self IP address is an IP address that you associate with a VLAN, to access hosts in that VLAN. By virtue of its netmask, a self IP address represents an address space, that is, a range of IP addresses spanning the hosts in the VLAN, rather than a single host address. You can associate self IP addresses not only with VLANs, but also with VLAN group. Self IP addresses serve two purposes. First, when sending a message to a destination server, the BIG-IP system uses the self IP addresses of its VLANs to determine the specific VLAN in which a destination server resides. Second, a self IP address serves as the default route for each destination server in the corresponding VLAN. In this case, the self IP address of a VLAN appears as the destination IP address in the packet header when the server sends a response to the BIG-IP system. Each self IP address has a feature known as port lockdown. Port lockdown is a security feature that allows you to specify particular UDP and TCP protocols and services from which the self IP address can accept traffic. This article will dicuss how to use the iControl API to manage Port Lockdown Access Lists. Usage The following code samples will build a PowerShell command line application allowing control over Self IP access lists. This program takes as input the bigip, username, and password as well as a subcommand and optional parameters. Usage is displayed with the Write-Usage function. param ( $g_bigip = $null, $g_uid = $null, $g_pwd = $null, $g_cmd = $null, $g_selfip = $null, $g_arg1 = $null, $g_arg2 = $null, $g_arg3 = $null, $g_arg4 = $null ); Set-PSDebug -strict; function Write-Usage() { Write-Host @" Usage: SelfIPPortLockdown.ps1 host uid pwd [options] options ------- list - Get a list of Self IPs getaccesslist <selfip> - Gets the access lists for the specified self IP. addaccesslist <selfip> <mode> <protocol> <port> - Adds the list of access methods, with optional protocols/ports, for the specified self IP. deleteaccesslist <selfip> <mode> <protocol> <port> - Deletes protocols and ports from the allow access list for the specified self IP. getdefaccesslist - Gets the default protocol/port access list on which access is allowed. adddefaccesslist <protocol> <port> - Adds to the default list of protocols/ports on which access is allowed. removedefaccesslist <protocol> <port> - Remove protocols and ports from the default list of protocols/ports on which access is allowed. "@; exit; } Initialization As is with all of my iControl PowerShell scripts, validation is made as to whether the iControlSnapin is loaded into the current powershell context. The Initialize-F5.iControl cmdlet is then called to setup the connection to the BIG-IP for subsequent calls. The main application logic checks for the passed in command and then passes control to one of the location functions defined 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; } #------------------------------------------------------------------------- # Main Application Logic #------------------------------------------------------------------------- if ( ($g_bigip -eq $null) -or ($g_uid -eq $null) -or ($g_pwd -eq $null) ) { Write-Usage; } if ( Do-Initialize ) { switch ($g_cmd) { "" { Get-SelfIPList; } "getaccesslist" { Get-AccessList $g_selfip; } "addaccesslist" { Add-AccessList $g_selfip $g_arg1 $g_arg2 $g_arg3 $g_arg4; } "deleteaccesslist" { Delete-AccessList $g_selfip; } "getdefaccesslist" { Get-DefaultAccessList $g_selfip; } "adddefaccesslist" { Add-DefaultAccessList $g_selfip $g_arg1 $g_arg2; } "removedefaccesslist" { Remove-DefaultAccessList $g_selfip $g_arg1 $g_arg2; } default { Write-Usage; } } } else { Write-Error "ERROR: iControl subsystem not initialized" } Getting a List of Self IP Addresses If you don't know the configured Self IP addresses on your system, you can retrieve them with the iControl Networking.SelfIP.get_list() method. The Get-SelfIPList function calls this iControl method and prints out the Self IP addresses returned from the iControl call. function Get-SelfIPList() { $ip_list = (Get-F5.iControl).NetworkingSelfIP.get_list(); Write-Host "Available SelfIPs:"; foreach ($ip in $ip_list) { Write-Host " $ip"; } } PS C:\> .\SelfIPPortLockdown.ps1 bigip username password Available SelfIPs: 10.10.10.1 20.20.20.1 Querying the Port Lockdown Access Lists for a Self IP Now that you have the Self IP address you want to work on, you can use the Get-AccessList local function to call the iControl Networking.SelfIPPortLockdown.get_allow_access_list() method This takes as input an array of Self IP addresses and returns the associated Port Lockdown settings. In my example, I do not have a Port Lockdown configured for my Self IP so it returns a Port Lockdown mode of ALLOW_MODE_ALL which means that all traffic is allowed on that Self IP. function Get-AccessList() { param([string]$selfip = $null); $pld = (Get-F5.iControl).NetworkingSelfIPPortLockdown; $SelfIPAccessA = $pld.get_allow_access_list( (,$selfip) ); foreach ($SelfIPAccess in $SelfIPAccessA) { Write-Host "--------------------------------"; Write-Host "Self IP : " $SelfIPAccess.self_ip; Write-Host "Mode : " $SelfIPAccess.mode; Write-Host "Protocol Ports : "; $pA = $SelfIPAccess.protocol_ports; foreach ($ProtocolPort in $pA) { Write-Host " Protocol : " $ProtocolPort.protocol; Write-Host " Port : " $ProtocolPort.port; } } } PS C:\> .\SelfIPPortLockdown.ps1 bigip username password getaccesslist 10.10.10.1 -------------------------------------- Self IP : 10.10.10.1 Mode : ALLOW_MODE_ALL Protocol Ports : Adding a Port Lockdown to a Self IP The local Add-AccessList function takes as input a Self IP address, a Port Lockdown mode, a Protocol type, and a port to lockdown. The available Allow Modes are: ALLOW_MODE_PROTOCOL_PORT - Access to the Self IP is allowed through the specified protocol/port. ALLOW_MODE_NONE - Allow no access to the Self IP. ALLOW_MODE_DEFAULTS - Allow access ot the Self IP using a pre-determined set of protocols/ports. ALLOW_MODE_ALL - Allow full access to the Self IP. And the Protocol types are: PROTOCOL_ANY - Wildcard protocol. PROTOCOL_IPV6 - IPv6 header. PROTOCOL_ROUTING - Routing header. PROTOCOL_NONE - Next header. PROTOCOL_FRAGMENT - Fragment header. PROTOCOL_DSTOPTS - Destination options. PROTOCOL_TCP - TCP protocol. PROTOCOL_UDP - UCP protocol. PROTOCOL_ICMP - ICMP protcool. PROTOCOL_ICMPV6 - ICMPv6 protocol. PROTOCOL_OSPF - OSPF protocol. PROTOCOL_SCTP - SCTP protocol. In the below example I'll add a TCP port 44 Allow. function Add-AccessList() { param( [string]$selfip = $null, [string]$mode = "ALLOW_MODE_NONE", [string]$protocol = "PROTOCOL_ANY", [int]$port = 0); $pld = (Get-F5.iControl).NetworkingSelfIPPortLockdown; $SelfIPAccess = New-Object -TypeName iControl.NetworkingSelfIPPortLockdownSelfIPAccess; $SelfIPAccess.self_ip = $selfip; $SelfIPAccess.mode = $mode; $SelfIPAccess.protocol_ports = New-Object -TypeName iControl.NetworkingSelfIPPortLockdownProtocolPort; $(${SelfIPAccess}.protocol_ports).protocol = $protocol; $(${SelfIPAccess}.protocol_ports).port = $port; $pld.add_allow_access_list( (,$SelfIPAccess) ); Get-AccessList $selfip; } PS C:\> .\SelfIPPortLockdown.ps1 bigip username password addaccesslist 10.10.10.1 ALLOW_MODE_PROTOCOL_PORT PROTOCOL_TCP 44 -------------------------------------- Self IP : 10.10.10.1 Mode : ALLOW_MODE_PROTOCOL_PORT Protocol Ports : Protocol : PROTOCOL_ANY Port : 0 Protocol : PROTOCOL_TCP Port : 44 Deleting a Port Lockdown From a Self IP If you want to add a Port Lockdown, you will likely want to delete one as well. The Delete-AccessList list function takes the same parameters as the Add-AccessList function and it calls the iControl Networking.SelfIPPortLockdown.delete_allow_access_list() method to remove the Port Lockdown settings from the specified Self IP. You'll notice that by removing the last Port Lockdown on a Self IP, the mode will be set to ALLOW_MODE_NONE allowing no traffic on the Self IP. To re-enable traffic on the Self IP, you'll need to add another Port Lockdown with the ALLOW_MODE_ALL mode. function Delete-AccessList() { param( [string]$selfip = $null, [string]$mode = "ALLOW_MODE_NONE", [string]$protocol = "PROTOCOL_ANY", [int]$port = 0); $pld = (Get-F5.iControl).NetworkingSelfIPPortLockdown; $SelfIPAccess = New-Object -TypeName iControl.NetworkingSelfIPPortLockdownSelfIPAccess; $SelfIPAccess.self_ip = $selfip; $SelfIPAccess.mode = $mode; $SelfIPAccess.protocol_ports = New-Object -TypeName iControl.NetworkingSelfIPPortLockdownProtocolPort; $(${SelfIPAccess}.protocol_ports).protocol = $protocol; $(${SelfIPAccess}.protocol_ports).port = $port; $pld.delete_allow_access_list( (,$SelfIPAccess) ); Get-AccessList $selfip; } PS C:\> .\SelfIPPortLockdown.ps1 bigip username password deleteaccesslist 10.10.10.1 ALLOW_MODE_PROTOCOL_PORT PROTOCOL_TCP 44 -------------------------------------- Self IP : 10.10.10.1 Mode : ALLOW_MODE_NONE PS C:\> .\SelfIPPortLockdown.ps1 bigip username password addaccesslist 10.10.10.1 ALLOW_MODE_ALL PROTOCOL_TCP 0 -------------------------------------- Self IP : 10.10.10.1 Mode : ALLOW_ALL Querying the Default Port Lockdown Access List Above, I mentioned that you can configure a Port Lockdown on a Self IP to mode ALLOW_MODE_DEFAULTS. These default settings can be queried with the iControl Networking.SelfIPPortLockdown.get_default_protocol_port_access_list() method. The local Get-DefaultAccessList function calls this iControl command and prints out all the default Protocol Port allow lists. function Get-DefaultAccessList() { $pld = (Get-F5.iControl).NetworkingSelfIPPortLockdown; $ProtocolPortA = $pld.get_default_protocol_port_access_list(); if ( $ProtocolPortA.Length ) { foreach ($ProtocolPort in $ProtocolPortA) { Write-Host "--------------------------------"; Write-Host "Protocol : " $ProtocolPort.protocol; Write-Host " Port : " $ProtocolPort.port; } } else { Write-Host "No default Protocol Port Access Lists defined"; } } PS C:\> .\SelfIPPortLockdown.ps1 bigip username password getdefaccesslist No default Protocol Port Access Lists defined. Adding a Default Port Lockdown Access List The local Add-DefaultAccessList function takes as input a Protocol Type and Port and then calls the iControl Networking.SelfIPPortLockdown.add_default_protocol_port_access_list() method. In my example I add a default allow list of PROTOCOL_TCP on Port 44. function Add-DefaultAccessList() { param([string]$protocol = "PROTOCOL_ANY", [int]$port = 0); $pld = (Get-F5.iControl).NetworkingSelfIPPortLockdown; $protocol_port = New-Object -TypeName iControl.NetworkingSelfIPPortLockdownProtocolPort; $protocol_port.protocol = $protocol; $protocol_port.port = $port; $pld.add_default_protocol_port_access_list( (,$protocol_port) ); Get-DefaultAccessList; } PS C:\> .\SelfIPPortLockdown.ps1 bigip username password adddefaccesslist PROTOCOL_TCP 44 --------------------------------- Protocol : PROTOCOL_TCP Port : 44 Removing a Default Port Lockdown Access List If you have a default Port Lockdown Access List that you no longer need, you can remove it with the iControl Networking.SelfIPPortLockdown.remove_default_protocol_port_access_list() method. function Remove-DefaultAccessList() { param([string]$protocol = "PROTOCOL_ANY", [int]$port = 0); $pld = (Get-F5.iControl).NetworkingSelfIPPortLockdown; $protocol_port = New-Object -TypeName iControl.NetworkingSelfIPPortLockdownProtocolPort; $protocol_port.protocol = $protocol; $protocol_port.port = $port; $pld.remove_default_protocol_port_access_list( (,$protocol_port) ); Get-DefaultAccessList; } PS C:\> .\SelfIPPortLockdown.ps1 bigip username password removedefaccesslist No default Protocol Port Access Lists defined. Conclusion Now you have all the tools you need to automate the management of you Port Lockdown Access Lists on your BIG-IP. The full application can be found in the iControl CodeShare under PsSelfIPPortLockdown. Get the Flash Player to see this player. 20090115-iControl101_20_PortLockdown.mp3738Views0likes0CommentsiControl 101 - #07 - User Management
iControl is a robust management API that not only encompasses the control of network management objects, but also many other aspects of the system level configuration. This article will discuss the methods used to create and manage all aspects of system user accounts. Creating a new user First it will be helpful in looking at the portion of the administration GUI on the BIG-IP to get a context of what methods map to which properties of a user. The "User Name" and "Password" fields are somewhat self explanatory. The other fields likely need a bit of explanation. Role The role is the authorization role that you assign to this user. The level you give will be dependent on what you want the user to have access to on the device. Valid values are as follows: No Access - No access to the device. Guest - Read-only view on all objects and the ability to change their own password. Operator - Ability to enable and disable nodes and pool members. Application Editor -Ability to modify nodes, pools, pool members, and monitors. Application Security Policy Editor - Complete access to Application Security Manager (ASM) policy objects. Manager - Ability to create, modify, and delete virtual servers, nodes, profiles, monitors, and iRules. User Manager - Ability to create, modify, and delete non-admin local user accounts. Resource Administrator - Complete access to all objects on the system, except user account objects. Administrator - Full access to all objects in the system. Partition Access For version 9.4 and above, users can be assigned to a partition (another word for administrative domain). This is the partition that the user can modify objects in. Terminal Access For all types of accounts, you can give access to the device from the command line through a ssh connection. "Disabled" means ssh connections are not allowed. "Advanced Shell" allows access to the unix bash shell. And, finally, "bigpipe shell" allows login access to F5's shell that supports bigpipe management commands. Automation So, how does one automate the management of users? The UserManagement interface in the Management iControl module has all the methods you would need to create and modify the properties of the users. To create a new user, you'll want the create_user_2 method. The "_2" methods allow support for encrypted passwords, so you'll likely want to use them in your applications. The method prototype is: Management::UserManagement::create_user_2( in Management::UserManagement::UserInfo2 [] users ); struct Management::UserManagement::UserInfo2 { Management::UserManagement::UserID user; Management::UserManagement::UserRole role; Management::UserManagement::PasswordInfo password; String home_directory String login_shell long user_id long group_id }; struct Management::UserManagement::UserID { String name; String full_name; }; enum Management::UserManagement::UserRole { USER_ROLE_ADMINISTRATOR = 0, USER_ROLE_TRAFFIC_MANAGER = 1, USER_ROLE_GUEST = 2, USER_ROLE_ASM_POLICY_EDITOR = 3, USER_ROLE_INVALID = 4, USER_ROLE_MANAGER = 5, USER_ROLE_EDITOR = 6, USER_ROLE_APPLICATION_EDITOR = 7, USER_ROLE_CERTIFICATE_MANAGER = 8, USER_ROLE_USER_MANAGER = 9, USER_ROLE_RESOURCE_ADMINISTRATOR = 10 }; struct Management::UserManagement::PasswordInfo { boolean is_encrypted; String password; }; Let's go by each of the fields in the UserInfo2 structure. user - you put the username (name and full name) in here. role - specify the authorization role that you would like this user to have. password - include the encrypted (with crypt()) or unencrypted plain text password here. home_directory - This is the home directory for the user. It is typically in the format of /home/$username login_shell - This is what allows terminal access. Use /bin/false for Disabled access, /bin/bash for the advanced shell, and /bin/bpsh for the bigpipe shell. user_id - Use a default value of 0. group_id - Use a default value of 500. So, how do you set partition information? That's the job for the Management::UserManagement::set_user_permissions() method. If you want to assign the user to a specific partition, then you'll want to pass in that user's name along with the partition name and role for that partition you would like the user to have. Management::UserManagement::set_user_permission( in String [] user_names, in Management::UserManagement::UserPermission [] [] permissions ); struct Management::UserManagement::UserPermission { UserRole role; String partition; } Managing an existing user account For an existing user account, you'll want to use the "get" and "set" methods for all of the user attributes. I won't list the full prototypes out here, but this is a list of methods you have access to: change_my_password change_password change_password_2 create_user create_user_2 delete_user get_authentication_method get_default_partition get_default_role get_encrypted_password get_fullname get_group_id get_home_directory get_list get_login_shell get_my_permission get_remote_console_access get_role get_user_id get_user_permission get_version set_authentication_method set_default_partition set_default_role set_fullname set_group_id set_home_directory set_login_shell set_remote_console_access set_role set_user_id set_user_permission Putting it together Here is some sample code in C# showing how to automate the New User dialog in the administrative GUI using iControl: void createUser ( String sUsername, String sPassword, iControl.ManagementUserManagementUserRole role, String sPartition, bool bTerminalAccess ) { iControl.ManagementUserManagementUserInfo2 userInfo = new iControl.ManagementUserManagementUserInfo2(); userInfo.user = new iControl.ManagementUserManagementUserID(); userInfo.user.full_name = sUsername; userInfo.user.name = sUsername; userInfo.password = new iControl.ManagementUserManagementPasswordInfo(); userInfo.password.is_encrypted = false; userInfo.password.password = sPassword; userInfo.role = role; userInfo.home_directory = "/home/" + sUsername; userInfo.login_shell = bTerminalAccess ? "/bin/bash" : "/bin/false"; userInfo.user_id = 0; userInfo.group_id = 500; // m_interfaces is defined as a class variable in the calling application. m_interfaces.ManagementUserManagement.create_user_2( new iControl.ManagementUserManagementUserInfo2 [] { userInfo }); if ((sPartition != null) && (sPartition.Length > 0) && (!sPartition.Equals("All"))) { iControl.ManagementUserManagementUserPermission [][] userPerms = new iControl.ManagementUserManagementUserPermission[1][]; userPerms[0] = new iControl.ManagementUserManagementUserPermission[1]; userPerms[0][0] = new iControl.ManagementUserManagementUserPermission(); userPerms[0][0].partition = sPartition; userPerms[0][0].role = role; m_interfaces.ManagementUserManagement.set_user_permission( new String[] { sUsername }, userPerms); } } To individually modify each of the user attributes on an existing user, use the individual accessor methods defined in the Management::UserManagement interface in a way similar to how the set_user_permission method is used in the previous example. Get the Flash Player to see this player.738Views0likes0CommentsiControl 101 - #15 - System Services
This installment of iControl 101, I'll focus on the System Service interface where you can have full control of the running state of all the services on the system. 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 theboss -Username admin -Password admin True PS > $Services = (Get-F5.iControl).SystemServices Getting a list of services Like most other interfaces, the get_list() method takes no parameters as input and returns a list of contained objects. In this case, it returns a list of System.ServiceType's that are installed on the system. PS > $Services.get_list() SERVICE_HTTPD SERVICE_NTPD SERVICE_SSHD SERVICE_SYSLOGD SERVICE_ALERTD SERVICE_ASM SERVICE_BCM56XXD SERVICE_BIG3D SERVICE_BIGD SERVICE_BIGDBD SERVICE_CHMAND SERVICE_COMM_SRV ... Querying the status of services There are two methods for querying the status of the services on the system. As illustrated in this example, the get_all_service_statuses() method takes no parameters and returns a list of structures containing the ServiceType in the service field, and the ServiceStatusType in the status field. PS > $Services.get_all_service_statuses() service status ------- ------ SERVICE_HTTPD SERVICE_STATUS_UP SERVICE_NTPD SERVICE_STATUS_UP SERVICE_SSHD SERVICE_STATUS_UP SERVICE_SYSLOGD SERVICE_STATUS_UP SERVICE_ALERTD SERVICE_STATUS_UP SERVICE_ASM SERVICE_STATUS_UP SERVICE_BCM56XXD SERVICE_STATUS_UP SERVICE_BIG3D SERVICE_STATUS_UP SERVICE_BIGD SERVICE_STATUS_UP SERVICE_BIGDBD SERVICE_STATUS_UP SERVICE_CHMAND SERVICE_STATUS_UP SERVICE_COMM_SRV SERVICE_STATUS_DOWN ... If you are concerned with a single service, or even a small subset of services, you can use the get_service_status() method that takes as input an array of ServiceTypes and returns, like the get_all_service_statuses() method, an array of structures containing the ServiceType and ServiceStatusType values for each requested service. PS > $Services.get_service_status( ("SERVICE_HTTPD", "SERVICE_ASM") ) service status ------- ------ SERVICE_HTTPD SERVICE_STATUS_UP SERVICE_ASM SERVICE_STATUS_UP Controlling services Querying the status of a service is nice, but the real fun is stopping or restarting a service. The set_service() method takes as input an array of ServiceType's and a single ServiceStatusType to assign to all of those services. The below example will stop the eventd service and then query the eventd service's status to verify that the action worked. I then call set_service() again, this time enabling the service and querying the get_service_status() method to verify that it's back up. PS C:\> $Services.set_service( (,"SERVICE_EVENTD"), "SERVICE_ACTION_STOP") PS > $Services.get_service_status( (,"SERVICE_EVENTD") ) service status ------- ------ SERVICE_EVENTD SERVICE_STATUS_DOWN PS > $Services.set_service( (,"SERVICE_EVENTD"), "SERVICE_ACTION_START") PS > $Services.get_service_status( (,"SERVICE_EVENTD") ) service status ------- ------ SERVICE_EVENTD SERVICE_STATUS_UP There may be a situation where you want to restart all running services. This can be accomplished with the set_all_services method. It takes as input the ServiceStatusType you would like to set and applies it to all of the services. This is equivalent to calling the set_service() method passing in the list of ServiceTypes returned from the get_list() method. PS > $Services.set_all_services("SERVICE_ACTION_RESTART") Start, Stop, and Restart are not the only controls you can use. There are also Reinit, Add to Boot List, Remove from Boot List, Add to Default List, and Remove from Default List. The values are defined in the following table. SERVICE_ACTION_START 0 Start a service. SERVICE_ACTION_STOP 1 Stop a service. SERVICE_ACTION_REINIT 2 Reinitialize a service. SERVICE_ACTION_RESTART 3 Restart a service by stopping and starting the service SERVICE_ACTION_ADD_TO_BOOT_LIST 4 Add a service to the boot/reboot list. If on this list, the service will be started on bootup and stopped on reboot. SERVICE_ACTION_REMOVE_FROM_BOOT_LIST 5 Remove a service from the boot/reboot list. If on this list, the service will be started on bootup and stopped on reboot. SERVICE_ACTION_ADD_TO_DEFAULT_LIST 6 Add a service to the default action list. SERVICE_ACTION_REMOVE_FROM_DEFAULT_LIST 7 Remove a service from the default action list. So, if you never want to see the TMM running again, then you can call set_service_status on SERVICE_TMM with SERVICE_ACTION_STOP and SERVICE_ACTION_REMOVE_FROM_BOOT_LIST and you'll be all set. Warning, doing so will disable almost all functionality on the BIG-IP so it's not recommended. Controlling Remote Access There are a couple of methods controlling the ssh service. Specifically you can query and set the enabled/disabled state of specific client addresses. The get_ssh_access_v2() method will return an array of states and a address list for each entry in the restriction list. PS > $Services.get_ssh_access_v2() state addresses ----- --------- STATE_ENABLED {ALL} If you want to add a restriction for a specific client address, you can do so with the set_ssh_access_v2() method that takes as input a SSHAccess_v2 structure containing the EnabledState and array of addresses for that state. This example will disable ssh access from client address 10.10.10.10 and verifies the call succeeded. It then will overwrite the previous entry with enabled access for all client addresses. The special value of "ALL" can be used for all addresses. PS > $SSHAccess = New-Object -TypeName iControl.SystemServicesSSHAccess_v2 PS > $SSHAccess.state = "STATE_DISABLED" PS > $SSHAccess.addresses = (, "10.10.10.10") PS > $Services.set_ssh_access_v2($SSHAccess) PS > $Services.get_ssh_access_v2() state addresses ----- --------- STATE_DISABLED {10.10.10.10} PS > $SSHAccess.state = "STATE_ENABLED" PS > $SSHAccess.addresses = (, "ALL") PS > $Services.set_ssh_access_v2($SSHAccess) PS > $Services.get_ssh_access_v2() state addresses ----- --------- STATE_ENABLED {ALL} Rebooting the System In the rare case that you need to reboot your system, the System.Services interface can help. The reboot_system() method takes as input a number of seconds until reboot. This method call is equivalent to the /usr/bin/delayedReboot command line program on the BIG-IP. PS C:\> $Services.reboot_system(5) Conclusion With the System.Services interface, you have full control of what services are running on the system, and control to enable or disable them at your whim. Use this power wisely as it could have adverse effects if you disable a service that you are relying on running. Get the Flash Player to see this player. 20080529-iControl101_15_SystemServices.mp3675Views0likes8Comments