github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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/gophercloud/gophercloud"
    10  	"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/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 resourceBlockStorageVolumeV2() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceBlockStorageVolumeV2Create,
    20  		Read:   resourceBlockStorageVolumeV2Read,
    21  		Update: resourceBlockStorageVolumeV2Update,
    22  		Delete: resourceBlockStorageVolumeV2Delete,
    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  			"consistency_group_id": &schema.Schema{
    83  				Type:     schema.TypeString,
    84  				Optional: true,
    85  				ForceNew: true,
    86  			},
    87  			"source_replica": &schema.Schema{
    88  				Type:     schema.TypeString,
    89  				Optional: true,
    90  				ForceNew: true,
    91  			},
    92  			"attachment": &schema.Schema{
    93  				Type:     schema.TypeSet,
    94  				Computed: true,
    95  				Elem: &schema.Resource{
    96  					Schema: map[string]*schema.Schema{
    97  						"id": &schema.Schema{
    98  							Type:     schema.TypeString,
    99  							Computed: true,
   100  						},
   101  						"instance_id": &schema.Schema{
   102  							Type:     schema.TypeString,
   103  							Computed: true,
   104  						},
   105  						"device": &schema.Schema{
   106  							Type:     schema.TypeString,
   107  							Computed: true,
   108  						},
   109  					},
   110  				},
   111  				Set: resourceVolumeV2AttachmentHash,
   112  			},
   113  		},
   114  	}
   115  }
   116  
   117  func resourceBlockStorageVolumeV2Create(d *schema.ResourceData, meta interface{}) error {
   118  	config := meta.(*Config)
   119  	blockStorageClient, err := config.blockStorageV2Client(GetRegion(d))
   120  	if err != nil {
   121  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   122  	}
   123  
   124  	createOpts := &volumes.CreateOpts{
   125  		AvailabilityZone:   d.Get("availability_zone").(string),
   126  		ConsistencyGroupID: d.Get("consistency_group_id").(string),
   127  		Description:        d.Get("description").(string),
   128  		ImageID:            d.Get("image_id").(string),
   129  		Metadata:           resourceContainerMetadataV2(d),
   130  		Name:               d.Get("name").(string),
   131  		Size:               d.Get("size").(int),
   132  		SnapshotID:         d.Get("snapshot_id").(string),
   133  		SourceReplica:      d.Get("source_replica").(string),
   134  		SourceVolID:        d.Get("source_vol_id").(string),
   135  		VolumeType:         d.Get("volume_type").(string),
   136  	}
   137  
   138  	log.Printf("[DEBUG] Create Options: %#v", createOpts)
   139  	v, err := volumes.Create(blockStorageClient, createOpts).Extract()
   140  	if err != nil {
   141  		return fmt.Errorf("Error creating OpenStack volume: %s", err)
   142  	}
   143  	log.Printf("[INFO] Volume ID: %s", 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  	// Store the ID now
   167  	d.SetId(v.ID)
   168  
   169  	return resourceBlockStorageVolumeV2Read(d, meta)
   170  }
   171  
   172  func resourceBlockStorageVolumeV2Read(d *schema.ResourceData, meta interface{}) error {
   173  	config := meta.(*Config)
   174  	blockStorageClient, err := config.blockStorageV2Client(GetRegion(d))
   175  	if err != nil {
   176  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   177  	}
   178  
   179  	v, err := volumes.Get(blockStorageClient, d.Id()).Extract()
   180  	if err != nil {
   181  		return CheckDeleted(d, err, "volume")
   182  	}
   183  
   184  	log.Printf("[DEBUG] Retrieved volume %s: %+v", d.Id(), v)
   185  
   186  	d.Set("size", v.Size)
   187  	d.Set("description", v.Description)
   188  	d.Set("availability_zone", v.AvailabilityZone)
   189  	d.Set("name", v.Name)
   190  	d.Set("snapshot_id", v.SnapshotID)
   191  	d.Set("source_vol_id", v.SourceVolID)
   192  	d.Set("volume_type", v.VolumeType)
   193  	d.Set("metadata", v.Metadata)
   194  	d.Set("region", GetRegion(d))
   195  
   196  	attachments := make([]map[string]interface{}, len(v.Attachments))
   197  	for i, attachment := range v.Attachments {
   198  		attachments[i] = make(map[string]interface{})
   199  		attachments[i]["id"] = attachment.ID
   200  		attachments[i]["instance_id"] = attachment.ServerID
   201  		attachments[i]["device"] = attachment.Device
   202  		log.Printf("[DEBUG] attachment: %v", attachment)
   203  	}
   204  	d.Set("attachment", attachments)
   205  
   206  	return nil
   207  }
   208  
   209  func resourceBlockStorageVolumeV2Update(d *schema.ResourceData, meta interface{}) error {
   210  	config := meta.(*Config)
   211  	blockStorageClient, err := config.blockStorageV2Client(GetRegion(d))
   212  	if err != nil {
   213  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   214  	}
   215  
   216  	updateOpts := volumes.UpdateOpts{
   217  		Name:        d.Get("name").(string),
   218  		Description: d.Get("description").(string),
   219  	}
   220  
   221  	if d.HasChange("metadata") {
   222  		updateOpts.Metadata = resourceVolumeMetadataV2(d)
   223  	}
   224  
   225  	_, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract()
   226  	if err != nil {
   227  		return fmt.Errorf("Error updating OpenStack volume: %s", err)
   228  	}
   229  
   230  	return resourceBlockStorageVolumeV2Read(d, meta)
   231  }
   232  
   233  func resourceBlockStorageVolumeV2Delete(d *schema.ResourceData, meta interface{}) error {
   234  	config := meta.(*Config)
   235  	blockStorageClient, err := config.blockStorageV2Client(GetRegion(d))
   236  	if err != nil {
   237  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   238  	}
   239  
   240  	v, err := volumes.Get(blockStorageClient, d.Id()).Extract()
   241  	if err != nil {
   242  		return CheckDeleted(d, err, "volume")
   243  	}
   244  
   245  	// make sure this volume is detached from all instances before deleting
   246  	if len(v.Attachments) > 0 {
   247  		log.Printf("[DEBUG] detaching volumes")
   248  		if computeClient, err := config.computeV2Client(GetRegion(d)); err != nil {
   249  			return err
   250  		} else {
   251  			for _, volumeAttachment := range v.Attachments {
   252  				log.Printf("[DEBUG] Attachment: %v", volumeAttachment)
   253  				if err := volumeattach.Delete(computeClient, volumeAttachment.ServerID, volumeAttachment.ID).ExtractErr(); err != nil {
   254  					return err
   255  				}
   256  			}
   257  
   258  			stateConf := &resource.StateChangeConf{
   259  				Pending:    []string{"in-use", "attaching", "detaching"},
   260  				Target:     []string{"available"},
   261  				Refresh:    VolumeV2StateRefreshFunc(blockStorageClient, d.Id()),
   262  				Timeout:    10 * time.Minute,
   263  				Delay:      10 * time.Second,
   264  				MinTimeout: 3 * time.Second,
   265  			}
   266  
   267  			_, err = stateConf.WaitForState()
   268  			if err != nil {
   269  				return fmt.Errorf(
   270  					"Error waiting for volume (%s) to become available: %s",
   271  					d.Id(), err)
   272  			}
   273  		}
   274  	}
   275  
   276  	// It's possible that this volume was used as a boot device and is currently
   277  	// in a "deleting" state from when the instance was terminated.
   278  	// If this is true, just move on. It'll eventually delete.
   279  	if v.Status != "deleting" {
   280  		if err := volumes.Delete(blockStorageClient, d.Id()).ExtractErr(); err != nil {
   281  			return CheckDeleted(d, err, "volume")
   282  		}
   283  	}
   284  
   285  	// Wait for the volume to delete before moving on.
   286  	log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id())
   287  
   288  	stateConf := &resource.StateChangeConf{
   289  		Pending:    []string{"deleting", "downloading", "available"},
   290  		Target:     []string{"deleted"},
   291  		Refresh:    VolumeV2StateRefreshFunc(blockStorageClient, d.Id()),
   292  		Timeout:    10 * time.Minute,
   293  		Delay:      10 * time.Second,
   294  		MinTimeout: 3 * time.Second,
   295  	}
   296  
   297  	_, err = stateConf.WaitForState()
   298  	if err != nil {
   299  		return fmt.Errorf(
   300  			"Error waiting for volume (%s) to delete: %s",
   301  			d.Id(), err)
   302  	}
   303  
   304  	d.SetId("")
   305  	return nil
   306  }
   307  
   308  func resourceVolumeMetadataV2(d *schema.ResourceData) map[string]string {
   309  	m := make(map[string]string)
   310  	for key, val := range d.Get("metadata").(map[string]interface{}) {
   311  		m[key] = val.(string)
   312  	}
   313  	return m
   314  }
   315  
   316  // VolumeV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   317  // an OpenStack volume.
   318  func VolumeV2StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc {
   319  	return func() (interface{}, string, error) {
   320  		v, err := volumes.Get(client, volumeID).Extract()
   321  		if err != nil {
   322  			if _, ok := err.(gophercloud.ErrDefault404); ok {
   323  				return v, "deleted", nil
   324  			}
   325  			return nil, "", err
   326  		}
   327  
   328  		if v.Status == "error" {
   329  			return v, v.Status, fmt.Errorf("There was an error creating the volume. " +
   330  				"Please check with your cloud admin or check the Block Storage " +
   331  				"API logs to see why this error occurred.")
   332  		}
   333  
   334  		return v, v.Status, nil
   335  	}
   336  }
   337  
   338  func resourceVolumeV2AttachmentHash(v interface{}) int {
   339  	var buf bytes.Buffer
   340  	m := v.(map[string]interface{})
   341  	if m["instance_id"] != nil {
   342  		buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string)))
   343  	}
   344  	return hashcode.String(buf.String())
   345  }