github.com/anuaimi/terraform@v0.6.4-0.20150904235404-2bf9aec61da8/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 for _, g := range secgroupsToAdd.List() { 614 err := secgroups.AddServerToGroup(computeClient, d.Id(), g.(string)).ExtractErr() 615 if err != nil { 616 return fmt.Errorf("Error adding security group to OpenStack server (%s): %s", d.Id(), err) 617 } 618 log.Printf("[DEBUG] Added security group (%s) to instance (%s)", g.(string), d.Id()) 619 } 620 621 for _, g := range secgroupsToRemove.List() { 622 err := secgroups.RemoveServerFromGroup(computeClient, d.Id(), g.(string)).ExtractErr() 623 if err != nil { 624 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 625 if !ok { 626 return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) 627 } 628 if errCode.Actual == 404 { 629 continue 630 } else { 631 return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) 632 } 633 } else { 634 log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g.(string), d.Id()) 635 } 636 } 637 } 638 639 if d.HasChange("admin_pass") { 640 if newPwd, ok := d.Get("admin_pass").(string); ok { 641 err := servers.ChangeAdminPassword(computeClient, d.Id(), newPwd).ExtractErr() 642 if err != nil { 643 return fmt.Errorf("Error changing admin password of OpenStack server (%s): %s", d.Id(), err) 644 } 645 } 646 } 647 648 if d.HasChange("floating_ip") { 649 oldFIP, newFIP := d.GetChange("floating_ip") 650 log.Printf("[DEBUG] Old Floating IP: %v", oldFIP) 651 log.Printf("[DEBUG] New Floating IP: %v", newFIP) 652 if oldFIP.(string) != "" { 653 log.Printf("[DEBUG] Attemping to disassociate %s from %s", oldFIP, d.Id()) 654 if err := floatingip.Disassociate(computeClient, d.Id(), oldFIP.(string)).ExtractErr(); err != nil { 655 return fmt.Errorf("Error disassociating Floating IP during update: %s", err) 656 } 657 } 658 659 if newFIP.(string) != "" { 660 log.Printf("[DEBUG] Attemping to associate %s to %s", newFIP, d.Id()) 661 if err := floatingip.Associate(computeClient, d.Id(), newFIP.(string)).ExtractErr(); err != nil { 662 return fmt.Errorf("Error associating Floating IP during update: %s", err) 663 } 664 } 665 } 666 667 if d.HasChange("volume") { 668 // old attachments and new attachments 669 oldAttachments, newAttachments := d.GetChange("volume") 670 671 // for each old attachment, detach the volume 672 oldAttachmentSet := oldAttachments.(*schema.Set).List() 673 if len(oldAttachmentSet) > 0 { 674 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 675 return err 676 } else { 677 if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), oldAttachmentSet); err != nil { 678 return err 679 } 680 } 681 } 682 683 // for each new attachment, attach the volume 684 newAttachmentSet := newAttachments.(*schema.Set).List() 685 if len(newAttachmentSet) > 0 { 686 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 687 return err 688 } else { 689 if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), newAttachmentSet); err != nil { 690 return err 691 } 692 } 693 } 694 695 d.SetPartial("volume") 696 } 697 698 if d.HasChange("flavor_id") || d.HasChange("flavor_name") { 699 flavorId, err := getFlavorID(computeClient, d) 700 if err != nil { 701 return err 702 } 703 resizeOpts := &servers.ResizeOpts{ 704 FlavorRef: flavorId, 705 } 706 log.Printf("[DEBUG] Resize configuration: %#v", resizeOpts) 707 err = servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr() 708 if err != nil { 709 return fmt.Errorf("Error resizing OpenStack server: %s", err) 710 } 711 712 // Wait for the instance to finish resizing. 713 log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id()) 714 715 stateConf := &resource.StateChangeConf{ 716 Pending: []string{"RESIZE"}, 717 Target: "VERIFY_RESIZE", 718 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 719 Timeout: 3 * time.Minute, 720 Delay: 10 * time.Second, 721 MinTimeout: 3 * time.Second, 722 } 723 724 _, err = stateConf.WaitForState() 725 if err != nil { 726 return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err) 727 } 728 729 // Confirm resize. 730 log.Printf("[DEBUG] Confirming resize") 731 err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr() 732 if err != nil { 733 return fmt.Errorf("Error confirming resize of OpenStack server: %s", err) 734 } 735 736 stateConf = &resource.StateChangeConf{ 737 Pending: []string{"VERIFY_RESIZE"}, 738 Target: "ACTIVE", 739 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 740 Timeout: 3 * time.Minute, 741 Delay: 10 * time.Second, 742 MinTimeout: 3 * time.Second, 743 } 744 745 _, err = stateConf.WaitForState() 746 if err != nil { 747 return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err) 748 } 749 } 750 751 return resourceComputeInstanceV2Read(d, meta) 752 } 753 754 func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) error { 755 config := meta.(*Config) 756 computeClient, err := config.computeV2Client(d.Get("region").(string)) 757 if err != nil { 758 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 759 } 760 761 err = servers.Delete(computeClient, d.Id()).ExtractErr() 762 if err != nil { 763 return fmt.Errorf("Error deleting OpenStack server: %s", err) 764 } 765 766 // Wait for the instance to delete before moving on. 767 log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id()) 768 769 stateConf := &resource.StateChangeConf{ 770 Pending: []string{"ACTIVE"}, 771 Target: "DELETED", 772 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 773 Timeout: 10 * time.Minute, 774 Delay: 10 * time.Second, 775 MinTimeout: 3 * time.Second, 776 } 777 778 _, err = stateConf.WaitForState() 779 if err != nil { 780 return fmt.Errorf( 781 "Error waiting for instance (%s) to delete: %s", 782 d.Id(), err) 783 } 784 785 d.SetId("") 786 return nil 787 } 788 789 // ServerV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 790 // an OpenStack instance. 791 func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc { 792 return func() (interface{}, string, error) { 793 s, err := servers.Get(client, instanceID).Extract() 794 if err != nil { 795 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 796 if !ok { 797 return nil, "", err 798 } 799 if errCode.Actual == 404 { 800 return s, "DELETED", nil 801 } 802 return nil, "", err 803 } 804 805 return s, s.Status, nil 806 } 807 } 808 809 func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string { 810 rawSecGroups := d.Get("security_groups").([]interface{}) 811 secgroups := make([]string, len(rawSecGroups)) 812 for i, raw := range rawSecGroups { 813 secgroups[i] = raw.(string) 814 } 815 return secgroups 816 } 817 818 func resourceInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) { 819 rawNetworks := d.Get("network").([]interface{}) 820 newNetworks := make([]map[string]interface{}, 0, len(rawNetworks)) 821 var tenantnet tenantnetworks.Network 822 823 tenantNetworkExt := true 824 for _, raw := range rawNetworks { 825 // Not sure what causes this, but it is a possibility (see GH-2323). 826 // Since we call this function to reconcile what we'll save in the 827 // state anyways, we just ignore it. 828 if raw == nil { 829 continue 830 } 831 832 rawMap := raw.(map[string]interface{}) 833 allPages, err := tenantnetworks.List(computeClient).AllPages() 834 if err != nil { 835 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 836 if !ok { 837 return nil, err 838 } 839 840 if errCode.Actual == 404 { 841 tenantNetworkExt = false 842 } else { 843 return nil, err 844 } 845 } 846 847 networkID := "" 848 networkName := "" 849 if tenantNetworkExt { 850 networkList, err := tenantnetworks.ExtractNetworks(allPages) 851 if err != nil { 852 return nil, err 853 } 854 855 for _, network := range networkList { 856 if network.Name == rawMap["name"] { 857 tenantnet = network 858 } 859 if network.ID == rawMap["uuid"] { 860 tenantnet = network 861 } 862 } 863 864 networkID = tenantnet.ID 865 networkName = tenantnet.Name 866 } else { 867 networkID = rawMap["uuid"].(string) 868 networkName = rawMap["name"].(string) 869 } 870 871 newNetworks = append(newNetworks, map[string]interface{}{ 872 "uuid": networkID, 873 "name": networkName, 874 "port": rawMap["port"].(string), 875 "fixed_ip_v4": rawMap["fixed_ip_v4"].(string), 876 }) 877 } 878 879 log.Printf("[DEBUG] networks: %+v", newNetworks) 880 return newNetworks, nil 881 } 882 883 func resourceInstanceAddresses(addresses map[string]interface{}) map[string]map[string]interface{} { 884 885 addrs := make(map[string]map[string]interface{}) 886 for n, networkAddresses := range addresses { 887 addrs[n] = make(map[string]interface{}) 888 for _, element := range networkAddresses.([]interface{}) { 889 address := element.(map[string]interface{}) 890 if address["OS-EXT-IPS:type"] == "floating" { 891 addrs[n]["floating_ip"] = address["addr"] 892 } else { 893 if address["version"].(float64) == 4 { 894 addrs[n]["fixed_ip_v4"] = address["addr"].(string) 895 } else { 896 addrs[n]["fixed_ip_v6"] = fmt.Sprintf("[%s]", address["addr"].(string)) 897 } 898 } 899 if mac, ok := address["OS-EXT-IPS-MAC:mac_addr"]; ok { 900 addrs[n]["mac"] = mac.(string) 901 } 902 } 903 } 904 905 log.Printf("[DEBUG] Addresses: %+v", addresses) 906 907 return addrs 908 } 909 910 func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string { 911 m := make(map[string]string) 912 for key, val := range d.Get("metadata").(map[string]interface{}) { 913 m[key] = val.(string) 914 } 915 return m 916 } 917 918 func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interface{}) []bootfromvolume.BlockDevice { 919 sourceType := bootfromvolume.SourceType(bd["source_type"].(string)) 920 bfvOpts := []bootfromvolume.BlockDevice{ 921 bootfromvolume.BlockDevice{ 922 UUID: bd["uuid"].(string), 923 SourceType: sourceType, 924 VolumeSize: bd["volume_size"].(int), 925 DestinationType: bd["destination_type"].(string), 926 BootIndex: bd["boot_index"].(int), 927 }, 928 } 929 930 return bfvOpts 931 } 932 933 func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw map[string]interface{}) schedulerhints.SchedulerHints { 934 differentHost := []string{} 935 if len(schedulerHintsRaw["different_host"].([]interface{})) > 0 { 936 for _, dh := range schedulerHintsRaw["different_host"].([]interface{}) { 937 differentHost = append(differentHost, dh.(string)) 938 } 939 } 940 941 sameHost := []string{} 942 if len(schedulerHintsRaw["same_host"].([]interface{})) > 0 { 943 for _, sh := range schedulerHintsRaw["same_host"].([]interface{}) { 944 sameHost = append(sameHost, sh.(string)) 945 } 946 } 947 948 query := make([]interface{}, len(schedulerHintsRaw["query"].([]interface{}))) 949 if len(schedulerHintsRaw["query"].([]interface{})) > 0 { 950 for _, q := range schedulerHintsRaw["query"].([]interface{}) { 951 query = append(query, q.(string)) 952 } 953 } 954 955 schedulerHints := schedulerhints.SchedulerHints{ 956 Group: schedulerHintsRaw["group"].(string), 957 DifferentHost: differentHost, 958 SameHost: sameHost, 959 Query: query, 960 TargetCell: schedulerHintsRaw["target_cell"].(string), 961 BuildNearHostIP: schedulerHintsRaw["build_near_host_ip"].(string), 962 } 963 964 return schedulerHints 965 } 966 967 func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { 968 imageId := d.Get("image_id").(string) 969 970 if imageId != "" { 971 return imageId, nil 972 } 973 974 imageCount := 0 975 imageName := d.Get("image_name").(string) 976 if imageName != "" { 977 pager := images.ListDetail(client, &images.ListOpts{ 978 Name: imageName, 979 }) 980 pager.EachPage(func(page pagination.Page) (bool, error) { 981 imageList, err := images.ExtractImages(page) 982 if err != nil { 983 return false, err 984 } 985 986 for _, i := range imageList { 987 if i.Name == imageName { 988 imageCount++ 989 imageId = i.ID 990 } 991 } 992 return true, nil 993 }) 994 995 switch imageCount { 996 case 0: 997 return "", fmt.Errorf("Unable to find image: %s", imageName) 998 case 1: 999 return imageId, nil 1000 default: 1001 return "", fmt.Errorf("Found %d images matching %s", imageCount, imageName) 1002 } 1003 } 1004 return "", fmt.Errorf("Neither an image ID nor an image name were able to be determined.") 1005 } 1006 1007 func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { 1008 flavorId := d.Get("flavor_id").(string) 1009 1010 if flavorId != "" { 1011 return flavorId, nil 1012 } 1013 1014 flavorCount := 0 1015 flavorName := d.Get("flavor_name").(string) 1016 if flavorName != "" { 1017 pager := flavors.ListDetail(client, nil) 1018 pager.EachPage(func(page pagination.Page) (bool, error) { 1019 flavorList, err := flavors.ExtractFlavors(page) 1020 if err != nil { 1021 return false, err 1022 } 1023 1024 for _, f := range flavorList { 1025 if f.Name == flavorName { 1026 flavorCount++ 1027 flavorId = f.ID 1028 } 1029 } 1030 return true, nil 1031 }) 1032 1033 switch flavorCount { 1034 case 0: 1035 return "", fmt.Errorf("Unable to find flavor: %s", flavorName) 1036 case 1: 1037 return flavorId, nil 1038 default: 1039 return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, flavorName) 1040 } 1041 } 1042 return "", fmt.Errorf("Neither a flavor ID nor a flavor name were able to be determined.") 1043 } 1044 1045 func resourceComputeVolumeAttachmentHash(v interface{}) int { 1046 var buf bytes.Buffer 1047 m := v.(map[string]interface{}) 1048 buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string))) 1049 return hashcode.String(buf.String()) 1050 } 1051 1052 func resourceComputeSchedulerHintsHash(v interface{}) int { 1053 var buf bytes.Buffer 1054 m := v.(map[string]interface{}) 1055 1056 if m["group"] != nil { 1057 buf.WriteString(fmt.Sprintf("%s-", m["group"].(string))) 1058 } 1059 1060 if m["target_cell"] != nil { 1061 buf.WriteString(fmt.Sprintf("%s-", m["target_cell"].(string))) 1062 } 1063 1064 if m["build_host_near_ip"] != nil { 1065 buf.WriteString(fmt.Sprintf("%s-", m["build_host_near_ip"].(string))) 1066 } 1067 1068 buf.WriteString(fmt.Sprintf("%s-", m["different_host"].([]interface{}))) 1069 buf.WriteString(fmt.Sprintf("%s-", m["same_host"].([]interface{}))) 1070 buf.WriteString(fmt.Sprintf("%s-", m["query"].([]interface{}))) 1071 1072 return hashcode.String(buf.String()) 1073 } 1074 1075 func attachVolumesToInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { 1076 if len(vols) > 0 { 1077 for _, v := range vols { 1078 va := v.(map[string]interface{}) 1079 volumeId := va["volume_id"].(string) 1080 device := va["device"].(string) 1081 1082 s := "" 1083 if serverId != "" { 1084 s = serverId 1085 } else if va["server_id"] != "" { 1086 s = va["server_id"].(string) 1087 } else { 1088 return fmt.Errorf("Unable to determine server ID to attach volume.") 1089 } 1090 1091 vaOpts := &volumeattach.CreateOpts{ 1092 Device: device, 1093 VolumeID: volumeId, 1094 } 1095 1096 if _, err := volumeattach.Create(computeClient, s, vaOpts).Extract(); err != nil { 1097 return err 1098 } 1099 1100 stateConf := &resource.StateChangeConf{ 1101 Pending: []string{"attaching", "available"}, 1102 Target: "in-use", 1103 Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), 1104 Timeout: 30 * time.Minute, 1105 Delay: 5 * time.Second, 1106 MinTimeout: 2 * time.Second, 1107 } 1108 1109 if _, err := stateConf.WaitForState(); err != nil { 1110 return err 1111 } 1112 1113 log.Printf("[INFO] Attached volume %s to instance %s", volumeId, serverId) 1114 } 1115 } 1116 return nil 1117 } 1118 1119 func detachVolumesFromInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { 1120 if len(vols) > 0 { 1121 for _, v := range vols { 1122 va := v.(map[string]interface{}) 1123 aId := va["id"].(string) 1124 1125 if err := volumeattach.Delete(computeClient, serverId, aId).ExtractErr(); err != nil { 1126 return err 1127 } 1128 1129 stateConf := &resource.StateChangeConf{ 1130 Pending: []string{"detaching", "in-use"}, 1131 Target: "available", 1132 Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), 1133 Timeout: 30 * time.Minute, 1134 Delay: 5 * time.Second, 1135 MinTimeout: 2 * time.Second, 1136 } 1137 1138 if _, err := stateConf.WaitForState(); err != nil { 1139 return err 1140 } 1141 log.Printf("[INFO] Detached volume %s from instance %s", va["volume_id"], serverId) 1142 } 1143 } 1144 1145 return nil 1146 } 1147 1148 func getVolumeAttachments(computeClient *gophercloud.ServiceClient, serverId string) ([]volumeattach.VolumeAttachment, error) { 1149 var attachments []volumeattach.VolumeAttachment 1150 err := volumeattach.List(computeClient, serverId).EachPage(func(page pagination.Page) (bool, error) { 1151 actual, err := volumeattach.ExtractVolumeAttachments(page) 1152 if err != nil { 1153 return false, err 1154 } 1155 1156 attachments = actual 1157 return true, nil 1158 }) 1159 1160 if err != nil { 1161 return nil, err 1162 } 1163 1164 return attachments, nil 1165 }