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