Getting started with the python SDK part 6: transactions
In the last article we looked at request parameter options. In this article we'll look at how to use transactions with the python SDK.
Before we dig into the SDK implementation, let’s take a look at what a transaction is, why you’d use one, and how the REST interface implements them.
What is a transaction?
In the context of BIG-IP configuration, a transaction is a sequence of individual commands performed as a single unit of work, similar to how a relational database transaction commit only updates the database if all commands within the commit are successful.
Why would I use a transaction?
Transactions exist for the situations where multiple commands are required, or a series of commands are dependent on other commands being successful. It is also useful simply as a desired state for configuration control, where if anything fails, roll it all back and evaluate and not leave partial configs hanging out there.
One situation that has dependencies is the ssl key/cert pair. Updating one or the other, but not both simultaneously, results in an error.
How does the iControl REST interface implement them?
There are three phases in the life of a transaction:
- Creation - This is pretty self explanatory. This is done with a POST to /mgmt/tm/transaction. The json data returned from BIG-IP includes the transaction id.
- Modification - In this phase, you can add, modify, reorder, and delete any commands to the transaction. In order to do so, you generate regular commands (e.g. creating a pool via POST /ltm/pool), but you must include the X-F5-REST-Coordination-Id header with a value of your transaction ID.
- Commit - When your transaction script is as you want it, you send a PATCH to /mgmt/tm/transaction/ID with the json blob {’state’: ‘VALIDATING’}. You can add the attribute validateOnly and set to True if you only want to validate your changes, not commit them.
The steps for the transaction flow without the SDK look like this:
import json import requests # Create your session object b = requests.session() b.auth = ('admin', 'admin') b.verify = False b.headers.update({'Content-Type':'application/json'}) # Create the transaction tx = b.post('https://ltm3.test.local/mgmt/tm/transaction', json.dumps({}).json()['transId'] # Apply the transaction header b.headers.update({'X-F5-REST-Coordination-Id': tx}) ## Do your stuff here ## # Remove the transaction header del b.headers['X-F5-REST-Coordination-Id'] # Commit the transaction Result = b.patch('https://ltm3.test.local/mgmt/tm/transaction/{}'.format(tx), json.dumps({'state':'VALIDATING'})).json()
How do I use them in the python SDK?
With the python SDK, you can use the transaction context manager to abstract all the inner workings of transactions and focus on the actual tasks at hand:
from f5.bigip import ManagementRoot from f5.bigip.contexts import TransactionContextManager # Create your session object b = ManagementRoot('ltm3.test.local', 'admin', 'admin') # Set up the transaction tx = b.tm.transactions.transaction with TransactionContextManager(tx) as api: ## do your stuff here ##
Far more on the side of simple, no? The context manager has enter and exit states that manages all the transaction id, header, and json blob details for you. You can take a peek here on GitHub if you are interested.
For an example error should your transaction fail, below I try to create a pool and create a virtual server with that pool attached. Note that the pool should work, but the virtual will fail as I already have a virtual with that name. Finally, because this was completed within a transaction, the pool will not actually be created because the virtual server creation failed.
>>> with TransactionContextManager(tx) as api: ... api.tm.ltm.pools.pool.create(name='testpool1') ... api.tm.ltm.virtuals.virtual.create(name='testvip', pool='testpool', destination='192.168.102.99:80') Traceback (most recent call last): File "/Users/rahm/Documents/PycharmProjects/f5-common-python/f5/bigip/contexts.py", line 97, in __exit__ validateOnly=self.validate_only) File "/Users/rahm/Documents/PycharmProjects/f5-common-python/f5/bigip/resource.py", line 411, in modify self._modify(**patch) File "/Users/rahm/Documents/PycharmProjects/f5-common-python/f5/bigip/resource.py", line 404, in _modify response = session.patch(patch_uri, json=patch, **requests_params) File "/Users/rahm/Documents/PycharmProjects/f5-common-python/f5py3/lib/python3.7/site-packages/icontrol/session.py", line 284, in wrapper raise iControlUnexpectedHTTPError(error_message, response=response) icontrol.exceptions.iControlUnexpectedHTTPError: 409 Unexpected Error: Conflict for uri: https://ltm3.test.local:443/mgmt/tm/transaction/1536877430032827/ Text: '{"code":409,"message":"transaction failed:01020066:3: The requested Virtual Server (/Common/testvip) already exists in partition Common.","errorStack":[],"apiError":2}' During handling of the above exception, another exception occurred: Traceback (most recent call last): File input, line 3, in File "/Users/rahm/Documents/PycharmProjects/f5-common-python/f5/bigip/contexts.py", line 100, in __exit__ raise TransactionSubmitException(e) f5.sdk_exception.TransactionSubmitException: 409 Unexpected Error: Conflict for uri: https://ltm3.test.local:443/mgmt/tm/transaction/1536877430032827/ Text: '{"code":409,"message":"transaction failed:01020066:3: The requested Virtual Server (/Common/testvip) already exists in partition Common.","errorStack":[],"apiError":2}'
And we can also run a successful transaction, but use the validate_only so it doesn't update the configuration. Notice no error here and that the objects were not created:
>>> with TransactionContextManager(tx, validate_only=True) as api: ... api.tm.ltm.pools.pool.create(name='testpool2') ... api.tm.ltm.virtuals.virtual.create(name='testvip2', pool='testpool2', destination='192.168.102.99:80') >>> >>> b.tm.ltm.pools.pool.exists(name='testpool2') False >>> b.tm.ltm.virtuals.virtual.exists(name='testvip2') False
Hopefully this gives you an idea of how to simplify your transaction updates with python utilizing the f5-common-python SDK. For a more concrete example of a useful transaction, you can see my Let's Encrypt hook script where I'm modifying the ssl key/cert. Happy coding out there!
Note that much of this information is presented strictly from the perspective of the REST interface instead of focusing on the python SDK in the earlier Demystifying the iControl Interface article on transactions.
- Vitaly_NikolaevNimbostratus
Interesting, we fragmenting ltm configuration to domains, basically: route-domain + partition + vlans + bunch of VIPs.
Right now we create "domain" semi-automatically, you need to login to both active/standby LTMs and start creating vlans/route-domain/partition/self_ips on both, then check if everything in sync and start creating the floating part.
we have a script that generates configuration and admin need run multiple config merge commands in sequence to apply it. It pretty risky even with verify. easy to mess up order.
Do you think iControl REST with transactions would be the best way to fully automate this process? can I create global objects (vlans, self ips, route domain) with it.
Thank you
- JRahmAdmin
the sdk certainly supports this, but I would encourage you to use the AS3 package within the automation toolchain. Everything is declarative, so you submit all the config in one request and you're done.
- Peter_BaumannCirrostratus
Well with the SDK and REST-API we can change objects in the configuration AND configure it further in the F5 Admin UI.
With the AS3 we need to do as you said everything in declarative.
For example: We need a solution to only do certificate automation. That means renew/replace/create certificates/keys in F5.
The rest will still be managed by the team over the F5 Admin UI.
I like the AS3 approach but it means when you implement it that everything needs to be declarative.
So in a big organization with a NOC and support and engineering not everyone can create these declarative configs (yet).