Merge pull request #10260 from jtopjian/openstack-compute-volume-attach
provider/openstack: openstack_compute_volume_attach_v2 resource
This commit is contained in:
commit
1aff30dd5e
|
@ -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"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -143,6 +143,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(),
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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}"
|
||||||
|
}
|
||||||
|
`
|
|
@ -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
|
||||||
|
```
|
|
@ -43,6 +43,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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue