Ruby and iControl: Understanding Complex Type Syntax

Anyone who has worked with SOAP knows it can make a lot of things easier, but it is not without its hurdles. Interpreting complex type syntax for use with a SOAP driver can present one of the largest headaches for a web services developer. In this tech tip, we are going to walk through a few examples of complex types commonly used within the iControl SDK.

Creating A Virtual Server

When creating a virtual server, the LocalLB.VirtualServer WSDL will be used along with the ‘create’ method described within. The ‘createRequest’ message expects four arguments: definitions, wildmasks, resources, and profiles. The first thing you’ll notice is that all these arguments are plural. The reason being is that multiple virtual servers can be created with a single request. This reduces the overhead of multiple HTTP requests as well as perform multiple operations with a single request. Enter the need for complex types.

If we navigate to the ‘create’ method under the ‘LocalLB.VirtualServer’ leg of the iControl SDK documentation, you will see a method definition that shows the syntax for the four arguments. You should see this:

void create(
    in VirtualServerDefinition[] definitions,
    in String[] wildmasks,
    in VirtualServerResource[] resources,
    in VirtualServerProfile[][] profiles
);

Let’s start with ‘definitions'. The ‘create’ method expects the ‘definitions’ argument to be defined as an array (note the brackets) of ‘VirtualServerDefinitions’. If we then click through to the ‘VirtualServerDefinition’ syntax, we see that it is a structure that consists of four elements: a name, address, port, and protocol. These four elements uniquely identify every LTM virtual server. The virtual server ‘name’ and ‘address’ are strings, while the port is a long (integer), and the protocol can be chosen from an enumeration of ‘ProtocolTypes’. As of this writing, there are twelve ‘ProtocolTypes’. The most common being ‘PROTOCOL_TCP’ and ‘PROTOCOL_UDP’. If we were to create a ‘VirtualServerDefinition’ for a new HTTP virtual server located at 10.0.0.1, our virtual server definition would be a hash and be formatted as such:

[ { 'name' => 'vs.test_http', 'address' => '10.0.0.1', 'port' => 80, 'protocol' => 'PROTOCOL_TCP’ } ]

The ‘wildmask’ is used to define the host and network bits of a virtual server. This value is used for defining network virtuals. If you are creating a host virtual (destination is one IP), it should be set to ‘255.255.255.255’. However if you wanted to created a HTTP virtual for an entire class C, you would set the address in the ‘VirtualServerDefinition’ to ‘10.0.0.0’ and the wildmask to ‘255.255.255.0’. The syntax of the ‘wildmask’ is very straightforward as it is an array of strings corresponding to each ‘VirtualServerDefinition’. If you have three ‘VirtualServerDefinitions’ in your create request, there should be three corresponding ‘wildmasks’. For the sake of brevity, I will skip an example here, but will include it in our final example at the end.

Next are the virtual server ‘resources’. These are defined by the ‘VirtualServerResource’ structure, which is an array of hashes mapping resources (such as pools) to the virtual server. If we wanted to add a default pool called ‘my_test_http_pool’ to our new virtual server, the ‘VirtualServerResource’ syntax would look like this:

[ { 'type' => 'RESOURCE_TYPE_POOL', 'default_pool_name' => 'my_test_http_pool' } ]

Lastly are the virtual server profiles, which are described by the ‘VirtualServerProfile’ type. The ‘VirtualServerProfile’ structure is an array of arrays of hashes. This allows for multiple profiles to be assigned to multiple virtual servers with a single request. Each profile assignment has two arguments: profile_context and profile_name. The ‘profile_context’ is described by the ‘ProfileContextType’ enumeration and indicates if the profile should be applied to the client-side, server-side, or both. For our new HTTP virtual, we would want to assign it a TCP profile as well as an HTTP profile like this:

[ [ { 'profile_context' => 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name' => 'tcp-lan-optimized' }, \
    { 'profile_context' => 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name' => 'http-lan-optimized-caching' } ] ]

Bringing It All Together

Now that we have walked through all the the arguments of the virtual server ‘create’ method and their corresponding complex types, we can bring it all together into one statement and create our new virtual server. In this case, we will be creating two virtual servers with a single request. One will be an HTTP virtual for use on our corporate LAN, the other will be a virtual for serving our external DNS.

bigip['LocalLB.VirtualServer'].create( \
    # definitions -> VirtualServerDefinition[]
    [   { 'name' => 'vs.test_http_internal', 'address' => '10.0.0.1', 'port' => 80, 'protocol' => 'PROTOCOL_TCP' }, \
        { 'name' => 'vs.test_dns_external', 'address' => '4.2.2.2', 'port' => 53, 'protocol' => 'PROTOCOL_UDP' } ], \

    # wildmasks -> String[]
    [   '255.255.255.255', '255.255.255.255' ], \

    # resources -> VirtualServerResource[]
    [   { 'type' => 'RESOURCE_TYPE_POOL', 'default_pool_name' => 'my_test_http_pool' }, \
        { 'type' => 'RESOURCE_TYPE_POOL', 'default_pool_name' => 'my_big_test_external_dns_pool' } ], \

    # profiles -> VirtualServerProfile[][]
    [   [   { 'profile_context' => 'PROFILE_CONTEXT_TYPE_SERVER', 'profile_name' => 'tcp-lan-optimized' }, \
            { 'profile_context' => 'PROFILE_CONTEXT_TYPE_CLIENT', 'profile_name' => 'tcp' }, \
            { 'profile_context' => 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name' => 'http-lan-optimized-caching' } ], \
        [   { 'profile_context' => 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name' => 'udp' } ] \
    ] \
)

Conclusion

Complex type syntax can be one of the larger hurdles when using a SOAP driver, but once the syntax is understood, they become much more manageable. Complex types are used throughout the iControl API and are daunting at first, but become very powerful once they can be used to their fullest capacity. They allow the developer to complete large operations with a small number of requests. This in turn can reduce the manual labor involved in deploying and maintaining your F5 infrastructure. We hope this little bit of information will help you in managing your infrastructure with the iControl Ruby Library.

Published Jan 27, 2011
Version 1.0
  • is there a syntax error with your virtualServerResource definition? I don't understand your bracketing. for example, to me it looks like your 2nd bracket is facing the wrong way "]" instead of "["

     

     

    shouldn't it be something like below (which i think applies the 1st 3 profiles to your 1st VS and the last profile to the 2nd VS):

     

    profiles -> VirtualServerProfile[][]

     

    [ [ { 'profile_context' => 'PROFILE_CONTEXT_TYPE_SERVER', 'profile_name' => 'tcp-lan-optimized' }, \

     

    { 'profile_context' => 'PROFILE_CONTEXT_TYPE_CLIENT', 'profile_name' => 'tcp' }, \

     

    { 'profile_context' => 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name' => 'http-lan-optimized-caching' } ], \

     

    [ { 'profile_context' => 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name' => 'udp' } ] \

     

    ] \
  • weird, my posted syntax got changed once my comment was approved (brackets got reversed). let me see if i can figure out how to post it so it maintains my original formatting