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