Managing ZoneRunner Resource Records with Bigsuds

Over the last several years, there have been questions internal and external on how to manage ZoneRunner (the GUI tool in F5 DNS that allows you to manage DNS zones and records) resources via the REST interface. But that's a no can do with the iControl REST--it doesn't have that functionality. It was brought to my attention by one of our solutions engineers that a customer is using some methods in the SOAP interface that allows you to do just that...which was news to me! The things you learn... In this article, I'll highlight a few of the methods available to you and work on a sample domain in the python module bigsuds that utilizes the suds SOAP library for communication duties with the BIG-IP iControl SOAP interface.

Test Domain & Procedure

For demonstration purposes, I'll create a domain in the external view, dctest1.local, with the following attributes that mirrors nearly identically one I created in the GUI:

  • Type: master
  • Zone Name: dctest1.local.
  • Zone File Name: db.external.dctest1.local.
  • Options: allow-update from localhost
  • TTL: 500
  • SOA: ns1.dctest1.local.
  • Email: hostmaster.ns1.dctest1.local.
  • Serial: 2021092201
  • Refresh: 10800
  • Retry: 3600
  • Expire: 604800
  • Negative TTL: 60

I'll also add a couple type A records to that domain:

  • name: mail.dctest1.local., address:, TTL: 86400
  • name: www.dctest1.local., address:, TTL: 3600

After adding the records, I'll update one of them, changing the IP and the TTL:

  • name: mail.dctest1.local., address:, ttl: 900

Then I'll delete the other one:

  • name: www.dctest1.local., address:, TTL: 3600

And finally, I'll delete the zone:

  • name: dctest1.local.

ZoneRunner Methods

All the methods can be found on Clouddocs in the ZoneRunner, Zone, and ResourceRecord method pages. The specific methods we'll use in our highlight real are:

With each method, there is a data structure that the interface expects. Each link above provides the details, but let's look at an example with the add_a method. The method requires three parameters, view_zones, a_records, and sync_ptrs, which the image of the table shows below.

The boolean is just a True/False value in a list. The reason the list ( [] ) is there for all the attributes is because you can send a single request to update more than one zone, and add more than one record within each zone if desired. The data structure for view_zones and a_records is in the following two images.

Now that we have an idea of what the methods require, let's take a look at some code!

Methods In Action

First, I import bigsuds and initialize the BIG-IP. The arguments are ordered in bigsuds for host, username, and password. If the default “admin/admin” is used, they are assumed, as is shown here.

import bigsuds

b = bigsuds.BIGIP(hostname='ltm3.test.local')

Next, I need to format the ViewZone data in a native python dictionary, and then I check for the existence of that zone.

zone_view = {'view_name': 'external',
             'zone_name': 'dctest1.local.'

# [0]

Note that the return value, which should be a list of booleans, is a list with a 0. I’m guessing that’s either suds or the bigsuds implementation doing that, but it’s important to note if you’re checking for a boolean False. It’s also necessary to set the booleans as 0 or 1 as well when sending requests to BIG-IP with bigsuds.

Now I will create the zone since it does not yet exist. From the add_zone_text method description on Clouddocs, note that I need to supply, in separate parameters, the zone info, the appropriate zone records, and the boolean to sync reverse records or not.

zone_add_info = {'view_name': 'external',
                 'zone_name': 'dctest1.local.',
                 'zone_type': 'MASTER',
                 'zone_file': 'db.external.dctest1.local.',
                 'option_seq': ['allow-update {    localhost;};']}
zone_add_records = 'dctest1.local. 500 IN SOA ns1.dctest1.local. hostmaster.ns1.dctest1.local. 2021092201 10800 3600 604800 60;\n' \
                   'dctest1.local. 3600 IN NS ns1.dctest1.local.;\n' \
                   'ns1.dctest1.local. 3600 IN A;'


# [1]

Note that the strings here require a detailed understanding of DNS record formatting, the individual fields are not parameters that can be set like in the ZoneRunner GUI. But, I am confident there is an abundance of modules that manage DNS formatting in the python ecosystem that could simplify the data structuring. After creating the zone, another check to see if the zone exists results in a true condition. Huzzah!

Now I’ll check the zone info and the existing records for that zone.

zone = b.Management.Zone.get_zone_v2([zone_view])
for k, v in zone[0].items():
    print(f'{k}: {v}')

# view_name: external
# zone_name: dctest1.local.
# zone_type: MASTER
# zone_file: "db.external.dctest1.local."
# option_seq: ['allow-update {    localhost;};']

rrs = b.Management.ResourceRecord.get_rrs([zone_view])
for rr in rrs[0]:

# dctest1.local.          500     IN      SOA     ns1.dctest1.local. hostmaster.ns1.dctest1.local. 2021092201 10800 3600 604800 60
# dctest1.local.          3600    IN      NS      ns1.dctest1.local.
# ns1.dctest1.local.      3600    IN      A

Everything checks outs! Next I’ll create the A records for the mail and www services. I’m going to add a filter to only check for the mail/www services for printing to cut down on the lines, but know that they’re still there going forward.

a1 = {'domain_name': 'mail.dctest1.local.',
      'ip_address': '',
      'ttl': 86400}
a2 = {'domain_name': 'www.dctest1.local.',
      'ip_address': '',
      'ttl': 3600}
                                  a_records=[[a1, a2]],
rrs = b.Management.ResourceRecord.get_rrs([zone_view])
for rr in rrs[0]:
    if any(item in rr for item in ['mail', 'www']):

# mail.dctest1.local.     86400   IN      A
# www.dctest1.local.      3600    IN      A

Here you can see that I’m adding two records to the zone specified and not creating the reverse records (not included for brevity, but in prod would be likely). Now I’ll update the mail address and TTL.

rrs = b.Management.ResourceRecord.get_rrs([zone_view])
for rr in rrs[0]:
    if any(item in rr for item in ['mail', 'www']):

# mail.dctest1.local.     900     IN      A
# www.dctest1.local.      3600    IN      A

You can see that the address and TTL updated as expected. Note that with the update_/N/ methods, you need to provide the old and new, not just the new. Let’s get destruction and delete the www record!

rrs = b.Management.ResourceRecord.get_rrs([zone_view])
for rr in rrs[0]:
    if any(item in rr for item in ['mail', 'www']):

# mail.dctest1.local.     900     IN      A

And your web service is now unreachable via DNS. Congratulations! But there’s more damage we can do: it’s time to delete the whole zone.


# [0]

And that’s a wrap! As I said, it’s been years since I have spent time with the iControl SOAP interface. It’s nice to know that even though most of what we do is done through REST, imperatively or declaratively, that some missing functionality in that interface is still alive and kicking via SOAP. H/T to Scott Huddy for the nudge to investigate this. Questions? Drop me a comment below. Happy coding!

A gist of these samples is available on GitHub.

Published Sep 23, 2021
Version 1.0

Was this article helpful?

1 Comment

  • Awesome post, right now I'm doing an automation to add new zones and records in Zonerunner. Do you know if this 'big suds' library only runs with python 2?