github.com/armen/terraform@v0.5.2-0.20150529052519-caa8117a08f1/builtin/providers/openstack/resource_openstack_compute_instance_v2.go (about) 1 package openstack 2 3 import ( 4 "bytes" 5 "crypto/sha1" 6 "encoding/hex" 7 "fmt" 8 "log" 9 "time" 10 11 "github.com/hashicorp/terraform/helper/hashcode" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 "github.com/rackspace/gophercloud" 15 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" 16 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" 17 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" 18 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" 19 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/tenantnetworks" 20 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" 21 "github.com/rackspace/gophercloud/openstack/compute/v2/flavors" 22 "github.com/rackspace/gophercloud/openstack/compute/v2/images" 23 "github.com/rackspace/gophercloud/openstack/compute/v2/servers" 24 "github.com/rackspace/gophercloud/pagination" 25 ) 26 27 func resourceComputeInstanceV2() *schema.Resource { 28 return &schema.Resource{ 29 Create: resourceComputeInstanceV2Create, 30 Read: resourceComputeInstanceV2Read, 31 Update: resourceComputeInstanceV2Update, 32 Delete: resourceComputeInstanceV2Delete, 33 34 Schema: map[string]*schema.Schema{ 35 "region": &schema.Schema{ 36 Type: schema.TypeString, 37 Required: true, 38 ForceNew: true, 39 DefaultFunc: envDefaultFuncAllowMissing("OS_REGION_NAME"), 40 }, 41 "name": &schema.Schema{ 42 Type: schema.TypeString, 43 Required: true, 44 ForceNew: false, 45 }, 46 "image_id": &schema.Schema{ 47 Type: schema.TypeString, 48 Optional: true, 49 ForceNew: true, 50 Computed: true, 51 DefaultFunc: envDefaultFunc("OS_IMAGE_ID"), 52 }, 53 "image_name": &schema.Schema{ 54 Type: schema.TypeString, 55 Optional: true, 56 ForceNew: true, 57 Computed: true, 58 DefaultFunc: envDefaultFunc("OS_IMAGE_NAME"), 59 }, 60 "flavor_id": &schema.Schema{ 61 Type: schema.TypeString, 62 Optional: true, 63 ForceNew: false, 64 Computed: true, 65 DefaultFunc: envDefaultFunc("OS_FLAVOR_ID"), 66 }, 67 "flavor_name": &schema.Schema{ 68 Type: schema.TypeString, 69 Optional: true, 70 ForceNew: false, 71 Computed: true, 72 DefaultFunc: envDefaultFunc("OS_FLAVOR_NAME"), 73 }, 74 "floating_ip": &schema.Schema{ 75 Type: schema.TypeString, 76 Optional: true, 77 ForceNew: false, 78 }, 79 "user_data": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 ForceNew: true, 83 // just stash the hash for state & diff comparisons 84 StateFunc: func(v interface{}) string { 85 switch v.(type) { 86 case string: 87 hash := sha1.Sum([]byte(v.(string))) 88 return hex.EncodeToString(hash[:]) 89 default: 90 return "" 91 } 92 }, 93 }, 94 "security_groups": &schema.Schema{ 95 Type: schema.TypeList, 96 Optional: true, 97 ForceNew: false, 98 Elem: &schema.Schema{Type: schema.TypeString}, 99 }, 100 "availability_zone": &schema.Schema{ 101 Type: schema.TypeString, 102 Optional: true, 103 ForceNew: true, 104 }, 105 "network": &schema.Schema{ 106 Type: schema.TypeList, 107 Optional: true, 108 ForceNew: true, 109 Computed: true, 110 Elem: &schema.Resource{ 111 Schema: map[string]*schema.Schema{ 112 "uuid": &schema.Schema{ 113 Type: schema.TypeString, 114 Optional: true, 115 Computed: true, 116 }, 117 "name": &schema.Schema{ 118 Type: schema.TypeString, 119 Optional: true, 120 Computed: true, 121 }, 122 "port": &schema.Schema{ 123 Type: schema.TypeString, 124 Optional: true, 125 Computed: true, 126 }, 127 "fixed_ip_v4": &schema.Schema{ 128 Type: schema.TypeString, 129 Optional: true, 130 Computed: true, 131 }, 132 "fixed_ip_v6": &schema.Schema{ 133 Type: schema.TypeString, 134 Optional: true, 135 Computed: true, 136 }, 137 "mac": &schema.Schema{ 138 Type: schema.TypeString, 139 Computed: true, 140 }, 141 }, 142 }, 143 }, 144 "metadata": &schema.Schema{ 145 Type: schema.TypeMap, 146 Optional: true, 147 ForceNew: false, 148 }, 149 "config_drive": &schema.Schema{ 150 Type: schema.TypeBool, 151 Optional: true, 152 ForceNew: true, 153 }, 154 "admin_pass": &schema.Schema{ 155 Type: schema.TypeString, 156 Optional: true, 157 ForceNew: false, 158 }, 159 "access_ip_v4": &schema.Schema{ 160 Type: schema.TypeString, 161 Computed: true, 162 Optional: true, 163 ForceNew: false, 164 }, 165 "access_ip_v6": &schema.Schema{ 166 Type: schema.TypeString, 167 Computed: true, 168 Optional: true, 169 ForceNew: false, 170 }, 171 "key_pair": &schema.Schema{ 172 Type: schema.TypeString, 173 Optional: true, 174 ForceNew: true, 175 }, 176 "block_device": &schema.Schema{ 177 Type: schema.TypeList, 178 Optional: true, 179 ForceNew: true, 180 Elem: &schema.Resource{ 181 Schema: map[string]*schema.Schema{ 182 "uuid": &schema.Schema{ 183 Type: schema.TypeString, 184 Required: true, 185 }, 186 "source_type": &schema.Schema{ 187 Type: schema.TypeString, 188 Required: true, 189 }, 190 "volume_size": &schema.Schema{ 191 Type: schema.TypeInt, 192 Optional: true, 193 }, 194 "destination_type": &schema.Schema{ 195 Type: schema.TypeString, 196 Optional: true, 197 }, 198 "boot_index": &schema.Schema{ 199 Type: schema.TypeInt, 200 Optional: true, 201 }, 202 }, 203 }, 204 }, 205 "volume": &schema.Schema{ 206 Type: schema.TypeSet, 207 Optional: true, 208 Elem: &schema.Resource{ 209 Schema: map[string]*schema.Schema{ 210 "id": &schema.Schema{ 211 Type: schema.TypeString, 212 Computed: true, 213 }, 214 "volume_id": &schema.Schema{ 215 Type: schema.TypeString, 216 Required: true, 217 }, 218 "device": &schema.Schema{ 219 Type: schema.TypeString, 220 Optional: true, 221 Computed: true, 222 }, 223 }, 224 }, 225 Set: resourceComputeVolumeAttachmentHash, 226 }, 227 }, 228 } 229 } 230 231 func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) error { 232 config := meta.(*Config) 233 computeClient, err := config.computeV2Client(d.Get("region").(string)) 234 if err != nil { 235 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 236 } 237 238 var createOpts servers.CreateOptsBuilder 239 240 imageId, err := getImageID(computeClient, d) 241 if err != nil { 242 return err 243 } 244 245 flavorId, err := getFlavorID(computeClient, d) 246 if err != nil { 247 return err 248 } 249 250 networkDetails, err := resourceInstanceNetworks(computeClient, d) 251 if err != nil { 252 return err 253 } 254 255 networks := make([]servers.Network, len(networkDetails)) 256 for i, net := range networkDetails { 257 networks[i] = servers.Network{ 258 UUID: net["uuid"].(string), 259 Port: net["port"].(string), 260 FixedIP: net["fixed_ip_v4"].(string), 261 } 262 } 263 264 createOpts = &servers.CreateOpts{ 265 Name: d.Get("name").(string), 266 ImageRef: imageId, 267 FlavorRef: flavorId, 268 SecurityGroups: resourceInstanceSecGroupsV2(d), 269 AvailabilityZone: d.Get("availability_zone").(string), 270 Networks: networks, 271 Metadata: resourceInstanceMetadataV2(d), 272 ConfigDrive: d.Get("config_drive").(bool), 273 AdminPass: d.Get("admin_pass").(string), 274 UserData: []byte(d.Get("user_data").(string)), 275 } 276 277 if keyName, ok := d.Get("key_pair").(string); ok && keyName != "" { 278 createOpts = &keypairs.CreateOptsExt{ 279 createOpts, 280 keyName, 281 } 282 } 283 284 if blockDeviceRaw, ok := d.Get("block_device").(map[string]interface{}); ok && blockDeviceRaw != nil { 285 blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw) 286 createOpts = &bootfromvolume.CreateOptsExt{ 287 createOpts, 288 blockDevice, 289 } 290 } 291 292 log.Printf("[DEBUG] Create Options: %#v", createOpts) 293 server, err := servers.Create(computeClient, createOpts).Extract() 294 if err != nil { 295 return fmt.Errorf("Error creating OpenStack server: %s", err) 296 } 297 log.Printf("[INFO] Instance ID: %s", server.ID) 298 299 // Store the ID now 300 d.SetId(server.ID) 301 302 // Wait for the instance to become running so we can get some attributes 303 // that aren't available until later. 304 log.Printf( 305 "[DEBUG] Waiting for instance (%s) to become running", 306 server.ID) 307 308 stateConf := &resource.StateChangeConf{ 309 Pending: []string{"BUILD"}, 310 Target: "ACTIVE", 311 Refresh: ServerV2StateRefreshFunc(computeClient, server.ID), 312 Timeout: 10 * time.Minute, 313 Delay: 10 * time.Second, 314 MinTimeout: 3 * time.Second, 315 } 316 317 _, err = stateConf.WaitForState() 318 if err != nil { 319 return fmt.Errorf( 320 "Error waiting for instance (%s) to become ready: %s", 321 server.ID, err) 322 } 323 floatingIP := d.Get("floating_ip").(string) 324 if floatingIP != "" { 325 if err := floatingip.Associate(computeClient, server.ID, floatingIP).ExtractErr(); err != nil { 326 return fmt.Errorf("Error associating floating IP: %s", err) 327 } 328 } 329 330 // were volume attachments specified? 331 if v := d.Get("volume"); v != nil { 332 vols := v.(*schema.Set).List() 333 if len(vols) > 0 { 334 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 335 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 336 } else { 337 if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil { 338 return err 339 } 340 } 341 } 342 } 343 344 return resourceComputeInstanceV2Read(d, meta) 345 } 346 347 func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) error { 348 config := meta.(*Config) 349 computeClient, err := config.computeV2Client(d.Get("region").(string)) 350 if err != nil { 351 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 352 } 353 354 server, err := servers.Get(computeClient, d.Id()).Extract() 355 if err != nil { 356 return CheckDeleted(d, err, "server") 357 } 358 359 log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server) 360 361 d.Set("name", server.Name) 362 363 // begin reading the network configuration 364 d.Set("access_ip_v4", server.AccessIPv4) 365 d.Set("access_ip_v6", server.AccessIPv6) 366 hostv4 := server.AccessIPv4 367 hostv6 := server.AccessIPv6 368 369 networkDetails, err := resourceInstanceNetworks(computeClient, d) 370 addresses := resourceInstanceAddresses(server.Addresses) 371 if err != nil { 372 return err 373 } 374 375 // if there are no networkDetails, make networks at least a length of 1 376 networkLength := 1 377 if len(networkDetails) > 0 { 378 networkLength = len(networkDetails) 379 } 380 networks := make([]map[string]interface{}, networkLength) 381 382 // Loop through all networks and addresses, 383 // merge relevant address details. 384 if len(networkDetails) == 0 { 385 for netName, n := range addresses { 386 if floatingIP, ok := n["floating_ip"]; ok { 387 hostv4 = floatingIP.(string) 388 } else { 389 if hostv4 == "" && n["fixed_ip_v4"] != nil { 390 hostv4 = n["fixed_ip_v4"].(string) 391 } 392 } 393 394 if hostv6 == "" && n["fixed_ip_v6"] != nil { 395 hostv6 = n["fixed_ip_v6"].(string) 396 } 397 398 networks[0] = map[string]interface{}{ 399 "name": netName, 400 "fixed_ip_v4": n["fixed_ip_v4"], 401 "fixed_ip_v6": n["fixed_ip_v6"], 402 "mac": n["mac"], 403 } 404 } 405 } else { 406 for i, net := range networkDetails { 407 n := addresses[net["name"].(string)] 408 409 if floatingIP, ok := n["floating_ip"]; ok { 410 hostv4 = floatingIP.(string) 411 } else { 412 if hostv4 == "" && n["fixed_ip_v4"] != nil { 413 hostv4 = n["fixed_ip_v4"].(string) 414 } 415 } 416 417 if hostv6 == "" && n["fixed_ip_v6"] != nil { 418 hostv6 = n["fixed_ip_v6"].(string) 419 } 420 421 networks[i] = map[string]interface{}{ 422 "uuid": networkDetails[i]["uuid"], 423 "name": networkDetails[i]["name"], 424 "port": networkDetails[i]["port"], 425 "fixed_ip_v4": n["fixed_ip_v4"], 426 "fixed_ip_v6": n["fixed_ip_v6"], 427 "mac": n["mac"], 428 } 429 } 430 } 431 432 log.Printf("[DEBUG] new networks: %+v", networks) 433 434 d.Set("network", networks) 435 d.Set("access_ip_v4", hostv4) 436 d.Set("access_ip_v6", hostv6) 437 log.Printf("hostv4: %s", hostv4) 438 log.Printf("hostv6: %s", hostv6) 439 440 // prefer the v6 address if no v4 address exists. 441 preferredv := "" 442 if hostv4 != "" { 443 preferredv = hostv4 444 } else if hostv6 != "" { 445 preferredv = hostv6 446 } 447 448 if preferredv != "" { 449 // Initialize the connection info 450 d.SetConnInfo(map[string]string{ 451 "type": "ssh", 452 "host": preferredv, 453 }) 454 } 455 // end network configuration 456 457 d.Set("metadata", server.Metadata) 458 459 secGrpNames := []string{} 460 for _, sg := range server.SecurityGroups { 461 secGrpNames = append(secGrpNames, sg["name"].(string)) 462 } 463 d.Set("security_groups", secGrpNames) 464 465 flavorId, ok := server.Flavor["id"].(string) 466 if !ok { 467 return fmt.Errorf("Error setting OpenStack server's flavor: %v", server.Flavor) 468 } 469 d.Set("flavor_id", flavorId) 470 471 flavor, err := flavors.Get(computeClient, flavorId).Extract() 472 if err != nil { 473 return err 474 } 475 d.Set("flavor_name", flavor.Name) 476 477 imageId, ok := server.Image["id"].(string) 478 if !ok { 479 return fmt.Errorf("Error setting OpenStack server's image: %v", server.Image) 480 } 481 d.Set("image_id", imageId) 482 483 image, err := images.Get(computeClient, imageId).Extract() 484 if err != nil { 485 return err 486 } 487 d.Set("image_name", image.Name) 488 489 // volume attachments 490 vas, err := getVolumeAttachments(computeClient, d.Id()) 491 if err != nil { 492 return err 493 } 494 if len(vas) > 0 { 495 attachments := make([]map[string]interface{}, len(vas)) 496 for i, attachment := range vas { 497 attachments[i] = make(map[string]interface{}) 498 attachments[i]["id"] = attachment.ID 499 attachments[i]["volume_id"] = attachment.VolumeID 500 attachments[i]["device"] = attachment.Device 501 } 502 log.Printf("[INFO] Volume attachments: %v", attachments) 503 d.Set("volume", attachments) 504 } 505 506 return nil 507 } 508 509 func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) error { 510 config := meta.(*Config) 511 computeClient, err := config.computeV2Client(d.Get("region").(string)) 512 if err != nil { 513 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 514 } 515 516 var updateOpts servers.UpdateOpts 517 if d.HasChange("name") { 518 updateOpts.Name = d.Get("name").(string) 519 } 520 if d.HasChange("access_ip_v4") { 521 updateOpts.AccessIPv4 = d.Get("access_ip_v4").(string) 522 } 523 if d.HasChange("access_ip_v6") { 524 updateOpts.AccessIPv4 = d.Get("access_ip_v6").(string) 525 } 526 527 if updateOpts != (servers.UpdateOpts{}) { 528 _, err := servers.Update(computeClient, d.Id(), updateOpts).Extract() 529 if err != nil { 530 return fmt.Errorf("Error updating OpenStack server: %s", err) 531 } 532 } 533 534 if d.HasChange("metadata") { 535 var metadataOpts servers.MetadataOpts 536 metadataOpts = make(servers.MetadataOpts) 537 newMetadata := d.Get("metadata").(map[string]interface{}) 538 for k, v := range newMetadata { 539 metadataOpts[k] = v.(string) 540 } 541 542 _, err := servers.UpdateMetadata(computeClient, d.Id(), metadataOpts).Extract() 543 if err != nil { 544 return fmt.Errorf("Error updating OpenStack server (%s) metadata: %s", d.Id(), err) 545 } 546 } 547 548 if d.HasChange("security_groups") { 549 oldSGRaw, newSGRaw := d.GetChange("security_groups") 550 oldSGSlice, newSGSlice := oldSGRaw.([]interface{}), newSGRaw.([]interface{}) 551 oldSGSet := schema.NewSet(func(v interface{}) int { return hashcode.String(v.(string)) }, oldSGSlice) 552 newSGSet := schema.NewSet(func(v interface{}) int { return hashcode.String(v.(string)) }, newSGSlice) 553 secgroupsToAdd := newSGSet.Difference(oldSGSet) 554 secgroupsToRemove := oldSGSet.Difference(newSGSet) 555 556 log.Printf("[DEBUG] Security groups to add: %v", secgroupsToAdd) 557 558 log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove) 559 560 for _, g := range secgroupsToAdd.List() { 561 err := secgroups.AddServerToGroup(computeClient, d.Id(), g.(string)).ExtractErr() 562 if err != nil { 563 return fmt.Errorf("Error adding security group to OpenStack server (%s): %s", d.Id(), err) 564 } 565 log.Printf("[DEBUG] Added security group (%s) to instance (%s)", g.(string), d.Id()) 566 } 567 568 for _, g := range secgroupsToRemove.List() { 569 err := secgroups.RemoveServerFromGroup(computeClient, d.Id(), g.(string)).ExtractErr() 570 if err != nil { 571 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 572 if !ok { 573 return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) 574 } 575 if errCode.Actual == 404 { 576 continue 577 } else { 578 return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) 579 } 580 } else { 581 log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g.(string), d.Id()) 582 } 583 } 584 } 585 586 if d.HasChange("admin_pass") { 587 if newPwd, ok := d.Get("admin_pass").(string); ok { 588 err := servers.ChangeAdminPassword(computeClient, d.Id(), newPwd).ExtractErr() 589 if err != nil { 590 return fmt.Errorf("Error changing admin password of OpenStack server (%s): %s", d.Id(), err) 591 } 592 } 593 } 594 595 if d.HasChange("floating_ip") { 596 oldFIP, newFIP := d.GetChange("floating_ip") 597 log.Printf("[DEBUG] Old Floating IP: %v", oldFIP) 598 log.Printf("[DEBUG] New Floating IP: %v", newFIP) 599 if oldFIP.(string) != "" { 600 log.Printf("[DEBUG] Attemping to disassociate %s from %s", oldFIP, d.Id()) 601 if err := floatingip.Disassociate(computeClient, d.Id(), oldFIP.(string)).ExtractErr(); err != nil { 602 return fmt.Errorf("Error disassociating Floating IP during update: %s", err) 603 } 604 } 605 606 if newFIP.(string) != "" { 607 log.Printf("[DEBUG] Attemping to associate %s to %s", newFIP, d.Id()) 608 if err := floatingip.Associate(computeClient, d.Id(), newFIP.(string)).ExtractErr(); err != nil { 609 return fmt.Errorf("Error associating Floating IP during update: %s", err) 610 } 611 } 612 } 613 614 if d.HasChange("volume") { 615 // old attachments and new attachments 616 oldAttachments, newAttachments := d.GetChange("volume") 617 618 // for each old attachment, detach the volume 619 oldAttachmentSet := oldAttachments.(*schema.Set).List() 620 if len(oldAttachmentSet) > 0 { 621 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 622 return err 623 } else { 624 if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), oldAttachmentSet); err != nil { 625 return err 626 } 627 } 628 } 629 630 // for each new attachment, attach the volume 631 newAttachmentSet := newAttachments.(*schema.Set).List() 632 if len(newAttachmentSet) > 0 { 633 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 634 return err 635 } else { 636 if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), newAttachmentSet); err != nil { 637 return err 638 } 639 } 640 } 641 642 d.SetPartial("volume") 643 } 644 645 if d.HasChange("flavor_id") || d.HasChange("flavor_name") { 646 flavorId, err := getFlavorID(computeClient, d) 647 if err != nil { 648 return err 649 } 650 resizeOpts := &servers.ResizeOpts{ 651 FlavorRef: flavorId, 652 } 653 log.Printf("[DEBUG] Resize configuration: %#v", resizeOpts) 654 err = servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr() 655 if err != nil { 656 return fmt.Errorf("Error resizing OpenStack server: %s", err) 657 } 658 659 // Wait for the instance to finish resizing. 660 log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id()) 661 662 stateConf := &resource.StateChangeConf{ 663 Pending: []string{"RESIZE"}, 664 Target: "VERIFY_RESIZE", 665 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 666 Timeout: 3 * time.Minute, 667 Delay: 10 * time.Second, 668 MinTimeout: 3 * time.Second, 669 } 670 671 _, err = stateConf.WaitForState() 672 if err != nil { 673 return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err) 674 } 675 676 // Confirm resize. 677 log.Printf("[DEBUG] Confirming resize") 678 err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr() 679 if err != nil { 680 return fmt.Errorf("Error confirming resize of OpenStack server: %s", err) 681 } 682 683 stateConf = &resource.StateChangeConf{ 684 Pending: []string{"VERIFY_RESIZE"}, 685 Target: "ACTIVE", 686 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 687 Timeout: 3 * time.Minute, 688 Delay: 10 * time.Second, 689 MinTimeout: 3 * time.Second, 690 } 691 692 _, err = stateConf.WaitForState() 693 if err != nil { 694 return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err) 695 } 696 } 697 698 return resourceComputeInstanceV2Read(d, meta) 699 } 700 701 func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) error { 702 config := meta.(*Config) 703 computeClient, err := config.computeV2Client(d.Get("region").(string)) 704 if err != nil { 705 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 706 } 707 708 err = servers.Delete(computeClient, d.Id()).ExtractErr() 709 if err != nil { 710 return fmt.Errorf("Error deleting OpenStack server: %s", err) 711 } 712 713 // Wait for the instance to delete before moving on. 714 log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id()) 715 716 stateConf := &resource.StateChangeConf{ 717 Pending: []string{"ACTIVE"}, 718 Target: "DELETED", 719 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 720 Timeout: 10 * time.Minute, 721 Delay: 10 * time.Second, 722 MinTimeout: 3 * time.Second, 723 } 724 725 _, err = stateConf.WaitForState() 726 if err != nil { 727 return fmt.Errorf( 728 "Error waiting for instance (%s) to delete: %s", 729 d.Id(), err) 730 } 731 732 d.SetId("") 733 return nil 734 } 735 736 // ServerV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 737 // an OpenStack instance. 738 func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc { 739 return func() (interface{}, string, error) { 740 s, err := servers.Get(client, instanceID).Extract() 741 if err != nil { 742 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 743 if !ok { 744 return nil, "", err 745 } 746 if errCode.Actual == 404 { 747 return s, "DELETED", nil 748 } 749 return nil, "", err 750 } 751 752 return s, s.Status, nil 753 } 754 } 755 756 func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string { 757 rawSecGroups := d.Get("security_groups").([]interface{}) 758 secgroups := make([]string, len(rawSecGroups)) 759 for i, raw := range rawSecGroups { 760 secgroups[i] = raw.(string) 761 } 762 return secgroups 763 } 764 765 func resourceInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) { 766 rawNetworks := d.Get("network").([]interface{}) 767 newNetworks := make([]map[string]interface{}, len(rawNetworks)) 768 var tenantnet tenantnetworks.Network 769 770 tenantNetworkExt := true 771 for i, raw := range rawNetworks { 772 rawMap := raw.(map[string]interface{}) 773 774 allPages, err := tenantnetworks.List(computeClient).AllPages() 775 if err != nil { 776 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 777 if !ok { 778 return nil, err 779 } 780 781 if errCode.Actual == 404 { 782 tenantNetworkExt = false 783 } else { 784 return nil, err 785 } 786 } 787 788 networkID := "" 789 networkName := "" 790 if tenantNetworkExt { 791 networkList, err := tenantnetworks.ExtractNetworks(allPages) 792 if err != nil { 793 return nil, err 794 } 795 796 for _, network := range networkList { 797 if network.Name == rawMap["name"] { 798 tenantnet = network 799 } 800 if network.ID == rawMap["uuid"] { 801 tenantnet = network 802 } 803 } 804 805 networkID = tenantnet.ID 806 networkName = tenantnet.Name 807 } else { 808 networkID = rawMap["uuid"].(string) 809 networkName = rawMap["name"].(string) 810 } 811 812 newNetworks[i] = map[string]interface{}{ 813 "uuid": networkID, 814 "name": networkName, 815 "port": rawMap["port"].(string), 816 "fixed_ip_v4": rawMap["fixed_ip_v4"].(string), 817 } 818 } 819 820 log.Printf("[DEBUG] networks: %+v", newNetworks) 821 822 return newNetworks, nil 823 } 824 825 func resourceInstanceAddresses(addresses map[string]interface{}) map[string]map[string]interface{} { 826 827 addrs := make(map[string]map[string]interface{}) 828 for n, networkAddresses := range addresses { 829 addrs[n] = make(map[string]interface{}) 830 for _, element := range networkAddresses.([]interface{}) { 831 address := element.(map[string]interface{}) 832 if address["OS-EXT-IPS:type"] == "floating" { 833 addrs[n]["floating_ip"] = address["addr"] 834 } else { 835 if address["version"].(float64) == 4 { 836 addrs[n]["fixed_ip_v4"] = address["addr"].(string) 837 } else { 838 addrs[n]["fixed_ip_v6"] = fmt.Sprintf("[%s]", address["addr"].(string)) 839 } 840 } 841 if mac, ok := address["OS-EXT-IPS-MAC:mac_addr"]; ok { 842 addrs[n]["mac"] = mac.(string) 843 } 844 } 845 } 846 847 log.Printf("[DEBUG] Addresses: %+v", addresses) 848 849 return addrs 850 } 851 852 func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string { 853 m := make(map[string]string) 854 for key, val := range d.Get("metadata").(map[string]interface{}) { 855 m[key] = val.(string) 856 } 857 return m 858 } 859 860 func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interface{}) []bootfromvolume.BlockDevice { 861 sourceType := bootfromvolume.SourceType(bd["source_type"].(string)) 862 bfvOpts := []bootfromvolume.BlockDevice{ 863 bootfromvolume.BlockDevice{ 864 UUID: bd["uuid"].(string), 865 SourceType: sourceType, 866 VolumeSize: bd["volume_size"].(int), 867 DestinationType: bd["destination_type"].(string), 868 BootIndex: bd["boot_index"].(int), 869 }, 870 } 871 872 return bfvOpts 873 } 874 875 func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { 876 imageId := d.Get("image_id").(string) 877 878 if imageId != "" { 879 return imageId, nil 880 } 881 882 imageCount := 0 883 imageName := d.Get("image_name").(string) 884 if imageName != "" { 885 pager := images.ListDetail(client, &images.ListOpts{ 886 Name: imageName, 887 }) 888 pager.EachPage(func(page pagination.Page) (bool, error) { 889 imageList, err := images.ExtractImages(page) 890 if err != nil { 891 return false, err 892 } 893 894 for _, i := range imageList { 895 if i.Name == imageName { 896 imageCount++ 897 imageId = i.ID 898 } 899 } 900 return true, nil 901 }) 902 903 switch imageCount { 904 case 0: 905 return "", fmt.Errorf("Unable to find image: %s", imageName) 906 case 1: 907 return imageId, nil 908 default: 909 return "", fmt.Errorf("Found %d images matching %s", imageCount, imageName) 910 } 911 } 912 return "", fmt.Errorf("Neither an image ID nor an image name were able to be determined.") 913 } 914 915 func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { 916 flavorId := d.Get("flavor_id").(string) 917 918 if flavorId != "" { 919 return flavorId, nil 920 } 921 922 flavorCount := 0 923 flavorName := d.Get("flavor_name").(string) 924 if flavorName != "" { 925 pager := flavors.ListDetail(client, nil) 926 pager.EachPage(func(page pagination.Page) (bool, error) { 927 flavorList, err := flavors.ExtractFlavors(page) 928 if err != nil { 929 return false, err 930 } 931 932 for _, f := range flavorList { 933 if f.Name == flavorName { 934 flavorCount++ 935 flavorId = f.ID 936 } 937 } 938 return true, nil 939 }) 940 941 switch flavorCount { 942 case 0: 943 return "", fmt.Errorf("Unable to find flavor: %s", flavorName) 944 case 1: 945 return flavorId, nil 946 default: 947 return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, flavorName) 948 } 949 } 950 return "", fmt.Errorf("Neither a flavor ID nor a flavor name were able to be determined.") 951 } 952 953 func resourceComputeVolumeAttachmentHash(v interface{}) int { 954 var buf bytes.Buffer 955 m := v.(map[string]interface{}) 956 buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string))) 957 return hashcode.String(buf.String()) 958 } 959 960 func attachVolumesToInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { 961 if len(vols) > 0 { 962 for _, v := range vols { 963 va := v.(map[string]interface{}) 964 volumeId := va["volume_id"].(string) 965 device := va["device"].(string) 966 967 s := "" 968 if serverId != "" { 969 s = serverId 970 } else if va["server_id"] != "" { 971 s = va["server_id"].(string) 972 } else { 973 return fmt.Errorf("Unable to determine server ID to attach volume.") 974 } 975 976 vaOpts := &volumeattach.CreateOpts{ 977 Device: device, 978 VolumeID: volumeId, 979 } 980 981 if _, err := volumeattach.Create(computeClient, s, vaOpts).Extract(); err != nil { 982 return err 983 } 984 985 stateConf := &resource.StateChangeConf{ 986 Pending: []string{"attaching", "available"}, 987 Target: "in-use", 988 Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), 989 Timeout: 30 * time.Minute, 990 Delay: 5 * time.Second, 991 MinTimeout: 2 * time.Second, 992 } 993 994 if _, err := stateConf.WaitForState(); err != nil { 995 return err 996 } 997 998 log.Printf("[INFO] Attached volume %s to instance %s", volumeId, serverId) 999 } 1000 } 1001 return nil 1002 } 1003 1004 func detachVolumesFromInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { 1005 if len(vols) > 0 { 1006 for _, v := range vols { 1007 va := v.(map[string]interface{}) 1008 aId := va["id"].(string) 1009 1010 if err := volumeattach.Delete(computeClient, serverId, aId).ExtractErr(); err != nil { 1011 return err 1012 } 1013 1014 stateConf := &resource.StateChangeConf{ 1015 Pending: []string{"detaching", "in-use"}, 1016 Target: "available", 1017 Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), 1018 Timeout: 30 * time.Minute, 1019 Delay: 5 * time.Second, 1020 MinTimeout: 2 * time.Second, 1021 } 1022 1023 if _, err := stateConf.WaitForState(); err != nil { 1024 return err 1025 } 1026 log.Printf("[INFO] Detached volume %s from instance %s", va["volume_id"], serverId) 1027 } 1028 } 1029 1030 return nil 1031 } 1032 1033 func getVolumeAttachments(computeClient *gophercloud.ServiceClient, serverId string) ([]volumeattach.VolumeAttachment, error) { 1034 var attachments []volumeattach.VolumeAttachment 1035 err := volumeattach.List(computeClient, serverId).EachPage(func(page pagination.Page) (bool, error) { 1036 actual, err := volumeattach.ExtractVolumeAttachments(page) 1037 if err != nil { 1038 return false, err 1039 } 1040 1041 attachments = actual 1042 return true, nil 1043 }) 1044 1045 if err != nil { 1046 return nil, err 1047 } 1048 1049 return attachments, nil 1050 }