vRA Developer: Part 3 – Performing CRUD Operations on IaaS Entity Objects


In a previous post vRA Developer: Part 1 – IaaS & Understanding The Entity Framework I detailed what the Entity Framework is and how objects and their properties could be discovered using an application called LINQPad.

This post is going to detail how we can interact with the IaaS servers and work with the objects within the Entity Framework. I have also written a number of actions that provide a standard interface to use when working with the entities and perform CRUD (Create, Read, Update, Delete) operations on them. 

Note: these actions are designed to be used with wrappers for your code (i.e. other actions performing a task, which call these actions) and not directly in your workflows (although there is no real harm if you want to, it’s just cleaner not to).

All code that I have provided or talked about in this post can be downloaded as a vRO package for your consumption here.

The vCACEntityManager

There is a scripting class in vRO that comes with the vRA IaaS plugin called ‘vCACEntityManager‘. This class provides a number of functions that can be used to perform CRUD operations.

There are quite a few functions here and it can be confusing when deciding which is the best to use. Therefore, I have created a set of actions that use this scripting class that simplifies the process and removes many of the required parameters (most are discovered from the host or entity).

Create Entity

I have created an action called ‘createvCACEntity‘ that uses the ‘createModelEntity‘ function from the ‘vCACEntityManager‘ scripting class. The only use case that you are likely to come across for creating new entities is if you are creating new custom properties for a virtual machine as anything else is unlikely, given the complexity.

/*global System Properties vcacHost vCACEntityManager entitySetName entityProperties entityLinks*/

/**
 * Creates a new vCAC Entity with the provided property values and optional entity links.
 * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk>
 * @version 1.2.0
 * @function createvCACEntity
 * @param {vCAC:VCACHost} vcacHost - The vCAC Host.
 * @param {string} entitySetName - The Entity Set Name.
 * @param {Properties} entityProperties - The Entity properties as a collection of key/value pairs.
 * @param {Properties} [entityLinks] - Links to other entities.
 * @returns {vCAC:Entity} Returns the vCAC Entity object after it has been created.
 */

function checkParams(vcacHost, entitySetName, entityProperties) {
    var inputErrors = [];
    var errorMessage;
    if (!vcacHost || System.getObjectType(vcacHost) !== "vCAC:VCACHost") {
        inputErrors.push(" - vcacHost missing or not of type 'vCAC:VCACHost'");
    }
    if (!entitySetName || typeof entitySetName !== "string") {
        inputErrors.push(" - entitySetName missing or not of type 'string'");
    }
    if (!entityProperties || !(entityProperties instanceof Properties)) {
        inputErrors.push(" - entityProperties missing or not of type 'Properties'");
    }
    if (inputErrors.length > 0) {
        errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n");
        log.error(errorMessage);
    }
}

var logType = "Action";
var logName = "createvCACEntity"; // This must be set to the name of the action
var Logger = System.getModule("com.simplygeek.library.util").logger(logType, logName);
var log = new Logger(logType, logName);
var hostId;
var modelName = "ManagementModelEntities.svc";
var headers = null;
var newEntity;
var entityLinkProps = new Properties();

try {
    checkParams(vcacHost, entitySetName, entityProperties);
    log.debug("Creating vCAC entity...");
    if (entityLinks) {
        entityLinkProps = entityLinks;
    }
    hostId = vcacHost.id;
    newEntity = vCACEntityManager.createModelEntity(hostId, modelName, entitySetName,
                                                    entityProperties, entityLinkProps, headers);
    log.debug("Successfully created vCAC entity.");
} catch (e) {
    log.error("Action failed trying to create vCAC Entity.",e);
}

return newEntity;

If the new entity is created successfully, the new object will be returned.

Read (get) Entity or Entities

I have created three different sets of actions that can be used to get existing entities.

  • getVcacEntitiyByUniqieId – Use this action to return a single entity when the entity ID is known.
  • getVcacEntitiesbyCustomFilter – Use this action to return an array of entities using a properties object with the key/value pairs to filter the query on. A good use case is when trying to locate a virtual machine by its name.
  • getVcacEntitiesBySystemQuery – Use this action to return an array of entities using a string query. This is useful when the query is more complex and/or requires querying related entities.

getVcacEntitiyByUniqieId

/*global System Properties vCACEntityManager vcacHost entitySetName entityProperties*/

/**
 * Searches for an entity by its unique ID that is provided in the entityProperties object.
 * Uses a Properties object for the entity id because different entities do not share the same key for the id field.
 * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk>
 * @version 1.3.0
 * @function getVcacEntitiyByUniqieId
 * @param {vCAC:VCACHost} vcacHost - The vCAC Host.
 * @param {string} entitySetName - The Entity Set Name.
 * @param {Properties} entityProperties - Properties object containing the entity id.
 * @returns {vCAC:Entity} The vCAC Entity that is found.
 */

function checkParams(vcacHost, entitySetName, entityProperties) {
    var inputErrors = [];
    var errorMessage;
    if (!vcacHost || System.getObjectType(vcacHost) !== "vCAC:VCACHost") {
        inputErrors.push(" - vcacHost missing or not of type 'vCAC:VCACHost'");
    }
    if (!entitySetName || typeof entitySetName !== "string") {
        inputErrors.push(" - entitySetName missing or not of type 'string'");
    }
    if (!entityProperties || !(entityProperties instanceof Properties)) {
        inputErrors.push(" - entityProperties missing or not of type 'Properties'");
    }
    if (inputErrors.length > 0) {
        errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n");
        log.error(errorMessage);
    }
}

var logType = "Action";
var logName = "getVcacEntitiyByUniqieId";
var Logger = System.getModule("com.simplygeek.library.util").logger(logType, logName);
var log = new Logger(logType, logName);
var modelName = "ManagementModelEntities.svc";
var headers = null;
var vcacEntity;

try {
    checkParams(vcacHost, entitySetName, entityProperties);
    log.debug("Retrieving vCAC Entity for set: " + entitySetName);
    vcacEntity = vCACEntityManager.readModelEntity(vcacHost.id, modelName, entitySetName,
                                                   entityProperties, headers);
    if (!vcacEntity) {
        log.error("No vCAC Entity was found.");
    } else {
        log.debug("Successfully retrieved vCAC entity.");
    }
} catch (e) {
    log.error("Action failed to retrieve vCAC Entity.",e);
}

return vcacEntity;

The input ‘entityProperties‘ has to be prepared when calling this action. For example, if trying to locate a vCAC virtual machine with the ID ‘e5167032-d781-11e8-9f8b-f2801f1b9fd1’ you would need to create a properties input such as ‘properties.put(“VirtualMachineID”, “e5167032-d781-11e8-9f8b-f2801f1b9fd1”)‘. This would then locate the vCAC entity for this virtual machine ID.

getVcacEntitiesbyCustomFilter

/*global System Properties vCACEntityManager vcacHost entitySetName*/

/**
 * Returns an array of vcac entities by using a property set of key/values to filter the search on.
 * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk>
 * @version 1.1.0
 * @function getVcacEntitiesByCustomFilter
 * @param {vCAC:VCACHost} vcacHost - The vCAC Host.
 * @param {string} entitySetName - The Entity Set Name.
 * @param {Properties} [entityProperties] - Properties to filter the query on. Returnd all entities if no properties provided.
 * @returns {vCAC:Entity[]} Returns an array of vCAC entities.
 */

function checkParams(vcacHost, entitySetName) {
    var inputErrors = [];
    var errorMessage;
    if (!vcacHost || System.getObjectType(vcacHost) !== "vCAC:VCACHost") {
        inputErrors.push(" - vcacHost missing or not of type 'vCAC:VCACHost'");
    }
    if (!entitySetName || typeof entitySetName !== "string") {
        inputErrors.push(" - entitySetName missing or not of type 'string'");
    }
    if (inputErrors.length > 0) {
        errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n");
        log.error(errorMessage);
    }
}

var logType = "Action";
var logName = "getVcacEntitiesByCustomFilter";
var Logger = System.getModule("com.simplygeek.library.util").logger(logType, logName);
var log = new Logger(logType, logName);
var modelName = "ManagementModelEntities.svc";
var headers = null;
var entities = [];
var numEntitiesFound = 0;

try {
    checkParams(vcacHost, entitySetName);
    log.log("Retrieving vCAC Entities for set: " + entitySetName);
    if (!entityProperties) {
        log.debug("No properties were provided, all entities will be returned.");
        var entityProperties = new Properties();
    }
    log.debug("Querying vCAC Host for entities in set '" + entitySetName + "'");
    entities = vCACEntityManager.readModelEntitiesByCustomFilter(vcacHost.id,
                                                                 modelName,
                                                                 entitySetName,
                                                                 entityProperties,
                                                                 headers);
    numEntitiesFound = entities.length;
    log.debug("Found: " + numEntitiesFound + " entities.");
    if (numEntitiesFound < 1) {
        log.error("Unable to locate vCAC entities using the properties provided.");
    }
} catch (e) {
    log.error("Action failed to retrieve vCAC Entities.",e);
}

return entities;

The input ‘entityProperties‘ has to be prepared when calling this action. For example, if trying to locate a vCAC virtual machine with the name ‘vmName’ you would need to create a properties input such as ‘properties.put(“VirtualMachineName”, “vmName”)‘.

getVcacEntitiesBySystemQuery

/*global System vCACEntityManager vcacHost entitySetName query*/

/**
 * Returns an array of vcac entities by using an OData system query to filter the search on.
 * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk>
 * @version 1.1.0
 * @function getVcacEntitiesBySystemQuery
 * @param {vCAC:VCACHost} vcacHost - The vCAC Host.
 * @param {string} entitySetName - The Entity Set Name.
 * @param {string} query - Query string.
 * @returns {vCAC:Entity[]} Returns an array of vCAC entities.
 */

function checkParams(vcacHost, entitySetName, query) {
    var inputErrors = [];
    var errorMessage;
    if (!vcacHost || System.getObjectType(vcacHost) !== "vCAC:VCACHost") {
        inputErrors.push(" - vcacHost missing or not of type 'vCAC:VCACHost'");
    }
    if (!entitySetName || typeof entitySetName !== "string") {
        inputErrors.push(" - entitySetName missing or not of type 'string'");
    }
    if (!query || typeof query !== "string") {
        inputErrors.push(" - query missing or not of type 'string'");
    }
    if (inputErrors.length > 0) {
        errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n");
        log.error(errorMessage);
    }
}

var logType = "Action";
var logName = "getVcacEntitiesBySystemQuery";
var Logger = System.getModule("com.simplygeek.library.util").logger(logType, logName);
var log = new Logger(logType, logName);
var modelName = "ManagementModelEntities.svc";
var entities = [];
var numEntitiesFound = 0;

try {
    checkParams(vcacHost, entitySetName, query);
    log.debug("Retrieving vCAC Entities for set: " + entitySetName);
    entities = vCACEntityManager.readModelEntitiesBySystemQuery(vcacHost.id, modelName,
                                                                entitySetName, query);
    numEntitiesFound = entities.length;
    log.debug("Found: " + numEntitiesFound + " entities.");
    if (numEntitiesFound < 1) {
        log.error("Unable to locate vCAC entities using the provided query.");
    }
} catch (e) {
    log.error("Action failed to retrieve vCAC Entities.",e);
}

return entities;

An example of what a query would look like to locate an inventory data collector with the uuid ‘e5167032-d781-11e8-9f8b-f2801f1b9fd1’ is “FilterSpec/FilterSpecGroup/FilterSpecGroupName eq ‘inventory’ and EntityID eq guid’e5167032-d781-11e8-9f8b-f2801f1b9fd1”.

Update Entity

I have created an action called ‘updatevCACEntity‘ that uses the ‘updateModelEntityBySerializedKey‘ function from the ‘vCACEntityManager‘ scripting class. This action is useful for updating custom property values, virtual machine and reservation properties (and all other entities but these are the most common use cases).

/*global System Properties vCACEntityManager vcacEntity entityProperties*/

/**
 * Updates an existing vCAC Entity with new property values including optional updates to linked entities.
 * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk>
 * @version 1.1.0
 * @function updatevCACEntity
 * @param {vCAC:Entity} vcacEntity - vCAC Entity.
 * @param {Properties} entityProperties - Properties to update.
 * @param {Properties} [entityLinks] - Links to update [optional].
 * @returns {vCAC:Entity} Returns the updated vCAC Entity object.
 */

function checkParams(vcacEntity, entityProperties) {
    var inputErrors = [];
    var errorMessage;
    if (!vcacEntity || System.getObjectType(vcacEntity) !== "vCAC:Entity") {
        inputErrors.push(" - vcacEntity missing or not of type 'vCAC:Entity'");
    }
    if (!entityProperties || !(entityProperties instanceof Properties)) {
        inputErrors.push(" - entityProperties missing or not of type 'Properties'");
    }
    if (inputErrors.length > 0) {
        errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n");
        log.error(errorMessage);
    }
}

var logType = "Action";
var logName = "updatevCACEntity"; // This must be set to the name of the action
var Logger = System.getModule("com.simplygeek.library.util").logger(logType, logName);
var log = new Logger(logType, logName);
var hostId;
var modelName;
var entitySetName;
var entityId;
var headers = null;
var updatedEntity;

try {
    checkParams(vcacEntity, entityProperties);
    hostId = vcacEntity.hostId;
    modelName = vcacEntity.modelName;
    entitySetName = vcacEntity.entitySetName;
    entityId = vcacEntity.keyString;
    log.debug("Updating vCAC entity...");
    if (!entityLinks) {
        var entityLinks = new Properties();
    }
    updatedEntity = vCACEntityManager.updateModelEntityBySerializedKey(hostId, modelName, entitySetName,
                                                                       entityId, entityProperties,
                                                                       entityLinks, headers);
    log.debug("Updating vCAC entity complete.");
} catch (e) {
    log.error("Action failed to update vCAC entity.",e);
}

return updatedEntity;

This will return the updated entity object, which you can verify in your wrapper actions if the update has had the expected result.

Delete Entity

I have created an action called ‘deletevCACEntity‘ that uses the ‘deleteModelEntityBySerializedKey‘ function from the ‘vCACEntityManager‘ scripting class. This action is useful for deleting custom properties, virtual machines and reservations. (and all other entities but these are the most common use cases).

/*global System vcacEntity vCACEntityManager*/

/**
 * Deletes the provided vCAC Entity.
 * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk>
 * @version 1.1.0
 * @function deletevCACEntity
 * @param {vCAC:Entity} vcacEntity - vCAC Entity.
 */

function checkParams(vcacEntity) {
    var inputErrors = [];
    var errorMessage;
    if (!vcacEntity || System.getObjectType(vcacEntity) !== "vCAC:Entity") {
        inputErrors.push(" - vcacEntity missing or not of type 'vCAC:Entity'");
    }
    if (inputErrors.length > 0) {
        errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n");
        log.error(errorMessage);
    }
}

var logType = "Action";
var logName = "deletevCACEntity"; // This must be set to the name of the action
var Logger = System.getModule("com.simplygeek.library.util").logger(logType, logName);
var log = new Logger(logType, logName);
var hostId;
var modelName;
var entitySetName;
var entityId;
var parameters = null;
var links = null;
var headers = null;

try {
    checkParams(vcacEntity);
    hostId = vcacEntity.hostId;
    modelName = vcacEntity.modelName;
    entitySetName = vcacEntity.entitySetName;
    entityId = vcacEntity.keyString;
    log.log("Deleting vCAC entity with id: " + entityId);
    vCACEntityManager.deleteModelEntityBySerializedKey(hostId, modelName, entitySetName, entityId,
                                                       parameters, links, headers);
    log.log("Successfully deleted vCAC entity with id: " + entityId);
} catch (e) {
    log.error("Action failed to delete vCAC entity.",e);
}

This has been a big post and provides code in the format that I like to use. Using interfaces like this will make your code a lot cleaner and much easier to maintain. Please feel free to contact me using Drift if you’d like any specialist help.

5 1 vote
Article Rating

Related Posts

Subscribe
Notify of
guest

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
xian
xian
5 years ago

Hello Gavin, thanks for these excellent posts! I’m looking for a way to re-assign VCAC VMs from one Deployment to another, but I can’t find the correct class function doing that. I hoped to find the relation in the Entity Framework but no luck so far. I also tried to use vCACCAFECatalogResource.setParentResourceRef() but did not work either. This was my first idea as I believed this relationship is managed by the Cafe component. The usecase is to bring existing, related VMs under vRA management, but bulk import allows simple blueprints only while we’d like to use multiple VMs in the… Read more »

xian
xian
5 years ago
Reply to  Gavin Stephens

There is a similar attempt at https://communities.vmware.com/thread/592041 and one of replies writes the problem is that a local variable is modified instead of the actual DB data. I’m not sure how to make a workaround of that. I tried to make a simpler task working: using setName function. I checked the result in the same script with System.log(resource.name). According to the logs the name was changed but nothing committed back to the DB: if I re-load the resource I get the old name back. I skimmed through the API but this does not seems to be exposed (no surprise). CloudClient… Read more »

xian
xian
4 years ago

vCACCAFECompositionDeploymentResourceService.register*() functions seems to create a new deployment, but I was not able to figure out the request payload. I experimented with them and got forbidden “POST /composition-service/api/deploymentresources/dev?upgrade=true&dryRun=true HTTP/1.1” 403 325 [tomcat-http–43] in vcac access log. Propably restricted to vRA solution user.

/composition-service/api/deploymentresources/ seems to be an internal API, used by bulk import as well: https://communities.vmware.com/thread/574106

Vladimir
Vladimir
2 years ago

Many thanks!
It would be nice to have samples of usage Your functions.