github.com/memsql/terraform@v0.7.0-rc2.0.20160706152241-21e2173e0a32/builtin/providers/openstack/resource_openstack_blockstorage_volume_v2.go (about)

     1  package openstack
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/helper/hashcode"
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  	"github.com/rackspace/gophercloud"
    13  	"github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes"
    14  	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
    15  )
    16  
    17  func resourceBlockStorageVolumeV2() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceBlockStorageVolumeV2Create,
    20  		Read:   resourceBlockStorageVolumeV2Read,
    21  		Update: resourceBlockStorageVolumeV2Update,
    22  		Delete: resourceBlockStorageVolumeV2Delete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"region": &schema.Schema{
    26  				Type:        schema.TypeString,
    27  				Required:    true,
    28  				ForceNew:    true,
    29  				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
    30  			},
    31  			"size": &schema.Schema{
    32  				Type:     schema.TypeInt,
    33  				Required: true,
    34  				ForceNew: true,
    35  			},
    36  			"name": &schema.Schema{
    37  				Type:     schema.TypeString,
    38  				Optional: true,
    39  				ForceNew: false,
    40  			},
    41  			"description": &schema.Schema{
    42  				Type:     schema.TypeString,
    43  				Optional: true,
    44  				ForceNew: false,
    45  			},
    46  			"availability_zone": &schema.Schema{
    47  				Type:     schema.TypeString,
    48  				Optional: true,
    49  				ForceNew: true,
    50  				Computed: true,
    51  			},
    52  			"metadata": &schema.Schema{
    53  				Type:     schema.TypeMap,
    54  				Optional: true,
    55  				ForceNew: false,
    56  				Computed: true,
    57  			},
    58  			"snapshot_id": &schema.Schema{
    59  				Type:     schema.TypeString,
    60  				Optional: true,
    61  				ForceNew: true,
    62  			},
    63  			"source_vol_id": &schema.Schema{
    64  				Type:     schema.TypeString,
    65  				Optional: true,
    66  				ForceNew: true,
    67  			},
    68  			"image_id": &schema.Schema{
    69  				Type:     schema.TypeString,
    70  				Optional: true,
    71  				ForceNew: true,
    72  			},
    73  			"volume_type": &schema.Schema{
    74  				Type:     schema.TypeString,
    75  				Optional: true,
    76  				ForceNew: true,
    77  				Computed: true,
    78  			},
    79  			"consistency_group_id": &schema.Schema{
    80  				Type:     schema.TypeString,
    81  				Optional: true,
    82  				ForceNew: true,
    83  			},
    84  			"source_replica": &schema.Schema{
    85  				Type:     schema.TypeString,
    86  				Optional: true,
    87  				ForceNew: true,
    88  			},
    89  			"attachment": &schema.Schema{
    90  				Type:     schema.TypeSet,
    91  				Computed: true,
    92  				Elem: &schema.Resource{
    93  					Schema: map[string]*schema.Schema{
    94  						"id": &schema.Schema{
    95  							Type:     schema.TypeString,
    96  							Computed: true,
    97  						},
    98  						"instance_id": &schema.Schema{
    99  							Type:     schema.TypeString,
   100  							Computed: true,
   101  						},
   102  						"device": &schema.Schema{
   103  							Type:     schema.TypeString,
   104  							Computed: true,
   105  						},
   106  					},
   107  				},
   108  				Set: resourceVolumeV2AttachmentHash,
   109  			},
   110  		},
   111  	}
   112  }
   113  
   114  func resourceBlockStorageVolumeV2Create(d *schema.ResourceData, meta interface{}) error {
   115  	config := meta.(*Config)
   116  	blockStorageClient, err := config.blockStorageV2Client(d.Get("region").(string))
   117  	if err != nil {
   118  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   119  	}
   120  
   121  	createOpts := &volumes.CreateOpts{
   122  		AvailabilityZone:   d.Get("availability_zone").(string),
   123  		ConsistencyGroupID: d.Get("consistency_group_id").(string),
   124  		Description:        d.Get("description").(string),
   125  		ImageID:            d.Get("image_id").(string),
   126  		Metadata:           resourceContainerMetadataV2(d),
   127  		Name:               d.Get("name").(string),
   128  		Size:               d.Get("size").(int),
   129  		SnapshotID:         d.Get("snapshot_id").(string),
   130  		SourceReplica:      d.Get("source_replica").(string),
   131  		SourceVolID:        d.Get("source_vol_id").(string),
   132  		VolumeType:         d.Get("volume_type").(string),
   133  	}
   134  
   135  	log.Printf("[DEBUG] Create Options: %#v", createOpts)
   136  	v, err := volumes.Create(blockStorageClient, createOpts).Extract()
   137  	if err != nil {
   138  		return fmt.Errorf("Error creating OpenStack volume: %s", err)
   139  	}
   140  	log.Printf("[INFO] Volume ID: %s", v.ID)
   141  
   142  	// Store the ID now
   143  	d.SetId(v.ID)
   144  
   145  	// Wait for the volume to become available.
   146  	log.Printf(
   147  		"[DEBUG] Waiting for volume (%s) to become available",
   148  		v.ID)
   149  
   150  	stateConf := &resource.StateChangeConf{
   151  		Pending:    []string{"downloading", "creating"},
   152  		Target:     []string{"available"},
   153  		Refresh:    VolumeV2StateRefreshFunc(blockStorageClient, v.ID),
   154  		Timeout:    10 * time.Minute,
   155  		Delay:      10 * time.Second,
   156  		MinTimeout: 3 * time.Second,
   157  	}
   158  
   159  	_, err = stateConf.WaitForState()
   160  	if err != nil {
   161  		return fmt.Errorf(
   162  			"Error waiting for volume (%s) to become ready: %s",
   163  			v.ID, err)
   164  	}
   165  
   166  	return resourceBlockStorageVolumeV2Read(d, meta)
   167  }
   168  
   169  func resourceBlockStorageVolumeV2Read(d *schema.ResourceData, meta interface{}) error {
   170  	config := meta.(*Config)
   171  	blockStorageClient, err := config.blockStorageV2Client(d.Get("region").(string))
   172  	if err != nil {
   173  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   174  	}
   175  
   176  	v, err := volumes.Get(blockStorageClient, d.Id()).Extract()
   177  	if err != nil {
   178  		return CheckDeleted(d, err, "volume")
   179  	}
   180  
   181  	log.Printf("[DEBUG] Retreived volume %s: %+v", d.Id(), v)
   182  
   183  	d.Set("size", v.Size)
   184  	d.Set("description", v.Description)
   185  	d.Set("availability_zone", v.AvailabilityZone)
   186  	d.Set("name", v.Name)
   187  	d.Set("snapshot_id", v.SnapshotID)
   188  	d.Set("source_vol_id", v.SourceVolID)
   189  	d.Set("volume_type", v.VolumeType)
   190  	d.Set("metadata", v.Metadata)
   191  
   192  	if len(v.Attachments) > 0 {
   193  		attachments := make([]map[string]interface{}, len(v.Attachments))
   194  		for i, attachment := range v.Attachments {
   195  			attachments[i] = make(map[string]interface{})
   196  			attachments[i]["id"] = attachment["id"]
   197  			attachments[i]["instance_id"] = attachment["server_id"]
   198  			attachments[i]["device"] = attachment["device"]
   199  			log.Printf("[DEBUG] attachment: %v", attachment)
   200  		}
   201  		d.Set("attachment", attachments)
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  func resourceBlockStorageVolumeV2Update(d *schema.ResourceData, meta interface{}) error {
   208  	config := meta.(*Config)
   209  	blockStorageClient, err := config.blockStorageV2Client(d.Get("region").(string))
   210  	if err != nil {
   211  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   212  	}
   213  
   214  	updateOpts := volumes.UpdateOpts{
   215  		Name:        d.Get("name").(string),
   216  		Description: d.Get("description").(string),
   217  	}
   218  
   219  	if d.HasChange("metadata") {
   220  		updateOpts.Metadata = resourceVolumeMetadataV2(d)
   221  	}
   222  
   223  	_, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract()
   224  	if err != nil {
   225  		return fmt.Errorf("Error updating OpenStack volume: %s", err)
   226  	}
   227  
   228  	return resourceBlockStorageVolumeV2Read(d, meta)
   229  }
   230  
   231  func resourceBlockStorageVolumeV2Delete(d *schema.ResourceData, meta interface{}) error {
   232  	config := meta.(*Config)
   233  	blockStorageClient, err := config.blockStorageV2Client(d.Get("region").(string))
   234  	if err != nil {
   235  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   236  	}
   237  
   238  	v, err := volumes.Get(blockStorageClient, d.Id()).Extract()
   239  	if err != nil {
   240  		return CheckDeleted(d, err, "volume")
   241  	}
   242  
   243  	// make sure this volume is detached from all instances before deleting
   244  	if len(v.Attachments) > 0 {
   245  		log.Printf("[DEBUG] detaching volumes")
   246  		if computeClient, err := config.computeV2Client(d.Get("region").(string)); err != nil {
   247  			return err
   248  		} else {
   249  			for _, volumeAttachment := range v.Attachments {
   250  				log.Printf("[DEBUG] Attachment: %v", volumeAttachment)
   251  				if err := volumeattach.Delete(computeClient, volumeAttachment["server_id"].(string), volumeAttachment["id"].(string)).ExtractErr(); err != nil {
   252  					return err
   253  				}
   254  			}
   255  
   256  			stateConf := &resource.StateChangeConf{
   257  				Pending:    []string{"in-use", "attaching", "detaching"},
   258  				Target:     []string{"available"},
   259  				Refresh:    VolumeV2StateRefreshFunc(blockStorageClient, d.Id()),
   260  				Timeout:    10 * time.Minute,
   261  				Delay:      10 * time.Second,
   262  				MinTimeout: 3 * time.Second,
   263  			}
   264  
   265  			_, err = stateConf.WaitForState()
   266  			if err != nil {
   267  				return fmt.Errorf(
   268  					"Error waiting for volume (%s) to become available: %s",
   269  					d.Id(), err)
   270  			}
   271  		}
   272  	}
   273  
   274  	// It's possible that this volume was used as a boot device and is currently
   275  	// in a "deleting" state from when the instance was terminated.
   276  	// If this is true, just move on. It'll eventually delete.
   277  	if v.Status != "deleting" {
   278  		if err := volumes.Delete(blockStorageClient, d.Id()).ExtractErr(); err != nil {
   279  			return CheckDeleted(d, err, "volume")
   280  		}
   281  	}
   282  
   283  	// Wait for the volume to delete before moving on.
   284  	log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id())
   285  
   286  	stateConf := &resource.StateChangeConf{
   287  		Pending:    []string{"deleting", "downloading", "available"},
   288  		Target:     []string{"deleted"},
   289  		Refresh:    VolumeV2StateRefreshFunc(blockStorageClient, d.Id()),
   290  		Timeout:    10 * time.Minute,
   291  		Delay:      10 * time.Second,
   292  		MinTimeout: 3 * time.Second,
   293  	}
   294  
   295  	_, err = stateConf.WaitForState()
   296  	if err != nil {
   297  		return fmt.Errorf(
   298  			"Error waiting for volume (%s) to delete: %s",
   299  			d.Id(), err)
   300  	}
   301  
   302  	d.SetId("")
   303  	return nil
   304  }
   305  
   306  func resourceVolumeMetadataV2(d *schema.ResourceData) map[string]string {
   307  	m := make(map[string]string)
   308  	for key, val := range d.Get("metadata").(map[string]interface{}) {
   309  		m[key] = val.(string)
   310  	}
   311  	return m
   312  }
   313  
   314  // VolumeV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   315  // an OpenStack volume.
   316  func VolumeV2StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc {
   317  	return func() (interface{}, string, error) {
   318  		v, err := volumes.Get(client, volumeID).Extract()
   319  		if err != nil {
   320  			errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
   321  			if !ok {
   322  				return nil, "", err
   323  			}
   324  			if errCode.Actual == 404 {
   325  				return v, "deleted", nil
   326  			}
   327  			return nil, "", err
   328  		}
   329  
   330  		return v, v.Status, nil
   331  	}
   332  }
   333  
   334  func resourceVolumeV2AttachmentHash(v interface{}) int {
   335  	var buf bytes.Buffer
   336  	m := v.(map[string]interface{})
   337  	if m["instance_id"] != nil {
   338  		buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string)))
   339  	}
   340  	return hashcode.String(buf.String())
   341  }