github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/openstack/resource_openstack_compute_volume_attach_v2.go (about)

     1  package openstack
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/gophercloud/gophercloud"
    10  	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
    11  
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  )
    15  
    16  func resourceComputeVolumeAttachV2() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceComputeVolumeAttachV2Create,
    19  		Read:   resourceComputeVolumeAttachV2Read,
    20  		Delete: resourceComputeVolumeAttachV2Delete,
    21  		Importer: &schema.ResourceImporter{
    22  			State: schema.ImportStatePassthrough,
    23  		},
    24  
    25  		Timeouts: &schema.ResourceTimeout{
    26  			Create: schema.DefaultTimeout(10 * time.Minute),
    27  			Delete: schema.DefaultTimeout(10 * time.Minute),
    28  		},
    29  
    30  		Schema: map[string]*schema.Schema{
    31  			"region": &schema.Schema{
    32  				Type:        schema.TypeString,
    33  				Required:    true,
    34  				ForceNew:    true,
    35  				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
    36  			},
    37  
    38  			"instance_id": &schema.Schema{
    39  				Type:     schema.TypeString,
    40  				Required: true,
    41  				ForceNew: true,
    42  			},
    43  
    44  			"volume_id": &schema.Schema{
    45  				Type:     schema.TypeString,
    46  				Required: true,
    47  				ForceNew: true,
    48  			},
    49  
    50  			"device": &schema.Schema{
    51  				Type:     schema.TypeString,
    52  				Computed: true,
    53  				Optional: true,
    54  			},
    55  		},
    56  	}
    57  }
    58  
    59  func resourceComputeVolumeAttachV2Create(d *schema.ResourceData, meta interface{}) error {
    60  	config := meta.(*Config)
    61  	computeClient, err := config.computeV2Client(GetRegion(d))
    62  	if err != nil {
    63  		return fmt.Errorf("Error creating OpenStack compute client: %s", err)
    64  	}
    65  
    66  	instanceId := d.Get("instance_id").(string)
    67  	volumeId := d.Get("volume_id").(string)
    68  
    69  	var device string
    70  	if v, ok := d.GetOk("device"); ok {
    71  		device = v.(string)
    72  	}
    73  
    74  	attachOpts := volumeattach.CreateOpts{
    75  		Device:   device,
    76  		VolumeID: volumeId,
    77  	}
    78  
    79  	log.Printf("[DEBUG] Creating volume attachment: %#v", attachOpts)
    80  
    81  	attachment, err := volumeattach.Create(computeClient, instanceId, attachOpts).Extract()
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	stateConf := &resource.StateChangeConf{
    87  		Pending:    []string{"ATTACHING"},
    88  		Target:     []string{"ATTACHED"},
    89  		Refresh:    resourceComputeVolumeAttachV2AttachFunc(computeClient, instanceId, attachment.ID),
    90  		Timeout:    d.Timeout(schema.TimeoutCreate),
    91  		Delay:      30 * time.Second,
    92  		MinTimeout: 15 * time.Second,
    93  	}
    94  
    95  	if _, err = stateConf.WaitForState(); err != nil {
    96  		return fmt.Errorf("Error attaching OpenStack volume: %s", err)
    97  	}
    98  
    99  	log.Printf("[DEBUG] Created volume attachment: %#v", attachment)
   100  
   101  	// Use the instance ID and attachment ID as the resource ID.
   102  	// This is because an attachment cannot be retrieved just by its ID alone.
   103  	id := fmt.Sprintf("%s/%s", instanceId, attachment.ID)
   104  
   105  	d.SetId(id)
   106  
   107  	return resourceComputeVolumeAttachV2Read(d, meta)
   108  }
   109  
   110  func resourceComputeVolumeAttachV2Read(d *schema.ResourceData, meta interface{}) error {
   111  	config := meta.(*Config)
   112  	computeClient, err := config.computeV2Client(GetRegion(d))
   113  	if err != nil {
   114  		return fmt.Errorf("Error creating OpenStack compute client: %s", err)
   115  	}
   116  
   117  	instanceId, attachmentId, err := parseComputeVolumeAttachmentId(d.Id())
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	attachment, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract()
   123  	if err != nil {
   124  		return CheckDeleted(d, err, "compute_volume_attach")
   125  	}
   126  
   127  	log.Printf("[DEBUG] Retrieved volume attachment: %#v", attachment)
   128  
   129  	d.Set("instance_id", attachment.ServerID)
   130  	d.Set("volume_id", attachment.VolumeID)
   131  	d.Set("device", attachment.Device)
   132  	d.Set("region", GetRegion(d))
   133  
   134  	return nil
   135  }
   136  
   137  func resourceComputeVolumeAttachV2Delete(d *schema.ResourceData, meta interface{}) error {
   138  	config := meta.(*Config)
   139  	computeClient, err := config.computeV2Client(GetRegion(d))
   140  	if err != nil {
   141  		return fmt.Errorf("Error creating OpenStack compute client: %s", err)
   142  	}
   143  
   144  	instanceId, attachmentId, err := parseComputeVolumeAttachmentId(d.Id())
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	stateConf := &resource.StateChangeConf{
   150  		Pending:    []string{""},
   151  		Target:     []string{"DETACHED"},
   152  		Refresh:    resourceComputeVolumeAttachV2DetachFunc(computeClient, instanceId, attachmentId),
   153  		Timeout:    d.Timeout(schema.TimeoutDelete),
   154  		Delay:      15 * time.Second,
   155  		MinTimeout: 15 * time.Second,
   156  	}
   157  
   158  	if _, err = stateConf.WaitForState(); err != nil {
   159  		return fmt.Errorf("Error detaching OpenStack volume: %s", err)
   160  	}
   161  
   162  	return nil
   163  }
   164  
   165  func resourceComputeVolumeAttachV2AttachFunc(
   166  	computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc {
   167  	return func() (interface{}, string, error) {
   168  		va, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract()
   169  		if err != nil {
   170  			if _, ok := err.(gophercloud.ErrDefault404); ok {
   171  				return va, "ATTACHING", nil
   172  			}
   173  			return va, "", err
   174  		}
   175  
   176  		return va, "ATTACHED", nil
   177  	}
   178  }
   179  
   180  func resourceComputeVolumeAttachV2DetachFunc(
   181  	computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc {
   182  	return func() (interface{}, string, error) {
   183  		log.Printf("[DEBUG] Attempting to detach OpenStack volume %s from instance %s",
   184  			attachmentId, instanceId)
   185  
   186  		va, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract()
   187  		if err != nil {
   188  			if _, ok := err.(gophercloud.ErrDefault404); ok {
   189  				return va, "DETACHED", nil
   190  			}
   191  			return va, "", err
   192  		}
   193  
   194  		err = volumeattach.Delete(computeClient, instanceId, attachmentId).ExtractErr()
   195  		if err != nil {
   196  			if _, ok := err.(gophercloud.ErrDefault404); ok {
   197  				return va, "DETACHED", nil
   198  			}
   199  
   200  			if _, ok := err.(gophercloud.ErrDefault400); ok {
   201  				return nil, "", nil
   202  			}
   203  
   204  			return nil, "", err
   205  		}
   206  
   207  		log.Printf("[DEBUG] OpenStack Volume Attachment (%s) is still active.", attachmentId)
   208  		return nil, "", nil
   209  	}
   210  }
   211  
   212  func parseComputeVolumeAttachmentId(id string) (string, string, error) {
   213  	idParts := strings.Split(id, "/")
   214  	if len(idParts) < 2 {
   215  		return "", "", fmt.Errorf("Unable to determine volume attachment ID")
   216  	}
   217  
   218  	instanceId := idParts[0]
   219  	attachmentId := idParts[1]
   220  
   221  	return instanceId, attachmentId, nil
   222  }