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