github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/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/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 "github.com/rackspace/gophercloud" 16 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" 17 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" 18 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" 19 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints" 20 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" 21 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop" 22 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/tenantnetworks" 23 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" 24 "github.com/rackspace/gophercloud/openstack/compute/v2/flavors" 25 "github.com/rackspace/gophercloud/openstack/compute/v2/images" 26 "github.com/rackspace/gophercloud/openstack/compute/v2/servers" 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(d.Get("region").(string)) 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 createOpts = &servers.CreateOpts{ 380 Name: d.Get("name").(string), 381 ImageRef: imageId, 382 FlavorRef: flavorId, 383 SecurityGroups: resourceInstanceSecGroupsV2(d), 384 AvailabilityZone: d.Get("availability_zone").(string), 385 Networks: networks, 386 Metadata: resourceInstanceMetadataV2(d), 387 ConfigDrive: d.Get("config_drive").(bool), 388 AdminPass: d.Get("admin_pass").(string), 389 UserData: []byte(d.Get("user_data").(string)), 390 Personality: resourceInstancePersonalityV2(d), 391 } 392 393 if keyName, ok := d.Get("key_pair").(string); ok && keyName != "" { 394 createOpts = &keypairs.CreateOptsExt{ 395 CreateOptsBuilder: createOpts, 396 KeyName: keyName, 397 } 398 } 399 400 if vL, ok := d.GetOk("block_device"); ok { 401 blockDevices := resourceInstanceBlockDevicesV2(d, vL.([]interface{})) 402 createOpts = &bootfromvolume.CreateOptsExt{ 403 CreateOptsBuilder: createOpts, 404 BlockDevice: blockDevices, 405 } 406 } 407 408 schedulerHintsRaw := d.Get("scheduler_hints").(*schema.Set).List() 409 if len(schedulerHintsRaw) > 0 { 410 log.Printf("[DEBUG] schedulerhints: %+v", schedulerHintsRaw) 411 schedulerHints := resourceInstanceSchedulerHintsV2(d, schedulerHintsRaw[0].(map[string]interface{})) 412 createOpts = &schedulerhints.CreateOptsExt{ 413 CreateOptsBuilder: createOpts, 414 SchedulerHints: schedulerHints, 415 } 416 } 417 418 log.Printf("[DEBUG] Create Options: %#v", createOpts) 419 420 // If a block_device is used, use the bootfromvolume.Create function as it allows an empty ImageRef. 421 // Otherwise, use the normal servers.Create function. 422 var server *servers.Server 423 if _, ok := d.GetOk("block_device"); ok { 424 server, err = bootfromvolume.Create(computeClient, createOpts).Extract() 425 } else { 426 server, err = servers.Create(computeClient, createOpts).Extract() 427 } 428 429 if err != nil { 430 return fmt.Errorf("Error creating OpenStack server: %s", err) 431 } 432 log.Printf("[INFO] Instance ID: %s", server.ID) 433 434 // Store the ID now 435 d.SetId(server.ID) 436 437 // Wait for the instance to become running so we can get some attributes 438 // that aren't available until later. 439 log.Printf( 440 "[DEBUG] Waiting for instance (%s) to become running", 441 server.ID) 442 443 stateConf := &resource.StateChangeConf{ 444 Pending: []string{"BUILD"}, 445 Target: []string{"ACTIVE"}, 446 Refresh: ServerV2StateRefreshFunc(computeClient, server.ID), 447 Timeout: 30 * time.Minute, 448 Delay: 10 * time.Second, 449 MinTimeout: 3 * time.Second, 450 } 451 452 _, err = stateConf.WaitForState() 453 if err != nil { 454 return fmt.Errorf( 455 "Error waiting for instance (%s) to become ready: %s", 456 server.ID, err) 457 } 458 459 // Now that the instance has been created, we need to do an early read on the 460 // networks in order to associate floating IPs 461 _, err = getInstanceNetworksAndAddresses(computeClient, d) 462 463 // If floating IPs were specified, associate them after the instance has launched. 464 err = associateFloatingIPsToInstance(computeClient, d) 465 if err != nil { 466 return err 467 } 468 469 // if volumes were specified, attach them after the instance has launched. 470 if v, ok := d.GetOk("volume"); ok { 471 vols := v.(*schema.Set).List() 472 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 473 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 474 } else { 475 if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil { 476 return err 477 } 478 } 479 } 480 481 return resourceComputeInstanceV2Read(d, meta) 482 } 483 484 func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) error { 485 config := meta.(*Config) 486 computeClient, err := config.computeV2Client(d.Get("region").(string)) 487 if err != nil { 488 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 489 } 490 491 server, err := servers.Get(computeClient, d.Id()).Extract() 492 if err != nil { 493 return CheckDeleted(d, err, "server") 494 } 495 496 log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server) 497 498 d.Set("name", server.Name) 499 500 // Get the instance network and address information 501 networks, err := getInstanceNetworksAndAddresses(computeClient, d) 502 if err != nil { 503 return err 504 } 505 506 // Determine the best IPv4 and IPv6 addresses to access the instance with 507 hostv4, hostv6 := getInstanceAccessAddresses(d, networks) 508 509 if server.AccessIPv4 != "" && hostv4 == "" { 510 hostv4 = server.AccessIPv4 511 } 512 513 if server.AccessIPv6 != "" && hostv6 == "" { 514 hostv6 = server.AccessIPv6 515 } 516 517 d.Set("network", networks) 518 d.Set("access_ip_v4", hostv4) 519 d.Set("access_ip_v6", hostv6) 520 521 // Determine the best IP address to use for SSH connectivity. 522 // Prefer IPv4 over IPv6. 523 preferredSSHAddress := "" 524 if hostv4 != "" { 525 preferredSSHAddress = hostv4 526 } else if hostv6 != "" { 527 preferredSSHAddress = hostv6 528 } 529 530 if preferredSSHAddress != "" { 531 // Initialize the connection info 532 d.SetConnInfo(map[string]string{ 533 "type": "ssh", 534 "host": preferredSSHAddress, 535 }) 536 } 537 538 d.Set("metadata", server.Metadata) 539 540 secGrpNames := []string{} 541 for _, sg := range server.SecurityGroups { 542 secGrpNames = append(secGrpNames, sg["name"].(string)) 543 } 544 d.Set("security_groups", secGrpNames) 545 546 flavorId, ok := server.Flavor["id"].(string) 547 if !ok { 548 return fmt.Errorf("Error setting OpenStack server's flavor: %v", server.Flavor) 549 } 550 d.Set("flavor_id", flavorId) 551 552 flavor, err := flavors.Get(computeClient, flavorId).Extract() 553 if err != nil { 554 return err 555 } 556 d.Set("flavor_name", flavor.Name) 557 558 // Set the instance's image information appropriately 559 if err := setImageInformation(computeClient, server, d); err != nil { 560 return err 561 } 562 563 // volume attachments 564 if err := getVolumeAttachments(computeClient, d); err != nil { 565 return err 566 } 567 568 return nil 569 } 570 571 func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) error { 572 config := meta.(*Config) 573 computeClient, err := config.computeV2Client(d.Get("region").(string)) 574 if err != nil { 575 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 576 } 577 578 var updateOpts servers.UpdateOpts 579 if d.HasChange("name") { 580 updateOpts.Name = d.Get("name").(string) 581 } 582 583 if updateOpts != (servers.UpdateOpts{}) { 584 _, err := servers.Update(computeClient, d.Id(), updateOpts).Extract() 585 if err != nil { 586 return fmt.Errorf("Error updating OpenStack server: %s", err) 587 } 588 } 589 590 if d.HasChange("metadata") { 591 var metadataOpts servers.MetadataOpts 592 metadataOpts = make(servers.MetadataOpts) 593 newMetadata := d.Get("metadata").(map[string]interface{}) 594 for k, v := range newMetadata { 595 metadataOpts[k] = v.(string) 596 } 597 598 _, err := servers.UpdateMetadata(computeClient, d.Id(), metadataOpts).Extract() 599 if err != nil { 600 return fmt.Errorf("Error updating OpenStack server (%s) metadata: %s", d.Id(), err) 601 } 602 } 603 604 if d.HasChange("security_groups") { 605 oldSGRaw, newSGRaw := d.GetChange("security_groups") 606 oldSGSet := oldSGRaw.(*schema.Set) 607 newSGSet := newSGRaw.(*schema.Set) 608 secgroupsToAdd := newSGSet.Difference(oldSGSet) 609 secgroupsToRemove := oldSGSet.Difference(newSGSet) 610 611 log.Printf("[DEBUG] Security groups to add: %v", secgroupsToAdd) 612 613 log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove) 614 615 for _, g := range secgroupsToRemove.List() { 616 err := secgroups.RemoveServerFromGroup(computeClient, d.Id(), g.(string)).ExtractErr() 617 if err != nil && err.Error() != "EOF" { 618 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 619 if !ok { 620 return fmt.Errorf("Error removing security group (%s) from OpenStack server (%s): %s", g, d.Id(), err) 621 } 622 if errCode.Actual == 404 { 623 continue 624 } else { 625 return fmt.Errorf("Error removing security group (%s) from OpenStack server (%s): %s", g, d.Id(), err) 626 } 627 } else { 628 log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g, d.Id()) 629 } 630 } 631 632 for _, g := range secgroupsToAdd.List() { 633 err := secgroups.AddServerToGroup(computeClient, d.Id(), g.(string)).ExtractErr() 634 if err != nil && err.Error() != "EOF" { 635 return fmt.Errorf("Error adding security group (%s) to OpenStack server (%s): %s", g, d.Id(), err) 636 } 637 log.Printf("[DEBUG] Added security group (%s) to instance (%s)", g, d.Id()) 638 } 639 } 640 641 if d.HasChange("admin_pass") { 642 if newPwd, ok := d.Get("admin_pass").(string); ok { 643 err := servers.ChangeAdminPassword(computeClient, d.Id(), newPwd).ExtractErr() 644 if err != nil { 645 return fmt.Errorf("Error changing admin password of OpenStack server (%s): %s", d.Id(), err) 646 } 647 } 648 } 649 650 if d.HasChange("floating_ip") { 651 oldFIP, newFIP := d.GetChange("floating_ip") 652 log.Printf("[DEBUG] Old Floating IP: %v", oldFIP) 653 log.Printf("[DEBUG] New Floating IP: %v", newFIP) 654 if oldFIP.(string) != "" { 655 log.Printf("[DEBUG] Attempting to disassociate %s from %s", oldFIP, d.Id()) 656 if err := disassociateFloatingIPFromInstance(computeClient, oldFIP.(string), d.Id(), ""); err != nil { 657 return fmt.Errorf("Error disassociating Floating IP during update: %s", err) 658 } 659 } 660 661 if newFIP.(string) != "" { 662 log.Printf("[DEBUG] Attempting to associate %s to %s", newFIP, d.Id()) 663 if err := associateFloatingIPToInstance(computeClient, newFIP.(string), d.Id(), ""); err != nil { 664 return fmt.Errorf("Error associating Floating IP during update: %s", err) 665 } 666 } 667 } 668 669 if d.HasChange("network") { 670 oldNetworks, newNetworks := d.GetChange("network") 671 oldNetworkList := oldNetworks.([]interface{}) 672 newNetworkList := newNetworks.([]interface{}) 673 for i, oldNet := range oldNetworkList { 674 var oldFIP, newFIP string 675 var oldFixedIP, newFixedIP string 676 677 if oldNetRaw, ok := oldNet.(map[string]interface{}); ok { 678 oldFIP = oldNetRaw["floating_ip"].(string) 679 oldFixedIP = oldNetRaw["fixed_ip_v4"].(string) 680 } 681 682 if len(newNetworkList) > i { 683 if newNetRaw, ok := newNetworkList[i].(map[string]interface{}); ok { 684 newFIP = newNetRaw["floating_ip"].(string) 685 newFixedIP = newNetRaw["fixed_ip_v4"].(string) 686 } 687 } 688 689 // Only changes to the floating IP are supported 690 if oldFIP != "" && oldFIP != newFIP { 691 log.Printf("[DEBUG] Attempting to disassociate %s from %s", oldFIP, d.Id()) 692 if err := disassociateFloatingIPFromInstance(computeClient, oldFIP, d.Id(), oldFixedIP); err != nil { 693 return fmt.Errorf("Error disassociating Floating IP during update: %s", err) 694 } 695 } 696 697 if newFIP != "" && oldFIP != newFIP { 698 log.Printf("[DEBUG] Attempting to associate %s to %s", newFIP, d.Id()) 699 if err := associateFloatingIPToInstance(computeClient, newFIP, d.Id(), newFixedIP); err != nil { 700 return fmt.Errorf("Error associating Floating IP during update: %s", err) 701 } 702 } 703 } 704 } 705 706 if d.HasChange("volume") { 707 // old attachments and new attachments 708 oldAttachments, newAttachments := d.GetChange("volume") 709 // for each old attachment, detach the volume 710 oldAttachmentSet := oldAttachments.(*schema.Set).List() 711 712 log.Printf("[DEBUG] Attempting to detach the following volumes: %#v", oldAttachmentSet) 713 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 714 return err 715 } else { 716 if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), oldAttachmentSet); err != nil { 717 return err 718 } 719 } 720 721 // for each new attachment, attach the volume 722 newAttachmentSet := newAttachments.(*schema.Set).List() 723 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 724 return err 725 } else { 726 if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), newAttachmentSet); err != nil { 727 return err 728 } 729 } 730 731 d.SetPartial("volume") 732 } 733 734 if d.HasChange("flavor_id") || d.HasChange("flavor_name") { 735 var newFlavorId string 736 var err error 737 if d.HasChange("flavor_id") { 738 newFlavorId = d.Get("flavor_id").(string) 739 } else { 740 newFlavorName := d.Get("flavor_name").(string) 741 newFlavorId, err = flavors.IDFromName(computeClient, newFlavorName) 742 if err != nil { 743 return err 744 } 745 } 746 747 resizeOpts := &servers.ResizeOpts{ 748 FlavorRef: newFlavorId, 749 } 750 log.Printf("[DEBUG] Resize configuration: %#v", resizeOpts) 751 err = servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr() 752 if err != nil { 753 return fmt.Errorf("Error resizing OpenStack server: %s", err) 754 } 755 756 // Wait for the instance to finish resizing. 757 log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id()) 758 759 stateConf := &resource.StateChangeConf{ 760 Pending: []string{"RESIZE"}, 761 Target: []string{"VERIFY_RESIZE"}, 762 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 763 Timeout: 30 * time.Minute, 764 Delay: 10 * time.Second, 765 MinTimeout: 3 * time.Second, 766 } 767 768 _, err = stateConf.WaitForState() 769 if err != nil { 770 return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err) 771 } 772 773 // Confirm resize. 774 log.Printf("[DEBUG] Confirming resize") 775 err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr() 776 if err != nil { 777 return fmt.Errorf("Error confirming resize of OpenStack server: %s", err) 778 } 779 780 stateConf = &resource.StateChangeConf{ 781 Pending: []string{"VERIFY_RESIZE"}, 782 Target: []string{"ACTIVE"}, 783 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 784 Timeout: 30 * time.Minute, 785 Delay: 10 * time.Second, 786 MinTimeout: 3 * time.Second, 787 } 788 789 _, err = stateConf.WaitForState() 790 if err != nil { 791 return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err) 792 } 793 } 794 795 return resourceComputeInstanceV2Read(d, meta) 796 } 797 798 func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) error { 799 config := meta.(*Config) 800 computeClient, err := config.computeV2Client(d.Get("region").(string)) 801 if err != nil { 802 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 803 } 804 805 // Make sure all volumes are detached before deleting 806 volumes := d.Get("volume") 807 if volumeSet, ok := volumes.(*schema.Set); ok { 808 volumeList := volumeSet.List() 809 if len(volumeList) > 0 { 810 log.Printf("[DEBUG] Attempting to detach the following volumes: %#v", volumeList) 811 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 812 return err 813 } else { 814 if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), volumeList); err != nil { 815 return err 816 } 817 } 818 } 819 } 820 821 if d.Get("stop_before_destroy").(bool) { 822 err = startstop.Stop(computeClient, d.Id()).ExtractErr() 823 if err != nil { 824 log.Printf("[WARN] Error stopping OpenStack instance: %s", err) 825 } else { 826 stopStateConf := &resource.StateChangeConf{ 827 Pending: []string{"ACTIVE"}, 828 Target: []string{"SHUTOFF"}, 829 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 830 Timeout: 3 * time.Minute, 831 Delay: 10 * time.Second, 832 MinTimeout: 3 * time.Second, 833 } 834 log.Printf("[DEBUG] Waiting for instance (%s) to stop", d.Id()) 835 _, err = stopStateConf.WaitForState() 836 if err != nil { 837 log.Printf("[WARN] Error waiting for instance (%s) to stop: %s, proceeding to delete", d.Id(), err) 838 } 839 } 840 } 841 842 err = servers.Delete(computeClient, d.Id()).ExtractErr() 843 if err != nil { 844 return fmt.Errorf("Error deleting OpenStack server: %s", err) 845 } 846 847 // Wait for the instance to delete before moving on. 848 log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id()) 849 850 stateConf := &resource.StateChangeConf{ 851 Pending: []string{"ACTIVE", "SHUTOFF"}, 852 Target: []string{"DELETED"}, 853 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 854 Timeout: 30 * time.Minute, 855 Delay: 10 * time.Second, 856 MinTimeout: 3 * time.Second, 857 } 858 859 _, err = stateConf.WaitForState() 860 if err != nil { 861 return fmt.Errorf( 862 "Error waiting for instance (%s) to delete: %s", 863 d.Id(), err) 864 } 865 866 d.SetId("") 867 return nil 868 } 869 870 // ServerV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 871 // an OpenStack instance. 872 func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc { 873 return func() (interface{}, string, error) { 874 s, err := servers.Get(client, instanceID).Extract() 875 if err != nil { 876 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 877 if !ok { 878 return nil, "", err 879 } 880 if errCode.Actual == 404 { 881 return s, "DELETED", nil 882 } 883 return nil, "", err 884 } 885 886 return s, s.Status, nil 887 } 888 } 889 890 func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string { 891 rawSecGroups := d.Get("security_groups").(*schema.Set).List() 892 secgroups := make([]string, len(rawSecGroups)) 893 for i, raw := range rawSecGroups { 894 secgroups[i] = raw.(string) 895 } 896 return secgroups 897 } 898 899 // getInstanceNetworks collects instance network information from different sources 900 // and aggregates it all together. 901 func getInstanceNetworksAndAddresses(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) { 902 server, err := servers.Get(computeClient, d.Id()).Extract() 903 904 if err != nil { 905 return nil, CheckDeleted(d, err, "server") 906 } 907 908 networkDetails, err := getInstanceNetworks(computeClient, d) 909 addresses := getInstanceAddresses(server.Addresses) 910 if err != nil { 911 return nil, err 912 } 913 914 // if there are no networkDetails, make networks at least a length of 1 915 networkLength := 1 916 if len(networkDetails) > 0 { 917 networkLength = len(networkDetails) 918 } 919 networks := make([]map[string]interface{}, networkLength) 920 921 // Loop through all networks and addresses, 922 // merge relevant address details. 923 if len(networkDetails) == 0 { 924 for netName, n := range addresses { 925 networks[0] = map[string]interface{}{ 926 "name": netName, 927 "fixed_ip_v4": n["fixed_ip_v4"], 928 "fixed_ip_v6": n["fixed_ip_v6"], 929 "floating_ip": n["floating_ip"], 930 "mac": n["mac"], 931 } 932 } 933 } else { 934 for i, net := range networkDetails { 935 n := addresses[net["name"].(string)] 936 937 networks[i] = map[string]interface{}{ 938 "uuid": networkDetails[i]["uuid"], 939 "name": networkDetails[i]["name"], 940 "port": networkDetails[i]["port"], 941 "fixed_ip_v4": n["fixed_ip_v4"], 942 "fixed_ip_v6": n["fixed_ip_v6"], 943 "floating_ip": n["floating_ip"], 944 "mac": n["mac"], 945 "access_network": networkDetails[i]["access_network"], 946 } 947 } 948 } 949 950 log.Printf("[DEBUG] networks: %+v", networks) 951 952 return networks, nil 953 } 954 955 func getInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) { 956 rawNetworks := d.Get("network").([]interface{}) 957 newNetworks := make([]map[string]interface{}, 0, len(rawNetworks)) 958 var tenantnet tenantnetworks.Network 959 960 tenantNetworkExt := true 961 for _, raw := range rawNetworks { 962 // Not sure what causes this, but it is a possibility (see GH-2323). 963 // Since we call this function to reconcile what we'll save in the 964 // state anyways, we just ignore it. 965 if raw == nil { 966 continue 967 } 968 969 rawMap := raw.(map[string]interface{}) 970 971 allPages, err := tenantnetworks.List(computeClient).AllPages() 972 if err != nil { 973 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 974 if !ok { 975 return nil, err 976 } 977 978 if errCode.Actual == 404 || errCode.Actual == 403 { 979 tenantNetworkExt = false 980 } else { 981 return nil, err 982 } 983 } 984 985 networkID := "" 986 networkName := "" 987 if tenantNetworkExt { 988 networkList, err := tenantnetworks.ExtractNetworks(allPages) 989 if err != nil { 990 return nil, err 991 } 992 993 for _, network := range networkList { 994 if network.Name == rawMap["name"] { 995 tenantnet = network 996 } 997 if network.ID == rawMap["uuid"] { 998 tenantnet = network 999 } 1000 } 1001 1002 networkID = tenantnet.ID 1003 networkName = tenantnet.Name 1004 } else { 1005 networkID = rawMap["uuid"].(string) 1006 networkName = rawMap["name"].(string) 1007 } 1008 1009 newNetworks = append(newNetworks, map[string]interface{}{ 1010 "uuid": networkID, 1011 "name": networkName, 1012 "port": rawMap["port"].(string), 1013 "fixed_ip_v4": rawMap["fixed_ip_v4"].(string), 1014 "access_network": rawMap["access_network"].(bool), 1015 }) 1016 } 1017 1018 log.Printf("[DEBUG] networks: %+v", newNetworks) 1019 return newNetworks, nil 1020 } 1021 1022 func getInstanceAddresses(addresses map[string]interface{}) map[string]map[string]interface{} { 1023 addrs := make(map[string]map[string]interface{}) 1024 for n, networkAddresses := range addresses { 1025 addrs[n] = make(map[string]interface{}) 1026 for _, element := range networkAddresses.([]interface{}) { 1027 address := element.(map[string]interface{}) 1028 if address["OS-EXT-IPS:type"] == "floating" { 1029 addrs[n]["floating_ip"] = address["addr"] 1030 } else { 1031 if address["version"].(float64) == 4 { 1032 addrs[n]["fixed_ip_v4"] = address["addr"].(string) 1033 } else { 1034 addrs[n]["fixed_ip_v6"] = fmt.Sprintf("[%s]", address["addr"].(string)) 1035 } 1036 } 1037 if mac, ok := address["OS-EXT-IPS-MAC:mac_addr"]; ok { 1038 addrs[n]["mac"] = mac.(string) 1039 } 1040 } 1041 } 1042 1043 log.Printf("[DEBUG] Addresses: %+v", addresses) 1044 1045 return addrs 1046 } 1047 1048 func getInstanceAccessAddresses(d *schema.ResourceData, networks []map[string]interface{}) (string, string) { 1049 var hostv4, hostv6 string 1050 1051 // Start with a global floating IP 1052 floatingIP := d.Get("floating_ip").(string) 1053 if floatingIP != "" { 1054 hostv4 = floatingIP 1055 } 1056 1057 // Loop through all networks 1058 // If the network has a valid floating, fixed v4, or fixed v6 address 1059 // and hostv4 or hostv6 is not set, set hostv4/hostv6. 1060 // If the network is an "access_network" overwrite hostv4/hostv6. 1061 for _, n := range networks { 1062 var accessNetwork bool 1063 1064 if an, ok := n["access_network"].(bool); ok && an { 1065 accessNetwork = true 1066 } 1067 1068 if fixedIPv4, ok := n["fixed_ip_v4"].(string); ok && fixedIPv4 != "" { 1069 if hostv4 == "" || accessNetwork { 1070 hostv4 = fixedIPv4 1071 } 1072 } 1073 1074 if floatingIP, ok := n["floating_ip"].(string); ok && floatingIP != "" { 1075 if hostv4 == "" || accessNetwork { 1076 hostv4 = floatingIP 1077 } 1078 } 1079 1080 if fixedIPv6, ok := n["fixed_ip_v6"].(string); ok && fixedIPv6 != "" { 1081 if hostv6 == "" || accessNetwork { 1082 hostv6 = fixedIPv6 1083 } 1084 } 1085 } 1086 1087 log.Printf("[DEBUG] OpenStack Instance Network Access Addresses: %s, %s", hostv4, hostv6) 1088 1089 return hostv4, hostv6 1090 } 1091 1092 func checkInstanceFloatingIPs(d *schema.ResourceData) error { 1093 rawNetworks := d.Get("network").([]interface{}) 1094 floatingIP := d.Get("floating_ip").(string) 1095 1096 for _, raw := range rawNetworks { 1097 if raw == nil { 1098 continue 1099 } 1100 1101 rawMap := raw.(map[string]interface{}) 1102 1103 // Error if a floating IP was specified both globally and in the network block. 1104 if floatingIP != "" && rawMap["floating_ip"] != "" { 1105 return fmt.Errorf("Cannot specify a floating IP both globally and in a network block.") 1106 } 1107 } 1108 return nil 1109 } 1110 1111 func associateFloatingIPsToInstance(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) error { 1112 floatingIP := d.Get("floating_ip").(string) 1113 rawNetworks := d.Get("network").([]interface{}) 1114 instanceID := d.Id() 1115 1116 if floatingIP != "" { 1117 if err := associateFloatingIPToInstance(computeClient, floatingIP, instanceID, ""); err != nil { 1118 return err 1119 } 1120 } else { 1121 for _, raw := range rawNetworks { 1122 if raw == nil { 1123 continue 1124 } 1125 1126 rawMap := raw.(map[string]interface{}) 1127 if rawMap["floating_ip"].(string) != "" { 1128 floatingIP := rawMap["floating_ip"].(string) 1129 fixedIP := rawMap["fixed_ip_v4"].(string) 1130 if err := associateFloatingIPToInstance(computeClient, floatingIP, instanceID, fixedIP); err != nil { 1131 return err 1132 } 1133 } 1134 } 1135 } 1136 return nil 1137 } 1138 1139 func associateFloatingIPToInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error { 1140 associateOpts := floatingip.AssociateOpts{ 1141 ServerID: instanceID, 1142 FloatingIP: floatingIP, 1143 FixedIP: fixedIP, 1144 } 1145 1146 if err := floatingip.AssociateInstance(computeClient, associateOpts).ExtractErr(); err != nil { 1147 return fmt.Errorf("Error associating floating IP: %s", err) 1148 } 1149 1150 return nil 1151 } 1152 1153 func disassociateFloatingIPFromInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error { 1154 associateOpts := floatingip.AssociateOpts{ 1155 ServerID: instanceID, 1156 FloatingIP: floatingIP, 1157 FixedIP: fixedIP, 1158 } 1159 1160 if err := floatingip.DisassociateInstance(computeClient, associateOpts).ExtractErr(); err != nil { 1161 return fmt.Errorf("Error disassociating floating IP: %s", err) 1162 } 1163 1164 return nil 1165 } 1166 1167 func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string { 1168 m := make(map[string]string) 1169 for key, val := range d.Get("metadata").(map[string]interface{}) { 1170 m[key] = val.(string) 1171 } 1172 return m 1173 } 1174 1175 func resourceInstanceBlockDevicesV2(d *schema.ResourceData, bds []interface{}) []bootfromvolume.BlockDevice { 1176 blockDeviceOpts := make([]bootfromvolume.BlockDevice, len(bds)) 1177 for i, bd := range bds { 1178 bdM := bd.(map[string]interface{}) 1179 sourceType := bootfromvolume.SourceType(bdM["source_type"].(string)) 1180 blockDeviceOpts[i] = bootfromvolume.BlockDevice{ 1181 UUID: bdM["uuid"].(string), 1182 SourceType: sourceType, 1183 VolumeSize: bdM["volume_size"].(int), 1184 DestinationType: bdM["destination_type"].(string), 1185 BootIndex: bdM["boot_index"].(int), 1186 DeleteOnTermination: bdM["delete_on_termination"].(bool), 1187 GuestFormat: bdM["guest_format"].(string), 1188 } 1189 } 1190 1191 log.Printf("[DEBUG] Block Device Options: %+v", blockDeviceOpts) 1192 return blockDeviceOpts 1193 } 1194 1195 func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw map[string]interface{}) schedulerhints.SchedulerHints { 1196 differentHost := []string{} 1197 if len(schedulerHintsRaw["different_host"].([]interface{})) > 0 { 1198 for _, dh := range schedulerHintsRaw["different_host"].([]interface{}) { 1199 differentHost = append(differentHost, dh.(string)) 1200 } 1201 } 1202 1203 sameHost := []string{} 1204 if len(schedulerHintsRaw["same_host"].([]interface{})) > 0 { 1205 for _, sh := range schedulerHintsRaw["same_host"].([]interface{}) { 1206 sameHost = append(sameHost, sh.(string)) 1207 } 1208 } 1209 1210 query := make([]interface{}, len(schedulerHintsRaw["query"].([]interface{}))) 1211 if len(schedulerHintsRaw["query"].([]interface{})) > 0 { 1212 for _, q := range schedulerHintsRaw["query"].([]interface{}) { 1213 query = append(query, q.(string)) 1214 } 1215 } 1216 1217 schedulerHints := schedulerhints.SchedulerHints{ 1218 Group: schedulerHintsRaw["group"].(string), 1219 DifferentHost: differentHost, 1220 SameHost: sameHost, 1221 Query: query, 1222 TargetCell: schedulerHintsRaw["target_cell"].(string), 1223 BuildNearHostIP: schedulerHintsRaw["build_near_host_ip"].(string), 1224 } 1225 1226 return schedulerHints 1227 } 1228 1229 func getImageIDFromConfig(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { 1230 // If block_device was used, an Image does not need to be specified, unless an image/local 1231 // combination was used. This emulates normal boot behavior. Otherwise, ignore the image altogether. 1232 if vL, ok := d.GetOk("block_device"); ok { 1233 needImage := false 1234 for _, v := range vL.([]interface{}) { 1235 vM := v.(map[string]interface{}) 1236 if vM["source_type"] == "image" && vM["destination_type"] == "local" { 1237 needImage = true 1238 } 1239 } 1240 if !needImage { 1241 return "", nil 1242 } 1243 } 1244 1245 if imageId := d.Get("image_id").(string); imageId != "" { 1246 return imageId, nil 1247 } else { 1248 // try the OS_IMAGE_ID environment variable 1249 if v := os.Getenv("OS_IMAGE_ID"); v != "" { 1250 return v, nil 1251 } 1252 } 1253 1254 imageName := d.Get("image_name").(string) 1255 if imageName == "" { 1256 // try the OS_IMAGE_NAME environment variable 1257 if v := os.Getenv("OS_IMAGE_NAME"); v != "" { 1258 imageName = v 1259 } 1260 } 1261 1262 if imageName != "" { 1263 imageId, err := images.IDFromName(computeClient, imageName) 1264 if err != nil { 1265 return "", err 1266 } 1267 return imageId, nil 1268 } 1269 1270 return "", fmt.Errorf("Neither a boot device, image ID, or image name were able to be determined.") 1271 } 1272 1273 func setImageInformation(computeClient *gophercloud.ServiceClient, server *servers.Server, d *schema.ResourceData) error { 1274 // If block_device was used, an Image does not need to be specified, unless an image/local 1275 // combination was used. This emulates normal boot behavior. Otherwise, ignore the image altogether. 1276 if vL, ok := d.GetOk("block_device"); ok { 1277 needImage := false 1278 for _, v := range vL.([]interface{}) { 1279 vM := v.(map[string]interface{}) 1280 if vM["source_type"] == "image" && vM["destination_type"] == "local" { 1281 needImage = true 1282 } 1283 } 1284 if !needImage { 1285 d.Set("image_id", "Attempt to boot from volume - no image supplied") 1286 return nil 1287 } 1288 } 1289 1290 imageId := server.Image["id"].(string) 1291 if imageId != "" { 1292 d.Set("image_id", imageId) 1293 if image, err := images.Get(computeClient, imageId).Extract(); err != nil { 1294 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 1295 if !ok { 1296 return err 1297 } 1298 if errCode.Actual == 404 { 1299 // If the image name can't be found, set the value to "Image not found". 1300 // The most likely scenario is that the image no longer exists in the Image Service 1301 // but the instance still has a record from when it existed. 1302 d.Set("image_name", "Image not found") 1303 return nil 1304 } else { 1305 return err 1306 } 1307 } else { 1308 d.Set("image_name", image.Name) 1309 } 1310 } 1311 1312 return nil 1313 } 1314 1315 func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { 1316 flavorId := d.Get("flavor_id").(string) 1317 1318 if flavorId != "" { 1319 return flavorId, nil 1320 } 1321 1322 flavorName := d.Get("flavor_name").(string) 1323 return flavors.IDFromName(client, flavorName) 1324 } 1325 1326 func resourceComputeVolumeAttachmentHash(v interface{}) int { 1327 var buf bytes.Buffer 1328 m := v.(map[string]interface{}) 1329 buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string))) 1330 1331 return hashcode.String(buf.String()) 1332 } 1333 1334 func resourceComputeSchedulerHintsHash(v interface{}) int { 1335 var buf bytes.Buffer 1336 m := v.(map[string]interface{}) 1337 1338 if m["group"] != nil { 1339 buf.WriteString(fmt.Sprintf("%s-", m["group"].(string))) 1340 } 1341 1342 if m["target_cell"] != nil { 1343 buf.WriteString(fmt.Sprintf("%s-", m["target_cell"].(string))) 1344 } 1345 1346 if m["build_host_near_ip"] != nil { 1347 buf.WriteString(fmt.Sprintf("%s-", m["build_host_near_ip"].(string))) 1348 } 1349 1350 buf.WriteString(fmt.Sprintf("%s-", m["different_host"].([]interface{}))) 1351 buf.WriteString(fmt.Sprintf("%s-", m["same_host"].([]interface{}))) 1352 buf.WriteString(fmt.Sprintf("%s-", m["query"].([]interface{}))) 1353 1354 return hashcode.String(buf.String()) 1355 } 1356 1357 func attachVolumesToInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { 1358 for _, v := range vols { 1359 va := v.(map[string]interface{}) 1360 volumeId := va["volume_id"].(string) 1361 device := va["device"].(string) 1362 1363 s := "" 1364 if serverId != "" { 1365 s = serverId 1366 } else if va["server_id"] != "" { 1367 s = va["server_id"].(string) 1368 } else { 1369 return fmt.Errorf("Unable to determine server ID to attach volume.") 1370 } 1371 1372 vaOpts := &volumeattach.CreateOpts{ 1373 Device: device, 1374 VolumeID: volumeId, 1375 } 1376 1377 if _, err := volumeattach.Create(computeClient, s, vaOpts).Extract(); err != nil { 1378 return err 1379 } 1380 1381 stateConf := &resource.StateChangeConf{ 1382 Pending: []string{"attaching", "available"}, 1383 Target: []string{"in-use"}, 1384 Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), 1385 Timeout: 30 * time.Minute, 1386 Delay: 5 * time.Second, 1387 MinTimeout: 2 * time.Second, 1388 } 1389 1390 if _, err := stateConf.WaitForState(); err != nil { 1391 return err 1392 } 1393 1394 log.Printf("[INFO] Attached volume %s to instance %s", volumeId, serverId) 1395 } 1396 return nil 1397 } 1398 1399 func detachVolumesFromInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { 1400 for _, v := range vols { 1401 va := v.(map[string]interface{}) 1402 aId := va["id"].(string) 1403 1404 log.Printf("[INFO] Attempting to detach volume %s", va["volume_id"]) 1405 if err := volumeattach.Delete(computeClient, serverId, aId).ExtractErr(); err != nil { 1406 return err 1407 } 1408 1409 stateConf := &resource.StateChangeConf{ 1410 Pending: []string{"detaching", "in-use"}, 1411 Target: []string{"available"}, 1412 Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), 1413 Timeout: 30 * time.Minute, 1414 Delay: 5 * time.Second, 1415 MinTimeout: 2 * time.Second, 1416 } 1417 1418 if _, err := stateConf.WaitForState(); err != nil { 1419 return err 1420 } 1421 log.Printf("[INFO] Detached volume %s from instance %s", va["volume_id"], serverId) 1422 } 1423 1424 return nil 1425 } 1426 1427 func getVolumeAttachments(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) error { 1428 var vols []map[string]interface{} 1429 1430 allPages, err := volumeattach.List(computeClient, d.Id()).AllPages() 1431 if err != nil { 1432 return err 1433 } 1434 1435 allVolumeAttachments, err := volumeattach.ExtractVolumeAttachments(allPages) 1436 if err != nil { 1437 return err 1438 } 1439 1440 if v, ok := d.GetOk("volume"); ok { 1441 volumes := v.(*schema.Set).List() 1442 for _, volume := range volumes { 1443 if volumeMap, ok := volume.(map[string]interface{}); ok { 1444 if v, ok := volumeMap["volume_id"].(string); ok { 1445 for _, volumeAttachment := range allVolumeAttachments { 1446 if v == volumeAttachment.ID { 1447 vol := make(map[string]interface{}) 1448 vol["id"] = volumeAttachment.ID 1449 vol["volume_id"] = volumeAttachment.VolumeID 1450 vol["device"] = volumeAttachment.Device 1451 vols = append(vols, vol) 1452 } 1453 } 1454 } 1455 } 1456 } 1457 } 1458 1459 log.Printf("[INFO] Volume attachments: %v", vols) 1460 d.Set("volume", vols) 1461 1462 return nil 1463 } 1464 1465 func checkBlockDeviceConfig(d *schema.ResourceData) error { 1466 if vL, ok := d.GetOk("block_device"); ok { 1467 for _, v := range vL.([]interface{}) { 1468 vM := v.(map[string]interface{}) 1469 1470 if vM["source_type"] != "blank" && vM["uuid"] == "" { 1471 return fmt.Errorf("You must specify a uuid for %s block device types", vM["source_type"]) 1472 } 1473 1474 if vM["source_type"] == "image" && vM["destination_type"] == "volume" { 1475 if vM["volume_size"] == 0 { 1476 return fmt.Errorf("You must specify a volume_size when creating a volume from an image") 1477 } 1478 } 1479 1480 if vM["source_type"] == "blank" && vM["destination_type"] == "local" { 1481 if vM["volume_size"] == 0 { 1482 return fmt.Errorf("You must specify a volume_size when creating a blank block device") 1483 } 1484 } 1485 } 1486 } 1487 1488 return nil 1489 } 1490 1491 func resourceComputeInstancePersonalityHash(v interface{}) int { 1492 var buf bytes.Buffer 1493 m := v.(map[string]interface{}) 1494 buf.WriteString(fmt.Sprintf("%s-", m["file"].(string))) 1495 1496 return hashcode.String(buf.String()) 1497 } 1498 1499 func resourceInstancePersonalityV2(d *schema.ResourceData) servers.Personality { 1500 var personalities servers.Personality 1501 1502 if v := d.Get("personality"); v != nil { 1503 personalityList := v.(*schema.Set).List() 1504 if len(personalityList) > 0 { 1505 for _, p := range personalityList { 1506 rawPersonality := p.(map[string]interface{}) 1507 file := servers.File{ 1508 Path: rawPersonality["file"].(string), 1509 Contents: []byte(rawPersonality["content"].(string)), 1510 } 1511 1512 log.Printf("[DEBUG] OpenStack Compute Instance Personality: %+v", file) 1513 1514 personalities = append(personalities, &file) 1515 } 1516 } 1517 } 1518 1519 return personalities 1520 }