provider/openstack: multi ephemeral support

This commit adds the ability to create instances with multiple
ephemeral disks. The ephemeral disks will appear as local block
devices to the instance.
This commit is contained in:
Joe Topjian 2015-12-12 20:32:10 +00:00
parent 4443d8e4a2
commit ed9e7de901
3 changed files with 153 additions and 34 deletions

View File

@ -191,14 +191,14 @@ func resourceComputeInstanceV2() *schema.Resource {
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"uuid": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"source_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"uuid": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"volume_size": &schema.Schema{
Type: schema.TypeInt,
Required: true,
@ -216,6 +216,10 @@ func resourceComputeInstanceV2() *schema.Resource {
Optional: true,
Default: false,
},
"guest_format": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
},
@ -380,14 +384,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,
}
}
@ -1091,20 +1091,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 {
@ -1142,10 +1146,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 != "" {
@ -1177,11 +1190,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)
@ -1395,8 +1417,11 @@ func checkVolumeConfig(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"])
}
}
}

View File

@ -459,6 +459,53 @@ 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"
guest_format = "ext4"
source_type = "blank"
volume_size = 1
}
block_device {
boot_index = -1
delete_on_termination = true
destination_type = "local"
guest_format = "ext4"
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)

View File

@ -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.
@ -187,6 +189,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`
@ -199,3 +203,46 @@ 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"
guest_format = "ext4"
source_type = "blank"
volume_size = 1
}
block_device {
boot_index = -1
delete_on_termination = true
destination_type = "local"
guest_format = "ext4"
source_type = "blank"
volume_size = 1
}
}
```