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