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