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