provider/openstack: openstack_blockstorage_volume_attach_v2 resource
This commit adds the openstack_blockstorage_volume_attach_v2 resource. This resource enables a volume to be attached to an instance by using the OpenStack Block Storage (Cinder) v2 API.
This commit is contained in:
parent
fd8d41f6a5
commit
672d4d20d7
|
@ -0,0 +1,29 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
)
|
||||
|
||||
func TestAccBlockStorageVolumeAttachV2_importBasic(t *testing.T) {
|
||||
resourceName := "openstack_blockstorage_volume_attach_v2.va_1"
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckBlockStorageVolumeAttachV2Destroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccBlockStorageVolumeAttachV2_basic,
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
ResourceName: resourceName,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
ImportStateVerifyIgnore: []string{"region"},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -130,6 +130,7 @@ func Provider() terraform.ResourceProvider {
|
|||
ResourcesMap: map[string]*schema.Resource{
|
||||
"openstack_blockstorage_volume_v1": resourceBlockStorageVolumeV1(),
|
||||
"openstack_blockstorage_volume_v2": resourceBlockStorageVolumeV2(),
|
||||
"openstack_blockstorage_volume_attach_v2": resourceBlockStorageVolumeAttachV2(),
|
||||
"openstack_compute_instance_v2": resourceComputeInstanceV2(),
|
||||
"openstack_compute_keypair_v2": resourceComputeKeypairV2(),
|
||||
"openstack_compute_secgroup_v2": resourceComputeSecGroupV2(),
|
||||
|
|
|
@ -0,0 +1,254 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
|
||||
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceBlockStorageVolumeAttachV2() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceBlockStorageVolumeAttachV2Create,
|
||||
Read: resourceBlockStorageVolumeAttachV2Read,
|
||||
Update: nil,
|
||||
Delete: resourceBlockStorageVolumeAttachV2Delete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"region": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
|
||||
},
|
||||
|
||||
"volume_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"instance_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ConflictsWith: []string{"host_name"},
|
||||
},
|
||||
|
||||
"host_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ConflictsWith: []string{"instance_id"},
|
||||
},
|
||||
|
||||
"device": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"attach_mode": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(string)
|
||||
if value != "ro" && value != "rw" {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"Only 'ro' and 'rw' are supported values for 'attach_mode'"))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceBlockStorageVolumeAttachV2Create(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
client, err := config.blockStorageV2Client(d.Get("region").(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||
}
|
||||
|
||||
// Check if either instance_id or host_name was set.
|
||||
instanceId := d.Get("instance_id").(string)
|
||||
hostName := d.Get("host_name").(string)
|
||||
if instanceId == "" && hostName == "" {
|
||||
return fmt.Errorf("One of 'instance_id' or 'host_name' must be set.")
|
||||
}
|
||||
|
||||
volumeId := d.Get("volume_id").(string)
|
||||
|
||||
attachMode, err := blockStorageVolumeAttachV2AttachMode(d.Get("attach_mode").(string))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
attachOpts := &volumeactions.AttachOpts{
|
||||
InstanceUUID: d.Get("instance_id").(string),
|
||||
HostName: d.Get("host_name").(string),
|
||||
MountPoint: d.Get("device").(string),
|
||||
Mode: attachMode,
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Attachment Options: %#v", attachOpts)
|
||||
|
||||
if err := volumeactions.Attach(client, volumeId, attachOpts).ExtractErr(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the volume to become available.
|
||||
log.Printf("[DEBUG] Waiting for volume (%s) to become available", volumeId)
|
||||
|
||||
stateConf := &resource.StateChangeConf{
|
||||
Pending: []string{"available", "attaching"},
|
||||
Target: []string{"in-use"},
|
||||
Refresh: VolumeV2StateRefreshFunc(client, volumeId),
|
||||
Timeout: 10 * time.Minute,
|
||||
Delay: 10 * time.Second,
|
||||
MinTimeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
_, err = stateConf.WaitForState()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error waiting for volume (%s) to become ready: %s", volumeId, err)
|
||||
}
|
||||
|
||||
volume, err := volumes.Get(client, volumeId).Extract()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var attachmentId string
|
||||
for _, attachment := range volume.Attachments {
|
||||
if instanceId != "" && instanceId == attachment.ServerID {
|
||||
attachmentId = attachment.AttachmentID
|
||||
}
|
||||
|
||||
if hostName != "" && hostName == attachment.HostName {
|
||||
attachmentId = attachment.AttachmentID
|
||||
}
|
||||
}
|
||||
|
||||
if attachmentId == "" {
|
||||
return fmt.Errorf("Unable to determine attachment ID.")
|
||||
}
|
||||
|
||||
// The ID must be a combination of the volume and attachment ID
|
||||
// in order to import attachments.
|
||||
id := fmt.Sprintf("%s/%s", volumeId, attachmentId)
|
||||
d.SetId(id)
|
||||
|
||||
return resourceBlockStorageVolumeAttachV2Read(d, meta)
|
||||
}
|
||||
|
||||
func resourceBlockStorageVolumeAttachV2Read(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
client, err := config.blockStorageV2Client(d.Get("region").(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||
}
|
||||
|
||||
volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
volume, err := volumes.Get(client, volumeId).Extract()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Retrieved volume %s: %#v", d.Id(), volume)
|
||||
|
||||
var attachment volumes.Attachment
|
||||
for _, v := range volume.Attachments {
|
||||
if attachmentId == v.AttachmentID {
|
||||
attachment = v
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Retrieved volume attachment: %#v", attachment)
|
||||
|
||||
d.Set("volume_id", volumeId)
|
||||
d.Set("attachment_id", attachmentId)
|
||||
d.Set("device", attachment.Device)
|
||||
d.Set("instance_id", attachment.ServerID)
|
||||
d.Set("host_name", attachment.HostName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceBlockStorageVolumeAttachV2Delete(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
client, err := config.blockStorageV2Client(d.Get("region").(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||
}
|
||||
|
||||
volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
detachOpts := volumeactions.DetachOpts{
|
||||
AttachmentID: attachmentId,
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Detachment Options: %#v", detachOpts)
|
||||
|
||||
if err := volumeactions.Detach(client, volumeId, detachOpts).ExtractErr(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stateConf := &resource.StateChangeConf{
|
||||
Pending: []string{"in-use", "attaching", "detaching"},
|
||||
Target: []string{"available"},
|
||||
Refresh: VolumeV2StateRefreshFunc(client, volumeId),
|
||||
Timeout: 10 * time.Minute,
|
||||
Delay: 10 * time.Second,
|
||||
MinTimeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
_, err = stateConf.WaitForState()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error waiting for volume (%s) to become available: %s", volumeId, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func blockStorageVolumeAttachV2AttachMode(v string) (volumeactions.AttachMode, error) {
|
||||
var attachMode volumeactions.AttachMode
|
||||
var attachError error
|
||||
switch v {
|
||||
case "":
|
||||
case "ro":
|
||||
attachMode = volumeactions.ReadOnly
|
||||
case "rw":
|
||||
attachMode = volumeactions.ReadWrite
|
||||
default:
|
||||
attachError = fmt.Errorf("Invalid attach_mode specified")
|
||||
}
|
||||
|
||||
return attachMode, attachError
|
||||
}
|
||||
|
||||
func blockStorageVolumeAttachV2ParseId(id string) (string, string, error) {
|
||||
parts := strings.Split(id, "/")
|
||||
if len(parts) < 2 {
|
||||
return "", "", fmt.Errorf("Unable to determine attachment ID")
|
||||
}
|
||||
|
||||
return parts[0], parts[1], nil
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
|
||||
)
|
||||
|
||||
func TestAccBlockStorageVolumeAttachV2_basic(t *testing.T) {
|
||||
var va volumes.Attachment
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckBlockStorageVolumeAttachV2Destroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccBlockStorageVolumeAttachV2_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckBlockStorageVolumeAttachV2Exists(t, "openstack_blockstorage_volume_attach_v2.va_1", &va),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckBlockStorageVolumeAttachV2Destroy(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
client, err := config.blockStorageV2Client(OS_REGION_NAME)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||
}
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "openstack_blockstorage_volume_attach_v2" {
|
||||
continue
|
||||
}
|
||||
|
||||
volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
volume, err := volumes.Get(client, volumeId).Extract()
|
||||
if err != nil {
|
||||
if _, ok := err.(gophercloud.ErrDefault404); ok {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range volume.Attachments {
|
||||
if attachmentId == v.AttachmentID {
|
||||
return fmt.Errorf("Volume attachment still exists")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckBlockStorageVolumeAttachV2Exists(t *testing.T, n string, va *volumes.Attachment) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
client, err := config.blockStorageV2Client(OS_REGION_NAME)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||
}
|
||||
|
||||
volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
volume, err := volumes.Get(client, volumeId).Extract()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var found bool
|
||||
for _, v := range volume.Attachments {
|
||||
if attachmentId == v.AttachmentID {
|
||||
found = true
|
||||
*va = v
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Volume Attachment not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccBlockStorageVolumeAttachV2_basic = `
|
||||
resource "openstack_blockstorage_volume_v2" "volume_1" {
|
||||
name = "volume_1"
|
||||
size = 1
|
||||
}
|
||||
|
||||
resource "openstack_compute_instance_v2" "instance_1" {
|
||||
name = "instance_1"
|
||||
security_groups = ["default"]
|
||||
}
|
||||
|
||||
resource "openstack_blockstorage_volume_attach_v2" "va_1" {
|
||||
instance_id = "${openstack_compute_instance_v2.instance_1.id}"
|
||||
volume_id = "${openstack_blockstorage_volume_v2.volume_1.id}"
|
||||
device = "auto"
|
||||
}
|
||||
`
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
layout: "openstack"
|
||||
page_title: "OpenStack: openstack_blockstorage_volume_attach_v2"
|
||||
sidebar_current: "docs-openstack-resource-blockstorage-volume-attach-v2"
|
||||
description: |-
|
||||
Attaches a Block Storage Volume to an Instance.
|
||||
---
|
||||
|
||||
# openstack\_blockstorage\_volume_attach_v2
|
||||
|
||||
Attaches a Block Storage Volume to an Instance using the OpenStack
|
||||
Block Storage (Cinder) v2 API.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "openstack_blockstorage_volume_v2" "volume_1" {
|
||||
name = "volume_1"
|
||||
size = 1
|
||||
}
|
||||
|
||||
resource "openstack_blockstorage_instance_v2" "instance_1" {
|
||||
name = "instance_1"
|
||||
security_groups = ["default"]
|
||||
}
|
||||
|
||||
resource "openstack_blockstorage_volume_attach_v2" "va_1" {
|
||||
instance_id = "${openstack_blockstorage_instance_v2.instance_1.id}"
|
||||
volume_id = "${openstack_blockstorage_volume_v2.volume_1.id}"
|
||||
device = "auto"
|
||||
attach_mode = "rw"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `region` - (Required) The region in which to obtain the V2 Block Storage
|
||||
client. A Block Storage client is needed to create a volume attachment.
|
||||
If omitted, the `OS_REGION_NAME` environment variable is used. Changing
|
||||
this creates a new volume attachment.
|
||||
|
||||
* `volume_id` - (Required) The ID of the Volume to attach to an Instance.
|
||||
|
||||
* `instance_id` - (Required if `host_name` is not used) The ID of the Instance
|
||||
to attach the Volume to.
|
||||
|
||||
* `host_name` - (Required if `instance_id` is not used) The host to attach the
|
||||
volume to.
|
||||
|
||||
* `device` - (Optional) The device to attach the volume as.
|
||||
|
||||
* `attach_mode` - (Optional) Specify whether to attach the volume as Read-Only
|
||||
(`ro`) or Read-Write (`rw`). Only values of `ro` and `rw` are accepted.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `region` - See Argument Reference above.
|
||||
* `volume_id` - See Argument Reference above.
|
||||
* `instance_id` - See Argument Reference above.
|
||||
* `host_name` - See Argument Reference above.
|
||||
* `attach_mode` - See Argument Reference above.
|
||||
* `device` - See Argument Reference above.
|
||||
_NOTE_: Whether or not this is really the device the volume was attached
|
||||
as depends on the hypervisor being used in the OpenStack cloud. Do not
|
||||
consider this an authoritative piece of information.
|
||||
|
||||
## Import
|
||||
|
||||
Volume Attachments can be imported using the Volume and Attachment ID
|
||||
separated by a slash, e.g.
|
||||
|
||||
```
|
||||
$ terraform import openstack_blockstorage_volume_attach_v2.va_1 89c60255-9bd6-460c-822a-e2b959ede9d2/45670584-225f-46c3-b33e-6707b589b666
|
||||
```
|
||||
|
|
@ -19,6 +19,9 @@
|
|||
<li<%= sidebar_current("docs-openstack-resource-blockstorage-volume-v2") %>>
|
||||
<a href="/docs/providers/openstack/r/blockstorage_volume_v2.html">openstack_blockstorage_volume_v2</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-openstack-resource-blockstorage-volume-attach-v2") %>>
|
||||
<a href="/docs/providers/openstack/r/blockstorage_volume_attach_v2.html">openstack_blockstorage_volume_attach_v2</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
|
Loading…
Reference in New Issue