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