Create vSphere Namespaces with Aria Automation - part two

In this previous post we saw how the out-of-the-box functionality for creating Supervisor Namespaces in vSphere with Tanzu (TKGs) worked in vRealize Aria Automation in it's simplest way. In short, we didn't do more than create the namespace with the chosen name. In an upcoming post we will take a look at a few more configuration options for things like limits, VM classes and so forth.

There are however a few things that can't be done through this integration, permissions being one.

This post will make use of the vSphere API and vRealize Aria Orchestrator to provide a way for users to request Supervisor namespaces with a few configurable settings.

Note that the environment used in this blog post is a lab environment. Please take appropriate steps to test this out before applying it in a production environment

The purpose of this post is to show a few ways of extending the built-in functionality and a use-case of the Supervisor API possibilities in vCenter. It is by no means meant to be a full fledged self-service solution, and I would also like to point out that a few of the Actions and Workflows in use could be simplified and/or refactored to be a bit more effective. For instance you will se that the API calls will not reuse the API session id, but instead create and delete for almost each API call. I chose to do it like this to simplify the setup.

With that said, let's get to it.

The workflow

The basis for this customization is a vRO workflow

vRO workflow

The workflow accepts a few input parameters, like the name of the namespace, the Supervisor cluster to use, a Storage Policy, VM classes and resource limits.

vRO Input form

Two of the inputs make use of vRO Actions to pull in some info from the vCenter API. These are the Supervisor cluster and Storage policy dropdowns.

vRO Input form dropdown

Again I would like to point out that the purpose here is to show how we could integrate vRA and vRO with 3rd party solutions rather than building the perfect and fully-fledged namespace integration

The important part of the workflow is the Scriptable task that generates the JSON body needed for the API from the input parameters

 1//https://developer.vmware.com/apis/vsphere-automation/v7.0U3/vcenter/api/vcenter/namespaces/instances/post/
 2var method = "POST";
 3var path = "/vcenter/namespaces/instances";
 4
 5var clusterId = System.getModule("net.rhmlab.library.vc.rest").getIdForSupervisorClusterByName(cluster);
 6var storagePolicyId = System.getModule("net.rhmlab.library.vc.rest").getIdForStoragePolicyByName(storagePolicy);
 7
 8var body = {};
 9body.cluster = clusterId;
10body.namespace = name;
11if(owner.length > 1){
12    body.access_list = [{
13        "domain": "vsphere.local",
14        "role": "OWNER",
15        "subject": owner,
16        "subject_type": "USER"
17    }]
18}
19body.resource_spec = {};
20if(cpuLimit > 10){
21    body.resource_spec.cpu_limit = cpuLimit;
22}
23if(memoryLimit > 0){
24    body.resource_spec.memory_limit = memoryLimit;
25}
26body.storage_specs = [{
27    "limit": 0,
28    "policy": storagePolicyId
29}]
30body.vm_service_spec = {
31    "vm_classes": vmClasses
32}
33
34body = JSON.stringify(body)

And the actual API call which makes use of a vRO Action that takes care of requesting things from the vCenter API.

The Actions

There are a few actions in play here which we'll take a quick look at. But first, we have a function for retrieving variables from ConfigElements, the ConfigElementAccessor which is created as an action.

The config elements in play are the RESTHost object created for vCenter, a user account with a username and password that has access and permissions to work with namespaces in vCenter.

Config elements

ConfigElementAccessor

 1//ConfigElementAccessor()
 2function ConfigElementAccessor (configPath) {
 3    var separateIndex = configPath.lastIndexOf("/");
 4    var categoryPath = configPath.substring(0, separateIndex);
 5    var elementName = configPath.substring(separateIndex+1);
 6
 7    var category = Server.getConfigurationElementCategoryWithPath(categoryPath);
 8    if (category === null){
 9        throw new Error("Category does not exist");
10    }
11
12    var configElements = typeof category.configurationElements === "undefined" ? [] : category.configurationElements;
13    var configElement;
14    for (var i = 0; i < configElements.length; i++){
15        if(configElements[i].name === elementName){
16            configElement = configElements[i];
17        }
18    }
19
20    if(!configElement){
21        throw new Error("Configelement not found");
22    }
23
24    this.get = function (key) {
25        var result = configElement.getAttributeWithKey(key);
26        if(!result){
27            throw new Error("No attributes found");
28        }
29        return result.value;
30    }
31}
32
33return ConfigElementAccessor;

Create session

For creating a session with the API, equivalent to logging in, we have a specific action, createSession, which makes use of the ConfigElementAccessor to fetch the resthost and user credentials for requesting the session id from the /api/session endpoint to use for subsequent calls to the vCenter API.

 1//createSession()
 2//Get the REST Host and credentials from a config element
 3var ConfigElementAccessor = System.getModule("net.rhmlab.library.vro.utils").ConfigElementAccessor();
 4var configElement = new ConfigElementAccessor("RHMLab/vCenter");
 5var restHost = configElement.get("restHost");
 6var sessionUri = "/session";
 7var authStringPlain = configElement.get("user") + ":" + configElement.get("password");
 8var authString = System.getModule("net.rhmlab.library.vro.utils").base64Encode(authStringPlain);
 9
10var request = restHost.createRequest("POST", sessionUri, null);
11request.setHeader("Authorization", "Basic " + authString);
12var response = request.execute();
13System.log("statusCode: " + response.statusCode);
14System.log("content: " + response.contentAsString);
15//The response comes with quotes ("), need to remove them before returning
16return response.contentAsString.split('"')[1];

Base64 encoding

The session ID is requested with a username/password passed in an Authorization header. To create this we need to base64 encode the username and password which is done with an Action running with nodejs runtime

1//base64Encode(stringToEncode)
2exports.handler = (context, inputs, callback) => {
3    let b64Encoded = Buffer.from(inputs.stringToEncode).toString('base64');
4    //console.log(b64Encoded);
5    callback(undefined, b64Encoded);
6}

Delete session

There's also a delete session action which is very similar to the create one, but to keep it simple I've kept it as two different actions. This action takes the session Id to delete as an input

 1//deleteSession(sessionId)
 2var ConfigElementAccessor = System.getModule("net.rhmlab.library.vro.utils").ConfigElementAccessor();
 3var configElement = new ConfigElementAccessor("RHMLab/vCenter");
 4var restHost = configElement.get("restHost");
 5var sessionUri = "/session";
 6var authStringPlain = configElement.get("user") + ":" + configElement.get("password");
 7var authString = System.getModule("net.rhmlab.library.vro.utils").base64Encode(authStringPlain);
 8
 9var request = restHost.createRequest("DELETE", sessionUri, null);
10request.setHeader("Authorization", "Basic " + authString);
11request.setHeader("vmware-api-session-id", sessionId);
12var response = request.execute();
13System.log("statusCode: " + response.statusCode);
14System.log("content: " + response.contentAsString);

Run API Call

Besides the actions for creating and deleting the session all API calls are made with a specific action. Inputs are the session Id, the HTTP method, the API path and optionally the body (as a JSON encoded string).

The action returns a Properties object with the status code and the content from the API response

 1//runApiCall(sessionId, method, path, body)
 2var ConfigElementAccessor = System.getModule("net.rhmlab.library.vro.utils").ConfigElementAccessor();
 3var configElement = new ConfigElementAccessor("RHMLab/vCenter");
 4var restHost = configElement.get("restHost");
 5var request = restHost.createRequest(method.toUpperCase(), path);
 6request.setHeader("vmware-api-session-id", sessionId);
 7if(typeof body != "undefined" && body.length > 0){
 8    request.setHeader("Content-Type", "application/json");
 9    request.content = body;
10    System.debug("body: " + body);
11}
12System.debug(method + " " + request.fullUrl);
13var response = request.execute();
14System.log("statusCode: " + response.statusCode);
15System.debug("content: " + response.contentAsString);
16
17//Return a Properties object with the status code and content, could probably do a JSON parse on the content here to save that in workflows/actions that uses this action
18return {statusCode: response.statusCode, content: response.contentAsString};

Supervisor Cluster actions

To list the Supervisor enabled clusters in vCenter in the Input form we have an action that returns an array of cluster names with an API call to the /vcenter/namespace-management/clusters endpoint

 1//listSupervisorClusterNames()
 2var sessionId = System.getModule("net.rhmlab.library.vc.rest").createSession();
 3var path = "/vcenter/namespace-management/clusters"
 4var response = System.getModule("net.rhmlab.library.vc.rest").runApiCall(sessionId,"GET",path);
 5
 6System.getModule("net.rhmlab.library.vc.rest").deleteSession(sessionId);
 7System.log(JSON.parse(response.content));
 8
 9return JSON.parse(response.content).map(function (c) {
10    return c.cluster_name;
11})

And we have an action for matching the name of a Supervisor cluster with the ID we need when creating a Namespace

 1//getIdForSupervisorClusterByName(name)
 2var sessionId = System.getModule("net.rhmlab.library.vc.rest").createSession();
 3
 4var path = "/vcenter/namespace-management/clusters";
 5
 6var response = System.getModule("net.rhmlab.library.vc.rest").runApiCall(sessionId, "GET", path);
 7//System.debug(JSON.parse(response));
 8
 9System.getModule("net.rhmlab.library.vc.rest").deleteSession(sessionId);
10
11var id = null;
12var clusters = JSON.parse(response.content);
13clusters.forEach(function (c) {
14    System.debug(c.cluster_name);
15    if (c.cluster_name == name){
16        id = c.cluster;
17    }
18})
19
20return id;

Storage policy actions

As with the Supervisor cluster actions we have similar actions for Storage Policies

 1//listStoragePolicyNames()
 2var sessionId = System.getModule("net.rhmlab.library.vc.rest").createSession();
 3var path = "/vcenter/storage/policies"
 4var response = System.getModule("net.rhmlab.library.vc.rest").runApiCall(sessionId,"GET",path);
 5
 6System.getModule("net.rhmlab.library.vc.rest").deleteSession(sessionId);
 7System.log(JSON.parse(response.content));
 8
 9return JSON.parse(response.content).map(function (p) {
10    return p.name;
11})

And again an action for matching names and ID

 1//getIdForStoragePolicyByName(name)
 2var sessionId = System.getModule("net.rhmlab.library.vc.rest").createSession();
 3
 4var path = "/vcenter/storage/policies"
 5var response = System.getModule("net.rhmlab.library.vc.rest").runApiCall(sessionId,"GET",path);
 6//System.debug(JSON.parse(response));
 7
 8System.getModule("net.rhmlab.library.vc.rest").deleteSession(sessionId);
 9
10var id = null;
11var policies = JSON.parse(response.content);
12policies.forEach(function (p) {
13    System.debug(p.name);
14    if (p.name == name){
15        id = p.policy;
16    }
17})
18
19return id;

Service Broker

Lets tie this up in vRA and Service Broker to let users request namespaces.

Note that these workflows will from vRA be a kind of "fire and forget". There's no Custom Resource behind that we can work with for e.g. Day 2 actions etc. This is something to look for when considering future improvements, it should be solvable with e.g. Dynamic Types in vRO.

The workflows are part of the vRO workflow content source

vRO Workflow content source

And if content definitions are in place they should be available for Projects and users with access

Catalog items

The form has no customizations besides what is done in vRO

Service Broker form

Again, note that there are no Custom Resource that gets created in vRA so the Deployment includes only a "workflow" element

Service Broker deployment

vSphere namespace created

Namespace created in vCenter

Delete workflow

For deleting we have a workflow quite similar to the create one, but where the scriptable task is just a way to build the correct API url for sending the DELETE request

1//https://developer.vmware.com/apis/vsphere-automation/v7.0U3/vcenter/api/vcenter/namespaces/instances/post/
2var method = "DELETE";
3var path = "/vcenter/namespaces/instances/" + namespace;

Delete workflow

There's two inputs to the workflow, the Supervisor cluster and the name of the namespace to be deleted.

Delete input form

The Input form is using an action for listing the namespaces based on the Supervisor cluster chosen.

 1//listSupervisorNamespaces()
 2var sessionId = System.getModule("net.rhmlab.library.vc.rest").createSession();
 3var path = "/vcenter/namespaces/instances"
 4var response = System.getModule("net.rhmlab.library.vc.rest").runApiCall(sessionId,"GET",path);
 5
 6System.getModule("net.rhmlab.library.vc.rest").deleteSession(sessionId);
 7System.log(JSON.parse(response.content));
 8
 9return JSON.parse(response.content).map(function (n) {
10    return n.namespace;
11})

Summary

This purpose of this post has been to show a use case for utilizing the vCenter Supervisor APIs to extend the (rather limited) built-in functionality for working with vSphere namespaces in Aria Automation.

Again, there's plenty of improvements to be done with my examples, but that is a task for later.

The vRO code can be found on Github

Thanks for reading and please reach out if you have any questions or comments

This page was modified on February 16, 2023: Updated with note on upcoming post