github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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_id": &schema.Schema{ 32 Type: schema.TypeInt, 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 Computed: true, 63 ForceNew: true, 64 }, 65 66 "zone": &schema.Schema{ 67 Type: schema.TypeString, 68 Required: true, 69 ForceNew: true, 70 }, 71 }, 72 } 73 } 74 75 func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) error { 76 cs := meta.(*cloudstack.CloudStackClient) 77 d.Partial(true) 78 79 name := d.Get("name").(string) 80 81 // Create a new parameter struct 82 p := cs.Volume.NewCreateVolumeParams() 83 p.SetName(name) 84 85 // Retrieve the disk_offering ID 86 diskofferingid, e := retrieveID(cs, "disk_offering", d.Get("disk_offering").(string)) 87 if e != nil { 88 return e.Error() 89 } 90 // Set the disk_offering ID 91 p.SetDiskofferingid(diskofferingid) 92 93 if d.Get("size").(int) != 0 { 94 // Set the volume size 95 p.SetSize(int64(d.Get("size").(int))) 96 } 97 98 // If there is a project supplied, we retrieve and set the project id 99 if err := setProjectid(p, cs, d); err != nil { 100 return err 101 } 102 103 // Retrieve the zone ID 104 zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string)) 105 if e != nil { 106 return e.Error() 107 } 108 // Set the zone ID 109 p.SetZoneid(zoneid) 110 111 // Create the new volume 112 r, err := cs.Volume.CreateVolume(p) 113 if err != nil { 114 return fmt.Errorf("Error creating the new disk %s: %s", name, err) 115 } 116 117 // Set the volume ID and partials 118 d.SetId(r.Id) 119 d.SetPartial("name") 120 d.SetPartial("device_id") 121 d.SetPartial("disk_offering") 122 d.SetPartial("size") 123 d.SetPartial("virtual_machine_id") 124 d.SetPartial("project") 125 d.SetPartial("zone") 126 127 if d.Get("attach").(bool) { 128 err := resourceCloudStackDiskAttach(d, meta) 129 if err != nil { 130 return fmt.Errorf("Error attaching the new disk %s to virtual machine: %s", name, err) 131 } 132 133 // Set the additional partial 134 d.SetPartial("attach") 135 } 136 137 d.Partial(false) 138 return resourceCloudStackDiskRead(d, meta) 139 } 140 141 func resourceCloudStackDiskRead(d *schema.ResourceData, meta interface{}) error { 142 cs := meta.(*cloudstack.CloudStackClient) 143 144 // Get the volume details 145 v, count, err := cs.Volume.GetVolumeByID( 146 d.Id(), 147 cloudstack.WithProject(d.Get("project").(string)), 148 ) 149 if err != nil { 150 if count == 0 { 151 d.SetId("") 152 return nil 153 } 154 155 return err 156 } 157 158 d.Set("name", v.Name) 159 d.Set("attach", v.Attached != "") // If attached this contains a timestamp when attached 160 d.Set("size", int(v.Size/(1024*1024*1024))) // Needed to get GB's again 161 162 setValueOrID(d, "disk_offering", v.Diskofferingname, v.Diskofferingid) 163 setValueOrID(d, "project", v.Project, v.Projectid) 164 setValueOrID(d, "zone", v.Zonename, v.Zoneid) 165 166 if v.Attached != "" { 167 d.Set("device_id", int(v.Deviceid)) 168 d.Set("virtual_machine_id", v.Virtualmachineid) 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 ID 190 diskofferingid, e := retrieveID(cs, "disk_offering", d.Get("disk_offering").(string)) 191 if e != nil { 192 return e.Error() 193 } 194 195 // Set the disk_offering ID 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 ID and set partials 213 d.SetId(r.Id) 214 d.SetPartial("disk_offering") 215 d.SetPartial("size") 216 } 217 218 // If the device ID changed, just detach here so we can re-attach the 219 // volume at the end of this function 220 if d.HasChange("device_id") || 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_id") 237 d.SetPartial("virtual_machine_id") 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 ID 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 if virtualmachineid, ok := d.GetOk("virtual_machine_id"); ok { 279 // First check if the disk isn't already attached 280 if attached, err := isAttached(d, meta); err != nil || attached { 281 return err 282 } 283 284 // Create a new parameter struct 285 p := cs.Volume.NewAttachVolumeParams(d.Id(), virtualmachineid.(string)) 286 287 if deviceid, ok := d.GetOk("device_id"); ok { 288 p.SetDeviceid(int64(deviceid.(int))) 289 } 290 291 // Attach the new volume 292 r, err := Retry(10, retryableAttachVolumeFunc(cs, p)) 293 if err != nil { 294 return fmt.Errorf("Error attaching volume to VM: %s", err) 295 } 296 297 d.SetId(r.(*cloudstack.AttachVolumeResponse).Id) 298 } 299 300 return nil 301 } 302 303 func resourceCloudStackDiskDetach(d *schema.ResourceData, meta interface{}) error { 304 cs := meta.(*cloudstack.CloudStackClient) 305 306 // Check if the volume is actually attached, before detaching 307 if attached, err := isAttached(d, meta); err != nil || !attached { 308 return err 309 } 310 311 // Create a new parameter struct 312 p := cs.Volume.NewDetachVolumeParams() 313 314 // Set the volume ID 315 p.SetId(d.Id()) 316 317 // Detach the currently attached volume 318 _, err := cs.Volume.DetachVolume(p) 319 if err != nil { 320 if virtualmachineid, ok := d.GetOk("virtual_machine_id"); ok { 321 // Create a new parameter struct 322 pd := cs.VirtualMachine.NewStopVirtualMachineParams(virtualmachineid.(string)) 323 324 // Stop the virtual machine in order to be able to detach the disk 325 if _, err := cs.VirtualMachine.StopVirtualMachine(pd); err != nil { 326 return err 327 } 328 329 // Try again to detach the currently attached volume 330 if _, err := cs.Volume.DetachVolume(p); err != nil { 331 return err 332 } 333 334 // Create a new parameter struct 335 pu := cs.VirtualMachine.NewStartVirtualMachineParams(virtualmachineid.(string)) 336 337 // Start the virtual machine again 338 if _, err := cs.VirtualMachine.StartVirtualMachine(pu); err != nil { 339 return err 340 } 341 } 342 } 343 344 return err 345 } 346 347 func isAttached(d *schema.ResourceData, meta interface{}) (bool, error) { 348 cs := meta.(*cloudstack.CloudStackClient) 349 350 // Get the volume details 351 v, _, err := cs.Volume.GetVolumeByID( 352 d.Id(), 353 cloudstack.WithProject(d.Get("project").(string)), 354 ) 355 if err != nil { 356 return false, err 357 } 358 359 return v.Attached != "", nil 360 } 361 362 func retryableAttachVolumeFunc( 363 cs *cloudstack.CloudStackClient, 364 p *cloudstack.AttachVolumeParams) func() (interface{}, error) { 365 return func() (interface{}, error) { 366 r, err := cs.Volume.AttachVolume(p) 367 if err != nil { 368 return nil, err 369 } 370 return r, nil 371 } 372 }