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