provider/openstack: Fixes Boot From Volume
This commit fixes the previously broken "boot from volume" feature. It also adds an acceptance test to ensure the feature continues to work. The "delete_on_termination" option was also added.
This commit is contained in:
parent
5302becd5d
commit
e75553fd9d
|
@ -176,7 +176,7 @@ func resourceComputeInstanceV2() *schema.Resource {
|
|||
ForceNew: true,
|
||||
},
|
||||
"block_device": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Elem: &schema.Resource{
|
||||
|
@ -201,12 +201,22 @@ func resourceComputeInstanceV2() *schema.Resource {
|
|||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
"delete_on_termination": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Set: func(v interface{}) int {
|
||||
// there can only be one bootable block device; no need to hash anything
|
||||
return 0
|
||||
},
|
||||
},
|
||||
"volume": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"id": &schema.Schema{
|
||||
|
@ -215,7 +225,8 @@ func resourceComputeInstanceV2() *schema.Resource {
|
|||
},
|
||||
"volume_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"device": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
|
@ -325,12 +336,20 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
|
|||
}
|
||||
}
|
||||
|
||||
if blockDeviceRaw, ok := d.Get("block_device").(map[string]interface{}); ok && blockDeviceRaw != nil {
|
||||
if v, ok := d.GetOk("block_device"); ok {
|
||||
vL := v.(*schema.Set).List()
|
||||
if len(vL) > 1 {
|
||||
return fmt.Errorf("Can only specify one block device to boot from.")
|
||||
}
|
||||
for _, v := range vL {
|
||||
blockDeviceRaw := v.(map[string]interface{})
|
||||
blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw)
|
||||
createOpts = &bootfromvolume.CreateOptsExt{
|
||||
createOpts,
|
||||
blockDevice,
|
||||
}
|
||||
log.Printf("[DEBUG] Create BFV Options: %+v", createOpts)
|
||||
}
|
||||
}
|
||||
|
||||
schedulerHintsRaw := d.Get("scheduler_hints").(*schema.Set).List()
|
||||
|
@ -343,6 +362,23 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
|
|||
}
|
||||
}
|
||||
|
||||
// Boot From Volume makes the root volume/disk appear as an attached volume.
|
||||
// Because of that, and in order to accurately report volume status, the volume_id
|
||||
// of the "volume" parameter must be computed and optional.
|
||||
// However, a volume_id, of course, is required to attach a volume. We do the check
|
||||
// here to fail early (before the instance is created) if a volume_id was not specified.
|
||||
if v := d.Get("volume"); v != nil {
|
||||
vols := v.(*schema.Set).List()
|
||||
if len(vols) > 0 {
|
||||
for _, v := range vols {
|
||||
va := v.(map[string]interface{})
|
||||
if va["volume_id"].(string) == "" {
|
||||
return fmt.Errorf("A volume_id must be specified when attaching volumes.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||
server, err := servers.Create(computeClient, createOpts).Extract()
|
||||
if err != nil {
|
||||
|
@ -388,6 +424,7 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
|
|||
if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil {
|
||||
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||
} else {
|
||||
|
||||
if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -924,6 +961,7 @@ func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interfa
|
|||
VolumeSize: bd["volume_size"].(int),
|
||||
DestinationType: bd["destination_type"].(string),
|
||||
BootIndex: bd["boot_index"].(int),
|
||||
DeleteOnTermination: bd["delete_on_termination"].(bool),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1046,6 +1084,7 @@ func resourceComputeVolumeAttachmentHash(v interface{}) int {
|
|||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string)))
|
||||
|
||||
return hashcode.String(buf.String())
|
||||
}
|
||||
|
||||
|
|
|
@ -143,6 +143,39 @@ func TestAccComputeV2Instance_multi_secgroups(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccComputeV2Instance_bootFromVolume(t *testing.T) {
|
||||
var instance servers.Server
|
||||
var testAccComputeV2Instance_bootFromVolume = fmt.Sprintf(`
|
||||
resource "openstack_compute_instance_v2" "foo" {
|
||||
name = "terraform-test"
|
||||
security_groups = ["default"]
|
||||
block_device {
|
||||
uuid = "%s"
|
||||
source_type = "image"
|
||||
volume_size = 5
|
||||
boot_index = 0
|
||||
destination_type = "volume"
|
||||
delete_on_termination = true
|
||||
}
|
||||
}`,
|
||||
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_bootFromVolume,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance),
|
||||
testAccCheckComputeV2InstanceBootVolumeAttachment(&instance),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||
|
@ -249,6 +282,34 @@ func testAccCheckComputeV2InstanceVolumeAttachment(
|
|||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeV2InstanceBootVolumeAttachment(
|
||||
instance *servers.Server) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
var attachments []volumeattach.VolumeAttachment
|
||||
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = volumeattach.List(computeClient, instance.ID).EachPage(func(page pagination.Page) (bool, error) {
|
||||
actual, err := volumeattach.ExtractVolumeAttachments(page)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Unable to lookup attachment: %s", err)
|
||||
}
|
||||
|
||||
attachments = actual
|
||||
return true, nil
|
||||
})
|
||||
|
||||
if len(attachments) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("No attached volume found.")
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeV2InstanceFloatingIPAttach(
|
||||
instance *servers.Server, fip *floatingip.FloatingIP) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
|
|
Loading…
Reference in New Issue