github.com/jrasell/terraform@v0.6.17-0.20160523115548-2652f5232949/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()
    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")
   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  		setValueOrID(d, "virtual_machine", v.Vmname, 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")
   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  	// First check if the disk isn't already attached
   299  	if attached, err := isAttached(d, meta); err != nil || attached {
   300  		return err
   301  	}
   302  
   303  	// Retrieve the virtual_machine ID
   304  	virtualmachineid, e := retrieveID(
   305  		cs,
   306  		"virtual_machine",
   307  		d.Get("virtual_machine").(string),
   308  		cloudstack.WithProject(d.Get("project").(string)),
   309  	)
   310  	if e != nil {
   311  		return e.Error()
   312  	}
   313  
   314  	// Create a new parameter struct
   315  	p := cs.Volume.NewAttachVolumeParams(d.Id(), virtualmachineid)
   316  
   317  	if device, ok := d.GetOk("device"); ok {
   318  		// Retrieve the device ID
   319  		deviceid := retrieveDeviceID(device.(string))
   320  		if deviceid == -1 {
   321  			return fmt.Errorf("Device %s is not a valid device", device.(string))
   322  		}
   323  
   324  		// Set the device ID
   325  		p.SetDeviceid(deviceid)
   326  	}
   327  
   328  	// Attach the new volume
   329  	r, err := Retry(4, retryableAttachVolumeFunc(cs, p))
   330  	if err != nil {
   331  		return err
   332  	}
   333  
   334  	d.SetId(r.(*cloudstack.AttachVolumeResponse).Id)
   335  
   336  	return nil
   337  }
   338  
   339  func resourceCloudStackDiskDetach(d *schema.ResourceData, meta interface{}) error {
   340  	cs := meta.(*cloudstack.CloudStackClient)
   341  
   342  	// Check if the volume is actually attached, before detaching
   343  	if attached, err := isAttached(d, meta); err != nil || !attached {
   344  		return err
   345  	}
   346  
   347  	// Create a new parameter struct
   348  	p := cs.Volume.NewDetachVolumeParams()
   349  
   350  	// Set the volume ID
   351  	p.SetId(d.Id())
   352  
   353  	// Detach the currently attached volume
   354  	if _, err := cs.Volume.DetachVolume(p); err != nil {
   355  		// Retrieve the virtual_machine ID
   356  		virtualmachineid, e := retrieveID(
   357  			cs,
   358  			"virtual_machine",
   359  			d.Get("virtual_machine").(string),
   360  			cloudstack.WithProject(d.Get("project").(string)),
   361  		)
   362  		if e != nil {
   363  			return e.Error()
   364  		}
   365  
   366  		// Create a new parameter struct
   367  		pd := cs.VirtualMachine.NewStopVirtualMachineParams(virtualmachineid)
   368  
   369  		// Stop the virtual machine in order to be able to detach the disk
   370  		if _, err := cs.VirtualMachine.StopVirtualMachine(pd); err != nil {
   371  			return err
   372  		}
   373  
   374  		// Try again to detach the currently attached volume
   375  		if _, err := cs.Volume.DetachVolume(p); err != nil {
   376  			return err
   377  		}
   378  
   379  		// Create a new parameter struct
   380  		pu := cs.VirtualMachine.NewStartVirtualMachineParams(virtualmachineid)
   381  
   382  		// Start the virtual machine again
   383  		if _, err := cs.VirtualMachine.StartVirtualMachine(pu); err != nil {
   384  			return err
   385  		}
   386  	}
   387  
   388  	return nil
   389  }
   390  
   391  func isAttached(d *schema.ResourceData, meta interface{}) (bool, error) {
   392  	cs := meta.(*cloudstack.CloudStackClient)
   393  
   394  	// Get the volume details
   395  	v, _, err := cs.Volume.GetVolumeByID(
   396  		d.Id(),
   397  		cloudstack.WithProject(d.Get("project").(string)),
   398  	)
   399  	if err != nil {
   400  		return false, err
   401  	}
   402  
   403  	return v.Attached != "", nil
   404  }
   405  
   406  func retryableAttachVolumeFunc(
   407  	cs *cloudstack.CloudStackClient,
   408  	p *cloudstack.AttachVolumeParams) func() (interface{}, error) {
   409  	return func() (interface{}, error) {
   410  		r, err := cs.Volume.AttachVolume(p)
   411  		if err != nil {
   412  			return nil, err
   413  		}
   414  		return r, nil
   415  	}
   416  }
   417  
   418  func retrieveDeviceID(device string) int64 {
   419  	switch device {
   420  	case "/dev/xvdb", "D:":
   421  		return 1
   422  	case "/dev/xvdc", "E:":
   423  		return 2
   424  	case "/dev/xvde", "F:":
   425  		return 4
   426  	case "/dev/xvdf", "G:":
   427  		return 5
   428  	case "/dev/xvdg", "H:":
   429  		return 6
   430  	case "/dev/xvdh", "I:":
   431  		return 7
   432  	case "/dev/xvdi", "J:":
   433  		return 8
   434  	case "/dev/xvdj", "K:":
   435  		return 9
   436  	case "/dev/xvdk", "L:":
   437  		return 10
   438  	case "/dev/xvdl", "M:":
   439  		return 11
   440  	case "/dev/xvdm", "N:":
   441  		return 12
   442  	case "/dev/xvdn", "O:":
   443  		return 13
   444  	case "/dev/xvdo", "P:":
   445  		return 14
   446  	case "/dev/xvdp", "Q:":
   447  		return 15
   448  	default:
   449  		return -1
   450  	}
   451  }
   452  
   453  func retrieveDeviceName(device int64, os string) string {
   454  	switch device {
   455  	case 1:
   456  		if os == "Windows" {
   457  			return "D:"
   458  		}
   459  		return "/dev/xvdb"
   460  	case 2:
   461  		if os == "Windows" {
   462  			return "E:"
   463  		}
   464  		return "/dev/xvdc"
   465  	case 4:
   466  		if os == "Windows" {
   467  			return "F:"
   468  		}
   469  		return "/dev/xvde"
   470  	case 5:
   471  		if os == "Windows" {
   472  			return "G:"
   473  		}
   474  		return "/dev/xvdf"
   475  	case 6:
   476  		if os == "Windows" {
   477  			return "H:"
   478  		}
   479  		return "/dev/xvdg"
   480  	case 7:
   481  		if os == "Windows" {
   482  			return "I:"
   483  		}
   484  		return "/dev/xvdh"
   485  	case 8:
   486  		if os == "Windows" {
   487  			return "J:"
   488  		}
   489  		return "/dev/xvdi"
   490  	case 9:
   491  		if os == "Windows" {
   492  			return "K:"
   493  		}
   494  		return "/dev/xvdj"
   495  	case 10:
   496  		if os == "Windows" {
   497  			return "L:"
   498  		}
   499  		return "/dev/xvdk"
   500  	case 11:
   501  		if os == "Windows" {
   502  			return "M:"
   503  		}
   504  		return "/dev/xvdl"
   505  	case 12:
   506  		if os == "Windows" {
   507  			return "N:"
   508  		}
   509  		return "/dev/xvdm"
   510  	case 13:
   511  		if os == "Windows" {
   512  			return "O:"
   513  		}
   514  		return "/dev/xvdn"
   515  	case 14:
   516  		if os == "Windows" {
   517  			return "P:"
   518  		}
   519  		return "/dev/xvdo"
   520  	case 15:
   521  		if os == "Windows" {
   522  			return "Q:"
   523  		}
   524  		return "/dev/xvdp"
   525  	default:
   526  		return "unknown"
   527  	}
   528  }