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