github.com/armen/terraform@v0.5.2-0.20150529052519-caa8117a08f1/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 "zone": &schema.Schema{ 60 Type: schema.TypeString, 61 Required: true, 62 ForceNew: true, 63 }, 64 }, 65 } 66 } 67 68 func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) error { 69 cs := meta.(*cloudstack.CloudStackClient) 70 d.Partial(true) 71 72 name := d.Get("name").(string) 73 74 // Create a new parameter struct 75 p := cs.Volume.NewCreateVolumeParams(name) 76 77 // Retrieve the disk_offering UUID 78 diskofferingid, e := retrieveUUID(cs, "disk_offering", d.Get("disk_offering").(string)) 79 if e != nil { 80 return e.Error() 81 } 82 // Set the disk_offering UUID 83 p.SetDiskofferingid(diskofferingid) 84 85 if d.Get("size").(int) != 0 { 86 // Set the volume size 87 p.SetSize(int64(d.Get("size").(int))) 88 } 89 90 // Retrieve the zone UUID 91 zoneid, e := retrieveUUID(cs, "zone", d.Get("zone").(string)) 92 if e != nil { 93 return e.Error() 94 } 95 // Set the zone ID 96 p.SetZoneid(zoneid) 97 98 // Create the new volume 99 r, err := cs.Volume.CreateVolume(p) 100 if err != nil { 101 return fmt.Errorf("Error creating the new disk %s: %s", name, err) 102 } 103 104 // Set the volume UUID and partials 105 d.SetId(r.Id) 106 d.SetPartial("name") 107 d.SetPartial("device") 108 d.SetPartial("disk_offering") 109 d.SetPartial("size") 110 d.SetPartial("virtual_machine") 111 d.SetPartial("zone") 112 113 if d.Get("attach").(bool) { 114 err := resourceCloudStackDiskAttach(d, meta) 115 if err != nil { 116 return fmt.Errorf("Error attaching the new disk %s to virtual machine: %s", name, err) 117 } 118 119 // Set the additional partial 120 d.SetPartial("attach") 121 } 122 123 d.Partial(false) 124 return resourceCloudStackDiskRead(d, meta) 125 } 126 127 func resourceCloudStackDiskRead(d *schema.ResourceData, meta interface{}) error { 128 cs := meta.(*cloudstack.CloudStackClient) 129 130 // Get the volume details 131 v, count, err := cs.Volume.GetVolumeByID(d.Id()) 132 if err != nil { 133 if count == 0 { 134 d.SetId("") 135 return nil 136 } 137 138 return err 139 } 140 141 d.Set("name", v.Name) 142 d.Set("attach", v.Attached != "") // If attached this will contain a timestamp when attached 143 d.Set("size", int(v.Size/(1024*1024*1024))) // Needed to get GB's again 144 145 setValueOrUUID(d, "disk_offering", v.Diskofferingname, v.Diskofferingid) 146 setValueOrUUID(d, "zone", v.Zonename, v.Zoneid) 147 148 if v.Attached != "" { 149 // Get the virtual machine details 150 vm, _, err := cs.VirtualMachine.GetVirtualMachineByID(v.Virtualmachineid) 151 if err != nil { 152 return err 153 } 154 155 // Get the guest OS type details 156 os, _, err := cs.GuestOS.GetOsTypeByID(vm.Guestosid) 157 if err != nil { 158 return err 159 } 160 161 // Get the guest OS category details 162 c, _, err := cs.GuestOS.GetOsCategoryByID(os.Oscategoryid) 163 if err != nil { 164 return err 165 } 166 167 d.Set("device", retrieveDeviceName(v.Deviceid, c.Name)) 168 d.Set("virtual_machine", v.Vmname) 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 UUID 190 diskofferingid, e := retrieveUUID(cs, "disk_offering", d.Get("disk_offering").(string)) 191 if e != nil { 192 return e.Error() 193 } 194 195 // Set the disk_offering UUID 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 UUID and set partials 213 d.SetId(r.Id) 214 d.SetPartial("disk_offering") 215 d.SetPartial("size") 216 } 217 218 // If the device changed, just detach here so we can re-attach the 219 // volume at the end of this function 220 if d.HasChange("device") || 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") 237 d.SetPartial("virtual_machine") 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 UUID 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 // First check if the disk isn't already attached 279 if attached, err := isAttached(cs, d.Id()); err != nil || attached { 280 return err 281 } 282 283 // Retrieve the virtual_machine UUID 284 virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string)) 285 if e != nil { 286 return e.Error() 287 } 288 289 // Create a new parameter struct 290 p := cs.Volume.NewAttachVolumeParams(d.Id(), virtualmachineid) 291 292 if device, ok := d.GetOk("device"); ok { 293 // Retrieve the device ID 294 deviceid := retrieveDeviceID(device.(string)) 295 if deviceid == -1 { 296 return fmt.Errorf("Device %s is not a valid device", device.(string)) 297 } 298 299 // Set the device ID 300 p.SetDeviceid(deviceid) 301 } 302 303 // Attach the new volume 304 r, err := cs.Volume.AttachVolume(p) 305 if err != nil { 306 return err 307 } 308 309 d.SetId(r.Id) 310 311 return nil 312 } 313 314 func resourceCloudStackDiskDetach(d *schema.ResourceData, meta interface{}) error { 315 cs := meta.(*cloudstack.CloudStackClient) 316 317 // Check if the volume is actually attached, before detaching 318 if attached, err := isAttached(cs, d.Id()); err != nil || !attached { 319 return err 320 } 321 322 // Create a new parameter struct 323 p := cs.Volume.NewDetachVolumeParams() 324 325 // Set the volume UUID 326 p.SetId(d.Id()) 327 328 // Detach the currently attached volume 329 if _, err := cs.Volume.DetachVolume(p); err != nil { 330 // Retrieve the virtual_machine UUID 331 virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string)) 332 if e != nil { 333 return e.Error() 334 } 335 336 // Create a new parameter struct 337 pd := cs.VirtualMachine.NewStopVirtualMachineParams(virtualmachineid) 338 339 // Stop the virtual machine in order to be able to detach the disk 340 if _, err := cs.VirtualMachine.StopVirtualMachine(pd); err != nil { 341 return err 342 } 343 344 // Try again to detach the currently attached volume 345 if _, err := cs.Volume.DetachVolume(p); err != nil { 346 return err 347 } 348 349 // Create a new parameter struct 350 pu := cs.VirtualMachine.NewStartVirtualMachineParams(virtualmachineid) 351 352 // Start the virtual machine again 353 if _, err := cs.VirtualMachine.StartVirtualMachine(pu); err != nil { 354 return err 355 } 356 } 357 358 return nil 359 } 360 361 func isAttached(cs *cloudstack.CloudStackClient, id string) (bool, error) { 362 // Get the volume details 363 v, _, err := cs.Volume.GetVolumeByID(id) 364 if err != nil { 365 return false, err 366 } 367 368 return v.Attached != "", nil 369 } 370 371 func retrieveDeviceID(device string) int64 { 372 switch device { 373 case "/dev/xvdb", "D:": 374 return 1 375 case "/dev/xvdc", "E:": 376 return 2 377 case "/dev/xvde", "F:": 378 return 4 379 case "/dev/xvdf", "G:": 380 return 5 381 case "/dev/xvdg", "H:": 382 return 6 383 case "/dev/xvdh", "I:": 384 return 7 385 case "/dev/xvdi", "J:": 386 return 8 387 case "/dev/xvdj", "K:": 388 return 9 389 case "/dev/xvdk", "L:": 390 return 10 391 case "/dev/xvdl", "M:": 392 return 11 393 case "/dev/xvdm", "N:": 394 return 12 395 case "/dev/xvdn", "O:": 396 return 13 397 case "/dev/xvdo", "P:": 398 return 14 399 case "/dev/xvdp", "Q:": 400 return 15 401 default: 402 return -1 403 } 404 } 405 406 func retrieveDeviceName(device int64, os string) string { 407 switch device { 408 case 1: 409 if os == "Windows" { 410 return "D:" 411 } else { 412 return "/dev/xvdb" 413 } 414 case 2: 415 if os == "Windows" { 416 return "E:" 417 } else { 418 return "/dev/xvdc" 419 } 420 case 4: 421 if os == "Windows" { 422 return "F:" 423 } else { 424 return "/dev/xvde" 425 } 426 case 5: 427 if os == "Windows" { 428 return "G:" 429 } else { 430 return "/dev/xvdf" 431 } 432 case 6: 433 if os == "Windows" { 434 return "H:" 435 } else { 436 return "/dev/xvdg" 437 } 438 case 7: 439 if os == "Windows" { 440 return "I:" 441 } else { 442 return "/dev/xvdh" 443 } 444 case 8: 445 if os == "Windows" { 446 return "J:" 447 } else { 448 return "/dev/xvdi" 449 } 450 case 9: 451 if os == "Windows" { 452 return "K:" 453 } else { 454 return "/dev/xvdj" 455 } 456 case 10: 457 if os == "Windows" { 458 return "L:" 459 } else { 460 return "/dev/xvdk" 461 } 462 case 11: 463 if os == "Windows" { 464 return "M:" 465 } else { 466 return "/dev/xvdl" 467 } 468 case 12: 469 if os == "Windows" { 470 return "N:" 471 } else { 472 return "/dev/xvdm" 473 } 474 case 13: 475 if os == "Windows" { 476 return "O:" 477 } else { 478 return "/dev/xvdn" 479 } 480 case 14: 481 if os == "Windows" { 482 return "P:" 483 } else { 484 return "/dev/xvdo" 485 } 486 case 15: 487 if os == "Windows" { 488 return "Q:" 489 } else { 490 return "/dev/xvdp" 491 } 492 default: 493 return "unknown" 494 } 495 }