bfv updates (#100)

This commit creates a DestinationType for the possible block device
destination types.

It allows VolumeSize to be omitted.

Finally, it adds both unit and acceptance tests for all possible ways
that the bootfromvolume extension can be used.

* Renaming and reordering source and destination types

* Erroneous rename
diff --git a/acceptance/openstack/compute/v2/bootfromvolume_test.go b/acceptance/openstack/compute/v2/bootfromvolume_test.go
index 844a7cb..54719d0 100644
--- a/acceptance/openstack/compute/v2/bootfromvolume_test.go
+++ b/acceptance/openstack/compute/v2/bootfromvolume_test.go
@@ -6,10 +6,11 @@
 	"testing"
 
 	"github.com/gophercloud/gophercloud/acceptance/clients"
+	blockstorage "github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
 )
 
-func TestBootFromVolumeSingleVolume(t *testing.T) {
+func TestBootFromImage(t *testing.T) {
 	if testing.Short() {
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
@@ -26,10 +27,44 @@
 
 	blockDevices := []bootfromvolume.BlockDevice{
 		bootfromvolume.BlockDevice{
-			UUID:                choices.ImageID,
-			SourceType:          bootfromvolume.Image,
+			BootIndex:           0,
 			DeleteOnTermination: true,
-			DestinationType:     "volume",
+			DestinationType:     bootfromvolume.DestinationLocal,
+			SourceType:          bootfromvolume.SourceImage,
+			UUID:                choices.ImageID,
+		},
+	}
+
+	server, err := CreateBootableVolumeServer(t, client, blockDevices, choices)
+	if err != nil {
+		t.Fatalf("Unable to create server: %v", err)
+	}
+	defer DeleteServer(t, client, server)
+
+	PrintServer(t, server)
+}
+
+func TestBootFromNewVolume(t *testing.T) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	client, err := clients.NewComputeV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	blockDevices := []bootfromvolume.BlockDevice{
+		bootfromvolume.BlockDevice{
+			DeleteOnTermination: true,
+			DestinationType:     bootfromvolume.DestinationVolume,
+			SourceType:          bootfromvolume.SourceImage,
+			UUID:                choices.ImageID,
 			VolumeSize:          2,
 		},
 	}
@@ -43,6 +78,49 @@
 	PrintServer(t, server)
 }
 
+func TestBootFromExistingVolume(t *testing.T) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	computeClient, err := clients.NewComputeV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	blockStorageClient, err := clients.NewBlockStorageV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create a block storage client: %v", err)
+	}
+
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	volume, err := blockstorage.CreateVolumeFromImage(t, blockStorageClient, choices)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	blockDevices := []bootfromvolume.BlockDevice{
+		bootfromvolume.BlockDevice{
+			DeleteOnTermination: true,
+			DestinationType:     bootfromvolume.DestinationVolume,
+			SourceType:          bootfromvolume.SourceVolume,
+			UUID:                volume.ID,
+		},
+	}
+
+	server, err := CreateBootableVolumeServer(t, computeClient, blockDevices, choices)
+	if err != nil {
+		t.Fatalf("Unable to create server: %v", err)
+	}
+	defer DeleteServer(t, computeClient, server)
+
+	PrintServer(t, server)
+}
+
 func TestBootFromMultiEphemeralServer(t *testing.T) {
 	if testing.Short() {
 		t.Skip("Skipping test that requires server creation in short mode.")
@@ -61,26 +139,26 @@
 	blockDevices := []bootfromvolume.BlockDevice{
 		bootfromvolume.BlockDevice{
 			BootIndex:           0,
-			UUID:                choices.ImageID,
-			SourceType:          bootfromvolume.Image,
-			DestinationType:     "local",
+			DestinationType:     bootfromvolume.DestinationLocal,
 			DeleteOnTermination: true,
+			SourceType:          bootfromvolume.SourceImage,
+			UUID:                choices.ImageID,
 			VolumeSize:          5,
 		},
 		bootfromvolume.BlockDevice{
 			BootIndex:           -1,
-			SourceType:          bootfromvolume.Blank,
-			DestinationType:     "local",
+			DestinationType:     bootfromvolume.DestinationLocal,
 			DeleteOnTermination: true,
 			GuestFormat:         "ext4",
+			SourceType:          bootfromvolume.SourceBlank,
 			VolumeSize:          1,
 		},
 		bootfromvolume.BlockDevice{
 			BootIndex:           -1,
-			SourceType:          bootfromvolume.Blank,
-			DestinationType:     "local",
+			DestinationType:     bootfromvolume.DestinationLocal,
 			DeleteOnTermination: true,
 			GuestFormat:         "ext4",
+			SourceType:          bootfromvolume.SourceBlank,
 			VolumeSize:          1,
 		},
 	}
@@ -93,3 +171,95 @@
 
 	PrintServer(t, server)
 }
+
+func TestAttachNewVolume(t *testing.T) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	client, err := clients.NewComputeV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	blockDevices := []bootfromvolume.BlockDevice{
+		bootfromvolume.BlockDevice{
+			BootIndex:           0,
+			DeleteOnTermination: true,
+			DestinationType:     bootfromvolume.DestinationLocal,
+			SourceType:          bootfromvolume.SourceImage,
+			UUID:                choices.ImageID,
+		},
+		bootfromvolume.BlockDevice{
+			BootIndex:           1,
+			DeleteOnTermination: true,
+			DestinationType:     bootfromvolume.DestinationVolume,
+			SourceType:          bootfromvolume.SourceBlank,
+			VolumeSize:          2,
+		},
+	}
+
+	server, err := CreateBootableVolumeServer(t, client, blockDevices, choices)
+	if err != nil {
+		t.Fatalf("Unable to create server: %v", err)
+	}
+	defer DeleteServer(t, client, server)
+
+	PrintServer(t, server)
+}
+
+func TestAttachExistingVolume(t *testing.T) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	computeClient, err := clients.NewComputeV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	blockStorageClient, err := clients.NewBlockStorageV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create a block storage client: %v", err)
+	}
+
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	volume, err := blockstorage.CreateVolume(t, blockStorageClient)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	blockDevices := []bootfromvolume.BlockDevice{
+		bootfromvolume.BlockDevice{
+			BootIndex:           0,
+			DeleteOnTermination: true,
+			DestinationType:     bootfromvolume.DestinationLocal,
+			SourceType:          bootfromvolume.SourceImage,
+			UUID:                choices.ImageID,
+		},
+		bootfromvolume.BlockDevice{
+			BootIndex:           1,
+			DeleteOnTermination: true,
+			DestinationType:     bootfromvolume.DestinationVolume,
+			SourceType:          bootfromvolume.SourceVolume,
+			UUID:                volume.ID,
+		},
+	}
+
+	server, err := CreateBootableVolumeServer(t, computeClient, blockDevices, choices)
+	if err != nil {
+		t.Fatalf("Unable to create server: %v", err)
+	}
+	defer DeleteServer(t, computeClient, server)
+
+	PrintServer(t, server)
+}
diff --git a/acceptance/openstack/compute/v2/compute.go b/acceptance/openstack/compute/v2/compute.go
index 9b284c3..3392c2e 100644
--- a/acceptance/openstack/compute/v2/compute.go
+++ b/acceptance/openstack/compute/v2/compute.go
@@ -90,6 +90,10 @@
 		},
 	}
 
+	if blockDevices[0].SourceType == bootfromvolume.SourceImage && blockDevices[0].DestinationType == bootfromvolume.DestinationLocal {
+		serverCreateOpts.ImageRef = blockDevices[0].UUID
+	}
+
 	server, err = bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
 		serverCreateOpts,
 		blockDevices,