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