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