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