Merge pull request #5131 from jtopjian/openstack-multi-ephemeral
provider/openstack: Multi Ephemeral Disk Support
This commit is contained in:
commit
09c192c753
|
@ -191,17 +191,17 @@ 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,
|
||||
Optional: true,
|
||||
},
|
||||
"destination_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
|
@ -216,6 +216,10 @@ func resourceComputeInstanceV2() *schema.Resource {
|
|||
Optional: true,
|
||||
Default: false,
|
||||
},
|
||||
"guest_format": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 != "" {
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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`
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
Loading…
Reference in New Issue