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