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
We only have a google pool and two nodes. The following section will demonstrate auto provisioning with user’s input.
Test the script
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
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:
Pool member or node addition test
This sub menu will check for available pools and list them for user to choose.
So there is a third node which I just added.
Virtual server creation test
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.
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.
Error checking test
This script captures human error and exceptions so that the script will not crash.
Pool conflict test
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()