Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/openstack: Multi Ephemeral Disk Support #5131

Merged
merged 2 commits into from
Feb 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 83 additions & 36 deletions builtin/providers/openstack/resource_openstack_compute_instance_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,17 +191,17 @@ func resourceComputeInstanceV2() *schema.Resource {
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"uuid": &schema.Schema{
"source_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"source_type": &schema.Schema{
"uuid": &schema.Schema{
Type: schema.TypeString,
Required: true,
Optional: true,
},
"volume_size": &schema.Schema{
Type: schema.TypeInt,
Required: true,
Optional: true,
},
"destination_type": &schema.Schema{
Type: schema.TypeString,
Expand All @@ -216,6 +216,10 @@ func resourceComputeInstanceV2() *schema.Resource {
Optional: true,
Default: false,
},
"guest_format": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
},
Expand Down Expand Up @@ -330,13 +334,18 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
return err
}

// determine if volume/block_device configuration is correct
// determine if volume configuration is correct
// this includes ensuring volume_ids are set
// and if only one block_device was specified.
if err := checkVolumeConfig(d); err != nil {
return err
}

// determine if block_device configuration is correct
// this includes valid combinations and required attributes
if err := checkBlockDeviceConfig(d); err != nil {
return err
}

// check if floating IP configuration is correct
if err := checkInstanceFloatingIPs(d); err != nil {
return err
Expand Down Expand Up @@ -380,14 +389,10 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
}

if vL, ok := d.GetOk("block_device"); ok {
for _, v := range vL.([]interface{}) {
blockDeviceRaw := v.(map[string]interface{})
blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw)
createOpts = &bootfromvolume.CreateOptsExt{
CreateOptsBuilder: createOpts,
BlockDevice: blockDevice,
}
log.Printf("[DEBUG] Create BFV Options: %+v", createOpts)
blockDevices := resourceInstanceBlockDevicesV2(d, vL.([]interface{}))
createOpts = &bootfromvolume.CreateOptsExt{
createOpts,
blockDevices,
}
}

Expand Down Expand Up @@ -1091,20 +1096,24 @@ func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string {
return m
}

func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interface{}) []bootfromvolume.BlockDevice {
sourceType := bootfromvolume.SourceType(bd["source_type"].(string))
bfvOpts := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
UUID: bd["uuid"].(string),
func resourceInstanceBlockDevicesV2(d *schema.ResourceData, bds []interface{}) []bootfromvolume.BlockDevice {
blockDeviceOpts := make([]bootfromvolume.BlockDevice, len(bds))
for i, bd := range bds {
bdM := bd.(map[string]interface{})
sourceType := bootfromvolume.SourceType(bdM["source_type"].(string))
blockDeviceOpts[i] = bootfromvolume.BlockDevice{
UUID: bdM["uuid"].(string),
SourceType: sourceType,
VolumeSize: bd["volume_size"].(int),
DestinationType: bd["destination_type"].(string),
BootIndex: bd["boot_index"].(int),
DeleteOnTermination: bd["delete_on_termination"].(bool),
},
VolumeSize: bdM["volume_size"].(int),
DestinationType: bdM["destination_type"].(string),
BootIndex: bdM["boot_index"].(int),
DeleteOnTermination: bdM["delete_on_termination"].(bool),
GuestFormat: bdM["guest_format"].(string),
}
}

return bfvOpts
log.Printf("[DEBUG] Block Device Options: %+v", blockDeviceOpts)
return blockDeviceOpts
}

func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw map[string]interface{}) schedulerhints.SchedulerHints {
Expand Down Expand Up @@ -1142,10 +1151,19 @@ func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw
}

func getImageIDFromConfig(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) {
// If block_device was used, an Image does not need to be specified.
// If an Image was specified, ignore it
if _, ok := d.GetOk("block_device"); ok {
return "", nil
// If block_device was used, an Image does not need to be specified, unless an image/local
// combination was used. This emulates normal boot behavior. Otherwise, ignore the image altogether.
if vL, ok := d.GetOk("block_device"); ok {
needImage := false
for _, v := range vL.([]interface{}) {
vM := v.(map[string]interface{})
if vM["source_type"] == "image" && vM["destination_type"] == "local" {
needImage = true
}
}
if !needImage {
return "", nil
}
}

if imageId := d.Get("image_id").(string); imageId != "" {
Expand Down Expand Up @@ -1177,11 +1195,20 @@ func getImageIDFromConfig(computeClient *gophercloud.ServiceClient, d *schema.Re
}

func setImageInformation(computeClient *gophercloud.ServiceClient, server *servers.Server, d *schema.ResourceData) error {
// If block_device was used, an Image does not need to be specified.
// If an Image was specified, ignore it
if _, ok := d.GetOk("block_device"); ok {
d.Set("image_id", "Attempt to boot from volume - no image supplied")
return nil
// If block_device was used, an Image does not need to be specified, unless an image/local
// combination was used. This emulates normal boot behavior. Otherwise, ignore the image altogether.
if vL, ok := d.GetOk("block_device"); ok {
needImage := false
for _, v := range vL.([]interface{}) {
vM := v.(map[string]interface{})
if vM["source_type"] == "image" && vM["destination_type"] == "local" {
needImage = true
}
}
if !needImage {
d.Set("image_id", "Attempt to boot from volume - no image supplied")
return nil
}
}

imageId := server.Image["id"].(string)
Expand Down Expand Up @@ -1394,9 +1421,29 @@ func checkVolumeConfig(d *schema.ResourceData) error {
}
}

return nil
}

func checkBlockDeviceConfig(d *schema.ResourceData) error {
if vL, ok := d.GetOk("block_device"); ok {
if len(vL.([]interface{})) > 1 {
return fmt.Errorf("Can only specify one block device to boot from.")
for _, v := range vL.([]interface{}) {
vM := v.(map[string]interface{})

if vM["source_type"] != "blank" && vM["uuid"] == "" {
return fmt.Errorf("You must specify a uuid for %s block device types", vM["source_type"])
}

if vM["source_type"] == "image" && vM["destination_type"] == "volume" {
if vM["volume_size"] == 0 {
return fmt.Errorf("You must specify a volume_size when creating a volume from an image")
}
}

if vM["source_type"] == "blank" && vM["destination_type"] == "local" {
if vM["volume_size"] == 0 {
return fmt.Errorf("You must specify a volume_size when creating a blank block device")
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,6 @@ func TestAccComputeV2Instance_bootFromVolumeVolume(t *testing.T) {
block_device {
uuid = "${openstack_blockstorage_volume_v1.foo.id}"
source_type = "volume"
volume_size = 5
boot_index = 0
destination_type = "volume"
delete_on_termination = true
Expand Down Expand Up @@ -459,6 +458,51 @@ func TestAccComputeV2Instance_personality(t *testing.T) {
})
}

func TestAccComputeV2Instance_multiEphemeral(t *testing.T) {
var instance servers.Server
var testAccComputeV2Instance_multiEphemeral = fmt.Sprintf(`
resource "openstack_compute_instance_v2" "foo" {
name = "terraform-test"
security_groups = ["default"]
block_device {
boot_index = 0
delete_on_termination = true
destination_type = "local"
source_type = "image"
uuid = "%s"
}
block_device {
boot_index = -1
delete_on_termination = true
destination_type = "local"
source_type = "blank"
volume_size = 1
}
block_device {
boot_index = -1
delete_on_termination = true
destination_type = "local"
source_type = "blank"
volume_size = 1
}
}`,
os.Getenv("OS_IMAGE_ID"))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeV2InstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeV2Instance_multiEphemeral,
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance),
),
},
},
})
}

func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
computeClient, err := config.computeV2Client(OS_REGION_NAME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ The following arguments are supported:

* `block_device` - (Optional) The object for booting by volume. The block_device
object structure is documented below. Changing this creates a new server.
You can specify multiple block devices which will create an instance with
multiple ephemeral (local) disks.

* `volume` - (Optional) Attach an existing volume to the instance. The volume
structure is described below.
Expand Down Expand Up @@ -121,7 +123,9 @@ The `block_device` block supports:
* `source_type` - (Required) The source type of the device. Must be one of
"image", "volume", or "snapshot".

* `volume_size` - (Required) The size of the volume to create (in gigabytes).
* `volume_size` - The size of the volume to create (in gigabytes). Required
in the following combinations: source=image and destination=volume,
source=blank and destination=local.

* `boot_index` - (Optional) The boot index of the volume. It defaults to 0.

Expand Down Expand Up @@ -187,6 +191,8 @@ The following attributes are exported:

## Notes

### Floating IPs

Floating IPs can be associated in one of two ways:

* You can specify a Floating IP address by using the top-level `floating_ip`
Expand All @@ -199,3 +205,44 @@ defined in the `network` block. Each `network` block can have its own floating
IP address.

Only one of the above methods can be used.

### Multiple Ephemeral Disks

It's possible to specify multiple `block_device` entries to create an instance
with multiple ephemeral (local) disks. In order to create multiple ephemeral
disks, the sum of the total amount of ephemeral space must be less than or
equal to what the chosen flavor supports.

The following example shows how to create an instance with multiple ephemeral
disks:

```
resource "openstack_compute_instance_v2" "foo" {
name = "terraform-test"
security_groups = ["default"]

block_device {
boot_index = 0
delete_on_termination = true
destination_type = "local"
source_type = "image"
uuid = "<image uuid>"
}

block_device {
boot_index = -1
delete_on_termination = true
destination_type = "local"
source_type = "blank"
volume_size = 1
}

block_device {
boot_index = -1
delete_on_termination = true
destination_type = "local"
source_type = "blank"
volume_size = 1
}
}
```