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