[python]F5 iControl API demo

Background
This demo script is to demonstrate to call F5 iCloud API with F5 SDK. The script is written in python, however I have found some limitation in using F5 SDK, best to try with direct call to the REST API by sending json template. This script took me 10hours to finish due to debugging and some exception handling for human error. The purpose is to get familiarise with the F5 SDK features.

An easier method is to use Ansible or Puppet to do auto provision on F5 bigip, with Puppet I only need to add in the json parameters which is much much easier than writing a home brew script.

What this script does?
This script can do the following:
1. Create Pool
2. Choice for user to pool member, if user skips this choice he/she can come add members later.
3. Create virtual server. You can create a VS first without assigning pool, however this option will ask user if he wants to add a pool, currently this option allows only one pool. On next update I will improve this by using an array to store the pools in order to add more than one pool to a virtual server.

Initial setting in F5 bigip
Screen Shot 2018-04-22 at 5.14.43 AM
Screen Shot 2018-04-22 at 5.15.36 AM.png
Screen Shot 2018-04-22 at 5.16.20 AM
We only have a google pool and two nodes. The following section will demonstrate auto provisioning with user’s input.

Test the script
Screen Shot 2018-04-22 at 5.18.03 AM.png
This is the ugly cli menu, I initially wanted to use tkinter, easygui, flask (as a webserver) and wtforms (form validation) modules, but I gave up as learning how to use one of them is another learning curve… I am anxious to know what F5 SDK can do so I improvised…

Pool Creation test

Screen Shot 2018-04-22 at 5.21.41 AM
I have created two nodes in a newly created pool, user can choose to create the node (pool member) later. The sub menu will loop infinitely until user press enter without putting any input.
So here is the result:
Screen Shot 2018-04-22 at 5.23.57 AM
Screen Shot 2018-04-22 at 5.24.41 AM

Pool member or node addition test
Screen Shot 2018-04-22 at 5.26.30 AM
This sub menu will check for available pools and list them for user to choose.

Screen Shot 2018-04-22 at 5.28.26 AM
So there is a third node which I just added.

Virtual server creation test
Screen Shot 2018-04-22 at 5.30.11 AM
I did not provide input for two questions as I have defaults if user just skip the questions, if no inputs from user a F5 accepted default will be used.

Screen Shot 2018-04-22 at 5.32.30 AM
Screen Shot 2018-04-22 at 5.33.28 AM.png
There are other options which I leave them as defaults, F5 SDK does not have an attribute for me to change the performance type, so when I create one VS it is always default to Standard, no attribute for Performance (Http) and Performance Layer4, there are however attributes for IP forwarding and Forward Layer2. To change the fasthttp and fastL4 profiles of a virtual server will need a request module to call the iControl API directly and supply json data.

Screen Shot 2018-04-22 at 5.36.16 AM.png

Error checking test
This script captures human error and exceptions so that the script will not crash.

Pool conflict test
Screen Shot 2018-04-22 at 5.39.12 AM
Screen Shot 2018-04-22 at 5.51.23 AM.png

Here’s the sample code, still lots of bugs to clean, making the script to work is easier if there is no user’s input sanitisation… lol… oh well… below is the sample code….

from f5.bigip import ManagementRoot
import logging, ipaddress

# enable logging with timestamp
logging.basicConfig(filename="bigip_script.log",
                    format="%(asctime)s %(levelname)s:%(message)s",
                    level=logging.INFO)

try:
    # Need to re-write this to store as an encrypted credential file
    mgmt = ManagementRoot("192.168.1.11","admin","121278")
except Exception as e:
    logging.error("Error in connecting to bigip.",e)
    print(e)
    exit(1)

ltm = mgmt.tm.ltm # LTM
vs = ltm.virtuals.virtual # Virtual Server


def choosePool():
    index = 1 # index starts from 1.
    dictPool = {} # dictionary to store the pool.name
    for pool in ltm.pools.get_collection():
        print(str(index) + ". " + pool.name)
        dictPool[index] = pool.name
        index += 1 # this index maps to the pool.name stored.
    choice = input("Which pool do you want to update? ")
    return dictPool[int(choice)] # user enters a digit, need to type cast choice to integer.


def vsMenu():
    # Simple menu to take in user input for creating vs.
    name = input("Enter virtual server name ")
    destination = input("Enter ip address of virtual server ")
    try:
        ipaddress.ip_address(destination) # check for valid ipv4 address.
    except Exception as e:
        logging.error(e)
        print("You have entered an invalid ipv4 address ", e)
        exit(1)
    port = input("Enter the service port number of {} ".format(name))
    if not port:# if user did not enter a value
        port = '0' # default port is any
    ipProtocol = input("Enter protocol of {} ".format(name))
    if not ipProtocol: # if user did not enter a value
        ipProtocol = 'tcp' # default is tcp
    source = input("Enter the source address (your expected visitor, if none just press enter) ")
    if not source: # if user did not enter a value
        source = '0.0.0.0/0' # default is any address
    pool = choosePool()
    print("Which persistence do you prefer?\n")
    print("[1] source address\n")
    print("[2] destination address\n")
    persistChoice = input("Your choice (press enter to skip): ")
    if persistChoice == '1':
        persist = 'source_addr' # session persistence based on source address
    elif persistChoice == '2':
        persist = 'dest_addr' # session persistence based on destination address
    else:
        persist = "" # default value is none.
    destination = destination + ":" + port # persist parameter accepts ip_address:port eg. 192.168.1.1:80 only.
    #print(name,destination,source,ipProtocol,pool,persist)
    createVS(name,destination,source,ipProtocol,pool,persist)


def createVS(name,destination,source,ipProtocol,pool,persist):
    if vs.exists(partition="Common", name=name):
        print("{} exists in bigip!".format(name))
    try:
        logging.info("Creating the Virtual Server {}".format(name))
        # Calling the iControl API using http-post
        vs.create(partition="Common",
                name=name,
                destination=destination,
                source=source,
                ipProtocol=ipProtocol,
                pool=pool,
                persist = persist)
    except Exception as e:
        logging.error(e)
        exit(1)


def addMember():
    poolobj = ltm.pools.pool.load(partition="Common", name=choosePool())
    createMembers(poolobj)


def createMembers(poolObj):
    members = []

    while True:
        nodeMember = input("Enter member ip address: ")
        if not nodeMember: # Quit asking if user does not put in value
            break
        try:
            ipaddress.ip_address(nodeMember) # validate if user has entered a valid ip address
            logging.debug("Collecting {}...".format(nodeMember))
        except Exception as e:
            logging.error("{} is an invalid ipv4 address: ".format(nodeMember),e)
            print("You have entered an invalid ip address: ",e)
            exit(1)
        nodeMemberPort = input("Service port of this member") # Collect server port
        if not nodeMemberPort: # Quit asking when no port is provided.
            break
        # make sure the port number is between 0 and 65535
        elif int(nodeMemberPort) >= 0 and int(nodeMemberPort) <= 65535:
            # collect the ip_address:port eg. 192.168.1.1:80 to members array.
            members.append(nodeMember + ":" + nodeMemberPort)
            logging.info("Collecting {}:{}".format(nodeMember,nodeMemberPort))
        else:
            logging.info("Unknown service port, assume quitting sub-menu...")
            break
    for member in members:
        poolObj.members_s.members.create(partition="Common", name=member)


def createPool():
    poolName = input("Enter pool name eg. pool-Gwen: ")
    # check if pool exists in bigip
    while ltm.pools.pool.exists(partition="Common",name=poolName):
        print("This pool {} is already exist! Choose another name".format(poolName))
        poolName = input("Enter pool name eg. pool-Gwen: ")
    if poolName: # Ask further for inputs if user has enter Pool Name
        print("Select load balancing method:\n")
        print("[1] Round Robin (default)\n")
        print("[2] Least Connections (Member)\n")
        print("[3] Least Connections (Node)\n")
        print("[4] Least sessions\n")
        choice = input("Enter a load balancing method: ")

        if not choice or choice is '1':
            lbMode = "round-robin" # default load balancing method, if user did not enter any value.
        elif choice is '2':
            lbMode = "least-connections-member"
        elif choice is '3':
            lbMode = "least-connections-node"
        else:
            lbMode = "least-sessions"
        try:
            poolObj = ltm.pools.pool.create(partition="Common", name=poolName, loadBalancingMode=lbMode)
            logging.info("Creating pool {} with load balancing method as {}".format(poolName,lbMode))
        except Exception as e:
            logging.error("Error in creating pool:",e)
            print(e)
            exit(1)
        choice = input("Proceed to create members? ")
        if choice.lower() == 'y':
            logging.info("User has selected to create pool members.")
            createMembers(poolObj)
        elif choice.lower() == 'n':
            logging.info("User does not want to create pool members.")
        else:
            print("You have entered an invalid choice, only y or n.\n")


# Ugly menu in CLI
def cliMenu():
    choice = 0
    while choice is not '9':
        print("Menu\n")
        print("====\n")
        print("[1]Create Pool\n")
        print("[2]Add members to existing Pool\n")
        print("[3]Create Virtual Server\n")
        print("[9]Quit\n")
        choice = input("Enter a choice: ")
        if choice == '1':
            createPool()
        elif choice == '2':
            addMember()
        elif choice == '3':
            vsMenu()
        elif choice == '9':
            print("Bye.")
        else:
            print("You have entered an invalid choice.")
            logging.error("User has entered an invalid choice in main menu.")


if __name__ == "__main__":
    cliMenu()
Advertisements
This entry was posted in F5, Python, Scripting and tagged , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s