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