Control Apps - #21 - App Automation

In certain environments it is desired to have multiple application instances of different versions running side by side in your production envioronment.  An example of this might be for a new feature you want to add to the user interface for you web application.  You may not want to expose that version of the application to every user, but would rather selectively allow access to a certain class of users.  You will need to have server configurations for each version of your applications. 

Also consider the scenario where you are producing a programming interface (API) to a set of users and you need to maintain backward compatibility for older applications that make use of the older versions of the API.  Now, let's consider when you release new versions frequently. 

For both of these situations, you will have to create server pools and their associated monitoring systems to allow for dynamic assignment for specific requests.  This PowerShell script will automate the process of creating these application pools and their monitors for use in a HTTP and HTTPS virtual server.

Usage

The arguments for this application are the BIG-IP hostname, username, and password along with the Application Name (APPName), Application Version (APPVersion), a list of pool members, and an optional ports for the HTTP and HTTPS members.  Oh, there's also a Delete option in case you want to clean things up.

param(
  [string]$Bigip = "",
  [string]$User = "",
  [string]$Pass = "",
  [string]$APPName = "",
  [string]$APPVer = "",
  [int]$HTTPPort = 80,
  [int]$HTTPSPort = 443,
  [string[]]$MemberList = $null,
  [switch]$Delete = $false
);

function Show-Usage()
{
  Write-Host @"
Usage: APIPoolCreate.ps1 Arguments
       Argument   - Description
       ----------   -----------
       Bigip      - The ip/hostname of your BIG-IP.
       User       - The Managmenet username for your BIG-IP.
       Pass       - The Management password for your BIG-IP.
       APPName    - The Name of the application.
       APPVer     - The Version of the application.
       HTTPPort   - The port the HTTP pool members will be listening on (def = 80)
       HTTPSPort  - The port the HTTPS pool members will be listening on (def = 443)
       MemberList - The list of pool members for the HTTP and HTTPS pools.
"@;
}

Initialization

The initialization component of this script will check for the required parameters, attempt to load the iControl Snapin into the current PowerShell runspace, and then either create or delete the pool and monitor instances.  For the creation, two pools are created with the supplied pool members (one for HTTP and the other for HTTPS), the HTTP and HTTPS monitor templates are then created, and finally the monitor templates are associated with the created pools.  If the Delete option is supplied, then the two pools and two monitor instances are deleted from the system.

function Do-Initialize()
{
  if ( (Get-PSSnapin | Where-Object { $_.Name -eq "iControlSnapIn"}) -eq $null )
  {
    Add-PSSnapIn iControlSnapIn
  }
  $success = Initialize-F5.iControl -HostName $Bigip -Username $User -Password $Pass;
  
  return $success;
}

if ( ($Bigip.Length -eq 0) -or ($User.Length -eq 0) -or ($Pass.Length -eq 0) -or`
     ($APPName.Length -eq 0) -or ($APPVer.Length -eq 0) -or ($MemberList -eq $null) )
{
  Show-Usage;
}
else
{
  if ( Do-Initialize )
  {
    $Basename = "${APPName}_${APPVer}";
    
    # HTTP Objects
    $Name = "${Basename}_HTTP";
    
    if ( $Delete -eq $false )
    {
      Write-Host "Creating HTTP Pool and Monitor Association...";
      Create-MonitorTemplate -Name $Name -Send "GET /CardMgmt/${APPVer}/CMS.asmx" `
        -Receive "FindCardRecord" -Type "TTYPE_HTTP";
      Create-Pool -Name $Name -MemberList $MemberList -MemberPort $HTTPPort;
      Associate-MonitorWithPool -PoolName $Name -MonitorName $Name;
    }
    else
    {
      Write-Host "Deleting HTTP Pool and Monitor Association...";
      Delete-Pool -Name $Name;
      Delete-MonitorTemplate -Name $Name;
    }
    
    # HTTPS Objects
    $Name = "${Basename}_HTTPS";
    if ( $Delete -eq $false )
    {
      Write-Host "Creating HTTPS Pool and Monitor Association...";
      Create-MonitorTemplate -Name $Name -Send "GET /CardMgmt/${APPVer}/CMS.asmx" `
        -Receive "FindCardRecord" -Type "TTYPE_HTTPS";
      Create-Pool -Name $Name -MemberList $MemberList -MemberPort $HTTPSPort;
      Associate-MonitorWithPool -PoolName $Name -MonitorName $Name;
    }
    else
    {
      Write-Host "Deleting HTTPS Pool and Monitor Association...";
      Delete-Pool -Name $Name;
      Delete-MonitorTemplate -Name $Name;
    }
    
    # Save Configuration
    Save-Configuration;
  }
  else
  {
    Write-Error "ERROR: iControl subsystem not initialized"
  }
}

Checking For Pool Existence

To avoid runtime errors, the code will check for the existence of a pool before attemping to create or delete one.  The Does-PoolExist function does that by querying the list of pools and then iterating through that list to look for a match.  True or False is returned depending on whether the pool exists in the system.

function Does-PoolExist()
{
  param([string]$Name);
  
  $bExists = $false;
  $pool_list = $(Get-F5.iControl).LocalLBPool.get_list();
  foreach($pool in $pool_list)
  {
    if ( $pool.Equals($Name) )
    {
      $bExists = $true;
      break;
    }
  }
  $bExists;
}

Creating A Pool

The parameters for the Pool.create method are the pool name, load balancing method, and the list of pool members.  In this example we are using the default Round Robin (LB_METHOD_ROUND_ROBIN) load balancing method but you could change this to whatever you wish.   All of the parameters are arrays to allow for the creation of multiple objects.  The parameters are converted to arrays with the PowerShell comma operator.

function Create-Pool()
{
  param(
    [string]$Name,
    [string[]]$MemberList,
    [int]$MemberPort
  );
  
  if ( Does-PoolExist -Name $Name )
  {
    Write-Host "Pool '$Name' Already Exists";
  }
  else
  {
    $IPPortDefList = New-Object -TypeName iControl.CommonIPPortDefinition[] $MemberList.Length;
    for($i=0; $i-lt$MemberList.Length; $i++)
    {
      $IPPortDefList[$i] = New-Object -TypeName iControl.CommonIPPortDefinition;
      $IPPortDefList[$i].address = $MemberList[$i];
      $IPPortDefList[$i].port = $MemberPort;
    }
    
    $(Get-F5.iControl).LocalLBPool.create(
      (,$Name),
      (,"LB_METHOD_ROUND_ROBIN"),
      (,$IPPortDefList)
    );
    Write-Host "Pool '$Name' Successfully Created";
  }
}

Deleting A Pool

Deleting a pool only requires the pool name.  The LocalLB.Pool.delete_pool method is called with the supplied pool name.

function Delete-Pool()
{
  param( [string]$Name );
  if ( Does-PoolExist -Name $Name )
  {
    $(Get-F5.iControl).LocalLBPool.delete_pool( (,$Name) );
    Write-Host "Deleted Pool '$Name'";
  }
}

Checking For Monitor Template Existence

As was for the pools, I wrote a Does-MonitorTemplateExist function to check to see if a monitor template exists before attempting to create or delete one.  The LocalLB.Monitor.get_template_list method is called and the returned list is iterated through and compared to the supplied name and True or False is returned depending on if the template exists.

function Does-MonitorTemplateExist()
{
  param([string]$Name);
  
  $bExists = $false;
  $template_list = $(Get-F5.iControl).LocalLBMonitor.get_template_list();
  foreach($template in $template_list)
  {
    if ( $template.template_name.Equals($Name) )
    {
      $bExists = $true;
      break;
    }
  }
  $bExists;
}

Creating A Monitor Template

Creating a monitor template is a bit more complicated than creating a pool.  The LocalLB.Monitor.create_template method is called with the LocalLB.Monitor.MonitorTemplate array along with an array of LocalLB.Monitor.CommonAttribute items.  For this example, we are creating templates of type TTYPE_HTTP and TTYPE_HTTPS which are both HTTP type monitors.  This function supplies the send (STYPE_SEND) and receive (STYPE_RECEIVE) properties for the template.  The are assigned to the newly created template with the LocalLB.Monitor.set_template_string_property method.

function Create-MonitorTemplate()
{
  param(
    [string]$Name,
    [string]$Send,
    [string]$Receive,
    [string]$Type
  );
  
  if ( Does-MonitorTemplateExist -Name $Name )
  {
    Write-Host "Monitor Template '$Name' Already Exists";
  }
  else
  {
    $template = New-Object -TypeName iControl.LocalLBMonitorMonitorTemplate;
    $template.template_name = $Name;
    $template.template_type = $Type;

    $template_attribute = New-Object -TypeName iControl.LocalLBMonitorCommonAttributes;
    $template_attribute.parent_template = "";
    $template_attribute.interval = 5;
    $template_attribute.timeout = 16;
    $template_attribute.dest_ipport = New-Object -TypeName iControl.LocalLBMonitorIPPort;
    $template_attribute.dest_ipport.address_type = "ATYPE_STAR_ADDRESS_STAR_PORT";
    $template_attribute.dest_ipport.ipport = New-Object -TypeName iControl.CommonIPPortDefinition;
    $template_attribute.dest_ipport.ipport.address = "0.0.0.0";
    $template_attribute.dest_ipport.ipport.port = 0;
    $template_attribute.is_read_only = $false;
    $template_attribute.is_directly_usable = $true;
    
    $(Get-F5.iControl).LocalLBMonitor.create_template(
      (, $template),
      (, $template_attribute)
    );
    
    $StringValues = New-Object -TypeName iControl.LocalLBMonitorStringValue[] 2;
    $StringValues[0] = New-Object -TypeName iControl.LocalLBMonitorStringValue;
    $StringValues[0].type = "STYPE_SEND";
    $StringValues[0].value = $Send;
    $StringValues[1] = New-Object -TypeName iControl.LocalLBMonitorStringValue;
    $StringValues[1].type = "STYPE_RECEIVE";
    $StringValues[1].value = $Receive;
    
    # Set HTTP Specific attributes
    $(Get-F5.iControl).LocalLBMonitor.set_template_string_property(
      ($Name,$Name),
      $StringValues
    );
    
    Write-Host "Monitor '$Monitor '$Name' Succesffully Created";
  }
}

Deleting A Monitor Template

Deleting the monitor template involves calling the iControl LocalLB.Monitor.delete_template method with the list of template names you would like to delete.

function Delete-MonitorTemplate()
{
  param( [string]$Name );
  if ( Does-MonitorTemplateExist -Name $Name )
  {
    $(Get-F5.iControl).LocalLBMonitor.delete_template( (,$Name) );
    Write-Host "Deleted Monitor Template '$Name'";
  }
}

Associating A Monitor With A Pool

Once the pool and monitor template are created, you must associate the template with the pool.  The LocalLB.Pool.set_monitor_association method takes as input an array of LocalLB.Pool.MonitorAssociation structure that define the pool and the monitor rules.  In this example we are using a single monitor for the rule type.

function Associate-MonitorWithPool()
{
  param(
    [string]$PoolName,
    [string]$MonitorName
  );
  
  $monitor_association = New-Object -TypeName iControl.LocalLBPoolMonitorAssociation;
  $monitor_association.pool_name = $PoolName;
  $monitor_association.monitor_rule = New-Object -TypeName iControl.LocalLBMonitorRule;
  $monitor_association.monitor_rule.type = "MONITOR_RULE_TYPE_SINGLE";
  $monitor_association.monitor_rule.quorum = 0;
  $monitor_association.monitor_rule.monitor_templates = (, $MonitorName);
  
  $(Get-F5.iControl).LocalLBPool.set_monitor_association(
    (, $monitor_association)
  );
  
  Write-Host "Monitor '$MonitorName' Is Associated With Pool '$PoolName'";
}

Saving The Configuration

Don't forget that all iControl calls are not persisted to disk without explicitly calling the System.ConfigSync.save_configuration method.  In this example, we are only saving the high level config (bigip.conf).

function Save-Configuration()
{
  $(Get-F5.iControl).SystemConfigSync.save_configuration(
    "/config/bigip.conf",
    "SAVE_HIGH_LEVEL_CONFIG"
  );
  Write-Host "Configuration Changes Successfully Saved";
}

Conclusion

This example illustrates how to automate the creation of a pool and it's monitor for use in the management of multiple application versions.  You can see the full script under PowerShellAppAutomation in the iControl wiki.

Published Nov 17, 2009
Version 1.0

Was this article helpful?

2 Comments

  • Hey Joe, this is great stuff, thanks for posting. I'm having an issue with your create-pool () function. When I pass in two IPs, I successfully build ipportdeflist, however, when I send that object into the .create method, it only makes the first entry as a member. I placed a $host.enternestedprompt() immediately preceding issuing the create command and can see the object. It is displayed below as an array of commonIPportdefinition[]. Any thoughts on this? Also, I tried to capture the traffic, but of course it is https. Anyway to see the actual soap request received by the webservice? Or perhaps force non-ssl?

     

     

    Thanks! -Joe

     

     

    PS C:\utilities\Scripts\powershell\LTM>>> $IPportdefinitionslist.gettype()

     

     

    IsPublic IsSerial Name BaseType

     

    -------- -------- ---- --------

     

    True True CommonIPPortDefinition[] System.Array

     

     

     

    PS C:\utilities\Scripts\powershell\LTM>>> $IPportdefinitionslist | ft -auto

     

     

    address port

     

    ------- ----

     

    1.2.3.4 80

     

    1.2.3.5 80
  • Nevermind on the previous problem, it was a version 9.x issue. Ran against 10.x and worked just fine.