Go BIG(-IP) or Go Home

I really love leveraging API's to get more out of some of the tasks that I do, and to interact with a lot of the systems that I work on. One of my favorites is the F5 LTM load-balancer. It's quite the platform, and if any of you have ever worked with/on one...I'm sure you can relate.

Newer in version 11.4+, is the ability to manage your F5 using a REST API. This is a framework that sits atop of tmsh. Using this API makes it extremely easy and efficient to add/modify pools, virtual servers, nodes, etc.

go-bigip

I'm a huge fan of the Go programming language, and every scripting or automation task I tend to do now days I write it in Go. Since I work with F5 systems quite a bit, I wanted to leverage their REST API and make it more efficient when modifying the system...for myself and others. So I began work on go-bigip, and I'm happy to share some examples of what it can do!

Most of these examples assume that you have a working knowledge of Go. It's pretty easy to learn, and I would start here and also take the "tour of Go" here.

Listing Pools, Nodes, Virtual Servers, etc.

First, I'll show you how to easily list and iterate over pools, nodes, virtual servers, etc.

package main

import (  
    "fmt"
    "github.com/scottdware/go-bigip"
)

func main() {  
    // Establish our session to the BIG-IP
    f5 := bigip.NewSession("f5.company.com", "admin", "secret")

    // Iterate over all the virtual servers, and display their names.
    vservers, err := f5.VirtualServers()
    if err != nil {
        fmt.Println(err)
    }

    for _, vs := range vservers.VirtualServers {
        fmt.Printf("Name: %s\n", vs.Name)
    }
}

You will get output similar to the following:

Name: L3_Forwarding  
Name: Port80-Redirect  
Name: web_443  
Name: dmz_apps_443  
Name: some_application_443  

Let's say we want to list our pools, and what members are in each pool. We can do that by iterating over the pools like we did with virtual servers, but also use the PoolMembers() function to list the members:

I'm ignoring error checking for this example by using the _ variable assignment for the poolMembers variable.

package main

import (  
    "fmt"
    "github.com/scottdware/go-bigip"
)

func main() {  
    // Establish our session to the BIG-IP
    f5 := bigip.NewSession("f5.company.com", "admin", "secret")

    // Iterate over all the pools, and list what members are in each one.
    pools, err := f5.Pools()
    if err != nil {
        fmt.Println(err)
    }

    for _, pool := range pools.Pools {
        fmt.Printf("Pool: %s\n", pool.Name)
        poolMembers, _ := f5.PoolMembers(pool.Name)
        for i, member := range f5.PoolMembers(pool.Name) {
            fmt.Printf("--> Member %d: %s\n", i, member)
        }
        fmt.Println()
    }
}

The above will output:

Pool: web_443_pool  
--> Member 0: 10.10.10.20:443
--> Member 1: 10.10.10.21:443

Pool: dmz_apps_443_pool  
--> Member 0: 10.20.20.5:443
--> Member 1: 10.20.20.6:443

Pool: some_application_443_pool  
--> Member 0: 192.168.1.10:443
--> Member 1: 192.168.1.11:443
--> Member 2: 192.168.1.12:443
--> Member 3: 192.168.1.13:443

Let's get a list of VLAN's on the F5:

package main

import (  
    "fmt"
    "github.com/scottdware/go-bigip"
)

func main() {  
    f5 := bigip.NewSession("f5.company.com", "admin", "secret")

    vlans, err := f5.Vlans()
    if err != nil {
        fmt.Println(err)
    }

    for _, vlan := range vlans.Vlans {
        fmt.Printf("VLAN: %s\n", vlan.Name)
    }
}

Will produce:

VLAN: internal  
VLAN: vlan1005  
VLAN: vlan1006  
VLAN: vlan1007  
VLAN: vlan1008  

You can also do the same for nodes, interfaces, self IP's, routes etc. Please see the official documentation for how to use those functions.

Creating Objects

Now that we've had our fun in listing what configured items are on the F5, let's actually create some!

First, let's create a VLAN, and then assign a tagged interface to it:

package main

import (  
    "github.com/scottdware/go-bigip"
)

func main() {  
    f5 := bigip.NewSession("f5.company.com", "admin", "secret")

    f5.CreateVlan("vlan1010", 1010)

    // Specifying "true" or "false" as the third option represents if the
    // interface is tagged or not.
    f5.AddInterfaceToVlan("vlan1010", "1.2", true)
}

Now, let's create a trunk with a couple of interfaces assigned to it:

package main

import (  
    "github.com/scottdware/go-bigip"
)

func main() {  
    f5 := bigip.NewSession("f5.company.com", "admin", "secret")

    // Separate interfaces by using a comma.
    // "true" or "false" as the third option is whether or not LACP should be active.
    f5.CreateTrunk("AG-Ethernet", "1.4, 1.6, 1.8", true)
}

Now let's create a self IP for the VLAN we created earlier:

package main

import (  
    "github.com/scottdware/go-bigip"
)

func main() {  
    f5 := bigip.NewSession("f5.company.com", "admin", "secret")

    f5.CreateSelfIP("vlan1010-self", "10.10.5.1/24", "vlan1010")
}

In the next example, we'll be doing the following tasks:

  • Create three nodes
  • Create a pool
  • Assigning members to our pool
  • Take a pool member offline
  • Create a virtual server with our newly created pool
  • Disable a node
  • Remove the disabled node from our pool
package main

import (  
    "github.com/scottdware/go-bigip"
)

func main() {  
    f5 := bigip.NewSession("f5.company.com", "admin", "secret")

    // Node creation
    f5.CreateNode("web-server1", "10.5.1.10")
    f5.CreateNode("web-server2", "10.5.1.11")
    f5.CreateNode("web-server3", "10.5.1.12")

    // Create our pool
    f5.CreatePool("web_443_pool")

    // Add members to our pool
    f5.AddPoolMember("web_443_pool", "web-server1:443")
    f5.AddPoolMember("web_443_pool", "web-server2:443")
    f5.AddPoolMember("web_443_pool", "web-server3:443")

    // Take our #3 web server offline (disable)
    f5.PoolMemberStatus("web_443_pool", "web-server3:443", "disable")

    // Create a virtual server using the above pool
    // The second parameter is our destination, and the third is the mask. You can use CIDR notation if you wish (as shown here)
    f5.CreateVirtualServer("web_443", "0.0.0.0", "0", "web_443_pool", 443)

    // Take the #3 web server offline for all pools
    f5.NodeStatus("web-server3", "disable")

    // Remove the server from our pool
    f5.DeletePoolMember("web_443_pool", "web-server3:443")
}

Here's an example of how to create a monitor, and assign it to a pool:

package main

import (  
    "github.com/scottdware/go-bigip"
)

func main() {  
    f5 := bigip.NewSession("f5.company.com", "admin", "secret")

    // Parameters are: "name", "parent type", "interval", "timeout", "send string", "receive string".
    f5.CreateMonitor("web_http", "http", 5, 16, "GET /\r\n", "200 OK")
    f5.AddMonitorToPool("web_http", "web_80_pool")
}

Modifying Attributes

You can also modify the settings of everything that we have covered: vlans, pools, virtual servers, routes, route domains, self IP's, nodes, etc.

Because there are so many options, the easiest way (for now) is to modify the fields you want by using the struct for that object. For example, let's modify the VLAN (vlan1010) we created earlier by changing the MTU, Tag, and DAG Round Robin options.

First, let's do a curl against the VLAN and see what the current settings are:

{
  "kind": "tm:net:vlan:vlanstate",
  "name": "vlan1010",
  "fullPath": "vlan1010",
  "generation": 1210,
  "selfLink": "https://f5.company.com/mgmt/tm/net/vlan/vlan1010?ver=11.5.2",
  "autoLasthop": "default",
  "cmpHash": "default",
  "dagRoundRobin": "disabled",
  "failsafe": "disabled",
  "failsafeAction": "failover-restart-tm",
  "failsafeTimeout": 90,
  "ifIndex": 1120,
  "learning": "enable-forward",
  "mtu": 1500,
  "sflow": {
    "pollInterval": 0,
    "pollIntervalGlobal": "yes",
    "samplingRate": 0,
    "samplingRateGlobal": "yes"
  },
  "sourceChecking": "disabled",
  "tag": 4093,
  "interfacesReference": {
    "link": "https://f5.company.com/mgmt/tm/net/vlan/~Common~vlan1010/interfaces?ver=11.5.2",
    "isSubcollection": true
  }
}
package main

import (  
    "github.com/scottdware/go-bigip"
)

func main() {  
    f5 := bigip.NewSession("f5.company.com", "admin", "secret")

    // We need to create a variable which references our Vlan struct to hold our changed values.
    config := &bigip.Vlan{
        MTU: 1450,
        Tag: 1010,
        DAGRoundRobin: "enabled",
    }
    f5.ModifyVlan("vlan1010", config)
}

Now if we do another curl against that VLAN, we should see our changed settings:

{
  "kind": "tm:net:vlan:vlanstate",
  "name": "vlan1010",
  "fullPath": "vlan1010",
  "generation": 1213,
  "selfLink": "https://localhost/mgmt/tm/net/vlan/vlan1010?ver=11.5.2",
  "autoLasthop": "default",
  "cmpHash": "default",
  "dagRoundRobin": "enabled",
  "failsafe": "disabled",
  "failsafeAction": "failover-restart-tm",
  "failsafeTimeout": 90,
  "ifIndex": 1120,
  "learning": "enable-forward",
  "mtu": 1450,
  "sflow": {
    "pollInterval": 0,
    "pollIntervalGlobal": "yes",
    "samplingRate": 0,
    "samplingRateGlobal": "yes"
  },
  "sourceChecking": "disabled",
  "tag": 1010,
  "interfacesReference": {
    "link": "https://f5.company.com/mgmt/tm/net/vlan/~Common~vlan1010/interfaces?ver=11.5.2",
    "isSubcollection": true
  }
}

As you can see, all of the settings reflect our recent changes. This same concept applies to other objects as well. I recommend taking a look at the fields in each struct (documentation) to know what options you can change, and play around with it a bit.

Error Checking/Validation

For all of these examples, I choose not to assign the returned error value to a variable. As you will see in the package documentation, every function can return an error, which you can choose to do the same or check for one:

package main

import (  
    "fmt"
    "github.com/scottdware/go-bigip"
)

func main() {  
    f5 := bigip.NewSession("f5.company.com", "admin", "secret")

    // If we try to add a VLAN tag that is invalid, and we trap/check for the error...
    err := f5.CreateVlan("vlan1010", 10101)
    if err != nil {
        fmt.Println(err)
    }
    // The output would be similar to:
    // 01070061:3: The requested tag 10101 for VLAN /Common/vlan1010 is invalid.

    // Specifying "true" or "false" as the third option represents if the
    // interface is tagged or not.
    err = f5.AddInterfaceToVlan("vlan1010", "1.2", true)
    if err != nil {
        fmt.Println(err)
    }
}

Conclusion

As we demonstrated, the F5's REST API is quite powerful! Currently, only the LTM and Network modules are supported, but I'm actively working on supporting more modules (System, GTM, etc.).

I hope you enjoyed this post, and don't forget to check out the go-bigip package and kick the tires!