github.com/ottenhoff/terraform@v0.7.0-rc1.0.20160607213102-ac2d195cc560/builtin/providers/openstack/resource_openstack_compute_instance_v2.go (about)

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