Getting started with the python SDK part 2: unnamed resources and commands
In the first article in this series we looked at the sdk installation steps, nomenclature, and basic BIG-IP instantiation. In this article, we’ll focus on unnamed resources and commands.
Working with Unnamed Resources
Unlike named resources like virtual servers and pools, an unnamed resource cannot be created or deleted, but otherwise can be configured similarly. A good example of an unnamed resource would be the BIG-IP system DNS settings. The path for this object is mgmt->tm->sys->dns. First, we need to load the object. I'll instantiate in this first example and assume it for the remaining ones.
import requests from f5.bigip import ManagementRoot from pprint import pprint as pp # for pretty print of json data requests.packages.urllib3.disable_warnings() # ignore cert warnings b = ManagementRoot('ltm3.test.local', 'admin', 'admin') obj_dns = b.tm.sys.dns.load() pp(obj_dns.raw) {'_meta_data': {'allowed_commands': [], 'bigip': , 'container': , 'exclusive_attributes': [], 'icontrol_version': '', 'icr_session': , 'minimum_version': '11.5.0', 'object_has_stats': True, 'required_command_parameters': set([]), 'required_json_kind': 'tm:sys:dns:dnsstate', 'required_load_parameters': set([]), 'uri': 'https://ltm3.test.local:443/mgmt/tm/sys/dns/'}, u'description': u'configured-by-dhcp', u'kind': u'tm:sys:dns:dnsstate', u'nameServers': [u'10.10.10.1'], u'numberOfDots': 0, u'search': [u'localdomain'], u'selfLink': u'https://localhost/mgmt/tm/sys/dns?ver=12.1.0'}
The metadata is added by the sdk, but the important attributes for configuration purposes here are nameServers and search. If we want to add a google cache resolver and a test.local domain to the seach field, we set the attributes appropriately as lists as shown below.
obj_dns.nameServers = ['10.10.10.1', '8.8.8.8'] obj_dns.search = ['localdomain', 'test.local'] obj_dns.update() obj_dns.refresh() obj_dns.nameServers [u'10.10.10.1', u'8.8.8.8'] obj_dns.search [u'localdomain', u'test.local']
Once the attributes are set, we call the update method. This will execute a refresh of the local object as well, but for demonstration purposes let’s call the refresh method as well so we can validate our new name servers and search list are accurate, which they are.
There are named resources in the API that behave like unnamed resources, in that they cannot be created or deleted, just updated. Examples would include authentication sources like tacacs, and the management-ip. But they still need to be loaded by name, unlike unnamed resources.
Working with Commands
Commands are interesting as we’re not working with configuration objects, we’re executing tasks like saving or loading the configuration, or kicking off a qkview for diagnostics. The method for commands is exec_cmd. Most commands, like the tmsh utilities, use the keyword run, but for configuration it would be load or save. There is usually a keyword argument required but not always. For our first example, let's look at a simple save of the configuration.
b.tm.sys.config.exec_cmd('save')
That’s it! Pretty simple and straight forward. A load is the same way, unless you are merging files, in which case you need to do a little extra work.
options = {} options['file'] = '/var/config/rest/downloads/myfile.txt' options['merge'] = True b.tm.sys.config.exec_cmd('load', options=[options])
I'm looking into updating the sdk to make a merge cleaner. Also note that the ability to load has been merged to development but isn't yet in an official release.
Utility commands to work with files, ciphers, or dns clients are also available via the sdk. Here are a few examples:
## Checking DNS resolution from BIG-IP >>> digresults = b.tm.util.dig.exec_cmd('run', utilCmdArgs='@8.8.8.8 www.google.com') >>> digresults.commandResult u'\n; DiG 9.9.8-P4 @8.8.8.8 www.google.com\n; (1 server found)\n;; global options: +cmd\n;; Got answer:\n;; -HEADER- opcode: QUERY, status: NOERROR, id: 253\n;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1\n\n;; OPT PSEUDOSECTION:\n; EDNS: version: 0, flags:; udp: 512\n;; QUESTION SECTION:\n;www.google.com.\t\t\tIN\tA\n\n;; ANSWER SECTION:\nwww.google.com.\t\t225\tIN\tA\t216.58.192.228\n\n;; Query time: 29 msec\n;; SERVER: 8.8.8.8#53(8.8.8.8)\n;; WHEN: Thu Aug 24 14:12:30 CDT 2017\n;; MSG SIZE rcvd: 59\n\n' >
Note: data returned from utility commands isn't formatted as objects, it's just standard console output (note the newlines above) so any parsing required is up to you. In this next example, we use the python print command to format the newlines properly in displaying the data.
## Checking the Client Ciphers with a specified string >>> cciphers = b.tm.util.clientssl_ciphers.exec_cmd('run', utilCmdArgs='TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:@STRENGTH') >>> print cciphers.commandResult ID SUITE BITS PROT METHOD CIPHER MAC KEYX 0: 49172 ECDHE-RSA-AES256-CBC-SHA 256 TLS1 Native AES SHA ECDHE_RSA 1: 49162 ECDHE-ECDSA-AES256-SHA 256 TLS1 Native AES SHA ECDHE_ECDSA 2: 57 DHE-RSA-AES256-SHA 256 TLS1 Native AES SHA EDH/RSA 3: 56 DHE-DSS-AES256-SHA 256 TLS1 Native AES SHA DHE/DSS 4: 58 ADH-AES256-SHA 256 TLS1 Native AES SHA ADH 5: 49167 ECDH-RSA-AES256-SHA 256 TLS1 Native AES SHA ECDH_RSA 6: 49157 ECDH-ECDSA-AES256-SHA 256 TLS1 Native AES SHA ECDH_ECDSA 7: 53 AES256-SHA 256 TLS1 Native AES SHA RSA 8: 136 DHE-RSA-CAMELLIA256-SHA 256 TLS1 Native CAMELLIA SHA EDH/RSA 9: 135 DHE-DSS-CAMELLIA256-SHA 256 TLS1 Native CAMELLIA SHA DHE/DSS 10: 132 CAMELLIA256-SHA 256 TLS1 Native CAMELLIA SHA RSA 11: 49170 ECDHE-RSA-DES-CBC3-SHA 168 TLS1 Native DES SHA ECDHE_RSA 12: 49160 ECDHE-ECDSA-DES-CBC3-SHA 168 TLS1 Native DES SHA ECDHE_ECDSA 13: 22 DHE-RSA-DES-CBC3-SHA 168 TLS1 Native DES SHA EDH/RSA 14: 27 ADH-DES-CBC3-SHA 168 TLS1 Native DES SHA ADH 15: 49165 ECDH-RSA-DES-CBC3-SHA 168 TLS1 Native DES SHA ECDH_RSA 16: 49155 ECDH-ECDSA-DES-CBC3-SHA 168 TLS1 Native DES SHA ECDH_ECDSA 17: 10 DES-CBC3-SHA 168 TLS1 Native DES SHA RSA
I haven't finished adding all of the REST endpoints available yet, but any missing ones can be worked around using the bash command. For example, if you want to get the current status of the routing table, you can pass netstat -rn via the bash command like this:
>>> rtstat = b.tm.util.bash.exec_cmd('run', utilCmdArgs='-c "netstat -rn"') >>> print rtstat.commandResult Kernel IP routing table Destination Gateway Genmask Flags MSS Window irtt Iface 127.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 tmm 192.168.102.0 0.0.0.0 255.255.255.0 U 0 0 0 vlan102 192.168.103.0 0.0.0.0 255.255.255.0 U 0 0 0 vlan103 10.0.2.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 10.10.10.0 0.0.0.0 255.255.255.0 U 0 0 0 vlan10 127.7.0.0 127.1.1.253 255.255.0.0 UG 0 0 0 tmm 127.20.0.0 0.0.0.0 255.255.0.0 U 0 0 0 tmm_bp 0.0.0.0 10.10.10.1 0.0.0.0 UG 0 0 0 vlan10
Note the extra quotes within the utilCmdArgs string around netstat -rn. These are necessary to pass the argument to the command you want bash to run, rather than to bash itself.
Join me next time where we will explore transactions!