Building Application Delivery Services from Templates using the REST API (for People Like Me).
I’ve talked before about the value of Application Policies (AKA Service Templates, AKA iApp Templates). So has my esteemed colleague Nathan Pearce. We think they are pretty important, especially if you’re looking to deploy complex application delivery services using automation tools.
There are quite a lot of resources availabe to help you do this, including a great guide from Chris Mutzel, and some cool Python scripts and tools from the shy and retiring Hitesh Patel on Github. These are great, and if you are up to speed on iControl and REST then stop reading this and head to one of those places. They are clever over there.
However, to steal a line: “I’m not a smart man” (that’s probably why I’ve ended up as an explainer, rather than a true creator.) I needed some more basic information and examples before I moved on to these. I thought it might be worth documenting my journey, if nothing else for the amusement of others.
First REST – I read up on the BIG-IP REST API here. There is a lot of useful information there.
Now our service template implementation – iApp Templates and their resultant Application Services. For the purposes of this first blog we are going to be using about the most basic template – HTTP. This template simply creates a standard L7 VIP with a pool of servers at the backend. Jump on a BIG-IP and take a look – it’s pretty simple:
We are going to stick with straight HTTP, port 80 for now. This is just to understand the template and how to create a service using it via the REST API. To keep this as simple as possible we’ll be using the Chrome Advanced REST Client. You can use other tools like curl or the REST client of your choice, of course.
The first thing we are going to do is to create a new application service on the BIG-IP using the GUI. What? Yes, I know that seems backwards – but first we need to see what a deployed service looks like when we qurey it using REST. If anyone blagged their way through Visual Basic for Applications by mainly recording macro steps and then editing the resulting code, you know what I’m talking about.
So here we have the resulting service – note that the pool members are dummies - they don’t exist so the monitor will mark them as down.
OK, so what does this look like if you query for it using REST? Lets find out by executing a GET in the REST client with this URL:
https://<my-bigip>/mgmt/tm/sys/application/service/~Common~test1.app~test1?expandSubcollections=true
So what does all that mean? Well the first bit is obvious, even to me. “
https://<my-bigip>
” is the address of the BIG-IP, not much to ponder there. Now we have the path to the API section that gets us to application services “/mgmt/tm/sys/application/service/
” – there is a good post explaining how this works here. Full documentation for the URLs/modules are in the REST user guide. Now we get down to our deployed service “/~Common~test1.app~test1
”. This one is maybe a little harder to get. First you need to know that some of our resource names have a “/” in their name - to get around that we simply replace “/” with “~” in the URL. If you were to query all the application services on the BIG-IP using
https://<my-bigip>/mgmt/tm/sys/application/service/
If you are using the advanced REST client display this using the JSON tab to get a nice color coded output.
You would be able to see the key value pair
"fullPath": "/Common/test1.app/test1"
– that’s what we are representing with our “~Common~test1.app~test1
”
. I’m sure there is an excellent architectural reason for why this is the full path, and one day I’ll find the motivation to go and discover it. But today is not that day, today I want to make something work. Finally there is the query string
“?expandSubcollections=true”
This simply expands any objects that are part of the application service but also an object in their own right – known as subcollections. A great example is a pool member of a pool. The pool is an object with its own properties (such as load balancing algorithm) that also contains a collection of pool members that each have their own properties (e.g. address, port, connection limit). In this instance it actually makes no difference in this example, but it may well do so later so we will leave it in. Subcollections are nicely explained here.
Still with me? Great. Let’s take a look at the output of the GET:
"selfLink":"https://localhost/mgmt/tm/sys/application/service/~Common~test1.app~test1?expandSubcollections=true&ver=12.0.0", "deviceGroup":"none", "inheritedDevicegroup":"true", "inheritedTrafficGroup":"true","strictUpdates":"enabled", "template":"/Common/f5.http", "templateReference":{"link":"https://localhost/mgmt/tm/sys/application/template/~Common~f5.http?ver=12.0.0"}, "templateModified":"no", "trafficGroup":"/Common/traffic-group-1", "trafficGroupReference":{"link":"https://localhost/mgmt/tm/cm/traffic-group/~Common~traffic-group-1?ver=12.0.0"}, "tables":[{"name":"basic__snatpool_members"}, {"name":"net__snatpool_members"}, {"name":"optimizations__hosts"}, {"name":"pool__hosts","columnNames":["name"], "rows":[{"row":["test.test.com"]}]}, {"name":"pool__members", "columnNames":["addr","port","connection_limit"], "rows":[{"row":["10.0.0.10","80","0"]}]}, {"name":"server_pools__servers"}], "variables":[{"name":"client__http_compression", "encrypted":"no","value":"/#create_new#"}, {"name":"monitor__monitor","encrypted":"no","value":"/#create_new#"}, {"name":"monitor__response","encrypted":"no","value":"none"}, {"name":"monitor__uri","encrypted":"no","value":"/"}, {"name":"net__client_mode","encrypted":"no","value":"wan"}, {"name":"net__server_mode","encrypted":"no","value":"lan"}, {"name":"pool__addr","encrypted":"no","value":"10.0.1.28"}, {"name":"pool__pool_to_use","encrypted":"no","value":"/#create_new#"}, {"name":"pool__port","encrypted":"no","value":"80"}, {"name":"ssl__mode","encrypted":"no","value":"no_ssl"}, {"name":"ssl_encryption_questions__advanced","encrypted":"no","value":"no"}, {"name":"ssl_encryption_questions__help","encrypted":"no","value":"hide"} ] }{"kind":"tm:sys:application:service:servicestate", "name":"test1","partition":"Common", "subPath":"test1.app", "fullPath":"/Common/test1.app/test1", "generation":188,
OK, so now we just need to do a quick match up with the properties we created via the GUI to see what everything means.
{"kind":"tm:sys:application:service:servicestate", "name":"test1","partition":"Common", "subPath":"test1.app", "fullPath":"/Common/test1.app/test1", "generation":188, "selfLink":"https://localhost/mgmt/tm/sys/application/service/~Common~test1.app~test1?
"Test1" - That’s the the name of our application service. Easy. Now let’s ignore some other bits until we get to another section we recognize.
{"name":"pool__members", "columnNames":["addr","port","connection_limit"], "rows":[{"row":["10.0.0.10","80","0"]}
This is the line that defines the pool members – because there can be more than one we define a list of columns and then the rows afterwards. After that there are lines that instruct the iApp to create new monitors, set the TCP profiles, turn on or off compression etc. Now we get to another important line:
{"name":"pool__addr","encrypted":"no","value":"10.0.1.28"}
That’s the virtual server address, then the port is next.
{"name":"pool__port","encrypted":"no","value":"80"}
Why, you are asking, is this the ‘
pool_address
’ and not ‘VS_address
’? Honestly – I have no idea. I could find out, but that won’t get either of us anywhere and I have a stack of books to read and records to listen to.
So actually, there we have it, just a few key lines that define a simple iApp service. That’s probably enough to create a new one right? So let’s try doing a POST with this body:
{"kind":"tm:sys:application:service:servicestate", "name":"test2","partition":"Common", "subPath":"test2.app", "fullPath":"/Common/test2.app/test2", "generation":485, "selfLink":"https://localhost/mgmt/tm/sys/application/service/~Common~test2.app~test2?ver=12.0.0", "deviceGroup":"none", "inheritedDevicegroup":"true", "inheritedTrafficGroup":"false", "strictUpdates":"enabled", "template":"/Common/f5.http", "templateReference":{"link":"https://localhost/mgmt/tm/sys/application/template/~Common~f5.http?ver=12.0.0"}, "templateModified":"no","trafficGroup":"/Common/traffic-group-1", "trafficGroupReference":{"link":"https://localhost/mgmt/tm/cm/traffic-group/~Common~traffic-group-1?ver=12.0.0"}, "tables":[{"name":"basic__snatpool_members"}, {"name":"net__snatpool_members"}, {"name":"optimizations__hosts"}, {"name":"pool__hosts", "columnNames":["name"], "rows":[{"row":["test2.test.com"]}]}, {"name":"pool__members", "columnNames":["addr","port","connection_limit"], "rows":[{"row":["10.0.0.20","80","0"]}]}, {"name":"server_pools__servers"}], "variables":[{"name":"client__http_compression", "encrypted":"no","value":"/#create_new#"}, {"name":"monitor__monitor", "encrypted":"no","value":"/#create_new#"}, {"name":"monitor__response","encrypted":"no","value":"none"}, {"name":"monitor__uri","encrypted":"no","value":"/"}, {"name":"net__client_mode","encrypted":"no","value":"wan"}, {"name":"net__server_mode","encrypted":"no","value":"lan"}, {"name":"pool__addr","encrypted":"no","value":"10.0.0.30"}, {"name":"pool__pool_to_use","encrypted":"no","value":"/#create_new#"}, {"name":"pool__port","encrypted":"no","value":"80"}, {"name":"ssl__mode","encrypted":"no","value":"no_ssl"}, {"name":"ssl_encryption_questions__advanced","encrypted":"no","value":"no"}, {"name":"ssl_encryption_questions__help","encrypted":"no","value":"hide"} ]}
POST this, and your return should be similar to the GET we did before, but with your new variables. Check the GUI and see what we have:
Looks like we’ve just created a new service. Go us.
Let’s expand out the test2 application service:
There you go – we have created a new configuration from a template in a fairly easy manner. Now I know that the Advanced REST API client is not a scalable enterprise automation tool, but it exposes the heart of how this works and now you might be in a better position to automate with the tool of your choice.
Next I’m going to work out how to do an update to the config and redeploy it. Stay tuned.
(Note - I'm sorry about the typo riddled earlier version of this article. There were a combination of technological and meat-bag failures. As I say below - "I'm not a smart man". Thanks to those that pointed out my many errors, and no thanks to my blog writing app.)
- Maurice_G_EmployeeNice work!
- AABEIGH_220211Nimbostratushi, i am getting following error Can't associate (/pt1/myapp2.app/myapp2.app) with folder (/pt1/myapp2.app) folder does not exist",
- Rick_Goncalves_Nimbostratuswhat URL do you use on the POST??
- Robert_HaynesRet. EmployeeGood Question Rick: You need https:///mgmt/tm/sys/application/service