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