github.com/btobolaski/terraform@v0.6.4-0.20150928030114-0c3f2a915c02/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  	"time"
    10  
    11  	"github.com/hashicorp/terraform/helper/hashcode"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  	"github.com/rackspace/gophercloud"
    15  	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
    16  	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
    17  	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
    18  	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints"
    19  	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups"
    20  	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
    21  	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
    22  	"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
    23  	"github.com/rackspace/gophercloud/openstack/compute/v2/images"
    24  	"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
    25  	"github.com/rackspace/gophercloud/pagination"
    26  )
    27  
    28  func resourceComputeInstanceV2() *schema.Resource {
    29  	return &schema.Resource{
    30  		Create: resourceComputeInstanceV2Create,
    31  		Read:   resourceComputeInstanceV2Read,
    32  		Update: resourceComputeInstanceV2Update,
    33  		Delete: resourceComputeInstanceV2Delete,
    34  
    35  		Schema: map[string]*schema.Schema{
    36  			"region": &schema.Schema{
    37  				Type:        schema.TypeString,
    38  				Required:    true,
    39  				ForceNew:    true,
    40  				DefaultFunc: envDefaultFuncAllowMissing("OS_REGION_NAME"),
    41  			},
    42  			"name": &schema.Schema{
    43  				Type:     schema.TypeString,
    44  				Required: true,
    45  				ForceNew: false,
    46  			},
    47  			"image_id": &schema.Schema{
    48  				Type:        schema.TypeString,
    49  				Optional:    true,
    50  				ForceNew:    true,
    51  				Computed:    true,
    52  				DefaultFunc: envDefaultFunc("OS_IMAGE_ID"),
    53  			},
    54  			"image_name": &schema.Schema{
    55  				Type:        schema.TypeString,
    56  				Optional:    true,
    57  				ForceNew:    true,
    58  				Computed:    true,
    59  				DefaultFunc: envDefaultFunc("OS_IMAGE_NAME"),
    60  			},
    61  			"flavor_id": &schema.Schema{
    62  				Type:        schema.TypeString,
    63  				Optional:    true,
    64  				ForceNew:    false,
    65  				Computed:    true,
    66  				DefaultFunc: envDefaultFunc("OS_FLAVOR_ID"),
    67  			},
    68  			"flavor_name": &schema.Schema{
    69  				Type:        schema.TypeString,
    70  				Optional:    true,
    71  				ForceNew:    false,
    72  				Computed:    true,
    73  				DefaultFunc: envDefaultFunc("OS_FLAVOR_NAME"),
    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.TypeList,
    97  				Optional: true,
    98  				ForceNew: false,
    99  				Elem:     &schema.Schema{Type: schema.TypeString},
   100  			},
   101  			"availability_zone": &schema.Schema{
   102  				Type:     schema.TypeString,
   103  				Optional: true,
   104  				ForceNew: true,
   105  			},
   106  			"network": &schema.Schema{
   107  				Type:     schema.TypeList,
   108  				Optional: true,
   109  				ForceNew: true,
   110  				Computed: true,
   111  				Elem: &schema.Resource{
   112  					Schema: map[string]*schema.Schema{
   113  						"uuid": &schema.Schema{
   114  							Type:     schema.TypeString,
   115  							Optional: true,
   116  							Computed: true,
   117  						},
   118  						"name": &schema.Schema{
   119  							Type:     schema.TypeString,
   120  							Optional: true,
   121  							Computed: true,
   122  						},
   123  						"port": &schema.Schema{
   124  							Type:     schema.TypeString,
   125  							Optional: true,
   126  							Computed: true,
   127  						},
   128  						"fixed_ip_v4": &schema.Schema{
   129  							Type:     schema.TypeString,
   130  							Optional: true,
   131  							Computed: true,
   132  						},
   133  						"fixed_ip_v6": &schema.Schema{
   134  							Type:     schema.TypeString,
   135  							Optional: true,
   136  							Computed: true,
   137  						},
   138  						"mac": &schema.Schema{
   139  							Type:     schema.TypeString,
   140  							Computed: true,
   141  						},
   142  					},
   143  				},
   144  			},
   145  			"metadata": &schema.Schema{
   146  				Type:     schema.TypeMap,
   147  				Optional: true,
   148  				ForceNew: false,
   149  			},
   150  			"config_drive": &schema.Schema{
   151  				Type:     schema.TypeBool,
   152  				Optional: true,
   153  				ForceNew: true,
   154  			},
   155  			"admin_pass": &schema.Schema{
   156  				Type:     schema.TypeString,
   157  				Optional: true,
   158  				ForceNew: false,
   159  			},
   160  			"access_ip_v4": &schema.Schema{
   161  				Type:     schema.TypeString,
   162  				Computed: true,
   163  				Optional: true,
   164  				ForceNew: false,
   165  			},
   166  			"access_ip_v6": &schema.Schema{
   167  				Type:     schema.TypeString,
   168  				Computed: true,
   169  				Optional: true,
   170  				ForceNew: false,
   171  			},
   172  			"key_pair": &schema.Schema{
   173  				Type:     schema.TypeString,
   174  				Optional: true,
   175  				ForceNew: true,
   176  			},
   177  			"block_device": &schema.Schema{
   178  				Type:     schema.TypeList,
   179  				Optional: true,
   180  				ForceNew: true,
   181  				Elem: &schema.Resource{
   182  					Schema: map[string]*schema.Schema{
   183  						"uuid": &schema.Schema{
   184  							Type:     schema.TypeString,
   185  							Required: true,
   186  						},
   187  						"source_type": &schema.Schema{
   188  							Type:     schema.TypeString,
   189  							Required: true,
   190  						},
   191  						"volume_size": &schema.Schema{
   192  							Type:     schema.TypeInt,
   193  							Optional: true,
   194  						},
   195  						"destination_type": &schema.Schema{
   196  							Type:     schema.TypeString,
   197  							Optional: true,
   198  						},
   199  						"boot_index": &schema.Schema{
   200  							Type:     schema.TypeInt,
   201  							Optional: true,
   202  						},
   203  					},
   204  				},
   205  			},
   206  			"volume": &schema.Schema{
   207  				Type:     schema.TypeSet,
   208  				Optional: true,
   209  				Elem: &schema.Resource{
   210  					Schema: map[string]*schema.Schema{
   211  						"id": &schema.Schema{
   212  							Type:     schema.TypeString,
   213  							Computed: true,
   214  						},
   215  						"volume_id": &schema.Schema{
   216  							Type:     schema.TypeString,
   217  							Required: true,
   218  						},
   219  						"device": &schema.Schema{
   220  							Type:     schema.TypeString,
   221  							Optional: true,
   222  							Computed: true,
   223  						},
   224  					},
   225  				},
   226  				Set: resourceComputeVolumeAttachmentHash,
   227  			},
   228  			"scheduler_hints": &schema.Schema{
   229  				Type:     schema.TypeSet,
   230  				Optional: true,
   231  				Elem: &schema.Resource{
   232  					Schema: map[string]*schema.Schema{
   233  						"group": &schema.Schema{
   234  							Type:     schema.TypeString,
   235  							Optional: true,
   236  							ForceNew: true,
   237  						},
   238  						"different_host": &schema.Schema{
   239  							Type:     schema.TypeList,
   240  							Optional: true,
   241  							ForceNew: true,
   242  							Elem:     &schema.Schema{Type: schema.TypeString},
   243  						},
   244  						"same_host": &schema.Schema{
   245  							Type:     schema.TypeList,
   246  							Optional: true,
   247  							ForceNew: true,
   248  							Elem:     &schema.Schema{Type: schema.TypeString},
   249  						},
   250  						"query": &schema.Schema{
   251  							Type:     schema.TypeList,
   252  							Optional: true,
   253  							ForceNew: true,
   254  							Elem:     &schema.Schema{Type: schema.TypeString},
   255  						},
   256  						"target_cell": &schema.Schema{
   257  							Type:     schema.TypeString,
   258  							Optional: true,
   259  							ForceNew: true,
   260  						},
   261  						"build_near_host_ip": &schema.Schema{
   262  							Type:     schema.TypeString,
   263  							Optional: true,
   264  							ForceNew: true,
   265  						},
   266  					},
   267  				},
   268  				Set: resourceComputeSchedulerHintsHash,
   269  			},
   270  		},
   271  	}
   272  }
   273  
   274  func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) error {
   275  	config := meta.(*Config)
   276  	computeClient, err := config.computeV2Client(d.Get("region").(string))
   277  	if err != nil {
   278  		return fmt.Errorf("Error creating OpenStack compute client: %s", err)
   279  	}
   280  
   281  	var createOpts servers.CreateOptsBuilder
   282  
   283  	imageId, err := getImageID(computeClient, d)
   284  	if err != nil {
   285  		return err
   286  	}
   287  
   288  	flavorId, err := getFlavorID(computeClient, d)
   289  	if err != nil {
   290  		return err
   291  	}
   292  
   293  	networkDetails, err := resourceInstanceNetworks(computeClient, d)
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	networks := make([]servers.Network, len(networkDetails))
   299  	for i, net := range networkDetails {
   300  		networks[i] = servers.Network{
   301  			UUID:    net["uuid"].(string),
   302  			Port:    net["port"].(string),
   303  			FixedIP: net["fixed_ip_v4"].(string),
   304  		}
   305  	}
   306  
   307  	createOpts = &servers.CreateOpts{
   308  		Name:             d.Get("name").(string),
   309  		ImageRef:         imageId,
   310  		FlavorRef:        flavorId,
   311  		SecurityGroups:   resourceInstanceSecGroupsV2(d),
   312  		AvailabilityZone: d.Get("availability_zone").(string),
   313  		Networks:         networks,
   314  		Metadata:         resourceInstanceMetadataV2(d),
   315  		ConfigDrive:      d.Get("config_drive").(bool),
   316  		AdminPass:        d.Get("admin_pass").(string),
   317  		UserData:         []byte(d.Get("user_data").(string)),
   318  	}
   319  
   320  	if keyName, ok := d.Get("key_pair").(string); ok && keyName != "" {
   321  		createOpts = &keypairs.CreateOptsExt{
   322  			createOpts,
   323  			keyName,
   324  		}
   325  	}
   326  
   327  	if blockDeviceRaw, ok := d.Get("block_device").(map[string]interface{}); ok && blockDeviceRaw != nil {
   328  		blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw)
   329  		createOpts = &bootfromvolume.CreateOptsExt{
   330  			createOpts,
   331  			blockDevice,
   332  		}
   333  	}
   334  
   335  	schedulerHintsRaw := d.Get("scheduler_hints").(*schema.Set).List()
   336  	if len(schedulerHintsRaw) > 0 {
   337  		log.Printf("[DEBUG] schedulerhints: %+v", schedulerHintsRaw)
   338  		schedulerHints := resourceInstanceSchedulerHintsV2(d, schedulerHintsRaw[0].(map[string]interface{}))
   339  		createOpts = &schedulerhints.CreateOptsExt{
   340  			createOpts,
   341  			schedulerHints,
   342  		}
   343  	}
   344  
   345  	log.Printf("[DEBUG] Create Options: %#v", createOpts)
   346  	server, err := servers.Create(computeClient, createOpts).Extract()
   347  	if err != nil {
   348  		return fmt.Errorf("Error creating OpenStack server: %s", err)
   349  	}
   350  	log.Printf("[INFO] Instance ID: %s", server.ID)
   351  
   352  	// Store the ID now
   353  	d.SetId(server.ID)
   354  
   355  	// Wait for the instance to become running so we can get some attributes
   356  	// that aren't available until later.
   357  	log.Printf(
   358  		"[DEBUG] Waiting for instance (%s) to become running",
   359  		server.ID)
   360  
   361  	stateConf := &resource.StateChangeConf{
   362  		Pending:    []string{"BUILD"},
   363  		Target:     "ACTIVE",
   364  		Refresh:    ServerV2StateRefreshFunc(computeClient, server.ID),
   365  		Timeout:    10 * time.Minute,
   366  		Delay:      10 * time.Second,
   367  		MinTimeout: 3 * time.Second,
   368  	}
   369  
   370  	_, err = stateConf.WaitForState()
   371  	if err != nil {
   372  		return fmt.Errorf(
   373  			"Error waiting for instance (%s) to become ready: %s",
   374  			server.ID, err)
   375  	}
   376  	floatingIP := d.Get("floating_ip").(string)
   377  	if floatingIP != "" {
   378  		if err := floatingip.Associate(computeClient, server.ID, floatingIP).ExtractErr(); err != nil {
   379  			return fmt.Errorf("Error associating floating IP: %s", err)
   380  		}
   381  	}
   382  
   383  	// were volume attachments specified?
   384  	if v := d.Get("volume"); v != nil {
   385  		vols := v.(*schema.Set).List()
   386  		if len(vols) > 0 {
   387  			if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil {
   388  				return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   389  			} else {
   390  				if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil {
   391  					return err
   392  				}
   393  			}
   394  		}
   395  	}
   396  
   397  	return resourceComputeInstanceV2Read(d, meta)
   398  }
   399  
   400  func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) error {
   401  	config := meta.(*Config)
   402  	computeClient, err := config.computeV2Client(d.Get("region").(string))
   403  	if err != nil {
   404  		return fmt.Errorf("Error creating OpenStack compute client: %s", err)
   405  	}
   406  
   407  	server, err := servers.Get(computeClient, d.Id()).Extract()
   408  	if err != nil {
   409  		return CheckDeleted(d, err, "server")
   410  	}
   411  
   412  	log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server)
   413  
   414  	d.Set("name", server.Name)
   415  
   416  	// begin reading the network configuration
   417  	d.Set("access_ip_v4", server.AccessIPv4)
   418  	d.Set("access_ip_v6", server.AccessIPv6)
   419  	hostv4 := server.AccessIPv4
   420  	hostv6 := server.AccessIPv6
   421  
   422  	networkDetails, err := resourceInstanceNetworks(computeClient, d)
   423  	addresses := resourceInstanceAddresses(server.Addresses)
   424  	if err != nil {
   425  		return err
   426  	}
   427  
   428  	// if there are no networkDetails, make networks at least a length of 1
   429  	networkLength := 1
   430  	if len(networkDetails) > 0 {
   431  		networkLength = len(networkDetails)
   432  	}
   433  	networks := make([]map[string]interface{}, networkLength)
   434  
   435  	// Loop through all networks and addresses,
   436  	// merge relevant address details.
   437  	if len(networkDetails) == 0 {
   438  		for netName, n := range addresses {
   439  			if floatingIP, ok := n["floating_ip"]; ok {
   440  				hostv4 = floatingIP.(string)
   441  			} else {
   442  				if hostv4 == "" && n["fixed_ip_v4"] != nil {
   443  					hostv4 = n["fixed_ip_v4"].(string)
   444  				}
   445  			}
   446  
   447  			if hostv6 == "" && n["fixed_ip_v6"] != nil {
   448  				hostv6 = n["fixed_ip_v6"].(string)
   449  			}
   450  
   451  			networks[0] = map[string]interface{}{
   452  				"name":        netName,
   453  				"fixed_ip_v4": n["fixed_ip_v4"],
   454  				"fixed_ip_v6": n["fixed_ip_v6"],
   455  				"mac":         n["mac"],
   456  			}
   457  		}
   458  	} else {
   459  		for i, net := range networkDetails {
   460  			n := addresses[net["name"].(string)]
   461  
   462  			if floatingIP, ok := n["floating_ip"]; ok {
   463  				hostv4 = floatingIP.(string)
   464  			} else {
   465  				if hostv4 == "" && n["fixed_ip_v4"] != nil {
   466  					hostv4 = n["fixed_ip_v4"].(string)
   467  				}
   468  			}
   469  
   470  			if hostv6 == "" && n["fixed_ip_v6"] != nil {
   471  				hostv6 = n["fixed_ip_v6"].(string)
   472  			}
   473  
   474  			networks[i] = map[string]interface{}{
   475  				"uuid":        networkDetails[i]["uuid"],
   476  				"name":        networkDetails[i]["name"],
   477  				"port":        networkDetails[i]["port"],
   478  				"fixed_ip_v4": n["fixed_ip_v4"],
   479  				"fixed_ip_v6": n["fixed_ip_v6"],
   480  				"mac":         n["mac"],
   481  			}
   482  		}
   483  	}
   484  
   485  	log.Printf("[DEBUG] new networks: %+v", networks)
   486  
   487  	d.Set("network", networks)
   488  	d.Set("access_ip_v4", hostv4)
   489  	d.Set("access_ip_v6", hostv6)
   490  	log.Printf("hostv4: %s", hostv4)
   491  	log.Printf("hostv6: %s", hostv6)
   492  
   493  	// prefer the v6 address if no v4 address exists.
   494  	preferredv := ""
   495  	if hostv4 != "" {
   496  		preferredv = hostv4
   497  	} else if hostv6 != "" {
   498  		preferredv = hostv6
   499  	}
   500  
   501  	if preferredv != "" {
   502  		// Initialize the connection info
   503  		d.SetConnInfo(map[string]string{
   504  			"type": "ssh",
   505  			"host": preferredv,
   506  		})
   507  	}
   508  	// end network configuration
   509  
   510  	d.Set("metadata", server.Metadata)
   511  
   512  	secGrpNames := []string{}
   513  	for _, sg := range server.SecurityGroups {
   514  		secGrpNames = append(secGrpNames, sg["name"].(string))
   515  	}
   516  	d.Set("security_groups", secGrpNames)
   517  
   518  	flavorId, ok := server.Flavor["id"].(string)
   519  	if !ok {
   520  		return fmt.Errorf("Error setting OpenStack server's flavor: %v", server.Flavor)
   521  	}
   522  	d.Set("flavor_id", flavorId)
   523  
   524  	flavor, err := flavors.Get(computeClient, flavorId).Extract()
   525  	if err != nil {
   526  		return err
   527  	}
   528  	d.Set("flavor_name", flavor.Name)
   529  
   530  	imageId, ok := server.Image["id"].(string)
   531  	if !ok {
   532  		return fmt.Errorf("Error setting OpenStack server's image: %v", server.Image)
   533  	}
   534  	d.Set("image_id", imageId)
   535  
   536  	image, err := images.Get(computeClient, imageId).Extract()
   537  	if err != nil {
   538  		return err
   539  	}
   540  	d.Set("image_name", image.Name)
   541  
   542  	// volume attachments
   543  	vas, err := getVolumeAttachments(computeClient, d.Id())
   544  	if err != nil {
   545  		return err
   546  	}
   547  	if len(vas) > 0 {
   548  		attachments := make([]map[string]interface{}, len(vas))
   549  		for i, attachment := range vas {
   550  			attachments[i] = make(map[string]interface{})
   551  			attachments[i]["id"] = attachment.ID
   552  			attachments[i]["volume_id"] = attachment.VolumeID
   553  			attachments[i]["device"] = attachment.Device
   554  		}
   555  		log.Printf("[INFO] Volume attachments: %v", attachments)
   556  		d.Set("volume", attachments)
   557  	}
   558  
   559  	return nil
   560  }
   561  
   562  func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) error {
   563  	config := meta.(*Config)
   564  	computeClient, err := config.computeV2Client(d.Get("region").(string))
   565  	if err != nil {
   566  		return fmt.Errorf("Error creating OpenStack compute client: %s", err)
   567  	}
   568  
   569  	var updateOpts servers.UpdateOpts
   570  	if d.HasChange("name") {
   571  		updateOpts.Name = d.Get("name").(string)
   572  	}
   573  	if d.HasChange("access_ip_v4") {
   574  		updateOpts.AccessIPv4 = d.Get("access_ip_v4").(string)
   575  	}
   576  	if d.HasChange("access_ip_v6") {
   577  		updateOpts.AccessIPv4 = d.Get("access_ip_v6").(string)
   578  	}
   579  
   580  	if updateOpts != (servers.UpdateOpts{}) {
   581  		_, err := servers.Update(computeClient, d.Id(), updateOpts).Extract()
   582  		if err != nil {
   583  			return fmt.Errorf("Error updating OpenStack server: %s", err)
   584  		}
   585  	}
   586  
   587  	if d.HasChange("metadata") {
   588  		var metadataOpts servers.MetadataOpts
   589  		metadataOpts = make(servers.MetadataOpts)
   590  		newMetadata := d.Get("metadata").(map[string]interface{})
   591  		for k, v := range newMetadata {
   592  			metadataOpts[k] = v.(string)
   593  		}
   594  
   595  		_, err := servers.UpdateMetadata(computeClient, d.Id(), metadataOpts).Extract()
   596  		if err != nil {
   597  			return fmt.Errorf("Error updating OpenStack server (%s) metadata: %s", d.Id(), err)
   598  		}
   599  	}
   600  
   601  	if d.HasChange("security_groups") {
   602  		oldSGRaw, newSGRaw := d.GetChange("security_groups")
   603  		oldSGSlice, newSGSlice := oldSGRaw.([]interface{}), newSGRaw.([]interface{})
   604  		oldSGSet := schema.NewSet(func(v interface{}) int { return hashcode.String(v.(string)) }, oldSGSlice)
   605  		newSGSet := schema.NewSet(func(v interface{}) int { return hashcode.String(v.(string)) }, newSGSlice)
   606  		secgroupsToAdd := newSGSet.Difference(oldSGSet)
   607  		secgroupsToRemove := oldSGSet.Difference(newSGSet)
   608  
   609  		log.Printf("[DEBUG] Security groups to add: %v", secgroupsToAdd)
   610  
   611  		log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove)
   612  
   613  
   614  		for _, g := range secgroupsToRemove.List() {
   615  			err := secgroups.RemoveServerFromGroup(computeClient, d.Id(), g.(string)).ExtractErr()
   616  			if err != nil {
   617  				errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
   618  				if !ok {
   619  					return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err)
   620  				}
   621  				if errCode.Actual == 404 {
   622  					continue
   623  				} else {
   624  					return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err)
   625  				}
   626  			} else {
   627  				log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g.(string), d.Id())
   628  			}
   629  		}
   630  		for _, g := range secgroupsToAdd.List() {
   631  			err := secgroups.AddServerToGroup(computeClient, d.Id(), g.(string)).ExtractErr()
   632  			if err != nil {
   633  				return fmt.Errorf("Error adding security group to OpenStack server (%s): %s", d.Id(), err)
   634  			}
   635  			log.Printf("[DEBUG] Added security group (%s) to instance (%s)", g.(string), d.Id())
   636  		}
   637  
   638  	}
   639  
   640  	if d.HasChange("admin_pass") {
   641  		if newPwd, ok := d.Get("admin_pass").(string); ok {
   642  			err := servers.ChangeAdminPassword(computeClient, d.Id(), newPwd).ExtractErr()
   643  			if err != nil {
   644  				return fmt.Errorf("Error changing admin password of OpenStack server (%s): %s", d.Id(), err)
   645  			}
   646  		}
   647  	}
   648  
   649  	if d.HasChange("floating_ip") {
   650  		oldFIP, newFIP := d.GetChange("floating_ip")
   651  		log.Printf("[DEBUG] Old Floating IP: %v", oldFIP)
   652  		log.Printf("[DEBUG] New Floating IP: %v", newFIP)
   653  		if oldFIP.(string) != "" {
   654  			log.Printf("[DEBUG] Attemping to disassociate %s from %s", oldFIP, d.Id())
   655  			if err := floatingip.Disassociate(computeClient, d.Id(), oldFIP.(string)).ExtractErr(); err != nil {
   656  				return fmt.Errorf("Error disassociating Floating IP during update: %s", err)
   657  			}
   658  		}
   659  
   660  		if newFIP.(string) != "" {
   661  			log.Printf("[DEBUG] Attemping to associate %s to %s", newFIP, d.Id())
   662  			if err := floatingip.Associate(computeClient, d.Id(), newFIP.(string)).ExtractErr(); err != nil {
   663  				return fmt.Errorf("Error associating Floating IP during update: %s", err)
   664  			}
   665  		}
   666  	}
   667  
   668  	if d.HasChange("volume") {
   669  		// old attachments and new attachments
   670  		oldAttachments, newAttachments := d.GetChange("volume")
   671  
   672  		// for each old attachment, detach the volume
   673  		oldAttachmentSet := oldAttachments.(*schema.Set).List()
   674  		if len(oldAttachmentSet) > 0 {
   675  			if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil {
   676  				return err
   677  			} else {
   678  				if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), oldAttachmentSet); err != nil {
   679  					return err
   680  				}
   681  			}
   682  		}
   683  
   684  		// for each new attachment, attach the volume
   685  		newAttachmentSet := newAttachments.(*schema.Set).List()
   686  		if len(newAttachmentSet) > 0 {
   687  			if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil {
   688  				return err
   689  			} else {
   690  				if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), newAttachmentSet); err != nil {
   691  					return err
   692  				}
   693  			}
   694  		}
   695  
   696  		d.SetPartial("volume")
   697  	}
   698  
   699  	if d.HasChange("flavor_id") || d.HasChange("flavor_name") {
   700  		flavorId, err := getFlavorID(computeClient, d)
   701  		if err != nil {
   702  			return err
   703  		}
   704  		resizeOpts := &servers.ResizeOpts{
   705  			FlavorRef: flavorId,
   706  		}
   707  		log.Printf("[DEBUG] Resize configuration: %#v", resizeOpts)
   708  		err = servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr()
   709  		if err != nil {
   710  			return fmt.Errorf("Error resizing OpenStack server: %s", err)
   711  		}
   712  
   713  		// Wait for the instance to finish resizing.
   714  		log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id())
   715  
   716  		stateConf := &resource.StateChangeConf{
   717  			Pending:    []string{"RESIZE"},
   718  			Target:     "VERIFY_RESIZE",
   719  			Refresh:    ServerV2StateRefreshFunc(computeClient, d.Id()),
   720  			Timeout:    3 * time.Minute,
   721  			Delay:      10 * time.Second,
   722  			MinTimeout: 3 * time.Second,
   723  		}
   724  
   725  		_, err = stateConf.WaitForState()
   726  		if err != nil {
   727  			return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err)
   728  		}
   729  
   730  		// Confirm resize.
   731  		log.Printf("[DEBUG] Confirming resize")
   732  		err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr()
   733  		if err != nil {
   734  			return fmt.Errorf("Error confirming resize of OpenStack server: %s", err)
   735  		}
   736  
   737  		stateConf = &resource.StateChangeConf{
   738  			Pending:    []string{"VERIFY_RESIZE"},
   739  			Target:     "ACTIVE",
   740  			Refresh:    ServerV2StateRefreshFunc(computeClient, d.Id()),
   741  			Timeout:    3 * time.Minute,
   742  			Delay:      10 * time.Second,
   743  			MinTimeout: 3 * time.Second,
   744  		}
   745  
   746  		_, err = stateConf.WaitForState()
   747  		if err != nil {
   748  			return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err)
   749  		}
   750  	}
   751  
   752  	return resourceComputeInstanceV2Read(d, meta)
   753  }
   754  
   755  func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) error {
   756  	config := meta.(*Config)
   757  	computeClient, err := config.computeV2Client(d.Get("region").(string))
   758  	if err != nil {
   759  		return fmt.Errorf("Error creating OpenStack compute client: %s", err)
   760  	}
   761  
   762  	err = servers.Delete(computeClient, d.Id()).ExtractErr()
   763  	if err != nil {
   764  		return fmt.Errorf("Error deleting OpenStack server: %s", err)
   765  	}
   766  
   767  	// Wait for the instance to delete before moving on.
   768  	log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id())
   769  
   770  	stateConf := &resource.StateChangeConf{
   771  		Pending:    []string{"ACTIVE"},
   772  		Target:     "DELETED",
   773  		Refresh:    ServerV2StateRefreshFunc(computeClient, d.Id()),
   774  		Timeout:    10 * time.Minute,
   775  		Delay:      10 * time.Second,
   776  		MinTimeout: 3 * time.Second,
   777  	}
   778  
   779  	_, err = stateConf.WaitForState()
   780  	if err != nil {
   781  		return fmt.Errorf(
   782  			"Error waiting for instance (%s) to delete: %s",
   783  			d.Id(), err)
   784  	}
   785  
   786  	d.SetId("")
   787  	return nil
   788  }
   789  
   790  // ServerV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   791  // an OpenStack instance.
   792  func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc {
   793  	return func() (interface{}, string, error) {
   794  		s, err := servers.Get(client, instanceID).Extract()
   795  		if err != nil {
   796  			errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
   797  			if !ok {
   798  				return nil, "", err
   799  			}
   800  			if errCode.Actual == 404 {
   801  				return s, "DELETED", nil
   802  			}
   803  			return nil, "", err
   804  		}
   805  
   806  		return s, s.Status, nil
   807  	}
   808  }
   809  
   810  func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string {
   811  	rawSecGroups := d.Get("security_groups").([]interface{})
   812  	secgroups := make([]string, len(rawSecGroups))
   813  	for i, raw := range rawSecGroups {
   814  		secgroups[i] = raw.(string)
   815  	}
   816  	return secgroups
   817  }
   818  
   819  func resourceInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) {
   820  	rawNetworks := d.Get("network").([]interface{})
   821  	newNetworks := make([]map[string]interface{}, 0, len(rawNetworks))
   822  	var tenantnet tenantnetworks.Network
   823  
   824  	tenantNetworkExt := true
   825  	for _, raw := range rawNetworks {
   826  		// Not sure what causes this, but it is a possibility (see GH-2323).
   827  		// Since we call this function to reconcile what we'll save in the
   828  		// state anyways, we just ignore it.
   829  		if raw == nil {
   830  			continue
   831  		}
   832  
   833  		rawMap := raw.(map[string]interface{})
   834  		allPages, err := tenantnetworks.List(computeClient).AllPages()
   835  		if err != nil {
   836  			errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
   837  			if !ok {
   838  				return nil, err
   839  			}
   840  
   841  			if errCode.Actual == 404 {
   842  				tenantNetworkExt = false
   843  			} else {
   844  				return nil, err
   845  			}
   846  		}
   847  
   848  		networkID := ""
   849  		networkName := ""
   850  		if tenantNetworkExt {
   851  			networkList, err := tenantnetworks.ExtractNetworks(allPages)
   852  			if err != nil {
   853  				return nil, err
   854  			}
   855  
   856  			for _, network := range networkList {
   857  				if network.Name == rawMap["name"] {
   858  					tenantnet = network
   859  				}
   860  				if network.ID == rawMap["uuid"] {
   861  					tenantnet = network
   862  				}
   863  			}
   864  
   865  			networkID = tenantnet.ID
   866  			networkName = tenantnet.Name
   867  		} else {
   868  			networkID = rawMap["uuid"].(string)
   869  			networkName = rawMap["name"].(string)
   870  		}
   871  
   872  		newNetworks = append(newNetworks, map[string]interface{}{
   873  			"uuid":        networkID,
   874  			"name":        networkName,
   875  			"port":        rawMap["port"].(string),
   876  			"fixed_ip_v4": rawMap["fixed_ip_v4"].(string),
   877  		})
   878  	}
   879  
   880  	log.Printf("[DEBUG] networks: %+v", newNetworks)
   881  	return newNetworks, nil
   882  }
   883  
   884  func resourceInstanceAddresses(addresses map[string]interface{}) map[string]map[string]interface{} {
   885  
   886  	addrs := make(map[string]map[string]interface{})
   887  	for n, networkAddresses := range addresses {
   888  		addrs[n] = make(map[string]interface{})
   889  		for _, element := range networkAddresses.([]interface{}) {
   890  			address := element.(map[string]interface{})
   891  			if address["OS-EXT-IPS:type"] == "floating" {
   892  				addrs[n]["floating_ip"] = address["addr"]
   893  			} else {
   894  				if address["version"].(float64) == 4 {
   895  					addrs[n]["fixed_ip_v4"] = address["addr"].(string)
   896  				} else {
   897  					addrs[n]["fixed_ip_v6"] = fmt.Sprintf("[%s]", address["addr"].(string))
   898  				}
   899  			}
   900  			if mac, ok := address["OS-EXT-IPS-MAC:mac_addr"]; ok {
   901  				addrs[n]["mac"] = mac.(string)
   902  			}
   903  		}
   904  	}
   905  
   906  	log.Printf("[DEBUG] Addresses: %+v", addresses)
   907  
   908  	return addrs
   909  }
   910  
   911  func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string {
   912  	m := make(map[string]string)
   913  	for key, val := range d.Get("metadata").(map[string]interface{}) {
   914  		m[key] = val.(string)
   915  	}
   916  	return m
   917  }
   918  
   919  func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interface{}) []bootfromvolume.BlockDevice {
   920  	sourceType := bootfromvolume.SourceType(bd["source_type"].(string))
   921  	bfvOpts := []bootfromvolume.BlockDevice{
   922  		bootfromvolume.BlockDevice{
   923  			UUID:            bd["uuid"].(string),
   924  			SourceType:      sourceType,
   925  			VolumeSize:      bd["volume_size"].(int),
   926  			DestinationType: bd["destination_type"].(string),
   927  			BootIndex:       bd["boot_index"].(int),
   928  		},
   929  	}
   930  
   931  	return bfvOpts
   932  }
   933  
   934  func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw map[string]interface{}) schedulerhints.SchedulerHints {
   935  	differentHost := []string{}
   936  	if len(schedulerHintsRaw["different_host"].([]interface{})) > 0 {
   937  		for _, dh := range schedulerHintsRaw["different_host"].([]interface{}) {
   938  			differentHost = append(differentHost, dh.(string))
   939  		}
   940  	}
   941  
   942  	sameHost := []string{}
   943  	if len(schedulerHintsRaw["same_host"].([]interface{})) > 0 {
   944  		for _, sh := range schedulerHintsRaw["same_host"].([]interface{}) {
   945  			sameHost = append(sameHost, sh.(string))
   946  		}
   947  	}
   948  
   949  	query := make([]interface{}, len(schedulerHintsRaw["query"].([]interface{})))
   950  	if len(schedulerHintsRaw["query"].([]interface{})) > 0 {
   951  		for _, q := range schedulerHintsRaw["query"].([]interface{}) {
   952  			query = append(query, q.(string))
   953  		}
   954  	}
   955  
   956  	schedulerHints := schedulerhints.SchedulerHints{
   957  		Group:           schedulerHintsRaw["group"].(string),
   958  		DifferentHost:   differentHost,
   959  		SameHost:        sameHost,
   960  		Query:           query,
   961  		TargetCell:      schedulerHintsRaw["target_cell"].(string),
   962  		BuildNearHostIP: schedulerHintsRaw["build_near_host_ip"].(string),
   963  	}
   964  
   965  	return schedulerHints
   966  }
   967  
   968  func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) {
   969  	imageId := d.Get("image_id").(string)
   970  
   971  	if imageId != "" {
   972  		return imageId, nil
   973  	}
   974  
   975  	imageCount := 0
   976  	imageName := d.Get("image_name").(string)
   977  	if imageName != "" {
   978  		pager := images.ListDetail(client, &images.ListOpts{
   979  			Name: imageName,
   980  		})
   981  		pager.EachPage(func(page pagination.Page) (bool, error) {
   982  			imageList, err := images.ExtractImages(page)
   983  			if err != nil {
   984  				return false, err
   985  			}
   986  
   987  			for _, i := range imageList {
   988  				if i.Name == imageName {
   989  					imageCount++
   990  					imageId = i.ID
   991  				}
   992  			}
   993  			return true, nil
   994  		})
   995  
   996  		switch imageCount {
   997  		case 0:
   998  			return "", fmt.Errorf("Unable to find image: %s", imageName)
   999  		case 1:
  1000  			return imageId, nil
  1001  		default:
  1002  			return "", fmt.Errorf("Found %d images matching %s", imageCount, imageName)
  1003  		}
  1004  	}
  1005  	return "", fmt.Errorf("Neither an image ID nor an image name were able to be determined.")
  1006  }
  1007  
  1008  func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) {
  1009  	flavorId := d.Get("flavor_id").(string)
  1010  
  1011  	if flavorId != "" {
  1012  		return flavorId, nil
  1013  	}
  1014  
  1015  	flavorCount := 0
  1016  	flavorName := d.Get("flavor_name").(string)
  1017  	if flavorName != "" {
  1018  		pager := flavors.ListDetail(client, nil)
  1019  		pager.EachPage(func(page pagination.Page) (bool, error) {
  1020  			flavorList, err := flavors.ExtractFlavors(page)
  1021  			if err != nil {
  1022  				return false, err
  1023  			}
  1024  
  1025  			for _, f := range flavorList {
  1026  				if f.Name == flavorName {
  1027  					flavorCount++
  1028  					flavorId = f.ID
  1029  				}
  1030  			}
  1031  			return true, nil
  1032  		})
  1033  
  1034  		switch flavorCount {
  1035  		case 0:
  1036  			return "", fmt.Errorf("Unable to find flavor: %s", flavorName)
  1037  		case 1:
  1038  			return flavorId, nil
  1039  		default:
  1040  			return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, flavorName)
  1041  		}
  1042  	}
  1043  	return "", fmt.Errorf("Neither a flavor ID nor a flavor name were able to be determined.")
  1044  }
  1045  
  1046  func resourceComputeVolumeAttachmentHash(v interface{}) int {
  1047  	var buf bytes.Buffer
  1048  	m := v.(map[string]interface{})
  1049  	buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string)))
  1050  	return hashcode.String(buf.String())
  1051  }
  1052  
  1053  func resourceComputeSchedulerHintsHash(v interface{}) int {
  1054  	var buf bytes.Buffer
  1055  	m := v.(map[string]interface{})
  1056  
  1057  	if m["group"] != nil {
  1058  		buf.WriteString(fmt.Sprintf("%s-", m["group"].(string)))
  1059  	}
  1060  
  1061  	if m["target_cell"] != nil {
  1062  		buf.WriteString(fmt.Sprintf("%s-", m["target_cell"].(string)))
  1063  	}
  1064  
  1065  	if m["build_host_near_ip"] != nil {
  1066  		buf.WriteString(fmt.Sprintf("%s-", m["build_host_near_ip"].(string)))
  1067  	}
  1068  
  1069  	buf.WriteString(fmt.Sprintf("%s-", m["different_host"].([]interface{})))
  1070  	buf.WriteString(fmt.Sprintf("%s-", m["same_host"].([]interface{})))
  1071  	buf.WriteString(fmt.Sprintf("%s-", m["query"].([]interface{})))
  1072  
  1073  	return hashcode.String(buf.String())
  1074  }
  1075  
  1076  func attachVolumesToInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error {
  1077  	if len(vols) > 0 {
  1078  		for _, v := range vols {
  1079  			va := v.(map[string]interface{})
  1080  			volumeId := va["volume_id"].(string)
  1081  			device := va["device"].(string)
  1082  
  1083  			s := ""
  1084  			if serverId != "" {
  1085  				s = serverId
  1086  			} else if va["server_id"] != "" {
  1087  				s = va["server_id"].(string)
  1088  			} else {
  1089  				return fmt.Errorf("Unable to determine server ID to attach volume.")
  1090  			}
  1091  
  1092  			vaOpts := &volumeattach.CreateOpts{
  1093  				Device:   device,
  1094  				VolumeID: volumeId,
  1095  			}
  1096  
  1097  			if _, err := volumeattach.Create(computeClient, s, vaOpts).Extract(); err != nil {
  1098  				return err
  1099  			}
  1100  
  1101  			stateConf := &resource.StateChangeConf{
  1102  				Pending:    []string{"attaching", "available"},
  1103  				Target:     "in-use",
  1104  				Refresh:    VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)),
  1105  				Timeout:    30 * time.Minute,
  1106  				Delay:      5 * time.Second,
  1107  				MinTimeout: 2 * time.Second,
  1108  			}
  1109  
  1110  			if _, err := stateConf.WaitForState(); err != nil {
  1111  				return err
  1112  			}
  1113  
  1114  			log.Printf("[INFO] Attached volume %s to instance %s", volumeId, serverId)
  1115  		}
  1116  	}
  1117  	return nil
  1118  }
  1119  
  1120  func detachVolumesFromInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error {
  1121  	if len(vols) > 0 {
  1122  		for _, v := range vols {
  1123  			va := v.(map[string]interface{})
  1124  			aId := va["id"].(string)
  1125  
  1126  			if err := volumeattach.Delete(computeClient, serverId, aId).ExtractErr(); err != nil {
  1127  				return err
  1128  			}
  1129  
  1130  			stateConf := &resource.StateChangeConf{
  1131  				Pending:    []string{"detaching", "in-use"},
  1132  				Target:     "available",
  1133  				Refresh:    VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)),
  1134  				Timeout:    30 * time.Minute,
  1135  				Delay:      5 * time.Second,
  1136  				MinTimeout: 2 * time.Second,
  1137  			}
  1138  
  1139  			if _, err := stateConf.WaitForState(); err != nil {
  1140  				return err
  1141  			}
  1142  			log.Printf("[INFO] Detached volume %s from instance %s", va["volume_id"], serverId)
  1143  		}
  1144  	}
  1145  
  1146  	return nil
  1147  }
  1148  
  1149  func getVolumeAttachments(computeClient *gophercloud.ServiceClient, serverId string) ([]volumeattach.VolumeAttachment, error) {
  1150  	var attachments []volumeattach.VolumeAttachment
  1151  	err := volumeattach.List(computeClient, serverId).EachPage(func(page pagination.Page) (bool, error) {
  1152  		actual, err := volumeattach.ExtractVolumeAttachments(page)
  1153  		if err != nil {
  1154  			return false, err
  1155  		}
  1156  
  1157  		attachments = actual
  1158  		return true, nil
  1159  	})
  1160  
  1161  	if err != nil {
  1162  		return nil, err
  1163  	}
  1164  
  1165  	return attachments, nil
  1166  }