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