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