Create Avi Virtual Service from vRealize Automation
In my lab environment I have an NSX Advanced Load Balancer (Avi) running and providing load balancing capabilities for a couple of services. Using the Avi UI isn't very complicated even though there are a lot of options that can be configured, but I wanted to have the ability to configure it through a catalog item in the vRealize Automation Service Broker so I wanted to see how I could accomplish just that.
We'll make use of the Avi API to integrate the two via vRealize Orchestrator.
Avi Plugin
Before we dive into the Avi Api I want to make a note of the existing Avi vRO plugin which can be downloaded from the VMware Marketplace
I have this installed in vRO, but I chose to do my integration to the Avi API directly both for getting some experience with the API, but also because I couldn't get the vRO plugin to work through vRA as I wanted in an easy way.
Now there are a lot of Workflows and actions available in the Avi vRO plugin, and even though the workflows require a lot of inputs to create stuff we could have used them as a starting point and built wrapper workflows for them so if you rathter want to go that route please give it a go. I might do that my self in the future.
Please note that the examples in this blog post will create a very basic Virtual Service. We've left out SSL, Load balancing algorithms, policies and all of that stuff which we'll normally configure. This post is more for showing the capabilities and getting experience with the Api
The Avi API
Now, let's check out the Avi API. The REST API documentation can be found here
First off as mentioned I already have Avi running in my lab. I'm only working with the Default cloud and have only one tenant in my environment and examples. If you'r setup is different you might need to adjust accordingly.
Authentication
Basic Auth is disabled by default in Avi, and I've tried to leave it like that as the API will return a cookie which can be used in subsequent calls.
In vRO we'll make use of a dynamic RESTHost and work with this cookie in our calls to the API.
Endpoints in use
In this example we'll use a few endpoints. Both the Pool, VsVip and Virtual Service will be used.
vRO
The process of creating a Virtual service will be split in three parts
- Create a Server pool
- Create a Virtual IP object (VIP)
- Create a Virtual Service which connects the pool and the VIP
So in vRO we're going to create a few workflows for creating the stuff we need.
Preferably we'd move some of the work shown below into Actions that can be reused (we'll see a perfect example of this later on), but I wanted to keep it like this for demo purposes. The only part I've moved to an action is the login part and how to retrieve the CSFR token.
Authenticating and token actions
The login action will pull the Avi details from a configuration element I've created for my lab Avi instance, and create a Transient Rest host which we'll return from the action. We can then use this Rest host in our workflows when we make our calls to the Api.
1var confElement;
2var token;
3
4var configElementCats = Server.getAllConfigurationElementCategories();
5var confElementCat = Server.getConfigurationElementCategoryWithPath("RHMLab");
6for each (var ce in confElementCat.allConfigurationElements){
7 System.debug(ce.name)
8 if (ce.name === "RHMLab_Avi")
9 confElement = ce;
10}
11if(confElement == undefined || confElement == null){
12 throw new Error("Couldn't find configuration element");
13}
14var user = confElement.getAttributeWithKey("user").value;
15var password = confElement.getAttributeWithKey("password").value;
16var controller = confElement.getAttributeWithKey("controller").value;
17
18var baseUrl = "https://" + controller;
19var restHost = RESTHostManager.createHost("DynamicRequest");
20var restHost = RESTHostManager.createTransientHostFrom(restHost);
21restHost.url = baseUrl;
22
23var payload = {
24 "username": user,
25 "password": password
26};
27
28var contentType = "application/json"
29var httpMethod = "POST"
30var request = restHost.createRequest(httpMethod, "/login", JSON.stringify(payload));
31request.contentType = contentType;
32
33var response;
34response = request.execute();
35System.debug("response: " + response.contentAsString);
36
37return restHost;
I've also created an action just to retrieve the token from the RESTHost as we'll have to pass this token when doing POST Api calls
1var cookies = restHost.getCookies();
2System.debug("cookies: " + cookies);
3for each(var c in cookies) {
4 //System.debug(c);
5 if(c.name == "csrftoken"){
6 token = c.value;
7 }
8}
9
10System.debug("token: " + token)
11return token;
And finally we have the logout action
1var token = System.getModule("local.rhmlab.avi.rest").getToken(restHost);
2var request = restHost.createRequest("POST", "/logout", null);
3request.setHeader("X-CSRFToken", token);
4request.setHeader("Referer", restHost.url);
5
6var response = request.execute();
7statusCode = response.statusCode;
8System.log("statusCode: " + statusCode);
A better way of handle the RESTHost and cookie stuff might be to create a sort of AviClient out of this which we could retrieve both the RESTHost and the token out of. This could also handle the login and logout actions
Create Server pool
The Create Server pool workflow will use the action for logging in, then it will retrieve the token from the RESTHost before it goes ahead and creates the Server pool. After that it will end the Api session by logging out
As inputs to this workflow I have the following
I've also added variables for lbAlgorithm
and aviVersion
which are set as hardcoded variables in the workflow.
The task for creating the server pool looks like this
1var arrServers = new Array();
2for (var i = 0; i<servers.length;i++){
3 System.debug(servers[i]);
4 var arrSrv = new Object();
5 var arrSrvIp = new Object();
6 arrSrvIp.addr = servers[i];
7 arrSrvIp.type = "V4";
8 arrSrv.port = port;
9 arrSrv.ip = arrSrvIp;
10 arrServers.push(arrSrv);
11 System.debug(JSON.stringify(arrSrv));
12}
13System.debug(arrServers);
14var payload = {
15 "description": "Created by vRO",
16 "name": name,
17 "lb_algorithm": lbAlgorithm,
18 "default_server_port": servicePort,
19 "servers": arrServers
20};
21
22var contentType = "application/json"
23var httpMethod = "POST"
24System.debug("payload: " + JSON.stringify(payload));
25
26var request = restHost.createRequest(httpMethod, "/api/pool", JSON.stringify(payload));
27request.contentType = contentType;
28request.setHeader("X-CSRFToken", token);
29request.setHeader("Referer", restHost.url);
30request.setHeader("X-Avi-Version", aviVersion);
31
32var response;
33response = request.execute();
34statusCode = response.statusCode;
35System.log("statusCode: " + statusCode);
36System.log("response: " + response.contentAsString);
37
38if(statusCode >= 300){
39 throw new Error("Error when creating pool object");
40}
41jsonContent = JSON.parse(response.contentAsString);
42System.debug("created pool: " + jsonContent.uuid);
43poolUuid = jsonContent.uuid;
Create Vip object
For the VIP object we're accepting either a pre-defined IP address, or if this is omitted we can have Avi auto-allocate one from a pool of IPs (needs to be pre-created in Avi).
In this workflow I've also specified a network to use for the IP address and for brevity I've hardcoded this network UUID as a variable to the workflow. In a real world example we'd probably pull a list of networks for the user to choose from.
The inputs to the workflow are the following
The task for creating the server pool looks like this
1var autoIp = false;
2var ipAddr = "";
3if (ip == "" || ip == undefined || ip == null){
4 autoIp = true;
5}
6else{
7 ipAddr = {
8 "addr": ip,
9 "type": "V4"
10 }
11}
12
13var payload = {
14 "name": name,
15 "vip": [
16 {
17 "auto_allocate_ip": autoIp,
18 "ip_address" : ipAddr,
19 "discovered_networks": [
20 {
21 "network_ref": restHost.url + "/api/network/" + network
22 }
23 ]
24 }
25 ],
26 "services": [
27 {
28 "enable_ssl": false,
29 "port": servicePort
30 }
31 ]
32};
33var contentType = "application/json"
34var httpMethod = "POST"
35
36System.debug("payload: " + JSON.stringify(payload));
37var request = restHost.createRequest(httpMethod, "/api/vsvip", JSON.stringify(payload));
38request.contentType = contentType;
39request.setHeader("X-CSRFToken", token);
40request.setHeader("Referer", restHost.url);
41request.setHeader("X-Avi-Version", aviVersion);
42
43var response = request.execute();
44statusCode = response.statusCode;
45System.log("statusCode: " + statusCode);
46System.log("content: " + response.contentAsString);
47
48if(statusCode >= 300){
49 throw "Error when creating vs object";
50}
51
52var jsonContent = JSON.parse(response.contentAsString);
53System.debug("Created vip with uuid: " + jsonContent.uuid);
54vipUuid = jsonContent.uuid;
Create Virtual Service
Finally we can create our virtual service. In this workflow we'll tie everything together
Here is my point of putting stuff in actions instead of workflows. With the current workflow we might end up with logging in to the Api three times because we log in and out in the different workflows.
This workflow will accept inputs for already created Pool and VIP objects, or the user can specify the details of these and have the workflow create them for us
The script for creating the Virtual service is as follows
1var baseUrl = restHost.url;
2var payload = {
3 "pool_ref": baseUrl + "/api/pool/" + poolExisting,
4 "vsvip_ref" : baseUrl + "/api/vsvip/" + vipExisting,
5 "name": name,
6 "services": [
7 {
8 "enable_ssl": false,
9 "port": servicePort
10 }
11 ]
12 };
13var contentType = "application/json"
14var httpMethod = "POST"
15
16System.debug("payload: " + JSON.stringify(payload));
17var request = restHost.createRequest(httpMethod, "/api/virtualservice", JSON.stringify(payload));
18request.contentType = contentType;
19request.setHeader("X-CSRFToken", token);
20request.setHeader("Referer", baseUrl);
21request.setHeader("X-Avi-Version", "20.1.2");
22
23var response = request.execute();
24statusCode = response.statusCode;
25System.log("statusCode: " + statusCode);
26System.log("response: " + response.contentAsString);
27if(statusCode >= 300){
28 throw new Error("Error when creating vs object");
29}
30
31var jsonContent = JSON.parse(response.contentAsString);
32System.log("Created virtual service with uuid: " + jsonContent);
33vsUuid = jsonContent.uuid;
Test vRO workflows
Now, let's try this in action!
As our example we'll work with a Grafana server that runs on IP 192.168.100.16 and port 3000. We want to create a virtual service for this and use port 80 as the service port
The workflow succeeds
And we can verify the objects created in Avi
Virtual service with VIP
Pool with our grafana server
vRA
Now, let's head over to Service Broker in vRA and try to create a Catalog item for this
Content sources
I already have a content source for vRO so I'll update this to include our newly created workflow (make sure that Data collection has been run) and click Save & Import
Remember to share it with the correct projects
I've added a logo to our item
And adjusted the form labels and field order
Catalog items
With that we can verify our catalog item
Test vRA
Let's test the workflow run via vRA. This time we'll try to use the existing Server pool, but we need to create a new VIP because this can't be shared between services.
As we can see from our workflow run, we've skipped the creation of a server pool, but we had to create a VIP object before running the Api call for creating the Virtual Service
And in Avi we can verify that we have both the vRA and vRO created Virtual Services sharing the same server pool, but with different VIPs
Note, to properly implement this in vRA we should make use of Dynamic Types to be able to perform Day 2 operations against the resources we create. As of now we're doing a "fire and forget" thing which won't let us perform actions against the resource from vRA.
Summary
So, in this post we've seen how we can integrate the NSX Advanced Load Balancer with vRA via vRO. Again the examples in this post are pretty simple, and needs to be adjusted to support stuff like Load balancing policies, security policies, SSL and so forth. Also as mentioned there's already a vRO plugin created by VMware/Avi which supports a lot of this.
We've also discussed that there are a couple of things that should be handled a bit differently to keep the code a bit cleaner and also not have to authenticate multiple times.
Thanks for reading and please reach out if you have any comments!