bigrest
5 TopicsTinkering with the BIGREST Python SDK - Part 1
A couple months back, Leonardo Souza (one of our MVPs) released a new SDK for interacting with the iControl REST interface. A write up right here on DevCentral covers the high level basics of why he rolled his own when we already had one (f5-common-python). In this article, I'm going to compare and contrast the basic functionality of the old SDK with Leo's new SDK, and then add some color commentary after each section. Instantiating BIG-IP This first task is required to build a session with the BIG-IP (or BIG-IQ, but this article will focus on BIG-IP.) This can be done with basic authentication or with tokens, the latter of which you’ll need for remote authentication servers. For this article, the device objects will be labeled fcp for the f5-common-python SDK and br for the bigrest SDK. f5-common-python from f5.bigip import ManagementRoot # Basic Auth fcp = ManagementRoot('10.0.2.15', 'admin', 'admin') # Token Auth fcp = ManagementRoot('10.0.2.15', 'admin', 'admin', token=True) bigrest from bigrest.bigip import BIGIP # Basic Auth br = BIGIP('10.0.2.15', 'admin', 'admin') # Token Auth br = BIGIP('10.0.2.15', 'admin', 'admin', request_token=True) Conclusion For basic auth, instantiation is a wash with the exception of the method itself. In the f5-common-python SDK, ManagementRoot is used for both BIG-IP and BIG-IQ systems, whereas in BIGREST, it is system specific. Being explicit is a good thing, as with BIGREST you’ll always know if you are on BIG-IP or BIG-IQ, but if you are automating tests, using a single nomenclature would likely reduce lines of code. (Updated 9/25) For token authentication, the token and request_token parameters exist to handle exactly that. No differentiator in functionality between the two at all, both work as advertised. What you don’t see in either of these is how they handle SSL certificates. Ideally, from a security standpoint you should have to opt out of a secure transaction, but both libraries are set to ignore certificate errors. f5-common-python has an attribute (verify) that is defaulted to false and it doesn’t look configurable in bigrest. In my view both should be refactored to default to a secure posture so insecurity is an explicit decision by the programmer. The final observation I have on instantiation is proxy support, which is built-in for f5-common-python but not bigrest. Retrieving a List of Pools In this quick comparison, I just want to see how easy it is to grab a list of objects and display their names. It could be anything, but I’ve chosen the pool here. We’ll assume instantiation has already taken place. f5-common-python p1 = fcp.tm.ltm.pools.get_collection() for p in p1: ... print(p.name) ... NewPool p_tcpopt testpool bigrest p2 = br.load('/mgmt/tm/ltm/pool') for p in p2: ... print(p.properties.get('name')) ... NewPool p_tcpopt testpool Conclusion Here is where you can start to see some differences in design choices. In the f5-common-python approach, the SDK methods are associated to defined iControl REST interfaces. With bigrest, that approach is avoided so that no development effort is needed to support missing or new interfaces in the SDK. Whereas that’s a smart choice in my opinion, it does put a little more effort on the programmer to know the interfaces, but on the flip side, the programmer doesn’t need to learn another somewhat different nomenclature in the SDK if they are already familiar with the tmsh endpoints. As far as the data itself is concerned, I do like the easy access to the key/value pairs in f5-common-python (p.name) versus bigrest (p.properties.get(‘name’), or p.properties[’name’] if you prefer), but that’s a minor gripe given the flexibility of loading any endpoint with a single simple load command. CRUD Operations for a Pool and its Members f5-common-python Creating a pool and a pool member pool = fcp.tm.ltm.pools.pool.create(name='fcp_pool', partition='Common') fcp.tm.ltm.pools.pool.exists(name='fcp_pool', partition='Common') True pool_member = pool.members_s.members.create(name='192.168.102.44:80', partition='Common') pool.members_s.members.exists(name='192.168.102.44:80', partition='Common') True Refreshing and updating a pool # Load and refresh without updating p1 = fcp.tm.ltm.pools.pool.load(name='fcp_pool', partition='Common') p1.description Traceback (most recent call last): File "", line 1, in File "/Users/rahm/Documents/PycharmProjects/scripts/py38/lib/python3.8/site-packages/f5/bigip/mixins.py", line 102, in __getattr__ raise AttributeError(error_message) AttributeError: '<class 'f5.bigip.tm.ltm.pool.Pool'>' object has no attribute 'description' p1.description = 'updating description' p1.description 'updating description' p1.refresh() p1.description Traceback (most recent call last): File "", line 1, in File "/Users/rahm/Documents/PycharmProjects/scripts/py38/lib/python3.8/site-packages/f5/bigip/mixins.py", line 102, in __getattr__ raise AttributeError(error_message) AttributeError: '<class 'f5.bigip.tm.ltm.pool.Pool'>' object has no attribute 'description' # Load, update, and refresh p1 = fcp.tm.ltm.pools.pool.load(name='fcp_pool', partition='Common') p2 = fcp.tm.ltm.pools.pool.load(name='fcp_pool', partition='Common') assert p1.name == p2.name p1.description = 'updating description' p1.update() p1.description 'updating description' p2.description Traceback (most recent call last): File "", line 1, in File "/Users/rahm/Documents/PycharmProjects/scripts/py38/lib/python3.8/site-packages/f5/bigip/mixins.py", line 102, in __getattr__ raise AttributeError(error_message) AttributeError: '<class 'f5.bigip.tm.ltm.pool.Pool'>' object has no attribute 'description' p2.refresh() p2.description 'updating description' Deleting a pool p1 = fcp.tm.ltm.pools.pool.load(name='fcp_pool', partition='Common') p1.delete() fcp.tm.ltm.pools.pool.exists(name='fcp_pool', partition='Common') False bigrest Creating a pool and a pool member pool_data = {} pool_data['name'] = 'br_pool' pool = br.create('/mgmt/tm/ltm/pool', pool_data) br.exist('/mgmt/tm/ltm/pool/br_pool') True pm_data = {} pm_data['name'] = '192.168.102.44:80' pool_member = br.create('/mgmt/tm/ltm/pool/br_pool/members', pm_data) br.exist('/mgmt/tm/ltm/pool/br_pool/members/~Common~192.168.102.44:80') True Refreshing and updating a pool # Load and refresh without updating p1 = br.load('/mgmt/tm/ltm/pool/br_pool')[0] p1.properties['description'] Traceback (most recent call last): File "", line 1, in KeyError: 'description' p1.properties['description'] = 'updating description' p1.properties['description'] 'updating description' p1 = br.load('/mgmt/tm/ltm/pool/br_pool')[0] p1.properties['description'] Traceback (most recent call last): File "", line 1, in KeyError: 'description' # Load, update and refresh p1 = br.load('/mgmt/tm/ltm/pool/br_pool')[0] p2 = br.load('/mgmt/tm/ltm/pool/br_pool')[0] assert p1.properties['name'] == p2.properties['name'] p1.properties['description'] = 'updating description' p1 = br.save(p1) p1.properties['description'] 'updating description' p2.properties['description'] Traceback (most recent call last): File "", line 1, in KeyError: 'description' p2 = br.load('/mgmt/tm/ltm/pool/br_pool')[0] p2.properties['description'] 'updating description' Deleting a pool br.delete('/mgmt/tm/ltm/pool/br_pool') br.exist('/mgmt/tm/ltm/pool/br_pool') False Conclusion With both approaches, you need to understand the tmsh hierarchy of BIG-IP objects. That said, with bigrest, you only need to know that. With f5-common-python, you need to understand as well the nuances of the SDK authors and how it was built (see pools.pool.members_s.members above in the code.) I also like with bigrest that you can delete an object with one command; in f5-common-python you have to load the object before deleting it. I don’t like that bigrest doesn’t distinguish between collections and single objects, so that means you have to do a little extra repetitive work with isolating the single list object instead of that being handled for you. Being able to work with any collection or single object, though, is super handy and a big differentiator, as bigrest isn’t bogged down with defining the endpoints. I’m sure there are some (BIG-IP) version specific gotchas with the bigrest save command throwing the whole object back to the REST interface, as I recall coding around many of the nuances with f5-common-python where we’d have to strip some attributes before updating, but that’s a design choice where keeping things super “dumb” makes everything more flexible. There is no refresh method in bigrest, but reloading the object works just as well, and it should be clear by now that bigrest’s update method is called save and works at the device level instead of the local python object level but that’s also a design choice that doesn’t bother me at all. My final observation here is the requirement in bigrest to build a data object. I don't hate it, but I do like with f5-common-python how I can just pass an attribute to the method and it creates the data object for me. Updating Unnamed Objects Most objects are created and thus have names, but there are many that exist in the system that are not named and cannot be created or deleted. f5-common-python Adding DNS servers to the BIG-IP configuration settings = fcp.tm.sys.dns.load() settings.nameServers = ['8.8.8.8'] settings.update() bigrest Adding DNS servers to the BIG-IP configuration settings = br.load('/mgmt/tm/sys/dns')[0] settings.properties['nameServers'] = ['8.8.8.8'] settings = br.save(settings) Conclusion There are no real challenging or intriguing differences here that I haven’t already covered, so let’s just move on! More to come... In part two I’ll move beyond the basic building blocks of configuration to look at how to run commands, upload and download files, and work with stats, transactions, and task. Afterward, I’ll weigh in on my overall thoughts for both projects and where to go from here.1.4KViews1like5Comments