Skip to content

Commit 09c192c

Browse files
committed
Merge pull request hashicorp#5131 from jtopjian/openstack-multi-ephemeral
provider/openstack: Multi Ephemeral Disk Support
2 parents 4443d8e + e872c3d commit 09c192c

3 files changed

Lines changed: 176 additions & 38 deletions

File tree

builtin/providers/openstack/resource_openstack_compute_instance_v2.go

Lines changed: 83 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -191,17 +191,17 @@ func resourceComputeInstanceV2() *schema.Resource {
191191
ForceNew: true,
192192
Elem: &schema.Resource{
193193
Schema: map[string]*schema.Schema{
194-
"uuid": &schema.Schema{
194+
"source_type": &schema.Schema{
195195
Type: schema.TypeString,
196196
Required: true,
197197
},
198-
"source_type": &schema.Schema{
198+
"uuid": &schema.Schema{
199199
Type: schema.TypeString,
200-
Required: true,
200+
Optional: true,
201201
},
202202
"volume_size": &schema.Schema{
203203
Type: schema.TypeInt,
204-
Required: true,
204+
Optional: true,
205205
},
206206
"destination_type": &schema.Schema{
207207
Type: schema.TypeString,
@@ -216,6 +216,10 @@ func resourceComputeInstanceV2() *schema.Resource {
216216
Optional: true,
217217
Default: false,
218218
},
219+
"guest_format": &schema.Schema{
220+
Type: schema.TypeString,
221+
Optional: true,
222+
},
219223
},
220224
},
221225
},
@@ -330,13 +334,18 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
330334
return err
331335
}
332336

333-
// determine if volume/block_device configuration is correct
337+
// determine if volume configuration is correct
334338
// this includes ensuring volume_ids are set
335-
// and if only one block_device was specified.
336339
if err := checkVolumeConfig(d); err != nil {
337340
return err
338341
}
339342

343+
// determine if block_device configuration is correct
344+
// this includes valid combinations and required attributes
345+
if err := checkBlockDeviceConfig(d); err != nil {
346+
return err
347+
}
348+
340349
// check if floating IP configuration is correct
341350
if err := checkInstanceFloatingIPs(d); err != nil {
342351
return err
@@ -380,14 +389,10 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
380389
}
381390

382391
if vL, ok := d.GetOk("block_device"); ok {
383-
for _, v := range vL.([]interface{}) {
384-
blockDeviceRaw := v.(map[string]interface{})
385-
blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw)
386-
createOpts = &bootfromvolume.CreateOptsExt{
387-
CreateOptsBuilder: createOpts,
388-
BlockDevice: blockDevice,
389-
}
390-
log.Printf("[DEBUG] Create BFV Options: %+v", createOpts)
392+
blockDevices := resourceInstanceBlockDevicesV2(d, vL.([]interface{}))
393+
createOpts = &bootfromvolume.CreateOptsExt{
394+
createOpts,
395+
blockDevices,
391396
}
392397
}
393398

@@ -1091,20 +1096,24 @@ func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string {
10911096
return m
10921097
}
10931098

1094-
func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interface{}) []bootfromvolume.BlockDevice {
1095-
sourceType := bootfromvolume.SourceType(bd["source_type"].(string))
1096-
bfvOpts := []bootfromvolume.BlockDevice{
1097-
bootfromvolume.BlockDevice{
1098-
UUID: bd["uuid"].(string),
1099+
func resourceInstanceBlockDevicesV2(d *schema.ResourceData, bds []interface{}) []bootfromvolume.BlockDevice {
1100+
blockDeviceOpts := make([]bootfromvolume.BlockDevice, len(bds))
1101+
for i, bd := range bds {
1102+
bdM := bd.(map[string]interface{})
1103+
sourceType := bootfromvolume.SourceType(bdM["source_type"].(string))
1104+
blockDeviceOpts[i] = bootfromvolume.BlockDevice{
1105+
UUID: bdM["uuid"].(string),
10991106
SourceType: sourceType,
1100-
VolumeSize: bd["volume_size"].(int),
1101-
DestinationType: bd["destination_type"].(string),
1102-
BootIndex: bd["boot_index"].(int),
1103-
DeleteOnTermination: bd["delete_on_termination"].(bool),
1104-
},
1107+
VolumeSize: bdM["volume_size"].(int),
1108+
DestinationType: bdM["destination_type"].(string),
1109+
BootIndex: bdM["boot_index"].(int),
1110+
DeleteOnTermination: bdM["delete_on_termination"].(bool),
1111+
GuestFormat: bdM["guest_format"].(string),
1112+
}
11051113
}
11061114

1107-
return bfvOpts
1115+
log.Printf("[DEBUG] Block Device Options: %+v", blockDeviceOpts)
1116+
return blockDeviceOpts
11081117
}
11091118

11101119
func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw map[string]interface{}) schedulerhints.SchedulerHints {
@@ -1142,10 +1151,19 @@ func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw
11421151
}
11431152

11441153
func getImageIDFromConfig(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) {
1145-
// If block_device was used, an Image does not need to be specified.
1146-
// If an Image was specified, ignore it
1147-
if _, ok := d.GetOk("block_device"); ok {
1148-
return "", nil
1154+
// If block_device was used, an Image does not need to be specified, unless an image/local
1155+
// combination was used. This emulates normal boot behavior. Otherwise, ignore the image altogether.
1156+
if vL, ok := d.GetOk("block_device"); ok {
1157+
needImage := false
1158+
for _, v := range vL.([]interface{}) {
1159+
vM := v.(map[string]interface{})
1160+
if vM["source_type"] == "image" && vM["destination_type"] == "local" {
1161+
needImage = true
1162+
}
1163+
}
1164+
if !needImage {
1165+
return "", nil
1166+
}
11491167
}
11501168

11511169
if imageId := d.Get("image_id").(string); imageId != "" {
@@ -1177,11 +1195,20 @@ func getImageIDFromConfig(computeClient *gophercloud.ServiceClient, d *schema.Re
11771195
}
11781196

11791197
func setImageInformation(computeClient *gophercloud.ServiceClient, server *servers.Server, d *schema.ResourceData) error {
1180-
// If block_device was used, an Image does not need to be specified.
1181-
// If an Image was specified, ignore it
1182-
if _, ok := d.GetOk("block_device"); ok {
1183-
d.Set("image_id", "Attempt to boot from volume - no image supplied")
1184-
return nil
1198+
// If block_device was used, an Image does not need to be specified, unless an image/local
1199+
// combination was used. This emulates normal boot behavior. Otherwise, ignore the image altogether.
1200+
if vL, ok := d.GetOk("block_device"); ok {
1201+
needImage := false
1202+
for _, v := range vL.([]interface{}) {
1203+
vM := v.(map[string]interface{})
1204+
if vM["source_type"] == "image" && vM["destination_type"] == "local" {
1205+
needImage = true
1206+
}
1207+
}
1208+
if !needImage {
1209+
d.Set("image_id", "Attempt to boot from volume - no image supplied")
1210+
return nil
1211+
}
11851212
}
11861213

11871214
imageId := server.Image["id"].(string)
@@ -1394,9 +1421,29 @@ func checkVolumeConfig(d *schema.ResourceData) error {
13941421
}
13951422
}
13961423

1424+
return nil
1425+
}
1426+
1427+
func checkBlockDeviceConfig(d *schema.ResourceData) error {
13971428
if vL, ok := d.GetOk("block_device"); ok {
1398-
if len(vL.([]interface{})) > 1 {
1399-
return fmt.Errorf("Can only specify one block device to boot from.")
1429+
for _, v := range vL.([]interface{}) {
1430+
vM := v.(map[string]interface{})
1431+
1432+
if vM["source_type"] != "blank" && vM["uuid"] == "" {
1433+
return fmt.Errorf("You must specify a uuid for %s block device types", vM["source_type"])
1434+
}
1435+
1436+
if vM["source_type"] == "image" && vM["destination_type"] == "volume" {
1437+
if vM["volume_size"] == 0 {
1438+
return fmt.Errorf("You must specify a volume_size when creating a volume from an image")
1439+
}
1440+
}
1441+
1442+
if vM["source_type"] == "blank" && vM["destination_type"] == "local" {
1443+
if vM["volume_size"] == 0 {
1444+
return fmt.Errorf("You must specify a volume_size when creating a blank block device")
1445+
}
1446+
}
14001447
}
14011448
}
14021449

builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,6 @@ func TestAccComputeV2Instance_bootFromVolumeVolume(t *testing.T) {
403403
block_device {
404404
uuid = "${openstack_blockstorage_volume_v1.foo.id}"
405405
source_type = "volume"
406-
volume_size = 5
407406
boot_index = 0
408407
destination_type = "volume"
409408
delete_on_termination = true
@@ -459,6 +458,51 @@ func TestAccComputeV2Instance_personality(t *testing.T) {
459458
})
460459
}
461460

461+
func TestAccComputeV2Instance_multiEphemeral(t *testing.T) {
462+
var instance servers.Server
463+
var testAccComputeV2Instance_multiEphemeral = fmt.Sprintf(`
464+
resource "openstack_compute_instance_v2" "foo" {
465+
name = "terraform-test"
466+
security_groups = ["default"]
467+
block_device {
468+
boot_index = 0
469+
delete_on_termination = true
470+
destination_type = "local"
471+
source_type = "image"
472+
uuid = "%s"
473+
}
474+
block_device {
475+
boot_index = -1
476+
delete_on_termination = true
477+
destination_type = "local"
478+
source_type = "blank"
479+
volume_size = 1
480+
}
481+
block_device {
482+
boot_index = -1
483+
delete_on_termination = true
484+
destination_type = "local"
485+
source_type = "blank"
486+
volume_size = 1
487+
}
488+
}`,
489+
os.Getenv("OS_IMAGE_ID"))
490+
491+
resource.Test(t, resource.TestCase{
492+
PreCheck: func() { testAccPreCheck(t) },
493+
Providers: testAccProviders,
494+
CheckDestroy: testAccCheckComputeV2InstanceDestroy,
495+
Steps: []resource.TestStep{
496+
resource.TestStep{
497+
Config: testAccComputeV2Instance_multiEphemeral,
498+
Check: resource.ComposeTestCheckFunc(
499+
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance),
500+
),
501+
},
502+
},
503+
})
504+
}
505+
462506
func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error {
463507
config := testAccProvider.Meta().(*Config)
464508
computeClient, err := config.computeV2Client(OS_REGION_NAME)

website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ The following arguments are supported:
8282

8383
* `block_device` - (Optional) The object for booting by volume. The block_device
8484
object structure is documented below. Changing this creates a new server.
85+
You can specify multiple block devices which will create an instance with
86+
multiple ephemeral (local) disks.
8587

8688
* `volume` - (Optional) Attach an existing volume to the instance. The volume
8789
structure is described below.
@@ -121,7 +123,9 @@ The `block_device` block supports:
121123
* `source_type` - (Required) The source type of the device. Must be one of
122124
"image", "volume", or "snapshot".
123125

124-
* `volume_size` - (Required) The size of the volume to create (in gigabytes).
126+
* `volume_size` - The size of the volume to create (in gigabytes). Required
127+
in the following combinations: source=image and destination=volume,
128+
source=blank and destination=local.
125129

126130
* `boot_index` - (Optional) The boot index of the volume. It defaults to 0.
127131

@@ -187,6 +191,8 @@ The following attributes are exported:
187191

188192
## Notes
189193

194+
### Floating IPs
195+
190196
Floating IPs can be associated in one of two ways:
191197

192198
* 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
199205
IP address.
200206

201207
Only one of the above methods can be used.
208+
209+
### Multiple Ephemeral Disks
210+
211+
It's possible to specify multiple `block_device` entries to create an instance
212+
with multiple ephemeral (local) disks. In order to create multiple ephemeral
213+
disks, the sum of the total amount of ephemeral space must be less than or
214+
equal to what the chosen flavor supports.
215+
216+
The following example shows how to create an instance with multiple ephemeral
217+
disks:
218+
219+
```
220+
resource "openstack_compute_instance_v2" "foo" {
221+
name = "terraform-test"
222+
security_groups = ["default"]
223+
224+
block_device {
225+
boot_index = 0
226+
delete_on_termination = true
227+
destination_type = "local"
228+
source_type = "image"
229+
uuid = "<image uuid>"
230+
}
231+
232+
block_device {
233+
boot_index = -1
234+
delete_on_termination = true
235+
destination_type = "local"
236+
source_type = "blank"
237+
volume_size = 1
238+
}
239+
240+
block_device {
241+
boot_index = -1
242+
delete_on_termination = true
243+
destination_type = "local"
244+
source_type = "blank"
245+
volume_size = 1
246+
}
247+
}
248+
```

0 commit comments

Comments
 (0)