diff --git a/.changelog/11645.txt b/.changelog/11645.txt new file mode 100644 index 00000000000..c08f0ea16df --- /dev/null +++ b/.changelog/11645.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +netapp: added `allow_auto_tiering` field to StoragePool and `tiering_policy` field to Volume resource +``` \ No newline at end of file diff --git a/google/services/netapp/resource_netapp_storage_pool.go b/google/services/netapp/resource_netapp_storage_pool.go index f51cdffea4c..6e6997cb617 100644 --- a/google/services/netapp/resource_netapp_storage_pool.go +++ b/google/services/netapp/resource_netapp_storage_pool.go @@ -92,6 +92,13 @@ func ResourceNetappStoragePool() *schema.Resource { Optional: true, Description: `Specifies the Active Directory policy to be used. Format: 'projects/{{project}}/locations/{{location}}/activeDirectories/{{name}}'. The policy needs to be in the same location as the storage pool.`, + }, + "allow_auto_tiering": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: `Optional. True if the storage pool supports Auto Tiering enabled volumes. Default is false. +Auto-tiering can be enabled after storage pool creation but it can't be disabled once enabled.`, }, "description": { Type: schema.TypeString, @@ -211,6 +218,12 @@ func resourceNetappStoragePoolCreate(d *schema.ResourceData, meta interface{}) e } else if v, ok := d.GetOkExists("ldap_enabled"); !tpgresource.IsEmptyValue(reflect.ValueOf(ldapEnabledProp)) && (ok || !reflect.DeepEqual(v, ldapEnabledProp)) { obj["ldapEnabled"] = ldapEnabledProp } + allowAutoTieringProp, err := expandNetappStoragePoolAllowAutoTiering(d.Get("allow_auto_tiering"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("allow_auto_tiering"); !tpgresource.IsEmptyValue(reflect.ValueOf(allowAutoTieringProp)) && (ok || !reflect.DeepEqual(v, allowAutoTieringProp)) { + obj["allowAutoTiering"] = allowAutoTieringProp + } labelsProp, err := expandNetappStoragePoolEffectiveLabels(d.Get("effective_labels"), d, config) if err != nil { return err @@ -349,6 +362,9 @@ func resourceNetappStoragePoolRead(d *schema.ResourceData, meta interface{}) err if err := d.Set("encryption_type", flattenNetappStoragePoolEncryptionType(res["encryptionType"], d, config)); err != nil { return fmt.Errorf("Error reading StoragePool: %s", err) } + if err := d.Set("allow_auto_tiering", flattenNetappStoragePoolAllowAutoTiering(res["allowAutoTiering"], d, config)); err != nil { + return fmt.Errorf("Error reading StoragePool: %s", err) + } if err := d.Set("terraform_labels", flattenNetappStoragePoolTerraformLabels(res["labels"], d, config)); err != nil { return fmt.Errorf("Error reading StoragePool: %s", err) } @@ -676,6 +692,10 @@ func flattenNetappStoragePoolEncryptionType(v interface{}, d *schema.ResourceDat return v } +func flattenNetappStoragePoolAllowAutoTiering(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func flattenNetappStoragePoolTerraformLabels(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { if v == nil { return v @@ -723,6 +743,10 @@ func expandNetappStoragePoolLdapEnabled(v interface{}, d tpgresource.TerraformRe return v, nil } +func expandNetappStoragePoolAllowAutoTiering(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + func expandNetappStoragePoolEffectiveLabels(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]string, error) { if v == nil { return map[string]string{}, nil diff --git a/google/services/netapp/resource_netapp_storage_pool_test.go b/google/services/netapp/resource_netapp_storage_pool_test.go index 91432e76fbe..f6bfe1431f4 100644 --- a/google/services/netapp/resource_netapp_storage_pool_test.go +++ b/google/services/netapp/resource_netapp_storage_pool_test.go @@ -125,3 +125,71 @@ resource "google_netapp_storage_pool" "test_pool" { } `, context) } + +func TestAccNetappStoragePool_autoTieredStoragePoolCreateExample_update(t *testing.T) { + context := map[string]interface{}{ + "network_name": acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog")), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckNetappStoragePoolDestroyProducer(t), + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + Steps: []resource.TestStep{ + { + Config: testAccNetappStoragePool_autoTieredStoragePoolCreateExample_full(context), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccNetappStoragePool_autoTieredStoragePoolCreateExample_full(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_compute_network" "peering_network" { + name = "tf-test-network%{random_suffix}" +} + +# Create an IP address +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-address%{random_suffix}" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.peering_network.id +} + +# Create a private connection +resource "google_service_networking_connection" "default" { + network = google_compute_network.peering_network.id + service = "netapp.servicenetworking.goog" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} + +resource "google_netapp_storage_pool" "test_pool" { + name = "tf-test-pool%{random_suffix}" + location = "us-east4" + service_level = "PREMIUM" + capacity_gib = "2048" + network = google_compute_network.peering_network.id + active_directory = "" + description = "this is a test description" + kms_config = "" + labels = { + key= "test" + value= "pool" + } + ldap_enabled = false + allow_auto_tiering = true +} +`, context) +} diff --git a/google/services/netapp/resource_netapp_volume.go b/google/services/netapp/resource_netapp_volume.go index 900a9158909..90c30f30aa8 100644 --- a/google/services/netapp/resource_netapp_volume.go +++ b/google/services/netapp/resource_netapp_volume.go @@ -425,6 +425,29 @@ To disable automatic snapshot creation you have to remove the whole snapshot_pol }, }, }, + "tiering_policy": { + Type: schema.TypeList, + Optional: true, + Description: `Tiering policy for the volume.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cooling_threshold_days": { + Type: schema.TypeInt, + Optional: true, + Description: `Optional. Time in days to mark the volume's data block as cold and make it eligible for tiering, can be range from 7-183. +Default is 31.`, + }, + "tier_action": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidateEnum([]string{"ENABLED", "PAUSED", ""}), + Description: `Optional. Flag indicating if the volume has tiering policy enable/pause. Default is PAUSED. Default value: "PAUSED" Possible values: ["ENABLED", "PAUSED"]`, + Default: "PAUSED", + }, + }, + }, + }, "unix_permissions": { Type: schema.TypeString, Computed: true, @@ -436,6 +459,11 @@ To disable automatic snapshot creation you have to remove the whole snapshot_pol Computed: true, Description: `Reports the resource name of the Active Directory policy being used. Inherited from storage pool.`, }, + "cold_tier_size_gib": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. Size of the volume cold tier data in GiB.`, + }, "create_time": { Type: schema.TypeString, Computed: true, @@ -667,6 +695,12 @@ func resourceNetappVolumeCreate(d *schema.ResourceData, meta interface{}) error } else if v, ok := d.GetOkExists("multiple_endpoints"); !tpgresource.IsEmptyValue(reflect.ValueOf(multipleEndpointsProp)) && (ok || !reflect.DeepEqual(v, multipleEndpointsProp)) { obj["multipleEndpoints"] = multipleEndpointsProp } + tieringPolicyProp, err := expandNetappVolumeTieringPolicy(d.Get("tiering_policy"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("tiering_policy"); !tpgresource.IsEmptyValue(reflect.ValueOf(tieringPolicyProp)) && (ok || !reflect.DeepEqual(v, tieringPolicyProp)) { + obj["tieringPolicy"] = tieringPolicyProp + } labelsProp, err := expandNetappVolumeEffectiveLabels(d.Get("effective_labels"), d, config) if err != nil { return err @@ -868,6 +902,12 @@ func resourceNetappVolumeRead(d *schema.ResourceData, meta interface{}) error { if err := d.Set("multiple_endpoints", flattenNetappVolumeMultipleEndpoints(res["multipleEndpoints"], d, config)); err != nil { return fmt.Errorf("Error reading Volume: %s", err) } + if err := d.Set("cold_tier_size_gib", flattenNetappVolumeColdTierSizeGib(res["coldTierSizeGib"], d, config)); err != nil { + return fmt.Errorf("Error reading Volume: %s", err) + } + if err := d.Set("tiering_policy", flattenNetappVolumeTieringPolicy(res["tieringPolicy"], d, config)); err != nil { + return fmt.Errorf("Error reading Volume: %s", err) + } if err := d.Set("terraform_labels", flattenNetappVolumeTerraformLabels(res["labels"], d, config)); err != nil { return fmt.Errorf("Error reading Volume: %s", err) } @@ -966,6 +1006,12 @@ func resourceNetappVolumeUpdate(d *schema.ResourceData, meta interface{}) error } else if v, ok := d.GetOkExists("multiple_endpoints"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, multipleEndpointsProp)) { obj["multipleEndpoints"] = multipleEndpointsProp } + tieringPolicyProp, err := expandNetappVolumeTieringPolicy(d.Get("tiering_policy"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("tiering_policy"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, tieringPolicyProp)) { + obj["tieringPolicy"] = tieringPolicyProp + } labelsProp, err := expandNetappVolumeEffectiveLabels(d.Get("effective_labels"), d, config) if err != nil { return err @@ -1032,6 +1078,11 @@ func resourceNetappVolumeUpdate(d *schema.ResourceData, meta interface{}) error updateMask = append(updateMask, "multipleEndpoints") } + if d.HasChange("tiering_policy") { + updateMask = append(updateMask, "tiering_policy.cooling_threshold_days", + "tiering_policy.tier_action") + } + if d.HasChange("effective_labels") { updateMask = append(updateMask, "labels") } @@ -1719,6 +1770,46 @@ func flattenNetappVolumeMultipleEndpoints(v interface{}, d *schema.ResourceData, return v } +func flattenNetappVolumeColdTierSizeGib(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenNetappVolumeTieringPolicy(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["cooling_threshold_days"] = + flattenNetappVolumeTieringPolicyCoolingThresholdDays(original["coolingThresholdDays"], d, config) + transformed["tier_action"] = + flattenNetappVolumeTieringPolicyTierAction(original["tierAction"], d, config) + return []interface{}{transformed} +} +func flattenNetappVolumeTieringPolicyCoolingThresholdDays(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := tpgresource.StringToFixed64(strVal); err == nil { + return intVal + } + } + + // number values are represented as float64 + if floatVal, ok := v.(float64); ok { + intVal := int(floatVal) + return intVal + } + + return v // let terraform core handle it otherwise +} + +func flattenNetappVolumeTieringPolicyTierAction(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func flattenNetappVolumeTerraformLabels(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { if v == nil { return v @@ -2266,6 +2357,40 @@ func expandNetappVolumeMultipleEndpoints(v interface{}, d tpgresource.TerraformR return v, nil } +func expandNetappVolumeTieringPolicy(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedCoolingThresholdDays, err := expandNetappVolumeTieringPolicyCoolingThresholdDays(original["cooling_threshold_days"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCoolingThresholdDays); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["coolingThresholdDays"] = transformedCoolingThresholdDays + } + + transformedTierAction, err := expandNetappVolumeTieringPolicyTierAction(original["tier_action"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTierAction); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["tierAction"] = transformedTierAction + } + + return transformed, nil +} + +func expandNetappVolumeTieringPolicyCoolingThresholdDays(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandNetappVolumeTieringPolicyTierAction(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + func expandNetappVolumeEffectiveLabels(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]string, error) { if v == nil { return map[string]string{}, nil diff --git a/google/services/netapp/resource_netapp_volume_test.go b/google/services/netapp/resource_netapp_volume_test.go index c7926c29e83..ff423a8e25e 100644 --- a/google/services/netapp/resource_netapp_volume_test.go +++ b/google/services/netapp/resource_netapp_volume_test.go @@ -231,7 +231,6 @@ resource "google_netapp_storage_pool" "default" { capacity_gib = "2048" network = data.google_compute_network.default.id } - resource "google_netapp_storage_pool" "default2" { name = "tf-test-pool%{random_suffix}" location = "us-west2" @@ -661,3 +660,97 @@ func testAccNetappVolume_volumeBasicExample_cleanupScheduledBackup(t *testing.T, return nil } } + +func TestAccNetappVolume_autoTieredNetappVolume_update(t *testing.T) { + context := map[string]interface{}{ + "network_name": acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog")), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckNetappVolumeDestroyProducer(t), + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + Steps: []resource.TestStep{ + { + Config: testAccNetappVolume_autoTieredVolume_default(context), + }, + { + ResourceName: "google_netapp_volume.test_volume", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"restore_parameters", "location", "name", "deletion_policy", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappVolume_autoTieredVolume_custom(context), + }, + { + ResourceName: "google_netapp_volume.test_volume", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"restore_parameters", "location", "name", "deletion_policy", "labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccNetappVolume_autoTieredVolume_default(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "default" { + name = "tf-test-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = "2048" + network = data.google_compute_network.default.id + allow_auto_tiering = true +} +resource "google_netapp_volume" "test_volume" { + location = "us-west2" + name = "tf-test-volume%{random_suffix}" + capacity_gib = "100" + share_name = "tf-test-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.default.name + protocols = ["NFSV3"] + tiering_policy { + cooling_threshold_days = 31 + tier_action = "ENABLED" + } +} +data "google_compute_network" "default" { + name = "%{network_name}" +} +`, context) +} + +func testAccNetappVolume_autoTieredVolume_custom(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "default" { + name = "tf-test-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = "2048" + network = data.google_compute_network.default.id + allow_auto_tiering = true +} + +resource "google_netapp_volume" "test_volume" { + location = "us-west2" + name = "tf-test-volume%{random_suffix}" + capacity_gib = "100" + share_name = "tf-test-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.default.name + protocols = ["NFSV3"] + tiering_policy { + cooling_threshold_days = 20 + tier_action = "ENABLED" + } +} + +data "google_compute_network" "default" { + name = "%{network_name}" +} +`, context) +} diff --git a/website/docs/r/netapp_storage_pool.html.markdown b/website/docs/r/netapp_storage_pool.html.markdown index 851b7d9d1db..78504d816b5 100644 --- a/website/docs/r/netapp_storage_pool.html.markdown +++ b/website/docs/r/netapp_storage_pool.html.markdown @@ -171,6 +171,11 @@ The following arguments are supported: Specifies the replica zone for regional Flex pools. `zone` and `replica_zone` values can be swapped to initiate a [zone switch](https://cloud.google.com/netapp/volumes/docs/configure-and-use/storage-pools/edit-or-delete-storage-pool#switch_active_and_replica_zones). +* `allow_auto_tiering` - + (Optional) + Optional. True if the storage pool supports Auto Tiering enabled volumes. Default is false. + Auto-tiering can be enabled after storage pool creation but it can't be disabled once enabled. + * `project` - (Optional) The ID of the project in which the resource belongs. If it is not provided, the provider project is used. diff --git a/website/docs/r/netapp_volume.html.markdown b/website/docs/r/netapp_volume.html.markdown index ba6752c25dc..5de0d6a1015 100644 --- a/website/docs/r/netapp_volume.html.markdown +++ b/website/docs/r/netapp_volume.html.markdown @@ -168,6 +168,11 @@ The following arguments are supported: Optional. Flag indicating if the volume will have an IP address per node for volumes supporting multiple IP endpoints. Only the volume with largeCapacity will be allowed to have multiple endpoints. +* `tiering_policy` - + (Optional) + Tiering policy for the volume. + Structure is [documented below](#nested_tiering_policy). + * `project` - (Optional) The ID of the project in which the resource belongs. If it is not provided, the provider project is used. @@ -349,6 +354,19 @@ Possible values: DEFAULT, FORCE. (Optional) When set to true, scheduled backup is enabled on the volume. Omit if no backup_policy is specified. +The `tiering_policy` block supports: + +* `cooling_threshold_days` - + (Optional) + Optional. Time in days to mark the volume's data block as cold and make it eligible for tiering, can be range from 7-183. + Default is 31. + +* `tier_action` - + (Optional) + Optional. Flag indicating if the volume has tiering policy enable/pause. Default is PAUSED. + Default value is `PAUSED`. + Possible values are: `ENABLED`, `PAUSED`. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: @@ -403,6 +421,9 @@ In addition to the arguments listed above, the following computed attributes are ([Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) Specifies the replica zone for regional volume. +* `cold_tier_size_gib` - + Output only. Size of the volume cold tier data in GiB. + * `terraform_labels` - The combination of labels configured directly on the resource and default labels configured on the provider.