move block storage extensions from 'v2' to 'blockstorage' (#112)

diff --git a/acceptance/openstack/blockstorage/extensions/extensions.go b/acceptance/openstack/blockstorage/extensions/extensions.go
new file mode 100644
index 0000000..05a3e12
--- /dev/null
+++ b/acceptance/openstack/blockstorage/extensions/extensions.go
@@ -0,0 +1,97 @@
+// Package extensions contains common functions for creating block storage
+// resources that are extensions of the block storage API. See the `*_test.go`
+// files for example usages.
+package extensions
+
+import (
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
+	"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
+)
+
+// CreateVolumeAttach will attach a volume to an instance. An error will be
+// returned if the attachment failed.
+func CreateVolumeAttach(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume, server *servers.Server) error {
+	if testing.Short() {
+		t.Skip("Skipping test that requires volume attachment in short mode.")
+	}
+
+	attachOpts := volumeactions.AttachOpts{
+		MountPoint:   "/mnt",
+		Mode:         "rw",
+		InstanceUUID: server.ID,
+	}
+
+	t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID)
+
+	if err := volumeactions.Attach(client, volume.ID, attachOpts).ExtractErr(); err != nil {
+		return err
+	}
+
+	if err := volumes.WaitForStatus(client, volume.ID, "in-use", 60); err != nil {
+		return err
+	}
+
+	t.Logf("Attached volume %s to server %s", volume.ID, server.ID)
+
+	return nil
+}
+
+// CreateVolumeReserve creates a volume reservation. An error will be returned
+// if the reservation failed.
+func CreateVolumeReserve(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error {
+	if testing.Short() {
+		t.Skip("Skipping test that requires volume reservation in short mode.")
+	}
+
+	t.Logf("Attempting to reserve volume %s", volume.ID)
+
+	if err := volumeactions.Reserve(client, volume.ID).ExtractErr(); err != nil {
+		return err
+	}
+
+	t.Logf("Reserved volume %s", volume.ID)
+
+	return nil
+}
+
+// DeleteVolumeAttach will detach a volume from an instance. A fatal error will
+// occur if the snapshot failed to be deleted. This works best when used as a
+// deferred function.
+func DeleteVolumeAttach(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) {
+	t.Logf("Attepting to detach volume volume: %s", volume.ID)
+
+	detachOpts := volumeactions.DetachOpts{
+		AttachmentID: volume.Attachments[0].AttachmentID,
+	}
+
+	if err := volumeactions.Detach(client, volume.ID, detachOpts).ExtractErr(); err != nil {
+		t.Fatalf("Unable to detach volume %s: %v", volume.ID, err)
+	}
+
+	if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil {
+		t.Fatalf("Volume %s failed to become unavailable in 60 seconds: %v", volume.ID, err)
+	}
+
+	t.Logf("Detached volume: %s", volume.ID)
+}
+
+// DeleteVolumeReserve deletes a volume reservation. A fatal error will occur
+// if the deletion request failed. This works best when used as a deferred
+// function.
+func DeleteVolumeReserve(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires volume reservation in short mode.")
+	}
+
+	t.Logf("Attempting to unreserve volume %s", volume.ID)
+
+	if err := volumeactions.Unreserve(client, volume.ID).ExtractErr(); err != nil {
+		t.Fatalf("Unable to unreserve volume %s: %v", volume.ID, err)
+	}
+
+	t.Logf("Unreserved volume %s", volume.ID)
+}