diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 4c71ebf66a10..b5fb0d15ccb5 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -191,13 +191,13 @@ 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, @@ -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{ - createOpts, - 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"]) + } } } diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go index 77f970f853bf..09ac11345e89 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go @@ -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) diff --git a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index ef91edfaad49..88d0460b7aa1 100644 --- a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -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 = "" + } + + 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 + } +} +```