Files
opentf/builtin/providers/openstack/resource_openstack_compute_volume_attach_v2.go
Joe Topjian b593d6903d provider/openstack: Volume Attachment Updates (#11285)
This commit adds a StateRefresh func for volume attachments. Mostly
this is to add a buffer of time between the request and the return
of the attachment to give time for the volume to become attached,
however, in some cases the refresh function could work as specified.

Docs have also been updated to reflect that a device could be specified,
but to use with caution.
2017-01-23 22:19:50 +00:00

218 lines
5.7 KiB
Go

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(GetRegion(d))
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
}
stateConf := &resource.StateChangeConf{
Pending: []string{"ATTACHING"},
Target: []string{"ATTACHED"},
Refresh: resourceComputeVolumeAttachV2AttachFunc(computeClient, instanceId, attachment.ID),
Timeout: 10 * time.Minute,
Delay: 30 * time.Second,
MinTimeout: 15 * time.Second,
}
if _, err = stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error attaching OpenStack volume: %s", 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(GetRegion(d))
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)
d.Set("region", GetRegion(d))
return nil
}
func resourceComputeVolumeAttachV2Delete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
computeClient, err := config.computeV2Client(GetRegion(d))
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: resourceComputeVolumeAttachV2DetachFunc(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 resourceComputeVolumeAttachV2AttachFunc(
computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
va, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
return va, "ATTACHING", nil
}
return va, "", err
}
return va, "ATTACHED", nil
}
}
func resourceComputeVolumeAttachV2DetachFunc(
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
}