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