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,
|
ForceNew: true,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"uuid": &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
"source_type": &schema.Schema{
|
"source_type": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
|
"uuid": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
"volume_size": &schema.Schema{
|
"volume_size": &schema.Schema{
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Required: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"destination_type": &schema.Schema{
|
"destination_type": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
|
@ -216,6 +216,10 @@ func resourceComputeInstanceV2() *schema.Resource {
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: false,
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine if volume/block_device configuration is correct
|
// determine if volume configuration is correct
|
||||||
// this includes ensuring volume_ids are set
|
// this includes ensuring volume_ids are set
|
||||||
// and if only one block_device was specified.
|
|
||||||
if err := checkVolumeConfig(d); err != nil {
|
if err := checkVolumeConfig(d); err != nil {
|
||||||
return err
|
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
|
// check if floating IP configuration is correct
|
||||||
if err := checkInstanceFloatingIPs(d); err != nil {
|
if err := checkInstanceFloatingIPs(d); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -380,14 +389,10 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
|
||||||
}
|
}
|
||||||
|
|
||||||
if vL, ok := d.GetOk("block_device"); ok {
|
if vL, ok := d.GetOk("block_device"); ok {
|
||||||
for _, v := range vL.([]interface{}) {
|
blockDevices := resourceInstanceBlockDevicesV2(d, vL.([]interface{}))
|
||||||
blockDeviceRaw := v.(map[string]interface{})
|
createOpts = &bootfromvolume.CreateOptsExt{
|
||||||
blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw)
|
createOpts,
|
||||||
createOpts = &bootfromvolume.CreateOptsExt{
|
blockDevices,
|
||||||
CreateOptsBuilder: createOpts,
|
|
||||||
BlockDevice: blockDevice,
|
|
||||||
}
|
|
||||||
log.Printf("[DEBUG] Create BFV Options: %+v", createOpts)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1091,20 +1096,24 @@ func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interface{}) []bootfromvolume.BlockDevice {
|
func resourceInstanceBlockDevicesV2(d *schema.ResourceData, bds []interface{}) []bootfromvolume.BlockDevice {
|
||||||
sourceType := bootfromvolume.SourceType(bd["source_type"].(string))
|
blockDeviceOpts := make([]bootfromvolume.BlockDevice, len(bds))
|
||||||
bfvOpts := []bootfromvolume.BlockDevice{
|
for i, bd := range bds {
|
||||||
bootfromvolume.BlockDevice{
|
bdM := bd.(map[string]interface{})
|
||||||
UUID: bd["uuid"].(string),
|
sourceType := bootfromvolume.SourceType(bdM["source_type"].(string))
|
||||||
|
blockDeviceOpts[i] = bootfromvolume.BlockDevice{
|
||||||
|
UUID: bdM["uuid"].(string),
|
||||||
SourceType: sourceType,
|
SourceType: sourceType,
|
||||||
VolumeSize: bd["volume_size"].(int),
|
VolumeSize: bdM["volume_size"].(int),
|
||||||
DestinationType: bd["destination_type"].(string),
|
DestinationType: bdM["destination_type"].(string),
|
||||||
BootIndex: bd["boot_index"].(int),
|
BootIndex: bdM["boot_index"].(int),
|
||||||
DeleteOnTermination: bd["delete_on_termination"].(bool),
|
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 {
|
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) {
|
func getImageIDFromConfig(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) {
|
||||||
// If block_device was used, an Image does not need to be specified.
|
// If block_device was used, an Image does not need to be specified, unless an image/local
|
||||||
// If an Image was specified, ignore it
|
// combination was used. This emulates normal boot behavior. Otherwise, ignore the image altogether.
|
||||||
if _, ok := d.GetOk("block_device"); ok {
|
if vL, ok := d.GetOk("block_device"); ok {
|
||||||
return "", nil
|
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 != "" {
|
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 {
|
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 block_device was used, an Image does not need to be specified, unless an image/local
|
||||||
// If an Image was specified, ignore it
|
// combination was used. This emulates normal boot behavior. Otherwise, ignore the image altogether.
|
||||||
if _, ok := d.GetOk("block_device"); ok {
|
if vL, ok := d.GetOk("block_device"); ok {
|
||||||
d.Set("image_id", "Attempt to boot from volume - no image supplied")
|
needImage := false
|
||||||
return nil
|
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)
|
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 vL, ok := d.GetOk("block_device"); ok {
|
||||||
if len(vL.([]interface{})) > 1 {
|
for _, v := range vL.([]interface{}) {
|
||||||
return fmt.Errorf("Can only specify one block device to boot from.")
|
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 {
|
block_device {
|
||||||
uuid = "${openstack_blockstorage_volume_v1.foo.id}"
|
uuid = "${openstack_blockstorage_volume_v1.foo.id}"
|
||||||
source_type = "volume"
|
source_type = "volume"
|
||||||
volume_size = 5
|
|
||||||
boot_index = 0
|
boot_index = 0
|
||||||
destination_type = "volume"
|
destination_type = "volume"
|
||||||
delete_on_termination = true
|
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 {
|
func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error {
|
||||||
config := testAccProvider.Meta().(*Config)
|
config := testAccProvider.Meta().(*Config)
|
||||||
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
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
|
* `block_device` - (Optional) The object for booting by volume. The block_device
|
||||||
object structure is documented below. Changing this creates a new server.
|
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
|
* `volume` - (Optional) Attach an existing volume to the instance. The volume
|
||||||
structure is described below.
|
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
|
* `source_type` - (Required) The source type of the device. Must be one of
|
||||||
"image", "volume", or "snapshot".
|
"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.
|
* `boot_index` - (Optional) The boot index of the volume. It defaults to 0.
|
||||||
|
|
||||||
|
@ -187,6 +191,8 @@ The following attributes are exported:
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
### Floating IPs
|
||||||
|
|
||||||
Floating IPs can be associated in one of two ways:
|
Floating IPs can be associated in one of two ways:
|
||||||
|
|
||||||
* You can specify a Floating IP address by using the top-level `floating_ip`
|
* 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.
|
IP address.
|
||||||
|
|
||||||
Only one of the above methods can be used.
|
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