From 61276fc1d04cad76a70be75c6ab4af4780c2505e Mon Sep 17 00:00:00 2001 From: nikfot Date: Fri, 5 May 2023 14:47:07 +0300 Subject: [PATCH] Add disk stats for datastore Add total capacity and free space for datastore. This way more criteria can be added to datastore selection Add list of datastores with free space and total capacity. Enhance documentation for datastore data source. Add documentation for datastore stats data source. Signed-off-by: nikfot --- vsphere/data_source_vsphere_datastore.go | 10 +++ .../data_source_vsphere_datastore_stats.go | 71 +++++++++++++++ ...ata_source_vsphere_datastore_stats_test.go | 86 +++++++++++++++++++ vsphere/data_source_vsphere_datastore_test.go | 42 +++++++++ vsphere/provider.go | 1 + website/docs/d/datastore.html.markdown | 14 +-- website/docs/d/datastore_stats.html .markdown | 80 +++++++++++++++++ 7 files changed, 299 insertions(+), 5 deletions(-) create mode 100644 vsphere/data_source_vsphere_datastore_stats.go create mode 100644 vsphere/data_source_vsphere_datastore_stats_test.go create mode 100644 website/docs/d/datastore_stats.html .markdown diff --git a/vsphere/data_source_vsphere_datastore.go b/vsphere/data_source_vsphere_datastore.go index 173276ccd..b425d0451 100644 --- a/vsphere/data_source_vsphere_datastore.go +++ b/vsphere/data_source_vsphere_datastore.go @@ -26,6 +26,11 @@ func dataSourceVSphereDatastore() *schema.Resource { Description: "The managed object ID of the datacenter the datastore is in. This is not required when using ESXi directly, or if there is only one datacenter in your infrastructure.", Optional: true, }, + "stats": { + Type: schema.TypeMap, + Description: "The usage stats of the datastore, include total capacity and free space in bytes.", + Optional: true, + }, }, } } @@ -48,5 +53,10 @@ func dataSourceVSphereDatastoreRead(d *schema.ResourceData, meta interface{}) er } d.SetId(ds.Reference().Value) + props, err := datastore.Properties(ds) + if err != nil { + return fmt.Errorf("error getting properties for datastore ID %q: %s", ds.Reference().Value, err) + } + d.Set("stats", map[string]string{"capacity": fmt.Sprintf("%v", props.Summary.Capacity), "free": fmt.Sprintf("%v", props.Summary.FreeSpace)}) return nil } diff --git a/vsphere/data_source_vsphere_datastore_stats.go b/vsphere/data_source_vsphere_datastore_stats.go new file mode 100644 index 000000000..fc153a8d3 --- /dev/null +++ b/vsphere/data_source_vsphere_datastore_stats.go @@ -0,0 +1,71 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vsphere + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-vsphere/vsphere/internal/helper/datastore" + "github.com/vmware/govmomi/object" +) + +func dataSourceVSphereDatastoreStats() *schema.Resource { + return &schema.Resource{ + Read: dataSourceVSphereDatastoreStatsRead, + + Schema: map[string]*schema.Schema{ + "datacenter_id": { + Type: schema.TypeString, + Description: "The managed object ID of the datacenter to get datastores from.", + Required: true, + }, + "free_space": { + Type: schema.TypeMap, + Description: "The free space of the datastores.", + Optional: true, + }, + "capacity": { + Type: schema.TypeMap, + Description: "The capacity of the datastores.", + Optional: true, + }, + }, + } +} + +func dataSourceVSphereDatastoreStatsRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Client).vimClient + var dc *object.Datacenter + if dcID, ok := d.GetOk("datacenter_id"); ok { + var err error + dc, err = datacenterFromID(client, dcID.(string)) + if err != nil { + return fmt.Errorf("cannot locate datacenter: %s", err) + } + } + dss, err := datastore.List(client) + if err != nil { + return fmt.Errorf("error listing datastores: %s", err) + } + for i := range dss { + ds, err := datastore.FromPath(client, dss[i].Name(), dc) + if err != nil { + return fmt.Errorf("error fetching datastore: %s", err) + } + props, err := datastore.Properties(ds) + if err != nil { + return fmt.Errorf("error getting properties for datastore ID %q: %s", ds.Reference().Value, err) + } + cap := d.Get("capacity").(map[string]interface{}) + cap[dss[i].Name()] = fmt.Sprintf("%v", props.Summary.Capacity) + d.Set("capacity", cap) + fr := d.Get("free_space").(map[string]interface{}) + fr[dss[i].Name()] = fmt.Sprintf("%v", props.Summary.FreeSpace) + d.Set("free_space", fr) + } + d.SetId(fmt.Sprintf("%s_stats", dc.Reference().Value)) + + return nil +} diff --git a/vsphere/data_source_vsphere_datastore_stats_test.go b/vsphere/data_source_vsphere_datastore_stats_test.go new file mode 100644 index 000000000..1214c1da3 --- /dev/null +++ b/vsphere/data_source_vsphere_datastore_stats_test.go @@ -0,0 +1,86 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vsphere + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceVSphereDatastoreStats_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + RunSweepers() + testAccPreCheck(t) + testAccDataSourceVSphereDatastoreStatsPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceVSphereDatastoreStatsConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.vsphere_datastore_stats.datastore_stats", "datacenter_id", os.Getenv("VSPHERE_DATACENTER"), + ), + resource.TestCheckResourceAttr( + "data.vsphere_datastore_stats.datastore_stats", "id", fmt.Sprintf("%s_stats", os.Getenv("VSPHERE_DATACENTER")), + ), + testCheckOutputBool("found_free_space", "true"), + testCheckOutputBool("found_capacity", "true"), + testCheckOutputBool("free_values_exist", "true"), + testCheckOutputBool("capacity_values_exist", "true"), + ), + }, + }, + }) +} + +func testAccDataSourceVSphereDatastoreStatsPreCheck(t *testing.T) { + if os.Getenv("VSPHERE_DATACENTER") == "" { + t.Skip("set TF_VAR_VSPHERE_DATACENTER to run vsphere_datastore_stats acceptance tests") + } + if os.Getenv("VSPHERE_USER") == "" { + t.Skip("set TF_VAR_VSPHERE_DATACENTER to run vsphere_datastore_stats acceptance tests") + } + if os.Getenv("VSPHERE_PASSWORD") == "" { + t.Skip("set TF_VAR_VSPHERE_PASSWORD to run vsphere_datastore_stats acceptance tests") + } +} + +func testAccDataSourceVSphereDatastoreStatsConfig() string { + return fmt.Sprintf(` +variable "datacenter_id" { + default = "%s" +} + +data "vsphere_datastore_stats" "datastore_stats" { + datacenter_id = "${var.datacenter_id}" +} + +output "found_free_space" { + value = "${length(data.vsphere_datastore_stats.datastore_stats.free_space) >= 1 ? "true" : "false" }" +} + +output "found_capacity" { + value = "${length(data.vsphere_datastore_stats.datastore_stats.capacity) >= 1 ? "true" : "false" }" +} + +output "free_values_exist" { + value = alltrue([ + for free in values(data.vsphere_datastore_stats.datastore_stats.free_space): + free >= 1 + ]) +} + +output "capacity_values_exist" { + value = alltrue([ + for free in values(data.vsphere_datastore_stats.datastore_stats.capacity): + free >= 1 + ]) +} +`, os.Getenv("VSPHERE_DATACENTER")) +} diff --git a/vsphere/data_source_vsphere_datastore_test.go b/vsphere/data_source_vsphere_datastore_test.go index 95642ba08..8481f711f 100644 --- a/vsphere/data_source_vsphere_datastore_test.go +++ b/vsphere/data_source_vsphere_datastore_test.go @@ -57,6 +57,28 @@ func TestAccDataSourceVSphereDatastore_noDatacenterAndAbsolutePath(t *testing.T) }) } +func TestAccDataSourceVSphereDatastore_getStats(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + RunSweepers() + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceVSphereDatastoreConfigGetStats(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.vsphere_datastore.datastore_data", "name", os.Getenv("VSPHERE_DATASTORE_NAME"), + ), + + testCheckOutputBool("found_stats", "true"), + ), + }, + }, + }) +} + func testAccDataSourceVSphereDatastorePreCheck(t *testing.T) { if os.Getenv("TF_VAR_VSPHERE_NFS_DS_NAME") == "" { t.Skip("set TF_VAR_VSPHERE_NFS_DS_NAME to run vsphere_nas_datastore acceptance tests") @@ -90,3 +112,23 @@ data "vsphere_datastore" "datastore_data" { testhelper.CombineConfigs(testhelper.ConfigDataRootDC1(), testhelper.ConfigDataRootHost1(), testhelper.ConfigDataRootHost2(), testhelper.ConfigResDS1(), testhelper.ConfigDataRootComputeCluster1(), testhelper.ConfigResResourcePool1(), testhelper.ConfigDataRootPortGroup1()), ) } + +func testAccDataSourceVSphereDatastoreConfigGetStats() string { + return fmt.Sprintf(` +variable "datastore_name" { + default = "%s" +} +variable "datacenter_id" { + default = "%s" +} +data "vsphere_datastore" "datastore_data" { + name = "${var.datastore_name}" + datacenter_id = "${var.datacenter_id}" +} + +output "found_stats" { + value = "${length(data.vsphere_datastore.datastore_data.stats) >= 1 ? "true" : "false" }" +} +`, os.Getenv("VSPHERE_DATASTORE_NAME"), os.Getenv("VSPHERE_DATACENTER"), + ) +} diff --git a/vsphere/provider.go b/vsphere/provider.go index d731c0676..90240ece3 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -152,6 +152,7 @@ func Provider() *schema.Provider { "vsphere_datacenter": dataSourceVSphereDatacenter(), "vsphere_datastore": dataSourceVSphereDatastore(), "vsphere_datastore_cluster": dataSourceVSphereDatastoreCluster(), + "vsphere_datastore_stats": dataSourceVSphereDatastoreStats(), "vsphere_distributed_virtual_switch": dataSourceVSphereDistributedVirtualSwitch(), "vsphere_dynamic": dataSourceVSphereDynamic(), "vsphere_folder": dataSourceVSphereFolder(), diff --git a/website/docs/d/datastore.html.markdown b/website/docs/d/datastore.html.markdown index 37f336dd0..cd016bd21 100644 --- a/website/docs/d/datastore.html.markdown +++ b/website/docs/d/datastore.html.markdown @@ -7,7 +7,7 @@ description: |- Provides a data source to return the ID of a vSphere datastore object. --- -# vsphere\_datastore +# vsphere_datastore The `vsphere_datastore` data source can be used to discover the ID of a vSphere datastore object. This can then be used with resources or data sources @@ -33,8 +33,8 @@ data "vsphere_datastore" "datastore" { The following arguments are supported: -* `name` - (Required) The name of the datastore. This can be a name or path. -* `datacenter_id` - (Optional) The [managed object reference ID][docs-about-morefs] +- `name` - (Required) The name of the datastore. This can be a name or path. +- `datacenter_id` - (Optional) The [managed object reference ID][docs-about-morefs] of the datacenter the datastore is located in. This can be omitted if the search path used in `name` is an absolute path. For default datacenters, use the `id` attribute from an empty `vsphere_datacenter` data source. @@ -43,5 +43,9 @@ The following arguments are supported: ## Attribute Reference -The only exported attribute from this data source is `id`, which represents the -ID of the datastore. +The following attributes are exported: + +- `id` - The ID of the datastore. +- `stats` - The disk space usage statistics for the specific datastore. The total + datastore capacity is represented as `capacity` and the free remaining disk is + represented as `free`. diff --git a/website/docs/d/datastore_stats.html .markdown b/website/docs/d/datastore_stats.html .markdown new file mode 100644 index 000000000..d3b72f826 --- /dev/null +++ b/website/docs/d/datastore_stats.html .markdown @@ -0,0 +1,80 @@ +--- +subcategory: "Storage" +layout: "vsphere" +page_title: "VMware vSphere: vsphere_datastore_stats" +sidebar_current: "docs-vsphere-data-source-datastore-stats" +description: |- + Provides a data source to return the usage stats for all vSphere datastore objects + in a datacenter. +--- + +# vsphere_datastore_stats + +The `vsphere_datastore_stats` data source can be used to retrieve the usage stats +of all vSphere datastore objects in a datacenter. This can then be used as a +standalone datasource to get information required as input to other data sources. + +## Example Usage + +```hcl +data "vsphere_datacenter" "datacenter" { + name = "dc-01" +} + +data "vsphere_datastore_stats" "datastore_stats" { + datacenter_id = data.vsphere_datacenter.datacenter.id +} +``` + +A usefull example of this datasource would be to determine the +datastore with the most free space. For example, in addition to +the above: + +Create an `outputs.tf` like that: + +```hcl +output "max_free_space_name" { + value = local.max_free_space_name +} + +output "max_free_space" { + value = local.max_free_space +} +``` + +and a `locals.tf` like that: + +```hcl +locals { + free_space_values = { for k, v in data.vsphere_datastore_stats.datastore_stats.free_space : k => tonumber(v) } + filtered_values = { for k, v in local.free_space_values : k => tonumber(v) if v != null } + numeric_values = [for v in values(local.filtered_values) : tonumber(v)] + max_free_space = max(local.numeric_values...) + max_free_space_name = [for k, v in local.filtered_values : k if v == local.max_free_space][0] +} +``` + +This way you can get the storage object with the most +space available. + +## Argument Reference + +The following arguments are supported: + +- `datacenter_id` - (Required) The [managed object reference ID][docs-about-morefs] + of the datacenter the datastores are located in. For default datacenters, use + the `id` attribute from an empty `vsphere_datacenter` data source. + +[docs-about-morefs]: /docs/providers/vsphere/index.html#use-of-managed-object-references-by-the-vsphere-provider + +## Attribute Reference + +The following attributes are exported: + +- `datacenter_id` - The [managed object reference ID][docs-about-morefs] + of the datacenter the datastores are located in. +- `free_space` - A mapping of the free space for each datastore in the + datacenter, where the name of the datastore is used as key and the free + space as value. +- `capacity` - A mapping of the capacity for all datastore in the datacenter + , where the name of the datastore is used as key and the capacity as value.