Hello!
We are trying to improve our OS provision blueprint by retrieving and presenting some additional details for selected networks. This would be things like, grabbing a friendly name from our IPAM, get number of free IPs in the range, etc.
Right now we are using the getApplicableNetworks action to populate a drop down on our blueplrint. This works great but it displays a somewhat unfriendly name. This is not a deal breaker. But what I'm trying to figure our is how I can get an associated network profile for the selected network adapter?
In my mind, I'd like to use the network adapter as an input, lookup the business group association, then look at the network profile. Our network profiles only contain one IP range. Once I have the network profile, I can use this to retrieve details on the range and then return those to display in the blueprint. Most likely will be using custom forms to do some of this work.
Am I on the right track here? Should I be thinking about this different to achieve similar results?
I think you're going to have to copy and then modify this action to get the results you want. I don't really see any other good way than that. At least then you have a new, single Action bundled up which encapsulates all that logic to your forms without having it fragmented across lots of smaller pieces here and there.
I've made a bit of progress on this. But I hit a snag.
First off, I've managed to grab the network profile from the Network Adapter but doing the following:
var host = vCACCAFEHostManager.getDefaultHostForTenant(tenant , true);
var reservations = System.getModule("com.vmware.vra.reservations").getReservationsForUserAndComponent(user, tenant, host, blueprint, component, subtenantId);
var count = 0;
for each(var res in reservations) {
var extensionData = res.getExtensionData();
var keys = extensionData.keySet();
var networks = extensionData.get("reservationNetworks");
if(networks) {
for each(var network in networks.getValue()) {
var path = network.getValue().get("networkPath");
var profile = network.getValue().get("networkProfile");
System.log(path.label);
System.log(profile.label);
}
}
}
}
}
Now that I have the Network Adapter and associated network profile, I'd like to l pull information about the range in the profile. Specifically name and description.
I believe I found some sample code to do this from: Get vCAC Network Ranges - Samples - VMware {code}
//Name: getNetworkRanges
//Description: Used to get all network ranges from a given vCAC IaaS Host
//Inputs: vcacHost [vCAC:VCACHost]
//Return Type: string
//Initialize Array or JSON
var networkRanges = [];
var netRanges = vCACEntityManager.readModelEntitiesBySystemExpandQuery(vcacHost.id,"ManagementModelEntities.svc","StaticIPv4Ranges",null,'StaticIPv4NetworkProfile/ID',null,null,null,null,null);
for each (netRange in netRanges){
//Create JSON Object
var networkRange = {};
networkRange["name"] = netRange.getProperties().get("StaticIPv4RangeName");
networkRange["rangeDescription"] = netRange.getProperties().get("IPv4RangeDescription");
networkRange["networkProfileId"] = netRange.getLink(vcacHost, "StaticIPv4NetworkProfile")[0].getProperties().get("ID");; //Gets the id from a forien key in StaticIPv4NetworkProfile
networkRange["rangeId"] = netRange.getProperties().get("ID");
networkRange["rangeState"] = netRange.getProperties().get("StaticIPv4State");
networkRange["externalId"] = netRange.getProperties().get("ExternalId");
networkRanges.push(networkRange);
}
return JSON.stringify(networkRanges,null,4)
However this errors out on line 11 with:
[2019-04-23 09:20:36.343] [E] Error in (Workflow:getNetworkRanges / Scriptable task (item1)#8) java.lang.NullPointerException
[2019-04-23 09:20:36.353] [E] Workflow execution stack:
***
item: 'getNetworkRanges/item1', state: 'failed', business state: 'null', exception: 'java.lang.NullPointerException (Workflow:getNetworkRanges / Scriptable task (item1)#8)'
workflow: 'getNetworkRanges' (56d543d8-ae41-4f2f-8467-7259aa369840)
| 'attribute': name=vcacHost type=vCACCAFE:VCACHost value=dunes://service.dunes.ch/CustomSDKObject?id='0420c0c0-0fad-49ec-9666-4383cae8e70a'&dunesName='vCACCAFE:VCACHost'
| 'no inputs'
| 'no outputs'
*** End of execution stack.
I am passing my vCAC host into vcacHost. Everything else is the same as the sample code.
Am I overlooking something?
Also should note, we are running vRA 7.5.
What do you have in line #8 ?
Edited my initial response as I misunderstood your question.
Line 8 of the script I ran is line 11 posted above:
var netRanges = vCACEntityManager.readModelEntitiesBySystemExpandQuery(vcacHost.id,"ManagementModelEntities.svc","StaticIPv4Ranges",null,'StaticIPv4NetworkProfile/ID',null,null,null,null,null);
The problem is that you're expecting an input of the type vCACCAFE:VCACHost. This is the vRA server, but you need the IaaS server instead, type vCAC:VCACHost. The Model Entity is running on this Windows machine.
You need to add your IaaS server to the vRO inventory (if you have not done so) by running Library/vRealize Automation/Configuration/Add the IaaS host of a vRA host workflow. Once added, re-run your code with the correct input type and select the IaaS server as input.
See the excellent series on Entities by Gavin Stephens at https://www.simplygeek.co.uk/vra-developer-master-series/
Although deprecated, the iaas-proxy-provider api should be easier to use: Get a Network Profile List Example
Thank you!
I should have know, I mean its specified in the notes of the script. I changed the type and specified the correct input and it works as expected.
Thanks for the links, I'll check those out now.
I'm happy to report I was able to re-write getApplicableNetworks to display a more friendly strings on the dropdown. Since this forum has been so helpful I'm happy to post for others to get ideas from. This is not a truly portable script as there are likely references to environmental specific resources. I'll also attach it as the formatting will likely get munched a bit.
/* Todo: If more than one IaaS host is present, select the default. var vcacHosts = Server.findAllForType("vCAC:VCACHost");
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////Functions///////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
function getNetworkRangesFromReservation() {
/* This function grabs network ranges from reservations associated with the user who called the action. It then returns an array of network range
objects that includes details of those ranges.
Example returned data for one single range: (In some cases there be multiple network range objects in the array.)
[{
"name": "10.128.191.224/28",
"rangeDescription": "IS_VRA_TEST3",
"networkProfileId": "d5aa4841-3b86-49f3-9ddf-c6033db3a52c",
"rangeId": "bfb18c99-c2d8-45fd-8a94-aa29f27ff3c9",
"rangeState": 1,
"externalId": "10.128.191.224/28"
}]
*/
var networkRanges = [];
var countRanges = 0;
// Inputs: vcacHost [vCAC:VCACHost]
// readModelEntitiesBySystemExpandQuery(String hostId, String modelName, String entitySetName, String filter, String expand, String orderBy, String select, int top, int skip, Properties headers)
var netRanges = vCACEntityManager.readModelEntitiesBySystemExpandQuery(vcacHosts[0].id,"ManagementModelEntities.svc","StaticIPv4Ranges",null,'StaticIPv4NetworkProfile/ID',null,null,null,null,null);
for each (netRange in netRanges){
// Create Object and push to array
countRanges++;
var networkRange = {};
networkRange["name"] = netRange.getProperties().get("StaticIPv4RangeName");
networkRange["rangeDescription"] = netRange.getProperties().get("IPv4RangeDescription");
networkRange["networkProfileId"] = netRange.getLink(vcacHosts[0], "StaticIPv4NetworkProfile")[0].getProperties().get("ID");; //Gets the id from a forien key in StaticIPv4NetworkProfile
networkRange["rangeId"] = netRange.getProperties().get("ID");
networkRange["rangeState"] = netRange.getProperties().get("StaticIPv4State");
networkRange["externalId"] = netRange.getProperties().get("ExternalId");
networkRanges.push(networkRange);
}
System.debug("Number of network profiles found:" + countRanges);
System.debug("Returning raw network range data:");
System.debug(JSON.stringify(networkRanges, null,' '));
return networkRanges;
}
function getExternalIdAndDescription(name){
// getExternalIdAndDescription takes in a network profile name (string) and returns am object that include range external ID and range description as defined in Netdot.
// These values are pulled from Netdot when the range is initially configured. If the details of the range get updated in netdot, simply navigate to the range in vRA
// open it. It will make the call to Netdot automatically and update the record.
// Get the network ranges from the reservation
var networkRanges = getNetworkRangesFromReservation();
System.debug("Number of ranges found: " + networkRanges.length);
System.debug("Name parameter: " + name);
for(var i = 0 ; i < networkRanges.length ; i++ ) {
// loop though the returned ranges until a match is found, then return an object containing externalId and rangeDescription. These values coorelate to values found in Netdot.
if(networkRanges[i].name == name) {
System.debug(name + " = " + networkRanges[i].externalId);
System.debug(name + " = " + networkRanges[i].rangeDescription);
/* Example of returned data:
{
"externalId": "10.128.191.224/28",
"rangeDescription": "IS_VRA_TEST3"
}
*/
return {externalId:networkRanges[i].externalId,rangeDescription:networkRanges[i].rangeDescription};
}
}
// If we fall out of the loop without a successful match throw error
throw "externalId with name: " + name + " was not found!";
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////// Main /////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
System.log("Blueprint: " + blueprint);
System.log("ComponentID: " + component);
System.log("requestedFor: " + user);
System.log("tenantRef: " + tenant);
System.log("sybtenantRef: " + subtenantId);
// Find all IaaS hosts. Change this later to add logic to select only the DEFAULT host
var vcacHosts = Server.findAllForType("vCAC:VCACHost");
System.log(vcacHosts);
// Checks if the default host is already added and returns it.
var host = vCACCAFEHostManager.getDefaultHostForTenant(tenant , true);
// Get reservations for the user
var reservations = System.getModule("com.vmware.vra.reservations").getReservationsForUserAndComponent(user, tenant, host, blueprint, component, subtenantId);
var count = 0;
var applicableNetworks = new Properties();
for each(var res in reservations) {
count++;
var extensionData = res.getExtensionData();
var keys = extensionData.keySet();
var networks = extensionData.get("reservationNetworks");
if(networks) {
for each(var network in networks.getValue()) {
var path = network.getValue().get("networkPath");
var profile = network.getValue().get("networkProfile");
var rangeObj = getExternalIdAndDescription(profile.label);
System.debug(JSON.stringify(rangeObj, null, ' '));
applicableNetworks.put(path.label, rangeObj.externalId + " - " + rangeObj.rangeDescription);
System.log(path.label + " - " + rangeObj.externalId + " - " + rangeObj.rangeDescription);
}
}
System.debug("Number of Reservations: " + count);
}
return applicableNetworks;
The last remaining todo is to handle multiple IaaS hosts if returned from:
var vcacHosts = Server.findAllForType("vCAC:VCACHost")
Ideally I just want to select the default IaaS host and use it. In our environment we only have one IaaS host, but I don't want the script to break if another one gets added in the future.
Is there a way to simply select the default? or do I need to write logic to enumerate through vcacHosts and return the default?
I've used a configuration element to set the default host. I don't know of any way to programmatically get that. If you tag a default vCACCAFE:VCACHost it changes the name property to "Default" and the displayName property to "Default [whatever the original name was when you added it]" so its easy to identify that. I also use a configuration element though to hold that value as I've liked being able to easily coax my workflows to interact with the endpoints I want without needing to run the configuration workflows. Most of the time I never have more than one of each defined but in my lab and other test environments I do occasionally.
Paul
Thanks for mentioning this! I started researching configuration elements after your post. This helped me solve a few other issues I was having. I haven't quite got the default IaaS server bit fixed, but I have added it to my list of things to do.