github.com/nalum/terraform@v0.3.2-0.20141223102918-aa2c22ffeff6/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  			},
    46  
    47  			"shrink_ok": &schema.Schema{
    48  				Type:     schema.TypeBool,
    49  				Optional: true,
    50  				Default:  false,
    51  			},
    52  
    53  			"virtual_machine": &schema.Schema{
    54  				Type:     schema.TypeString,
    55  				Optional: true,
    56  			},
    57  
    58  			"zone": &schema.Schema{
    59  				Type:     schema.TypeString,
    60  				Required: true,
    61  				ForceNew: true,
    62  			},
    63  		},
    64  	}
    65  }
    66  
    67  func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) error {
    68  	cs := meta.(*cloudstack.CloudStackClient)
    69  	d.Partial(true)
    70  
    71  	name := d.Get("name").(string)
    72  
    73  	// Create a new parameter struct
    74  	p := cs.Volume.NewCreateVolumeParams(name)
    75  
    76  	// Retrieve the disk_offering UUID
    77  	diskofferingid, e := retrieveUUID(cs, "disk_offering", d.Get("disk_offering").(string))
    78  	if e != nil {
    79  		return e.Error()
    80  	}
    81  	// Set the disk_offering UUID
    82  	p.SetDiskofferingid(diskofferingid)
    83  
    84  	if d.Get("size").(int) != 0 {
    85  		// Set the volume size
    86  		p.SetSize(d.Get("size").(int))
    87  	}
    88  
    89  	// Retrieve the zone UUID
    90  	zoneid, e := retrieveUUID(cs, "zone", d.Get("zone").(string))
    91  	if e != nil {
    92  		return e.Error()
    93  	}
    94  	// Set the zone ID
    95  	p.SetZoneid(zoneid)
    96  
    97  	// Create the new volume
    98  	r, err := cs.Volume.CreateVolume(p)
    99  	if err != nil {
   100  		return fmt.Errorf("Error creating the new disk %s: %s", name, err)
   101  	}
   102  
   103  	// Set the volume UUID and partials
   104  	d.SetId(r.Id)
   105  	d.SetPartial("name")
   106  	d.SetPartial("device")
   107  	d.SetPartial("disk_offering")
   108  	d.SetPartial("size")
   109  	d.SetPartial("virtual_machine")
   110  	d.SetPartial("zone")
   111  
   112  	if d.Get("attach").(bool) {
   113  		err := resourceCloudStackDiskAttach(d, meta)
   114  		if err != nil {
   115  			return fmt.Errorf("Error attaching the new disk %s to virtual machine: %s", name, err)
   116  		}
   117  
   118  		// Set the additional partial
   119  		d.SetPartial("attach")
   120  	}
   121  
   122  	d.Partial(false)
   123  	return resourceCloudStackDiskRead(d, meta)
   124  }
   125  
   126  func resourceCloudStackDiskRead(d *schema.ResourceData, meta interface{}) error {
   127  	cs := meta.(*cloudstack.CloudStackClient)
   128  
   129  	// Get the volume details
   130  	v, count, err := cs.Volume.GetVolumeByID(d.Id())
   131  	if err != nil {
   132  		if count == 0 {
   133  			d.SetId("")
   134  			return nil
   135  		}
   136  
   137  		return err
   138  	}
   139  
   140  	d.Set("name", v.Name)
   141  	d.Set("attach", v.Attached != "") // If attached this will contain a timestamp when attached
   142  	d.Set("disk_offering", v.Diskofferingname)
   143  	d.Set("size", v.Size/(1024*1024*1024)) // Needed to get GB's again
   144  	d.Set("zone", v.Zonename)
   145  
   146  	if v.Attached != "" {
   147  		// Get the virtual machine details
   148  		vm, _, err := cs.VirtualMachine.GetVirtualMachineByID(v.Virtualmachineid)
   149  		if err != nil {
   150  			return err
   151  		}
   152  
   153  		// Get the guest OS type details
   154  		os, _, err := cs.GuestOS.GetOsTypeByID(vm.Guestosid)
   155  		if err != nil {
   156  			return err
   157  		}
   158  
   159  		// Get the guest OS category details
   160  		c, _, err := cs.GuestOS.GetOsCategoryByID(os.Oscategoryid)
   161  		if err != nil {
   162  			return err
   163  		}
   164  
   165  		d.Set("device", retrieveDeviceName(v.Deviceid, c.Name))
   166  		d.Set("virtual_machine", v.Vmname)
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) error {
   173  	cs := meta.(*cloudstack.CloudStackClient)
   174  	d.Partial(true)
   175  
   176  	name := d.Get("name").(string)
   177  
   178  	if d.HasChange("disk_offering") || d.HasChange("size") {
   179  		// Detach the volume (re-attach is done at the end of this function)
   180  		if err := resourceCloudStackDiskDetach(d, meta); err != nil {
   181  			return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
   182  		}
   183  
   184  		// Create a new parameter struct
   185  		p := cs.Volume.NewResizeVolumeParams()
   186  
   187  		// Set the volume UUID
   188  		p.SetId(d.Id())
   189  
   190  		// Retrieve the disk_offering UUID
   191  		diskofferingid, e := retrieveUUID(cs, "disk_offering", d.Get("disk_offering").(string))
   192  		if e != nil {
   193  			return e.Error()
   194  		}
   195  
   196  		// Set the disk_offering UUID
   197  		p.SetDiskofferingid(diskofferingid)
   198  
   199  		if d.Get("size").(int) != 0 {
   200  			// Set the size
   201  			p.SetSize(d.Get("size").(int))
   202  		}
   203  
   204  		// Set the shrink bit
   205  		p.SetShrinkok(d.Get("shrink_ok").(bool))
   206  
   207  		// Change the disk_offering
   208  		r, err := cs.Volume.ResizeVolume(p)
   209  		if err != nil {
   210  			return fmt.Errorf("Error changing disk offering/size for disk %s: %s", name, err)
   211  		}
   212  
   213  		// Update the volume UUID and set partials
   214  		d.SetId(r.Id)
   215  		d.SetPartial("disk_offering")
   216  		d.SetPartial("size")
   217  	}
   218  
   219  	// If the device changed, just detach here so we can re-attach the
   220  	// volume at the end of this function
   221  	if d.HasChange("device") || d.HasChange("virtual_machine") {
   222  		// Detach the volume
   223  		if err := resourceCloudStackDiskDetach(d, meta); err != nil {
   224  			return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
   225  		}
   226  	}
   227  
   228  	if d.Get("attach").(bool) {
   229  		// Attach the volume
   230  		err := resourceCloudStackDiskAttach(d, meta)
   231  		if err != nil {
   232  			return fmt.Errorf("Error attaching disk %s to virtual machine: %s", name, err)
   233  		}
   234  
   235  		// Set the additional partials
   236  		d.SetPartial("attach")
   237  		d.SetPartial("device")
   238  		d.SetPartial("virtual_machine")
   239  	} else {
   240  		// Detach the volume
   241  		if err := resourceCloudStackDiskDetach(d, meta); err != nil {
   242  			return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
   243  		}
   244  	}
   245  
   246  	d.Partial(false)
   247  	return resourceCloudStackDiskRead(d, meta)
   248  }
   249  
   250  func resourceCloudStackDiskDelete(d *schema.ResourceData, meta interface{}) error {
   251  	cs := meta.(*cloudstack.CloudStackClient)
   252  
   253  	// Detach the volume
   254  	if err := resourceCloudStackDiskDetach(d, meta); err != nil {
   255  		return err
   256  	}
   257  
   258  	// Create a new parameter struct
   259  	p := cs.Volume.NewDeleteVolumeParams(d.Id())
   260  
   261  	// Delete the voluem
   262  	if _, err := cs.Volume.DeleteVolume(p); err != nil {
   263  		// This is a very poor way to be told the UUID does no longer exist :(
   264  		if strings.Contains(err.Error(), fmt.Sprintf(
   265  			"Invalid parameter id value=%s due to incorrect long value format, "+
   266  				"or entity does not exist", d.Id())) {
   267  			return nil
   268  		}
   269  
   270  		return err
   271  	}
   272  
   273  	return nil
   274  }
   275  
   276  func resourceCloudStackDiskAttach(d *schema.ResourceData, meta interface{}) error {
   277  	cs := meta.(*cloudstack.CloudStackClient)
   278  
   279  	// First check if the disk isn't already attached
   280  	if attached, err := isAttached(cs, d.Id()); err != nil || attached {
   281  		return err
   282  	}
   283  
   284  	// Retrieve the virtual_machine UUID
   285  	virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string))
   286  	if e != nil {
   287  		return e.Error()
   288  	}
   289  
   290  	// Create a new parameter struct
   291  	p := cs.Volume.NewAttachVolumeParams(d.Id(), virtualmachineid)
   292  
   293  	if device, ok := d.GetOk("device"); ok {
   294  		// Retrieve the device ID
   295  		deviceid := retrieveDeviceID(device.(string))
   296  		if deviceid == -1 {
   297  			return fmt.Errorf("Device %s is not a valid device", device.(string))
   298  		}
   299  
   300  		// Set the device ID
   301  		p.SetDeviceid(deviceid)
   302  	}
   303  
   304  	// Attach the new volume
   305  	r, err := cs.Volume.AttachVolume(p)
   306  	if err != nil {
   307  		return err
   308  	}
   309  
   310  	d.SetId(r.Id)
   311  
   312  	return nil
   313  }
   314  
   315  func resourceCloudStackDiskDetach(d *schema.ResourceData, meta interface{}) error {
   316  	cs := meta.(*cloudstack.CloudStackClient)
   317  
   318  	// Check if the volume is actually attached, before detaching
   319  	if attached, err := isAttached(cs, d.Id()); err != nil || !attached {
   320  		return err
   321  	}
   322  
   323  	// Create a new parameter struct
   324  	p := cs.Volume.NewDetachVolumeParams()
   325  
   326  	// Set the volume UUID
   327  	p.SetId(d.Id())
   328  
   329  	// Detach the currently attached volume
   330  	if _, err := cs.Volume.DetachVolume(p); err != nil {
   331  		// Retrieve the virtual_machine UUID
   332  		virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string))
   333  		if e != nil {
   334  			return e.Error()
   335  		}
   336  
   337  		// Create a new parameter struct
   338  		pd := cs.VirtualMachine.NewStopVirtualMachineParams(virtualmachineid)
   339  
   340  		// Stop the virtual machine in order to be able to detach the disk
   341  		if _, err := cs.VirtualMachine.StopVirtualMachine(pd); err != nil {
   342  			return err
   343  		}
   344  
   345  		// Try again to detach the currently attached volume
   346  		if _, err := cs.Volume.DetachVolume(p); err != nil {
   347  			return err
   348  		}
   349  
   350  		// Create a new parameter struct
   351  		pu := cs.VirtualMachine.NewStartVirtualMachineParams(virtualmachineid)
   352  
   353  		// Start the virtual machine again
   354  		if _, err := cs.VirtualMachine.StartVirtualMachine(pu); err != nil {
   355  			return err
   356  		}
   357  	}
   358  
   359  	return nil
   360  }
   361  
   362  func isAttached(cs *cloudstack.CloudStackClient, id string) (bool, error) {
   363  	// Get the volume details
   364  	v, _, err := cs.Volume.GetVolumeByID(id)
   365  	if err != nil {
   366  		return false, err
   367  	}
   368  
   369  	return v.Attached != "", nil
   370  }
   371  
   372  func retrieveDeviceID(device string) int {
   373  	switch device {
   374  	case "/dev/xvdb", "D:":
   375  		return 1
   376  	case "/dev/xvdc", "E:":
   377  		return 2
   378  	case "/dev/xvde", "F:":
   379  		return 4
   380  	case "/dev/xvdf", "G:":
   381  		return 5
   382  	case "/dev/xvdg", "H:":
   383  		return 6
   384  	case "/dev/xvdh", "I:":
   385  		return 7
   386  	case "/dev/xvdi", "J:":
   387  		return 8
   388  	case "/dev/xvdj", "K:":
   389  		return 9
   390  	case "/dev/xvdk", "L:":
   391  		return 10
   392  	case "/dev/xvdl", "M:":
   393  		return 11
   394  	case "/dev/xvdm", "N:":
   395  		return 12
   396  	case "/dev/xvdn", "O:":
   397  		return 13
   398  	case "/dev/xvdo", "P:":
   399  		return 14
   400  	case "/dev/xvdp", "Q:":
   401  		return 15
   402  	default:
   403  		return -1
   404  	}
   405  }
   406  
   407  func retrieveDeviceName(device int, os string) string {
   408  	switch device {
   409  	case 1:
   410  		if os == "Windows" {
   411  			return "D:"
   412  		} else {
   413  			return "/dev/xvdb"
   414  		}
   415  	case 2:
   416  		if os == "Windows" {
   417  			return "E:"
   418  		} else {
   419  			return "/dev/xvdc"
   420  		}
   421  	case 4:
   422  		if os == "Windows" {
   423  			return "F:"
   424  		} else {
   425  			return "/dev/xvde"
   426  		}
   427  	case 5:
   428  		if os == "Windows" {
   429  			return "G:"
   430  		} else {
   431  			return "/dev/xvdf"
   432  		}
   433  	case 6:
   434  		if os == "Windows" {
   435  			return "H:"
   436  		} else {
   437  			return "/dev/xvdg"
   438  		}
   439  	case 7:
   440  		if os == "Windows" {
   441  			return "I:"
   442  		} else {
   443  			return "/dev/xvdh"
   444  		}
   445  	case 8:
   446  		if os == "Windows" {
   447  			return "J:"
   448  		} else {
   449  			return "/dev/xvdi"
   450  		}
   451  	case 9:
   452  		if os == "Windows" {
   453  			return "K:"
   454  		} else {
   455  			return "/dev/xvdj"
   456  		}
   457  	case 10:
   458  		if os == "Windows" {
   459  			return "L:"
   460  		} else {
   461  			return "/dev/xvdk"
   462  		}
   463  	case 11:
   464  		if os == "Windows" {
   465  			return "M:"
   466  		} else {
   467  			return "/dev/xvdl"
   468  		}
   469  	case 12:
   470  		if os == "Windows" {
   471  			return "N:"
   472  		} else {
   473  			return "/dev/xvdm"
   474  		}
   475  	case 13:
   476  		if os == "Windows" {
   477  			return "O:"
   478  		} else {
   479  			return "/dev/xvdn"
   480  		}
   481  	case 14:
   482  		if os == "Windows" {
   483  			return "P:"
   484  		} else {
   485  			return "/dev/xvdo"
   486  		}
   487  	case 15:
   488  		if os == "Windows" {
   489  			return "Q:"
   490  		} else {
   491  			return "/dev/xvdp"
   492  		}
   493  	default:
   494  		return "unknown"
   495  	}
   496  }