github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/cloudstack/resource_cloudstack_disk.go (about)

     1  package cloudstack
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/hashicorp/terraform/helper/schema"
     8  	"github.com/xanzy/go-cloudstack/cloudstack"
     9  )
    10  
    11  func resourceCloudStackDisk() *schema.Resource {
    12  	return &schema.Resource{
    13  		Create: resourceCloudStackDiskCreate,
    14  		Read:   resourceCloudStackDiskRead,
    15  		Update: resourceCloudStackDiskUpdate,
    16  		Delete: resourceCloudStackDiskDelete,
    17  
    18  		Schema: map[string]*schema.Schema{
    19  			"name": &schema.Schema{
    20  				Type:     schema.TypeString,
    21  				Required: true,
    22  				ForceNew: true,
    23  			},
    24  
    25  			"attach": &schema.Schema{
    26  				Type:     schema.TypeBool,
    27  				Optional: true,
    28  				Default:  false,
    29  			},
    30  
    31  			"device_id": &schema.Schema{
    32  				Type:     schema.TypeInt,
    33  				Optional: true,
    34  				Computed: true,
    35  			},
    36  
    37  			"disk_offering": &schema.Schema{
    38  				Type:     schema.TypeString,
    39  				Optional: true,
    40  			},
    41  
    42  			"size": &schema.Schema{
    43  				Type:     schema.TypeInt,
    44  				Optional: true,
    45  				Computed: true,
    46  			},
    47  
    48  			"shrink_ok": &schema.Schema{
    49  				Type:     schema.TypeBool,
    50  				Optional: true,
    51  				Default:  false,
    52  			},
    53  
    54  			"virtual_machine_id": &schema.Schema{
    55  				Type:     schema.TypeString,
    56  				Optional: true,
    57  			},
    58  
    59  			"project": &schema.Schema{
    60  				Type:     schema.TypeString,
    61  				Optional: true,
    62  				Computed: true,
    63  				ForceNew: true,
    64  			},
    65  
    66  			"zone": &schema.Schema{
    67  				Type:     schema.TypeString,
    68  				Required: true,
    69  				ForceNew: true,
    70  			},
    71  		},
    72  	}
    73  }
    74  
    75  func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) error {
    76  	cs := meta.(*cloudstack.CloudStackClient)
    77  	d.Partial(true)
    78  
    79  	name := d.Get("name").(string)
    80  
    81  	// Create a new parameter struct
    82  	p := cs.Volume.NewCreateVolumeParams()
    83  	p.SetName(name)
    84  
    85  	// Retrieve the disk_offering ID
    86  	diskofferingid, e := retrieveID(cs, "disk_offering", d.Get("disk_offering").(string))
    87  	if e != nil {
    88  		return e.Error()
    89  	}
    90  	// Set the disk_offering ID
    91  	p.SetDiskofferingid(diskofferingid)
    92  
    93  	if d.Get("size").(int) != 0 {
    94  		// Set the volume size
    95  		p.SetSize(int64(d.Get("size").(int)))
    96  	}
    97  
    98  	// If there is a project supplied, we retrieve and set the project id
    99  	if err := setProjectid(p, cs, d); err != nil {
   100  		return err
   101  	}
   102  
   103  	// Retrieve the zone ID
   104  	zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
   105  	if e != nil {
   106  		return e.Error()
   107  	}
   108  	// Set the zone ID
   109  	p.SetZoneid(zoneid)
   110  
   111  	// Create the new volume
   112  	r, err := cs.Volume.CreateVolume(p)
   113  	if err != nil {
   114  		return fmt.Errorf("Error creating the new disk %s: %s", name, err)
   115  	}
   116  
   117  	// Set the volume ID and partials
   118  	d.SetId(r.Id)
   119  	d.SetPartial("name")
   120  	d.SetPartial("device_id")
   121  	d.SetPartial("disk_offering")
   122  	d.SetPartial("size")
   123  	d.SetPartial("virtual_machine_id")
   124  	d.SetPartial("project")
   125  	d.SetPartial("zone")
   126  
   127  	if d.Get("attach").(bool) {
   128  		err := resourceCloudStackDiskAttach(d, meta)
   129  		if err != nil {
   130  			return fmt.Errorf("Error attaching the new disk %s to virtual machine: %s", name, err)
   131  		}
   132  
   133  		// Set the additional partial
   134  		d.SetPartial("attach")
   135  	}
   136  
   137  	d.Partial(false)
   138  	return resourceCloudStackDiskRead(d, meta)
   139  }
   140  
   141  func resourceCloudStackDiskRead(d *schema.ResourceData, meta interface{}) error {
   142  	cs := meta.(*cloudstack.CloudStackClient)
   143  
   144  	// Get the volume details
   145  	v, count, err := cs.Volume.GetVolumeByID(
   146  		d.Id(),
   147  		cloudstack.WithProject(d.Get("project").(string)),
   148  	)
   149  	if err != nil {
   150  		if count == 0 {
   151  			d.SetId("")
   152  			return nil
   153  		}
   154  
   155  		return err
   156  	}
   157  
   158  	d.Set("name", v.Name)
   159  	d.Set("attach", v.Attached != "")           // If attached this contains a timestamp when attached
   160  	d.Set("size", int(v.Size/(1024*1024*1024))) // Needed to get GB's again
   161  
   162  	setValueOrID(d, "disk_offering", v.Diskofferingname, v.Diskofferingid)
   163  	setValueOrID(d, "project", v.Project, v.Projectid)
   164  	setValueOrID(d, "zone", v.Zonename, v.Zoneid)
   165  
   166  	if v.Attached != "" {
   167  		d.Set("device_id", int(v.Deviceid))
   168  		d.Set("virtual_machine_id", v.Virtualmachineid)
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) error {
   175  	cs := meta.(*cloudstack.CloudStackClient)
   176  	d.Partial(true)
   177  
   178  	name := d.Get("name").(string)
   179  
   180  	if d.HasChange("disk_offering") || d.HasChange("size") {
   181  		// Detach the volume (re-attach is done at the end of this function)
   182  		if err := resourceCloudStackDiskDetach(d, meta); err != nil {
   183  			return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
   184  		}
   185  
   186  		// Create a new parameter struct
   187  		p := cs.Volume.NewResizeVolumeParams(d.Id())
   188  
   189  		// Retrieve the disk_offering ID
   190  		diskofferingid, e := retrieveID(cs, "disk_offering", d.Get("disk_offering").(string))
   191  		if e != nil {
   192  			return e.Error()
   193  		}
   194  
   195  		// Set the disk_offering ID
   196  		p.SetDiskofferingid(diskofferingid)
   197  
   198  		if d.Get("size").(int) != 0 {
   199  			// Set the size
   200  			p.SetSize(int64(d.Get("size").(int)))
   201  		}
   202  
   203  		// Set the shrink bit
   204  		p.SetShrinkok(d.Get("shrink_ok").(bool))
   205  
   206  		// Change the disk_offering
   207  		r, err := cs.Volume.ResizeVolume(p)
   208  		if err != nil {
   209  			return fmt.Errorf("Error changing disk offering/size for disk %s: %s", name, err)
   210  		}
   211  
   212  		// Update the volume ID and set partials
   213  		d.SetId(r.Id)
   214  		d.SetPartial("disk_offering")
   215  		d.SetPartial("size")
   216  	}
   217  
   218  	// If the device ID changed, just detach here so we can re-attach the
   219  	// volume at the end of this function
   220  	if d.HasChange("device_id") || d.HasChange("virtual_machine") {
   221  		// Detach the volume
   222  		if err := resourceCloudStackDiskDetach(d, meta); err != nil {
   223  			return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
   224  		}
   225  	}
   226  
   227  	if d.Get("attach").(bool) {
   228  		// Attach the volume
   229  		err := resourceCloudStackDiskAttach(d, meta)
   230  		if err != nil {
   231  			return fmt.Errorf("Error attaching disk %s to virtual machine: %s", name, err)
   232  		}
   233  
   234  		// Set the additional partials
   235  		d.SetPartial("attach")
   236  		d.SetPartial("device_id")
   237  		d.SetPartial("virtual_machine_id")
   238  	} else {
   239  		// Detach the volume
   240  		if err := resourceCloudStackDiskDetach(d, meta); err != nil {
   241  			return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
   242  		}
   243  	}
   244  
   245  	d.Partial(false)
   246  	return resourceCloudStackDiskRead(d, meta)
   247  }
   248  
   249  func resourceCloudStackDiskDelete(d *schema.ResourceData, meta interface{}) error {
   250  	cs := meta.(*cloudstack.CloudStackClient)
   251  
   252  	// Detach the volume
   253  	if err := resourceCloudStackDiskDetach(d, meta); err != nil {
   254  		return err
   255  	}
   256  
   257  	// Create a new parameter struct
   258  	p := cs.Volume.NewDeleteVolumeParams(d.Id())
   259  
   260  	// Delete the voluem
   261  	if _, err := cs.Volume.DeleteVolume(p); err != nil {
   262  		// This is a very poor way to be told the ID does no longer exist :(
   263  		if strings.Contains(err.Error(), fmt.Sprintf(
   264  			"Invalid parameter id value=%s due to incorrect long value format, "+
   265  				"or entity does not exist", d.Id())) {
   266  			return nil
   267  		}
   268  
   269  		return err
   270  	}
   271  
   272  	return nil
   273  }
   274  
   275  func resourceCloudStackDiskAttach(d *schema.ResourceData, meta interface{}) error {
   276  	cs := meta.(*cloudstack.CloudStackClient)
   277  
   278  	if virtualmachineid, ok := d.GetOk("virtual_machine_id"); ok {
   279  		// First check if the disk isn't already attached
   280  		if attached, err := isAttached(d, meta); err != nil || attached {
   281  			return err
   282  		}
   283  
   284  		// Create a new parameter struct
   285  		p := cs.Volume.NewAttachVolumeParams(d.Id(), virtualmachineid.(string))
   286  
   287  		if deviceid, ok := d.GetOk("device_id"); ok {
   288  			p.SetDeviceid(int64(deviceid.(int)))
   289  		}
   290  
   291  		// Attach the new volume
   292  		r, err := Retry(10, retryableAttachVolumeFunc(cs, p))
   293  		if err != nil {
   294  			return fmt.Errorf("Error attaching volume to VM: %s", err)
   295  		}
   296  
   297  		d.SetId(r.(*cloudstack.AttachVolumeResponse).Id)
   298  	}
   299  
   300  	return nil
   301  }
   302  
   303  func resourceCloudStackDiskDetach(d *schema.ResourceData, meta interface{}) error {
   304  	cs := meta.(*cloudstack.CloudStackClient)
   305  
   306  	// Check if the volume is actually attached, before detaching
   307  	if attached, err := isAttached(d, meta); err != nil || !attached {
   308  		return err
   309  	}
   310  
   311  	// Create a new parameter struct
   312  	p := cs.Volume.NewDetachVolumeParams()
   313  
   314  	// Set the volume ID
   315  	p.SetId(d.Id())
   316  
   317  	// Detach the currently attached volume
   318  	_, err := cs.Volume.DetachVolume(p)
   319  	if err != nil {
   320  		if virtualmachineid, ok := d.GetOk("virtual_machine_id"); ok {
   321  			// Create a new parameter struct
   322  			pd := cs.VirtualMachine.NewStopVirtualMachineParams(virtualmachineid.(string))
   323  
   324  			// Stop the virtual machine in order to be able to detach the disk
   325  			if _, err := cs.VirtualMachine.StopVirtualMachine(pd); err != nil {
   326  				return err
   327  			}
   328  
   329  			// Try again to detach the currently attached volume
   330  			if _, err := cs.Volume.DetachVolume(p); err != nil {
   331  				return err
   332  			}
   333  
   334  			// Create a new parameter struct
   335  			pu := cs.VirtualMachine.NewStartVirtualMachineParams(virtualmachineid.(string))
   336  
   337  			// Start the virtual machine again
   338  			if _, err := cs.VirtualMachine.StartVirtualMachine(pu); err != nil {
   339  				return err
   340  			}
   341  		}
   342  	}
   343  
   344  	return err
   345  }
   346  
   347  func isAttached(d *schema.ResourceData, meta interface{}) (bool, error) {
   348  	cs := meta.(*cloudstack.CloudStackClient)
   349  
   350  	// Get the volume details
   351  	v, _, err := cs.Volume.GetVolumeByID(
   352  		d.Id(),
   353  		cloudstack.WithProject(d.Get("project").(string)),
   354  	)
   355  	if err != nil {
   356  		return false, err
   357  	}
   358  
   359  	return v.Attached != "", nil
   360  }
   361  
   362  func retryableAttachVolumeFunc(
   363  	cs *cloudstack.CloudStackClient,
   364  	p *cloudstack.AttachVolumeParams) func() (interface{}, error) {
   365  	return func() (interface{}, error) {
   366  		r, err := cs.Volume.AttachVolume(p)
   367  		if err != nil {
   368  			return nil, err
   369  		}
   370  		return r, nil
   371  	}
   372  }