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