Month: February 2020

A Virtual Machine by any other name would smell as sweet

If you ever want to start an interoffice hunger games struggle, suggest changing your corporate hostname standard. If you do succeed to move forward on a new naming standard, getting there by committee makes Brexit look easy. I started researching this topic expecting to show the limited capability of the vRA built-in custom naming and then diving directly into vRO event broker workflows to meet those pesky hostname requirements. I was surprised how far I could get using only native vRA constructs to meet a naming standard and saving the vRO implementation for another day.

Here is the naming standard I am using which lets me identify several meta data items from just the name:

< Environment >< OS Type > – < Project Code > – < Server usage ><###  Sequence>

  • Environment – first initial of environments ( devl/test/staging/prod)
  • OS Type – first initial of windows/linux
  • Project Code – unique 3 digit value added as custom property on each project
  • Server usage  – (a/d/w/k) for application, database, web, or container server
  • <### Sequence > – 3 digit number

For example – a development photon server running containers for project Tango which has a project code of CAS:

dl-cas-k001

First look at the Custom Naming on the projects shows it is fairly limited. Name can be configured to use various properties from the project, resource, endpoint and generated sequence (it says random but it is an increasing number sequence).

Custom Naming
Custom Naming Project
Custom Naming Resource
Custom Naming Endpoint

The biggest constraint with the custom naming template is user input during provisioning cannot be directly configured on the tamplate and it is not practical for the example naming standard which includes environments, os and server types.

This is where I originally planned to abandon built-in and go custom vRO, but then I started looking blueprint functions capabilities to manipulate the resource name. I set custom name template simply to:

${resource.name}${###}

On the blueprint I added inputs allowing the user to select the for deployment choices and which are also required for the naming standard.

Request Form

I then leveraged the cloud assembly blueprint functions to build the resource name.  The property value combines inputs, project properties, and functions to build the resource name to meet the standard. Here is the expression set for the name property on the resource to meet the naming standard (full blueprint is provided at end of post for reference):

    properties:
      name: '${input.environment}${substring(input.image,0,1) == "w" ? "w" : "l"}-${to_lower(resource.network.projectCode)}-${input.servertype}'

I did hit an interesting gotcha. The project custom property (projectCode) is automatically injected to all resources, but it appears to be an ordering issue if attempting to use “self.projectCode”.  I had to reference it from the network resource which is always configured before the VM resource.

As you can see from the deployments the servers are getting named according to standard based on the requests.

Project Lab:  Production, windows, database server

Project Pacific:  3 Staging, centos, container servers

Project Tango: 2 Development, centos, application servers


Deployments

The VM’s are also registered in DNS with the generated name:

ssh

At this point I still do not have any validation against DNS, AD, or CMDB to check if the names are unique and the objects do not exist. Hoping this validation becomes part of the custom name ability in vRA in future releases. I would have to default back to vRO/ABX today.

Would I use this for a large environment? No. I need the validation. vRO could be used to integrate with a hostname service or even provide a hostname service via XaaS for manual builds. This method would require modifying of all blueprints if a standards changed and is error prone depending on the blueprinter as well. But this proved to be an interesting experiment in the lab and forced me to learn the functions and expressions available on the blueprint.

Blueprint with inputs and functions:

formatVersion: 1
inputs:
  image:
    type: string
    title: Operating System
    description: The operating system version to use.
    enum:
      - centos
      - photon
      - windows 2016
      - windows 2019
    default: centos
  size:
    type: string
    title: Size
    description: How big do you need it.
    enum:
      - small
      - medium
      - large
    default: small
  environment:
    type: string
    title: Environment
    description: Target Application Environment
    oneOf:
      - title: Development
        const: d
      - title: Test
        const: t
      - title: Staging
        const: s
      - title: Production
        const: p
    default: d
  count:
    type: integer
    title: Count
    description: Number of VMs
    maximum: 8
    minimum: 1
    default: 1
  servertype:
    type: string
    title: Server Usage
    description: Server usage for VM
    oneOf:
      - title: Webserver
        const: w
      - title: Appserver
        const: a
      - title: Database
        const: d
      - title: Container
        const: k
resources:
  vm:
    type: Cloud.vSphere.Machine
    metadata:
      layoutPosition:
        - 0
        - 0
    properties:
      customizationSpec: '${substring(input.image,0,1) == "w" ? "windows" : "linux"}'
      image: '${input.image}'
      name: '${input.environment}${substring(input.image,0,1) == "w" ? "w" : "l"}-${to_lower(resource.network.projectCode)}-${input.servertype}'
      flavor: '${input.size}'
      count: '${input.count}'
      networks:
        - network: '${resource.network.id}'
      attachedDisks: []
  network:
    type: Cloud.vSphere.Network
    metadata:
      layoutPosition:
        - 1
        - 0
    properties:
      networkType: existing

 

Until next time…

Anytime you learn, you gain. -Bob Ross

Let’s Start to Fill Our Toolbox

One of the best features of vRA is the API first* approach to management, but I need some tools to get there. Postman is great for learning and prototyping, but I need solid vRO Actions to build functional workflows to integrate with the Event Subscriptions and to automate the configuration of vRA itself.

Much of this will be repetitive to experienced vRO users, but hopefully helpful to others who are just starting. In future blogs the actions covered here will be used in examples and I wanted readers to be able to have a reference. And as I warned the readers in the welcome, the ramblings will go where anywhere that I find interesting.

The toolbox and examples are all designed with the intent to use the vRealize Automation Cloud API available on VMware {code} or you can access the swagger API within the vRA deployed appliance.

https:// [vRA Appliance] /automation-ui/api-docs/

vra-api-docs

vRO has the built-in REST plugin which allows you to add REST Hosts, REST Operations, and generate operation workflows. Don’t use it! Ok, let me walk that back a bit. For certain use cases such as Puppet DB querying adding the trust keys to the vRO keystore and then setting up the DB rest host using the keys works well. Configuring the REST plugin with basic user/password authentication becomes a lot of maintenance later and plugin configurations cannot be migrated if you run multiple vRA environments. I really wish someone would have told me 5 years ago, but we live and learn.

Bypassing the REST plugin I use 3 base actions as my starting point for REST API integration. These work well for me and I take no credit for writing them. I used VMware {code} and vCommunity posts to find code examples to cobble these together. The driving design is to keep them simple, easy to use, and then build additional specialized actions extending the functionality of the core functionality which can be an slippery slope of action overload. Package of all actions shown in this post is available here.

Actions:

  • ar.util.rest.importCert
  • ar.util.rest.createTransientRestHost
  • ar.util.rest.request

importCert: does exactly what the name implies.  The code was borrowed from the vRO Library workflow “Import a Certificate from URL” and simplified down to this action. Since running in my isolated lab environment I don’t check  most standard certificate validation and will only throw an exception if the certificate is expired.

var ld = Config.getKeystores().getImportCAFromUrlAction();
var model = ld.getModel();

ld.setCertificateAlias("");

var model = ld.getModel();
model.value = url;

var certValidation = ld.validateCertificates();
var certInfo = ld.getCertInfo();

System.debug(certInfo);

if ( certValidation.isCertificateExpired() == true )   throw "Certificate is expired. \n " + certinfo;
	
var error = ld.execute();
if (error != null) throw error;

createTransientRestHost: This action allows me to bypass the REST Plugin. As the name suggests it creates a temporary and transient REST Host for the duration of the workflow and it is automatically destroyed. Input is the FQDN of the rest host and uses the importCert to add the certificate to vRO.

if (fqdn == null || fqdn == "" ) return null;

var url = "https://" + fqdn;

System.getModule("ar.util.rest").importCert(url);

var restHost = RESTHostManager.createHost("TransientRESTHost-"+fqdn);
var transientRestHost = RESTHostManager.createTransientHostFrom(restHost);
transientRestHost.url=url;

return transientRestHost;

request: This is the generic action for virtually any REST request and returns the REST Response object. I debated adding additional response error handling in the action, but opted leave out in this action. The responsibility for all error handling rests (pun intended) in the workflow/action using this low level action.  Several inputs have been configured to defaults used for the most common requests as well.

if (fqdn == null || fqdn == "" ) return null;
if (url == null || url == "" ) return null;
if (method == null || method == "" ) var method="GET";
if (contentType == null || contentType == "") var contentType="application/json";
if (content == null) content="";
if (headers == null) {
	var headers = new Properties();
	headers.put("Content-Type","application/json");
}

var restHost = System.getModule("ar.util.rest").createTransientRESTHost(fqdn);
var request = restHost.createRequest(method,url,content);
request.contentType=contentType;

for each (var header in headers.keys) {
	System.debug("Headers: "+header+":"+headers[header]);
	request.setHeader(header,headers[header]);
}

var response=request.execute();
System.debug("Response Code: "+  response.statusCode);
return response;

Now let’s use these actions to get the vRA Authentication Bearer Token used for subsequent API requests. This is the first of many specialized actions to extend the functionality of the core REST actions. I also wanted to make my vRA interactions extremely simple so I externalized the vRA url, userid, and password into configuration attributes.  The action to get the bearer token requires no inputs and the return properties object is the headers required for further vRA requests.

ar.vra.util.rest.getAccessTokenHeaders

var username=System.getModule("ar.util.helpers").getLabConfig("vra_userid");
var password=System.getModule("ar.util.helpers").getLabConfig("vra_password");
var fqdn=System.getModule("ar.util.helpers").getLabConfig("vra_fqdn");

var url="/csp/gateway/am/api/login?access_token";
var method="POST";
var content = {
	"username": username,
	"password": password
};

var response = System.getModule("ar.util.rest").request(fqdn,url,method,JSON.stringify(content),null,null);
var responseJSON=JSON.parse(response.contentAsString);
var headers = new Properties();
headers.put("Authorization","Bearer "+responseJSON.access_token);
headers.put("Content-Type","application/json");
return headers;

The next must have action is an action to make any REST API call to vRA using all the building blocks so far.

ar.vra.util.rest.genericRestAPI

if (url == null || url == "" ) return null;
if (method == null || method == "" ) var method="GET";
if (content == null ) var content="";

var headers=System.getModule("ar.vra.util.rest").getAccessTokenHeaders();
var fqdn=System.getModule("ar.util.helpers").getLabConfig("vra_fqdn");
return System.getModule("ar.util.rest").request(fqdn,url,method,content,headers,null);

At this point I have a single action one-liner in any scriptable task to interact with vRA.

var response=System.getModule("ar.vra.util.rest").genericRestAPI("/iaas/api/cloud-accounts","GET",null);

Time to jump on that slippery slope that I mentioned above for a ride. There are certain vRA API calls I use many times over. Building further specialized actions for specific API calls can be very useful, but it can also result in action overload which is where I tend to end up. A useful getDeployments action will retrieve one or all deployments for based on the one input of variable deploymentId.

ar.vra.util.rest.getDeployments

var  url = "/deployment/api/deployments";
if (deploymentId != null)  {
	 url=url+"/"+deploymentId;
} 
System.debug("getDeployments url: "+url);
return System.getModule("ar.vra.util.rest").genericRestAPI(url,null,null);

The toolbox will be gaining many more tools in the future, but now I have a starting point to get deeper into the Event Broker Service subscriptions and start to configure environments during provisioning – stay tuned. Package of all actions from this post is available here.

*Maybe should say “almost” to an API first approach to management since the current public API does not cover 100% of product configurations, but I hope this will be fixed in upcoming releases of vRA.

Anytime you learn, you gain.             -Bob Ross