vRA8 Datastore Custom Placement Logic

Share on:

How to implement custom placement logic for vSphere datastores

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