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