github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/builtin/providers/openstack/resource_openstack_compute_instance_v2.go (about)

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