github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/openstack/resource_openstack_blockstorage_volume_attach_v2.go (about)

     1  package openstack
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
    10  	"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
    11  
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  )
    15  
    16  func resourceBlockStorageVolumeAttachV2() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceBlockStorageVolumeAttachV2Create,
    19  		Read:   resourceBlockStorageVolumeAttachV2Read,
    20  		Delete: resourceBlockStorageVolumeAttachV2Delete,
    21  
    22  		Timeouts: &schema.ResourceTimeout{
    23  			Create: schema.DefaultTimeout(10 * time.Minute),
    24  			Delete: schema.DefaultTimeout(10 * time.Minute),
    25  		},
    26  
    27  		Schema: map[string]*schema.Schema{
    28  			"region": &schema.Schema{
    29  				Type:        schema.TypeString,
    30  				Required:    true,
    31  				ForceNew:    true,
    32  				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
    33  			},
    34  
    35  			"volume_id": &schema.Schema{
    36  				Type:     schema.TypeString,
    37  				Required: true,
    38  				ForceNew: true,
    39  			},
    40  
    41  			"instance_id": &schema.Schema{
    42  				Type:       schema.TypeString,
    43  				Optional:   true,
    44  				ForceNew:   true,
    45  				Deprecated: "instance_id is no longer used in this resource",
    46  			},
    47  
    48  			"host_name": &schema.Schema{
    49  				Type:     schema.TypeString,
    50  				Required: true,
    51  				ForceNew: true,
    52  			},
    53  
    54  			"device": &schema.Schema{
    55  				Type:     schema.TypeString,
    56  				Optional: true,
    57  				ForceNew: true,
    58  			},
    59  
    60  			"attach_mode": &schema.Schema{
    61  				Type:     schema.TypeString,
    62  				Optional: true,
    63  				ForceNew: true,
    64  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    65  					value := v.(string)
    66  					if value != "ro" && value != "rw" {
    67  						errors = append(errors, fmt.Errorf(
    68  							"Only 'ro' and 'rw' are supported values for 'attach_mode'"))
    69  					}
    70  					return
    71  				},
    72  			},
    73  
    74  			"initiator": &schema.Schema{
    75  				Type:     schema.TypeString,
    76  				Optional: true,
    77  				ForceNew: true,
    78  			},
    79  
    80  			"ip_address": &schema.Schema{
    81  				Type:     schema.TypeString,
    82  				Optional: true,
    83  				ForceNew: true,
    84  			},
    85  
    86  			"multipath": &schema.Schema{
    87  				Type:     schema.TypeBool,
    88  				Optional: true,
    89  				ForceNew: true,
    90  			},
    91  
    92  			"os_type": &schema.Schema{
    93  				Type:     schema.TypeString,
    94  				Optional: true,
    95  				ForceNew: true,
    96  			},
    97  
    98  			"platform": &schema.Schema{
    99  				Type:     schema.TypeString,
   100  				Optional: true,
   101  				ForceNew: true,
   102  			},
   103  
   104  			"wwpn": &schema.Schema{
   105  				Type:     schema.TypeList,
   106  				Optional: true,
   107  				ForceNew: true,
   108  				Elem:     &schema.Schema{Type: schema.TypeString},
   109  			},
   110  
   111  			"wwnn": &schema.Schema{
   112  				Type:     schema.TypeString,
   113  				Optional: true,
   114  				ForceNew: true,
   115  			},
   116  
   117  			// Volume attachment information
   118  			"data": &schema.Schema{
   119  				Type:      schema.TypeMap,
   120  				Computed:  true,
   121  				Sensitive: true,
   122  			},
   123  
   124  			"driver_volume_type": &schema.Schema{
   125  				Type:     schema.TypeString,
   126  				Computed: true,
   127  			},
   128  
   129  			"mount_point_base": &schema.Schema{
   130  				Type:     schema.TypeString,
   131  				Computed: true,
   132  			},
   133  		},
   134  	}
   135  }
   136  
   137  func resourceBlockStorageVolumeAttachV2Create(d *schema.ResourceData, meta interface{}) error {
   138  	config := meta.(*Config)
   139  	client, err := config.blockStorageV2Client(GetRegion(d))
   140  	if err != nil {
   141  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   142  	}
   143  
   144  	// initialize the connection
   145  	volumeId := d.Get("volume_id").(string)
   146  	connOpts := &volumeactions.InitializeConnectionOpts{}
   147  	if v, ok := d.GetOk("host_name"); ok {
   148  		connOpts.Host = v.(string)
   149  	}
   150  
   151  	if v, ok := d.GetOk("multipath"); ok {
   152  		multipath := v.(bool)
   153  		connOpts.Multipath = &multipath
   154  	}
   155  
   156  	if v, ok := d.GetOk("ip_address"); ok {
   157  		connOpts.IP = v.(string)
   158  	}
   159  
   160  	if v, ok := d.GetOk("initiator"); ok {
   161  		connOpts.Initiator = v.(string)
   162  	}
   163  
   164  	if v, ok := d.GetOk("os_type"); ok {
   165  		connOpts.OSType = v.(string)
   166  	}
   167  
   168  	if v, ok := d.GetOk("platform"); ok {
   169  		connOpts.Platform = v.(string)
   170  	}
   171  
   172  	if v, ok := d.GetOk("wwnns"); ok {
   173  		connOpts.Wwnns = v.(string)
   174  	}
   175  
   176  	if v, ok := d.GetOk("wwpns"); ok {
   177  		var wwpns []string
   178  		for _, i := range v.([]string) {
   179  			wwpns = append(wwpns, i)
   180  		}
   181  
   182  		connOpts.Wwpns = wwpns
   183  	}
   184  
   185  	connInfo, err := volumeactions.InitializeConnection(client, volumeId, connOpts).Extract()
   186  	if err != nil {
   187  		return fmt.Errorf("Unable to create connection: %s", err)
   188  	}
   189  
   190  	// Only uncomment this when debugging since connInfo contains sensitive information.
   191  	// log.Printf("[DEBUG] Volume Connection for %s: %#v", volumeId, connInfo)
   192  
   193  	// Because this information is only returned upon creation,
   194  	// it must be set in Create.
   195  	if v, ok := connInfo["data"]; ok {
   196  		data := make(map[string]string)
   197  		for key, value := range v.(map[string]interface{}) {
   198  			if v, ok := value.(string); ok {
   199  				data[key] = v
   200  			}
   201  		}
   202  
   203  		d.Set("data", data)
   204  	}
   205  
   206  	if v, ok := connInfo["driver_volume_type"]; ok {
   207  		d.Set("driver_volume_type", v)
   208  	}
   209  
   210  	if v, ok := connInfo["mount_point_base"]; ok {
   211  		d.Set("mount_point_base", v)
   212  	}
   213  
   214  	// Once the connection has been made, tell Cinder to mark the volume as attached.
   215  	attachMode, err := blockStorageVolumeAttachV2AttachMode(d.Get("attach_mode").(string))
   216  	if err != nil {
   217  		return nil
   218  	}
   219  
   220  	attachOpts := &volumeactions.AttachOpts{
   221  		HostName:   d.Get("host_name").(string),
   222  		MountPoint: d.Get("device").(string),
   223  		Mode:       attachMode,
   224  	}
   225  
   226  	log.Printf("[DEBUG] Attachment Options: %#v", attachOpts)
   227  
   228  	if err := volumeactions.Attach(client, volumeId, attachOpts).ExtractErr(); err != nil {
   229  		return err
   230  	}
   231  
   232  	// Wait for the volume to become available.
   233  	log.Printf("[DEBUG] Waiting for volume (%s) to become available", volumeId)
   234  
   235  	stateConf := &resource.StateChangeConf{
   236  		Pending:    []string{"available", "attaching"},
   237  		Target:     []string{"in-use"},
   238  		Refresh:    VolumeV2StateRefreshFunc(client, volumeId),
   239  		Timeout:    d.Timeout(schema.TimeoutCreate),
   240  		Delay:      10 * time.Second,
   241  		MinTimeout: 3 * time.Second,
   242  	}
   243  
   244  	_, err = stateConf.WaitForState()
   245  	if err != nil {
   246  		return fmt.Errorf("Error waiting for volume (%s) to become ready: %s", volumeId, err)
   247  	}
   248  
   249  	// Once the volume has been marked as attached,
   250  	// retrieve a fresh copy of it with all information now available.
   251  	volume, err := volumes.Get(client, volumeId).Extract()
   252  	if err != nil {
   253  		return err
   254  	}
   255  
   256  	// Search for the attachmentId
   257  	var attachmentId string
   258  	hostName := d.Get("host_name").(string)
   259  	for _, attachment := range volume.Attachments {
   260  		if hostName != "" && hostName == attachment.HostName {
   261  			attachmentId = attachment.AttachmentID
   262  		}
   263  	}
   264  
   265  	if attachmentId == "" {
   266  		return fmt.Errorf("Unable to determine attachment ID.")
   267  	}
   268  
   269  	// The ID must be a combination of the volume and attachment ID
   270  	// since a volume ID is required to retrieve an attachment ID.
   271  	id := fmt.Sprintf("%s/%s", volumeId, attachmentId)
   272  	d.SetId(id)
   273  
   274  	return resourceBlockStorageVolumeAttachV2Read(d, meta)
   275  }
   276  
   277  func resourceBlockStorageVolumeAttachV2Read(d *schema.ResourceData, meta interface{}) error {
   278  	config := meta.(*Config)
   279  	client, err := config.blockStorageV2Client(GetRegion(d))
   280  	if err != nil {
   281  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   282  	}
   283  
   284  	volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(d.Id())
   285  	if err != nil {
   286  		return err
   287  	}
   288  
   289  	volume, err := volumes.Get(client, volumeId).Extract()
   290  	if err != nil {
   291  		return err
   292  	}
   293  
   294  	log.Printf("[DEBUG] Retrieved volume %s: %#v", d.Id(), volume)
   295  
   296  	var attachment volumes.Attachment
   297  	for _, v := range volume.Attachments {
   298  		if attachmentId == v.AttachmentID {
   299  			attachment = v
   300  		}
   301  	}
   302  
   303  	log.Printf("[DEBUG] Retrieved volume attachment: %#v", attachment)
   304  
   305  	return nil
   306  }
   307  
   308  func resourceBlockStorageVolumeAttachV2Delete(d *schema.ResourceData, meta interface{}) error {
   309  	config := meta.(*Config)
   310  	client, err := config.blockStorageV2Client(GetRegion(d))
   311  	if err != nil {
   312  		return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
   313  	}
   314  
   315  	volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(d.Id())
   316  
   317  	// Terminate the connection
   318  	termOpts := &volumeactions.TerminateConnectionOpts{}
   319  	if v, ok := d.GetOk("host_name"); ok {
   320  		termOpts.Host = v.(string)
   321  	}
   322  
   323  	if v, ok := d.GetOk("multipath"); ok {
   324  		multipath := v.(bool)
   325  		termOpts.Multipath = &multipath
   326  	}
   327  
   328  	if v, ok := d.GetOk("ip_address"); ok {
   329  		termOpts.IP = v.(string)
   330  	}
   331  
   332  	if v, ok := d.GetOk("initiator"); ok {
   333  		termOpts.Initiator = v.(string)
   334  	}
   335  
   336  	if v, ok := d.GetOk("os_type"); ok {
   337  		termOpts.OSType = v.(string)
   338  	}
   339  
   340  	if v, ok := d.GetOk("platform"); ok {
   341  		termOpts.Platform = v.(string)
   342  	}
   343  
   344  	if v, ok := d.GetOk("wwnns"); ok {
   345  		termOpts.Wwnns = v.(string)
   346  	}
   347  
   348  	if v, ok := d.GetOk("wwpns"); ok {
   349  		var wwpns []string
   350  		for _, i := range v.([]string) {
   351  			wwpns = append(wwpns, i)
   352  		}
   353  
   354  		termOpts.Wwpns = wwpns
   355  	}
   356  
   357  	err = volumeactions.TerminateConnection(client, volumeId, termOpts).ExtractErr()
   358  	if err != nil {
   359  		return fmt.Errorf("Error terminating volume connection %s: %s", volumeId, err)
   360  	}
   361  
   362  	// Detach the volume
   363  	detachOpts := volumeactions.DetachOpts{
   364  		AttachmentID: attachmentId,
   365  	}
   366  
   367  	log.Printf("[DEBUG] Detachment Options: %#v", detachOpts)
   368  
   369  	if err := volumeactions.Detach(client, volumeId, detachOpts).ExtractErr(); err != nil {
   370  		return err
   371  	}
   372  
   373  	stateConf := &resource.StateChangeConf{
   374  		Pending:    []string{"in-use", "attaching", "detaching"},
   375  		Target:     []string{"available"},
   376  		Refresh:    VolumeV2StateRefreshFunc(client, volumeId),
   377  		Timeout:    d.Timeout(schema.TimeoutDelete),
   378  		Delay:      10 * time.Second,
   379  		MinTimeout: 3 * time.Second,
   380  	}
   381  
   382  	_, err = stateConf.WaitForState()
   383  	if err != nil {
   384  		return fmt.Errorf("Error waiting for volume (%s) to become available: %s", volumeId, err)
   385  	}
   386  
   387  	return nil
   388  }
   389  
   390  func blockStorageVolumeAttachV2AttachMode(v string) (volumeactions.AttachMode, error) {
   391  	var attachMode volumeactions.AttachMode
   392  	var attachError error
   393  	switch v {
   394  	case "":
   395  		attachMode = ""
   396  	case "ro":
   397  		attachMode = volumeactions.ReadOnly
   398  	case "rw":
   399  		attachMode = volumeactions.ReadWrite
   400  	default:
   401  		attachError = fmt.Errorf("Invalid attach_mode specified")
   402  	}
   403  
   404  	return attachMode, attachError
   405  }
   406  
   407  func blockStorageVolumeAttachV2ParseId(id string) (string, string, error) {
   408  	parts := strings.Split(id, "/")
   409  	if len(parts) < 2 {
   410  		return "", "", fmt.Errorf("Unable to determine attachment ID")
   411  	}
   412  
   413  	return parts[0], parts[1], nil
   414  }