vRA8 Datastore Custom Placement Logic
How to implement custom placement logic for vSphere datastores
Table of Contents
Introduction
Our customer has multiple datastores allocated for private cloud usage: any of them can be used. We applied tagging (storage:vra8) to mark available storage for provisioned VMs. vRA8 started to utilize the first datastore but the customer would like to balance the usage, not just filling the first datastore completely.
Tags
We can use constraint tags to control the placement logic. Static tags can limit the choices but will not balance the utilization. The idea is to produce the tags dynamically - based on our placement usage (largest free space available). Here is a sample tagging strategy with two available datastores:
vRA API
vRealize Automation REST API provides access to datastore inventory via the IaaS API. We can easily query available datastores (filtering the tagged ones):
1curl -s -k -H 'Content-Type: application/json' -H "Authorization: Bearer $access_token" \
2"https://vra.corp.local/iaas/api/fabric-vsphere-datastores?\$filter=tags.item.key+eq+'ds'" | jq .
The response contains the assigned tags and available free space:
1{
2 "content": [
3 {
4 "type": "VMFS",
5 "externalRegionId": "Datacenter:datacenter-2",
6 "freeSizeGB": "3175",
7 "cloudAccountIds": [
8 "7509914b-0aef-406a-9ca4-ada87398758b"
9 ],
10 "tags": [
11 {
12 "key": "ds",
13 "value": "ds2"
14 },
15 {
16 "key": "storage",
17 "value": "vra8"
18 }
19 ],
20 "externalId": "Datastore:datastore-1188",
21 "name": "cloud_test_02",
22 "id": "09d0e17b-c4e3-44ad-a40b-c3c714702cd5",
23 "createdAt": "2021-11-09",
24 "updatedAt": "2022-03-28",
25 "organizationId": "6427df80-6711-46dd-9dbc-1c6207edc3e8",
26 "orgId": "6427df80-6711-46dd-9dbc-1c6207edc3e8",
27 "_links": {
28 "cloud-accounts": {
29 "hrefs": [
30 "/iaas/api/cloud-accounts/7509914b-0aef-406a-9ca4-ada87398758b"
31 ]
32 },
33 "self": {
34 "href": "/iaas/api/fabric-vsphere-datastores/09d0e17b-c4e3-44ad-a40b-c3c714702cd5"
35 },
36 "region": {
37 "href": "/iaas/api/regions/c35bb6ee-7c95-4dbd-aa4e-200999ad3518"
38 }
39 }
40 },
41 {
42 "type": "VMFS",
43 "externalRegionId": "Datacenter:datacenter-2",
44 "freeSizeGB": "3962",
45 "cloudAccountIds": [
46 "7509914b-0aef-406a-9ca4-ada87398758b"
47 ],
48 "tags": [
49 {
50 "key": "ds",
51 "value": "ds1"
52 },
53 {
54 "key": "storage",
55 "value": "vra8"
56 }
57 ],
58 "externalId": "Datastore:datastore-1187",
59 "name": "cloud_test_01",
60 "id": "18832413-89e7-4d1f-9aa2-abc1552ef341",
61 "createdAt": "2021-11-09",
62 "updatedAt": "2022-03-28",
63 "organizationId": "6427df80-6711-46dd-9dbc-1c6207edc3e8",
64 "orgId": "6427df80-6711-46dd-9dbc-1c6207edc3e8",
65 "_links": {
66 "cloud-accounts": {
67 "hrefs": [
68 "/iaas/api/cloud-accounts/7509914b-0aef-406a-9ca4-ada87398758b"
69 ]
70 },
71 "self": {
72 "href": "/iaas/api/fabric-vsphere-datastores/18832413-89e7-4d1f-9aa2-abc1552ef341"
73 },
74 "region": {
75 "href": "/iaas/api/regions/c35bb6ee-7c95-4dbd-aa4e-200999ad3518"
76 }
77 }
78 }
79 ],
80 "totalElements": 2,
81 "numberOfElements": 2
82}
vRO action to select DS with largest free space
For vRA REST API we use the Orchestrator Plug-in for vRealize Automation.
If the user does not have Cloud Assembly access (average users will not have) then won't be able to read IaaS API, so we create a service user for that purpose with Read permissions:
and create a vRA endpoint within vRO:
Now we are ready to make our action, with a string input tagKey
1var vraHost = VraHostManager.findHostsByType("vra-onprem").filter(
2 function (host) {
3 return host.name == 'apiuser'
4 }
5)[0];
6
7var filter = "?$filter=tags.item.key+eq+%27" + tagKey + "%27";
8System.log("Datastore filter: " + filter);
9
10var restClient = vraHost.createRestClient();
11var request = restClient.createRequest("GET", "/iaas/api/fabric-vsphere-datastores" + filter, "{}");
12var response = restClient.execute(request);
13System.log("HTTP response code: " + response.statusCode);
14System.debug(JSON.stringify(JSON.parse(response.contentAsString), null, 2));
15
16var datastores = JSON.parse(response.contentAsString).content.sort(
17 function (a, b) {
18 return parseInt(b.freeSizeGB, 10) - parseInt(a.freeSizeGB, 10);
19 }
20).map(
21 function (ds) {
22 return {name: ds.name, freeSizeGB: ds.freeSizeGB, tags: ds.tags}
23 }
24);
25System.log(JSON.stringify(datastores, null, 2));
26var tag = datastores[0].tags.filter(
27 function (t) {
28 return t.key == tagKey;
29 }
30)[0];
31System.log(JSON.stringify(tag));
32
33return tag.value;
Via the apiuser endpoint we query the IaaS API, filtering out the datastores tagged with tagKey. Then we sort the result by free space available and get the tag value of the first (largest) datastore.
The following logs are produced with the input "ds"
12022-03-28 13:42:28.565 +02:00 INFO (com.test.storage/getStorageTag) Datastore filter: ?$filter=tags.item.key+eq+%27ds%27
22022-03-28 13:42:29.423 +02:00 INFO (com.test.storage/getStorageTag) HTTP response code: 200
32022-03-28 13:42:29.425 +02:00 INFO (com.test.storage/getStorageTag) [
4 {
5 "name": "cloud_test_01",
6 "freeSizeGB": "3962",
7 "tags": [
8 {
9 "key": "ds",
10 "value": "ds1"
11 },
12 {
13 "key": "storage",
14 "value": "vra8"
15 }
16 ]
17 },
18 {
19 "name": "cloud_test_02",
20 "freeSizeGB": "3175",
21 "tags": [
22 {
23 "key": "ds",
24 "value": "ds2"
25 },
26 {
27 "key": "storage",
28 "value": "vra8"
29 }
30 ]
31 }
32]
332022-03-28 13:42:29.426 +02:00INFO(com.test.storage/getStorageTag) {"key":"ds","value":"ds1"}
The last line shows the chosen tag (key/value pair).
vRA Cloud Template
Let's create our cloud template to use the action:
1formatVersion: 1
2inputs:
3 cpu:
4 type: integer
5 default: 1
6 title: vCPU(s)
7 memory:
8 type: integer
9 title: memory (GB)
10 default: 2
11 datastore:
12 type: string
13 $dynamicDefault: /data/vro-actions/com.test.storage/getStorageTag?tagKey=ds
14 readOnly: true
15 title: datastore tag
16resources:
17 linuxvm:
18 type: Cloud.vSphere.Machine
19 properties:
20 image: rhel8
21 cpuCount: '${input.cpu}'
22 totalMemoryMB: '${input.memory * 1024}'
23 storage:
24 constraints:
25 - tag: 'ds:${input.datastore}'
26 networks:
27 - network: '${resource.Cloud_vSphere_Network_1.id}'
28 assignment: static
29 Cloud_vSphere_Network_1:
30 type: Cloud.vSphere.Network
31 properties:
32 networkType: existing
33 constraints:
34 - tag: 'env:dev'
The highlighted rows show how to get the tag value and add as a storage constraint to make vRA choose our desired datastore:
Let the user choose!
If we want a dropdown to allow the user to select, we need to modify the action to return Properties with key-value pairs:
1var dropdown = new Properties();
2var vraHost = VraHostManager.findHostsByType("vra-onprem").filter(
3 function (host) {
4 return host.name == 'apiuser'
5 }
6)[0];
7
8var filter = "?$filter=tags.item.key+eq+%27" + tagKey + "%27";
9System.log("Datastore filter: " + filter);
10
11var restClient = vraHost.createRestClient();
12var request = restClient.createRequest("GET", "/iaas/api/fabric-vsphere-datastores" + filter, "{}");
13var response = restClient.execute(request);
14System.log("HTTP response code: " + response.statusCode);
15System.debug(JSON.stringify(JSON.parse(response.contentAsString), null, 2));
16
17var datastores = JSON.parse(response.contentAsString).content.sort(
18 function (a, b) {
19 return parseInt(b.freeSizeGB, 10) - parseInt(a.freeSizeGB, 10);
20 }
21).map(
22 function (ds) {
23 return { name: ds.name, freeSizeGB: ds.freeSizeGB, tags: ds.tags }
24 }
25);
26System.log(JSON.stringify(datastores, null, 2));
27
28datastores.forEach(
29 function (ds) {
30 var tag = ds.tags.filter(
31 function (t) {
32 return t.key == tagKey;
33 }
34 )[0];
35 dropdown.put(tag.value, ds.name + " (" + ds.freeSizeGB + " GB)")
36 }
37)
38
39return dropdown;
The resulting template interface:
The End.
You can implement other selection logic as well, based on the largest free space available method above.
Download the com.test.storage package containing the actions from GitHub: https://github.com/kuklis/vro8-packages