unit tests; consistency with the other OpenStack services
diff --git a/openstack/blockstorage/v1/volumes/requests.go b/openstack/blockstorage/v1/volumes/requests.go
index 448ebd5..2a4ce91 100644
--- a/openstack/blockstorage/v1/volumes/requests.go
+++ b/openstack/blockstorage/v1/volumes/requests.go
@@ -17,7 +17,7 @@
 	VolumeType                       string
 }
 
-func Create(client *gophercloud.ServiceClient, opts CreateOpts) (*Volume, error) {
+func Create(client *gophercloud.ServiceClient, opts *CreateOpts) CreateResult {
 
 	type volume struct {
 		Availability *string           `json:"availability_zone,omitempty"`
@@ -48,42 +48,43 @@
 	reqBody.Volume.SourceVolID = utils.MaybeString(opts.SourceVolID)
 	reqBody.Volume.VolumeType = utils.MaybeString(opts.VolumeType)
 
-	type response struct {
-		Volume Volume `json:"volume"`
-	}
-
-	var respBody response
-
-	_, err := perigee.Request("POST", volumesURL(client), perigee.Options{
+	var res CreateResult
+	_, res.Err = perigee.Request("POST", createURL(client), perigee.Options{
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
-		OkCodes:     []int{200, 201},
 		ReqBody:     &reqBody,
-		Results:     &respBody,
+		Results:     &res.Resp,
+		OkCodes:     []int{200, 201},
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return &respBody.Volume, nil
+	return res
 }
 
-func List(client *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
+// ListOpts holds options for listing volumes. It is passed to the volumes.List function.
+type ListOpts struct {
+	// AllTenants is an admin-only option. Set it to true to see a tenant volumes.
+	AllTenants bool
+	// List only volumes that contain Metadata.
+	Metadata map[string]string
+	// List only volumes that have Name as the display name.
+	Name string
+	// List only volumes that have a status of Status.
+	Status string
+}
 
+func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
 	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
 		return ListResult{pagination.SinglePageBase(r)}
 	}
-
-	return pagination.NewPager(client, volumesURL(client), createPage)
+	return pagination.NewPager(client, listURL(client), createPage)
 }
 
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var gr GetResult
-	_, err := perigee.Request("GET", volumeURL(client, id), perigee.Options{
-		Results:     &gr.r,
+	var res GetResult
+	_, res.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
+		Results:     &res.Resp,
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{200},
 	})
-	gr.err = err
-	return gr
+	return res
 }
 
 type UpdateOpts struct {
@@ -92,7 +93,7 @@
 	Metadata    map[string]string
 }
 
-func Update(client *gophercloud.ServiceClient, id string, opts UpdateOpts) (*Volume, error) {
+func Update(client *gophercloud.ServiceClient, id string, opts *UpdateOpts) UpdateResult {
 	type update struct {
 		Description *string           `json:"display_description,omitempty"`
 		Metadata    map[string]string `json:"metadata,omitempty"`
@@ -110,29 +111,23 @@
 	reqBody.Volume.Description = utils.MaybeString(opts.Description)
 	reqBody.Volume.Name = utils.MaybeString(opts.Name)
 
-	type response struct {
-		Volume Volume `json:"volume"`
-	}
+	var res UpdateResult
 
-	var respBody response
-
-	_, err := perigee.Request("PUT", volumeURL(client, id), perigee.Options{
+	_, res.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 		OkCodes:     []int{200},
 		ReqBody:     &reqBody,
-		Results:     &respBody,
+		Results:     &res.Resp,
 	})
-	if err != nil {
-		return nil, err
-	}
-
-	return &respBody.Volume, nil
+	return res
 
 }
 
-func Delete(client *gophercloud.ServiceClient, id string) error {
-	_, err := perigee.Request("DELETE", volumeURL(client, id), perigee.Options{
+func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
+	var res DeleteResult
+	_, res.Err = perigee.Request("DELETE", deleteURL(client, id), perigee.Options{
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{204},
 	})
-	return err
+	return res
 }
diff --git a/openstack/blockstorage/v1/volumes/requests_test.go b/openstack/blockstorage/v1/volumes/requests_test.go
index bea59b2..2ae8dd3 100644
--- a/openstack/blockstorage/v1/volumes/requests_test.go
+++ b/openstack/blockstorage/v1/volumes/requests_test.go
@@ -1 +1,160 @@
 package volumes
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const TokenID = "123"
+
+func ServiceClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{
+		Provider: &gophercloud.ProviderClient{
+			TokenID: TokenID,
+		},
+		Endpoint: th.Endpoint(),
+	}
+}
+
+func TestList(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+		{
+			"volumes": [
+				{
+					"id": "289da7f8-6440-407c-9fb4-7db01ec49164",
+					"display_name": "vol-001"
+				},
+				{
+					"id": "96c3bda7-c82a-4f50-be73-ca7621794835",
+					"display_name": "vol-002"
+				}
+			]
+		}
+		`)
+	})
+
+	client := ServiceClient()
+	count := 0
+
+	List(client, &ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractVolumes(page)
+		if err != nil {
+			t.Errorf("Failed to extract volumes: %v", err)
+			return false, err
+		}
+
+		expected := []Volume{
+			Volume{
+				ID:   "289da7f8-6440-407c-9fb4-7db01ec49164",
+				Name: "vol-001",
+			},
+			Volume{
+				ID:   "96c3bda7-c82a-4f50-be73-ca7621794835",
+				Name: "vol-002",
+			},
+		}
+
+		th.CheckDeepEquals(t, expected, actual)
+
+		return true, nil
+	})
+
+	if count != 1 {
+		t.Errorf("Expected 1 page, got %d", count)
+	}
+}
+
+func TestGet(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, `
+{
+    "volume": {
+        "display_name": "vol-001",
+        "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+    }
+}
+			`)
+	})
+
+	v, err := Get(ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract()
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, v.Name, "vol-001")
+	th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
+}
+
+func TestCreate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", TokenID)
+		th.TestHeader(t, r, "Content-Type", "application/json")
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestJSONRequest(t, r, `
+{
+    "volume": {
+        "display_name": "vol-001"
+    }
+}
+			`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusCreated)
+
+		fmt.Fprintf(w, `
+{
+    "volume": {
+        "display_name": "vol-001",
+        "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+    }
+}
+		`)
+	})
+
+	options := &CreateOpts{Name: "vol-001"}
+	n, err := Create(ServiceClient(), options).Extract()
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, n.Name, "vol-001")
+	th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
+}
+
+func TestDelete(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", TokenID)
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	res := Delete(ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22")
+	th.AssertNoErr(t, res.Err)
+}
diff --git a/openstack/blockstorage/v1/volumes/results.go b/openstack/blockstorage/v1/volumes/results.go
index 14dc4c4..961582d 100644
--- a/openstack/blockstorage/v1/volumes/results.go
+++ b/openstack/blockstorage/v1/volumes/results.go
@@ -3,6 +3,7 @@
 import (
 	"fmt"
 
+	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
 
 	"github.com/mitchellh/mapstructure"
@@ -24,18 +25,6 @@
 	Size             int               `mapstructure:"size"`
 }
 
-// ListOpts holds options for listing volumes. It is passed to the volumes.List function.
-type ListOpts struct {
-	// AllTenants is an admin-only option. Set it to true to see a tenant volumes.
-	AllTenants bool
-	// List only volumes that contain Metadata.
-	Metadata map[string]string
-	// List only volumes that have Name as the display name.
-	Name string
-	// List only volumes that have a status of Status.
-	Status string
-}
-
 // ListResult is a *http.Response that is returned from a call to the List function.
 type ListResult struct {
 	pagination.SinglePageBase
@@ -60,24 +49,36 @@
 	return response.Volumes, err
 }
 
-type GetResult struct {
-	err error
-	r   map[string]interface{}
+type commonResult struct {
+	gophercloud.CommonResult
 }
 
 // ExtractVolume extracts and returns the Volume from a 'Get' request.
-func (gr GetResult) ExtractVolume() (*Volume, error) {
-	if gr.err != nil {
-		return nil, gr.err
+func (r commonResult) Extract() (*Volume, error) {
+	if r.Err != nil {
+		return nil, r.Err
 	}
 
-	var response struct {
+	var res struct {
 		Volume *Volume `json:"volume"`
 	}
 
-	err := mapstructure.Decode(gr.r, &response)
+	err := mapstructure.Decode(r.Resp, &res)
 	if err != nil {
-		return nil, fmt.Errorf("volumes: Error decoding volumes.GetResult: %v", err)
+		return nil, fmt.Errorf("volumes: Error decoding volumes.commonResult: %v", err)
 	}
-	return response.Volume, nil
+	return res.Volume, nil
 }
+
+type GetResult struct {
+	commonResult
+}
+
+type CreateResult struct {
+	commonResult
+}
+type UpdateResult struct {
+	commonResult
+}
+
+type DeleteResult commonResult
diff --git a/openstack/blockstorage/v1/volumes/urls.go b/openstack/blockstorage/v1/volumes/urls.go
index d1a6499..29629a1 100644
--- a/openstack/blockstorage/v1/volumes/urls.go
+++ b/openstack/blockstorage/v1/volumes/urls.go
@@ -2,10 +2,22 @@
 
 import "github.com/rackspace/gophercloud"
 
-func volumesURL(c *gophercloud.ServiceClient) string {
+func createURL(c *gophercloud.ServiceClient) string {
 	return c.ServiceURL("volumes")
 }
 
-func volumeURL(c *gophercloud.ServiceClient, id string) string {
+func listURL(c *gophercloud.ServiceClient) string {
+	return createURL(c)
+}
+
+func deleteURL(c *gophercloud.ServiceClient, id string) string {
 	return c.ServiceURL("volumes", id)
 }
+
+func getURL(c *gophercloud.ServiceClient, id string) string {
+	return deleteURL(c, id)
+}
+
+func updateURL(c *gophercloud.ServiceClient, id string) string {
+	return deleteURL(c, id)
+}
diff --git a/openstack/blockstorage/v1/volumes/urls_test.go b/openstack/blockstorage/v1/volumes/urls_test.go
index fac4033..a95270e 100644
--- a/openstack/blockstorage/v1/volumes/urls_test.go
+++ b/openstack/blockstorage/v1/volumes/urls_test.go
@@ -13,14 +13,32 @@
 	return &gophercloud.ServiceClient{Endpoint: endpoint}
 }
 
-func TestVolumesURL(t *testing.T) {
-	actual := volumesURL(endpointClient())
+func TestCreateURL(t *testing.T) {
+	actual := createURL(endpointClient())
 	expected := endpoint + "volumes"
 	th.AssertEquals(t, expected, actual)
 }
 
-func TestVolumeURL(t *testing.T) {
-	actual := volumeURL(endpointClient(), "foo")
+func TestListURL(t *testing.T) {
+	actual := listURL(endpointClient())
+	expected := endpoint + "volumes"
+	th.AssertEquals(t, expected, actual)
+}
+
+func TestDeleteURL(t *testing.T) {
+	actual := deleteURL(endpointClient(), "foo")
+	expected := endpoint + "volumes/foo"
+	th.AssertEquals(t, expected, actual)
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient(), "foo")
+	expected := endpoint + "volumes/foo"
+	th.AssertEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+	actual := updateURL(endpointClient(), "foo")
 	expected := endpoint + "volumes/foo"
 	th.AssertEquals(t, expected, actual)
 }