diff --git a/examples/volume_disk_2/main.tf b/examples/volume_disk_2/main.tf new file mode 100644 index 000000000..d475c6e61 --- /dev/null +++ b/examples/volume_disk_2/main.tf @@ -0,0 +1,143 @@ +############################################################################# +# Example main.tf for Nutanix + Terraform +# +# Author: haroon.dweikat@nutanix.com +# +# This script is a quick demo of how to use the following provider objects: +# - providers +# - terraform-provider-nutanix +# - resources +# - nutanix_volume_group_disk_v2 +# - data sources +# - nutanix_volume_group_disks_v2 +# - nutanix_volume_group_disk_v2 +# - script Variables +# - clusterid's for targeting clusters within prism central +# +# Feel free to reuse, comment, and contribute, so that others may learn. +# +############################################################################# +### Define Provider Info for terraform-provider-nutanix +### This is where you define the credentials for ** Prism Central ** +### +### NOTE: +### While it may be possible to use Prism Element directly, Nutanix's +### provider is not structured or tested for this. Using Prism Central will +### give the broadest capabilities across the board + + +terraform { + required_providers { + nutanix = { + source = "nutanix/nutanix" + version = "1.7.0" + } + } +} + +#definig nutanix configuration +provider "nutanix" { + username = var.nutanix_username + password = var.nutanix_password + endpoint = var.nutanix_endpoint + port = 9440 + insecure = true +} + + +#pull cluster data +data "nutanix_clusters" "clusters" { +} + +#pull desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +########################## +### Resources +########################## + + +# create a voume group +resource "nutanix_volume_group_v2" "volume_group_example" { + name = var.volume_group_name + description = "Test Create Volume group with spec" + should_load_balance_vm_attachments = false + sharing_status = var.volume_group_sharing_status + target_name = "volumegroup-test-001234" + created_by = "example" + cluster_reference = local.cluster1 + iscsi_features { + enabled_authentications = "CHAP" + target_secret = var.volume_group_target_secret + } + + storage_features { + flash_mode { + is_enabled = true + } + } + usage_type = "USER" + is_hidden = false + + # ignore changes to target_secret, target secert will not be returned in terraform plan output + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } +} + +# create a volume group disk, and attach it to the volume group +resource "nutanix_volume_group_disk_v2" "disk_example" { + volume_group_ext_id = resource.nutanix_volume_group_v2.volume_group_example.id + # This Attribute is used to specify the index of the disk in the volume group. + # its Optional, if not provided, the disk will be added at the end of the volume group. + # if provided, the disk will be added at the specified index. make sure the index is unique. + index = 1 + description = "create volume disk example" + # disk size in bytes + disk_size_bytes = 5368709120 + + disk_data_source_reference { + name = "disk1" + ext_id = var.disk_data_source_reference_ext_id + entity_type = "STORAGE_CONTAINER" + uris = ["uri1", "uri2"] + } + + disk_storage_features { + flash_mode { + is_enabled = false + } + } + + # ignore changes to disk_data_source_reference, disk data source reference will not be returned in terraform plan output + lifecycle { + ignore_changes = [ + disk_data_source_reference + ] + } +} + + +########################## +### Data Sources +########################## + +# pull all disks in a volume group +data "nutanix_volume_group_disks_v2" "vg_disks_example" { + volume_group_ext_id = resource.nutanix_volume_group_v2.volume_group_example.id + filter = "startswith(storageContainerId, 'value')" + limit = 2 +} + +# pull a specific disk in a volume group +data "nutanix_volume_group_disk_v2" "vg_disk_example" { + ext_id = var.volume_group_disk_ext_id + volume_group_ext_id = var.volume_group_ext_id +} \ No newline at end of file diff --git a/examples/volume_disk_2/terraform.tfvars b/examples/volume_disk_2/terraform.tfvars new file mode 100644 index 000000000..ff51df9af --- /dev/null +++ b/examples/volume_disk_2/terraform.tfvars @@ -0,0 +1,5 @@ +#define values to the variables to be used in terraform file +nutanix_username = "admin" +nutanix_password = "password" +nutanix_endpoint = "10.xx.xx.xx" +nutanix_port = 9440 diff --git a/examples/volume_disk_2/variables.tf b/examples/volume_disk_2/variables.tf new file mode 100644 index 000000000..a1920a252 --- /dev/null +++ b/examples/volume_disk_2/variables.tf @@ -0,0 +1,53 @@ +#define the type of variables to be used in terraform file +variable "nutanix_username" { + type = string +} +variable "nutanix_password" { + type = string +} +variable "nutanix_endpoint" { + type = string +} +variable "nutanix_port" { + type = string +} + +variable "volume_group_name" { + type = string +} + +variable "volume_group_ext_id" { + type = string +} + +variable "volume_group_disk_ext_id" { + type = string +} + +variable "volume_group_sharing_status" { + type = string +} + +variable "volume_group_target_secret" { + type = string +} + +variable "disk_data_source_reference_ext_id" { + type = string +} + +variable "vg_iscsi_ext_id" { + type = string +} + +variable "vg_iscsi_initiator_name" { + type = string +} + +variable "vg_vm_ext_id" { + type = string +} + +variable "volume_iscsi_client_ext_id" { + type = string +} \ No newline at end of file diff --git a/examples/volume_group_2/main.tf b/examples/volume_group_2/main.tf new file mode 100644 index 000000000..5a5b94294 --- /dev/null +++ b/examples/volume_group_2/main.tf @@ -0,0 +1,110 @@ +############################################################################# +# Example main.tf for Nutanix + Terraform +# +# Author: haroon.dweikat@nutanix.com +# +# This script is a quick demo of how to use the following provider objects: +# - providers +# - terraform-provider-nutanix +# - resources +# - nutanix_volume_group_v2 +# - data sources +# - nutanix_volume_groups_v2 +# - nutanix_volume_group_v2 +# - script Variables +# - clusterid's for targeting clusters within prism central +# +# Feel free to reuse, comment, and contribute, so that others may learn. +# +############################################################################# +### Define Provider Info for terraform-provider-nutanix +### This is where you define the credentials for ** Prism Central ** +### +### NOTE: +### While it may be possible to use Prism Element directly, Nutanix's +### provider is not structured or tested for this. Using Prism Central will +### give the broadest capabilities across the board + + +terraform { + required_providers { + nutanix = { + source = "nutanix/nutanix" + version = "1.7.0" + } + } +} + +#definig nutanix configuration +provider "nutanix" { + username = var.nutanix_username + password = var.nutanix_password + endpoint = var.nutanix_endpoint + port = 9440 + insecure = true +} + + +#pull cluster data +data "nutanix_clusters" "clusters" { +} + +#pull desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +########################## +### Resources +########################## + + +# create a voume group +resource "nutanix_volume_group_v2" "volume_group_example" { + name = var.volume_group_name + description = "Test Create Volume group with spec" + should_load_balance_vm_attachments = false + sharing_status = var.volume_group_sharing_status + target_name = "volumegroup-test-001234" + created_by = "example" + cluster_reference = local.cluster1 + iscsi_features { + enabled_authentications = "CHAP" + target_secret = var.volume_group_target_secret + } + + storage_features { + flash_mode { + is_enabled = true + } + } + usage_type = "USER" + is_hidden = false + + # ignore changes to target_secret, target secert will not be returned in terraform plan output + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } +} + + +########################## +### Data Sources +########################## + +# pull all volume groups +data "nutanix_volume_groups_v2" "vgs_example" { + filter = "startswith(name, 'value')" + limit = 2 + page = 0 +} + +# pull a specific volume group +data "nutanix_volume_group_v2" "vg_example" { + ext_id = resource.nutanix_volume_group_v2.volume_group_example.id +} diff --git a/examples/volume_group_2/terraform.tfvars b/examples/volume_group_2/terraform.tfvars new file mode 100644 index 000000000..ff51df9af --- /dev/null +++ b/examples/volume_group_2/terraform.tfvars @@ -0,0 +1,5 @@ +#define values to the variables to be used in terraform file +nutanix_username = "admin" +nutanix_password = "password" +nutanix_endpoint = "10.xx.xx.xx" +nutanix_port = 9440 diff --git a/examples/volume_group_2/variables.tf b/examples/volume_group_2/variables.tf new file mode 100644 index 000000000..a1920a252 --- /dev/null +++ b/examples/volume_group_2/variables.tf @@ -0,0 +1,53 @@ +#define the type of variables to be used in terraform file +variable "nutanix_username" { + type = string +} +variable "nutanix_password" { + type = string +} +variable "nutanix_endpoint" { + type = string +} +variable "nutanix_port" { + type = string +} + +variable "volume_group_name" { + type = string +} + +variable "volume_group_ext_id" { + type = string +} + +variable "volume_group_disk_ext_id" { + type = string +} + +variable "volume_group_sharing_status" { + type = string +} + +variable "volume_group_target_secret" { + type = string +} + +variable "disk_data_source_reference_ext_id" { + type = string +} + +variable "vg_iscsi_ext_id" { + type = string +} + +variable "vg_iscsi_initiator_name" { + type = string +} + +variable "vg_vm_ext_id" { + type = string +} + +variable "volume_iscsi_client_ext_id" { + type = string +} \ No newline at end of file diff --git a/examples/volume_group_attach_vm_2/main.tf b/examples/volume_group_attach_vm_2/main.tf new file mode 100644 index 000000000..7bf910a29 --- /dev/null +++ b/examples/volume_group_attach_vm_2/main.tf @@ -0,0 +1,109 @@ +############################################################################# +# Example main.tf for Nutanix + Terraform +# +# Author: haroon.dweikat@nutanix.com +# +# This script is a quick demo of how to use the following provider objects: +# - providers +# - terraform-provider-nutanix +# - resources +# - nutanix_volume_group_vm_v2 +# - data sources +# - nutanix_volume_group_vms_v2 +# - script Variables +# - clusterid's for targeting clusters within prism central +# +# Feel free to reuse, comment, and contribute, so that others may learn. +# +############################################################################# +### Define Provider Info for terraform-provider-nutanix +### This is where you define the credentials for ** Prism Central ** +### +### NOTE: +### While it may be possible to use Prism Element directly, Nutanix's +### provider is not structured or tested for this. Using Prism Central will +### give the broadest capabilities across the board + + +terraform { + required_providers { + nutanix = { + source = "nutanix/nutanix" + version = "1.7.0" + } + } +} + +#definig nutanix configuration +provider "nutanix" { + username = var.nutanix_username + password = var.nutanix_password + endpoint = var.nutanix_endpoint + port = 9440 + insecure = true +} + + +#pull cluster data +data "nutanix_clusters" "clusters" { +} + +#pull desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +########################## +### Resources +########################## + + +# create a voume group +resource "nutanix_volume_group_v2" "volume_group_example" { + name = var.volume_group_name + description = "Test Create Volume group with spec" + should_load_balance_vm_attachments = false + sharing_status = var.volume_group_sharing_status + target_name = "volumegroup-test-001234" + created_by = "example" + cluster_reference = local.cluster1 + iscsi_features { + enabled_authentications = "CHAP" + target_secret = var.volume_group_target_secret + } + + storage_features { + flash_mode { + is_enabled = true + } + } + usage_type = "USER" + is_hidden = false + + # ignore changes to target_secret, target secert will not be returned in terraform plan output + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } +} + +# attach the volume group to a VM +resource "nutanix_volume_group_vm_v2" "vg_vm_example" { + volume_group_ext_id = resource.nutanix_volume_group_v2.volume_group_example.id + vm_ext_id = var.vg_vm_ext_id +} + + + +########################## +### Data Sources +########################## + +# List all the VM attachments for a Volume Group. +data "nutanix_volume_group_vms_v2" "vg_vm_example" { + ext_id = var.volume_group_ext_id +} diff --git a/examples/volume_group_attach_vm_2/terraform.tfvars b/examples/volume_group_attach_vm_2/terraform.tfvars new file mode 100644 index 000000000..ff51df9af --- /dev/null +++ b/examples/volume_group_attach_vm_2/terraform.tfvars @@ -0,0 +1,5 @@ +#define values to the variables to be used in terraform file +nutanix_username = "admin" +nutanix_password = "password" +nutanix_endpoint = "10.xx.xx.xx" +nutanix_port = 9440 diff --git a/examples/volume_group_attach_vm_2/variables.tf b/examples/volume_group_attach_vm_2/variables.tf new file mode 100644 index 000000000..a1920a252 --- /dev/null +++ b/examples/volume_group_attach_vm_2/variables.tf @@ -0,0 +1,53 @@ +#define the type of variables to be used in terraform file +variable "nutanix_username" { + type = string +} +variable "nutanix_password" { + type = string +} +variable "nutanix_endpoint" { + type = string +} +variable "nutanix_port" { + type = string +} + +variable "volume_group_name" { + type = string +} + +variable "volume_group_ext_id" { + type = string +} + +variable "volume_group_disk_ext_id" { + type = string +} + +variable "volume_group_sharing_status" { + type = string +} + +variable "volume_group_target_secret" { + type = string +} + +variable "disk_data_source_reference_ext_id" { + type = string +} + +variable "vg_iscsi_ext_id" { + type = string +} + +variable "vg_iscsi_initiator_name" { + type = string +} + +variable "vg_vm_ext_id" { + type = string +} + +variable "volume_iscsi_client_ext_id" { + type = string +} \ No newline at end of file diff --git a/examples/volume_group_category_details_2/main.tf b/examples/volume_group_category_details_2/main.tf new file mode 100644 index 000000000..6a43dd693 --- /dev/null +++ b/examples/volume_group_category_details_2/main.tf @@ -0,0 +1,102 @@ +############################################################################# +# Example main.tf for Nutanix + Terraform +# +# Author: haroon.dweikat@nutanix.com +# +# This script is a quick demo of how to use the following provider objects: +# - providers +# - terraform-provider-nutanix +# - data sources +# - nutanix_volume_category_details_v2 +# - script Variables +# - clusterid's for targeting clusters within prism central +# +# Feel free to reuse, comment, and contribute, so that others may learn. +# +############################################################################# +### Define Provider Info for terraform-provider-nutanix +### This is where you define the credentials for ** Prism Central ** +### +### NOTE: +### While it may be possible to use Prism Element directly, Nutanix's +### provider is not structured or tested for this. Using Prism Central will +### give the broadest capabilities across the board + + +terraform { + required_providers { + nutanix = { + source = "nutanix/nutanix" + version = "1.7.0" + } + } +} + +#definig nutanix configuration +provider "nutanix" { + username = var.nutanix_username + password = var.nutanix_password + endpoint = var.nutanix_endpoint + port = 9440 + insecure = true +} + + +#pull cluster data +data "nutanix_clusters" "clusters" { +} + +#pull desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +########################## +### Resources +########################## + + +# create a voume group +resource "nutanix_volume_group_v2" "volume_group_example" { + name = var.volume_group_name + description = "Test Create Volume group with spec" + should_load_balance_vm_attachments = false + sharing_status = var.volume_group_sharing_status + target_name = "volumegroup-test-001234" + created_by = "example" + cluster_reference = local.cluster1 + iscsi_features { + enabled_authentications = "CHAP" + target_secret = var.volume_group_target_secret + } + + storage_features { + flash_mode { + is_enabled = true + } + } + usage_type = "USER" + is_hidden = false + + # ignore changes to target_secret, target secert will not be returned in terraform plan output + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } +} + + +########################## +### Data Sources +########################## + + +# List of all category details that are associated with the Volume Group. +data "nutanix_volume_category_details_v4" "vg_cat_example" { + ext_id = resource.nutanix_volume_group_v2.volume_group_example.id + limit = 2 +} diff --git a/examples/volume_group_category_details_2/terraform.tfvars b/examples/volume_group_category_details_2/terraform.tfvars new file mode 100644 index 000000000..ff51df9af --- /dev/null +++ b/examples/volume_group_category_details_2/terraform.tfvars @@ -0,0 +1,5 @@ +#define values to the variables to be used in terraform file +nutanix_username = "admin" +nutanix_password = "password" +nutanix_endpoint = "10.xx.xx.xx" +nutanix_port = 9440 diff --git a/examples/volume_group_category_details_2/variables.tf b/examples/volume_group_category_details_2/variables.tf new file mode 100644 index 000000000..a1920a252 --- /dev/null +++ b/examples/volume_group_category_details_2/variables.tf @@ -0,0 +1,53 @@ +#define the type of variables to be used in terraform file +variable "nutanix_username" { + type = string +} +variable "nutanix_password" { + type = string +} +variable "nutanix_endpoint" { + type = string +} +variable "nutanix_port" { + type = string +} + +variable "volume_group_name" { + type = string +} + +variable "volume_group_ext_id" { + type = string +} + +variable "volume_group_disk_ext_id" { + type = string +} + +variable "volume_group_sharing_status" { + type = string +} + +variable "volume_group_target_secret" { + type = string +} + +variable "disk_data_source_reference_ext_id" { + type = string +} + +variable "vg_iscsi_ext_id" { + type = string +} + +variable "vg_iscsi_initiator_name" { + type = string +} + +variable "vg_vm_ext_id" { + type = string +} + +variable "volume_iscsi_client_ext_id" { + type = string +} \ No newline at end of file diff --git a/examples/volume_group_iscsi_client_2/main.tf b/examples/volume_group_iscsi_client_2/main.tf new file mode 100644 index 000000000..707807a1d --- /dev/null +++ b/examples/volume_group_iscsi_client_2/main.tf @@ -0,0 +1,115 @@ +############################################################################# +# Example main.tf for Nutanix + Terraform +# +# Author: haroon.dweikat@nutanix.com +# +# This script is a quick demo of how to use the following provider objects: +# - providers +# - terraform-provider-nutanix +# - resources +# - nutanix_volume_group_iscsi_client_v2 +# - data sources +# - nutanix_volume_group_iscsi_clients_v2 +# - script Variables +# - clusterid's for targeting clusters within prism central +# +# Feel free to reuse, comment, and contribute, so that others may learn. +# +############################################################################# +### Define Provider Info for terraform-provider-nutanix +### This is where you define the credentials for ** Prism Central ** +### +### NOTE: +### While it may be possible to use Prism Element directly, Nutanix's +### provider is not structured or tested for this. Using Prism Central will +### give the broadest capabilities across the board + + +terraform { + required_providers { + nutanix = { + source = "nutanix/nutanix" + version = "1.7.0" + } + } +} + +#definig nutanix configuration +provider "nutanix" { + username = var.nutanix_username + password = var.nutanix_password + endpoint = var.nutanix_endpoint + port = 9440 + insecure = true +} + + +#pull cluster data +data "nutanix_clusters" "clusters" { +} + +#pull desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +########################## +### Resources +########################## + + +# create a voume group +resource "nutanix_volume_group_v2" "volume_group_example" { + name = var.volume_group_name + description = "Test Create Volume group with spec" + should_load_balance_vm_attachments = false + sharing_status = var.volume_group_sharing_status + target_name = "volumegroup-test-001234" + created_by = "example" + cluster_reference = local.cluster1 + iscsi_features { + enabled_authentications = "CHAP" + target_secret = var.volume_group_target_secret + } + + storage_features { + flash_mode { + is_enabled = true + } + } + usage_type = "USER" + is_hidden = false + + # ignore changes to target_secret, target secert will not be returned in terraform plan output + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } +} + +# attach iscsi client to the volume group +resource "nutanix_volume_group_iscsi_client_v2" "vg_iscsi_example" { + vg_ext_id = resource.nutanix_volume_group_v2.volume_group_example.id + ext_id = var.vg_iscsi_ext_id + iscsi_initiator_name = var.vg_iscsi_initiator_name +} + + +########################## +### Data Sources +########################## + +# pull all iscsi clients in a volume group +data "nutanix_volume_group_iscsi_clients_v2" "vg_iscsi_example" { + ext_id = var.volume_group_ext_id +} + +# List of all category details that are associated with the Volume Group. +data "nutanix_volume_category_details_v4" "vg_cat_example" { + ext_id = resource.nutanix_volume_group_v2.volume_group_example.id + limit = 2 +} diff --git a/examples/volume_group_iscsi_client_2/terraform.tfvars b/examples/volume_group_iscsi_client_2/terraform.tfvars new file mode 100644 index 000000000..ff51df9af --- /dev/null +++ b/examples/volume_group_iscsi_client_2/terraform.tfvars @@ -0,0 +1,5 @@ +#define values to the variables to be used in terraform file +nutanix_username = "admin" +nutanix_password = "password" +nutanix_endpoint = "10.xx.xx.xx" +nutanix_port = 9440 diff --git a/examples/volume_group_iscsi_client_2/variables.tf b/examples/volume_group_iscsi_client_2/variables.tf new file mode 100644 index 000000000..a1920a252 --- /dev/null +++ b/examples/volume_group_iscsi_client_2/variables.tf @@ -0,0 +1,53 @@ +#define the type of variables to be used in terraform file +variable "nutanix_username" { + type = string +} +variable "nutanix_password" { + type = string +} +variable "nutanix_endpoint" { + type = string +} +variable "nutanix_port" { + type = string +} + +variable "volume_group_name" { + type = string +} + +variable "volume_group_ext_id" { + type = string +} + +variable "volume_group_disk_ext_id" { + type = string +} + +variable "volume_group_sharing_status" { + type = string +} + +variable "volume_group_target_secret" { + type = string +} + +variable "disk_data_source_reference_ext_id" { + type = string +} + +variable "vg_iscsi_ext_id" { + type = string +} + +variable "vg_iscsi_initiator_name" { + type = string +} + +variable "vg_vm_ext_id" { + type = string +} + +variable "volume_iscsi_client_ext_id" { + type = string +} \ No newline at end of file diff --git a/examples/volume_iscsi_clients_2/main.tf b/examples/volume_iscsi_clients_2/main.tf new file mode 100644 index 000000000..7ebdfe8c6 --- /dev/null +++ b/examples/volume_iscsi_clients_2/main.tf @@ -0,0 +1,69 @@ +############################################################################# +# Example main.tf for Nutanix + Terraform +# +# Author: haroon.dweikat@nutanix.com +# +# This script is a quick demo of how to use the following provider objects: +# - providers +# - terraform-provider-nutanix +# - data sources +# - nutanix_volume_iscsi_client_v2 +# - nutanix_volume_iscsi_clients_v2 +# - script Variables +# - clusterid's for targeting clusters within prism central +# +# Feel free to reuse, comment, and contribute, so that others may learn. +# +############################################################################# +### Define Provider Info for terraform-provider-nutanix +### This is where you define the credentials for ** Prism Central ** +### +### NOTE: +### While it may be possible to use Prism Element directly, Nutanix's +### provider is not structured or tested for this. Using Prism Central will +### give the broadest capabilities across the board + + +terraform { + required_providers { + nutanix = { + source = "nutanix/nutanix" + version = "1.7.0" + } + } +} + +#definig nutanix configuration +provider "nutanix" { + username = var.nutanix_username + password = var.nutanix_password + endpoint = var.nutanix_endpoint + port = 9440 + insecure = true +} + + +#pull cluster data +data "nutanix_clusters" "clusters" { +} + +#pull desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +########################## +### Data Sources +########################## + + +# Fetch an iSCSI client details. +data "nutanix_volume_iscsi_client_v2" "v_iscsi_client_example" { + ext_id = var.volume_iscsi_client_ext_id +} + +# List all the iSCSI clients. +data "nutanix_volume_iscsi_clients_v2" "v_iscsi_clients_example" {} \ No newline at end of file diff --git a/examples/volume_iscsi_clients_2/terraform.tfvars b/examples/volume_iscsi_clients_2/terraform.tfvars new file mode 100644 index 000000000..ff51df9af --- /dev/null +++ b/examples/volume_iscsi_clients_2/terraform.tfvars @@ -0,0 +1,5 @@ +#define values to the variables to be used in terraform file +nutanix_username = "admin" +nutanix_password = "password" +nutanix_endpoint = "10.xx.xx.xx" +nutanix_port = 9440 diff --git a/examples/volume_iscsi_clients_2/variables.tf b/examples/volume_iscsi_clients_2/variables.tf new file mode 100644 index 000000000..a1920a252 --- /dev/null +++ b/examples/volume_iscsi_clients_2/variables.tf @@ -0,0 +1,53 @@ +#define the type of variables to be used in terraform file +variable "nutanix_username" { + type = string +} +variable "nutanix_password" { + type = string +} +variable "nutanix_endpoint" { + type = string +} +variable "nutanix_port" { + type = string +} + +variable "volume_group_name" { + type = string +} + +variable "volume_group_ext_id" { + type = string +} + +variable "volume_group_disk_ext_id" { + type = string +} + +variable "volume_group_sharing_status" { + type = string +} + +variable "volume_group_target_secret" { + type = string +} + +variable "disk_data_source_reference_ext_id" { + type = string +} + +variable "vg_iscsi_ext_id" { + type = string +} + +variable "vg_iscsi_initiator_name" { + type = string +} + +variable "vg_vm_ext_id" { + type = string +} + +variable "volume_iscsi_client_ext_id" { + type = string +} \ No newline at end of file diff --git a/go.mod b/go.mod index c6c886ba4..edf126e7a 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/nutanix-core/ntnx-api-golang-sdk-internal/microseg-go-client/v16 v16.9.0-8831 github.com/nutanix-core/ntnx-api-golang-sdk-internal/networking-go-client/v16 v16.9.0-8856 github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16 v16.9.0-8887 + github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16 v16.9.0-8827 github.com/spf13/cast v1.3.1 github.com/stretchr/testify v1.7.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 0ecafa4a5..42a5ef24d 100644 --- a/go.sum +++ b/go.sum @@ -454,6 +454,10 @@ github.com/nutanix-core/ntnx-api-golang-sdk-internal/networking-go-client/v16 v1 github.com/nutanix-core/ntnx-api-golang-sdk-internal/networking-go-client/v16 v16.9.0-8856/go.mod h1:vHyQVF3IKxmip+xGxXDQznKk1ffrVa4HSiEEueiekaE= github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16 v16.9.0-8887 h1:TZKhy9NyH2VTnrRmz0sm8aRSHstL49mKbw63s+0hLB0= github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16 v16.9.0-8887/go.mod h1:qmOw/29LhPpII8cDmbTL0OF3btwp97ss7nFcQz72NDM= +github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16 v16.8.0-5295 h1:8Sv5R9lItsRi4m/+/kwCuFR2jSxc8K4RRxxPXsccphQ= +github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16 v16.8.0-5295/go.mod h1:qmOw/29LhPpII8cDmbTL0OF3btwp97ss7nFcQz72NDM= +github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16 v16.9.0-8827 h1:r/FSW++1SKRir/gMGZ1nuMSs960yknfNEvAEaEhI7Hw= +github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16 v16.9.0-8827/go.mod h1:ABLxzNkbsfu/05qZhS9jnO7altxsiWMs80rNcSc94LQ= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= diff --git a/nutanix/config.go b/nutanix/config.go index a898f5f04..e102f883a 100644 --- a/nutanix/config.go +++ b/nutanix/config.go @@ -3,18 +3,20 @@ package nutanix import ( "fmt" - "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v3/karbon" - "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v4/microseg" - "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v4/networking" - "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v4/prism" - "github.com/terraform-providers/terraform-provider-nutanix/nutanix/client" + "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v3/karbon" era "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v3/era" foundation_central "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v3/fc" "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v3/foundation" + "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v3/karbon" v3 "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v3/prism" + + "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v4/microseg" + "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v4/networking" + "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v4/prism" "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v4/clusters" "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v4/iam" + "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v4/volumes" ) // Version represents api version @@ -81,15 +83,15 @@ func (c *Config) Client() (*Client, error) { if err != nil { return nil, err } - networkingClient, err := networking.NewNetworkingClient(configCreds) + prismClient, err := prism.NewPrismClient(configCreds) if err != nil { return nil, err } - prismClient, err := prism.NewPrismClient(configCreds) + microsegClient, err := microseg.NewMicrosegClient(configCreds) if err != nil { return nil, err } - microsegClient, err := microseg.NewMicrosegClient(configCreds) + volumeClient, err := volumes.NewVolumeClient(configCreds) if err != nil { return nil, err } @@ -110,6 +112,7 @@ func (c *Config) Client() (*Client, error) { MicroSegAPI: microsegClient, IamAPI: iamClient, ClusterAPI: clustersClient, + VolumeAPI: volumeClient, }, nil } @@ -126,4 +129,5 @@ type Client struct { MicroSegAPI *microseg.Client IamAPI *iam.Client ClusterAPI *clusters.Client + VolumeAPI *volumes.Client } diff --git a/nutanix/provider/provider.go b/nutanix/provider/provider.go index 2d7ca50b6..44f896fea 100644 --- a/nutanix/provider/provider.go +++ b/nutanix/provider/provider.go @@ -20,6 +20,7 @@ import ( "github.com/terraform-providers/terraform-provider-nutanix/nutanix/services/v2/networkingv2" "github.com/terraform-providers/terraform-provider-nutanix/nutanix/services/v2/prismv2" "github.com/terraform-providers/terraform-provider-nutanix/nutanix/services/v2/storagecontainersv2" + "github.com/terraform-providers/terraform-provider-nutanix/nutanix/services/v2/volumesv2" ) var requiredProviderFields map[string][]string = map[string][]string{ @@ -258,6 +259,15 @@ func Provider() *schema.Provider { "nutanix_storage_container_stats_info_v2": storagecontainersv2.DatasourceNutanixStorageStatsInfoV2(), "nutanix_category_v2": prismv2.DatasourceNutanixCategoryV2(), "nutanix_categories_v2": prismv2.DatasourceNutanixCategoriesV2(), + "nutanix_volume_groups_v2": volumesv2.DatasourceNutanixVolumeGroupsV2(), + "nutanix_volume_group_v2": volumesv2.DatasourceNutanixVolumeGroupV2(), + "nutanix_volume_group_disks_v2": volumesv2.DatasourceNutanixVolumeDisksV2(), + "nutanix_volume_group_disk_v2": volumesv2.DatasourceNutanixVolumeDiskV2(), + "nutanix_volume_group_iscsi_clients_v2": volumesv2.DatasourceNutanixVolumeGroupIscsiClientsV2(), + "nutanix_volume_group_category_details_v2": volumesv2.DatasourceNutanixVolumeCategoryDetailsV2(), + "nutanix_volume_group_vms_v2": volumesv2.DataSourceNutanixVolumeGroupVmsV2(), + "nutanix_volume_iscsi_clients_v2": volumesv2.DatasourceNutanixVolumeIscsiClientsV2(), + "nutanix_volume_iscsi_client_v2": volumesv2.DatasourceNutanixVolumeIscsiClientV2(), }, ResourcesMap: map[string]*schema.Resource{ "nutanix_virtual_machine": prism.ResourceNutanixVirtualMachine(), @@ -325,6 +335,10 @@ func Provider() *schema.Provider { "nutanix_saml_identity_providers_v2": iamv2.ResourceNutanixSamlIdpV2(), "nutanix_storage_containers_v2": storagecontainersv2.ResourceNutanixStorageContainersV2(), "nutanix_category_v2": prismv2.ResourceNutanixCategoriesV2(), + "nutanix_volume_group_v2": volumesv2.ResourceNutanixVolumeGroupV2(), + "nutanix_volume_group_disk_v2": volumesv2.ResourceNutanixVolumeGroupDiskV2(), + "nutanix_volume_group_iscsi_client_v2": volumesv2.ResourceNutanixVolumeGroupIscsiClientV2(), + "nutanix_volume_group_vm_v2": volumesv2.ResourceNutanixVolumeAttachVmToVolumeGroupV2(), }, ConfigureContextFunc: providerConfigure, } diff --git a/nutanix/sdks/v4/volumes/volumes.go b/nutanix/sdks/v4/volumes/volumes.go new file mode 100644 index 000000000..5d6164a04 --- /dev/null +++ b/nutanix/sdks/v4/volumes/volumes.go @@ -0,0 +1,37 @@ +package volumes + +import ( + "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/api" + prism "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/client" + + "github.com/terraform-providers/terraform-provider-nutanix/nutanix/client" +) + +type Client struct { + VolumeAPIInstance *api.VolumeGroupsApi + IscsiClientAPIInstance *api.IscsiClientsApi +} + +func NewVolumeClient(credentials client.Credentials) (*Client, error) { + var baseClient *prism.ApiClient + + // check if all required fields are present. Else create an empty client + if credentials.Username != "" && credentials.Password != "" && credentials.Endpoint != "" { + pcClient := prism.NewApiClient() + + pcClient.Host = credentials.Endpoint + pcClient.Password = credentials.Password + pcClient.Username = credentials.Username + pcClient.Port = 9440 + pcClient.VerifySSL = false + + baseClient = pcClient + } + + f := &Client{ + VolumeAPIInstance: api.NewVolumeGroupsApi(baseClient), + IscsiClientAPIInstance: api.NewIscsiClientsApi(baseClient), + } + + return f, nil +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_category_details_v2.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_category_details_v2.go new file mode 100644 index 000000000..113ab2c1e --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_category_details_v2.go @@ -0,0 +1,138 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// List all the category details that are associated with the Volume Group. +func DatasourceNutanixVolumeCategoryDetailsV2() *schema.Resource { + return &schema.Resource{ + ReadContext: DatasourceNutanixVolumeCategoryDetailsV2Read, + + Description: "Query the category details that are associated with the Volume Group identified by {volumeGroupExtId}.", + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "The external identifier of the Volume Group.", + Type: schema.TypeString, + Required: true, + }, + "page": { + Description: "A URL query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. Any number out of this range might lead to no results.", + Type: schema.TypeInt, + Optional: true, + }, + "limit": { + Description: "A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set.", + Type: schema.TypeInt, + Optional: true, + }, + "category_details": { + Description: "List of all category details that are associated with the Volume Group.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "The external identifier of the category detail", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "The name of the category detail.", + Type: schema.TypeString, + Computed: true, + }, + "uris": { + Description: "The uri list of the category detail.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Description: "", + Type: schema.TypeList, + }, + }, + "entity_type": { + Description: "The Entity Type of the category detail.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func DatasourceNutanixVolumeCategoryDetailsV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + var page, limit *int + + volumeGroupExtId := d.Get("ext_id") + + // initialize the query parameters + if pagef, ok := d.GetOk("page"); ok { + page = utils.IntPtr(pagef.(int)) + } else { + page = nil + } + if limitf, ok := d.GetOk("limit"); ok { + limit = utils.IntPtr(limitf.(int)) + } else { + limit = nil + } + + // get the volume groups response + resp, err := conn.VolumeAPIInstance.ListCategoryAssociationsByVolumeGroupId(utils.StringPtr(volumeGroupExtId.(string)), page, limit) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching volumes : %v", errorMessage["message"]) + } + + // extract the volume groups data from the response + getResp := resp.Data.GetValue().([]volumesClient.CategoryDetails) + + // set the volume groups data in the terraform resource + if err := d.Set("category_details", flattenCategoryDetails(getResp)); err != nil { + return diag.FromErr(err) + } + + d.SetId(resource.UniqueId()) + return nil +} + +func flattenCategoryDetails(categories []volumesClient.CategoryDetails) []interface{} { + if len(categories) > 0 { + categoriesList := make([]interface{}, len(categories)) + + for k, v := range categories { + category := make(map[string]interface{}) + + category["ext_id"] = v.ExtId + category["name"] = v.Name + category["uris"] = v.Uris + category["entity_type"] = v.EntityType.GetName() + + categoriesList[k] = category + } + return categoriesList + } + return nil +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_category_details_v2_test.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_category_details_v2_test.go new file mode 100644 index 000000000..11c320eff --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_category_details_v2_test.go @@ -0,0 +1,45 @@ +package volumesv2_test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const dataSourceVolumeCategoryDetails = "data.nutanix_volume_group_category_details_v2.test" + +func TestAccNutanixVolumeCategoryDetailsV2_Basic(t *testing.T) { + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCategoryDetailsV2Config(filepath), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceVolumeCategoryDetails, "category_details.0.entity_type", "CATEGORY"), + resource.TestCheckResourceAttr(dataSourceVolumeCategoryDetails, "category_details.0.name", ""), + resource.TestCheckResourceAttrSet(dataSourceVolumeCategoryDetails, "category_details.#"), + ), + }, + }, + }) +} + +func testAccCategoryDetailsV2Config(filepath string) string { + return fmt.Sprintf(` + locals { + config = (jsondecode(file("%s"))) + volumes = local.config.volumes + } + + data "nutanix_volume_group_category_details_v2" "test" { + ext_id = local.volumes.vg_ext_id_with_category + } +`, filepath) +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disk_v2.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disk_v2.go new file mode 100644 index 000000000..9ef42f353 --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disk_v2.go @@ -0,0 +1,184 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// Get the details of a Volume Disk. +func DatasourceNutanixVolumeDiskV2() *schema.Resource { + return &schema.Resource{ + Description: "Query the Volume Disk identified by {extId} in the Volume Group identified by {volumeGroupExtId}.", + ReadContext: DatasourceNutanixVolumeDiskV2Read, + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "The external identifier of the Volume Disk.", + Type: schema.TypeString, + Required: true, + }, + "volume_group_ext_id": { + Description: "The external identifier of the Volume Group.", + Type: schema.TypeString, + Required: true, + }, + "tenant_id": { + Description: "A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server).", + Type: schema.TypeString, + Computed: true, + }, + "links": { + Description: "A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Description: "The URL at which the entity described by the link can be accessed.", + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Description: "A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of \"self\" identifies the URL for the object.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "index": { + Description: "Index of the disk in a Volume Group. This field is optional and immutable.", + Type: schema.TypeInt, + Computed: true, + }, + "disk_size_bytes": { + Description: "Size of the disk in bytes. This field is mandatory during Volume Group creation if a new disk is being created on the storage container.", + Type: schema.TypeInt, + Computed: true, + }, + "storage_container_id": { + Description: "Storage container on which the disk must be created. This is a read-only field.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "Volume Disk description. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "disk_data_source_reference": { + Description: "Disk Data Source Reference.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "The external identifier of the Data Source Reference.", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "The name of the Data Source Reference.", + Type: schema.TypeString, + Computed: true, + }, + "uris": { + Description: "The uri list of the Data Source Reference.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeList, + }, + }, + "entity_type": { + Description: "The Entity Type of the Data Source Reference.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "disk_storage_features": { + Description: "Storage optimization features which must be enabled on the Volume Disks. This is an optional field. If omitted, the disks will honor the Volume Group specific storage features setting.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flash_mode": { + Description: "Once configured, this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_enabled": { + Description: "The flash mode is enabled or not.", + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func DatasourceNutanixVolumeDiskV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + volumeGroupExtId := d.Get("volume_group_ext_id") + volumeDiskExtID := d.Get("ext_id") + + resp, err := conn.VolumeAPIInstance.GetVolumeDiskById(utils.StringPtr(volumeGroupExtId.(string)), utils.StringPtr(volumeDiskExtID.(string))) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching volume Disk : %v", errorMessage["message"]) + } + getResp := resp.Data.GetValue().(volumesClient.VolumeDisk) + + if err := d.Set("tenant_id", getResp.TenantId); err != nil { + return diag.FromErr(err) + } + if err := d.Set("ext_id", getResp.ExtId); err != nil { + return diag.FromErr(err) + } + if err := d.Set("links", flattenLinks(getResp.Links)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("index", getResp.Index); err != nil { + return diag.FromErr(err) + } + if err := d.Set("disk_size_bytes", getResp.DiskSizeBytes); err != nil { + return diag.FromErr(err) + } + if err := d.Set("storage_container_id", getResp.StorageContainerId); err != nil { + return diag.FromErr(err) + } + if err := d.Set("description", getResp.Description); err != nil { + return diag.FromErr(err) + } + if err := d.Set("disk_data_source_reference", flattenDiskDataSourceReference(getResp.DiskDataSourceReference)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("disk_storage_features", flattenDiskStorageFeatures(getResp.DiskStorageFeatures)); err != nil { + return diag.FromErr(err) + } + d.SetId(*getResp.ExtId) + return nil +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disk_v2_test.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disk_v2_test.go new file mode 100644 index 000000000..d4fd977e7 --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disk_v2_test.go @@ -0,0 +1,50 @@ +package volumesv2_test + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "os" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const dataSourceVolumeGroupsDisk = "data.nutanix_volume_group_disk_v2.test" + +func TestAccNutanixVolumeGroupsDiskV2DataSource_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("terraform-test-volume-group-disk-%d", r) + desc := "terraform test volume group disk description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupsDiskDataSourceConfig(filepath, name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisk, "index", "1"), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisk, "disk_size_bytes", strconv.Itoa(int(diskSizeBytes))), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisk, "disk_storage_features.0.flash_mode.0.is_enabled", "false"), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisk, "description", desc), + ), + }, + }, + }) +} + +func testAccVolumeGroupsDiskDataSourceConfig(filepath, name, desc string) string { + return testAccVolumeGroupDiskResourceConfig(filepath, name, desc) + + testAccVolumeGroupResourceConfig(filepath, name, desc) + + ` + data "nutanix_volume_group_disk_v2" "test" { + volume_group_ext_id = nutanix_volume_group_v2.test.id + ext_id = resource.nutanix_volume_group_disk_v2.test.id + depends_on = [resource.nutanix_volume_group_disk_v2.test] + } + ` +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disks_v2.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disks_v2.go new file mode 100644 index 000000000..f7df92c26 --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disks_v2.go @@ -0,0 +1,309 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/common/v1/config" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// List all the Volume Disks attached to the Volume Group. +func DatasourceNutanixVolumeDisksV2() *schema.Resource { + return &schema.Resource{ + Description: "Query the list of disks corresponding to a Volume Group identified by {volumeGroupExtId}.", + ReadContext: DatasourceNutanixVolumeDisksV2Read, + Schema: map[string]*schema.Schema{ + "volume_group_ext_id": { + Description: "The external identifier of the Volume Group.", + Type: schema.TypeString, + Required: true, + }, + "page": { + Description: "A URL query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. Any number out of this range might lead to no results.", + Type: schema.TypeInt, + Optional: true, + }, + "limit": { + Description: "A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set.", + Type: schema.TypeInt, + Optional: true, + }, + "filter": { + Description: "A URL query parameter that allows clients to filter a collection of resources. The expression specified with $filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Expression specified with the $filter must conform to the OData V4.01 URL conventions. For example, filter '$filter=name eq 'karbon-ntnx-1.0' would filter the result on cluster name 'karbon-ntnx1.0', filter '$filter=startswith(name, 'C')' would filter on cluster name starting with 'C'. The filter can be applied to the following fields: storageContainerId", + Type: schema.TypeString, + Optional: true, + }, + "orderby": { + Description: "A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default. For example, '$orderby=templateName desc' would get all templates sorted by templateName in descending order. The orderby can be applied to the following fields: diskSizeBytes", + Type: schema.TypeString, + Optional: true, + }, + "select": { + Description: "A URL query parameter that allows clients to request a specific set of properties for each entity or complex type. Expression specified with the $select must conform to the OData V4.01 URL conventions. If a $select expression consists of a single select item that is an asterisk (i.e., *), then all properties on the matching resource will be returned. The select can be applied to the following fields: extId, storageContainerId", + Type: schema.TypeString, + Optional: true, + }, + "disks": { + Description: "List of disks corresponding to a Volume Group identified by {volumeGroupExtId}.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "tenant_id": { + Description: "A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server).", + Type: schema.TypeString, + Computed: true, + }, + "ext_id": { + Description: "A globally unique identifier of an instance that is suitable for external consumption.", + Type: schema.TypeString, + Computed: true, + }, + "links": { + Description: "A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Description: "The URL at which the entity described by the link can be accessed.", + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Description: "A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of \"self\" identifies the URL for the object.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "index": { + Description: "Index of the disk in a Volume Group. This field is optional and immutable.", + Type: schema.TypeInt, + Computed: true, + }, + "disk_size_bytes": { + Description: "Size of the disk in bytes. This field is mandatory during Volume Group creation if a new disk is being created on the storage container.", + Type: schema.TypeInt, + Computed: true, + }, + "storage_container_id": { + Description: "Storage container on which the disk must be created. This is a read-only field.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "Volume Disk description. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "disk_data_source_reference": { + Description: "Disk Data Source Reference.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "The external identifier of the Data Source Reference.", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "The name of the Data Source Reference.", + Type: schema.TypeString, + Computed: true, + }, + "uris": { + Description: "The uri list of the Data Source Reference.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeList, + }, + }, + "entity_type": { + Description: "The Entity Type of the Data Source Reference.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "disk_storage_features": { + Description: "Storage optimization features which must be enabled on the Volume Disks. This is an optional field. If omitted, the disks will honor the Volume Group specific storage features setting.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flash_mode": { + Description: "The flash mode of the disk.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_enabled": { + Description: "The flash mode is enabled or not.", + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func DatasourceNutanixVolumeDisksV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + + conn := meta.(*conns.Client).VolumeAPI + + var filter, orderBy, selects *string + var page, limit *int + + volumeGroupExtID := d.Get("volume_group_ext_id") + + // initialize the query parameters + if pagef, ok := d.GetOk("page"); ok { + page = utils.IntPtr(pagef.(int)) + } else { + page = nil + } + if limitf, ok := d.GetOk("limit"); ok { + limit = utils.IntPtr(limitf.(int)) + } else { + limit = nil + } + if filterf, ok := d.GetOk("filter"); ok { + filter = utils.StringPtr(filterf.(string)) + } else { + filter = nil + } + if order, ok := d.GetOk("order_by"); ok { + orderBy = utils.StringPtr(order.(string)) + } else { + orderBy = nil + } + if selectf, ok := d.GetOk("select"); ok { + selects = utils.StringPtr(selectf.(string)) + } else { + selects = nil + } + + // get the volume groups response + resp, err := conn.VolumeAPIInstance.ListVolumeDisksByVolumeGroupId(utils.StringPtr(volumeGroupExtID.(string)), page, limit, filter, orderBy, selects) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching Disks attached to the volume group : %v", errorMessage["message"]) + } + // extract the volume groups data from the response + if resp.Data == nil { + if err := d.Set("disks", make([]interface{}, 0)); err != nil { + return diag.FromErr(err) + } + } else { + getResp := resp.Data.GetValue().([]volumesClient.VolumeDisk) + // set the volume groups data in the terraform resource + if err := d.Set("disks", flattenDisksEntities(getResp)); err != nil { + return diag.FromErr(err) + } + } + d.SetId(resource.UniqueId()) + return nil +} + +func flattenDisksEntities(volumeDisks []volumesClient.VolumeDisk) []interface{} { + if len(volumeDisks) > 0 { + volumeDiskList := make([]interface{}, len(volumeDisks)) + + for k, v := range volumeDisks { + volumeDisk := make(map[string]interface{}) + + if v.TenantId != nil { + volumeDisk["tenant_id"] = v.TenantId + } + if v.ExtId != nil { + volumeDisk["ext_id"] = v.ExtId + } + if v.Links != nil { + volumeDisk["links"] = flattenLinks(v.Links) + } + if v.Index != nil { + volumeDisk["index"] = v.Index + } + if v.DiskSizeBytes != nil { + volumeDisk["disk_size_bytes"] = v.DiskSizeBytes + } + if v.StorageContainerId != nil { + volumeDisk["storage_container_id"] = v.StorageContainerId + } + if v.Description != nil { + volumeDisk["description"] = v.Description + } + if v.DiskDataSourceReference != nil { + volumeDisk["disk_data_source_reference"] = flattenDiskDataSourceReference(v.DiskDataSourceReference) + } + if v.DiskStorageFeatures != nil { + volumeDisk["disk_storage_features"] = flattenDiskStorageFeatures(v.DiskStorageFeatures) + } + log.Printf("[DEBUG] Disk : %v", volumeDisk) + volumeDiskList[k] = volumeDisk + } + return volumeDiskList + } + return nil +} + +func flattenDiskDataSourceReference(entityReference *config.EntityReference) []map[string]interface{} { + if entityReference != nil { + diskDataSourceReferenceList := make([]map[string]interface{}, 0) + diskDataSourceReference := make(map[string]interface{}) + diskDataSourceReference["ext_id"] = entityReference.ExtId + diskDataSourceReference["name"] = entityReference.Name + diskDataSourceReference["uris"] = entityReference.Uris + diskDataSourceReference["entity_type"] = entityReference.EntityType + + log.Printf("[DEBUG] Disks Data Source Reference ext_id: %v", diskDataSourceReference["ext_id"]) + log.Printf("[DEBUG] Disks Data Source Reference name: %v", diskDataSourceReference["name"]) + log.Printf("[DEBUG] Disks Data Source Reference uris: %v", diskDataSourceReference["uris"]) + log.Printf("[DEBUG] Disks Data Source Reference entity_type: %v", diskDataSourceReference["entity_type"]) + + diskDataSourceReferenceList = append(diskDataSourceReferenceList, diskDataSourceReference) + + return diskDataSourceReferenceList + } + return nil +} + +func flattenDiskStorageFeatures(diskStorageFeatures *volumesClient.DiskStorageFeatures) []map[string]interface{} { + if diskStorageFeatures != nil { + diskStorageFeaturesList := make([]map[string]interface{}, 0) + flashMode := make(map[string]interface{}) + flashMode["flash_mode"] = flattenFlashMode(diskStorageFeatures.FlashMode) + diskStorageFeaturesList = append(diskStorageFeaturesList, flashMode) + return diskStorageFeaturesList + } + return nil +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disks_v2_test.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disks_v2_test.go new file mode 100644 index 000000000..c198d05c4 --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_disks_v2_test.go @@ -0,0 +1,137 @@ +package volumesv2_test + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "os" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const dataSourceVolumeGroupsDisks = "data.nutanix_volume_group_disks_v2.test" + +func TestAccNutanixVolumeGroupsDisksV2DataSource_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("terraform-test-volume-group-disk-%d", r) + desc := "terraform test volume group disk description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + resource.Test(t, resource.TestCase{ + + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupsDisksDataSourceConfig(filepath, name, desc), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourceAttrListNotEmpty(dataSourceVolumeGroupsDisks, "disks", "index"), + resource.TestCheckResourceAttrSet(dataSourceVolumeGroupsDisks, "disks.#"), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisks, "disks.#", "2"), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisks, "disks.0.disk_size_bytes", strconv.Itoa(int(diskSizeBytes))), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisks, "disks.0.disk_storage_features.0.flash_mode.0.is_enabled", "false"), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisks, "disks.0.description", desc), + ), + }, + }, + }) +} + +func TestAccNutanixVolumeGroupsDisksV2DataSource_WithLimit(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("terraform-test-volume-group-disk-%d", r) + desc := "terraform test volume group disk description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + limit := 1 + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupsDisksDataSourceWithLimit(filepath, name, desc, limit), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourceAttrListNotEmpty(dataSourceVolumeGroupsDisks, "disks", "index"), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisks, "disks.#", "1"), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisks, "disks.0.disk_size_bytes", strconv.Itoa(int(diskSizeBytes))), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisks, "disks.0.disk_storage_features.0.flash_mode.0.is_enabled", "false"), + resource.TestCheckResourceAttr(dataSourceVolumeGroupsDisks, "disks.0.description", desc), + ), + }, + }, + }) +} + +func testAccVolumeGroupsDisksDataSourceConfig(filepath, name, desc string) string { + return testAccVolumeGroupResourceConfig(filepath, name, desc) + testAccVolumeGroupDiskResourceConfig(filepath, name, desc) + + fmt.Sprintf(` + resource "nutanix_volume_group_disk_v2" "test-2" { + volume_group_ext_id = resource.nutanix_volume_group_v2.test.id + index = 2 + description = "%[1]s" + disk_size_bytes = %[2]d + disk_data_source_reference { + name = "terraform-test-disk_data_source_reference-disk-2" + ext_id = local.vg_disk.disk_data_source_reference.ext_id + entity_type = "STORAGE_CONTAINER" + uris = ["uri3","uri4"] + } + disk_storage_features { + flash_mode { + is_enabled = false + } + } + lifecycle { + ignore_changes = [ + disk_data_source_reference + ] + } + depends_on = [resource.nutanix_volume_group_v2.test] + } + + data "nutanix_volume_group_disks_v2" "test" { + volume_group_ext_id = resource.nutanix_volume_group_v2.test.id + depends_on = [resource.nutanix_volume_group_v2.test ,resource.nutanix_volume_group_disk_v2.test, resource.nutanix_volume_group_disk_v2.test-2] + } + `, desc, diskSizeBytes) +} + +func testAccVolumeGroupsDisksDataSourceWithLimit(filepath, name, desc string, limit int) string { + return testAccVolumeGroupResourceConfig(filepath, name, desc) + testAccVolumeGroupDiskResourceConfig(filepath, name, desc) + + fmt.Sprintf(` + resource "nutanix_volume_group_disk_v2" "test-2" { + volume_group_ext_id = resource.nutanix_volume_group_v2.test.id + index = 2 + description = "%[1]s" + disk_size_bytes = %[2]d + disk_data_source_reference { + name = "terraform-test-disk_data_source_reference-disk-2" + ext_id = local.vg_disk.disk_data_source_reference.ext_id + entity_type = "STORAGE_CONTAINER" + uris = ["uri3","uri4"] + } + disk_storage_features { + flash_mode { + is_enabled = false + } + } + lifecycle { + ignore_changes = [ + disk_data_source_reference + ] + } + depends_on = [resource.nutanix_volume_group_v2.test] + } + + data "nutanix_volume_group_disks_v2" "test" { + volume_group_ext_id = resource.nutanix_volume_group_v2.test.id + limit = %[3]d + depends_on = [resource.nutanix_volume_group_v2.test ,resource.nutanix_volume_group_disk_v2.test, resource.nutanix_volume_group_disk_v2.test-2] + } + + `, desc, diskSizeBytes, limit, + ) +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_iscsi_clients_v2.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_iscsi_clients_v2.go new file mode 100644 index 000000000..fb334c92e --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_iscsi_clients_v2.go @@ -0,0 +1,195 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// List all the iSCSI attachments associated with the given Volume Group. +func DatasourceNutanixVolumeGroupIscsiClientsV2() *schema.Resource { + return &schema.Resource{ + Description: "Query the list of external iSCSI attachments for a Volume Group identified by {extId}.", + ReadContext: DatasourceNutanixVolumeGroupIscsiClientsV2Read, + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "The external identifier of the Volume Group.", + Type: schema.TypeString, + Required: true, + }, + "page": { + Description: "A URL query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. Any number out of this range might lead to no results.", + Type: schema.TypeInt, + Optional: true, + }, + "limit": { + Description: "A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set.", + Type: schema.TypeInt, + Optional: true, + }, + "filter": { + Description: "A URL query parameter that allows clients to filter a collection of resources. The expression specified with $filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Expression specified with the $filter must conform to the OData V4.01 URL conventions. For example, filter '$filter=name eq 'karbon-ntnx-1.0' would filter the result on cluster name 'karbon-ntnx1.0', filter '$filter=startswith(name, 'C')' would filter on cluster name starting with 'C'. The filter can be applied to the following fields: clusterReference, extId", + Type: schema.TypeString, + Optional: true, + }, + "orderby": { + Description: "A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default. For example, '$orderby=templateName desc' would get all templates sorted by templateName in descending order. The orderby can be applied to the following fields: clusterReference, extId", + Type: schema.TypeString, + Optional: true, + }, + "expand": { + Description: "A URL query parameter that allows clients to request related resources when a resource that satisfies a particular request is retrieved. Each expanded item is evaluated relative to the entity containing the property being expanded. Other query options can be applied to an expanded property by appending a semicolon-separated list of query options, enclosed in parentheses, to the property name. Permissible system query options are $filter, $select and $orderby. The following expansion keys are supported. The expand can be applied to the following fields: iscsiClient", + Type: schema.TypeString, + Optional: true, + }, + "select": { + Description: "A URL query parameter that allows clients to request a specific set of properties for each entity or complex type. Expression specified with the $select must conform to the OData V4.01 URL conventions. If a $select expression consists of a single select item that is an asterisk (i.e., *), then all properties on the matching resource will be returned. The select can be applied to the following fields: clusterReference, extId", + Type: schema.TypeString, + Optional: true, + }, + "iscsi_clients": { + Description: "List of the iSCSI attachments associated with the given Volume Group.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "tenant_id": { + Description: "A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server).", + Type: schema.TypeString, + Computed: true, + }, + "ext_id": { + Description: "A globally unique identifier of an instance that is suitable for external consumption.", + Type: schema.TypeString, + Computed: true, + }, + "links": { + Description: "A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Description: "The URL at which the entity described by the link can be accessed.", + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Description: "A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of \"self\" identifies the URL for the object.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "cluster_reference": { + Description: "The UUID of the cluster that will host the iSCSI client. This field is read-only.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func DatasourceNutanixVolumeGroupIscsiClientsV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + var filter, orderBy, expand, selects *string + var page, limit *int + + volumeGroupExtID := d.Get("ext_id") + + // initialize the query parameters + if pagef, ok := d.GetOk("page"); ok { + page = utils.IntPtr(pagef.(int)) + } else { + page = nil + } + if limitf, ok := d.GetOk("limit"); ok { + limit = utils.IntPtr(limitf.(int)) + } else { + limit = nil + } + if filterf, ok := d.GetOk("filter"); ok { + filter = utils.StringPtr(filterf.(string)) + } else { + filter = nil + } + if order, ok := d.GetOk("order_by"); ok { + orderBy = utils.StringPtr(order.(string)) + } else { + orderBy = nil + } + if expandf, ok := d.GetOk("expand"); ok { + expand = utils.StringPtr(expandf.(string)) + } else { + expand = nil + } + if selectf, ok := d.GetOk("select"); ok { + selects = utils.StringPtr(selectf.(string)) + } else { + selects = nil + } + + // get the volume group iscsi clients + resp, err := conn.VolumeAPIInstance.ListExternalIscsiAttachmentsByVolumeGroupId(utils.StringPtr(volumeGroupExtID.(string)), page, limit, filter, orderBy, expand, selects) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching External Iscsi Attachments : %v", errorMessage["message"]) + } + + diskResp := resp.Data + + // extract the volume groups data from the response + if diskResp != nil { + + // set the volume groups iscsi clients data in the terraform resource + if err := d.Set("iscsi_clients", flattenVolumeIscsiClientsEntities(diskResp.GetValue().([]volumesClient.IscsiClientAttachment))); err != nil { + return diag.FromErr(err) + } + } + d.SetId(resource.UniqueId()) + return nil + +} + +func flattenVolumeIscsiClientsEntities(iscsiClientAttachments []volumesClient.IscsiClientAttachment) []interface{} { + if len(iscsiClientAttachments) > 0 { + iscsiClientList := make([]interface{}, len(iscsiClientAttachments)) + + for k, v := range iscsiClientAttachments { + iscsiClient := make(map[string]interface{}) + + if v.ExtId != nil { + iscsiClient["ext_id"] = v.ExtId + } + + if v.ClusterReference != nil { + iscsiClient["cluster_reference"] = v.ClusterReference + } + + iscsiClientList[k] = iscsiClient + + } + return iscsiClientList + } + return nil +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_iscsi_clients_v2_test.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_iscsi_clients_v2_test.go new file mode 100644 index 000000000..e23f9caa2 --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_iscsi_clients_v2_test.go @@ -0,0 +1,53 @@ +package volumesv2_test + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const dataSourceVolumeGroupIscsiClients = "data.nutanix_volume_group_iscsi_clients_v2.vg_iscsi_test" + +func TestAccNutanixVolumeGroupIscsiClientsV2_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("terraform-test-volume-group-disk-%d", r) + desc := "terraform test volume group disk description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupIscsiClientsV2Config(filepath, name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceVolumeGroupIscsiClients, "iscsi_clients.#"), + testAccCheckResourceAttrListNotEmpty(dataSourceVolumeGroupIscsiClients, "iscsi_clients", "ext_id"), + resource.TestCheckResourceAttrSet(dataSourceVolumeGroupIscsiClients, "iscsi_clients.0.cluster_reference"), + resource.TestCheckResourceAttrSet(dataSourceVolumeGroupIscsiClients, "iscsi_clients.0.ext_id"), + ), + }, + }, + }) +} + +func testAccVolumeGroupIscsiClientsV2Config(filepath, name, desc string) string { + return testAccVolumeGroupResourceConfig(filepath, name, desc) + ` + resource "nutanix_volume_group_iscsi_client_v2" "vg_iscsi_test" { + vg_ext_id = resource.nutanix_volume_group_v2.test.id + ext_id = local.volumes.iscsi_client.ext_id + iscsi_initiator_name = local.volumes.iscsi_client.initiator_name + depends_on = [ resource.nutanix_volume_group_v2.test ] + } + + data "nutanix_volume_group_iscsi_clients_v2" "vg_iscsi_test" { + ext_id= resource.nutanix_volume_group_v2.test.id + depends_on = [ resource.nutanix_volume_group_iscsi_client_v2.vg_iscsi_test , resource.nutanix_volume_group_v2.test] + } +` +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_v2.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_v2.go new file mode 100644 index 000000000..c64e78a7b --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_v2.go @@ -0,0 +1,201 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// Get a Volume Group. +func DatasourceNutanixVolumeGroupV2() *schema.Resource { + return &schema.Resource{ + Description: "Query the Volume Group identified by {extId}.", + ReadContext: DatasourceNutanixVolumeGroupV2Read, + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "The external identifier of the Volume Group.", + Type: schema.TypeString, + Required: true, + }, + "tenant_id": { + Description: "A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server).", + Type: schema.TypeString, + Computed: true, + }, + "links": { + Description: "A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Description: "The URL at which the entity described by the link can be accessed.", + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Description: "A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of \"self\" identifies the URL for the object.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "name": { + Description: "Volume Group name. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "Volume Group description. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "should_load_balance_vm_attachments": { + Description: "Indicates whether to enable Volume Group load balancing for VM attachments. This cannot be enabled if there are iSCSI client attachments already associated with the Volume Group, and vice-versa. This is an optional field.", + Type: schema.TypeBool, + Computed: true, + }, + "sharing_status": { + Description: "Indicates whether the Volume Group can be shared across multiple iSCSI initiators. The mode cannot be changed from SHARED to NOT_SHARED on a Volume Group with multiple attachments. Similarly, a Volume Group cannot be associated with more than one attachment as long as it is in exclusive mode. This is an optional field. Possible values [SHARED, NOT_SHARED]", + Type: schema.TypeString, + Computed: true, + }, + "target_name": { + Description: "Name of the external client target that will be visible and accessible to the client. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "enabled_authentications": { + Description: "The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided.", + Type: schema.TypeString, + Computed: true, + }, + "iscsi_features": { + Description: "iSCSI specific settings for the Volume Group. This is an optional field.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled_authentications": { + Description: "The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "created_by": { + Description: "Service/user who created this Volume Group. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "cluster_reference": { + Description: "The UUID of the cluster that will host the Volume Group. This is a mandatory field for creating a Volume Group on Prism Central.", + Type: schema.TypeString, + Computed: true, + }, + "storage_features": { + Description: "Storage optimization features which must be enabled on the Volume Group. This is an optional field.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flash_mode": { + Description: "Once configured, this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_enabled": { + Description: "Indicates whether the flash mode is enabled for the Volume Group.", + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "usage_type": { + Description: "Expected usage type for the Volume Group. This is an indicative hint on how the caller will consume the Volume Group. This is an optional", + Type: schema.TypeString, + Computed: true, + }, + "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, + Computed: true, + }, + }, + } +} + +func DatasourceNutanixVolumeGroupV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + extID := d.Get("ext_id") + + resp, err := conn.VolumeAPIInstance.GetVolumeGroupById(utils.StringPtr(extID.(string))) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching volume : %v", errorMessage["message"]) + } + + getResp := resp.Data.GetValue().(volumesClient.VolumeGroup) + + if err := d.Set("name", getResp.Name); err != nil { + return diag.FromErr(err) + } + if err := d.Set("description", getResp.Description); err != nil { + return diag.FromErr(err) + } + if err := d.Set("should_load_balance_vm_attachments", getResp.ShouldLoadBalanceVmAttachments); err != nil { + return diag.FromErr(err) + } + if err := d.Set("sharing_status", flattenSharingStatus(getResp.SharingStatus)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("target_name", getResp.TargetName); err != nil { + return diag.FromErr(err) + } + if err := d.Set("enabled_authentications", flattenEnabledAuthentications(getResp.EnabledAuthentications)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("iscsi_features", flattenIscsiFeatures(getResp.IscsiFeatures)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("created_by", getResp.CreatedBy); err != nil { + return diag.FromErr(err) + } + if err := d.Set("cluster_reference", getResp.ClusterReference); err != nil { + return diag.FromErr(err) + } + if err := d.Set("storage_features", flattenStorageFeatures(getResp.StorageFeatures)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("usage_type", flattenUsageType(getResp.UsageType)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("is_hidden", getResp.IsHidden); err != nil { + return diag.FromErr(err) + } + + d.SetId(*getResp.ExtId) + return nil +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_v2_test.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_v2_test.go new file mode 100644 index 000000000..b9ba4bee9 --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_v2_test.go @@ -0,0 +1,51 @@ +package volumesv2_test + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const dataSourceVolumeGroup = "data.nutanix_volume_group_v2.test" + +func TestAccNutanixVolumeGroupV2DataSource_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("terraform-test-volume-group-disk-%d", r) + desc := "terraform test volume group disk description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupDataSourceConfig(filepath, name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceVolumeGroup, "name", name), + resource.TestCheckResourceAttr(dataSourceVolumeGroup, "description", desc), + resource.TestCheckResourceAttr(dataSourceVolumeGroup, "should_load_balance_vm_attachments", "false"), + resource.TestCheckResourceAttr(dataSourceVolumeGroup, "sharing_status", "SHARED"), + resource.TestCheckResourceAttr(dataSourceVolumeGroup, "created_by", "admin"), + resource.TestCheckResourceAttr(dataSourceVolumeGroup, "iscsi_features.0.enabled_authentications", "CHAP"), + resource.TestCheckResourceAttr(dataSourceVolumeGroup, "storage_features.0.flash_mode.0.is_enabled", "true"), + resource.TestCheckResourceAttr(dataSourceVolumeGroup, "usage_type", "USER"), + resource.TestCheckResourceAttr(dataSourceVolumeGroup, "is_hidden", "false"), + ), + }, + }, + }) +} + +func testAccVolumeGroupDataSourceConfig(filepath, name, desc string) string { + return testAccVolumeGroupResourceConfig(filepath, name, desc) + ` + data "nutanix_volume_group_v2" "test" { + ext_id = resource.nutanix_volume_group_v2.test.id + depends_on = [resource.nutanix_volume_group_v2.test] + } + ` +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_vms_v2.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_vms_v2.go new file mode 100644 index 000000000..9a09d59da --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_vms_v2.go @@ -0,0 +1,169 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// List all the VM attachments for a Volume Group. +func DataSourceNutanixVolumeGroupVmsV2() *schema.Resource { + return &schema.Resource{ + Description: "Query the list of VM attachments for a Volume Group identified by {extId}.", + ReadContext: DataSourceNutanixVolumeGroupVmsV4Read, + + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "The external identifier of the volume group.", + Type: schema.TypeString, + Required: true, + }, + "page": { + Description: "A URL query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. Any number out of this range might lead to no results.", + Type: schema.TypeInt, + Optional: true, + }, + "limit": { + Description: "A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set.", + Type: schema.TypeInt, + Optional: true, + }, + "filter": { + Description: "A URL query parameter that allows clients to filter a collection of resources. The expression specified with $filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Expression specified with the $filter must conform to the OData V4.01 URL conventions. For example, filter '$filter=name eq 'karbon-ntnx-1.0' would filter the result on cluster name 'karbon-ntnx1.0', filter '$filter=startswith(name, 'C')' would filter on cluster name starting with 'C'. The filter can be applied to the following fields: extId", + Type: schema.TypeString, + Optional: true, + }, + "orderby": { + Description: "A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default. For example, '$orderby=templateName desc' would get all templates sorted by templateName in descending order. The orderby can be applied to the following fields: extId", + Type: schema.TypeString, + Optional: true, + }, + "vms_attachments": { + Description: "List of Volume Groups.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "tenant_id": { + Description: "A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server).", + Type: schema.TypeString, + Computed: true, + }, + "ext_id": { + Description: "A globally unique identifier of an instance that is suitable for external consumption.", + Type: schema.TypeString, + Computed: true, + }, + "links": { + Description: "A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Description: "The URL at which the entity described by the link can be accessed.", + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Description: "A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of \"self\" identifies the URL for the object.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +// DataSourceNutanixVolumeGroupVmsV4Read +func DataSourceNutanixVolumeGroupVmsV4Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + volumeGroupExtID := d.Get("ext_id") + + var filter, orderBy *string + var page, limit *int + + // initialize the query parameters + if pagef, ok := d.GetOk("page"); ok { + page = utils.IntPtr(pagef.(int)) + } else { + page = nil + } + if limitf, ok := d.GetOk("limit"); ok { + limit = utils.IntPtr(limitf.(int)) + } else { + limit = nil + } + if filterf, ok := d.GetOk("filter"); ok { + filter = utils.StringPtr(filterf.(string)) + } else { + filter = nil + } + if order, ok := d.GetOk("order_by"); ok { + orderBy = utils.StringPtr(order.(string)) + } else { + orderBy = nil + } + + // get the volume groups response + resp, err := conn.VolumeAPIInstance.ListVmAttachmentsByVolumeGroupId(utils.StringPtr(volumeGroupExtID.(string)), page, limit, filter, orderBy) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching volumes : %v", errorMessage["message"]) + } + + vmsAttachmentsResp := resp.Data + + if vmsAttachmentsResp != nil { + // set the volume groups data in the terraform resource + if err := d.Set("vms_attachments", flattenVolumeGroupVmsEntities(vmsAttachmentsResp.GetValue().([]volumesClient.VmAttachment))); err != nil { + return diag.FromErr(err) + } + + } else { + // set the volume groups data in the terraform resource + d.Set("volumes", make([]volumesClient.VolumeGroup, 0)) + } + + d.SetId(resource.UniqueId()) + return nil +} + +func flattenVolumeGroupVmsEntities(vms []volumesClient.VmAttachment) []interface{} { + if len(vms) > 0 { + vmAttachmentList := make([]interface{}, len(vms)) + + for k, v := range vms { + vmAttachment := make(map[string]interface{}) + + if v.ExtId != nil { + vmAttachment["ext_id"] = v.ExtId + } + vmAttachmentList[k] = vmAttachment + + } + return vmAttachmentList + } + return nil +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_vms_v2_test.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_vms_v2_test.go new file mode 100644 index 000000000..b5cb04c2c --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_group_vms_v2_test.go @@ -0,0 +1,50 @@ +package volumesv2_test + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const dataSourceVolumeGroupsVmsAttachments = "data.nutanix_volume_group_vms_v2.test" + +func TestAccNutanixVolumeGroupVmsAttachmentsV2DataSource_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("terraform-test-volume-group-disk-%d", r) + desc := "terraform test volume group disk description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupVmsAttachmentsDataSourceConfig(filepath, name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceVolumeGroupsVmsAttachments, "vms_attachments.#"), + resource.TestCheckResourceAttrSet(dataSourceVolumeGroupsVmsAttachments, "vms_attachments.0.ext_id"), + ), + }, + }, + }) +} + +func testAccVolumeGroupVmsAttachmentsDataSourceConfig(filepath, name, desc string) string { + return testAccVolumeGroupResourceConfig(filepath, name, desc) + ` + resource "nutanix_volume_group_vm_v2" "test" { + volume_group_ext_id = resource.nutanix_volume_group_v2.test.id + vm_ext_id = local.volumes.vm_ext_id + depends_on = [resource.nutanix_volume_group_v2.test] + } + + data "nutanix_volume_group_vms_v2" "test" { + ext_id = resource.nutanix_volume_group_v2.test.id + depends_on = [ resource.nutanix_volume_group_vm_v2.test ] + } + ` +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_groups_v2.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_groups_v2.go new file mode 100644 index 000000000..1eb5d03ac --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_groups_v2.go @@ -0,0 +1,406 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + volumesClientResponse "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/common/v1/response" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// List all the Volume Groups. +func DatasourceNutanixVolumeGroupsV2() *schema.Resource { + return &schema.Resource{ + Description: "Query the list of Volume Groups.", + ReadContext: DatasourceNutanixVolumeGroupsV2Read, + Schema: map[string]*schema.Schema{ + "page": { + Description: "A URL query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. Any number out of this range might lead to no results.", + Type: schema.TypeInt, + Optional: true, + }, + "limit": { + Description: "A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set.", + Type: schema.TypeInt, + Optional: true, + }, + "filter": { + Description: "A URL query parameter that allows clients to filter a collection of resources. The expression specified with $filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Expression specified with the $filter must conform to the OData V4.01 URL conventions. For example, filter '$filter=name eq 'karbon-ntnx-1.0' would filter the result on cluster name 'karbon-ntnx1.0', filter '$filter=startswith(name, 'C')' would filter on cluster name starting with 'C'. The filter can be applied to the following fields: clusterReference, extId, name", + Type: schema.TypeString, + Optional: true, + }, + "orderby": { + Description: "A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default. For example, '$orderby=templateName desc' would get all templates sorted by templateName in descending order. The orderby can be applied to the following fields: clusterReference, extId, name", + Type: schema.TypeString, + Optional: true, + }, + "expand": { + Description: "A URL query parameter that allows clients to request related resources when a resource that satisfies a particular request is retrieved. Each expanded item is evaluated relative to the entity containing the property being expanded. Other query options can be applied to an expanded property by appending a semicolon-separated list of query options, enclosed in parentheses, to the property name. Permissible system query options are $filter, $select and $orderby. The following expansion keys are supported. The expand can be applied to the following fields: clusterReference, metadata", + Type: schema.TypeString, + Optional: true, + }, + "select": { + Description: "A URL query parameter that allows clients to request a specific set of properties for each entity or complex type. Expression specified with the $select must conform to the OData V4.01 URL conventions. If a $select expression consists of a single select item that is an asterisk (i.e., *), then all properties on the matching resource will be returned. The select can be applied to the following fields: clusterReference, extId, name", + Type: schema.TypeString, + Optional: true, + }, + "volumes": { + Description: "List of Volume Groups.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "tenant_id": { + Description: "A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server).", + Type: schema.TypeString, + Computed: true, + }, + "ext_id": { + Description: "A globally unique identifier of an instance that is suitable for external consumption.", + Type: schema.TypeString, + Computed: true, + }, + "links": { + Description: "A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Description: "The URL at which the entity described by the link can be accessed.", + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Description: "A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of \"self\" identifies the URL for the object.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "name": { + Description: "Volume Group name. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "Volume Group description. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "should_load_balance_vm_attachments": { + Description: "Indicates whether to enable Volume Group load balancing for VM attachments. This cannot be enabled if there are iSCSI client attachments already associated with the Volume Group, and vice-versa. This is an optional field.", + Type: schema.TypeBool, + Computed: true, + }, + "sharing_status": { + Description: "Indicates whether the Volume Group can be shared across multiple iSCSI initiators. The mode cannot be changed from SHARED to NOT_SHARED on a Volume Group with multiple attachments. Similarly, a Volume Group cannot be associated with more than one attachment as long as it is in exclusive mode. This is an optional field", + Type: schema.TypeString, + Computed: true, + }, + "target_name": { + Description: "Name of the external client target that will be visible and accessible to the client. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "enabled_authentications": { + Description: "The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided.", + Type: schema.TypeString, + Computed: true, + }, + "iscsi_features": { + Description: "iSCSI specific settings for the Volume Group. This is an optional field.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled_authentications": { + Description: "The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "created_by": { + Description: "Service/user who created this Volume Group. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "cluster_reference": { + Description: "The UUID of the cluster that will host the Volume Group. This is a mandatory field for creating a Volume Group on Prism Central.", + Type: schema.TypeString, + Computed: true, + }, + "storage_features": { + Description: "Storage optimization features which must be enabled on the Volume Group. This is an optional field.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flash_mode": { + Description: "Once configured, this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_enabled": { + Description: "Indicates whether the flash mode is enabled for the Volume Group.", + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "usage_type": { + Description: "Expected usage type for the Volume Group. This is an indicative hint on how the caller will consume the Volume Group. This is an optional field.", + Type: schema.TypeString, + Computed: true, + }, + "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, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func DatasourceNutanixVolumeGroupsV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + var filter, orderBy, expand, selects *string + var page, limit *int + + // initialize the query parameters + if pagef, ok := d.GetOk("page"); ok { + page = utils.IntPtr(pagef.(int)) + } else { + page = nil + } + if limitf, ok := d.GetOk("limit"); ok { + limit = utils.IntPtr(limitf.(int)) + } else { + limit = nil + } + if filterf, ok := d.GetOk("filter"); ok { + filter = utils.StringPtr(filterf.(string)) + } else { + filter = nil + } + if order, ok := d.GetOk("order_by"); ok { + orderBy = utils.StringPtr(order.(string)) + } else { + orderBy = nil + } + if expandf, ok := d.GetOk("expand"); ok { + expand = utils.StringPtr(expandf.(string)) + } else { + expand = nil + } + if selectf, ok := d.GetOk("select"); ok { + selects = utils.StringPtr(selectf.(string)) + } else { + selects = nil + } + + // get the volume groups response + resp, err := conn.VolumeAPIInstance.ListVolumeGroups(page, limit, filter, orderBy, expand, selects) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching volumes : %v", errorMessage["message"]) + } + + volumesResp := resp.Data + + if volumesResp != nil { + // set the volume groups data in the terraform resource + if err := d.Set("volumes", flattenVolumesEntities(volumesResp.GetValue().([]volumesClient.VolumeGroup))); err != nil { + return diag.FromErr(err) + } + + } else { + // set the volume groups data in the terraform resource + d.Set("volumes", make([]volumesClient.VolumeGroup, 0)) + } + + d.SetId(resource.UniqueId()) + return nil +} + +func flattenVolumesEntities(volumeGroups []volumesClient.VolumeGroup) []interface{} { + if len(volumeGroups) > 0 { + volumeGroupList := make([]interface{}, len(volumeGroups)) + + for k, v := range volumeGroups { + volumeGroup := make(map[string]interface{}) + + if v.TenantId != nil { + volumeGroup["tenant_id"] = v.TenantId + } + if v.ExtId != nil { + volumeGroup["ext_id"] = v.ExtId + } + if v.Links != nil { + volumeGroup["links"] = flattenLinks(v.Links) + } + if v.Name != nil { + volumeGroup["name"] = v.Name + } + if v.Description != nil { + volumeGroup["description"] = v.Description + } + if v.ShouldLoadBalanceVmAttachments != nil { + volumeGroup["should_load_balance_vm_attachments"] = v.ShouldLoadBalanceVmAttachments + } + if v.SharingStatus != nil { + volumeGroup["sharing_status"] = flattenSharingStatus(v.SharingStatus) + } + if v.TargetName != nil { + volumeGroup["target_name"] = v.TargetName + } + if v.EnabledAuthentications != nil { + volumeGroup["enabled_authentications"] = flattenEnabledAuthentications(v.EnabledAuthentications) + } + if v.IscsiFeatures != nil { + volumeGroup["iscsi_features"] = flattenIscsiFeatures(v.IscsiFeatures) + } + if v.CreatedBy != nil { + volumeGroup["created_by"] = v.CreatedBy + } + if v.ClusterReference != nil { + volumeGroup["cluster_reference"] = v.ClusterReference + } + if v.StorageFeatures != nil { + volumeGroup["storage_features"] = flattenStorageFeatures(v.StorageFeatures) + } + if v.UsageType != nil { + volumeGroup["usage_type"] = flattenUsageType(v.UsageType) + } + if v.IsHidden != nil { + volumeGroup["is_hidden"] = v.IsHidden + } + + volumeGroupList[k] = volumeGroup + + } + return volumeGroupList + } + return nil +} + +func flattenLinks(apiLinks []volumesClientResponse.ApiLink) []map[string]interface{} { + if len(apiLinks) > 0 { + apiLinkList := make([]map[string]interface{}, len(apiLinks)) + + for k, v := range apiLinks { + links := map[string]interface{}{} + if v.Href != nil { + links["href"] = v.Href + } + if v.Rel != nil { + links["rel"] = v.Rel + } + + apiLinkList[k] = links + } + return apiLinkList + } + return nil +} + +func flattenSharingStatus(sharingStatus *volumesClient.SharingStatus) string { + var sharingStatusStr string + if sharingStatus != nil { + if *sharingStatus == volumesClient.SharingStatus(2) { + sharingStatusStr = "SHARED" + } + if *sharingStatus == volumesClient.SharingStatus(3) { + sharingStatusStr = "NOT_SHARED" + } + } + return sharingStatusStr +} + +func flattenEnabledAuthentications(authenticationType *volumesClient.AuthenticationType) string { + var enabledAuthentications string + if authenticationType != nil { + if *authenticationType == volumesClient.AuthenticationType(2) { + enabledAuthentications = "CHAP" + } + if *authenticationType == volumesClient.AuthenticationType(3) { + enabledAuthentications = "NONE" + } + } + return enabledAuthentications +} + +func flattenIscsiFeatures(iscsiFeatures *volumesClient.IscsiFeatures) []map[string]interface{} { + if iscsiFeatures != nil { + enabledAuthentications := make(map[string]interface{}) + enabledAuthentications["enabled_authentications"] = flattenEnabledAuthentications(iscsiFeatures.EnabledAuthentications) + return []map[string]interface{}{enabledAuthentications} + } + return nil +} + +func flattenFlashMode(flashMode *volumesClient.FlashMode) []map[string]interface{} { + if flashMode != nil { + flashModeList := make([]map[string]interface{}, 0) + isEnabled := make(map[string]interface{}) + isEnabled["is_enabled"] = flashMode.IsEnabled + flashModeList = append(flashModeList, isEnabled) + return flashModeList + } + return nil +} +func flattenStorageFeatures(storageFeatures *volumesClient.StorageFeatures) []map[string]interface{} { + if storageFeatures != nil { + storageFeaturesList := make([]map[string]interface{}, 0) + flashMode := make(map[string]interface{}) + flashMode["flash_mode"] = flattenFlashMode(storageFeatures.FlashMode) + storageFeaturesList = append(storageFeaturesList, flashMode) + return storageFeaturesList + } + return nil +} + +func flattenUsageType(usageType *volumesClient.UsageType) string { + var usageTypeStr string + if usageType != nil { + if *usageType == volumesClient.UsageType(2) { + usageTypeStr = "USER" + } + if *usageType == volumesClient.UsageType(3) { + usageTypeStr = "INTERNAL" + } + if *usageType == volumesClient.UsageType(4) { + usageTypeStr = "TEMPORARY" + } + if *usageType == volumesClient.UsageType(5) { + usageTypeStr = "BACKUP_TARGET" + } + } + return usageTypeStr +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_groups_v2_test.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_groups_v2_test.go new file mode 100644 index 000000000..1cc4e1d83 --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_groups_v2_test.go @@ -0,0 +1,142 @@ +package volumesv2_test + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "os" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const dataSourceVolumeGroups = "data.nutanix_volume_groups_v2.test" + +func TestAccNutanixVolumeGroupsV2DataSource_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("terraform-test-volume-group-%d", r) + desc := "terraform test volume group description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupsDataSourceConfig(filepath, name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceVolumeGroups, "volumes.#"), + resource.TestCheckResourceAttrSet(dataSourceVolumeGroups, "volumes.0.name"), + ), + }, + }, + }) +} + +func TestAccNutanixVolumeGroupsV4DataSource_WithFilter(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("terraform-test-volume-group-%d", r) + desc := "terraform test volume group description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupsDataSourceWithFilter(filepath, name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceVolumeGroups, "volumes.0.name", name), + resource.TestCheckResourceAttr(dataSourceVolumeGroups, "volumes.0.description", desc), + resource.TestCheckResourceAttr(dataSourceVolumeGroups, "volumes.0.sharing_status", "SHARED"), + resource.TestCheckResourceAttr(dataSourceVolumeGroups, "volumes.0.created_by", "admin"), + resource.TestCheckResourceAttr(dataSourceVolumeGroups, "volumes.0.iscsi_features.0.enabled_authentications", "CHAP"), + resource.TestCheckResourceAttr(dataSourceVolumeGroups, "volumes.0.storage_features.0.flash_mode.0.is_enabled", "true"), + resource.TestCheckResourceAttr(dataSourceVolumeGroups, "volumes.0.usage_type", "USER"), + resource.TestCheckResourceAttr(dataSourceVolumeGroups, "volumes.0.is_hidden", "false"), + ), + }, + }, + }) +} + +func TestAccNutanixVolumeGroupsV4DataSource_WithLimit(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("terraform-test-volume-group-%d", r) + desc := "terraform test volume group description" + limit := 3 + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupsDataSourceWithLimit(name, desc, limit), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourceAttrListNotEmpty(dataSourceVolumeGroups, "volumes", "name"), + resource.TestCheckResourceAttr(dataSourceVolumeGroups, "volumes.#", strconv.Itoa(limit)), + ), + }, + }, + }) +} + +func testAccVolumeGroupsDataSourceConfig(filepath, name, desc string) string { + return testAccVolumeGroupResourceConfig(filepath, name, desc) + ` + data "nutanix_volume_groups_v2" "test" { + depends_on = [resource.nutanix_volume_group_v2.test] + } + ` +} + +func testAccVolumeGroupsDataSourceWithFilter(filepath, name, desc string) string { + return testAccVolumeGroupResourceConfig(filepath, name, desc) + fmt.Sprintf(` + data "nutanix_volume_groups_v2" "test" { + filter = "name eq '%s'" + depends_on = [resource.nutanix_volume_group_v2.test] + } + `, name) +} + +func testAccVolumeGroupsDataSourceWithLimit(name, desc string, limit int) string { + return fmt.Sprintf( + ` + data "nutanix_clusters" "clusters" {} + + locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] + } + + resource "nutanix_volume_group_v2" "test1" { + name = "%[1]s_1" + cluster_reference = local.cluster1 + description = "%[2]s" + } + + resource "nutanix_volume_group_v2" "test2" { + name = "%[1]s_2" + cluster_reference = local.cluster1 + description = "%[2]s" + depends_on = [resource.nutanix_volume_group_v2.test1] + } + + resource "nutanix_volume_group_v2" "test3" { + name = "%[1]s_3" + cluster_reference = local.cluster1 + description = "%[2]s" + depends_on = [resource.nutanix_volume_group_v2.test2] + } + + data "nutanix_volume_groups_v2" "test" { + filter = "startswith(name, '%[1]s')" + limit = %[3]d + depends_on = [resource.nutanix_volume_group_v2.test3] + } + `, name, desc, limit) +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_client_v2.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_client_v2.go new file mode 100644 index 000000000..37b25dd9a --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_client_v2.go @@ -0,0 +1,170 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// Fetch an iSCSI client details. +func DatasourceNutanixVolumeIscsiClientV2() *schema.Resource { + return &schema.Resource{ + Description: "Fetches the iSCSI client details identified by {extId}.", + ReadContext: DatasourceNutanixVolumeIscsiClientV2Read, + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "The external identifier of the iSCSI client.", + Type: schema.TypeString, + Required: true, + }, + "tenant_id": { + Description: "A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server)", + Type: schema.TypeString, + Computed: true, + }, + "links": { + Description: "A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Description: "The URL at which the entity described by the link can be accessed.", + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Description: "A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of \"self\" identifies the URL for the object.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "iscsi_initiator_name": { + Description: "iSCSI initiator name. During the attach operation, exactly one of iscsiInitiatorName and iscsiInitiatorNetworkId must be specified. This field is immutable.", + Type: schema.TypeString, + Computed: true, + }, + "iscsi_initiator_network_id": { + Description: "An unique address that identifies a device on the internet or a local network in IPv4/IPv6 format or a Fully Qualified Domain Name.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForIpV4ValuePrefixLength(), + "ipv6": SchemaForIpV6ValuePrefixLength(), + "fqdn": { + Description: "A fully qualified domain name that specifies its exact location in the tree hierarchy of the Domain Name System.", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "enabled_authentications": { + Description: "The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided.", + Type: schema.TypeString, + Computed: true, + }, + "attached_targets": { + Description: "with each iSCSI target corresponding to the iSCSI client)", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "num_virtual_targets": { + Description: "Number of virtual targets generated for the iSCSI target. This field is immutable.", + Type: schema.TypeInt, + Computed: true, + }, + "iscsi_target_name": { + Description: "Name of the iSCSI target that the iSCSI client is connected to. This is a read-only field.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "cluster_reference": { + Description: "The UUID of the cluster that will host the iSCSI client. This field is read-only.", + Type: schema.TypeString, + Computed: true, + }, + "attachment_site": { + Description: "The site where the Volume Group attach operation should be processed. This is an optional field. This field may only be set if Metro DR has been configured for this Volume Group.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func DatasourceNutanixVolumeIscsiClientV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + extId := d.Get("ext_id") + + // get the volume group iscsi clients + resp, err := conn.IscsiClientAPIInstance.GetIscsiClientById(utils.StringPtr(extId.(string))) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching Iscsi Client : %v", errorMessage["message"]) + } + + getResp := resp.Data.GetValue().(volumesClient.IscsiClient) + + if err := d.Set("links", flattenLinks(getResp.Links)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("ext_id", getResp.ExtId); err != nil { + return diag.FromErr(err) + } + if err := d.Set("iscsi_initiator_name", getResp.IscsiInitiatorName); err != nil { + return diag.FromErr(err) + } + if err := d.Set("iscsi_initiator_network_id", flattenIscsiInitiatorNetworkId(getResp.IscsiInitiatorNetworkId)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("enabled_authentications", getResp.EnabledAuthentications); err != nil { + return diag.FromErr(err) + } + if err := d.Set("attached_targets", flattenAttachedTargets(getResp.AttachedTargets)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("cluster_reference", getResp.ClusterReference); err != nil { + return diag.FromErr(err) + } + if err := d.Set("attachment_site", getResp.AttachmentSite); err != nil { + return diag.FromErr(err) + } + + d.SetId(resource.UniqueId()) + return nil + +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_client_v2_test.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_client_v2_test.go new file mode 100644 index 000000000..5b9e3631f --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_client_v2_test.go @@ -0,0 +1,44 @@ +package volumesv2_test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const dataSourceVolumeIscsiClient = "data.nutanix_volume_iscsi_client_v2.v_iscsi" + +func TestAccNutanixVolumeIscsiClientV2_Basic(t *testing.T) { + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeIscsiClientV2Config(filepath), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceVolumeIscsiClient, "iscsi_initiator_name"), + resource.TestCheckResourceAttr(dataSourceVolumeIscsiClient, "ext_id", testVars.Volumes.IscsiClient.ExtId), + ), + }, + }, + }) +} + +func testAccVolumeIscsiClientV2Config(filepath string) string { + return fmt.Sprintf(` + locals { + config = (jsondecode(file("%s"))) + volumes = local.config.volumes + } + + data "nutanix_volume_iscsi_client_v2" "v_iscsi" { + ext_id = local.volumes.iscsi_client.ext_id + }`, filepath) +} diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_clients_v2.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_clients_v2.go new file mode 100644 index 000000000..311779dfb --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_clients_v2.go @@ -0,0 +1,385 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/common/v1/config" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// List all the iSCSI clients. +func DatasourceNutanixVolumeIscsiClientsV2() *schema.Resource { + return &schema.Resource{ + Description: "Fetches the list of iSCSI clients.", + ReadContext: DatasourceNutanixVolumeIscsiClientsV2Read, + Schema: map[string]*schema.Schema{ + "page": { + Description: "A URL query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. Any number out of this range might lead to no results.", + Type: schema.TypeInt, + Optional: true, + }, + "limit": { + Description: "A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set.", + Type: schema.TypeInt, + Optional: true, + }, + "filter": { + Description: "A URL query parameter that allows clients to filter a collection of resources. The expression specified with $filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Expression specified with the $filter must conform to the OData V4.01 URL conventions. For example, filter '$filter=name eq 'karbon-ntnx-1.0' would filter the result on cluster name 'karbon-ntnx1.0', filter '$filter=startswith(name, 'C')' would filter on cluster name starting with 'C'. The filter can be applied to the following fields: clusterReference, extId", + Type: schema.TypeString, + Optional: true, + }, + "orderby": { + Description: "A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default. For example, '$orderby=templateName desc' would get all templates sorted by templateName in descending order. The orderby can be applied to the following fields: clusterReference, extId", + Type: schema.TypeString, + Optional: true, + }, + "expand": { + Description: "A URL query parameter that allows clients to request related resources when a resource that satisfies a particular request is retrieved. Each expanded item is evaluated relative to the entity containing the property being expanded. Other query options can be applied to an expanded property by appending a semicolon-separated list of query options, enclosed in parentheses, to the property name. Permissible system query options are $filter, $select and $orderby. The following expansion keys are supported. The expand can be applied to the following fields: cluster", + Type: schema.TypeString, + Optional: true, + }, + "select": { + Description: "A URL query parameter that allows clients to request a specific set of properties for each entity or complex type. Expression specified with the $select must conform to the OData V4.01 URL conventions. If a $select expression consists of a single select item that is an asterisk (i.e., *), then all properties on the matching resource will be returned. The select can be applied to the following fields: clusterReference, extId", + Type: schema.TypeString, + Optional: true, + }, + "iscsi_clients": { + Description: "List of iSCSI clients.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "tenant_id": { + Description: "A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server).", + Type: schema.TypeString, + Computed: true, + }, + "ext_id": { + Description: "A globally unique identifier of an instance that is suitable for external consumption.", + Type: schema.TypeString, + Computed: true, + }, + "links": { + Description: "A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Description: "The URL at which the entity described by the link can be accessed.", + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Description: "A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of \"self\" identifies the URL for the object.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "iscsi_initiator_name": { + Description: "iSCSI initiator name. During the attach operation, exactly one of iscsiInitiatorName and iscsiInitiatorNetworkId must be specified. This field is immutable.", + Type: schema.TypeString, + Computed: true, + }, + "iscsi_initiator_network_id": { + Description: "An unique address that identifies a device on the internet or a local network in IPv4/IPv6 format or a Fully Qualified Domain Name.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForIpV4ValuePrefixLength(), + "ipv6": SchemaForIpV6ValuePrefixLength(), + "fqdn": { + Description: "A fully qualified domain name that specifies its exact location in the tree hierarchy of the Domain Name System.", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Description: "The fully qualified domain name.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "enabled_authentications": { + Description: "The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided.", + Type: schema.TypeString, + Computed: true, + }, + "attached_targets": { + Description: "associated with each iSCSI target corresponding to the iSCSI client)", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "num_virtual_targets": { + Description: "Number of virtual targets generated for the iSCSI target. This field is immutable.", + Type: schema.TypeInt, + Computed: true, + }, + "iscsi_target_name": { + Description: "Name of the iSCSI target that the iSCSI client is connected to. This is a read-only field.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "cluster_reference": { + Description: "The UUID of the cluster that will host the iSCSI client. This field is read-only.", + Type: schema.TypeString, + Computed: true, + }, + "attachment_site": { + Description: "The site where the Volume Group attach operation should be processed. This is an optional field. This field may only be set if Metro DR has been configured for this Volume Group.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func DatasourceNutanixVolumeIscsiClientsV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + var filter, orderBy, expand, selects *string + var page, limit *int + + // initialize the query parameters + if pagef, ok := d.GetOk("page"); ok { + page = utils.IntPtr(pagef.(int)) + } else { + page = nil + } + if limitf, ok := d.GetOk("limit"); ok { + limit = utils.IntPtr(limitf.(int)) + } else { + limit = nil + } + if filterf, ok := d.GetOk("filter"); ok { + filter = utils.StringPtr(filterf.(string)) + } else { + filter = nil + } + if order, ok := d.GetOk("order_by"); ok { + orderBy = utils.StringPtr(order.(string)) + } else { + orderBy = nil + } + if expandf, ok := d.GetOk("expand"); ok { + expand = utils.StringPtr(expandf.(string)) + } else { + expand = nil + } + if selectf, ok := d.GetOk("select"); ok { + selects = utils.StringPtr(selectf.(string)) + } else { + selects = nil + } + + // get the volume group iscsi clients + resp, err := conn.IscsiClientAPIInstance.ListIscsiClients(page, limit, filter, orderBy, expand, selects) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching Iscsi Clients : %v", errorMessage["message"]) + } + + // // Check if resp is nil before accessing its data + // if resp != nil { + // diskResp := resp.Data + + // // extract the volume groups data from the response + // if diskResp != nil { + + // // set the volume groups iscsi clients data in the terraform resource + // if err := d.Set("iscsi_clients", flattenIscsiClientsEntities(diskResp.GetValue().([]volumesClient.IscsiClient))); err != nil { + // return diag.FromErr(err) + // } + // } + // } + + iscsiClientsResp := resp.Data + + // extract the volume groups data from the response + if iscsiClientsResp != nil { + + // set the volume groups iscsi clients data in the terraform resource + if err := d.Set("iscsi_clients", flattenIscsiClientsEntities(iscsiClientsResp.GetValue().([]volumesClient.IscsiClient))); err != nil { + return diag.FromErr(err) + } + } + d.SetId(resource.UniqueId()) + return nil + +} + +func flattenIscsiClientsEntities(pr []volumesClient.IscsiClient) []interface{} { + if len(pr) > 0 { + iscsi_clients := make([]interface{}, len(pr)) + + for k, v := range pr { + iscsi_client := make(map[string]interface{}) + + if v.TenantId != nil { + iscsi_client["tenant_id"] = v.TenantId + } + if v.ExtId != nil { + iscsi_client["ext_id"] = v.ExtId + } + if v.Links != nil { + iscsi_client["links"] = flattenLinks(v.Links) + } + if v.IscsiInitiatorName != nil { + iscsi_client["iscsi_initiator_name"] = v.IscsiInitiatorName + } + if v.IscsiInitiatorNetworkId != nil { + iscsi_client["iscsi_initiator_network_id"] = flattenIscsiInitiatorNetworkId(v.IscsiInitiatorNetworkId) + } + if v.EnabledAuthentications != nil { + iscsi_client["enabled_authentications"] = flattenEnabledAuthentications(v.EnabledAuthentications) + } + if v.AttachedTargets != nil { + iscsi_client["attached_targets"] = flattenAttachedTargets(v.AttachedTargets) + } + if v.AttachmentSite != nil { + iscsi_client["attachment_site"] = flattenAttachmentSite(v.AttachmentSite) + } + if v.ClusterReference != nil { + iscsi_client["cluster_reference"] = v.ClusterReference + } + if v.TargetParams != nil { + iscsi_client["attached_targets"] = flattenAttachedTargets(v.TargetParams) + } + + iscsi_clients[k] = iscsi_client + + } + return iscsi_clients + } + return nil +} + +func flattenAttachmentSite(iscsiClientAttachmentSite *volumesClient.VolumeGroupAttachmentSite) string { + const two, three = 2, 3 + if iscsiClientAttachmentSite != nil { + if *iscsiClientAttachmentSite == volumesClient.VolumeGroupAttachmentSite(two) { + return "PRIMARY" + } + if *iscsiClientAttachmentSite == volumesClient.VolumeGroupAttachmentSite(two) { + return "SECONDARY" + } + } + return "UNKNOWN" +} + +func flattenAttachedTargets(targetParam []volumesClient.TargetParam) []interface{} { + if len(targetParam) > 0 { + targetParamList := make([]interface{}, len(targetParam)) + for k, v := range targetParam { + target := make(map[string]interface{}) + + if v.NumVirtualTargets != nil { + target["num_virtual_targets"] = v.NumVirtualTargets + } + if v.IscsiTargetName != nil { + target["iscsi_target_name"] = v.IscsiTargetName + } + targetParamList[k] = target + } + return targetParamList + } + return nil +} + +func flattenIscsiInitiatorNetworkId(iPAddressOrFQDN *config.IPAddressOrFQDN) []interface{} { + if iPAddressOrFQDN != nil { + ipAddressOrFQDN := make(map[string]interface{}) + if iPAddressOrFQDN.Ipv4 != nil { + ipAddressOrFQDN["ipv4"] = flattenIp4Address(iPAddressOrFQDN.Ipv4) + } + if iPAddressOrFQDN.Ipv6 != nil { + ipAddressOrFQDN["ipv6"] = flattenIp6Address(iPAddressOrFQDN.Ipv6) + } + if iPAddressOrFQDN.Fqdn != nil { + ipAddressOrFQDN["fqdn"] = flattenFQDN(iPAddressOrFQDN.Fqdn) + } + return []interface{}{ipAddressOrFQDN} + } + return nil +} + +func flattenIp6Address(iPv6Address *config.IPv6Address) []interface{} { + if iPv6Address != nil { + ipv6 := make([]interface{}, 0) + + ip := make(map[string]interface{}) + + ip["value"] = iPv6Address.Value + ip["prefix_length"] = iPv6Address.PrefixLength + + ipv6 = append(ipv6, ip) + + return ipv6 + } + return nil +} + +func flattenIp4Address(iPv4Address *config.IPv4Address) []interface{} { + if iPv4Address != nil { + ipv4 := make([]interface{}, 0) + + ip := make(map[string]interface{}) + + ip["value"] = iPv4Address.Value + ip["prefix_length"] = iPv4Address.PrefixLength + + ipv4 = append(ipv4, ip) + + return ipv4 + } + return nil +} + +func flattenFQDN(fQDN *config.FQDN) []interface{} { + if fQDN != nil { + fqdn := make([]interface{}, 0) + + ip := make(map[string]interface{}) + + ip["value"] = fQDN.Value + fqdn = append(fqdn, ip) + return fqdn + } + return nil +} + +// func flattenValuePrefixLength(iPv4Address *config.IPv4Address) { +// panic("unimplemented") +// } diff --git a/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_clients_v2_test.go b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_clients_v2_test.go new file mode 100644 index 000000000..9e1b87538 --- /dev/null +++ b/nutanix/services/v2/volumesv2/data_source_nutanix_volume_iscsi_clients_v2_test.go @@ -0,0 +1,32 @@ +package volumesv2_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const dataSourceVolumeIscsiClients = "data.nutanix_volume_iscsi_clients_v2.v_iscsi" + +func TestAccNutanixVolumeIscsiClientsV2_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeIscsiClientsV2Config(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceVolumeIscsiClients, "iscsi_clients.#"), + testAccCheckResourceAttrListNotEmpty(dataSourceVolumeIscsiClients, "iscsi_clients", "iscsi_initiator_name"), + ), + }, + }, + }) +} + +func testAccVolumeIscsiClientsV2Config() string { + return ` + data "nutanix_volume_iscsi_clients_v2" "v_iscsi" {}` +} diff --git a/nutanix/services/v2/volumesv2/helpers_test.go b/nutanix/services/v2/volumesv2/helpers_test.go new file mode 100644 index 000000000..bc2d4237f --- /dev/null +++ b/nutanix/services/v2/volumesv2/helpers_test.go @@ -0,0 +1,158 @@ +package volumesv2_test + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +var diskSizeBytes int64 = 5368709120 + +func testAccCheckResourceAttrListNotEmpty(resourceName, attrName, subAttr string) resource.TestCheckFunc { + log.Printf("[DEBUG] testAccCheckResourceAttrNotEmpty ###############################") + return func(s *terraform.State) error { + resourceInstance := s.RootModule().Resources[resourceName] + + if resourceInstance == nil { + return fmt.Errorf("Resource %s not found", resourceName) + } + + prefix := attrName + "." + subAttrPrefix := prefix + "%d." + subAttr + log.Printf("[DEBUG] Attributes : %s", resourceInstance.Primary.Attributes) + for i := 0; ; i++ { + attr := fmt.Sprintf(subAttrPrefix, i) + if _, ok := resourceInstance.Primary.Attributes[attr]; !ok { + // No more items in the list + break + } + log.Printf("[DEBUG] Attribute : %s", attr) + if resourceInstance.Primary.Attributes[attr] == "" { + return fmt.Errorf("%s attribute %s is empty", resourceName, attr) + } + + } + return nil + + } +} + +func resourceNutanixVolumeGroupV2Exists(conn *conns.Client, name string) (*string, error) { + var vgUUID *string + + filter := fmt.Sprintf("name==%s", name) + vgList, err := conn.VolumeAPI.VolumeAPIInstance.ListVolumeGroups(nil, nil, &filter, nil, nil, nil) + + log.Printf("Volume Group List: %v", vgList) + + if err != nil { + return nil, err + } + + for _, vg := range vgList.Data.GetValue().([]volumesClient.VolumeGroup) { + if utils.StringValue(vg.Name) == name { + vgUUID = vg.ExtId + } + } + log.Printf("Volume Group UUID: %v", vgUUID) + return vgUUID, nil +} + +// Helper Functions +func testAndCheckComputedValues(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("resource ID not set") + } + + return nil + } +} + +// VolumeGroup Resource + +func testAccVolumeGroupResourceConfig(filepath, name, desc string) string { + + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] + config = (jsondecode(file("%[1]s"))) + volumes = local.config.volumes + vg_disk = local.config.volumes.disk + } + + resource "nutanix_volume_group_v2" "test" { + name = "%[2]s" + description = "%[3]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" + is_hidden = false + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } + } + `, filepath, name, desc) +} + +func testAccVolumeGroupDiskResourceConfig(filepath, name, desc string) string { + + return fmt.Sprintf(` + resource "nutanix_volume_group_disk_v2" "test" { + volume_group_ext_id = resource.nutanix_volume_group_v2.test.id + index = 1 + description = "%[3]s" + disk_size_bytes = %[4]d + + disk_data_source_reference { + name = "terraform-test-disk_data_source_reference-disk-1" + ext_id = local.vg_disk.disk_data_source_reference.ext_id + entity_type = "STORAGE_CONTAINER" + uris = ["uri1","uri2"] + } + + disk_storage_features { + flash_mode { + is_enabled = false + } + } + + lifecycle { + ignore_changes = [ + disk_data_source_reference + ] + } + + depends_on = [resource.nutanix_volume_group_v2.test] + } + `, filepath, name, desc, diskSizeBytes) +} diff --git a/nutanix/services/v2/volumesv2/main_test.go b/nutanix/services/v2/volumesv2/main_test.go new file mode 100644 index 000000000..98270bc53 --- /dev/null +++ b/nutanix/services/v2/volumesv2/main_test.go @@ -0,0 +1,47 @@ +package volumesv2_test + +import ( + "encoding/json" + "log" + "os" + "testing" +) + +type TestConfig struct { + // Volumes config + Volumes struct { + VolumeGroupExtIdWithCategory string `json:"vg_ext_id_with_category"` + VmExtId string `json:"vm_ext_id"` + IscsiClient struct { + ExtId string `json:"ext_id"` + IscsiInitiatorName string `json:"iscsi_initiator_name"` + } `json:"iscsi_client"` + Disk struct { + DiskDataSourceReference struct { + ExtId string `json:"ext_id"` + } `json:"disk_data_source_reference"` + } `json:"disk"` + } `json:"volumes"` +} + +var testVars TestConfig + +func loadVars(filepath string, varStuct interface{}) { + // Read test_config_v4.json from home current path + configData, err := os.ReadFile(filepath) + if err != nil { + log.Printf("Got this error while reading config.json: %s", err.Error()) + os.Exit(1) + } + + err = json.Unmarshal(configData, varStuct) + if err != nil { + log.Printf("Got this error while unmarshalling config.json: %s", err.Error()) + os.Exit(1) + } +} +func TestMain(m *testing.M) { + log.Println("Do some crazy stuff before tests!") + loadVars("../../../../test_config_v4.json", &testVars) + os.Exit(m.Run()) +} diff --git a/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_disk_v2.go b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_disk_v2.go new file mode 100644 index 000000000..595cd4c00 --- /dev/null +++ b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_disk_v2.go @@ -0,0 +1,418 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + taskPoll "github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16/models/prism/v4/config" + "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/common/v1/config" + volumesPrism "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/prism/v4/config" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// Creates a new Volume Disk. +func ResourceNutanixVolumeGroupDiskV2() *schema.Resource { + return &schema.Resource{ + Description: "Creates a new Volume Disk.", + CreateContext: ResourceNutanixVolumeGroupDiskV2Create, + ReadContext: ResourceNutanixVolumeGroupDiskV2Read, + UpdateContext: ResourceNutanixVolumeGroupDiskV2Update, + DeleteContext: ResourceNutanixVolumeGroupDiskV2Delete, + + Schema: map[string]*schema.Schema{ + "volume_group_ext_id": { + Description: "The external identifier of the volume group.", + Type: schema.TypeString, + Required: true, + }, + "ext_id": { + Description: "A globally unique identifier of an instance that is suitable for external consumption.", + Type: schema.TypeString, + Computed: true, + }, + "index": { + Description: "Index of the disk in a Volume Group. This field is optional and immutable.", + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "disk_size_bytes": { + Description: "Size of the disk in bytes. This field is mandatory during Volume Group creation if a new disk is being created on the storage container.", + Type: schema.TypeInt, + Required: true, + }, + "description": { + Description: "Volume Disk description. This is an optional field.", + Type: schema.TypeString, + Optional: true, + }, + "disk_data_source_reference": { + Description: "Disk Data Source Reference.", + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "The external identifier of the Data Source Reference.", + Type: schema.TypeString, + Required: true, + }, + "name": { + Description: "The name of the Data Source Reference.", + Type: schema.TypeString, + Optional: true, + }, + "uris": { + Description: "The uri list of the Data Source Reference.", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "entity_type": { + Description: "The Entity Type of the Data Source Reference.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"STORAGE_CONTAINER", "VM_DISK", "VOLUME_DISK", "DISK_RECOVERY_POINT"}, false), + }, + }, + }, + }, + "disk_storage_features": { + Description: "Storage optimization features which must be enabled on the Volume Disks. This is an optional field. If omitted, the disks will honor the Volume Group specific storage features setting.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flash_mode": { + Description: "Once configured, this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_enabled": { + Description: "The flash mode is enabled or not.", + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func ResourceNutanixVolumeGroupDiskV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + volumeGroupExtId := d.Get("volume_group_ext_id") + + body := volumesClient.VolumeDisk{} + + if diskSizeBytes, ok := d.GetOk("disk_size_bytes"); ok { + diskSize := int64(diskSizeBytes.(int)) + body.DiskSizeBytes = utils.Int64Ptr(diskSize) + } + if index, ok := d.GetOk("index"); ok { + body.Index = utils.IntPtr(index.(int)) + } + if description, ok := d.GetOk("description"); ok { + body.Description = utils.StringPtr(description.(string)) + } + if diskDataSourceReference, ok := d.GetOk("disk_data_source_reference"); ok { + body.DiskDataSourceReference = expandDiskDataSourceReference(diskDataSourceReference) + } + if diskStorageFeatures, ok := d.GetOk("disk_storage_features"); ok { + body.DiskStorageFeatures = expandDiskStorageFeatures(diskStorageFeatures.([]interface{})) + } + + log.Printf("[DEBUG] Volume Disk Body body.DiskDataSourceReference.Uris : %v", body.DiskDataSourceReference.Uris) + resp, err := conn.VolumeAPIInstance.CreateVolumeDisk(utils.StringPtr(volumeGroupExtId.(string)), &body) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while creating Volume Disk : %v", errorMessage["message"]) + } + + TaskRef := resp.Data.GetValue().(volumesPrism.TaskReference) + taskUUID := TaskRef.ExtId + + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING", "QUEUED"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for template (%s) to create: %s", utils.StringValue(taskUUID), errWaitTask) + } + + // Get UUID from TASK API + + resourceUUID, err := taskconn.TaskRefAPI.GetTaskById(taskUUID, nil) + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching volume Disk: %v", errorMessage["message"]) + } + rUUID := resourceUUID.Data.GetValue().(taskPoll.Task) + + uuid := rUUID.EntitiesAffected[1].ExtId + + d.SetId(*uuid) + d.Set("ext_id", *uuid) + + return nil +} + +func ResourceNutanixVolumeGroupDiskV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + volumeGroupExtId := d.Get("volume_group_ext_id") + + volumeDiskExtID := d.Id() // d.Id gives volume_group_ext_id not volume_disk_ext_id + + resp, err := conn.VolumeAPIInstance.GetVolumeDiskById(utils.StringPtr(volumeGroupExtId.(string)), utils.StringPtr(volumeDiskExtID)) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching volume Disk : %v", errorMessage["message"]) + } + getResp := resp.Data.GetValue().(volumesClient.VolumeDisk) + + if err := d.Set("ext_id", getResp.ExtId); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("index", getResp.Index); err != nil { + return diag.FromErr(err) + } + if err := d.Set("disk_size_bytes", getResp.DiskSizeBytes); err != nil { + return diag.FromErr(err) + } + if err := d.Set("description", getResp.Description); err != nil { + return diag.FromErr(err) + } + if err := d.Set("disk_data_source_reference", flattenDiskDataSourceReference(getResp.DiskDataSourceReference)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("disk_storage_features", flattenDiskStorageFeatures(getResp.DiskStorageFeatures)); err != nil { + return diag.FromErr(err) + } + d.SetId(*getResp.ExtId) + return nil +} + +func ResourceNutanixVolumeGroupDiskV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + volumeGroupExtId := d.Get("volume_group_ext_id") + volumeDiskExtID := d.Id() + + resp, err := conn.VolumeAPIInstance.GetVolumeDiskById(utils.StringPtr(volumeGroupExtId.(string)), utils.StringPtr(volumeDiskExtID)) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while updating Volume Disk : %v", errorMessage) + } + updateSpec := resp.Data.GetValue().(volumesClient.VolumeDisk) + + if d.HasChange("index") { + index := d.Get("index").(int) + updateSpec.Index = &index + } else { + updateSpec.Index = nil + } + if d.HasChange("disk_size_bytes") { + diskSizeBytes := d.Get("disk_size_bytes").(int64) + updateSpec.DiskSizeBytes = &diskSizeBytes + } + if d.HasChange("description") { + description := d.Get("description").(string) + updateSpec.Description = &description + } + if d.HasChange("disk_storage_features") { + diskStorageFeatures := d.Get("disk_storage_features").([]interface{}) + updateSpec.DiskStorageFeatures = expandDiskStorageFeatures(diskStorageFeatures) + } + if d.HasChange("disk_data_source_reference") { + diskDataSourceReference := d.Get("disk_data_source_reference").([]interface{}) + updateSpec.DiskDataSourceReference = expandDiskDataSourceReference(diskDataSourceReference) + } else { + updateSpec.DiskDataSourceReference = nil + } + + updateResp, err := conn.VolumeAPIInstance.UpdateVolumeDiskById(utils.StringPtr(volumeGroupExtId.(string)), utils.StringPtr(volumeDiskExtID), &updateSpec) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while updating Volume Disk : %v", errorMessage) + } + + TaskRef := updateResp.Data.GetValue().(volumesPrism.TaskReference) + taskUUID := TaskRef.ExtId + + // calling group API to poll for completion of task + + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the Image to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING", "QUEUED"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for Volume Disk (%s) to update: %s", utils.StringValue(taskUUID), errWaitTask) + } + + return ResourceNutanixVolumeGroupDiskV2Read(ctx, d, meta) +} + +func ResourceNutanixVolumeGroupDiskV2Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + volumeGroupExtId := d.Get("volume_group_ext_id") + volumeDiskExtID := d.Get("ext_id") + + resp, err := conn.VolumeAPIInstance.DeleteVolumeDiskById(utils.StringPtr(volumeGroupExtId.(string)), utils.StringPtr(volumeDiskExtID.(string))) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching volume Disk : %v", errorMessage["message"]) + } + + TaskRef := resp.Data.GetValue().(volumesPrism.TaskReference) + taskUUID := TaskRef.ExtId + + // calling group API to poll for completion of task + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING", "QUEUED"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for template (%s) to create: %s", utils.StringValue(taskUUID), errWaitTask) + } + return nil + +} + +func expandDiskStorageFeatures(diskStorageFeatures []interface{}) *volumesClient.DiskStorageFeatures { + if len(diskStorageFeatures) > 0 { + diskStorageFeature := volumesClient.DiskStorageFeatures{} + + val := diskStorageFeatures[0].(map[string]interface{}) + + if flashMode, ok := val["flash_mode"]; ok { + diskStorageFeature.FlashMode = expandFlashMode(flashMode.([]interface{})) + } + return &diskStorageFeature + } + return nil +} + +func expandDiskDataSourceReference(entityReference interface{}) *config.EntityReference { + if entityReference != nil { + + entityReferenceI := entityReference.([]interface{}) + val := entityReferenceI[0].(map[string]interface{}) + + diskDataSourceReference := config.EntityReference{} + + if extId, ok := val["ext_id"]; ok { + diskDataSourceReference.ExtId = utils.StringPtr(extId.(string)) + } + if name, ok := val["name"]; ok { + diskDataSourceReference.Name = utils.StringPtr(name.(string)) + } + if uris, ok := val["uris"]; ok { + uriSlice := make([]*string, len(uris.([]interface{}))) + for i, uri := range uris.([]interface{}) { + uriSlice[i] = utils.StringPtr(uri.(string)) + } + diskDataSourceReference.Uris = utils.StringValueSlice(uriSlice) + } + if entityType, ok := val["entity_type"]; ok { + subMap := map[string]interface{}{ + "UNKNOWN": 0, + "REDACTED": 1, + "STORAGE_CONTAINER": 4, + "VM_DISK": 20, + "VOLUME_DISK": 21, + "DISK_RECOVERY_POINT": 22, + } + + pInt := subMap[entityType.(string)] + p := config.EntityType(pInt.(int)) + + diskDataSourceReference.EntityType = &p + + } + log.Printf("[DEBUG] Disk Data Source Reference : %v", diskDataSourceReference) + return &diskDataSourceReference + } + return nil +} diff --git a/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_disk_v2_test.go b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_disk_v2_test.go new file mode 100644 index 000000000..f3e62c1c8 --- /dev/null +++ b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_disk_v2_test.go @@ -0,0 +1,42 @@ +package volumesv2_test + +import ( + "fmt" + "os" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const resourceVolumeGroupDisk = "nutanix_volume_group_disk_v2.test" + +func TestAccNutanixVolumeGroupDiskV2_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-volume-group-%d", r) + desc := "test volume group disk description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupsDiskResourceConfig(filepath, name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceVolumeGroupDisk, "description", desc), + resource.TestCheckResourceAttr(resourceVolumeGroupDisk, "disk_size_bytes", strconv.Itoa(int(diskSizeBytes))), + resource.TestCheckResourceAttr(resourceVolumeGroupDisk, "index", "1"), + resource.TestCheckResourceAttr(resourceVolumeGroupDisk, "disk_storage_features.0.flash_mode.0.is_enabled", "false"), + ), + }, + }, + }) +} + +func testAccVolumeGroupsDiskResourceConfig(filepath, name, desc string) string { + return testAccVolumeGroupResourceConfig(filepath, name, desc) + testAccVolumeGroupDiskResourceConfig(filepath, name, desc) +} diff --git a/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_iscsi_client_v2.go b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_iscsi_client_v2.go new file mode 100644 index 000000000..23071bcf8 --- /dev/null +++ b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_iscsi_client_v2.go @@ -0,0 +1,385 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + taskPoll "github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16/models/prism/v4/config" + config "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/common/v1/config" + volumesPrism "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/prism/v4/config" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// Attach/Detach an iSCSI client to the given Volume Group. +func ResourceNutanixVolumeGroupIscsiClientV2() *schema.Resource { + return &schema.Resource{ + Description: "Attach iSCSI initiator to a Volume Group identified by {extId}", + CreateContext: ResourceNutanixVolumeGroupIscsiClientV2Create, + ReadContext: ResourceNutanixVolumeGroupIscsiClientV2Read, + UpdateContext: ResourceNutanixVolumeGroupIscsiClientV2Update, + DeleteContext: ResourceNutanixVVolumeGroupIscsiClientV2Delete, + Schema: map[string]*schema.Schema{ + "vg_ext_id": { + Description: "The external identifier of the Volume Group.", + Type: schema.TypeString, + Required: true, + }, + "ext_id": { + Description: "A globally unique identifier of an instance that is suitable for external consumption.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "iscsi_initiator_name": { + Description: "iSCSI initiator name. During the attach operation, exactly one of iscsiInitiatorName and iscsiInitiatorNetworkId must be specified. This field is immutable.", + Type: schema.TypeString, + Optional: true, + }, + "iscsi_initiator_network_id": { + Description: "An unique address that identifies a device on the internet or a local network in IPv4/IPv6 format or a Fully Qualified Domain Name.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForIpV4ValuePrefixLength(), + "ipv6": SchemaForIpV6ValuePrefixLength(), + "fqdn": { + Description: "A fully qualified domain name that specifies its exact location in the tree hierarchy of the Domain Name System.", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Description: "A fully qualified domain name that specifies its exact location in the tree hierarchy of the Domain Name System.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "client_secret": { + Description: "iSCSI initiator client secret in case of CHAP authentication. This field should not be provided in case the authentication type is not set to CHAP..", + Type: schema.TypeString, + Optional: true, + }, + "enabled_authentications": { + Description: "The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"CHAP", "NONE"}, false), + }, + "num_virtual_targets": { + Description: "Number of virtual targets generated for the iSCSI target. This field is immutable.", + Type: schema.TypeInt, + Optional: true, + }, + "attachment_site": { + Description: "The site where the Volume Group attach operation should be processed. This is an optional field. This field may only be set if Metro DR has been configured for this Volume Group.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"SECONDARY", "PRIMARY"}, false), + }, + }, + } +} + +func SchemaForIpV4ValuePrefixLength() *schema.Schema { + return &schema.Schema{ + Description: "An unique address that identifies a device on the internet or a local network in IPv4 format.", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Description: "An unique address that identifies a device on the internet or a local network in IPv4 format.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "prefix_length": { + Description: "The prefix length of the network to which this host IPv4 address belongs.", + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + } +} + +func SchemaForIpV6ValuePrefixLength() *schema.Schema { + return &schema.Schema{ + Description: "An unique address that identifies a device on the internet or a local network in IPv6 format.", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Description: "An unique address that identifies a device on the internet or a local network in IPv6 format.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "prefix_length": { + Description: "The prefix length of the network to which this host IPv6 address belongs.", + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + } +} + +// Attach an iSCSI client to the given Volume Group. +func ResourceNutanixVolumeGroupIscsiClientV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + volumeGroupExtId := d.Get("vg_ext_id") + + body := volumesClient.IscsiClient{} + + if iscsiInitiatorName, ok := d.GetOk("iscsi_initiator_name"); ok { + body.IscsiInitiatorName = utils.StringPtr(iscsiInitiatorName.(string)) + } + if iscsiInitiatorNetworkId, ok := d.GetOk("iscsi_initiator_network_id"); ok { + body.IscsiInitiatorNetworkId = expandIscsiInitiatorNetworkId(iscsiInitiatorNetworkId.([]interface{})) + } + if clientSecret, ok := d.GetOk("client_secret"); ok { + body.ClientSecret = utils.StringPtr(clientSecret.(string)) + } + if enabledAuthentications, ok := d.GetOk("enabled_authentications"); ok { + enabledAuthenticationsMap := map[string]interface{}{ + "CHAP": "2", + "NONE": "3", + } + pInt := enabledAuthenticationsMap[enabledAuthentications.(string)] + p := volumesClient.AuthenticationType(pInt.(int)) + body.EnabledAuthentications = &p + } + if numVirtualTargets, ok := d.GetOk("num_virtual_targets"); ok { + body.NumVirtualTargets = utils.IntPtr(numVirtualTargets.(int)) + } + if attachmentSite, ok := d.GetOk("attachment_site"); ok { + attachmentSiteMap := map[string]interface{}{ + "SECONDARY": "2", + "PRIMARY": "3", + } + pInt := attachmentSiteMap[attachmentSite.(string)] + p := volumesClient.VolumeGroupAttachmentSite(pInt.(int)) + body.AttachmentSite = &p + } + + resp, err := conn.VolumeAPIInstance.AttachIscsiClient(utils.StringPtr(volumeGroupExtId.(string)), &body) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while Attaching Iscsi Client to Volume Group: %v", errorMessage["message"]) + } + + TaskRef := resp.Data.GetValue().(volumesPrism.TaskReference) + taskUUID := TaskRef.ExtId + + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING", "QUEUED"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for template (%s) to Attach Iscsi Client to Volume Group: %s", utils.StringValue(taskUUID), errWaitTask) + } + + // Get UUID from TASK API + + resourceUUID, err := taskconn.TaskRefAPI.GetTaskById(taskUUID, nil) + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while Attaching Iscsi Client to Volume Group: %v", errorMessage["message"]) + } + rUUID := resourceUUID.Data.GetValue().(taskPoll.Task) + log.Printf("[DEBUG] rUUID 0: %v", *rUUID.EntitiesAffected[0].ExtId) + log.Printf("[DEBUG] rUUID 1: %v", *rUUID.EntitiesAffected[1].ExtId) + uuid := rUUID.EntitiesAffected[0].ExtId + + d.SetId(*uuid) + + return nil +} + +func ResourceNutanixVolumeGroupIscsiClientV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return nil +} + +func ResourceNutanixVolumeGroupIscsiClientV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return nil +} + +// Detach an iSCSi client from the given Volume Group. +func ResourceNutanixVVolumeGroupIscsiClientV2Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + volumeGroupExtId := d.Get("vg_ext_id") + + body := volumesClient.IscsiClientAttachment{} + + if extId, ok := d.GetOk("ext_id"); ok { + body.ExtId = utils.StringPtr(extId.(string)) + } + + resp, err := conn.VolumeAPIInstance.DetachIscsiClient(utils.StringPtr(volumeGroupExtId.(string)), &body) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + log.Printf("[ERROR] data: %v", data) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while Detaching Iscsi Client to Volume Group: %v", errorMessage["message"]) + } + + TaskRef := resp.Data.GetValue().(volumesPrism.TaskReference) + taskUUID := TaskRef.ExtId + + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING", "QUEUED"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for template (%s) to Detach Iscsi Client to Volume Group: %s", utils.StringValue(taskUUID), errWaitTask) + } + + // Get UUID from TASK API + + resourceUUID, err := taskconn.TaskRefAPI.GetTaskById(taskUUID, nil) + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while Detaching Iscsi Client to Volume Group: %v", errorMessage["message"]) + } + rUUID := resourceUUID.Data.GetValue().(taskPoll.Task) + log.Printf("[DEBUG] rUUID 0: %v", *rUUID.EntitiesAffected[0].ExtId) + log.Printf("[DEBUG] rUUID 1: %v", *rUUID.EntitiesAffected[1].ExtId) + + uuid := rUUID.EntitiesAffected[0].ExtId + + d.SetId(*uuid) + + return nil +} + +func expandIscsiInitiatorNetworkId(ipAddressOrFQDN interface{}) *config.IPAddressOrFQDN { + if ipAddressOrFQDN != nil { + fip := &config.IPAddressOrFQDN{} + prI := ipAddressOrFQDN.([]interface{}) + val := prI[0].(map[string]interface{}) + + if ipv4, ok := val["ipv4"]; ok { + fip.Ipv4 = expandFloatingIPv4Address(ipv4) + } + if ipv6, ok := val["ipv6"]; ok { + fip.Ipv6 = expandFloatingIPv6Address(ipv6) + } + if fqdn, ok := val["fqdn"]; ok { + fip.Fqdn = expandFQDN(fqdn) + } + + return fip + } + return nil +} + +func expandFloatingIPv4Address(IPv4I interface{}) *config.IPv4Address { + if IPv4I != nil { + ipv4 := &config.IPv4Address{} + prI := IPv4I.([]interface{}) + val := prI[0].(map[string]interface{}) + + if value, ok := val["value"]; ok { + ipv4.Value = utils.StringPtr(value.(string)) + } + if prefix, ok := val["prefix_length"]; ok { + ipv4.PrefixLength = utils.IntPtr(prefix.(int)) + } + return ipv4 + } + return nil +} + +func expandFloatingIPv6Address(IPv6I interface{}) *config.IPv6Address { + if IPv6I != nil { + ipv6 := &config.IPv6Address{} + prI := IPv6I.([]interface{}) + val := prI[0].(map[string]interface{}) + + if value, ok := val["value"]; ok { + ipv6.Value = utils.StringPtr(value.(string)) + } + if prefix, ok := val["prefix_length"]; ok { + ipv6.PrefixLength = utils.IntPtr(prefix.(int)) + } + return ipv6 + } + return nil +} + +func expandFQDN(FQDNI interface{}) *config.FQDN { + if FQDNI != nil { + fqdn := &config.FQDN{} + prI := FQDNI.([]interface{}) + val := prI[0].(map[string]interface{}) + + if value, ok := val["value"]; ok { + fqdn.Value = utils.StringPtr(value.(string)) + } + return fqdn + } + return nil +} diff --git a/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_iscsi_client_v2_test.go b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_iscsi_client_v2_test.go new file mode 100644 index 000000000..5e9456d13 --- /dev/null +++ b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_iscsi_client_v2_test.go @@ -0,0 +1,46 @@ +package volumesv2_test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const resourceVolumeGroupIscsiClient = "nutanix_volume_group_iscsi_client_v2.test" + +func TestAccNutanixVolumeGroupIscsiClientV2_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-volume-group-%d", r) + desc := "test volume group Iscsi Client description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupResourceConfig(filepath, name, desc) + testAccVolumeGroupIscsiClientResourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceVolumeGroupIscsiClient, "ext_id", testVars.Volumes.IscsiClient.ExtId), + ), + }, + }, + }) +} + +func testAccVolumeGroupIscsiClientResourceConfig() string { + + return ` + resource "nutanix_volume_group_iscsi_client_v2" "test" { + vg_ext_id = resource.nutanix_volume_group_v2.test.id + ext_id = local.volumes.iscsi_client.ext_id + iscsi_initiator_name = local.volumes.iscsi_client.initiator_name + depends_on = [ resource.nutanix_volume_group_v2.test ] + } + ` +} diff --git a/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_v2.go b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_v2.go new file mode 100644 index 000000000..416e90bce --- /dev/null +++ b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_v2.go @@ -0,0 +1,474 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + taskPoll "github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16/models/prism/v4/config" + volumesPrism "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/prism/v4/config" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/nutanix/sdks/v4/prism" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// CRUD for Volume Group. +func ResourceNutanixVolumeGroupV2() *schema.Resource { + return &schema.Resource{ + Description: "Creates a new Volume Group.", + CreateContext: ResourceNutanixVolumeGroupV2Create, + ReadContext: ResourceNutanixVolumeGroupV2Read, + UpdateContext: ResourceNutanixVolumeGroupV2Update, + DeleteContext: ResourceNutanixVolumeGroupV2Delete, + + Schema: map[string]*schema.Schema{ + "ext_id": { + Description: "A globally unique identifier of an instance that is suitable for external consumption.", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "Volume Group name. This is an Required field.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "Volume Group description. This is an optional field.", + Type: schema.TypeString, + Optional: true, + }, + "should_load_balance_vm_attachments": { + Description: "Indicates whether to enable Volume Group load balancing for VM attachments. This cannot be enabled if there are iSCSI client attachments already associated with the Volume Group, and vice-versa. This is an optional field.", + Type: schema.TypeBool, + Optional: true, + }, + "sharing_status": { + Description: "Indicates whether the Volume Group can be shared across multiple iSCSI initiators. The mode cannot be changed from SHARED to NOT_SHARED on a Volume Group with multiple attachments. Similarly, a Volume Group cannot be associated with more than one attachment as long as it is in exclusive mode. This is an optional field.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"NOT_SHARED", "SHARED"}, false), + }, + "target_prefix": { + Description: "The specifications contain the target prefix for external clients as the value. This is an optional field.", + Type: schema.TypeString, + Optional: true, + }, + "target_name": { + Description: "Name of the external client target that will be visible and accessible to the client. This is an optional field.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "enabled_authentications": { + Description: "The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"CHAP", "NONE"}, false), + }, + "iscsi_features": { + Description: "iSCSI specific settings for the Volume Group. This is an optional field.", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target_secret": { + Description: "Target secret in case of a CHAP authentication. This field must only be provided in case the authentication type is not set to CHAP. This is an optional field and it cannot be retrieved once configured.", + Type: schema.TypeString, + Optional: true, + }, + "enabled_authentications": { + Description: "The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"CHAP", "NONE"}, false), + }, + }, + }, + }, + "created_by": { + Description: "Service/user who created this Volume Group. This is an optional field.", + Type: schema.TypeString, + Optional: true, + }, + "cluster_reference": { + Description: "The UUID of the cluster that will host the Volume Group. This is a mandatory field for creating a Volume Group on Prism Central.", + Type: schema.TypeString, + Required: true, + }, + "storage_features": { + Description: "Storage optimization features which must be enabled on the Volume Group. This is an optional field.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flash_mode": { + Description: "Once configured, this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_enabled": { + Description: "Indicates whether the flash mode is enabled for the Volume Group.", + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "usage_type": { + Description: "Expected usage type for the Volume Group. This is an indicative hint on how the caller will consume the Volume Group. This is an optional field.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"USER", "INTERNAL", "TEMPORARY", "BACKUP_TARGET"}, 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, + }, + }, + } +} + +func ResourceNutanixVolumeGroupV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + log.Printf("[INFO_VG] Creating Volume Group") + conn := meta.(*conns.Client).VolumeAPI + + body := volumesClient.VolumeGroup{} + + // Required field + if name, nok := d.GetOk("name"); nok { + body.Name = utils.StringPtr(name.(string)) + } + if desc, ok := d.GetOk("description"); ok { + body.Description = utils.StringPtr(desc.(string)) + } + if shouldLoadBalanceVmAttachments, ok := d.GetOk("should_load_balance_vm_attachments"); ok { + body.ShouldLoadBalanceVmAttachments = utils.BoolPtr(shouldLoadBalanceVmAttachments.(bool)) + } + if sharingStatus, ok := d.GetOk("sharing_status"); ok { + sharingStatusMap := map[string]interface{}{ + "SHARED": 2, + "NOT_SHARED": 3, + } + pVal := sharingStatusMap[sharingStatus.(string)] + p := volumesClient.SharingStatus(pVal.(int)) + body.SharingStatus = &p + } + if targetPrefix, ok := d.GetOk("target_prefix"); ok { + body.TargetPrefix = utils.StringPtr(targetPrefix.(string)) + } + 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 iscsiFeatures, ok := d.GetOk("iscsi_features"); ok { + body.IscsiFeatures = expandIscsiFeatures(iscsiFeatures.([]interface{})) + } + if createdBy, ok := d.GetOk("created_by"); ok { + body.CreatedBy = utils.StringPtr(createdBy.(string)) + } + // Required field + if clusterReference, ok := d.GetOk("cluster_reference"); ok { + body.ClusterReference = utils.StringPtr(clusterReference.(string)) + } + if storageFeatures, ok := d.GetOk("storage_features"); ok { + body.StorageFeatures = expandStorageFeatures(storageFeatures.([]interface{})) + } + if usageType, ok := d.GetOk("usage_type"); ok { + usageTypeMap := map[string]interface{}{ + "USER": 2, + "INTERNAL": 3, + "TEMPORARY": 4, + "BACKUP_TARGET": 5, + } + pInt := usageTypeMap[usageType.(string)] + p := volumesClient.UsageType(pInt.(int)) + body.UsageType = &p + } + if isHidden, ok := d.GetOk("is_hidden"); ok { + body.IsHidden = utils.BoolPtr(isHidden.(bool)) + } + + resp, err := conn.VolumeAPIInstance.CreateVolumeGroup(&body) + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + log.Printf("[INFO_VG] Error Data: %v", errordata) + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while creating Volume Group : %v", errorMessage["message"]) + } + + TaskRef := resp.Data.GetValue().(volumesPrism.TaskReference) + taskUUID := TaskRef.ExtId + + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING", "QUEUED"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for template (%s) to create: %s", utils.StringValue(taskUUID), errWaitTask) + } + + // Get UUID from TASK API + + resourceUUID, err := taskconn.TaskRefAPI.GetTaskById(taskUUID, nil) + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching Volume Group UUID : %v", errorMessage["message"]) + } + rUUID := resourceUUID.Data.GetValue().(taskPoll.Task) + + uuid := rUUID.EntitiesAffected[0].ExtId + d.SetId(*uuid) + d.Set("ext_id", *uuid) + + return nil +} + +func ResourceNutanixVolumeGroupV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + resp, err := conn.VolumeAPIInstance.GetVolumeGroupById(utils.StringPtr(d.Id())) + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while fetching Volume Group : %v", errorMessage["message"]) + } + + getResp := resp.Data.GetValue().(volumesClient.VolumeGroup) + + if err := d.Set("name", getResp.Name); err != nil { + return diag.FromErr(err) + } + if err := d.Set("description", getResp.Description); err != nil { + return diag.FromErr(err) + } + if err := d.Set("should_load_balance_vm_attachments", getResp.ShouldLoadBalanceVmAttachments); err != nil { + return diag.FromErr(err) + } + if err := d.Set("sharing_status", flattenSharingStatus(getResp.SharingStatus)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("target_prefix", getResp.TargetPrefix); err != nil { + return diag.FromErr(err) + } + if err := d.Set("target_name", getResp.TargetName); err != nil { + return diag.FromErr(err) + } + if err := d.Set("enabled_authentications", flattenEnabledAuthentications(getResp.EnabledAuthentications)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("iscsi_features", flattenIscsiFeatures(getResp.IscsiFeatures)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("created_by", getResp.CreatedBy); err != nil { + return diag.FromErr(err) + } + if err := d.Set("cluster_reference", getResp.ClusterReference); err != nil { + return diag.FromErr(err) + } + if err := d.Set("storage_features", flattenStorageFeatures(getResp.StorageFeatures)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("usage_type", flattenUsageType(getResp.UsageType)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("is_hidden", getResp.IsHidden); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func ResourceNutanixVolumeGroupV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return nil +} + +func ResourceNutanixVolumeGroupV2Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + resp, err := conn.VolumeAPIInstance.DeleteVolumeGroupById(utils.StringPtr(d.Id())) + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while Deleting Volume group : %v", errorMessage["message"]) + } + + TaskRef := resp.Data.GetValue().(volumesPrism.TaskReference) + taskUUID := TaskRef.ExtId + + // calling group API to poll for completion of task + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING", "QUEUED"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for template (%s) to create: %s", utils.StringValue(taskUUID), errWaitTask) + } + return nil +} + +func expandIscsiFeatures(IscsiFeaturesList interface{}) *volumesClient.IscsiFeatures { + if len(IscsiFeaturesList.([]interface{})) > 0 { + iscsiFeature := &volumesClient.IscsiFeatures{} + iscsiFeaturesI := IscsiFeaturesList.([]interface{}) + if iscsiFeaturesI[0] == nil { + return nil + } + val := iscsiFeaturesI[0].(map[string]interface{}) + + if targetSecret, ok := val["target_secret"]; ok { + iscsiFeature.TargetSecret = utils.StringPtr(targetSecret.(string)) + } + + if enabledAuthentications, ok := val["enabled_authentications"]; ok { + enabledAuthenticationsMap := map[string]interface{}{ + "CHAP": 2, + "NONE": 3, + } + pVal := enabledAuthenticationsMap[enabledAuthentications.(string)] + p := volumesClient.AuthenticationType(pVal.(int)) + iscsiFeature.EnabledAuthentications = &p + } + log.Printf("[INFO_VG] iscsiFeature.EnabledAuthentications: %v", *iscsiFeature.EnabledAuthentications) + log.Printf("[INFO_VG] iscsiFeature.TargetSecret: %v", *iscsiFeature.TargetSecret) + return iscsiFeature + } + return nil +} + +func expandStorageFeatures(storageFeaturesList []interface{}) *volumesClient.StorageFeatures { + if len(storageFeaturesList) > 0 { + storageFeature := volumesClient.StorageFeatures{} + + val := storageFeaturesList[0].(map[string]interface{}) + + if flashMode, ok := val["flash_mode"]; ok { + storageFeature.FlashMode = expandFlashMode(flashMode.([]interface{})) + } + return &storageFeature + } + return nil +} + +func expandFlashMode(flashModeList []interface{}) *volumesClient.FlashMode { + if len(flashModeList) > 0 { + flashMode := volumesClient.FlashMode{} + + val := flashModeList[0].(map[string]interface{}) + + if isEnabled, ok := val["is_enabled"]; ok { + flashMode.IsEnabled = utils.BoolPtr(isEnabled.(bool)) + } + return &flashMode + } + return nil +} + +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) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return nil, "", e + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return "", "", (fmt.Errorf("error while polling prism task: %v", errorMessage["message"])) + } + + // get the group results + + v := vresp.Data.GetValue().(taskPoll.Task) + + if getTaskStatus(v.Status) == "CANCELED" || getTaskStatus(v.Status) == "FAILED" { + return v, getTaskStatus(v.Status), + fmt.Errorf("error_detail: %s, progress_message: %d", utils.StringValue(v.ErrorMessages[0].Message), utils.IntValue(v.ProgressPercentage)) + } + return v, getTaskStatus(v.Status), nil + } +} + +func getTaskStatus(taskStatus *taskPoll.TaskStatus) string { + if taskStatus != nil { + if *taskStatus == taskPoll.TaskStatus(6) { + return "FAILED" + } + if *taskStatus == taskPoll.TaskStatus(7) { + return "CANCELED" + } + if *taskStatus == taskPoll.TaskStatus(2) { + return "QUEUED" + } + if *taskStatus == taskPoll.TaskStatus(3) { + return "RUNNING" + } + if *taskStatus == taskPoll.TaskStatus(5) { + return "SUCCEEDED" + } + } + return "UNKNOWN" +} diff --git a/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_v2_test.go b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_v2_test.go new file mode 100644 index 000000000..61c187456 --- /dev/null +++ b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_v2_test.go @@ -0,0 +1,136 @@ +package volumesv2_test + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const resourceNameVolumeGroup = "nutanix_volume_group_v2.test" + +func TestAccNutanixVolumeGroupV2_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-volume-group-%d", r) + desc := "test volume group description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + // CheckDestroy: testAccCheckNutanixVolumeGroupV4Destroy, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupResourceConfig(filepath, 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"), + ), + }, + }, + }) +} + +func TestAccNutanixVolumeGroupV2_RequiredAttr(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-volume-group-%d", r) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupV2RequiredAttributes(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceNameVolumeGroup, "name", name), + testAndCheckComputedValues(resourceNameVolumeGroup), + ), + }, + }, + }) +} + +func TestAccNutanixVolumeGroupV2_WithNoName(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupV2ConfigWithNoName(), + ExpectError: regexp.MustCompile("Missing required argument"), + }, + }, + }) +} + +func TestAccNutanixVolumeGroupV2_WithNoClusterReference(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-volume-group-%d", r) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupV2ConfigWithNoClusterReference(name), + ExpectError: regexp.MustCompile("Missing required argument"), + }, + }, + }) +} + +// VG just required attributes +func testAccVolumeGroupV2RequiredAttributes(name string) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals{ + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] + } + + resource "nutanix_volume_group_v2" "test" { + name = "%s" + cluster_reference = local.cluster1 + } + +`, name) +} + +func testAccVolumeGroupV2ConfigWithNoName() string { + return ` + data "nutanix_clusters" "clusters" {} + + locals{ + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] + } + + resource "nutanix_volume_group_v2" "test" { + cluster_reference = local.cluster1 + } + ` +} + +func testAccVolumeGroupV2ConfigWithNoClusterReference(name string) string { + return fmt.Sprintf(` + resource "nutanix_volume_group_v2" "test" { + name = "%s" + } +`, name) +} diff --git a/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_vm_v2.go b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_vm_v2.go new file mode 100644 index 000000000..c5129bbea --- /dev/null +++ b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_vm_v2.go @@ -0,0 +1,197 @@ +package volumesv2 + +import ( + "context" + "encoding/json" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + taskPoll "github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16/models/prism/v4/config" + volumesPrism "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/prism/v4/config" + volumesClient "github.com/nutanix-core/ntnx-api-golang-sdk-internal/volumes-go-client/v16/models/volumes/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +// Attach an AHV VM to the given Volume Group. +func ResourceNutanixVolumeAttachVmToVolumeGroupV2() *schema.Resource { + return &schema.Resource{ + Description: "Attaches VM to a Volume Group identified by {extId}.", + CreateContext: ResourceNutanixVolumeAttachVmToVolumeGroupV2Create, + ReadContext: ResourceNutanixVolumeAttachVmToVolumeGroupV2Read, + UpdateContext: ResourceNutanixVolumeAttachVmToVolumeGroupV2Update, + DeleteContext: ResourceNutanixVolumeAttachVmToVolumeGroupV2Delete, + + Schema: map[string]*schema.Schema{ + "volume_group_ext_id": { + Description: "The external identifier of the volume group.", + Type: schema.TypeString, + Required: true, + }, + "vm_ext_id": { + Description: "A globally unique identifier of an instance that is suitable for external consumption. This Field is Required.", + Type: schema.TypeString, + Required: true, + }, + "index": { + Description: "The index on the SCSI bus to attach the VM to the Volume Group. This is an optional field.", + Type: schema.TypeInt, + Optional: true, + }, + "ext_id": { + Description: "A globally unique identifier of a task.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func ResourceNutanixVolumeAttachVmToVolumeGroupV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + volumeGroupExtId := d.Get("volume_group_ext_id") + + body := volumesClient.VmAttachment{} + + if vmExtId, ok := d.GetOk("vm_ext_id"); ok { + body.ExtId = utils.StringPtr(vmExtId.(string)) + } + + if index, ok := d.GetOk("index"); ok { + body.Index = utils.IntPtr(index.(int)) + } + + resp, err := conn.VolumeAPIInstance.AttachVm(utils.StringPtr(volumeGroupExtId.(string)), &body) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while Attaching Vm to Volume Group : %v", errorMessage["message"]) + } + + TaskRef := resp.Data.GetValue().(volumesPrism.TaskReference) + taskUUID := TaskRef.ExtId + + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING", "QUEUED"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for template (%s) to Attach Vm to Volume Group: %s", utils.StringValue(taskUUID), errWaitTask) + } + + // Get UUID from TASK API + + resourceUUID, err := taskconn.TaskRefAPI.GetTaskById(taskUUID, nil) + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while Attaching Vm to Volume Group: %v", errorMessage["message"]) + } + rUUID := resourceUUID.Data.GetValue().(taskPoll.Task) + + uuid := rUUID.EntitiesAffected[0].ExtId + + d.SetId(*uuid) + d.Set("ext_id", *uuid) + + return nil +} + +func ResourceNutanixVolumeAttachVmToVolumeGroupV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return nil +} + +func ResourceNutanixVolumeAttachVmToVolumeGroupV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return nil +} + +func ResourceNutanixVolumeAttachVmToVolumeGroupV2Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).VolumeAPI + + volumeGroupExtId := d.Get("volume_group_ext_id") + + body := volumesClient.VmAttachment{} + + if vmExtId, ok := d.GetOk("vm_ext_id"); ok { + body.ExtId = utils.StringPtr(vmExtId.(string)) + } + + if index, ok := d.GetOk("index"); ok { + body.Index = utils.IntPtr(index.(int)) + } + + resp, err := conn.VolumeAPIInstance.DetachVm(utils.StringPtr(volumeGroupExtId.(string)), &body) + + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while Detaching Vm to Volume Group : %v", errorMessage["message"]) + } + + TaskRef := resp.Data.GetValue().(volumesPrism.TaskReference) + taskUUID := TaskRef.ExtId + + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for template (%s) to Detach Vm to Volume Group: %s", utils.StringValue(taskUUID), errWaitTask) + } + + // Get UUID from TASK API + + resourceUUID, err := taskconn.TaskRefAPI.GetTaskById(taskUUID, nil) + if err != nil { + var errordata map[string]interface{} + e := json.Unmarshal([]byte(err.Error()), &errordata) + if e != nil { + return diag.FromErr(e) + } + data := errordata["data"].(map[string]interface{}) + errorList := data["error"].([]interface{}) + errorMessage := errorList[0].(map[string]interface{}) + return diag.Errorf("error while Detaching Vm to Volume Group: %v", errorMessage["message"]) + } + rUUID := resourceUUID.Data.GetValue().(taskPoll.Task) + + uuid := rUUID.EntitiesAffected[0].ExtId + + d.SetId(*uuid) + d.Set("ext_id", *uuid) + + return nil + +} diff --git a/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_vm_v2_test.go b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_vm_v2_test.go new file mode 100644 index 000000000..282013374 --- /dev/null +++ b/nutanix/services/v2/volumesv2/resource_nutanix_volume_group_vm_v2_test.go @@ -0,0 +1,46 @@ +package volumesv2_test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const resourceVolumeGroupVm = "nutanix_volume_group_vm_v2.test" + +func TestAccNutanixVolumeGroupVmV2_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-volume-group-%d", r) + desc := "test volume group Vm Attachment description" + path, _ := os.Getwd() + filepath := path + "/../../../../test_config_v4.json" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVolumeGroupVmConfig(filepath, name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceVolumeGroupVm, "vm_ext_id", testVars.Volumes.VmExtId), + ), + }, + }, + }) +} + +func testAccVolumeGroupVmConfig(filepath, name, desc string) string { + + return testAccVolumeGroupResourceConfig(filepath, name, desc) + ` + resource "nutanix_volume_group_vm_v2" "test" { + volume_group_ext_id = resource.nutanix_volume_group_v2.test.id + vm_ext_id = local.volumes.vm_ext_id + depends_on = [resource.nutanix_volume_group_v2.test] + } + + ` +} diff --git a/test_config_v2.json b/test_config_v2.json index c730f63dc..33b1d20fe 100644 --- a/test_config_v2.json +++ b/test_config_v2.json @@ -98,5 +98,18 @@ "prefix_length": 32 } } + }, + "volumes": { + "vg_ext_id_with_category": "", + "iscsi_client": { + "ext_id": "", + "initiator_name": "" + }, + "disk": { + "disk_data_source_reference": { + "ext_id": "2" + } + }, + "vm_ext_id": "" } } diff --git a/website/docs/d/nutanix_volume_group_category_details_v4.html.markdown b/website/docs/d/nutanix_volume_group_category_details_v4.html.markdown new file mode 100644 index 000000000..7949d87f4 --- /dev/null +++ b/website/docs/d/nutanix_volume_group_category_details_v4.html.markdown @@ -0,0 +1,47 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_group_category_details_v2" +sidebar_current: "docs-nutanix-datasource-volume-group-category-details-v4" +description: |- + Describes a List all the category details that are associated with the Volume Group. +--- + +# nutanix_volume_group_disks_v2 + +Query the category details that are associated with the Volume Group identified by {volumeGroupExtId}. +## Example Usage + +```hcl + +# List of all category details that are associated with the Volume Group. +data "nutanix_volume_group_category_details_v2" "vg_cat_example"{ + ext_id = var.volume_group_ext_id + limit = 6 +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `volume_group_ext_id`: -(Required) The external identifier of the Volume Group. +* `page`: - A query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. +* `limit` : A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set. + + +## Attributes Reference +The following attributes are exported: + +* `category_details`: - List of all category details that are associated with the Volume Group. + +### Category Details + +* `ext_id`: - A globally unique identifier of an instance that is suitable for external consumption. +* `name`: - The name of the category detail. +* `uris`: - The uri list of the category detail. +* `entity_type`: - SThe Entity Type of the category detail. + + + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/d/nutanix_volume_group_disk_v4.html.markdown b/website/docs/d/nutanix_volume_group_disk_v4.html.markdown new file mode 100644 index 000000000..608c3534f --- /dev/null +++ b/website/docs/d/nutanix_volume_group_disk_v4.html.markdown @@ -0,0 +1,138 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_group_disk_v2" +sidebar_current: "docs-nutanix-datasource-volume-group-disk-v4" +description: |- + Describes the details of a Volume Disk. +--- + +# nutanix_volume_group_disk_v2 + +Describes a Query the Volume Disk identified by {extId} in the Volume Group identified by {volumeGroupExtId}. + +## Example Usage + +```hcl + +data "nutanix_clusters" "clusters"{} + +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +resource "nutanix_volume_group_v2" "example"{ + name = "test_volume_group" + description = "Test Volume group with min spec and no Auth" + should_load_balance_vm_attachments = false + sharing_status = "SHARED" + target_name = "volumegroup-test-0" + created_by = "Test" + cluster_reference = local.cluster1 + iscsi_features { + enabled_authentications = "CHAP" + target_secret = "1234567891011" + } + + storage_features { + flash_mode { + is_enabled = true + } + } + usage_type = "USER" + is_hidden = false + + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } +} + + +# Attach a volume group disk to the pervious volume group +resource "nutanix_volume_group_disk_v2" "example"{ + volume_group_ext_id = resource.nutanix_volume_group_v2.example.id + index = 1 + description = "create volume disk test" + disk_size_bytes = 5368709120 + + disk_data_source_reference { + name = "disk1" + ext_id = var.disk_data_source_ref_ext_id + entity_type = "STORAGE_CONTAINER" + uris = ["uri1", "uri2"] + } + + disk_storage_features { + flash_mode { + is_enabled = false + } + } + + lifecycle { + ignore_changes = [ + disk_data_source_reference, links + ] + } +} + +# Get the details of a Volume Disk attached to the Volume Group. +data "nutanix_volume_group_disk_v2" "example"{ + volume_group_ext_id = resource.nutanix_volume_group_v2.example.id + ext_id = resource.nutanix_volume_group_disk_v2.example.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `volume_group_ext_id `: -(Required) The external identifier of the Volume Group. +* `ext_id `: -(Required) The external identifier of the Volume Disk. + + +## Attributes Reference + +The following attributes are exported: +* `tenant_id`: - A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server). +* `ext_id`: - A globally unique identifier of an instance that is suitable for external consumption. +* `links`: - A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. +* `index`: - Index of the disk in a Volume Group. This field is optional and immutable. +* `disk_size_bytes`: - ize of the disk in bytes. This field is mandatory during Volume Group creation if a new disk is being created on the storage container. +* `storage_container_id`: - Storage container on which the disk must be created. This is a read-only field. +* `description`: - Volume Disk description. +* `disk_data_source_reference`: - Disk Data Source Reference. +* `disk_storage_features`: - Storage optimization features which must be enabled on the Volume Disks. This is an optional field. If omitted, the disks will honor the Volume Group specific storage features setting. + +#### Links + +The links attribute supports the following: + +* `href`: - The URL at which the entity described by the link can be accessed. +* `rel`: - A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of "self" identifies the URL for the object. + +#### Disk Data Source Reference + +The disk_data_source_reference attribute supports the following: + +* `ext_id`: - The external identifier of the Data Source Reference. +* `name`: - The name of the Data Source Reference.bled for the Volume Group. +* `uris`: - The uri list of the Data Source Reference. +* `entity_type`: - The Entity Type of the Data Source Reference. + +#### Disk Storage Features + +The disk_storage_features attribute supports the following: + +* `flash_mode`: - this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks. + +##### Flash Mode + +The flash mode features attribute supports the following: + +* `is_enabled`: - Indicates whether the flash mode is enabled for the Volume Group Disk. + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/d/nutanix_volume_group_disks_v4.html.markdown b/website/docs/d/nutanix_volume_group_disks_v4.html.markdown new file mode 100644 index 000000000..01864346a --- /dev/null +++ b/website/docs/d/nutanix_volume_group_disks_v4.html.markdown @@ -0,0 +1,149 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_group_disks_v2" +sidebar_current: "docs-nutanix-datasource-volume-group-disks-v4" +description: |- + Describes a List all the Volume Disks attached to the Volume Group. +--- + +# nutanix_volume_group_disks_v2 + +Query the list of disks corresponding to a Volume Group identified by {volumeGroupExtId}. +## Example Usage + +```hcl + +data "nutanix_clusters" "clusters"{} + +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +resource "nutanix_volume_group_v2" "example"{ + name = "test_volume_group" + description = "Test Volume group with min spec and no Auth" + should_load_balance_vm_attachments = false + sharing_status = "SHARED" + target_name = "volumegroup-test-0" + created_by = "Test" + cluster_reference = local.cluster1 + iscsi_features { + enabled_authentications = "CHAP" + target_secret = "1234567891011" + } + + storage_features { + flash_mode { + is_enabled = true + } + } + usage_type = "USER" + is_hidden = false + + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } +} + + +# Attach a volume group disk to the pervious volume group +resource "nutanix_volume_group_disk_v2" "example"{ + volume_group_ext_id = resource.nutanix_volume_group_v2.example.id + index = 1 + description = "create volume disk test" + disk_size_bytes = 5368709120 + + disk_data_source_reference { + name = "disk1" + ext_id = var.disk_data_source_ref_ext_id + entity_type = "STORAGE_CONTAINER" + uris = ["uri1", "uri2"] + } + + disk_storage_features { + flash_mode { + is_enabled = false + } + } + + lifecycle { + ignore_changes = [ + disk_data_source_reference, links + ] + } +} + +# List all the Volume Disks attached to the Volume Group. +data "nutanix_volume_group_disks_v2" "example"{ + volume_group_ext_id = resource.nutanix_volume_group_v2.example.id + + filter = "startswith(storageContainerId, var.filter_value)" + limit = 1 +} + + +``` + +## Argument Reference + +The following arguments are supported: + +* `volume_group_ext_id`: -(Required) The external identifier of the Volume Group. +* `page`: - A query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. +* `limit` : A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set. +* `filter` : A URL query parameter that allows clients to filter a collection of resources. The expression specified with \$filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Expression specified with the \$filter must conform to the OData V4.01 URL conventions. For example, filter '\$filter=name eq 'karbon-ntnx-1.0' would filter the result on cluster name 'karbon-ntnx1.0', filter '\$filter=startswith(name, 'C')' would filter on cluster name starting with 'C'. The filter can be applied to the following fields: storageContainerId. +* `orderby` : A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default. For example, '\$orderby=templateName desc' would get all templates sorted by templateName in descending order. The orderby can be applied to the following fields: diskSizeBytes. +* `expand` : A URL query parameter that allows clients to request related resources when a resource that satisfies a particular request is retrieved. Each expanded item is evaluated relative to the entity containing the property being expanded. Other query options can be applied to an expanded property by appending a semicolon-separated list of query options, enclosed in parentheses, to the property name. Permissible system query options are \$filter, \$select and \$orderby. The following expansion keys are supported. The expand can be applied to the following fields: clusterReference, metadata. +* `select` : A query parameter that allows clients to request a specific set of properties for each entity or complex type. Expression specified with the \$select must conform to the OData V4.01 URL conventions. If a \$select expression consists of a single select item that is an asterisk (i.e., \*), then all properties on the matching resource will be returned. The select can be applied to the following fields: extId, storageContainerId. + +## Attributes Reference +The following attributes are exported: + +* `disks`: - List of disks corresponding to a Volume Group identified by {volumeGroupExtId}. + +### Disks + +* `tenant_id`: - A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server). +* `ext_id`: - A globally unique identifier of an instance that is suitable for external consumption. +* `links`: - A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. +* `index`: - Index of the disk in a Volume Group. This field is optional and immutable. +* `disk_size_bytes`: - ize of the disk in bytes. This field is mandatory during Volume Group creation if a new disk is being created on the storage container. +* `storage_container_id`: - Storage container on which the disk must be created. This is a read-only field. +* `description`: - Volume Disk description. +* `disk_data_source_reference`: - Disk Data Source Reference. +* `disk_storage_features`: - Storage optimization features which must be enabled on the Volume Disks. This is an optional field. If omitted, the disks will honor the Volume Group specific storage features setting. + +#### Links + +The links attribute supports the following: + +* `href`: - The URL at which the entity described by the link can be accessed. +* `rel`: - A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of "self" identifies the URL for the object. + +#### Disk Data Source Reference + +The disk_data_source_reference attribute supports the following: + +* `ext_id`: - The external identifier of the Data Source Reference. +* `name`: - The name of the Data Source Reference.bled for the Volume Group. +* `uris`: - The uri list of the Data Source Reference. +* `entity_type`: - The Entity Type of the Data Source Reference. + +#### Disk Storage Features + +The disk_storage_features attribute supports the following: + +* `flash_mode`: - this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks. + +##### Flash Mode + +The flash mode features attribute supports the following: + +* `is_enabled`: - Indicates whether the flash mode is enabled for the Volume Group Disk. + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/d/nutanix_volume_group_iscsi_clients_v4.html.markdown b/website/docs/d/nutanix_volume_group_iscsi_clients_v4.html.markdown new file mode 100644 index 000000000..8b9e9cf66 --- /dev/null +++ b/website/docs/d/nutanix_volume_group_iscsi_clients_v4.html.markdown @@ -0,0 +1,93 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_group_iscsi_clients_v2" +sidebar_current: "docs-nutanix-datasource-volume-group-iscsi-client-v4" +description: |- + Fetches the iSCSI client details identified by {extId}. +--- + +# nutanix_volume_group_iscsi_clients_v2 + +Fetches the iSCSI client details identified by {extId}. + +## Example Usage + +```hcl + +#pull clusters data +data "nutanix_clusters" "clusters" { +} + +#get desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +resource "nutanix_volume_group_v2" "test" { + name = "test_volume_group" + cluster_reference = local.cluster1 +} + +# attach iscsi client to the volume group +resource "nutanix_volume_group_iscsi_clients_v2" "vg_iscsi_example" { + vg_ext_id = resource.nutanix_volume_group_v2.test.id + ext_id = var.vg_iscsi_ext_id + iscsi_initiator_name = var.vg_iscsi_initiator_name +} + +data "nutanix_volume_group_iscsi_clients_v2" "volume_group" { + ext_id = resource.nutanix_volume_group_v2.test.id +} + + +``` + +## Argument Reference + +The following arguments are supported: + +* `ext_id `: -(Required) The external identifier of the Volume Group. + +* `page`: - A query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. + +* `limit` : A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set. + +* `filter` : A URL query parameter that allows clients to filter a collection of resources. The expression specified with \$filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Expression specified with the \$filter must conform to the OData V4.01 URL conventions. For example, filter '\$filter=name eq 'karbon-ntnx-1.0' would filter the result on cluster name 'karbon-ntnx1.0', filter '\$filter=startswith(name, 'C')' would filter on cluster name starting with 'C'. The filter can be applied to the following fields: clusterReference, extId. + +* `orderby` : A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default. For example, '\$orderby=templateName desc' would get all templates sorted by templateName in descending order. The orderby can be applied to the following fields: iscsiClient, extId. + +* `expand` : A URL query parameter that allows clients to request related resources when a resource that satisfies a particular request is retrieved. Each expanded item is evaluated relative to the entity containing the property being expanded. Other query options can be applied to an expanded property by appending a semicolon-separated list of query options, enclosed in parentheses, to the property name. Permissible system query options are \$filter, \$select and \$orderby. The following expansion keys are supported. The expand can be applied to the following fields: iscsiClient. + +* `select` : A query parameter that allows clients to request a specific set of properties for each entity or complex type. Expression specified with the \$select must conform to the OData V4.01 URL conventions. If a \$select expression consists of a single select item that is an asterisk (i.e., \*), then all properties on the matching resource will be returned. The select can be applied to the following fields: clusterReference, extId. + + + + +## Attributes Reference + +The following attributes are exported: + +* `iscsi_clients`: - List of the iSCSI attachments associated with the given Volume Group. + +### iscsi_clients + +The iscsi_clients entities attribute element contains the followings attributes: + + +* `tenant_id`: - A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server). +* `ext_id`: - A globally unique identifier of an instance that is suitable for external consumption. +* `links`: - A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. +* `cluster_reference`: - The UUID of the cluster that will host the Volume Group. + +#### Links + +The links attribute supports the following: + +* `href`: - The URL at which the entity described by the link can be accessed. +* `rel`: - A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of "self" identifies the URL for the object. + + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/d/nutanix_volume_group_v4.html.markdown b/website/docs/d/nutanix_volume_group_v4.html.markdown new file mode 100644 index 000000000..743b195e0 --- /dev/null +++ b/website/docs/d/nutanix_volume_group_v4.html.markdown @@ -0,0 +1,94 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_group_v2" +sidebar_current: "docs-nutanix-datasource-volume-group-v4" +description: |- + Describes a Volume Group. +--- + +# nutanix_volume_group_v2 + +Query the Volume Group identified by {extId}. + + +## Example Usage + +```hcl + +#pull clusters data +data "nutanix_clusters" "clusters" { +} + +#get desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +resource "nutanix_volume_group_v2" "test" { + name = "test_volume_group" + cluster_reference = local.cluster1 +} + +data "nutanix_volume_group_v2" "volume_group" { + ext_id = resource.nutanix_volume_group_v2.test.id +} + + +``` + +## Argument Reference + +The following arguments are supported: + +* `ext_id `: -(Required) The external identifier of the Volume Group. + + +## Attributes Reference + +The following attributes are exported: + +* `tenant_id`: - A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server). +* `ext_id`: - A globally unique identifier of an instance that is suitable for external consumption. +* `links`: - A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. +* `name`: -(Required) Volume Group name. This is an optional field. +* `description`: - Volume Group description. This is an optional field. +* `should_load_balance_vm_attachments`: - Indicates whether to enable Volume Group load balancing for VM attachments. This cannot be enabled if there are iSCSI client attachments already associated with the Volume Group, and vice-versa. This is an optional field. +* `sharing_status`: - Indicates whether the Volume Group can be shared across multiple iSCSI initiators. The mode cannot be changed from SHARED to NOT_SHARED on a Volume Group with multiple attachments. Similarly, a Volume Group cannot be associated with more than one attachment as long as it is in exclusive mode. This is an optional field. Valid values are SHARED, NOT_SHARED +* `target_name`: - Name of the external client target that will be visible and accessible to the client. +* `enabled_authentications`: - The authentication type enabled for the Volume Group. Valid values are CHAP, NONE +* `iscsi_features`: - iSCSI specific settings for the Volume Group. +* `created_by`: - Service/user who created this Volume Group. +* `cluster_reference`: - The UUID of the cluster that will host the Volume Group. +* `storage_features`: - Storage optimization features which must be enabled on the Volume Group. +* `usage_type`: - Expected usage type for the Volume Group. This is an indicative hint on how the caller will consume the Volume Group. Valid values are BACKUP_TARGET, INTERNAL, TEMPORARY, USER +* `is_hidden`: - Indicates whether the Volume Group is meant to be hidden or not. + +### Links + +The links attribute supports the following: + +* `href`: - The URL at which the entity described by the link can be accessed. +* `rel`: - A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of "self" identifies the URL for the object. + +### Iscsi Features + +The iscsi_features attribute supports the following: + +* `enabled_authentications`: - The authentication type enabled for the Volume Group. + +### Storage Features + +The storage features attribute supports the following: + +* `flash_mode`: - this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks. + +#### Flash Mode + +The flash mode features attribute supports the following: + +* `is_enabled`: - Indicates whether the flash mode is enabled for the Volume Group. + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/d/nutanix_volume_group_vms_v4.markdown b/website/docs/d/nutanix_volume_group_vms_v4.markdown new file mode 100644 index 000000000..aef8d9c36 --- /dev/null +++ b/website/docs/d/nutanix_volume_group_vms_v4.markdown @@ -0,0 +1,78 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_group_vms_v2" +sidebar_current: "docs-nutanix-datasource-volume-groups-v4" +description: |- + Describes a list of VM attachments for a Volume Group identified by {extId}. +--- + +# nutanix_volume_group_vms_v2 + +Describes a list of VM attachments for a Volume Group identified by {extId}. + +## Example Usage + +```hcl +data "nutanix_clusters" "clusters" { +} + +#get desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +resource "nutanix_volume_group_v2" "test" { + name = "test_volume_group" + cluster_reference = local.cluster1 +} + +resource "nutanix_volume_group_vm_v2" "vg_vm_test" { + volume_group_ext_id = resource.nutanix_volume_group_v2.test.id + vm_ext_id = var.vg_vm_ext_id +} + +# List all the VM attachments for a Volume Group. +data "nutanix_volume_group_vms_v2" "vg_vm_test" { + ext_id = resource.nutanix_volume_group_v2.test.id +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `ext_id`: -(Required) The external identifier of the volume group. + +* `page`: - A query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. + +* `limit` : A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set. + +* `filter` : A URL query parameter that allows clients to filter a collection of resources. The expression specified with $filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Expression specified with the $filter must conform to the OData V4.01 URL conventions. For example, filter '$filter=name eq 'karbon-ntnx-1.0' would filter the result on cluster name 'karbon-ntnx1.0', filter '$filter=startswith(name, 'C')' would filter on cluster name starting with 'C'. The filter can be applied to the following fields: clusterReference, extId. + +* `orderby` : A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default. For example, '$orderby=templateName desc' would get all templates sorted by templateName in descending order. The orderby can be applied to the following fields: clusterReference, extId. + +## Attributes Reference +The following attributes are exported: + +* `vms_attachments`: - List of Volume Groups. + +### vms_attachments + +The vms attachments entities attribute element contains the followings attributes: + +* `tenant_id`: - A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server). +* `ext_id`: - A globally unique identifier of an instance that is suitable for external consumption. +* `links`: - A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. + +#### Links + +The links attribute supports the following: + +* `href`: - The URL at which the entity described by the link can be accessed. +* `rel`: - A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of "self" identifies the URL for the object. + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/d/nutanix_volume_groups_v4.html.markdown b/website/docs/d/nutanix_volume_groups_v4.html.markdown new file mode 100644 index 000000000..7f6f2037c --- /dev/null +++ b/website/docs/d/nutanix_volume_groups_v4.html.markdown @@ -0,0 +1,82 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_groups_v2" +sidebar_current: "docs-nutanix-datasource-volume-groups-v4" +description: |- + Describes a List all the Volume Groups. +--- + +# nutanix_volume_groups_v2 + +Describes a List all the Volume Groups. + +## Example Usage + +```hcl +data "nutanix_volume_groups_v2" "volume_groups" {} + +data "nutanix_volume_groups_v2" "test" { + filter = "name eq 'volume_group_test'" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `page`: - A query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. +* `limit` : A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set. +* `filter` : A URL query parameter that allows clients to filter a collection of resources. The expression specified with $filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Expression specified with the $filter must conform to the OData V4.01 URL conventions. For example, filter '$filter=name eq 'karbon-ntnx-1.0' would filter the result on cluster name 'karbon-ntnx1.0', filter '$filter=startswith(name, 'C')' would filter on cluster name starting with 'C'. The filter can be applied to the following fields: clusterReference, extId, name. +* `orderby` : A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default. For example, '$orderby=templateName desc' would get all templates sorted by templateName in descending order. The orderby can be applied to the following fields: clusterReference, extId, name. +* `expand` : A URL query parameter that allows clients to request related resources when a resource that satisfies a particular request is retrieved. Each expanded item is evaluated relative to the entity containing the property being expanded. Other query options can be applied to an expanded property by appending a semicolon-separated list of query options, enclosed in parentheses, to the property name. Permissible system query options are $filter, $select and $orderby. The following expansion keys are supported. The expand can be applied to the following fields: clusterReference, metadata. +* `select` : A query parameter that allows clients to request a specific set of properties for each entity or complex type. Expression specified with the $select must conform to the OData V4.01 URL conventions. If a $select expression consists of a single select item that is an asterisk (i.e., \*), then all properties on the matching resource will be returned. The select can be applied to the following fields: clusterReference, extId, name. + +## Attributes Reference +The following attributes are exported: + +* `volumes`: - List of Volume Groups. + +### Volumes + +* `tenant_id`: - A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server). +* `ext_id`: - A globally unique identifier of an instance that is suitable for external consumption. +* `links`: - A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. +* `name`: -(Required) Volume Group name. This is an optional field. +* `description`: - Volume Group description. This is an optional field. +* `should_load_balance_vm_attachments`: - Indicates whether to enable Volume Group load balancing for VM attachments. This cannot be enabled if there are iSCSI client attachments already associated with the Volume Group, and vice-versa. This is an optional field. +* `sharing_status`: - Indicates whether the Volume Group can be shared across multiple iSCSI initiators. The mode cannot be changed from SHARED to NOT_SHARED on a Volume Group with multiple attachments. Similarly, a Volume Group cannot be associated with more than one attachment as long as it is in exclusive mode. This is an optional field. Valid values are SHARED, NOT_SHARED +* `target_name`: - Name of the external client target that will be visible and accessible to the client. +* `enabled_authentications`: - The authentication type enabled for the Volume Group. Valid values are CHAP, NONE +* `iscsi_features`: - iSCSI specific settings for the Volume Group. +* `created_by`: - Service/user who created this Volume Group. +* `cluster_reference`: - The UUID of the cluster that will host the Volume Group. +* `storage_features`: - Storage optimization features which must be enabled on the Volume Group. +* `usage_type`: - Expected usage type for the Volume Group. This is an indicative hint on how the caller will consume the Volume Group. Valid values are BACKUP_TARGET, INTERNAL, TEMPORARY, USER +* `is_hidden`: - Indicates whether the Volume Group is meant to be hidden or not. + +#### Links + +The links attribute supports the following: + +* `href`: - The URL at which the entity described by the link can be accessed. +* `rel`: - A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of "self" identifies the URL for the object. + +#### Iscsi Features + +The iscsi_features attribute supports the following: + +* `enabled_authentications`: - The authentication type enabled for the Volume Group. + +#### Storage Features + +The storage features attribute supports the following: + +* `flash_mode`: - this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks. + +##### Flash Mode + +The flash mode features attribute supports the following: + +* `is_enabled`: - Indicates whether the flash mode is enabled for the Volume Group. + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/d/nutanix_volume_iscsi_client_v4.html.markdown b/website/docs/d/nutanix_volume_iscsi_client_v4.html.markdown new file mode 100644 index 000000000..15d78e4cf --- /dev/null +++ b/website/docs/d/nutanix_volume_iscsi_client_v4.html.markdown @@ -0,0 +1,90 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_iscsi_client_v2" +sidebar_current: "docs-nutanix-datasource-volume-iscsi-client-v4" +description: |- + Describes iSCSI client details identified by {extId}. +--- + +# nutanix_volume_iscsi_clients_v2 + +Fetches the iSCSI client details identified by {extId}. + + + +## Example Usage + +```hcl +data "nutanix_volume_iscsi_client_v2" "example"{ + ext_id = var.iscsi_client_ext_id + } + +``` + +## Argument Reference +The following arguments are supported: + + +* `ext_id`: -(Required) A query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. + +## Attributes Reference +The following attributes are exported: + + +* `tenant_id`: - A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server). +* `ext_id`: - A globally unique identifier of an instance that is suitable for external consumption. +* `links`: - A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. +* `iscsi_initiator_name`: -iSCSI initiator name. During the attach operation, exactly one of iscsiInitiatorName and iscsiInitiatorNetworkId must be specified. This field is immutable. +* `iscsi_initiator_network_id`: - An unique address that identifies a device on the internet or a local network in IPv4/IPv6 format or a Fully Qualified Domain Name. +* `client_secret`: -(Optional) iSCSI initiator client secret in case of CHAP authentication. This field should not be provided in case the authentication type is not set to CHAP. +* `enabled_authentications`: -(Optional) (Optional) The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided. Valid values are CHAP, NONE +* `num_virtual_targets`: -(Optional) Number of virtual targets generated for the iSCSI target. This field is immutable. +* `attachment_site`: -(Optional) The site where the Volume Group attach operation should be processed. This is an optional field. This field may only be set if Metro DR has been configured for this Volume Group. Valid values are SECONDARY, PRIMARY. + + +#### Links + +The links attribute supports the following: + +* `href`: - The URL at which the entity described by the link can be accessed. +* `rel`: - A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of "self" identifies the URL for the object. + +#### iscsi initiator network id + +The iscsi_initiator_network_id attribute supports the following: + +* `ipv4`: - An unique address that identifies a device on the internet or a local network in IPv4 format. +* `ipv6`: - An unique address that identifies a device on the internet or a local network in IPv6 format. +* `fqdn`: - A fully qualified domain name that specifies its exact location in the tree hierarchy of the Domain Name System. + +##### IPV4 + +The ipv4 attribute supports the following: + +* `value`: - An unique address that identifies a device on the internet or a local network in IPv4 format. +* `prefix_length`: - The prefix length of the network to which this host IPv4 address belongs. + +##### IPV6 + +The ipv6 attribute supports the following: + +* `value`: - An unique address that identifies a device on the internet or a local network in IPv6 format. +* `prefix_length`: - The prefix length of the network to which this host IPv6 address belongs. + +##### FQDN + +The fqdn attribute supports the following: + +* `value`: - The fully qualified domain name. + + +#### Attached Targets + +The attached_targets attribute supports the following: + +* `num_virtual_targets`: - Number of virtual targets generated for the iSCSI target. This field is immutable. +* `iscsi_target_name`: - Name of the iSCSI target that the iSCSI client is connected to. This is a read-only field. + + + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/d/nutanix_volume_iscsi_clients_v4.html.markdown b/website/docs/d/nutanix_volume_iscsi_clients_v4.html.markdown new file mode 100644 index 000000000..d3eec8bdc --- /dev/null +++ b/website/docs/d/nutanix_volume_iscsi_clients_v4.html.markdown @@ -0,0 +1,95 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_iscsi_clients_v2" +sidebar_current: "docs-nutanix-datasource-volume-iscsi-clients-v4" +description: |- + Describes aList all the iSCSI clients. +--- + +# nutanix_volume_iscsi_clients_v2 + +Fetches the list of iSCSI clients. + + +## Example Usage + +```hcl +data "nutanix_volume_iscsi_clients_v2" "volume_groups"{} + +``` + +## Argument Reference + +The following arguments are supported: + +* `page`: - A query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. +* `limit` : A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set. +* `filter` : A URL query parameter that allows clients to filter a collection of resources. The expression specified with \$filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Expression specified with the \$filter must conform to the OData V4.01 URL conventions. For example, filter '\$filter=name eq 'karbon-ntnx-1.0' would filter the result on cluster name 'karbon-ntnx1.0', filter '\$filter=startswith(name, 'C')' would filter on cluster name starting with 'C'. The filter can be applied to the following fields: clusterReference, extId. +* `orderby` : A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default. For example, '\$orderby=templateName desc' would get all templates sorted by templateName in descending order. The orderby can be applied to the following fields: clusterReference, extId. +* `expand` : A URL query parameter that allows clients to request related resources when a resource that satisfies a particular request is retrieved. Each expanded item is evaluated relative to the entity containing the property being expanded. Other query options can be applied to an expanded property by appending a semicolon-separated list of query options, enclosed in parentheses, to the property name. Permissible system query options are \$filter, \$select and \$orderby. The following expansion keys are supported. The expand can be applied to the following fields: cluster. +* `select` : A query parameter that allows clients to request a specific set of properties for each entity or complex type. Expression specified with the \$select must conform to the OData V4.01 URL conventions. If a \$select expression consists of a single select item that is an asterisk (i.e., \*), then all properties on the matching resource will be returned. The select can be applied to the following fields: clusterReference, extId. + +## Attributes Reference +The following attributes are exported: + +* `iscsi_clients`: - List all the iSCSI clients. + +### Iscsi Clients + +* `tenant_id`: - A globally unique identifier that represents the tenant that owns this entity. The system automatically assigns it, and it and is immutable from an API consumer perspective (some use cases may cause this Id to change - For instance, a use case may require the transfer of ownership of the entity, but these cases are handled automatically on the server). +* `ext_id`: - A globally unique identifier of an instance that is suitable for external consumption. +* `links`: - A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. +* `iscsi_initiator_name`: -iSCSI initiator name. During the attach operation, exactly one of iscsiInitiatorName and iscsiInitiatorNetworkId must be specified. This field is immutable. +* `iscsi_initiator_network_id`: - An unique address that identifies a device on the internet or a local network in IPv4/IPv6 format or a Fully Qualified Domain Name. +* `client_secret`: -(Optional) iSCSI initiator client secret in case of CHAP authentication. This field should not be provided in case the authentication type is not set to CHAP. +* `enabled_authentications`: -(Optional) (Optional) The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided. Valid values are CHAP, NONE +* `num_virtual_targets`: -(Optional) Number of virtual targets generated for the iSCSI target. This field is immutable. +* `attachment_site`: -(Optional) The site where the Volume Group attach operation should be processed. This is an optional field. This field may only be set if Metro DR has been configured for this Volume Group. Valid values are SECONDARY, PRIMARY. + + +#### Links + +The links attribute supports the following: + +* `href`: - The URL at which the entity described by the link can be accessed. +* `rel`: - A name that identifies the relationship of the link to the object that is returned by the URL. The unique value of "self" identifies the URL for the object. + +#### iscsi initiator network id + +The iscsi_initiator_network_id attribute supports the following: + +* `ipv4`: - An unique address that identifies a device on the internet or a local network in IPv4 format. +* `ipv6`: - An unique address that identifies a device on the internet or a local network in IPv6 format. +* `fqdn`: - A fully qualified domain name that specifies its exact location in the tree hierarchy of the Domain Name System. + +##### IPV4 + +The ipv4 attribute supports the following: + +* `value`: - An unique address that identifies a device on the internet or a local network in IPv4 format. +* `prefix_length`: - The prefix length of the network to which this host IPv4 address belongs. + +##### IPV6 + +The ipv6 attribute supports the following: + +* `value`: - An unique address that identifies a device on the internet or a local network in IPv6 format. +* `prefix_length`: - The prefix length of the network to which this host IPv6 address belongs. + +##### FQDN + +The fqdn attribute supports the following: + +* `value`: - The fully qualified domain name. + + +#### Attached Targets + +The attached_targets attribute supports the following: + +* `num_virtual_targets`: - Number of virtual targets generated for the iSCSI target. This field is immutable. +* `iscsi_target_name`: - Name of the iSCSI target that the iSCSI client is connected to. This is a read-only field. + + + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/r/nutanix_volume_group_disk_v4.html.markdown b/website/docs/r/nutanix_volume_group_disk_v4.html.markdown new file mode 100644 index 000000000..fcb2be214 --- /dev/null +++ b/website/docs/r/nutanix_volume_group_disk_v4.html.markdown @@ -0,0 +1,123 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_group_disk_v2" +sidebar_current: "docs-nutanix-resource-volume-group-disk-v4" +description: |- + This operation submits a request to Creates a new Volume Disk. +--- + +# nutanix_volume_group_v2 + +Provides a resource to Creates a new Volume Disk. + +## Example Usage + +```hcl + +data "nutanix_clusters" "clusters"{} + +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +resource "nutanix_volume_group_v2" "example"{ + name = "test_volume_group" + description = "Test Volume group with min spec and no Auth" + should_load_balance_vm_attachments = false + sharing_status = "SHARED" + target_name = "volumegroup-test-0" + created_by = "Test" + cluster_reference = local.cluster1 + iscsi_features { + enabled_authentications = "CHAP" + target_secret = "1234567891011" + } + + storage_features { + flash_mode { + is_enabled = true + } + } + usage_type = "USER" + is_hidden = false + + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } +} + + +# create new volume group disk and attached it to the pervious volume group +resource "nutanix_volume_group_disk_v2" "example"{ + volume_group_ext_id = resource.nutanix_volume_group_v2.example.id + index = 1 + description = "create volume disk test" + disk_size_bytes = 5368709120 + + disk_data_source_reference { + name = "disk1" + ext_id = var.disk_data_source_ref_ext_id + entity_type = "STORAGE_CONTAINER" + uris = ["uri1", "uri2"] + } + + disk_storage_features { + flash_mode { + is_enabled = false + } + } + + lifecycle { + ignore_changes = [ + disk_data_source_reference, links + ] + } +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `volume_group_ext_id `: -(Required) The external identifier of the Volume Group. + +* `ext_id`: - A globally unique identifier of an instance that is suitable for external consumption. + +* `index`: - Index of the disk in a Volume Group. This field is optional and immutable. + +* `disk_size_bytes`: - ize of the disk in bytes. This field is mandatory during Volume Group creation if a new disk is being created on the storage container. + +* `description`: - Volume Disk description. + +* `disk_data_source_reference`: -(Required) Disk Data Source Reference. +* `disk_storage_features`: - Storage optimization features which must be enabled on the Volume Disks. This is an optional field. If omitted, the disks will honor the Volume Group specific storage features setting. + + +#### Disk Data Source Reference + +The disk_data_source_reference attribute supports the following: + +* `ext_id`: - The external identifier of the Data Source Reference. +* `name`: - The name of the Data Source Reference.bled for the Volume Group. +* `uris`: - The uri list of the Data Source Reference. +* `entity_type`: - The Entity Type of the Data Source Reference. + +#### Disk Storage Features + +The disk_storage_features attribute supports the following: + +* `flash_mode`: - this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks. + +##### Flash Mode + +The flash mode features attribute supports the following: + +* `is_enabled`: - Indicates whether the flash mode is enabled for the Volume Group Disk. + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/r/nutanix_volume_group_iscsi_client_v4.html.markdown b/website/docs/r/nutanix_volume_group_iscsi_client_v4.html.markdown new file mode 100644 index 000000000..b204783ac --- /dev/null +++ b/website/docs/r/nutanix_volume_group_iscsi_client_v4.html.markdown @@ -0,0 +1,84 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_group_iscsi_client_v2" +sidebar_current: "docs-nutanix-resource-volume-group-iscsi-client-v4" +description: |- + This operation submits a request to Attaches iSCSI initiator to a Volume Group identified by {extId}. +--- + +# nutanix_volume_group_iscsi_client_v2 +Attaches iSCSI initiator to a Volume Group identified by {extId}. + +## Example Usage + +``` hcl +#pull clusters data +data "nutanix_clusters" "clusters"{ +} + +#get desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +resource "nutanix_volume_group_v2" "test"{ + name = "test_volume_group" + cluster_reference = local.cluster1 +} + +# attach iscsi client to the volume group +resource "nutanix_volume_group_iscsi_clients_v2" "vg_iscsi_example"{ + vg_ext_id = resource.nutanix_volume_group_v2.test.id + ext_id = var.vg_iscsi_ext_id + iscsi_initiator_name = var.vg_iscsi_initiator_name +} + + +``` + +## Argument Reference +The following arguments are supported: + + +* `vg_ext_id`: -(Required) The external identifier of the volume group. +* `ext_id`: -(Required) A globally unique identifier of an instance that is suitable for external consumption. +* `iscsi_initiator_name`: -iSCSI initiator name. During the attach operation, exactly one of iscsiInitiatorName and iscsiInitiatorNetworkId must be specified. This field is immutable. +* `iscsi_initiator_network_id`: - An unique address that identifies a device on the internet or a local network in IPv4/IPv6 format or a Fully Qualified Domain Name. +* `client_secret`: -(Optional) iSCSI initiator client secret in case of CHAP authentication. This field should not be provided in case the authentication type is not set to CHAP. +* `enabled_authentications`: -(Optional) (Optional) The authentication type enabled for the Volume Group. This is an optional field. If omitted, authentication is not configured for the Volume Group. If this is set to CHAP, the target/client secret must be provided. Valid values are CHAP, NONE +* `num_virtual_targets`: -(Optional) Number of virtual targets generated for the iSCSI target. This field is immutable. +* `attachment_site`: -(Optional) The site where the Volume Group attach operation should be processed. This is an optional field. This field may only be set if Metro DR has been configured for this Volume Group. Valid values are SECONDARY, PRIMARY. + +#### iscsi initiator network id + +The iscsi_initiator_network_id attribute supports the following: + +* `ipv4`: - An unique address that identifies a device on the internet or a local network in IPv4 format. +* `ipv6`: - An unique address that identifies a device on the internet or a local network in IPv6 format. +* `fqdn`: - A fully qualified domain name that specifies its exact location in the tree hierarchy of the Domain Name System. + +##### IPV4 + +The ipv4 attribute supports the following: + +* `value`: - An unique address that identifies a device on the internet or a local network in IPv4 format. +* `prefix_length`: - The prefix length of the network to which this host IPv4 address belongs. + +##### IPV6 + +The ipv6 attribute supports the following: + +* `value`: - An unique address that identifies a device on the internet or a local network in IPv6 format. +* `prefix_length`: - The prefix length of the network to which this host IPv6 address belongs. + +##### FQDN + +The fqdn attribute supports the following: + +* `value`: - The fully qualified domain name. + + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/r/nutanix_volume_group_v4.html.markdown b/website/docs/r/nutanix_volume_group_v4.html.markdown new file mode 100644 index 000000000..78fa05019 --- /dev/null +++ b/website/docs/r/nutanix_volume_group_v4.html.markdown @@ -0,0 +1,96 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_group_v2" +sidebar_current: "docs-nutanix-resource-volume-group-v4" +description: |- + This operation submits a request to Create a new Volume Group. +--- + +# nutanix_volume_group_v2 + +Provides a resource to Create a new Volume Group. + +## Example Usage + +``` hcl +#pull clusters data +data "nutanix_clusters" "clusters"{ +} + +#get desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +resource "nutanix_volume_group_v2" "volume_group_example"{ + name = "volume_group_test" + description = "Test Create Volume group with spec" + should_load_balance_vm_attachments = false + sharing_status = "SHARED" + target_name = "volumegroup-test-001234" + created_by = "example" + cluster_reference = local.cluster1 + iscsi_features { + enabled_authentications = "CHAP" + target_secret = "123456789abc" + } + + storage_features { + flash_mode { + is_enabled = true + } + } + usage_type = "USER" + is_hidden = false + + # ignore changes to target_secret, target secret will not be returned in terraform plan output + lifecycle { + ignore_changes = [ + iscsi_features[0].target_secret + ] + } +} + +``` + +## Argument Reference +The following arguments are supported: + + +* `ext_id`: -(Optional) A globally unique identifier of an instance that is suitable for external consumption. +* `name`: -(Required) Volume Group name. This is an optional field. +* `description`: -(Optional) Volume Group description. This is an optional field. +* `should_load_balance_vm_attachments`: -(Optional) Indicates whether to enable Volume Group load balancing for VM attachments. This cannot be enabled if there are iSCSI client attachments already associated with the Volume Group, and vice-versa. This is an optional field. +* `sharing_status`: -(Optional) Indicates whether the Volume Group can be shared across multiple iSCSI initiators. The mode cannot be changed from SHARED to NOT_SHARED on a Volume Group with multiple attachments. Similarly, a Volume Group cannot be associated with more than one attachment as long as it is in exclusive mode. This is an optional field. Valid values are SHARED, NOT_SHARED +* `target_name`: -(Optional) Name of the external client target that will be visible and accessible to the client. +* `enabled_authentications`: -(Optional) The authentication type enabled for the Volume Group. Valid values are CHAP, NONE +* `iscsi_features`: -(Optional) iSCSI specific settings for the Volume Group. +* `created_by`: -(Optional) Service/user who created this Volume Group. +* `cluster_reference`: -(Required) The UUID of the cluster that will host the Volume Group. +* `storage_features`: -(Optional) Storage optimization features which must be enabled on the Volume Group. +* `usage_type`: -(Optional) Expected usage type for the Volume Group. This is an indicative hint on how the caller will consume the Volume Group. Valid values are BACKUP_TARGET, INTERNAL, TEMPORARY, USER +* `is_hidden`: -(Optional) Indicates whether the Volume Group is meant to be hidden or not. + + +### Iscsi Features + +The iscsi_features attribute supports the following: + +* `enabled_authentications`: - The authentication type enabled for the Volume Group. + +### Storage Features + +The storage features attribute supports the following: + +* `flash_mode`: - this field will avoid down migration of data from the hot tier unless the overrides field is specified for the virtual disks. + +#### Flash Mode + +The flash mode features attribute supports the following: + +* `is_enabled`: - Indicates whether the flash mode is enabled for the Volume Group. + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1). diff --git a/website/docs/r/nutanix_volume_group_vm_v4.markdown b/website/docs/r/nutanix_volume_group_vm_v4.markdown new file mode 100644 index 000000000..547492fc9 --- /dev/null +++ b/website/docs/r/nutanix_volume_group_vm_v4.markdown @@ -0,0 +1,48 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_volume_group_vm_v2" +sidebar_current: "docs-nutanix-resource-volume-group-vm-attachments-v4" +description: |- + This operation submits a request to Attaches VM to a Volume Group identified by {extId}. +--- + +# nutanix_volume_group_vm_v2 + +Provides a resource to Create a new Volume Group. + +## Example Usage + +``` hcl +data "nutanix_clusters" "clusters"{ +} + +#get desired cluster data from setup +locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] +} + +resource "nutanix_volume_group_v2" "test"{ + name = "test_volume_group" + cluster_reference = local.cluster1 +} + +resource "nutanix_volume_group_vm_v2" "vg_vm_example"{ + volume_group_ext_id = resource.nutanix_volume_group_v2.test.id + vm_ext_id = var.vg_vm_ext_id +} + +``` + +## Argument Reference +The following arguments are supported: + + +* `volume_group_ext_id`: -(Required) The external identifier of the volume group. +* `vm_ext_id`: -(Required) A globally unique identifier of an instance that is suitable for external consumption. +* `index`: -(Optional) The index on the SCSI bus to attach the VM to the Volume Group. + + +See detailed information in [Nutanix Volumes](https://developers.nutanix.com/api-reference?namespace=volumes&version=v4.0.b1).