github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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 "os" 10 "time" 11 12 "github.com/gophercloud/gophercloud" 13 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones" 14 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume" 15 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips" 16 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" 17 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints" 18 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups" 19 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop" 20 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks" 21 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" 22 "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" 23 "github.com/gophercloud/gophercloud/openstack/compute/v2/images" 24 "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" 25 "github.com/hashicorp/terraform/helper/hashcode" 26 "github.com/hashicorp/terraform/helper/resource" 27 "github.com/hashicorp/terraform/helper/schema" 28 ) 29 30 func resourceComputeInstanceV2() *schema.Resource { 31 return &schema.Resource{ 32 Create: resourceComputeInstanceV2Create, 33 Read: resourceComputeInstanceV2Read, 34 Update: resourceComputeInstanceV2Update, 35 Delete: resourceComputeInstanceV2Delete, 36 37 Timeouts: &schema.ResourceTimeout{ 38 Create: schema.DefaultTimeout(30 * time.Minute), 39 Update: schema.DefaultTimeout(30 * time.Minute), 40 Delete: schema.DefaultTimeout(30 * time.Minute), 41 }, 42 43 Schema: map[string]*schema.Schema{ 44 "region": &schema.Schema{ 45 Type: schema.TypeString, 46 Required: true, 47 ForceNew: true, 48 DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), 49 }, 50 "name": &schema.Schema{ 51 Type: schema.TypeString, 52 Required: true, 53 ForceNew: false, 54 }, 55 "image_id": &schema.Schema{ 56 Type: schema.TypeString, 57 Optional: true, 58 ForceNew: true, 59 Computed: true, 60 }, 61 "image_name": &schema.Schema{ 62 Type: schema.TypeString, 63 Optional: true, 64 ForceNew: true, 65 Computed: true, 66 }, 67 "flavor_id": &schema.Schema{ 68 Type: schema.TypeString, 69 Optional: true, 70 ForceNew: false, 71 Computed: true, 72 DefaultFunc: schema.EnvDefaultFunc("OS_FLAVOR_ID", nil), 73 }, 74 "flavor_name": &schema.Schema{ 75 Type: schema.TypeString, 76 Optional: true, 77 ForceNew: false, 78 Computed: true, 79 DefaultFunc: schema.EnvDefaultFunc("OS_FLAVOR_NAME", nil), 80 }, 81 "floating_ip": &schema.Schema{ 82 Type: schema.TypeString, 83 Optional: true, 84 ForceNew: false, 85 Deprecated: "Use the openstack_compute_floatingip_associate_v2 resource instead", 86 }, 87 "user_data": &schema.Schema{ 88 Type: schema.TypeString, 89 Optional: true, 90 ForceNew: true, 91 // just stash the hash for state & diff comparisons 92 StateFunc: func(v interface{}) string { 93 switch v.(type) { 94 case string: 95 hash := sha1.Sum([]byte(v.(string))) 96 return hex.EncodeToString(hash[:]) 97 default: 98 return "" 99 } 100 }, 101 }, 102 "security_groups": &schema.Schema{ 103 Type: schema.TypeSet, 104 Optional: true, 105 ForceNew: false, 106 Computed: true, 107 Elem: &schema.Schema{Type: schema.TypeString}, 108 Set: schema.HashString, 109 }, 110 "availability_zone": &schema.Schema{ 111 Type: schema.TypeString, 112 Optional: true, 113 ForceNew: true, 114 Computed: true, 115 }, 116 "network": &schema.Schema{ 117 Type: schema.TypeList, 118 Optional: true, 119 ForceNew: true, 120 Computed: true, 121 Elem: &schema.Resource{ 122 Schema: map[string]*schema.Schema{ 123 "uuid": &schema.Schema{ 124 Type: schema.TypeString, 125 Optional: true, 126 ForceNew: true, 127 Computed: true, 128 }, 129 "name": &schema.Schema{ 130 Type: schema.TypeString, 131 Optional: true, 132 ForceNew: true, 133 Computed: true, 134 }, 135 "port": &schema.Schema{ 136 Type: schema.TypeString, 137 Optional: true, 138 ForceNew: true, 139 Computed: true, 140 }, 141 "fixed_ip_v4": &schema.Schema{ 142 Type: schema.TypeString, 143 Optional: true, 144 ForceNew: true, 145 Computed: true, 146 }, 147 "fixed_ip_v6": &schema.Schema{ 148 Type: schema.TypeString, 149 Optional: true, 150 ForceNew: true, 151 Computed: true, 152 }, 153 "floating_ip": &schema.Schema{ 154 Type: schema.TypeString, 155 Optional: true, 156 Computed: true, 157 Deprecated: "Use the openstack_compute_floatingip_associate_v2 resource instead", 158 }, 159 "mac": &schema.Schema{ 160 Type: schema.TypeString, 161 Computed: true, 162 }, 163 "access_network": &schema.Schema{ 164 Type: schema.TypeBool, 165 Optional: true, 166 Default: false, 167 }, 168 }, 169 }, 170 }, 171 "metadata": &schema.Schema{ 172 Type: schema.TypeMap, 173 Optional: true, 174 ForceNew: false, 175 }, 176 "config_drive": &schema.Schema{ 177 Type: schema.TypeBool, 178 Optional: true, 179 ForceNew: true, 180 }, 181 "admin_pass": &schema.Schema{ 182 Type: schema.TypeString, 183 Optional: true, 184 ForceNew: false, 185 }, 186 "access_ip_v4": &schema.Schema{ 187 Type: schema.TypeString, 188 Computed: true, 189 Optional: true, 190 ForceNew: false, 191 }, 192 "access_ip_v6": &schema.Schema{ 193 Type: schema.TypeString, 194 Computed: true, 195 Optional: true, 196 ForceNew: false, 197 }, 198 "key_pair": &schema.Schema{ 199 Type: schema.TypeString, 200 Optional: true, 201 ForceNew: true, 202 }, 203 "block_device": &schema.Schema{ 204 Type: schema.TypeList, 205 Optional: true, 206 Elem: &schema.Resource{ 207 Schema: map[string]*schema.Schema{ 208 "source_type": &schema.Schema{ 209 Type: schema.TypeString, 210 Required: true, 211 ForceNew: true, 212 }, 213 "uuid": &schema.Schema{ 214 Type: schema.TypeString, 215 Optional: true, 216 ForceNew: true, 217 }, 218 "volume_size": &schema.Schema{ 219 Type: schema.TypeInt, 220 Optional: true, 221 ForceNew: true, 222 }, 223 "destination_type": &schema.Schema{ 224 Type: schema.TypeString, 225 Optional: true, 226 ForceNew: true, 227 }, 228 "boot_index": &schema.Schema{ 229 Type: schema.TypeInt, 230 Optional: true, 231 ForceNew: true, 232 }, 233 "delete_on_termination": &schema.Schema{ 234 Type: schema.TypeBool, 235 Optional: true, 236 Default: false, 237 ForceNew: true, 238 }, 239 "guest_format": &schema.Schema{ 240 Type: schema.TypeString, 241 Optional: true, 242 ForceNew: true, 243 }, 244 }, 245 }, 246 }, 247 "volume": &schema.Schema{ 248 Type: schema.TypeSet, 249 Optional: true, 250 Deprecated: "Use block_device or openstack_compute_volume_attach_v2 instead", 251 Elem: &schema.Resource{ 252 Schema: map[string]*schema.Schema{ 253 "id": &schema.Schema{ 254 Type: schema.TypeString, 255 Optional: true, 256 Computed: true, 257 }, 258 "volume_id": &schema.Schema{ 259 Type: schema.TypeString, 260 Required: true, 261 }, 262 "device": &schema.Schema{ 263 Type: schema.TypeString, 264 Optional: true, 265 Computed: true, 266 }, 267 }, 268 }, 269 Set: resourceComputeVolumeAttachmentHash, 270 }, 271 "scheduler_hints": &schema.Schema{ 272 Type: schema.TypeSet, 273 Optional: true, 274 Elem: &schema.Resource{ 275 Schema: map[string]*schema.Schema{ 276 "group": &schema.Schema{ 277 Type: schema.TypeString, 278 Optional: true, 279 ForceNew: true, 280 }, 281 "different_host": &schema.Schema{ 282 Type: schema.TypeList, 283 Optional: true, 284 ForceNew: true, 285 Elem: &schema.Schema{Type: schema.TypeString}, 286 }, 287 "same_host": &schema.Schema{ 288 Type: schema.TypeList, 289 Optional: true, 290 ForceNew: true, 291 Elem: &schema.Schema{Type: schema.TypeString}, 292 }, 293 "query": &schema.Schema{ 294 Type: schema.TypeList, 295 Optional: true, 296 ForceNew: true, 297 Elem: &schema.Schema{Type: schema.TypeString}, 298 }, 299 "target_cell": &schema.Schema{ 300 Type: schema.TypeString, 301 Optional: true, 302 ForceNew: true, 303 }, 304 "build_near_host_ip": &schema.Schema{ 305 Type: schema.TypeString, 306 Optional: true, 307 ForceNew: true, 308 }, 309 }, 310 }, 311 Set: resourceComputeSchedulerHintsHash, 312 }, 313 "personality": &schema.Schema{ 314 Type: schema.TypeSet, 315 Optional: true, 316 ForceNew: true, 317 Elem: &schema.Resource{ 318 Schema: map[string]*schema.Schema{ 319 "file": &schema.Schema{ 320 Type: schema.TypeString, 321 Required: true, 322 }, 323 "content": &schema.Schema{ 324 Type: schema.TypeString, 325 Required: true, 326 }, 327 }, 328 }, 329 Set: resourceComputeInstancePersonalityHash, 330 }, 331 "stop_before_destroy": &schema.Schema{ 332 Type: schema.TypeBool, 333 Optional: true, 334 Default: false, 335 }, 336 "force_delete": &schema.Schema{ 337 Type: schema.TypeBool, 338 Optional: true, 339 Default: false, 340 }, 341 "all_metadata": &schema.Schema{ 342 Type: schema.TypeMap, 343 Computed: true, 344 }, 345 }, 346 } 347 } 348 349 func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) error { 350 config := meta.(*Config) 351 computeClient, err := config.computeV2Client(GetRegion(d)) 352 if err != nil { 353 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 354 } 355 356 var createOpts servers.CreateOptsBuilder 357 358 // Determines the Image ID using the following rules: 359 // If a bootable block_device was specified, ignore the image altogether. 360 // If an image_id was specified, use it. 361 // If an image_name was specified, look up the image ID, report if error. 362 imageId, err := getImageIDFromConfig(computeClient, d) 363 if err != nil { 364 return err 365 } 366 367 flavorId, err := getFlavorID(computeClient, d) 368 if err != nil { 369 return err 370 } 371 372 // determine if block_device configuration is correct 373 // this includes valid combinations and required attributes 374 if err := checkBlockDeviceConfig(d); err != nil { 375 return err 376 } 377 378 // check if floating IP configuration is correct 379 if err := checkInstanceFloatingIPs(d); err != nil { 380 return err 381 } 382 383 // Build a list of networks with the information given upon creation. 384 // Error out if an invalid network configuration was used. 385 networkDetails, err := getInstanceNetworks(computeClient, d) 386 if err != nil { 387 return err 388 } 389 390 networks := make([]servers.Network, len(networkDetails)) 391 for i, net := range networkDetails { 392 networks[i] = servers.Network{ 393 UUID: net["uuid"].(string), 394 Port: net["port"].(string), 395 FixedIP: net["fixed_ip_v4"].(string), 396 } 397 } 398 399 configDrive := d.Get("config_drive").(bool) 400 401 createOpts = &servers.CreateOpts{ 402 Name: d.Get("name").(string), 403 ImageRef: imageId, 404 FlavorRef: flavorId, 405 SecurityGroups: resourceInstanceSecGroupsV2(d), 406 AvailabilityZone: d.Get("availability_zone").(string), 407 Networks: networks, 408 Metadata: resourceInstanceMetadataV2(d), 409 ConfigDrive: &configDrive, 410 AdminPass: d.Get("admin_pass").(string), 411 UserData: []byte(d.Get("user_data").(string)), 412 Personality: resourceInstancePersonalityV2(d), 413 } 414 415 if keyName, ok := d.Get("key_pair").(string); ok && keyName != "" { 416 createOpts = &keypairs.CreateOptsExt{ 417 CreateOptsBuilder: createOpts, 418 KeyName: keyName, 419 } 420 } 421 422 if vL, ok := d.GetOk("block_device"); ok { 423 blockDevices, err := resourceInstanceBlockDevicesV2(d, vL.([]interface{})) 424 if err != nil { 425 return err 426 } 427 428 createOpts = &bootfromvolume.CreateOptsExt{ 429 CreateOptsBuilder: createOpts, 430 BlockDevice: blockDevices, 431 } 432 } 433 434 schedulerHintsRaw := d.Get("scheduler_hints").(*schema.Set).List() 435 if len(schedulerHintsRaw) > 0 { 436 log.Printf("[DEBUG] schedulerhints: %+v", schedulerHintsRaw) 437 schedulerHints := resourceInstanceSchedulerHintsV2(d, schedulerHintsRaw[0].(map[string]interface{})) 438 createOpts = &schedulerhints.CreateOptsExt{ 439 CreateOptsBuilder: createOpts, 440 SchedulerHints: schedulerHints, 441 } 442 } 443 444 log.Printf("[DEBUG] Create Options: %#v", createOpts) 445 446 // If a block_device is used, use the bootfromvolume.Create function as it allows an empty ImageRef. 447 // Otherwise, use the normal servers.Create function. 448 var server *servers.Server 449 if _, ok := d.GetOk("block_device"); ok { 450 server, err = bootfromvolume.Create(computeClient, createOpts).Extract() 451 } else { 452 server, err = servers.Create(computeClient, createOpts).Extract() 453 } 454 455 if err != nil { 456 return fmt.Errorf("Error creating OpenStack server: %s", err) 457 } 458 log.Printf("[INFO] Instance ID: %s", server.ID) 459 460 // Store the ID now 461 d.SetId(server.ID) 462 463 // Wait for the instance to become running so we can get some attributes 464 // that aren't available until later. 465 log.Printf( 466 "[DEBUG] Waiting for instance (%s) to become running", 467 server.ID) 468 469 stateConf := &resource.StateChangeConf{ 470 Pending: []string{"BUILD"}, 471 Target: []string{"ACTIVE"}, 472 Refresh: ServerV2StateRefreshFunc(computeClient, server.ID), 473 Timeout: d.Timeout(schema.TimeoutCreate), 474 Delay: 10 * time.Second, 475 MinTimeout: 3 * time.Second, 476 } 477 478 _, err = stateConf.WaitForState() 479 if err != nil { 480 return fmt.Errorf( 481 "Error waiting for instance (%s) to become ready: %s", 482 server.ID, err) 483 } 484 485 // Now that the instance has been created, we need to do an early read on the 486 // networks in order to associate floating IPs 487 _, err = getInstanceNetworksAndAddresses(computeClient, d) 488 489 // If floating IPs were specified, associate them after the instance has launched. 490 err = associateFloatingIPsToInstance(computeClient, d) 491 if err != nil { 492 return err 493 } 494 495 // if volumes were specified, attach them after the instance has launched. 496 if v, ok := d.GetOk("volume"); ok { 497 vols := v.(*schema.Set).List() 498 if blockClient, err := config.blockStorageV1Client(GetRegion(d)); err != nil { 499 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 500 } else { 501 if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil { 502 return err 503 } 504 } 505 } 506 507 return resourceComputeInstanceV2Read(d, meta) 508 } 509 510 func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) error { 511 config := meta.(*Config) 512 computeClient, err := config.computeV2Client(GetRegion(d)) 513 if err != nil { 514 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 515 } 516 517 server, err := servers.Get(computeClient, d.Id()).Extract() 518 if err != nil { 519 return CheckDeleted(d, err, "server") 520 } 521 522 log.Printf("[DEBUG] Retrieved Server %s: %+v", d.Id(), server) 523 524 d.Set("name", server.Name) 525 526 // Get the instance network and address information 527 networks, err := getInstanceNetworksAndAddresses(computeClient, d) 528 if err != nil { 529 return err 530 } 531 532 // Determine the best IPv4 and IPv6 addresses to access the instance with 533 hostv4, hostv6 := getInstanceAccessAddresses(d, networks) 534 535 if server.AccessIPv4 != "" && hostv4 == "" { 536 hostv4 = server.AccessIPv4 537 } 538 539 if server.AccessIPv6 != "" && hostv6 == "" { 540 hostv6 = server.AccessIPv6 541 } 542 543 d.Set("network", networks) 544 d.Set("access_ip_v4", hostv4) 545 d.Set("access_ip_v6", hostv6) 546 547 // Determine the best IP address to use for SSH connectivity. 548 // Prefer IPv4 over IPv6. 549 preferredSSHAddress := "" 550 if hostv4 != "" { 551 preferredSSHAddress = hostv4 552 } else if hostv6 != "" { 553 preferredSSHAddress = hostv6 554 } 555 556 if preferredSSHAddress != "" { 557 // Initialize the connection info 558 d.SetConnInfo(map[string]string{ 559 "type": "ssh", 560 "host": preferredSSHAddress, 561 }) 562 } 563 564 d.Set("all_metadata", server.Metadata) 565 566 secGrpNames := []string{} 567 for _, sg := range server.SecurityGroups { 568 secGrpNames = append(secGrpNames, sg["name"].(string)) 569 } 570 d.Set("security_groups", secGrpNames) 571 572 flavorId, ok := server.Flavor["id"].(string) 573 if !ok { 574 return fmt.Errorf("Error setting OpenStack server's flavor: %v", server.Flavor) 575 } 576 d.Set("flavor_id", flavorId) 577 578 flavor, err := flavors.Get(computeClient, flavorId).Extract() 579 if err != nil { 580 return err 581 } 582 d.Set("flavor_name", flavor.Name) 583 584 // Set the instance's image information appropriately 585 if err := setImageInformation(computeClient, server, d); err != nil { 586 return err 587 } 588 589 // volume attachments 590 if err := getVolumeAttachments(computeClient, d); err != nil { 591 return err 592 } 593 594 // Build a custom struct for the availability zone extension 595 var serverWithAZ struct { 596 servers.Server 597 availabilityzones.ServerExt 598 } 599 600 // Do another Get so the above work is not disturbed. 601 err = servers.Get(computeClient, d.Id()).ExtractInto(&serverWithAZ) 602 if err != nil { 603 return CheckDeleted(d, err, "server") 604 } 605 606 // Set the availability zone 607 d.Set("availability_zone", serverWithAZ.AvailabilityZone) 608 609 return nil 610 } 611 612 func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) error { 613 config := meta.(*Config) 614 computeClient, err := config.computeV2Client(GetRegion(d)) 615 if err != nil { 616 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 617 } 618 619 var updateOpts servers.UpdateOpts 620 if d.HasChange("name") { 621 updateOpts.Name = d.Get("name").(string) 622 } 623 624 if updateOpts != (servers.UpdateOpts{}) { 625 _, err := servers.Update(computeClient, d.Id(), updateOpts).Extract() 626 if err != nil { 627 return fmt.Errorf("Error updating OpenStack server: %s", err) 628 } 629 } 630 631 if d.HasChange("metadata") { 632 oldMetadata, newMetadata := d.GetChange("metadata") 633 var metadataToDelete []string 634 635 // Determine if any metadata keys were removed from the configuration. 636 // Then request those keys to be deleted. 637 for oldKey, _ := range oldMetadata.(map[string]interface{}) { 638 var found bool 639 for newKey, _ := range newMetadata.(map[string]interface{}) { 640 if oldKey == newKey { 641 found = true 642 } 643 } 644 645 if !found { 646 metadataToDelete = append(metadataToDelete, oldKey) 647 } 648 } 649 650 for _, key := range metadataToDelete { 651 err := servers.DeleteMetadatum(computeClient, d.Id(), key).ExtractErr() 652 if err != nil { 653 return fmt.Errorf("Error deleting metadata (%s) from server (%s): %s", key, d.Id(), err) 654 } 655 } 656 657 // Update existing metadata and add any new metadata. 658 metadataOpts := make(servers.MetadataOpts) 659 for k, v := range newMetadata.(map[string]interface{}) { 660 metadataOpts[k] = v.(string) 661 } 662 663 _, err := servers.UpdateMetadata(computeClient, d.Id(), metadataOpts).Extract() 664 if err != nil { 665 return fmt.Errorf("Error updating OpenStack server (%s) metadata: %s", d.Id(), err) 666 } 667 } 668 669 if d.HasChange("security_groups") { 670 oldSGRaw, newSGRaw := d.GetChange("security_groups") 671 oldSGSet := oldSGRaw.(*schema.Set) 672 newSGSet := newSGRaw.(*schema.Set) 673 secgroupsToAdd := newSGSet.Difference(oldSGSet) 674 secgroupsToRemove := oldSGSet.Difference(newSGSet) 675 676 log.Printf("[DEBUG] Security groups to add: %v", secgroupsToAdd) 677 678 log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove) 679 680 for _, g := range secgroupsToRemove.List() { 681 err := secgroups.RemoveServer(computeClient, d.Id(), g.(string)).ExtractErr() 682 if err != nil && err.Error() != "EOF" { 683 if _, ok := err.(gophercloud.ErrDefault404); ok { 684 continue 685 } 686 687 return fmt.Errorf("Error removing security group (%s) from OpenStack server (%s): %s", g, d.Id(), err) 688 } else { 689 log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g, d.Id()) 690 } 691 } 692 693 for _, g := range secgroupsToAdd.List() { 694 err := secgroups.AddServer(computeClient, d.Id(), g.(string)).ExtractErr() 695 if err != nil && err.Error() != "EOF" { 696 return fmt.Errorf("Error adding security group (%s) to OpenStack server (%s): %s", g, d.Id(), err) 697 } 698 log.Printf("[DEBUG] Added security group (%s) to instance (%s)", g, d.Id()) 699 } 700 } 701 702 if d.HasChange("admin_pass") { 703 if newPwd, ok := d.Get("admin_pass").(string); ok { 704 err := servers.ChangeAdminPassword(computeClient, d.Id(), newPwd).ExtractErr() 705 if err != nil { 706 return fmt.Errorf("Error changing admin password of OpenStack server (%s): %s", d.Id(), err) 707 } 708 } 709 } 710 711 if d.HasChange("floating_ip") { 712 oldFIP, newFIP := d.GetChange("floating_ip") 713 log.Printf("[DEBUG] Old Floating IP: %v", oldFIP) 714 log.Printf("[DEBUG] New Floating IP: %v", newFIP) 715 if oldFIP.(string) != "" { 716 log.Printf("[DEBUG] Attempting to disassociate %s from %s", oldFIP, d.Id()) 717 if err := disassociateFloatingIPFromInstance(computeClient, oldFIP.(string), d.Id(), ""); err != nil { 718 return fmt.Errorf("Error disassociating Floating IP during update: %s", err) 719 } 720 } 721 722 if newFIP.(string) != "" { 723 log.Printf("[DEBUG] Attempting to associate %s to %s", newFIP, d.Id()) 724 if err := associateFloatingIPToInstance(computeClient, newFIP.(string), d.Id(), ""); err != nil { 725 return fmt.Errorf("Error associating Floating IP during update: %s", err) 726 } 727 } 728 } 729 730 if d.HasChange("network") { 731 oldNetworks, newNetworks := d.GetChange("network") 732 oldNetworkList := oldNetworks.([]interface{}) 733 newNetworkList := newNetworks.([]interface{}) 734 for i, oldNet := range oldNetworkList { 735 var oldFIP, newFIP string 736 var oldFixedIP, newFixedIP string 737 738 if oldNetRaw, ok := oldNet.(map[string]interface{}); ok { 739 oldFIP = oldNetRaw["floating_ip"].(string) 740 oldFixedIP = oldNetRaw["fixed_ip_v4"].(string) 741 } 742 743 if len(newNetworkList) > i { 744 if newNetRaw, ok := newNetworkList[i].(map[string]interface{}); ok { 745 newFIP = newNetRaw["floating_ip"].(string) 746 newFixedIP = newNetRaw["fixed_ip_v4"].(string) 747 } 748 } 749 750 // Only changes to the floating IP are supported 751 if oldFIP != "" && oldFIP != newFIP { 752 log.Printf("[DEBUG] Attempting to disassociate %s from %s", oldFIP, d.Id()) 753 if err := disassociateFloatingIPFromInstance(computeClient, oldFIP, d.Id(), oldFixedIP); err != nil { 754 return fmt.Errorf("Error disassociating Floating IP during update: %s", err) 755 } 756 } 757 758 if newFIP != "" && oldFIP != newFIP { 759 log.Printf("[DEBUG] Attempting to associate %s to %s", newFIP, d.Id()) 760 if err := associateFloatingIPToInstance(computeClient, newFIP, d.Id(), newFixedIP); err != nil { 761 return fmt.Errorf("Error associating Floating IP during update: %s", err) 762 } 763 } 764 } 765 } 766 767 if d.HasChange("volume") { 768 // old attachments and new attachments 769 oldAttachments, newAttachments := d.GetChange("volume") 770 // for each old attachment, detach the volume 771 oldAttachmentSet := oldAttachments.(*schema.Set).List() 772 773 log.Printf("[DEBUG] Attempting to detach the following volumes: %#v", oldAttachmentSet) 774 if blockClient, err := config.blockStorageV1Client(GetRegion(d)); err != nil { 775 return err 776 } else { 777 if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), oldAttachmentSet); err != nil { 778 return err 779 } 780 } 781 782 // for each new attachment, attach the volume 783 newAttachmentSet := newAttachments.(*schema.Set).List() 784 if blockClient, err := config.blockStorageV1Client(GetRegion(d)); err != nil { 785 return err 786 } else { 787 if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), newAttachmentSet); err != nil { 788 return err 789 } 790 } 791 792 d.SetPartial("volume") 793 } 794 795 if d.HasChange("flavor_id") || d.HasChange("flavor_name") { 796 var newFlavorId string 797 var err error 798 if d.HasChange("flavor_id") { 799 newFlavorId = d.Get("flavor_id").(string) 800 } else { 801 newFlavorName := d.Get("flavor_name").(string) 802 newFlavorId, err = flavors.IDFromName(computeClient, newFlavorName) 803 if err != nil { 804 return err 805 } 806 } 807 808 resizeOpts := &servers.ResizeOpts{ 809 FlavorRef: newFlavorId, 810 } 811 log.Printf("[DEBUG] Resize configuration: %#v", resizeOpts) 812 err = servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr() 813 if err != nil { 814 return fmt.Errorf("Error resizing OpenStack server: %s", err) 815 } 816 817 // Wait for the instance to finish resizing. 818 log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id()) 819 820 stateConf := &resource.StateChangeConf{ 821 Pending: []string{"RESIZE"}, 822 Target: []string{"VERIFY_RESIZE"}, 823 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 824 Timeout: d.Timeout(schema.TimeoutUpdate), 825 Delay: 10 * time.Second, 826 MinTimeout: 3 * time.Second, 827 } 828 829 _, err = stateConf.WaitForState() 830 if err != nil { 831 return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err) 832 } 833 834 // Confirm resize. 835 log.Printf("[DEBUG] Confirming resize") 836 err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr() 837 if err != nil { 838 return fmt.Errorf("Error confirming resize of OpenStack server: %s", err) 839 } 840 841 stateConf = &resource.StateChangeConf{ 842 Pending: []string{"VERIFY_RESIZE"}, 843 Target: []string{"ACTIVE"}, 844 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 845 Timeout: d.Timeout(schema.TimeoutUpdate), 846 Delay: 10 * time.Second, 847 MinTimeout: 3 * time.Second, 848 } 849 850 _, err = stateConf.WaitForState() 851 if err != nil { 852 return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err) 853 } 854 } 855 856 return resourceComputeInstanceV2Read(d, meta) 857 } 858 859 func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) error { 860 config := meta.(*Config) 861 computeClient, err := config.computeV2Client(GetRegion(d)) 862 if err != nil { 863 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 864 } 865 866 // Make sure all volumes are detached before deleting 867 volumes := d.Get("volume") 868 if volumeSet, ok := volumes.(*schema.Set); ok { 869 volumeList := volumeSet.List() 870 if len(volumeList) > 0 { 871 log.Printf("[DEBUG] Attempting to detach the following volumes: %#v", volumeList) 872 if blockClient, err := config.blockStorageV1Client(GetRegion(d)); err != nil { 873 return err 874 } else { 875 if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), volumeList); err != nil { 876 return err 877 } 878 } 879 } 880 } 881 882 if d.Get("stop_before_destroy").(bool) { 883 err = startstop.Stop(computeClient, d.Id()).ExtractErr() 884 if err != nil { 885 log.Printf("[WARN] Error stopping OpenStack instance: %s", err) 886 } else { 887 stopStateConf := &resource.StateChangeConf{ 888 Pending: []string{"ACTIVE"}, 889 Target: []string{"SHUTOFF"}, 890 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 891 Timeout: 3 * time.Minute, 892 Delay: 10 * time.Second, 893 MinTimeout: 3 * time.Second, 894 } 895 log.Printf("[DEBUG] Waiting for instance (%s) to stop", d.Id()) 896 _, err = stopStateConf.WaitForState() 897 if err != nil { 898 log.Printf("[WARN] Error waiting for instance (%s) to stop: %s, proceeding to delete", d.Id(), err) 899 } 900 } 901 } 902 903 if d.Get("force_delete").(bool) { 904 log.Printf("[DEBUG] Force deleting OpenStack Instance %s", d.Id()) 905 err = servers.ForceDelete(computeClient, d.Id()).ExtractErr() 906 if err != nil { 907 return fmt.Errorf("Error deleting OpenStack server: %s", err) 908 } 909 } else { 910 log.Printf("[DEBUG] Deleting OpenStack Instance %s", d.Id()) 911 err = servers.Delete(computeClient, d.Id()).ExtractErr() 912 if err != nil { 913 return fmt.Errorf("Error deleting OpenStack server: %s", err) 914 } 915 } 916 917 // Wait for the instance to delete before moving on. 918 log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id()) 919 920 stateConf := &resource.StateChangeConf{ 921 Pending: []string{"ACTIVE", "SHUTOFF"}, 922 Target: []string{"DELETED", "SOFT_DELETED"}, 923 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 924 Timeout: d.Timeout(schema.TimeoutDelete), 925 Delay: 10 * time.Second, 926 MinTimeout: 3 * time.Second, 927 } 928 929 _, err = stateConf.WaitForState() 930 if err != nil { 931 return fmt.Errorf( 932 "Error waiting for instance (%s) to delete: %s", 933 d.Id(), err) 934 } 935 936 d.SetId("") 937 return nil 938 } 939 940 // ServerV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 941 // an OpenStack instance. 942 func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc { 943 return func() (interface{}, string, error) { 944 s, err := servers.Get(client, instanceID).Extract() 945 if err != nil { 946 if _, ok := err.(gophercloud.ErrDefault404); ok { 947 return s, "DELETED", nil 948 } 949 return nil, "", err 950 } 951 952 return s, s.Status, nil 953 } 954 } 955 956 func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string { 957 rawSecGroups := d.Get("security_groups").(*schema.Set).List() 958 secgroups := make([]string, len(rawSecGroups)) 959 for i, raw := range rawSecGroups { 960 secgroups[i] = raw.(string) 961 } 962 return secgroups 963 } 964 965 // getInstanceNetworks collects instance network information from different sources 966 // and aggregates it all together. 967 func getInstanceNetworksAndAddresses(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) { 968 server, err := servers.Get(computeClient, d.Id()).Extract() 969 970 if err != nil { 971 return nil, CheckDeleted(d, err, "server") 972 } 973 974 networkDetails, err := getInstanceNetworks(computeClient, d) 975 addresses := getInstanceAddresses(server.Addresses) 976 if err != nil { 977 return nil, err 978 } 979 980 // if there are no networkDetails, make networks at least a length of 1 981 networkLength := 1 982 if len(networkDetails) > 0 { 983 networkLength = len(networkDetails) 984 } 985 networks := make([]map[string]interface{}, networkLength) 986 987 // Loop through all networks and addresses, 988 // merge relevant address details. 989 if len(networkDetails) == 0 { 990 for netName, n := range addresses { 991 networks[0] = map[string]interface{}{ 992 "name": netName, 993 "fixed_ip_v4": n["fixed_ip_v4"], 994 "fixed_ip_v6": n["fixed_ip_v6"], 995 "floating_ip": n["floating_ip"], 996 "mac": n["mac"], 997 } 998 } 999 } else { 1000 for i, net := range networkDetails { 1001 n := addresses[net["name"].(string)] 1002 1003 networks[i] = map[string]interface{}{ 1004 "uuid": networkDetails[i]["uuid"], 1005 "name": networkDetails[i]["name"], 1006 "port": networkDetails[i]["port"], 1007 "fixed_ip_v4": n["fixed_ip_v4"], 1008 "fixed_ip_v6": n["fixed_ip_v6"], 1009 "floating_ip": n["floating_ip"], 1010 "mac": n["mac"], 1011 "access_network": networkDetails[i]["access_network"], 1012 } 1013 } 1014 } 1015 1016 log.Printf("[DEBUG] networks: %+v", networks) 1017 1018 return networks, nil 1019 } 1020 1021 func getInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) { 1022 rawNetworks := d.Get("network").([]interface{}) 1023 newNetworks := make([]map[string]interface{}, 0, len(rawNetworks)) 1024 var tenantnet tenantnetworks.Network 1025 1026 tenantNetworkExt := true 1027 for _, raw := range rawNetworks { 1028 // Not sure what causes this, but it is a possibility (see GH-2323). 1029 // Since we call this function to reconcile what we'll save in the 1030 // state anyways, we just ignore it. 1031 if raw == nil { 1032 continue 1033 } 1034 1035 rawMap := raw.(map[string]interface{}) 1036 1037 // Both a floating IP and a port cannot be specified 1038 if fip, ok := rawMap["floating_ip"].(string); ok { 1039 if port, ok := rawMap["port"].(string); ok { 1040 if fip != "" && port != "" { 1041 return nil, fmt.Errorf("Only one of a floating IP or port may be specified per network.") 1042 } 1043 } 1044 } 1045 1046 allPages, err := tenantnetworks.List(computeClient).AllPages() 1047 if err != nil { 1048 if _, ok := err.(gophercloud.ErrDefault404); ok { 1049 log.Printf("[DEBUG] os-tenant-networks disabled") 1050 tenantNetworkExt = false 1051 } 1052 1053 log.Printf("[DEBUG] Err looks like: %+v", err) 1054 if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { 1055 if errCode.Actual == 403 { 1056 log.Printf("[DEBUG] os-tenant-networks disabled.") 1057 tenantNetworkExt = false 1058 } else { 1059 return nil, err 1060 } 1061 } 1062 } 1063 1064 networkID := "" 1065 networkName := "" 1066 if tenantNetworkExt { 1067 networkList, err := tenantnetworks.ExtractNetworks(allPages) 1068 if err != nil { 1069 return nil, err 1070 } 1071 1072 for _, network := range networkList { 1073 if network.Name == rawMap["name"] { 1074 tenantnet = network 1075 } 1076 if network.ID == rawMap["uuid"] { 1077 tenantnet = network 1078 } 1079 } 1080 1081 networkID = tenantnet.ID 1082 networkName = tenantnet.Name 1083 } else { 1084 networkID = rawMap["uuid"].(string) 1085 networkName = rawMap["name"].(string) 1086 } 1087 1088 newNetworks = append(newNetworks, map[string]interface{}{ 1089 "uuid": networkID, 1090 "name": networkName, 1091 "port": rawMap["port"].(string), 1092 "fixed_ip_v4": rawMap["fixed_ip_v4"].(string), 1093 "access_network": rawMap["access_network"].(bool), 1094 }) 1095 } 1096 1097 log.Printf("[DEBUG] networks: %+v", newNetworks) 1098 return newNetworks, nil 1099 } 1100 1101 func getInstanceAddresses(addresses map[string]interface{}) map[string]map[string]interface{} { 1102 addrs := make(map[string]map[string]interface{}) 1103 for n, networkAddresses := range addresses { 1104 addrs[n] = make(map[string]interface{}) 1105 for _, element := range networkAddresses.([]interface{}) { 1106 address := element.(map[string]interface{}) 1107 if address["OS-EXT-IPS:type"] == "floating" { 1108 addrs[n]["floating_ip"] = address["addr"] 1109 } else { 1110 if address["version"].(float64) == 4 { 1111 addrs[n]["fixed_ip_v4"] = address["addr"].(string) 1112 } else { 1113 addrs[n]["fixed_ip_v6"] = fmt.Sprintf("[%s]", address["addr"].(string)) 1114 } 1115 } 1116 if mac, ok := address["OS-EXT-IPS-MAC:mac_addr"]; ok { 1117 addrs[n]["mac"] = mac.(string) 1118 } 1119 } 1120 } 1121 1122 log.Printf("[DEBUG] Addresses: %+v", addresses) 1123 1124 return addrs 1125 } 1126 1127 func getInstanceAccessAddresses(d *schema.ResourceData, networks []map[string]interface{}) (string, string) { 1128 var hostv4, hostv6 string 1129 1130 // Start with a global floating IP 1131 floatingIP := d.Get("floating_ip").(string) 1132 if floatingIP != "" { 1133 hostv4 = floatingIP 1134 } 1135 1136 // Loop through all networks 1137 // If the network has a valid floating, fixed v4, or fixed v6 address 1138 // and hostv4 or hostv6 is not set, set hostv4/hostv6. 1139 // If the network is an "access_network" overwrite hostv4/hostv6. 1140 for _, n := range networks { 1141 var accessNetwork bool 1142 1143 if an, ok := n["access_network"].(bool); ok && an { 1144 accessNetwork = true 1145 } 1146 1147 if fixedIPv4, ok := n["fixed_ip_v4"].(string); ok && fixedIPv4 != "" { 1148 if hostv4 == "" || accessNetwork { 1149 hostv4 = fixedIPv4 1150 } 1151 } 1152 1153 if floatingIP, ok := n["floating_ip"].(string); ok && floatingIP != "" { 1154 if hostv4 == "" || accessNetwork { 1155 hostv4 = floatingIP 1156 } 1157 } 1158 1159 if fixedIPv6, ok := n["fixed_ip_v6"].(string); ok && fixedIPv6 != "" { 1160 if hostv6 == "" || accessNetwork { 1161 hostv6 = fixedIPv6 1162 } 1163 } 1164 } 1165 1166 log.Printf("[DEBUG] OpenStack Instance Network Access Addresses: %s, %s", hostv4, hostv6) 1167 1168 return hostv4, hostv6 1169 } 1170 1171 func checkInstanceFloatingIPs(d *schema.ResourceData) error { 1172 rawNetworks := d.Get("network").([]interface{}) 1173 floatingIP := d.Get("floating_ip").(string) 1174 1175 for _, raw := range rawNetworks { 1176 if raw == nil { 1177 continue 1178 } 1179 1180 rawMap := raw.(map[string]interface{}) 1181 1182 // Error if a floating IP was specified both globally and in the network block. 1183 if floatingIP != "" && rawMap["floating_ip"] != "" { 1184 return fmt.Errorf("Cannot specify a floating IP both globally and in a network block.") 1185 } 1186 } 1187 return nil 1188 } 1189 1190 func associateFloatingIPsToInstance(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) error { 1191 floatingIP := d.Get("floating_ip").(string) 1192 rawNetworks := d.Get("network").([]interface{}) 1193 instanceID := d.Id() 1194 1195 if floatingIP != "" { 1196 if err := associateFloatingIPToInstance(computeClient, floatingIP, instanceID, ""); err != nil { 1197 return err 1198 } 1199 } else { 1200 for _, raw := range rawNetworks { 1201 if raw == nil { 1202 continue 1203 } 1204 1205 rawMap := raw.(map[string]interface{}) 1206 if rawMap["floating_ip"].(string) != "" { 1207 floatingIP := rawMap["floating_ip"].(string) 1208 fixedIP := rawMap["fixed_ip_v4"].(string) 1209 if err := associateFloatingIPToInstance(computeClient, floatingIP, instanceID, fixedIP); err != nil { 1210 return err 1211 } 1212 } 1213 } 1214 } 1215 return nil 1216 } 1217 1218 func associateFloatingIPToInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error { 1219 associateOpts := floatingips.AssociateOpts{ 1220 FloatingIP: floatingIP, 1221 FixedIP: fixedIP, 1222 } 1223 1224 if err := floatingips.AssociateInstance(computeClient, instanceID, associateOpts).ExtractErr(); err != nil { 1225 return fmt.Errorf("Error associating floating IP: %s", err) 1226 } 1227 1228 return nil 1229 } 1230 1231 func disassociateFloatingIPFromInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error { 1232 disassociateOpts := floatingips.DisassociateOpts{ 1233 FloatingIP: floatingIP, 1234 } 1235 1236 if err := floatingips.DisassociateInstance(computeClient, instanceID, disassociateOpts).ExtractErr(); err != nil { 1237 return fmt.Errorf("Error disassociating floating IP: %s", err) 1238 } 1239 1240 return nil 1241 } 1242 1243 func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string { 1244 m := make(map[string]string) 1245 for key, val := range d.Get("metadata").(map[string]interface{}) { 1246 m[key] = val.(string) 1247 } 1248 return m 1249 } 1250 1251 func resourceInstanceBlockDevicesV2(d *schema.ResourceData, bds []interface{}) ([]bootfromvolume.BlockDevice, error) { 1252 blockDeviceOpts := make([]bootfromvolume.BlockDevice, len(bds)) 1253 for i, bd := range bds { 1254 bdM := bd.(map[string]interface{}) 1255 blockDeviceOpts[i] = bootfromvolume.BlockDevice{ 1256 UUID: bdM["uuid"].(string), 1257 VolumeSize: bdM["volume_size"].(int), 1258 BootIndex: bdM["boot_index"].(int), 1259 DeleteOnTermination: bdM["delete_on_termination"].(bool), 1260 GuestFormat: bdM["guest_format"].(string), 1261 } 1262 1263 sourceType := bdM["source_type"].(string) 1264 switch sourceType { 1265 case "blank": 1266 blockDeviceOpts[i].SourceType = bootfromvolume.SourceBlank 1267 case "image": 1268 blockDeviceOpts[i].SourceType = bootfromvolume.SourceImage 1269 case "snapshot": 1270 blockDeviceOpts[i].SourceType = bootfromvolume.SourceSnapshot 1271 case "volume": 1272 blockDeviceOpts[i].SourceType = bootfromvolume.SourceVolume 1273 default: 1274 return blockDeviceOpts, fmt.Errorf("unknown block device source type %s", sourceType) 1275 } 1276 1277 destinationType := bdM["destination_type"].(string) 1278 switch destinationType { 1279 case "local": 1280 blockDeviceOpts[i].DestinationType = bootfromvolume.DestinationLocal 1281 case "volume": 1282 blockDeviceOpts[i].DestinationType = bootfromvolume.DestinationVolume 1283 default: 1284 return blockDeviceOpts, fmt.Errorf("unknown block device destination type %s", destinationType) 1285 } 1286 } 1287 1288 log.Printf("[DEBUG] Block Device Options: %+v", blockDeviceOpts) 1289 return blockDeviceOpts, nil 1290 } 1291 1292 func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw map[string]interface{}) schedulerhints.SchedulerHints { 1293 differentHost := []string{} 1294 if len(schedulerHintsRaw["different_host"].([]interface{})) > 0 { 1295 for _, dh := range schedulerHintsRaw["different_host"].([]interface{}) { 1296 differentHost = append(differentHost, dh.(string)) 1297 } 1298 } 1299 1300 sameHost := []string{} 1301 if len(schedulerHintsRaw["same_host"].([]interface{})) > 0 { 1302 for _, sh := range schedulerHintsRaw["same_host"].([]interface{}) { 1303 sameHost = append(sameHost, sh.(string)) 1304 } 1305 } 1306 1307 query := make([]interface{}, len(schedulerHintsRaw["query"].([]interface{}))) 1308 if len(schedulerHintsRaw["query"].([]interface{})) > 0 { 1309 for _, q := range schedulerHintsRaw["query"].([]interface{}) { 1310 query = append(query, q.(string)) 1311 } 1312 } 1313 1314 schedulerHints := schedulerhints.SchedulerHints{ 1315 Group: schedulerHintsRaw["group"].(string), 1316 DifferentHost: differentHost, 1317 SameHost: sameHost, 1318 Query: query, 1319 TargetCell: schedulerHintsRaw["target_cell"].(string), 1320 BuildNearHostIP: schedulerHintsRaw["build_near_host_ip"].(string), 1321 } 1322 1323 return schedulerHints 1324 } 1325 1326 func getImageIDFromConfig(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { 1327 // If block_device was used, an Image does not need to be specified, unless an image/local 1328 // combination was used. This emulates normal boot behavior. Otherwise, ignore the image altogether. 1329 if vL, ok := d.GetOk("block_device"); ok { 1330 needImage := false 1331 for _, v := range vL.([]interface{}) { 1332 vM := v.(map[string]interface{}) 1333 if vM["source_type"] == "image" && vM["destination_type"] == "local" { 1334 needImage = true 1335 } 1336 } 1337 if !needImage { 1338 return "", nil 1339 } 1340 } 1341 1342 if imageId := d.Get("image_id").(string); imageId != "" { 1343 return imageId, nil 1344 } else { 1345 // try the OS_IMAGE_ID environment variable 1346 if v := os.Getenv("OS_IMAGE_ID"); v != "" { 1347 return v, nil 1348 } 1349 } 1350 1351 imageName := d.Get("image_name").(string) 1352 if imageName == "" { 1353 // try the OS_IMAGE_NAME environment variable 1354 if v := os.Getenv("OS_IMAGE_NAME"); v != "" { 1355 imageName = v 1356 } 1357 } 1358 1359 if imageName != "" { 1360 imageId, err := images.IDFromName(computeClient, imageName) 1361 if err != nil { 1362 return "", err 1363 } 1364 return imageId, nil 1365 } 1366 1367 return "", fmt.Errorf("Neither a boot device, image ID, or image name were able to be determined.") 1368 } 1369 1370 func setImageInformation(computeClient *gophercloud.ServiceClient, server *servers.Server, d *schema.ResourceData) error { 1371 // If block_device was used, an Image does not need to be specified, unless an image/local 1372 // combination was used. This emulates normal boot behavior. Otherwise, ignore the image altogether. 1373 if vL, ok := d.GetOk("block_device"); ok { 1374 needImage := false 1375 for _, v := range vL.([]interface{}) { 1376 vM := v.(map[string]interface{}) 1377 if vM["source_type"] == "image" && vM["destination_type"] == "local" { 1378 needImage = true 1379 } 1380 } 1381 if !needImage { 1382 d.Set("image_id", "Attempt to boot from volume - no image supplied") 1383 return nil 1384 } 1385 } 1386 1387 imageId := server.Image["id"].(string) 1388 if imageId != "" { 1389 d.Set("image_id", imageId) 1390 if image, err := images.Get(computeClient, imageId).Extract(); err != nil { 1391 if _, ok := err.(gophercloud.ErrDefault404); ok { 1392 // If the image name can't be found, set the value to "Image not found". 1393 // The most likely scenario is that the image no longer exists in the Image Service 1394 // but the instance still has a record from when it existed. 1395 d.Set("image_name", "Image not found") 1396 return nil 1397 } 1398 return err 1399 } else { 1400 d.Set("image_name", image.Name) 1401 } 1402 } 1403 1404 return nil 1405 } 1406 1407 func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { 1408 flavorId := d.Get("flavor_id").(string) 1409 1410 if flavorId != "" { 1411 return flavorId, nil 1412 } 1413 1414 flavorName := d.Get("flavor_name").(string) 1415 return flavors.IDFromName(client, flavorName) 1416 } 1417 1418 func resourceComputeVolumeAttachmentHash(v interface{}) int { 1419 var buf bytes.Buffer 1420 m := v.(map[string]interface{}) 1421 buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string))) 1422 1423 return hashcode.String(buf.String()) 1424 } 1425 1426 func resourceComputeSchedulerHintsHash(v interface{}) int { 1427 var buf bytes.Buffer 1428 m := v.(map[string]interface{}) 1429 1430 if m["group"] != nil { 1431 buf.WriteString(fmt.Sprintf("%s-", m["group"].(string))) 1432 } 1433 1434 if m["target_cell"] != nil { 1435 buf.WriteString(fmt.Sprintf("%s-", m["target_cell"].(string))) 1436 } 1437 1438 if m["build_host_near_ip"] != nil { 1439 buf.WriteString(fmt.Sprintf("%s-", m["build_host_near_ip"].(string))) 1440 } 1441 1442 buf.WriteString(fmt.Sprintf("%s-", m["different_host"].([]interface{}))) 1443 buf.WriteString(fmt.Sprintf("%s-", m["same_host"].([]interface{}))) 1444 buf.WriteString(fmt.Sprintf("%s-", m["query"].([]interface{}))) 1445 1446 return hashcode.String(buf.String()) 1447 } 1448 1449 func attachVolumesToInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { 1450 for _, v := range vols { 1451 va := v.(map[string]interface{}) 1452 volumeId := va["volume_id"].(string) 1453 device := va["device"].(string) 1454 1455 s := "" 1456 if serverId != "" { 1457 s = serverId 1458 } else if va["server_id"] != "" { 1459 s = va["server_id"].(string) 1460 } else { 1461 return fmt.Errorf("Unable to determine server ID to attach volume.") 1462 } 1463 1464 vaOpts := &volumeattach.CreateOpts{ 1465 Device: device, 1466 VolumeID: volumeId, 1467 } 1468 1469 if _, err := volumeattach.Create(computeClient, s, vaOpts).Extract(); err != nil { 1470 return err 1471 } 1472 1473 stateConf := &resource.StateChangeConf{ 1474 Pending: []string{"attaching", "available"}, 1475 Target: []string{"in-use"}, 1476 Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), 1477 Timeout: 30 * time.Minute, 1478 Delay: 5 * time.Second, 1479 MinTimeout: 2 * time.Second, 1480 } 1481 1482 if _, err := stateConf.WaitForState(); err != nil { 1483 return err 1484 } 1485 1486 log.Printf("[INFO] Attached volume %s to instance %s", volumeId, serverId) 1487 } 1488 return nil 1489 } 1490 1491 func detachVolumesFromInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { 1492 for _, v := range vols { 1493 va := v.(map[string]interface{}) 1494 aId := va["id"].(string) 1495 1496 log.Printf("[INFO] Attempting to detach volume %s", va["volume_id"]) 1497 if err := volumeattach.Delete(computeClient, serverId, aId).ExtractErr(); err != nil { 1498 return err 1499 } 1500 1501 stateConf := &resource.StateChangeConf{ 1502 Pending: []string{"detaching", "in-use"}, 1503 Target: []string{"available"}, 1504 Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), 1505 Timeout: 30 * time.Minute, 1506 Delay: 5 * time.Second, 1507 MinTimeout: 2 * time.Second, 1508 } 1509 1510 if _, err := stateConf.WaitForState(); err != nil { 1511 return err 1512 } 1513 log.Printf("[INFO] Detached volume %s from instance %s", va["volume_id"], serverId) 1514 } 1515 1516 return nil 1517 } 1518 1519 func getVolumeAttachments(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) error { 1520 var vols []map[string]interface{} 1521 1522 allPages, err := volumeattach.List(computeClient, d.Id()).AllPages() 1523 if err != nil { 1524 if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { 1525 if errCode.Actual == 403 { 1526 log.Printf("[DEBUG] os-volume_attachments disabled.") 1527 return nil 1528 } else { 1529 return err 1530 } 1531 } 1532 } 1533 1534 allVolumeAttachments, err := volumeattach.ExtractVolumeAttachments(allPages) 1535 if err != nil { 1536 return err 1537 } 1538 1539 if v, ok := d.GetOk("volume"); ok { 1540 volumes := v.(*schema.Set).List() 1541 for _, volume := range volumes { 1542 if volumeMap, ok := volume.(map[string]interface{}); ok { 1543 if v, ok := volumeMap["volume_id"].(string); ok { 1544 for _, volumeAttachment := range allVolumeAttachments { 1545 if v == volumeAttachment.ID { 1546 vol := make(map[string]interface{}) 1547 vol["id"] = volumeAttachment.ID 1548 vol["volume_id"] = volumeAttachment.VolumeID 1549 vol["device"] = volumeAttachment.Device 1550 vols = append(vols, vol) 1551 } 1552 } 1553 } 1554 } 1555 } 1556 } 1557 1558 log.Printf("[INFO] Volume attachments: %v", vols) 1559 d.Set("volume", vols) 1560 1561 return nil 1562 } 1563 1564 func checkBlockDeviceConfig(d *schema.ResourceData) error { 1565 if vL, ok := d.GetOk("block_device"); ok { 1566 for _, v := range vL.([]interface{}) { 1567 vM := v.(map[string]interface{}) 1568 1569 if vM["source_type"] != "blank" && vM["uuid"] == "" { 1570 return fmt.Errorf("You must specify a uuid for %s block device types", vM["source_type"]) 1571 } 1572 1573 if vM["source_type"] == "image" && vM["destination_type"] == "volume" { 1574 if vM["volume_size"] == 0 { 1575 return fmt.Errorf("You must specify a volume_size when creating a volume from an image") 1576 } 1577 } 1578 1579 if vM["source_type"] == "blank" && vM["destination_type"] == "local" { 1580 if vM["volume_size"] == 0 { 1581 return fmt.Errorf("You must specify a volume_size when creating a blank block device") 1582 } 1583 } 1584 } 1585 } 1586 1587 return nil 1588 } 1589 1590 func resourceComputeInstancePersonalityHash(v interface{}) int { 1591 var buf bytes.Buffer 1592 m := v.(map[string]interface{}) 1593 buf.WriteString(fmt.Sprintf("%s-", m["file"].(string))) 1594 1595 return hashcode.String(buf.String()) 1596 } 1597 1598 func resourceInstancePersonalityV2(d *schema.ResourceData) servers.Personality { 1599 var personalities servers.Personality 1600 1601 if v := d.Get("personality"); v != nil { 1602 personalityList := v.(*schema.Set).List() 1603 if len(personalityList) > 0 { 1604 for _, p := range personalityList { 1605 rawPersonality := p.(map[string]interface{}) 1606 file := servers.File{ 1607 Path: rawPersonality["file"].(string), 1608 Contents: []byte(rawPersonality["content"].(string)), 1609 } 1610 1611 log.Printf("[DEBUG] OpenStack Compute Instance Personality: %+v", file) 1612 1613 personalities = append(personalities, &file) 1614 } 1615 } 1616 } 1617 1618 return personalities 1619 }