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,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
"block_device": &schema.Schema{
|
"block_device": &schema.Schema{
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
|
@ -201,12 +201,22 @@ func resourceComputeInstanceV2() *schema.Resource {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
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{
|
"volume": &schema.Schema{
|
||||||
Type: schema.TypeSet,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"id": &schema.Schema{
|
"id": &schema.Schema{
|
||||||
|
@ -215,7 +225,8 @@ func resourceComputeInstanceV2() *schema.Resource {
|
||||||
},
|
},
|
||||||
"volume_id": &schema.Schema{
|
"volume_id": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
},
|
},
|
||||||
"device": &schema.Schema{
|
"device": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
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)
|
blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw)
|
||||||
createOpts = &bootfromvolume.CreateOptsExt{
|
createOpts = &bootfromvolume.CreateOptsExt{
|
||||||
createOpts,
|
createOpts,
|
||||||
blockDevice,
|
blockDevice,
|
||||||
}
|
}
|
||||||
|
log.Printf("[DEBUG] Create BFV Options: %+v", createOpts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
schedulerHintsRaw := d.Get("scheduler_hints").(*schema.Set).List()
|
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)
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
server, err := servers.Create(computeClient, createOpts).Extract()
|
server, err := servers.Create(computeClient, createOpts).Extract()
|
||||||
if err != nil {
|
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 {
|
if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil {
|
||||||
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil {
|
if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -924,6 +961,7 @@ func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interfa
|
||||||
VolumeSize: bd["volume_size"].(int),
|
VolumeSize: bd["volume_size"].(int),
|
||||||
DestinationType: bd["destination_type"].(string),
|
DestinationType: bd["destination_type"].(string),
|
||||||
BootIndex: bd["boot_index"].(int),
|
BootIndex: bd["boot_index"].(int),
|
||||||
|
DeleteOnTermination: bd["delete_on_termination"].(bool),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1046,6 +1084,7 @@ func resourceComputeVolumeAttachmentHash(v interface{}) int {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
m := v.(map[string]interface{})
|
m := v.(map[string]interface{})
|
||||||
buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string)))
|
buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string)))
|
||||||
|
|
||||||
return hashcode.String(buf.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 {
|
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)
|
||||||
|
@ -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(
|
func testAccCheckComputeV2InstanceFloatingIPAttach(
|
||||||
instance *servers.Server, fip *floatingip.FloatingIP) resource.TestCheckFunc {
|
instance *servers.Server, fip *floatingip.FloatingIP) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
|
|
Loading…
Reference in New Issue