provider/openstack: openstack_compute_volume_attach_v2 resource

This commit adds the openstack_compute_volume_attach_v2 resource. This
resource enables a volume to be attached to an instance by using the
OpenStack Compute (Nova) v2 volumeattach API.
This commit is contained in:
Joe Topjian 2016-11-10 16:00:37 +00:00
parent bda84e03f7
commit 24d7ada0e4
6 changed files with 393 additions and 0 deletions

View File

@ -0,0 +1,29 @@
package openstack
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccComputeV2VolumeAttach_importBasic(t *testing.T) {
resourceName := "openstack_compute_volume_attach_v2.va_1"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeV2VolumeAttachDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeV2VolumeAttach_basic,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"region"},
},
},
})
}

View File

@ -135,6 +135,7 @@ func Provider() terraform.ResourceProvider {
"openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(),
"openstack_compute_servergroup_v2": resourceComputeServerGroupV2(), "openstack_compute_servergroup_v2": resourceComputeServerGroupV2(),
"openstack_compute_floatingip_v2": resourceComputeFloatingIPV2(), "openstack_compute_floatingip_v2": resourceComputeFloatingIPV2(),
"openstack_compute_volume_attach_v2": resourceComputeVolumeAttachV2(),
"openstack_fw_firewall_v1": resourceFWFirewallV1(), "openstack_fw_firewall_v1": resourceFWFirewallV1(),
"openstack_fw_policy_v1": resourceFWPolicyV1(), "openstack_fw_policy_v1": resourceFWPolicyV1(),
"openstack_fw_rule_v1": resourceFWRuleV1(), "openstack_fw_rule_v1": resourceFWRuleV1(),

View File

@ -0,0 +1,186 @@
package openstack
import (
"fmt"
"log"
"strings"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceComputeVolumeAttachV2() *schema.Resource {
return &schema.Resource{
Create: resourceComputeVolumeAttachV2Create,
Read: resourceComputeVolumeAttachV2Read,
Delete: resourceComputeVolumeAttachV2Delete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"region": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
},
"instance_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"volume_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"device": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Optional: true,
},
},
}
}
func resourceComputeVolumeAttachV2Create(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
computeClient, err := config.computeV2Client(d.Get("region").(string))
if err != nil {
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
}
instanceId := d.Get("instance_id").(string)
volumeId := d.Get("volume_id").(string)
var device string
if v, ok := d.GetOk("device"); ok {
device = v.(string)
}
attachOpts := volumeattach.CreateOpts{
Device: device,
VolumeID: volumeId,
}
log.Printf("[DEBUG] Creating volume attachment: %#v", attachOpts)
attachment, err := volumeattach.Create(computeClient, instanceId, attachOpts).Extract()
if err != nil {
return err
}
log.Printf("[DEBUG] Created volume attachment: %#v", attachment)
// Use the instance ID and attachment ID as the resource ID.
// This is because an attachment cannot be retrieved just by its ID alone.
id := fmt.Sprintf("%s/%s", instanceId, attachment.ID)
d.SetId(id)
return resourceComputeVolumeAttachV2Read(d, meta)
}
func resourceComputeVolumeAttachV2Read(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
computeClient, err := config.computeV2Client(d.Get("region").(string))
if err != nil {
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
}
instanceId, attachmentId, err := parseComputeVolumeAttachmentId(d.Id())
if err != nil {
return err
}
attachment, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract()
if err != nil {
return err
}
log.Printf("[DEBUG] Retrieved volume attachment: %#v", attachment)
d.Set("instance_id", attachment.ServerID)
d.Set("volume_id", attachment.VolumeID)
d.Set("device", attachment.Device)
return nil
}
func resourceComputeVolumeAttachV2Delete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
computeClient, err := config.computeV2Client(d.Get("region").(string))
if err != nil {
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
}
instanceId, attachmentId, err := parseComputeVolumeAttachmentId(d.Id())
if err != nil {
return err
}
stateConf := &resource.StateChangeConf{
Pending: []string{""},
Target: []string{"DETACHED"},
Refresh: volumeDetachRefreshFunc(computeClient, instanceId, attachmentId),
Timeout: 10 * time.Minute,
Delay: 15 * time.Second,
MinTimeout: 15 * time.Second,
}
if _, err = stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error detaching OpenStack volume: %s", err)
}
return nil
}
func volumeDetachRefreshFunc(computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
log.Printf("[DEBUG] Attempting to detach OpenStack volume %s from instance %s", attachmentId, instanceId)
va, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
return va, "DETACHED", nil
}
return va, "", err
}
err = volumeattach.Delete(computeClient, instanceId, attachmentId).ExtractErr()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
return va, "DETACHED", nil
}
if _, ok := err.(gophercloud.ErrDefault400); ok {
return nil, "", nil
}
return nil, "", err
}
log.Printf("[DEBUG] OpenStack Volume Attachment (%s) is still active.", attachmentId)
return nil, "", nil
}
}
func parseComputeVolumeAttachmentId(id string) (string, string, error) {
idParts := strings.Split(id, "/")
if len(idParts) < 2 {
return "", "", fmt.Errorf("Unable to determine volume attachment ID")
}
instanceId := idParts[0]
attachmentId := idParts[1]
return instanceId, attachmentId, nil
}

View File

@ -0,0 +1,109 @@
package openstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
)
func TestAccComputeV2VolumeAttach_basic(t *testing.T) {
var va volumeattach.VolumeAttachment
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeV2VolumeAttachDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeV2VolumeAttach_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeV2VolumeAttachExists(t, "openstack_compute_volume_attach_v2.va_1", &va),
),
},
},
})
}
func testAccCheckComputeV2VolumeAttachDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
computeClient, err := config.computeV2Client(OS_REGION_NAME)
if err != nil {
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
}
for _, rs := range s.RootModule().Resources {
if rs.Type != "openstack_compute_volume_attach_v2" {
continue
}
instanceId, volumeId, err := parseComputeVolumeAttachmentId(rs.Primary.ID)
if err != nil {
return err
}
_, err = volumeattach.Get(computeClient, instanceId, volumeId).Extract()
if err == nil {
return fmt.Errorf("Volume attachment still exists")
}
}
return nil
}
func testAccCheckComputeV2VolumeAttachExists(t *testing.T, n string, va *volumeattach.VolumeAttachment) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
config := testAccProvider.Meta().(*Config)
computeClient, err := config.computeV2Client(OS_REGION_NAME)
if err != nil {
return fmt.Errorf("(testAccCheckComputeV2VolumeAttachExists) Error creating OpenStack compute client: %s", err)
}
instanceId, volumeId, err := parseComputeVolumeAttachmentId(rs.Primary.ID)
if err != nil {
return err
}
found, err := volumeattach.Get(computeClient, instanceId, volumeId).Extract()
if err != nil {
return err
}
if found.ServerID != instanceId || found.VolumeID != volumeId {
return fmt.Errorf("VolumeAttach not found")
}
*va = *found
return nil
}
}
var testAccComputeV2VolumeAttach_basic = `
resource "openstack_blockstorage_volume_v2" "volume_1" {
name = "volume_1"
size = 1
}
resource "openstack_compute_instance_v2" "instance_1" {
name = "instance_1"
security_groups = ["default"]
}
resource "openstack_compute_volume_attach_v2" "va_1" {
instance_id = "${openstack_compute_instance_v2.instance_1.id}"
volume_id = "${openstack_blockstorage_volume_v2.volume_1.id}"
}
`

View File

@ -0,0 +1,65 @@
---
layout: "openstack"
page_title: "OpenStack: openstack_compute_volume_attach_v2"
sidebar_current: "docs-openstack-resource-compute-volume-attach-v2"
description: |-
Attaches a Block Storage Volume to an Instance.
---
# openstack\_compute\_volume_attach_v2
Attaches a Block Storage Volume to an Instance using the OpenStack
Compute (Nova) v2 API.
## Example Usage
```
resource "openstack_blockstorage_volume_v2" "volume_1" {
name = "volume_1"
size = 1
}
resource "openstack_compute_instance_v2" "instance_1" {
name = "instance_1"
security_groups = ["default"]
}
resource "openstack_compute_volume_attach_v2" "va_1" {
instance_id = "${openstack_compute_instance_v2.instance_1.id}"
volume_id = "${openstack_blockstorage_volume_v2.volume_1.id}"
}
```
## Argument Reference
The following arguments are supported:
* `region` - (Required) The region in which to obtain the V2 Compute client.
A Compute client is needed to create a volume attachment. If omitted, the
`OS_REGION_NAME` environment variable is used. Changing this creates a
new volume attachment.
* `instance_id` - (Required) The ID of the Instance to attach the Volume to.
* `volume_id` - (Required) The ID of the Volume to attach to an Instance.
## Attributes Reference
The following attributes are exported:
* `region` - See Argument Reference above.
* `instance_id` - See Argument Reference above.
* `volume_id` - See Argument Reference above.
* `device` - The device of the volume attachment (ex: `/dev/vdc`).
_NOTE_: This is the device reported by the Compute API and the real device
might actually differ depending on the hypervisor being used. This should
not be used as an authoritative piece of information.
## Import
Volume Attachments can be imported using the Instance ID and Volume ID
separated by a slash, e.g.
```
$ terraform import openstack_compute_volume_attach_v2.va_1 89c60255-9bd6-460c-822a-e2b959ede9d2/45670584-225f-46c3-b33e-6707b589b666
```

View File

@ -40,6 +40,9 @@
<li<%= sidebar_current("docs-openstack-resource-compute-servergroup-v2") %>> <li<%= sidebar_current("docs-openstack-resource-compute-servergroup-v2") %>>
<a href="/docs/providers/openstack/r/compute_servergroup_v2.html">openstack_compute_servergroup_v2</a> <a href="/docs/providers/openstack/r/compute_servergroup_v2.html">openstack_compute_servergroup_v2</a>
</li> </li>
<li<%= sidebar_current("docs-openstack-resource-compute-volume-attach-v2") %>>
<a href="/docs/providers/openstack/r/compute_volume_attach_v2.html">openstack_compute_volume_attach_v2</a>
</li>
</ul> </ul>
</li> </li>