github.com/paulmey/terraform@v0.5.2-0.20150519145237-046e9b4c884d/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": &schema.Schema{
    32  				Type:     schema.TypeString,
    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": &schema.Schema{
    55  				Type:     schema.TypeString,
    56  				Optional: true,
    57  			},
    58  
    59  			"zone": &schema.Schema{
    60  				Type:     schema.TypeString,
    61  				Required: true,
    62  				ForceNew: true,
    63  			},
    64  		},
    65  	}
    66  }
    67  
    68  func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) error {
    69  	cs := meta.(*cloudstack.CloudStackClient)
    70  	d.Partial(true)
    71  
    72  	name := d.Get("name").(string)
    73  
    74  	// Create a new parameter struct
    75  	p := cs.Volume.NewCreateVolumeParams(name)
    76  
    77  	// Retrieve the disk_offering UUID
    78  	diskofferingid, e := retrieveUUID(cs, "disk_offering", d.Get("disk_offering").(string))
    79  	if e != nil {
    80  		return e.Error()
    81  	}
    82  	// Set the disk_offering UUID
    83  	p.SetDiskofferingid(diskofferingid)
    84  
    85  	if d.Get("size").(int) != 0 {
    86  		// Set the volume size
    87  		p.SetSize(int64(d.Get("size").(int)))
    88  	}
    89  
    90  	// Retrieve the zone UUID
    91  	zoneid, e := retrieveUUID(cs, "zone", d.Get("zone").(string))
    92  	if e != nil {
    93  		return e.Error()
    94  	}
    95  	// Set the zone ID
    96  	p.SetZoneid(zoneid)
    97  
    98  	// Create the new volume
    99  	r, err := cs.Volume.CreateVolume(p)
   100  	if err != nil {
   101  		return fmt.Errorf("Error creating the new disk %s: %s", name, err)
   102  	}
   103  
   104  	// Set the volume UUID and partials
   105  	d.SetId(r.Id)
   106  	d.SetPartial("name")
   107  	d.SetPartial("device")
   108  	d.SetPartial("disk_offering")
   109  	d.SetPartial("size")
   110  	d.SetPartial("virtual_machine")
   111  	d.SetPartial("zone")
   112  
   113  	if d.Get("attach").(bool) {
   114  		err := resourceCloudStackDiskAttach(d, meta)
   115  		if err != nil {
   116  			return fmt.Errorf("Error attaching the new disk %s to virtual machine: %s", name, err)
   117  		}
   118  
   119  		// Set the additional partial
   120  		d.SetPartial("attach")
   121  	}
   122  
   123  	d.Partial(false)
   124  	return resourceCloudStackDiskRead(d, meta)
   125  }
   126  
   127  func resourceCloudStackDiskRead(d *schema.ResourceData, meta interface{}) error {
   128  	cs := meta.(*cloudstack.CloudStackClient)
   129  
   130  	// Get the volume details
   131  	v, count, err := cs.Volume.GetVolumeByID(d.Id())
   132  	if err != nil {
   133  		if count == 0 {
   134  			d.SetId("")
   135  			return nil
   136  		}
   137  
   138  		return err
   139  	}
   140  
   141  	d.Set("name", v.Name)
   142  	d.Set("attach", v.Attached != "")           // If attached this will contain a timestamp when attached
   143  	d.Set("size", int(v.Size/(1024*1024*1024))) // Needed to get GB's again
   144  
   145  	setValueOrUUID(d, "disk_offering", v.Diskofferingname, v.Diskofferingid)
   146  	setValueOrUUID(d, "zone", v.Zonename, v.Zoneid)
   147  
   148  	if v.Attached != "" {
   149  		// Get the virtual machine details
   150  		vm, _, err := cs.VirtualMachine.GetVirtualMachineByID(v.Virtualmachineid)
   151  		if err != nil {
   152  			return err
   153  		}
   154  
   155  		// Get the guest OS type details
   156  		os, _, err := cs.GuestOS.GetOsTypeByID(vm.Guestosid)
   157  		if err != nil {
   158  			return err
   159  		}
   160  
   161  		// Get the guest OS category details
   162  		c, _, err := cs.GuestOS.GetOsCategoryByID(os.Oscategoryid)
   163  		if err != nil {
   164  			return err
   165  		}
   166  
   167  		d.Set("device", retrieveDeviceName(v.Deviceid, c.Name))
   168  		d.Set("virtual_machine", v.Vmname)
   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 UUID
   190  		diskofferingid, e := retrieveUUID(cs, "disk_offering", d.Get("disk_offering").(string))
   191  		if e != nil {
   192  			return e.Error()
   193  		}
   194  
   195  		// Set the disk_offering UUID
   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 UUID and set partials
   213  		d.SetId(r.Id)
   214  		d.SetPartial("disk_offering")
   215  		d.SetPartial("size")
   216  	}
   217  
   218  	// If the device changed, just detach here so we can re-attach the
   219  	// volume at the end of this function
   220  	if d.HasChange("device") || 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")
   237  		d.SetPartial("virtual_machine")
   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 UUID 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  	// First check if the disk isn't already attached
   279  	if attached, err := isAttached(cs, d.Id()); err != nil || attached {
   280  		return err
   281  	}
   282  
   283  	// Retrieve the virtual_machine UUID
   284  	virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string))
   285  	if e != nil {
   286  		return e.Error()
   287  	}
   288  
   289  	// Create a new parameter struct
   290  	p := cs.Volume.NewAttachVolumeParams(d.Id(), virtualmachineid)
   291  
   292  	if device, ok := d.GetOk("device"); ok {
   293  		// Retrieve the device ID
   294  		deviceid := retrieveDeviceID(device.(string))
   295  		if deviceid == -1 {
   296  			return fmt.Errorf("Device %s is not a valid device", device.(string))
   297  		}
   298  
   299  		// Set the device ID
   300  		p.SetDeviceid(deviceid)
   301  	}
   302  
   303  	// Attach the new volume
   304  	r, err := cs.Volume.AttachVolume(p)
   305  	if err != nil {
   306  		return err
   307  	}
   308  
   309  	d.SetId(r.Id)
   310  
   311  	return nil
   312  }
   313  
   314  func resourceCloudStackDiskDetach(d *schema.ResourceData, meta interface{}) error {
   315  	cs := meta.(*cloudstack.CloudStackClient)
   316  
   317  	// Check if the volume is actually attached, before detaching
   318  	if attached, err := isAttached(cs, d.Id()); err != nil || !attached {
   319  		return err
   320  	}
   321  
   322  	// Create a new parameter struct
   323  	p := cs.Volume.NewDetachVolumeParams()
   324  
   325  	// Set the volume UUID
   326  	p.SetId(d.Id())
   327  
   328  	// Detach the currently attached volume
   329  	if _, err := cs.Volume.DetachVolume(p); err != nil {
   330  		// Retrieve the virtual_machine UUID
   331  		virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string))
   332  		if e != nil {
   333  			return e.Error()
   334  		}
   335  
   336  		// Create a new parameter struct
   337  		pd := cs.VirtualMachine.NewStopVirtualMachineParams(virtualmachineid)
   338  
   339  		// Stop the virtual machine in order to be able to detach the disk
   340  		if _, err := cs.VirtualMachine.StopVirtualMachine(pd); err != nil {
   341  			return err
   342  		}
   343  
   344  		// Try again to detach the currently attached volume
   345  		if _, err := cs.Volume.DetachVolume(p); err != nil {
   346  			return err
   347  		}
   348  
   349  		// Create a new parameter struct
   350  		pu := cs.VirtualMachine.NewStartVirtualMachineParams(virtualmachineid)
   351  
   352  		// Start the virtual machine again
   353  		if _, err := cs.VirtualMachine.StartVirtualMachine(pu); err != nil {
   354  			return err
   355  		}
   356  	}
   357  
   358  	return nil
   359  }
   360  
   361  func isAttached(cs *cloudstack.CloudStackClient, id string) (bool, error) {
   362  	// Get the volume details
   363  	v, _, err := cs.Volume.GetVolumeByID(id)
   364  	if err != nil {
   365  		return false, err
   366  	}
   367  
   368  	return v.Attached != "", nil
   369  }
   370  
   371  func retrieveDeviceID(device string) int64 {
   372  	switch device {
   373  	case "/dev/xvdb", "D:":
   374  		return 1
   375  	case "/dev/xvdc", "E:":
   376  		return 2
   377  	case "/dev/xvde", "F:":
   378  		return 4
   379  	case "/dev/xvdf", "G:":
   380  		return 5
   381  	case "/dev/xvdg", "H:":
   382  		return 6
   383  	case "/dev/xvdh", "I:":
   384  		return 7
   385  	case "/dev/xvdi", "J:":
   386  		return 8
   387  	case "/dev/xvdj", "K:":
   388  		return 9
   389  	case "/dev/xvdk", "L:":
   390  		return 10
   391  	case "/dev/xvdl", "M:":
   392  		return 11
   393  	case "/dev/xvdm", "N:":
   394  		return 12
   395  	case "/dev/xvdn", "O:":
   396  		return 13
   397  	case "/dev/xvdo", "P:":
   398  		return 14
   399  	case "/dev/xvdp", "Q:":
   400  		return 15
   401  	default:
   402  		return -1
   403  	}
   404  }
   405  
   406  func retrieveDeviceName(device int64, os string) string {
   407  	switch device {
   408  	case 1:
   409  		if os == "Windows" {
   410  			return "D:"
   411  		} else {
   412  			return "/dev/xvdb"
   413  		}
   414  	case 2:
   415  		if os == "Windows" {
   416  			return "E:"
   417  		} else {
   418  			return "/dev/xvdc"
   419  		}
   420  	case 4:
   421  		if os == "Windows" {
   422  			return "F:"
   423  		} else {
   424  			return "/dev/xvde"
   425  		}
   426  	case 5:
   427  		if os == "Windows" {
   428  			return "G:"
   429  		} else {
   430  			return "/dev/xvdf"
   431  		}
   432  	case 6:
   433  		if os == "Windows" {
   434  			return "H:"
   435  		} else {
   436  			return "/dev/xvdg"
   437  		}
   438  	case 7:
   439  		if os == "Windows" {
   440  			return "I:"
   441  		} else {
   442  			return "/dev/xvdh"
   443  		}
   444  	case 8:
   445  		if os == "Windows" {
   446  			return "J:"
   447  		} else {
   448  			return "/dev/xvdi"
   449  		}
   450  	case 9:
   451  		if os == "Windows" {
   452  			return "K:"
   453  		} else {
   454  			return "/dev/xvdj"
   455  		}
   456  	case 10:
   457  		if os == "Windows" {
   458  			return "L:"
   459  		} else {
   460  			return "/dev/xvdk"
   461  		}
   462  	case 11:
   463  		if os == "Windows" {
   464  			return "M:"
   465  		} else {
   466  			return "/dev/xvdl"
   467  		}
   468  	case 12:
   469  		if os == "Windows" {
   470  			return "N:"
   471  		} else {
   472  			return "/dev/xvdm"
   473  		}
   474  	case 13:
   475  		if os == "Windows" {
   476  			return "O:"
   477  		} else {
   478  			return "/dev/xvdn"
   479  		}
   480  	case 14:
   481  		if os == "Windows" {
   482  			return "P:"
   483  		} else {
   484  			return "/dev/xvdo"
   485  		}
   486  	case 15:
   487  		if os == "Windows" {
   488  			return "Q:"
   489  		} else {
   490  			return "/dev/xvdp"
   491  		}
   492  	default:
   493  		return "unknown"
   494  	}
   495  }