Technical Articles
F5 SMEs share good practice.
cancel
Showing results for 
Search instead for 
Did you mean: 
JRahm
Community Manager
Community Manager

If you have dabbled with python and iControl over the years, you might be familiar with some of my other “Getting Stared with …” articles on python libraries. I started my last, on Bigsuds, this way:

I imagine the progression for you, the reader, will be something like this in the first six- or seven-hundred milliseconds after reading the title: Oh cool! Wait, what? Don’t we already have like two libraries for python? Really, a third library for python?

It’s past time to update those numbers as the forth library in our python support evolution, the f5-common-python SDK, has been available since March of last year! I still love Bigsuds, but it only supports the iControl SOAP interface. The f5-common-python SDK is under continuous development in support of the iControl REST interface, and like Bigsuds, does a lot of the API heavy lifting for you so you can just focus on the logic of bending BIG-IP configuration to your will. Not all endpoints are supported yet, but please feel free to open an issue on the GitHub repo if there’s something missing you need for your project. In this article, I’ll cover the basics of installing the SDK and how to utilize the core functionality.

Installing the SDK

This section is going to be really short, as the SDK is uploaded to PyPI after reach release, though you can clone the GitHub project and run the development branch with latest features if you so desire. I'd recommend installing in a virtual environment to keep your system python uncluttered, but YMMV.

pip install f5-sdk

A simple one-liner and we're done! Moving on...

Instantiating BIG-IP

The first thing you’ll want to do with your shiny new toy is authenticate to the BIG-IP. You can use basic or token authentication to do so.

I disable the certificate security warnings on my test boxes, but the first two lines in the sample code below are not necessary if you are using valid certificates

>>> import requests
>>> requests.packages.urllib3.disable_warnings()
>>> from f5.bigip import ManagementRoot
>>> # Basic Authentication
>>> b = ManagementRoot('ltm3.test.local', 'admin', 'admin')
>>> # Token Authentication
>>> b = ManagementRoot('ltm3.test.local', 'admin', 'admin', token=True)
>>> b.tmos_version
u'12.1.0'

The b object has credentials attached and various other attributes as well, such as the tmos_version attribute shown above. This is the root object you’ll use (of course you don’t have to call it b, you can call it plutoWillAlwaysBeAPlanetToMe if you want to, but that’s a lot more typing) for all the modules you might interact with on the system.

Nomenclature

The method mappings are tied to the tmsh and REST URL ids. Consider the tmsh command tmsh list /ltm pool. In the URL, this would be https://ip/mgmt/tm/ltm/pool. For the SDK, at the collection level the command would be b.tm.ltm.pools. It's plural here because we are signifying the collection.

0151T000003d743QAA.png

If there is a collection already ending in an s, like the subcollection of a pool in members, it would be addressed as members_s. This will be more clear as we work through examples in later articles, but I wanted to provide a little guidance before moving on.

Working with Collections

There are two types of collections (well three if you include subcollections, but we’ll cover those in a later article,) organizing collections and collections. An organizing collection is a superset of other collections. For example, the ltm or net module listing would be an organizing collection, whereas ltm/pool or net/vlan would be collections. To retrieve either type, you use the get_collection method as shown below, with abbreviated output.

# The LTM Organizing Collection
>>> for x in b.tm.ltm.get_collection():
...     print x
...
{u'reference': {u'link': u'https://localhost/mgmt/tm/ltm/auth?ver=12.1.0'}}
{u'reference': {u'link': u'https://localhost/mgmt/tm/ltm/data-group?ver=12.1.0'}}
{u'reference': {u'link': u'https://localhost/mgmt/tm/ltm/dns?ver=12.1.0'}}

# The Net/Vlan Collection:
>>> vlans = b.tm.net.vlans.get_collection()
>>> for vlan in vlans:
...     print vlan.name
...
vlan10
vlan102
vlan103

Working with Named Resources

A named resource, like a pool, vip, or vlan, is a fully configurable object for which the CURDLE methods are supported. These methods are:

  • create()
  • update()
  • refresh()
  • delete()
  • load()
  • exists()

Let’s work through all these methods with a pool object.

>>> b.tm.ltm.pools.pool.exists(name='mypool2017', partition='Common')
False
>>> p1 = b.tm.ltm.pools.pool.create(name='mypool2017', partition='Common')
>>> p2 = b.tm.ltm.pools.pool.load(name='mypool2017', partition='Common')
>>> p1.loadBalancingMode = 'least-connections-member'
>>> p1.update()
>>> assert p1.loadBalancingMode == p2.loadBalancingMode
Traceback (most recent call last):
  File "", line 1, in
AssertionError
>>> p2.refresh()
>>> assert p1.loadBalancingMode == p2.loadBalancingMode
>>> p1.delete()
>>> b.tm.ltm.pools.pool.exists(name='mypool2017', partition='Common')
False

Notice in line 1, I am looking to see if the pool called mypool2017 exists, to which I get a return value of False. So I can go ahead and create that pool as shown in line 3. In line 4, I load the same pool so I have two local python objects (p1, p2) that reference the same BIG-IP pool (mypool2017.) In line 5, I update the load balancing algorithm from the default of round robin to least connections member. But at this point, only the local python object has been updated. To update the BIG-IP, in line 6 I apply that method to the object. Now if I assert the LB algorithm between the local p1 and p2 python objects as shown in line 7, it fails, because we have updated p1, but p2 is still as it was when I initially loaded it. Refreshing p2 as shown in line 11 will update it (the local python object, not the BIG-IP pool.) Now I assert again in line 12, and it does not fail. As this was just an exercise, I delete the new pool (could be done on p1 or p2 since they reference the same BIG-IP object) in line 13, and a quick check to see if it exists in line 14 returns false.

The great thing is that even though the endpoints change from pool to virtual to rule and so on, the methods used for them do not. 

Next Steps

This is just the tip of the iceberg! There is much more to cover, so come back for the next installment, where we’ll cover unnamed resources and commands. If you can't wait, feel free to dig into the SDK documentation.

Comments
Kurt_Kite_11866
Nimbostratus
Nimbostratus

Hi,

 

I am struggling to figure out a way to retrieve the irules associated with a virtual server using the SDK. I load the virtual:

 

virtual = bigip.ltm.virtuals.virtual.load(partition='Common',name='my-virtual')

 

If I interactively do the above in python and print virtual.raw I can see a "rules" attribute and can iterate over it:

 

for rule in rules: print rule

 

However, if I try this in the python script then it errors out indicating that there is no "rules" attribute associated with the virtual object. What am I missing?

 

Thanks, Kurt

 

JRahm
Community Manager
Community Manager

rules is an attribute of virtual, so you need to do it this way:

 

for rule in virtual.rules:
    print rules

 

example:

 

>>> vip = b.tm.ltm.virtuals.virtual.load(name='testvip_http2', partition='Common')
>>> for rule in vip.rules:
...     print rule
... 
/Common/http2_test
/Common/static_test
/Common/uri_replace

 

Kurt_Kite_11866
Nimbostratus
Nimbostratus

Thanks Jason. It was me just being stupid. That is what I was doing in the script. It turns out the virtual I was checking did not have any rules and I misinterpreted the error returned, "object has no attribute 'rules'." I just had to check for attribute existence before executing that code.

 

JRahm
Community Manager
Community Manager

ah, yeah, I get eliminating attributes not in use to save transport object sizes, but it does make clientside code more complex on the error checking end.

 

IRONMAN
Cirrostratus
Cirrostratus

can we build SDK application to configure the F5 remotely, using python? any other skill required for this? any one guide me? i am not good in python

 

JRahm
Community Manager
Community Manager

the beauty of an API is you can use whatever language you are comfortable with, python just happens to be the one this SDK is based on. We have an entire series on getting started with iControl objects, which focuses on both the SOAP and REST interfaces and covers the basics of interrogating objects. You can find that here. Specific to python, I have a Demystifying the iControl REST interface that you can reference as well, and I will be writing articles in the future on utilizing the sdk itself.

 

Tariq_Iqbal_924
Nimbostratus
Nimbostratus

Jason, can I use the python SDK to generate UCS on daily basis and move the UCS to a remote server?

 

Regards, Tariq

 

JRahm
Community Manager
Community Manager

HI Tariq, yes, totally doable.

 

Hi Jason,

 

I am playing with F5 Python SDK and encountered an unexpected outcome with the exists() of pool member. Here is the code.

 

On my lap BIG-IP, there two pool members with the name, "10.10.10.4:80". and "10.10.10.5:80" To check the existence of the pool member, named "10.10.10.4:80", I ran the following code.

 

pools = mr.tm.ltm.pools.get_collection()

 

for apool in pools:

 

for apm in apool.members_s.get_collection():

 

print(apm.exists(name='10.10.10.4:80'))

 

if apm.name == '10.10.10.4:80':

 

print('10.10.10.4:80 found and True')

 

 

False

 

10.10.10.4:80 found and True

 

False

 

 

Is there anything wrong with my code?

 

Thank you.

 

JRahm
Community Manager
Community Manager

If you add the partition to that check does it return True?

 

.exists(name='10.10.10.4:80', partition='Common')

 

 

yes Jason.

 

 

With partition='Common' parameter, .exists() call works as expected.

 

Now it brought me up another question. How do I know if I need to specify additional parameter like partition='xxx' parameter for exist() call to works?

 

 

Based on my test, I didn't need to specify partition='xxx' parameter for exists() and it worked. For example, most of protocol profile resources; http, tcp, udp, http and persistence profile resources; source, destination, hash persistence )

 

didn't require any additional parameters like partition='xxx'.

 

One exception so far I found was the exists() of Local traffic policy profile resource which requires two additional parameter (partition='xxx' and subPath='xxx') as well as name='xxx'.

 

 

Do you know any easy way of figuring out required parameters of each methods call (Create(), Refresh(), Update(), Delete(), Load(), Exists())?

 

JRahm
Community Manager
Community Manager

there are some inconsistencies we'll need to clean up. Ideally, you should never need to supply the partition, we should assume 'Common' if not supplied. Some parameters are required by the REST interface, not the sdk.

 

Tariq_Iqbal_924
Nimbostratus
Nimbostratus

Jason,

 

What nomenclature should I use to trigger F5 to generate the UCS?

 

Your help in this regard wil be greatly appreciated.

 

Thanks

 

JRahm
Community Manager
Community Manager

Hi Tariq, give this a try:

 

from f5.bigip import ManagementRoot
b = ManagementRoot('192.168.1.245', 'admin', 'admin')
b.tm.sys.ucs.exec_cmd('save', name='bigip1.ucs')

 

Tariq_Iqbal_924
Nimbostratus
Nimbostratus

Thanks Jason for a prompt response. Is there a function that I can instantiate to check if the UCS is complete? I see the exist method and I don't know if that method can be used to check if the UCS creation is completed prior to running commands to copy the UCS to a remote server. Also, can I use the exec_cmd for copying the file using cp? Thanks Jason for all your help.

 

JRahm
Community Manager
Community Manager

no, there isn't a status since this is just a wrapper for a cli command. you could use bash to check for the existence of the file you are creating with the ucs call and then assert:

 

result = b.tm.util.bash.exec_cmd('run', utilCmdArgs='-c "ls /var/local/ucs"')
assert 'bigip1.ucs' in result.commandResult

 

KernelPanic
Nimbostratus
Nimbostratus

Hi Jason, great work you're doing here!

I'm trying to update user parameters from the SDK. Having trouble accessing subcollection partitionAccess and user roll. Could you show an example of how to change a user roll from guest to resource admin, without resorting to exec_cmd.

 

        u = b.tm.auth.users.user.load(name='myuser')
        pp(u.raw)

'uri': u'https://mybox.com:443/mgmt/tm/auth/user/,myuser/'},
     u'description': u'myuser',
     u'encryptedPassword': u'!!',
     u'fullPath': u'myuser',
     u'generation': 1,
     u'kind': u'tm:auth:user:userstate',
     u'name': u'myuser',
     u'partitionAccess': [{u'name': u'all-partitions',
                           u'nameReference': {u'link': u'https://localhost/mgmt/tm/auth/partition/all-partitions?ver=12.1.2'},
                           u'role': u'guest'}],
     u'selfLink': u'https://localhost/mgmt/tm/auth/user/myuser?ver=12.1.2',
     u'shell': u'bash'}

 

JRahm
Community Manager
Community Manager

The partitionAccess field is a list of attributes on the primary user object, but not a subcollection. A true subcollection, like pool members, is addressed like this:

 

>>pool = b.tm.pools.pool.load(name='testpool')
>>members = pool.members_s.get_collection()
>>for member in members:
... print member.name

10.10.10.100:80
192.168.103.20:80

 

To get to your attributes and change them, you just need to pull the dictionary indexed at list item 0:

 

>>> u = b.tm.auth.users.user.load(name='jason')
>>> u.partitionAccess[0]['role']
u'irule-manager'
>>> u.partitionAccess[0]['role'] = 'guest'
>>> u.update()

 

Joel_42834
Nimbostratus
Nimbostratus

F5_Digger has an excellent point that I don't believe has been answered:

 

Many of the objects in the SDK take keyword parameters that don't appear to be documented.

 

For example, the ManagementRoot object takes a "token" parameter. I don't see where in the SDK documentation this is listed.

 

In addition, many objects have functions that are similarly not in the class documentation for that object.

 

I'd like to be using this SDK for some serious work but am finding it very frustrating due to incomplete documentation.

 

JRahm
Community Manager
Community Manager

Hi @Joel, I'm working on documentation updates this quarter, and hope to have the first updated version released the first week of January.

 

Joel_42834
Nimbostratus
Nimbostratus

Thanks - It is very much appreciated.

 

Joel_42834
Nimbostratus
Nimbostratus

using bigsuds, you could set the active partition globally using "System.Session.set_active_folder".

 

what is the equivalent in this SDK? or must the partition be specified on each call?

 

JRahm
Community Manager
Community Manager

there is no active partition concept when interacting with REST interface. Specifying the partition on each object would be recommended if you have anything other than Common defined.

 

Aaron_Murray_22
Nimbostratus
Nimbostratus

I am trying to use the SDK in python to get the status of F5 GTM Pool Members on F5s running 11.6. I am able to read whether the Pool Member is enabled or disabled but I can not figure out how to find the availability of the member and have searched and searched the documentation and forums for this but am unable to find an answer. Can someone help me to find the availability of an F5 GTM Pool Member through the F5 SDK for python.

 

Thanks.

 

JRahm
Community Manager
Community Manager

I don't think this is an SDK issue, just direct to the rest interface, I am not seeing status for availability-state or enabled-state on GTM pool (or Server Virtual server objects for that matter) objects, though if you look at that detail in tmsh, it's there.

 

Aaron_Murray_22
Nimbostratus
Nimbostratus

Pools enabled or disabled is available as a Boolean, but I haven't been able to find a way to check availability. But if you can't find it then it must not be available. Would you suggest then checking it via command execution through the SDK then?

 

Thanks.

 

JRahm
Community Manager
Community Manager

Can do this via a tmsh script and a REST bash call. Wrote this up in article form...thanks for the idea, Aaron!

 

Aaron_Murray_22
Nimbostratus
Nimbostratus

Jason, Thank you so much for your help, I have just one more question as I am not familiar with the scripting and how to write it. Is there a way to modify the script such that it outputs the avaibility, status, and order all on the same line for each member?

 

Thanks.

 

JRahm
Community Manager
Community Manager

Instead of calling putstring (puts) in the for loop, you can create a variable (var) and then append each iteration through the for loop to that with a comma, then call puts afterward. Mocked up below:

 

% set str "ab cd ef gh ij kl mn op qr st uv wx yz"
% set var ""
% foreach c $str { append var $c, }
% puts $var
ab,cd,ef,gh,ij,kl,mn,op,qr,st,uv,wx,yz,

 

Joel_42834
Nimbostratus
Nimbostratus

What is the equivalent of "show /cm failover-status"?

 

I use it to determine whether the current LTM is the active unit in an HA pair.

 

Thanks

 

JRahm
Community Manager
Community Manager

Hi @Joel, the Stats utility needs work, but you can use it to demangle the nested stats details that you get from the rest interface to get that value:

 

from f5.bigip import ManagementRoot
from f5.utils.responses.handlers import Stats
b = ManagementRoot('ltm3.test.local', 'admin', 'admin')
ha_status = b.tm.cm.sync_status.load()
Stats(ha_status).stat.color['description']
u'green'

 

JRahm
Community Manager
Community Manager

oh wait, you said failover, not sync. That endpoint hasn't been added yet, tracking issue 1370 on the sdk and I'll try to get that added in the next release, but the methods will be the same as shown.

 

Joel_42834
Nimbostratus
Nimbostratus

Jason,

 

My very next question was going to be the sync status!!

 

For the failover status, what is your recommendation?

 

Thanks

 

JRahm
Community Manager
Community Manager

until I get the endpoint added properly, you can use the bash endpoint to run the tmsh command natively and pull the value out of commandResult.

 

JRahm
Community Manager
Community Manager

Hi @Joel, I got failover status built last night and it was merged to the sdk development branch today. You can grab that or wait for Friday’s release

 

Aaron_Murray_22
Nimbostratus
Nimbostratus

I just did an upgrade on my F5 code from 11.6.0 which is unsupported to 12.1.2 HF2. I am now having a problem querying the pools on the GTM. I get the following:

 

File "C:\Program Files (x86)\Python36-32\lib\site-packages\f5\bigip\mixins.py", line 102, in getattr raise AttributeError(error_message) AttributeError: 'f5.bigip.tm.gtm.pool.PoolOrganizingCollection'>' object has no attribute 'pool'

 

Has anyone seen this error before and know what could be causing it?

 

Thanks.

 

Kurt_Kite_11866
Nimbostratus
Nimbostratus

Hi Aaron,

 

When I have encountered with error it meant that the attribute I was trying to access was not present. In my case, this attribute was optional so I had to check for existence before trying to access it, e.g.:

 

if hasattr(virtual, "rules"): data["vs_rules"] = virtual.rules

 

Best Regards, Kurt

 

Aaron_Murray_22
Nimbostratus
Nimbostratus

So after some more research and tracking I found that I am having a problem with reading the number of current connections on the new version. This is how I read it before:

 

member_stats.entries.get('serverside.curConns')['value']

 

However now I get the following error:

 

member_stats.entries.get('serverside.curConns')['value'] TypeError: 'NoneType' object is not subscriptable

 

Anyone have any ideas on this one?

 

Thanks.

 

Aaron_Murray_22
Nimbostratus
Nimbostratus

So I should have specified that this problem is with getting information from LTM Pools and it is using the F5-sdk-3.0.11. The code was working fine on 11.6.0 but now throws the error on 12.1.2. Thanks.

 

JRahm_128324
Historic F5 Account

can you open an issue on GitHub? Please provide code and version details there.

 

Hi Jason,

 

I tried to get the list of policy-strategy from BIG-IP 12.1.2 and noticed that current python SDK doesn't provide a wrapper function for that.

 

Does it sound right?

 

I just want to make sure this is not supported through Python SDK yet.

 

Thank you.

 

JRahm_128324
Historic F5 Account

Can you open an issue on GitHub for that and I'll take a look?

 

Will do. Thank you Jason.

 

Joel_42834
Nimbostratus
Nimbostratus

Jason,

 

I just looked at the changes for obtaining the HA failover status of a node.

 

Here's what I was able to come up with:

 

mgmt = ManagementRoot(host, username, password)

 

xx = mgmt.tm.cm.failover_status.load()

 

print(xx.attrs['entries']['']['nestedStats']['entries']['status']['description'])

 

'ACTIVE'

 

Is there an easier way to get this information?

 

Thanks,

 

Joel

 

JRahm
Community Manager
Community Manager

Hi Joel, yes, using the Stats utility makes this a little cleaner:

 

>>> from f5.utils.responses.handlers import Stats
>>> x = Stats(b.tm.cm.failover_status.load())
>>> x.stat['status']['description']
u'ACTIVE'

 

Joel_42834
Nimbostratus
Nimbostratus

Thanks Jason, I'll give it a try.

 

I would suggest that you need to be thinking about cleaner interfaces as well the customer view.

 

An SDK is supposed to make getting this information easy/trivial.

 

I would have expected this:

 

b.tm.cm.failover_status.load()['status']['description']

 

Joel

 

JRahm
Community Manager
Community Manager

We try to make sure the methods themselves are "dumb" and do what the interface intends, and fix some methods that are out of standard from other normal behaviors. Stats were changed from 11.6 - 12.0 pretty significantly, and whereas the Stats utility isn't as robust as it should be yet, it allows us to honor the goals for methods while providing options for the nightmare you posted.

 

That said, feel free to open an issue on GitHub for stats object kinds and suggested options on how to handle that and we'll discuss.

 

Maybe an alternative in the load method is to pass a kwarg for Stats, or just an alternative load method called load_stats()?

 

Joel_42834
Nimbostratus
Nimbostratus

I guess the question is whether "stats" is for statistics or status.

 

IMHO, the failover state is a status and not a statistic and therefore, it should be a direct attribute of an object.

 

RANT WARNING

 

As for whether SDK methods should be "dumb", I would suggest that that is not the purpose of an SDK. An SDK should provide a view of the system that makes sense independent of the transport mechanism. Whether the SDK methods are "dumb" or not should be driven by the functionality of what the method is trying to do for the user.

 

My experience with this SDK is that it is a thin layer above the REST API. This view is supported by the SDK documentation which really is a description of how to map the SDK's Python objects to the REST API. This is further supported by the numerous references to URIs throughout the documentation. From a Python perspective, the URIs don't belong in the documenation. They are an implementation detail and not a functional one.

 

The fact that it is using a REST API, a SOAP API or some other proprietary protocol is irrelevant to me. I simply don't care. I care about getting stuff done on the LTM. And the SDK should present a view of the system that makes sense from a Python perspective.

 

And the SDK should be focused on that. And I don't think it is. Instead it is focused on presenting the REST API with as little work as possible.

 

I come from the world of TMSH. I never learned the REST API. My expectation with the SDK was that I wouldn't have to.

 

END OF RANT

 

JRahm
Community Manager
Community Manager

Hi Joel. I'm not sure why non-stats are returned as stats in some endpoints, but that's the way the REST interface is currently. The decision on the sdk team was to not create all the one-offs that would have been required to make it seamless across the interface.

 

Having used and participated in the development of the sdk, I happen to like it a lot and it's been very helpful for me in cutting down the amount of code I need to write. However, I understand the frustration and you have some very valid arguments, so no worries on the rant! The sdk itself (I think, I joined after the structure was decided upon) was started as an iterative stepping stone for building declarative models with tools like Ansible, so whereas we benefit from its existence, I'm not completely sure the original intent was for it to be consumed directly.

 

We will discuss your feedback. We are always open to feedback like this, and if you have suggestions on how this (or another library entirely) can be improved, how you would like to consume the interfac, etc, we'd be glad to have that discussion.

 

Muhammad_64435
Nimbostratus
Nimbostratus

Jason, great article and awesome SDK ... i just fall in love with it.

I am working on migrating sites from one 'old' unit to the new one. Plan is to read the following information from the Old unit :

  1. Virtual Server
  2. Pools
  3. Persistence profile
  4. SSL Profile
  5. Data Groups
  6. iRules etc.

and create a script to re-create them ditto on the new unit.

 

So far I have developed the Virtual Server script and wondering if you can see if its the right way of doing it :

 

from f5.bigip import ManagementRoot
import getpass

mgmt = ManagementRoot(BigIP_IP, "admin", "pass")
print ("===Create a Virtual===")
vip = mgmt.tm.ltm.virtuals.virtual.create(\
 name='testvip',\
 partition='Common',\
 destination='10.10.10.10:80',\
 ipProtocol='tcp',\
 sourceAddressTranslation={'type': 'automap'},\
 persist=[ { 'name': 'testvip-Cookie' } ],\
 rules=["testvip-rules"],\
 profilesReference={ 'items' : [ { 'name' : 'http_XForwardedFor' }, \
 {"name": "oneconnect"},\
 {"name": "tcp-lan-optimized", "context": "serverside"},\
 {"name": "tcp-wan-optimized", "context": "clientside"},\
 {"name": "testvip-clientssl-ssl"}
 ] }
)

 

But one thing I couldn't able to do is how to disable the above VIP. I mean "state"="disabled". I tried disabled = 'true' but it errors out.

 

Second, I need to modify the virtual-address attached to the above testVIP (10.10.10.10) to disable "arp" and "icmpEcho" and change the traffic-group but I couldn't able to even check if it exists :

 

 

vip = mgmt.tm.ltm.virtualAddress.exists(name='10.10.10.10', partition='Common')

 

It keeps giving an error

 

 

AttributeError: '' object has no attribute 'virtualAddress'

 

Please let me know what I am missing or doing wrong.

 

Highly appreciate your help.

 

Thank you,

 

Muhammad

 

Version history
Last update:
‎27-Jul-2017 12:30
Updated by:
Contributors