github.com/jrasell/terraform@v0.6.17-0.20160523115548-2652f5232949/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() 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") 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 setValueOrID(d, "virtual_machine", v.Vmname, 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") 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 // First check if the disk isn't already attached 299 if attached, err := isAttached(d, meta); err != nil || attached { 300 return err 301 } 302 303 // Retrieve the virtual_machine ID 304 virtualmachineid, e := retrieveID( 305 cs, 306 "virtual_machine", 307 d.Get("virtual_machine").(string), 308 cloudstack.WithProject(d.Get("project").(string)), 309 ) 310 if e != nil { 311 return e.Error() 312 } 313 314 // Create a new parameter struct 315 p := cs.Volume.NewAttachVolumeParams(d.Id(), virtualmachineid) 316 317 if device, ok := d.GetOk("device"); ok { 318 // Retrieve the device ID 319 deviceid := retrieveDeviceID(device.(string)) 320 if deviceid == -1 { 321 return fmt.Errorf("Device %s is not a valid device", device.(string)) 322 } 323 324 // Set the device ID 325 p.SetDeviceid(deviceid) 326 } 327 328 // Attach the new volume 329 r, err := Retry(4, retryableAttachVolumeFunc(cs, p)) 330 if err != nil { 331 return err 332 } 333 334 d.SetId(r.(*cloudstack.AttachVolumeResponse).Id) 335 336 return nil 337 } 338 339 func resourceCloudStackDiskDetach(d *schema.ResourceData, meta interface{}) error { 340 cs := meta.(*cloudstack.CloudStackClient) 341 342 // Check if the volume is actually attached, before detaching 343 if attached, err := isAttached(d, meta); err != nil || !attached { 344 return err 345 } 346 347 // Create a new parameter struct 348 p := cs.Volume.NewDetachVolumeParams() 349 350 // Set the volume ID 351 p.SetId(d.Id()) 352 353 // Detach the currently attached volume 354 if _, err := cs.Volume.DetachVolume(p); err != nil { 355 // Retrieve the virtual_machine ID 356 virtualmachineid, e := retrieveID( 357 cs, 358 "virtual_machine", 359 d.Get("virtual_machine").(string), 360 cloudstack.WithProject(d.Get("project").(string)), 361 ) 362 if e != nil { 363 return e.Error() 364 } 365 366 // Create a new parameter struct 367 pd := cs.VirtualMachine.NewStopVirtualMachineParams(virtualmachineid) 368 369 // Stop the virtual machine in order to be able to detach the disk 370 if _, err := cs.VirtualMachine.StopVirtualMachine(pd); err != nil { 371 return err 372 } 373 374 // Try again to detach the currently attached volume 375 if _, err := cs.Volume.DetachVolume(p); err != nil { 376 return err 377 } 378 379 // Create a new parameter struct 380 pu := cs.VirtualMachine.NewStartVirtualMachineParams(virtualmachineid) 381 382 // Start the virtual machine again 383 if _, err := cs.VirtualMachine.StartVirtualMachine(pu); err != nil { 384 return err 385 } 386 } 387 388 return nil 389 } 390 391 func isAttached(d *schema.ResourceData, meta interface{}) (bool, error) { 392 cs := meta.(*cloudstack.CloudStackClient) 393 394 // Get the volume details 395 v, _, err := cs.Volume.GetVolumeByID( 396 d.Id(), 397 cloudstack.WithProject(d.Get("project").(string)), 398 ) 399 if err != nil { 400 return false, err 401 } 402 403 return v.Attached != "", nil 404 } 405 406 func retryableAttachVolumeFunc( 407 cs *cloudstack.CloudStackClient, 408 p *cloudstack.AttachVolumeParams) func() (interface{}, error) { 409 return func() (interface{}, error) { 410 r, err := cs.Volume.AttachVolume(p) 411 if err != nil { 412 return nil, err 413 } 414 return r, nil 415 } 416 } 417 418 func retrieveDeviceID(device string) int64 { 419 switch device { 420 case "/dev/xvdb", "D:": 421 return 1 422 case "/dev/xvdc", "E:": 423 return 2 424 case "/dev/xvde", "F:": 425 return 4 426 case "/dev/xvdf", "G:": 427 return 5 428 case "/dev/xvdg", "H:": 429 return 6 430 case "/dev/xvdh", "I:": 431 return 7 432 case "/dev/xvdi", "J:": 433 return 8 434 case "/dev/xvdj", "K:": 435 return 9 436 case "/dev/xvdk", "L:": 437 return 10 438 case "/dev/xvdl", "M:": 439 return 11 440 case "/dev/xvdm", "N:": 441 return 12 442 case "/dev/xvdn", "O:": 443 return 13 444 case "/dev/xvdo", "P:": 445 return 14 446 case "/dev/xvdp", "Q:": 447 return 15 448 default: 449 return -1 450 } 451 } 452 453 func retrieveDeviceName(device int64, os string) string { 454 switch device { 455 case 1: 456 if os == "Windows" { 457 return "D:" 458 } 459 return "/dev/xvdb" 460 case 2: 461 if os == "Windows" { 462 return "E:" 463 } 464 return "/dev/xvdc" 465 case 4: 466 if os == "Windows" { 467 return "F:" 468 } 469 return "/dev/xvde" 470 case 5: 471 if os == "Windows" { 472 return "G:" 473 } 474 return "/dev/xvdf" 475 case 6: 476 if os == "Windows" { 477 return "H:" 478 } 479 return "/dev/xvdg" 480 case 7: 481 if os == "Windows" { 482 return "I:" 483 } 484 return "/dev/xvdh" 485 case 8: 486 if os == "Windows" { 487 return "J:" 488 } 489 return "/dev/xvdi" 490 case 9: 491 if os == "Windows" { 492 return "K:" 493 } 494 return "/dev/xvdj" 495 case 10: 496 if os == "Windows" { 497 return "L:" 498 } 499 return "/dev/xvdk" 500 case 11: 501 if os == "Windows" { 502 return "M:" 503 } 504 return "/dev/xvdl" 505 case 12: 506 if os == "Windows" { 507 return "N:" 508 } 509 return "/dev/xvdm" 510 case 13: 511 if os == "Windows" { 512 return "O:" 513 } 514 return "/dev/xvdn" 515 case 14: 516 if os == "Windows" { 517 return "P:" 518 } 519 return "/dev/xvdo" 520 case 15: 521 if os == "Windows" { 522 return "Q:" 523 } 524 return "/dev/xvdp" 525 default: 526 return "unknown" 527 } 528 }