diff --git a/nutanix/services/volumesv2/helpers_test.go b/nutanix/services/volumesv2/helpers_test.go index 7e8e85b90..08ecb087f 100644 --- a/nutanix/services/volumesv2/helpers_test.go +++ b/nutanix/services/volumesv2/helpers_test.go @@ -2,7 +2,9 @@ package volumesv2_test import ( "fmt" + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" "log" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -40,6 +42,23 @@ func testAccCheckResourceAttrListNotEmpty(resourceName, attrName, subAttr string } } +func testAccCheckNutanixVolumeGroupV2Destroy(s *terraform.State) error { + conn := acc.TestAccProvider.Meta().(*conns.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "nutanix_volume_group_v2" { + continue + } + if _, err := conn.VolumeAPI.VolumeAPIInstance.DeleteVolumeGroupById(utils.StringPtr(rs.Primary.ID)); err != nil { + if strings.Contains(fmt.Sprint(err), "VOLUME_UNKNOWN_ENTITY_ERROR") { + return nil + } + return err + } + } + return nil +} + func resourceNutanixVolumeGroupV2Exists(conn *conns.Client, name string) (*string, error) { var vgUUID *string diff --git a/nutanix/services/volumesv2/resource_nutanix_volume_group_v2.go b/nutanix/services/volumesv2/resource_nutanix_volume_group_v2.go index 34bb4c8fe..a5a5d4c49 100644 --- a/nutanix/services/volumesv2/resource_nutanix_volume_group_v2.go +++ b/nutanix/services/volumesv2/resource_nutanix_volume_group_v2.go @@ -131,11 +131,89 @@ func ResourceNutanixVolumeGroupV2() *schema.Resource { Optional: true, ValidateFunc: validation.StringInSlice([]string{"USER", "INTERNAL", "TEMPORARY", "BACKUP_TARGET"}, false), }, + "attachment_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"EXTERNAL", "NONE", "DIRECT"}, false), + }, + "protocol": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"NOT_ASSIGNED", "ISCSI", "NVMF"}, false), + }, "is_hidden": { - Description: "Indicates whether the Volume Group is meant to be hidden or not. This is an optional field. If omitted, the VG will not be hidden.", - Type: schema.TypeBool, - Optional: true, - Default: false, + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "disks": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "index": { + Type: schema.TypeInt, + Optional: true, + }, + "disk_size_bytes": { + Type: schema.TypeInt, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "disk_data_source_reference": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ext_id": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "uris": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "entity_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"STORAGE_CONTAINER", "VM_DISK", "VOLUME_DISK", "DISK_RECOVERY_POINT"}, false), + }, + }, + }, + }, + "disk_storage_features": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flash_mode": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_enabled": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, }, }, } @@ -173,18 +251,19 @@ func ResourceNutanixVolumeGroupV2Create(ctx context.Context, d *schema.ResourceD if targetName, ok := d.GetOk("target_name"); ok { body.TargetName = utils.StringPtr(targetName.(string)) } - // if enabledAuthentications, ok := d.GetOk("enabled_authentications"); ok { - // enabledAuthenticationsMap := map[string]interface{}{ - // "CHAP": 2, - // "NONE": 3, - // } - // pVal := enabledAuthenticationsMap[enabledAuthentications.(string)] - // p := volumesClient.AuthenticationType(pVal.(int)) - // body.EnabledAuthentications = &p - // } else { - // p := volumesClient.AuthenticationType(0) // Replace 0 with the appropriate default value - // body.EnabledAuthentications = &p - // } + if enabledAuthentications, ok := d.GetOk("enabled_authentications"); ok { + enabledAuthenticationsMap := map[string]interface{}{ + "CHAP": 2, + "NONE": 3, + } + pVal := enabledAuthenticationsMap[enabledAuthentications.(string)] + if pVal == nil { + body.EnabledAuthentications = nil + } else { + p := volumesClient.AuthenticationType(pVal.(int)) + body.EnabledAuthentications = &p + } + } if iscsiFeatures, ok := d.GetOk("iscsi_features"); ok { body.IscsiFeatures = expandIscsiFeatures(iscsiFeatures.([]interface{})) } @@ -210,10 +289,42 @@ func ResourceNutanixVolumeGroupV2Create(ctx context.Context, d *schema.ResourceD p := volumesClient.UsageType(pInt.(int)) body.UsageType = &p } + if attachmentType, ok := d.GetOk("attachment_type"); ok { + const NONE, DIRECT, EXTERNAL = 2, 3, 4 + attachmentTypeMap := map[string]interface{}{ + "NONE": NONE, + "DIRECT": DIRECT, + "EXTERNAL": EXTERNAL, + } + pInt := attachmentTypeMap[attachmentType.(string)] + if pInt == nil { + body.AttachmentType = nil + } else { + p := volumesClient.AttachmentType(pInt.(int)) + body.AttachmentType = &p + } + } + if protocol, ok := d.GetOk("protocol"); ok { + const NotAssigned, ISCSI, NVMF = 2, 3, 4 + protocolMap := map[string]interface{}{ + "NotAssigned": NotAssigned, + "ISCSI": ISCSI, + "NVMF": NVMF, + } + pInt := protocolMap[protocol.(string)] + if pInt == nil { + body.Protocol = nil + } else { + p := volumesClient.Protocol(pInt.(int)) + body.Protocol = &p + } + } if isHidden, ok := d.GetOk("is_hidden"); ok { body.IsHidden = utils.BoolPtr(isHidden.(bool)) } - + if disks, ok := d.GetOk("disks"); ok { + body.Disks = expandDisks(disks.([]interface{})) + } resp, err := conn.VolumeAPIInstance.CreateVolumeGroup(&body) if err != nil { return diag.Errorf("error while creating Volume Group : %v", err) @@ -408,6 +519,40 @@ func expandFlashMode(flashModeList []interface{}) *volumesClient.FlashMode { return nil } +func expandDisks(disks []interface{}) []volumesClient.VolumeDisk { + + if len(disks) == 0 { + return nil + } + + disksList := make([]volumesClient.VolumeDisk, len(disks)) + + for k, v := range disks { + disk := volumesClient.VolumeDisk{} + + diskI := v.(map[string]interface{}) + + if index, ok := diskI["index"]; ok { + disk.Index = utils.IntPtr(index.(int)) + } + if diskSizeBytes, ok := diskI["disk_size_bytes"]; ok { + diskSize := int64(diskSizeBytes.(int)) + disk.DiskSizeBytes = utils.Int64Ptr(diskSize) + } + if description, ok := diskI["description"]; ok { + disk.Description = utils.StringPtr(description.(string)) + } + if diskDataSourceReference, ok := diskI["disk_data_source_reference"]; ok { + disk.DiskDataSourceReference = expandDiskDataSourceReference(diskDataSourceReference.([]interface{})) + } + if diskStorageFeatures, ok := diskI["disk_storage_features"]; ok { + disk.DiskStorageFeatures = expandDiskStorageFeatures(diskStorageFeatures.([]interface{})) + } + disksList[k] = disk + } + return disksList +} + func taskStateRefreshPrismTaskGroupFunc(ctx context.Context, client *prism.Client, taskUUID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { vresp, err := client.TaskRefAPI.GetTaskById(utils.StringPtr(taskUUID), nil) diff --git a/nutanix/services/volumesv2/resource_nutanix_volume_group_v2_test.go b/nutanix/services/volumesv2/resource_nutanix_volume_group_v2_test.go index 05ccc8010..acf43a39b 100644 --- a/nutanix/services/volumesv2/resource_nutanix_volume_group_v2_test.go +++ b/nutanix/services/volumesv2/resource_nutanix_volume_group_v2_test.go @@ -19,9 +19,9 @@ func TestAccV2NutanixVolumeGroupResource_Basic(t *testing.T) { desc := "test volume group description" resource.Test(t, resource.TestCase{ - PreCheck: func() { acc.TestAccPreCheck(t) }, - Providers: acc.TestAccProviders, - // CheckDestroy: testAccCheckNutanixVolumeGroupV4Destroy, + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckNutanixVolumeGroupV2Destroy, Steps: []resource.TestStep{ { Config: testAccVolumeGroupResourceConfig(name, desc), @@ -87,6 +87,39 @@ func TestAccV2NutanixVolumeGroupResource_WithNoClusterReference(t *testing.T) { }) } +func TestAccV2NutanixVolumeGroupResource_WithAttachmentTypeAndProtocolAndDisks(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("tf-test-volume-group-%d", r) + desc := "test volume group description with attachment type and protocol and disks" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckNutanixVolumeGroupV2Destroy, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupResourceConfigWithAttachmentTypeAndProtocolAndDisks(name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "name", name), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "description", desc), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "should_load_balance_vm_attachments", "false"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "sharing_status", "SHARED"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "created_by", "admin"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "iscsi_features.0.enabled_authentications", "CHAP"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "storage_features.0.flash_mode.0.is_enabled", "true"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "is_hidden", "false"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "usage_type", "USER"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "attachment_type", "DIRECT"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "protocol", "ISCSI"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "disks.0.disk_size_bytes", "10737418240"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "disks.0.index", "1"), + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "disks.0.disk_storage_features.0.flash_mode.0.is_enabled", "false"), + ), + }, + }, + }) +} + // VG just required attributes func testAccVolumeGroupV2RequiredAttributes(name string) string { return fmt.Sprintf(` @@ -131,3 +164,63 @@ func testAccVolumeGroupV2ConfigWithNoClusterReference(name string) string { } `, name) } + +func testAccVolumeGroupResourceConfigWithAttachmentTypeAndProtocolAndDisks(name string, desc string) string { + return fmt.Sprintf(` + data "nutanix_clusters_v2" "clusters" {} + + locals { + cluster1 = [ + for cluster in data.nutanix_clusters_v2.clusters.cluster_entities : + cluster.ext_id if cluster.config[0].cluster_function[0] != "PRISM_CENTRAL" + ][0] + } + + data "nutanix_storage_containers_v2" "test" { + filter = "clusterExtId eq '${local.cluster1}'" + limit = 1 + } + + resource "nutanix_volume_group_v2" "test" { + name = "%[1]s" + description = "%[2]s" + should_load_balance_vm_attachments = false + sharing_status = "SHARED" + created_by = "admin" + cluster_reference = local.cluster1 + iscsi_features { + target_secret = "1234567891011" + enabled_authentications = "CHAP" + } + storage_features { + flash_mode { + is_enabled = true + } + } + usage_type = "USER" + attachment_type = "DIRECT" + protocol = "ISCSI" + disks { + disk_size_bytes = 10 * 1024 * 1024 * 1024 + index = 1 + disk_data_source_reference { + name = "vg-disk-%[1]s" + ext_id = data.nutanix_storage_containers_v2.test.storage_containers[0].ext_id + entity_type = "STORAGE_CONTAINER" + uris = ["uri1","uri2"] + } + disk_storage_features { + flash_mode { + is_enabled = false + } + } + } + is_hidden = false + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } + } + `, name, desc) +}