2014-12-10 22:20:52 +01:00
|
|
|
package cloudstack
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
|
|
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
|
|
)
|
|
|
|
|
|
|
|
func resourceCloudStackDisk() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: resourceCloudStackDiskCreate,
|
|
|
|
Read: resourceCloudStackDiskRead,
|
|
|
|
Update: resourceCloudStackDiskUpdate,
|
|
|
|
Delete: resourceCloudStackDiskDelete,
|
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"name": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"attach": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: false,
|
|
|
|
},
|
|
|
|
|
2016-07-07 15:09:58 +02:00
|
|
|
"device_id": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
2014-12-10 22:20:52 +01:00
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"disk_offering": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"size": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
2015-01-12 15:04:58 +01:00
|
|
|
Computed: true,
|
2014-12-10 22:20:52 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
"shrink_ok": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: false,
|
|
|
|
},
|
|
|
|
|
2016-06-27 10:19:00 +02:00
|
|
|
"virtual_machine_id": &schema.Schema{
|
2014-12-10 22:20:52 +01:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
2015-08-21 16:59:35 +02:00
|
|
|
"project": &schema.Schema{
|
2014-12-10 22:20:52 +01:00
|
|
|
Type: schema.TypeString,
|
2015-08-21 16:59:35 +02:00
|
|
|
Optional: true,
|
2016-07-08 17:16:35 +02:00
|
|
|
Computed: true,
|
2014-12-10 22:20:52 +01:00
|
|
|
ForceNew: true,
|
|
|
|
},
|
2015-08-20 19:35:11 +02:00
|
|
|
|
2015-08-21 16:59:35 +02:00
|
|
|
"zone": &schema.Schema{
|
2015-08-20 19:35:11 +02:00
|
|
|
Type: schema.TypeString,
|
2015-08-21 16:59:35 +02:00
|
|
|
Required: true,
|
2015-08-20 19:35:11 +02:00
|
|
|
ForceNew: true,
|
|
|
|
},
|
2014-12-10 22:20:52 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
cs := meta.(*cloudstack.CloudStackClient)
|
|
|
|
d.Partial(true)
|
|
|
|
|
|
|
|
name := d.Get("name").(string)
|
|
|
|
|
|
|
|
// Create a new parameter struct
|
2016-04-19 22:48:57 +02:00
|
|
|
p := cs.Volume.NewCreateVolumeParams()
|
|
|
|
p.SetName(name)
|
2014-12-10 22:20:52 +01:00
|
|
|
|
2015-10-05 14:05:21 +02:00
|
|
|
// Retrieve the disk_offering ID
|
|
|
|
diskofferingid, e := retrieveID(cs, "disk_offering", d.Get("disk_offering").(string))
|
2014-12-10 22:20:52 +01:00
|
|
|
if e != nil {
|
|
|
|
return e.Error()
|
|
|
|
}
|
2015-10-05 14:05:21 +02:00
|
|
|
// Set the disk_offering ID
|
2014-12-10 22:20:52 +01:00
|
|
|
p.SetDiskofferingid(diskofferingid)
|
|
|
|
|
|
|
|
if d.Get("size").(int) != 0 {
|
|
|
|
// Set the volume size
|
2015-03-25 22:48:44 +01:00
|
|
|
p.SetSize(int64(d.Get("size").(int)))
|
2014-12-10 22:20:52 +01:00
|
|
|
}
|
|
|
|
|
2015-09-11 20:56:20 +02:00
|
|
|
// If there is a project supplied, we retrieve and set the project id
|
2016-04-11 17:14:19 +02:00
|
|
|
if err := setProjectid(p, cs, d); err != nil {
|
|
|
|
return err
|
2015-08-21 16:59:35 +02:00
|
|
|
}
|
|
|
|
|
2015-10-05 14:05:21 +02:00
|
|
|
// Retrieve the zone ID
|
|
|
|
zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
|
2014-12-10 22:20:52 +01:00
|
|
|
if e != nil {
|
|
|
|
return e.Error()
|
|
|
|
}
|
|
|
|
// Set the zone ID
|
|
|
|
p.SetZoneid(zoneid)
|
|
|
|
|
|
|
|
// Create the new volume
|
|
|
|
r, err := cs.Volume.CreateVolume(p)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error creating the new disk %s: %s", name, err)
|
|
|
|
}
|
|
|
|
|
2015-10-05 14:05:21 +02:00
|
|
|
// Set the volume ID and partials
|
2014-12-10 22:20:52 +01:00
|
|
|
d.SetId(r.Id)
|
|
|
|
d.SetPartial("name")
|
2016-07-07 15:09:58 +02:00
|
|
|
d.SetPartial("device_id")
|
2014-12-10 22:20:52 +01:00
|
|
|
d.SetPartial("disk_offering")
|
|
|
|
d.SetPartial("size")
|
2016-06-27 10:19:00 +02:00
|
|
|
d.SetPartial("virtual_machine_id")
|
2015-08-21 16:59:35 +02:00
|
|
|
d.SetPartial("project")
|
2014-12-10 22:20:52 +01:00
|
|
|
d.SetPartial("zone")
|
|
|
|
|
|
|
|
if d.Get("attach").(bool) {
|
|
|
|
err := resourceCloudStackDiskAttach(d, meta)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error attaching the new disk %s to virtual machine: %s", name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the additional partial
|
|
|
|
d.SetPartial("attach")
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Partial(false)
|
|
|
|
return resourceCloudStackDiskRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceCloudStackDiskRead(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
cs := meta.(*cloudstack.CloudStackClient)
|
|
|
|
|
|
|
|
// Get the volume details
|
2016-04-21 17:31:53 +02:00
|
|
|
v, count, err := cs.Volume.GetVolumeByID(
|
|
|
|
d.Id(),
|
|
|
|
cloudstack.WithProject(d.Get("project").(string)),
|
|
|
|
)
|
2014-12-10 22:20:52 +01:00
|
|
|
if err != nil {
|
|
|
|
if count == 0 {
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Set("name", v.Name)
|
2016-04-21 17:31:53 +02:00
|
|
|
d.Set("attach", v.Attached != "") // If attached this contains a timestamp when attached
|
2015-03-25 22:48:44 +01:00
|
|
|
d.Set("size", int(v.Size/(1024*1024*1024))) // Needed to get GB's again
|
2015-04-29 11:21:37 +02:00
|
|
|
|
2015-10-05 14:05:21 +02:00
|
|
|
setValueOrID(d, "disk_offering", v.Diskofferingname, v.Diskofferingid)
|
|
|
|
setValueOrID(d, "project", v.Project, v.Projectid)
|
|
|
|
setValueOrID(d, "zone", v.Zonename, v.Zoneid)
|
2014-12-10 22:20:52 +01:00
|
|
|
|
|
|
|
if v.Attached != "" {
|
2016-07-07 15:09:58 +02:00
|
|
|
d.Set("device_id", int(v.Deviceid))
|
2016-06-27 10:19:00 +02:00
|
|
|
d.Set("virtual_machine_id", v.Virtualmachineid)
|
2014-12-10 22:20:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
cs := meta.(*cloudstack.CloudStackClient)
|
|
|
|
d.Partial(true)
|
|
|
|
|
|
|
|
name := d.Get("name").(string)
|
|
|
|
|
|
|
|
if d.HasChange("disk_offering") || d.HasChange("size") {
|
|
|
|
// Detach the volume (re-attach is done at the end of this function)
|
|
|
|
if err := resourceCloudStackDiskDetach(d, meta); err != nil {
|
|
|
|
return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new parameter struct
|
2015-01-15 10:04:09 +01:00
|
|
|
p := cs.Volume.NewResizeVolumeParams(d.Id())
|
2014-12-10 22:20:52 +01:00
|
|
|
|
2015-10-05 14:05:21 +02:00
|
|
|
// Retrieve the disk_offering ID
|
|
|
|
diskofferingid, e := retrieveID(cs, "disk_offering", d.Get("disk_offering").(string))
|
2014-12-10 22:20:52 +01:00
|
|
|
if e != nil {
|
|
|
|
return e.Error()
|
|
|
|
}
|
|
|
|
|
2015-10-05 14:05:21 +02:00
|
|
|
// Set the disk_offering ID
|
2014-12-10 22:20:52 +01:00
|
|
|
p.SetDiskofferingid(diskofferingid)
|
|
|
|
|
|
|
|
if d.Get("size").(int) != 0 {
|
|
|
|
// Set the size
|
2015-03-25 22:48:44 +01:00
|
|
|
p.SetSize(int64(d.Get("size").(int)))
|
2014-12-10 22:20:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set the shrink bit
|
|
|
|
p.SetShrinkok(d.Get("shrink_ok").(bool))
|
|
|
|
|
|
|
|
// Change the disk_offering
|
|
|
|
r, err := cs.Volume.ResizeVolume(p)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error changing disk offering/size for disk %s: %s", name, err)
|
|
|
|
}
|
|
|
|
|
2015-10-05 14:05:21 +02:00
|
|
|
// Update the volume ID and set partials
|
2014-12-10 22:20:52 +01:00
|
|
|
d.SetId(r.Id)
|
|
|
|
d.SetPartial("disk_offering")
|
|
|
|
d.SetPartial("size")
|
|
|
|
}
|
|
|
|
|
2016-07-07 15:09:58 +02:00
|
|
|
// If the device ID changed, just detach here so we can re-attach the
|
2014-12-10 22:20:52 +01:00
|
|
|
// volume at the end of this function
|
2016-07-07 15:09:58 +02:00
|
|
|
if d.HasChange("device_id") || d.HasChange("virtual_machine") {
|
2014-12-10 22:20:52 +01:00
|
|
|
// Detach the volume
|
|
|
|
if err := resourceCloudStackDiskDetach(d, meta); err != nil {
|
|
|
|
return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if d.Get("attach").(bool) {
|
|
|
|
// Attach the volume
|
|
|
|
err := resourceCloudStackDiskAttach(d, meta)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error attaching disk %s to virtual machine: %s", name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the additional partials
|
|
|
|
d.SetPartial("attach")
|
2016-07-07 15:09:58 +02:00
|
|
|
d.SetPartial("device_id")
|
2016-06-27 10:19:00 +02:00
|
|
|
d.SetPartial("virtual_machine_id")
|
2014-12-10 22:20:52 +01:00
|
|
|
} else {
|
|
|
|
// Detach the volume
|
|
|
|
if err := resourceCloudStackDiskDetach(d, meta); err != nil {
|
|
|
|
return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Partial(false)
|
|
|
|
return resourceCloudStackDiskRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceCloudStackDiskDelete(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
cs := meta.(*cloudstack.CloudStackClient)
|
|
|
|
|
|
|
|
// Detach the volume
|
|
|
|
if err := resourceCloudStackDiskDetach(d, meta); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new parameter struct
|
|
|
|
p := cs.Volume.NewDeleteVolumeParams(d.Id())
|
|
|
|
|
|
|
|
// Delete the voluem
|
|
|
|
if _, err := cs.Volume.DeleteVolume(p); err != nil {
|
2015-10-05 14:05:21 +02:00
|
|
|
// This is a very poor way to be told the ID does no longer exist :(
|
2014-12-10 22:20:52 +01:00
|
|
|
if strings.Contains(err.Error(), fmt.Sprintf(
|
|
|
|
"Invalid parameter id value=%s due to incorrect long value format, "+
|
|
|
|
"or entity does not exist", d.Id())) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceCloudStackDiskAttach(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
cs := meta.(*cloudstack.CloudStackClient)
|
|
|
|
|
2016-06-27 10:19:00 +02:00
|
|
|
if virtualmachineid, ok := d.GetOk("virtual_machine_id"); ok {
|
|
|
|
// First check if the disk isn't already attached
|
|
|
|
if attached, err := isAttached(d, meta); err != nil || attached {
|
|
|
|
return err
|
|
|
|
}
|
2014-12-10 22:20:52 +01:00
|
|
|
|
2016-06-27 10:19:00 +02:00
|
|
|
// Create a new parameter struct
|
|
|
|
p := cs.Volume.NewAttachVolumeParams(d.Id(), virtualmachineid.(string))
|
2014-12-10 22:20:52 +01:00
|
|
|
|
2016-07-07 15:09:58 +02:00
|
|
|
if deviceid, ok := d.GetOk("device_id"); ok {
|
|
|
|
p.SetDeviceid(int64(deviceid.(int)))
|
2014-12-10 22:20:52 +01:00
|
|
|
}
|
|
|
|
|
2016-06-27 10:19:00 +02:00
|
|
|
// Attach the new volume
|
2016-07-07 15:09:58 +02:00
|
|
|
r, err := Retry(10, retryableAttachVolumeFunc(cs, p))
|
2016-06-27 10:19:00 +02:00
|
|
|
if err != nil {
|
2016-07-07 15:09:58 +02:00
|
|
|
return fmt.Errorf("Error attaching volume to VM: %s", err)
|
2016-06-27 10:19:00 +02:00
|
|
|
}
|
2014-12-10 22:20:52 +01:00
|
|
|
|
2016-06-27 10:19:00 +02:00
|
|
|
d.SetId(r.(*cloudstack.AttachVolumeResponse).Id)
|
2014-12-10 22:20:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceCloudStackDiskDetach(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
cs := meta.(*cloudstack.CloudStackClient)
|
|
|
|
|
|
|
|
// Check if the volume is actually attached, before detaching
|
2016-04-21 17:31:53 +02:00
|
|
|
if attached, err := isAttached(d, meta); err != nil || !attached {
|
2014-12-10 22:20:52 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new parameter struct
|
|
|
|
p := cs.Volume.NewDetachVolumeParams()
|
|
|
|
|
2015-10-05 14:05:21 +02:00
|
|
|
// Set the volume ID
|
2014-12-10 22:20:52 +01:00
|
|
|
p.SetId(d.Id())
|
|
|
|
|
|
|
|
// Detach the currently attached volume
|
2016-06-27 10:19:00 +02:00
|
|
|
_, err := cs.Volume.DetachVolume(p)
|
|
|
|
if err != nil {
|
|
|
|
if virtualmachineid, ok := d.GetOk("virtual_machine_id"); ok {
|
|
|
|
// Create a new parameter struct
|
|
|
|
pd := cs.VirtualMachine.NewStopVirtualMachineParams(virtualmachineid.(string))
|
|
|
|
|
|
|
|
// Stop the virtual machine in order to be able to detach the disk
|
|
|
|
if _, err := cs.VirtualMachine.StopVirtualMachine(pd); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try again to detach the currently attached volume
|
|
|
|
if _, err := cs.Volume.DetachVolume(p); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new parameter struct
|
|
|
|
pu := cs.VirtualMachine.NewStartVirtualMachineParams(virtualmachineid.(string))
|
|
|
|
|
|
|
|
// Start the virtual machine again
|
|
|
|
if _, err := cs.VirtualMachine.StartVirtualMachine(pu); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-12-10 22:20:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-27 10:19:00 +02:00
|
|
|
return err
|
2014-12-10 22:20:52 +01:00
|
|
|
}
|
|
|
|
|
2016-04-21 17:31:53 +02:00
|
|
|
func isAttached(d *schema.ResourceData, meta interface{}) (bool, error) {
|
|
|
|
cs := meta.(*cloudstack.CloudStackClient)
|
|
|
|
|
2014-12-10 22:20:52 +01:00
|
|
|
// Get the volume details
|
2016-04-21 17:31:53 +02:00
|
|
|
v, _, err := cs.Volume.GetVolumeByID(
|
|
|
|
d.Id(),
|
|
|
|
cloudstack.WithProject(d.Get("project").(string)),
|
|
|
|
)
|
2014-12-10 22:20:52 +01:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return v.Attached != "", nil
|
|
|
|
}
|
|
|
|
|
2015-08-21 16:59:35 +02:00
|
|
|
func retryableAttachVolumeFunc(
|
|
|
|
cs *cloudstack.CloudStackClient,
|
|
|
|
p *cloudstack.AttachVolumeParams) func() (interface{}, error) {
|
|
|
|
return func() (interface{}, error) {
|
|
|
|
r, err := cs.Volume.AttachVolume(p)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
}
|