mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-27 11:00:18 -04:00
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.
218 lines
5.7 KiB
Go
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
|
|
}
|