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.

Avi vRO plugin workflows

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.

Basic Auth disabled

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

Create Server pool workflow

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

Workflow input

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

Create Virtual IP Object workflow

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

Workflow inputs

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

Create Virtual Service workflow

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

Workflow inputs

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

Create VS form

The workflow succeeds

Workflow completed

And we can verify the objects created in Avi

Virtual service with VIP

Virtual service created

Pool with our grafana server

Pool created

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

Add workflow to source

Remember to share it with the correct projects

I've added a logo to our item

Item logo

And adjusted the form labels and field order

Custom form

Catalog items

With that we can verify our catalog item

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.

Create request

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

Workflow completed

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

Avi resources

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!

This page was modified on January 16, 2022: Fixed failed commit-branch