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:
parent
4443d8e4a2
commit
ed9e7de901
|
@ -191,14 +191,14 @@ 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,
|
Required: true,
|
||||||
|
@ -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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -380,14 +384,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{})
|
|
||||||
blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw)
|
|
||||||
createOpts = &bootfromvolume.CreateOptsExt{
|
createOpts = &bootfromvolume.CreateOptsExt{
|
||||||
CreateOptsBuilder: createOpts,
|
createOpts,
|
||||||
BlockDevice: blockDevice,
|
blockDevices,
|
||||||
}
|
|
||||||
log.Printf("[DEBUG] Create BFV Options: %+v", createOpts)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1091,20 +1091,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,11 +1146,20 @@ 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 {
|
||||||
|
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
|
return "", nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if imageId := d.Get("image_id").(string); imageId != "" {
|
if imageId := d.Get("image_id").(string); imageId != "" {
|
||||||
return imageId, nil
|
return imageId, nil
|
||||||
|
@ -1177,12 +1190,21 @@ 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 {
|
||||||
|
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")
|
d.Set("image_id", "Attempt to boot from volume - no image supplied")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
imageId := server.Image["id"].(string)
|
imageId := server.Image["id"].(string)
|
||||||
if imageId != "" {
|
if imageId != "" {
|
||||||
|
@ -1395,8 +1417,11 @@ func checkVolumeConfig(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"])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
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.
|
||||||
|
@ -187,6 +189,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 +203,46 @@ 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"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
Loading…
Reference in New Issue