github.com/gabrielperezs/terraform@v0.7.0-rc2.0.20160715084931-f7da2612946f/builtin/providers/openstack/resource_openstack_compute_instance_v2.go (about)

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