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