github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.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/v1/volumes"
    14  	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
    15  )
    16  
    17  func resourceBlockStorageVolumeV1() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceBlockStorageVolumeV1Create,
    20  		Read:   resourceBlockStorageVolumeV1Read,
    21  		Update: resourceBlockStorageVolumeV1Update,
    22  		Delete: resourceBlockStorageVolumeV1Delete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"region": &schema.Schema{
    26  				Type:        schema.TypeString,
    27  				Required:    true,
    28  				ForceNew:    true,
    29  				DefaultFunc: envDefaultFuncAllowMissing("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  			"attachment": &schema.Schema{
    80  				Type:     schema.TypeSet,
    81  				Computed: true,
    82  				Elem: &schema.Resource{
    83  					Schema: map[string]*schema.Schema{
    84  						"id": &schema.Schema{
    85  							Type:     schema.TypeString,
    86  							Computed: true,
    87  						},
    88  						"instance_id": &schema.Schema{
    89  							Type:     schema.TypeString,
    90  							Computed: true,
    91  						},
    92  						"device": &schema.Schema{
    93  							Type:     schema.TypeString,
    94  							Computed: true,
    95  						},
    96  					},
    97  				},
    98  				Set: resourceVolumeAttachmentHash,
    99  			},
   100  		},
   101  	}
   102  }
   103  
   104  func resourceBlockStorageVolumeV1Create(d *schema.ResourceData, meta interface{}) error {
   105  	config := meta.(*Config)
   106  	blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string))
   107  	if err != nil {
   108  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   109  	}
   110  
   111  	createOpts := &volumes.CreateOpts{
   112  		Description:  d.Get("description").(string),
   113  		Availability: d.Get("availability_zone").(string),
   114  		Name:         d.Get("name").(string),
   115  		Size:         d.Get("size").(int),
   116  		SnapshotID:   d.Get("snapshot_id").(string),
   117  		SourceVolID:  d.Get("source_vol_id").(string),
   118  		ImageID:      d.Get("image_id").(string),
   119  		VolumeType:   d.Get("volume_type").(string),
   120  		Metadata:     resourceContainerMetadataV2(d),
   121  	}
   122  
   123  	log.Printf("[DEBUG] Create Options: %#v", createOpts)
   124  	v, err := volumes.Create(blockStorageClient, createOpts).Extract()
   125  	if err != nil {
   126  		return fmt.Errorf("Error creating OpenStack volume: %s", err)
   127  	}
   128  	log.Printf("[INFO] Volume ID: %s", v.ID)
   129  
   130  	// Store the ID now
   131  	d.SetId(v.ID)
   132  
   133  	// Wait for the volume to become available.
   134  	log.Printf(
   135  		"[DEBUG] Waiting for volume (%s) to become available",
   136  		v.ID)
   137  
   138  	stateConf := &resource.StateChangeConf{
   139  		Pending:    []string{"downloading", "creating"},
   140  		Target:     "available",
   141  		Refresh:    VolumeV1StateRefreshFunc(blockStorageClient, v.ID),
   142  		Timeout:    10 * time.Minute,
   143  		Delay:      10 * time.Second,
   144  		MinTimeout: 3 * time.Second,
   145  	}
   146  
   147  	_, err = stateConf.WaitForState()
   148  	if err != nil {
   149  		return fmt.Errorf(
   150  			"Error waiting for volume (%s) to become ready: %s",
   151  			v.ID, err)
   152  	}
   153  
   154  	return resourceBlockStorageVolumeV1Read(d, meta)
   155  }
   156  
   157  func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{}) error {
   158  	config := meta.(*Config)
   159  	blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string))
   160  	if err != nil {
   161  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   162  	}
   163  
   164  	v, err := volumes.Get(blockStorageClient, d.Id()).Extract()
   165  	if err != nil {
   166  		return CheckDeleted(d, err, "volume")
   167  	}
   168  
   169  	log.Printf("[DEBUG] Retreived volume %s: %+v", d.Id(), v)
   170  
   171  	d.Set("size", v.Size)
   172  	d.Set("description", v.Description)
   173  	d.Set("availability_zone", v.AvailabilityZone)
   174  	d.Set("name", v.Name)
   175  	d.Set("snapshot_id", v.SnapshotID)
   176  	d.Set("source_vol_id", v.SourceVolID)
   177  	d.Set("volume_type", v.VolumeType)
   178  	d.Set("metadata", v.Metadata)
   179  
   180  	if len(v.Attachments) > 0 {
   181  		attachments := make([]map[string]interface{}, len(v.Attachments))
   182  		for i, attachment := range v.Attachments {
   183  			attachments[i] = make(map[string]interface{})
   184  			attachments[i]["id"] = attachment["id"]
   185  			attachments[i]["instance_id"] = attachment["server_id"]
   186  			attachments[i]["device"] = attachment["device"]
   187  			log.Printf("[DEBUG] attachment: %v", attachment)
   188  		}
   189  		d.Set("attachment", attachments)
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  func resourceBlockStorageVolumeV1Update(d *schema.ResourceData, meta interface{}) error {
   196  	config := meta.(*Config)
   197  	blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string))
   198  	if err != nil {
   199  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   200  	}
   201  
   202  	updateOpts := volumes.UpdateOpts{
   203  		Name:        d.Get("name").(string),
   204  		Description: d.Get("description").(string),
   205  	}
   206  
   207  	if d.HasChange("metadata") {
   208  		updateOpts.Metadata = resourceVolumeMetadataV1(d)
   209  	}
   210  
   211  	_, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract()
   212  	if err != nil {
   213  		return fmt.Errorf("Error updating OpenStack volume: %s", err)
   214  	}
   215  
   216  	return resourceBlockStorageVolumeV1Read(d, meta)
   217  }
   218  
   219  func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{}) error {
   220  	config := meta.(*Config)
   221  	blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string))
   222  	if err != nil {
   223  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   224  	}
   225  
   226  	v, err := volumes.Get(blockStorageClient, d.Id()).Extract()
   227  	if err != nil {
   228  		return CheckDeleted(d, err, "volume")
   229  	}
   230  
   231  	// make sure this volume is detached from all instances before deleting
   232  	if len(v.Attachments) > 0 {
   233  		log.Printf("[DEBUG] detaching volumes")
   234  		if computeClient, err := config.computeV2Client(d.Get("region").(string)); err != nil {
   235  			return err
   236  		} else {
   237  			for _, volumeAttachment := range v.Attachments {
   238  				log.Printf("[DEBUG] Attachment: %v", volumeAttachment)
   239  				if err := volumeattach.Delete(computeClient, volumeAttachment["server_id"].(string), volumeAttachment["id"].(string)).ExtractErr(); err != nil {
   240  					return err
   241  				}
   242  			}
   243  
   244  			stateConf := &resource.StateChangeConf{
   245  				Pending:    []string{"in-use", "attaching"},
   246  				Target:     "available",
   247  				Refresh:    VolumeV1StateRefreshFunc(blockStorageClient, d.Id()),
   248  				Timeout:    10 * time.Minute,
   249  				Delay:      10 * time.Second,
   250  				MinTimeout: 3 * time.Second,
   251  			}
   252  
   253  			_, err = stateConf.WaitForState()
   254  			if err != nil {
   255  				return fmt.Errorf(
   256  					"Error waiting for volume (%s) to become available: %s",
   257  					d.Id(), err)
   258  			}
   259  		}
   260  	}
   261  
   262  	err = volumes.Delete(blockStorageClient, d.Id()).ExtractErr()
   263  	if err != nil {
   264  		return fmt.Errorf("Error deleting OpenStack volume: %s", err)
   265  	}
   266  
   267  	// Wait for the volume to delete before moving on.
   268  	log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id())
   269  
   270  	stateConf := &resource.StateChangeConf{
   271  		Pending:    []string{"deleting", "downloading", "available"},
   272  		Target:     "deleted",
   273  		Refresh:    VolumeV1StateRefreshFunc(blockStorageClient, d.Id()),
   274  		Timeout:    10 * time.Minute,
   275  		Delay:      10 * time.Second,
   276  		MinTimeout: 3 * time.Second,
   277  	}
   278  
   279  	_, err = stateConf.WaitForState()
   280  	if err != nil {
   281  		return fmt.Errorf(
   282  			"Error waiting for volume (%s) to delete: %s",
   283  			d.Id(), err)
   284  	}
   285  
   286  	d.SetId("")
   287  	return nil
   288  }
   289  
   290  func resourceVolumeMetadataV1(d *schema.ResourceData) map[string]string {
   291  	m := make(map[string]string)
   292  	for key, val := range d.Get("metadata").(map[string]interface{}) {
   293  		m[key] = val.(string)
   294  	}
   295  	return m
   296  }
   297  
   298  // VolumeV1StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   299  // an OpenStack volume.
   300  func VolumeV1StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc {
   301  	return func() (interface{}, string, error) {
   302  		v, err := volumes.Get(client, volumeID).Extract()
   303  		if err != nil {
   304  			errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
   305  			if !ok {
   306  				return nil, "", err
   307  			}
   308  			if errCode.Actual == 404 {
   309  				return v, "deleted", nil
   310  			}
   311  			return nil, "", err
   312  		}
   313  
   314  		return v, v.Status, nil
   315  	}
   316  }
   317  
   318  func resourceVolumeAttachmentHash(v interface{}) int {
   319  	var buf bytes.Buffer
   320  	m := v.(map[string]interface{})
   321  	if m["instance_id"] != nil {
   322  		buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string)))
   323  	}
   324  	return hashcode.String(buf.String())
   325  }