unit tests; consistency with the other OpenStack services
diff --git a/openstack/blockstorage/v1/snapshots/requests.go b/openstack/blockstorage/v1/snapshots/requests.go
index 5e9256d..f823b3d 100644
--- a/openstack/blockstorage/v1/snapshots/requests.go
+++ b/openstack/blockstorage/v1/snapshots/requests.go
@@ -1,9 +1,11 @@
package snapshots
import (
- "github.com/racker/perigee"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/utils"
+ "github.com/rackspace/gophercloud/pagination"
+
+ "github.com/racker/perigee"
)
type CreateOpts struct {
@@ -14,7 +16,7 @@
VolumeID string
}
-func Create(client *gophercloud.ServiceClient, opts CreateOpts) (*Snapshot, error) {
+func Create(client *gophercloud.ServiceClient, opts *CreateOpts) CreateResult {
type snapshot struct {
Description *string `json:"display_description,omitempty"`
Force bool `json:"force,omitempty"`
@@ -37,38 +39,86 @@
reqBody.Snapshot.Force = opts.Force
- type response struct {
- Snapshot Snapshot `json:"snapshot"`
- }
-
- var respBody response
-
- _, err := perigee.Request("POST", snapshotsURL(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,
})
- if err != nil {
- return nil, err
- }
-
- return &respBody.Snapshot, nil
+ return res
}
-func Delete(client *gophercloud.ServiceClient, id string) error {
- _, err := perigee.Request("Delete", snapshotURL(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
}
func Get(client *gophercloud.ServiceClient, id string) GetResult {
- var gr GetResult
- _, err := perigee.Request("GET", snapshotURL(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 ListOpts struct {
+ Name string `q:"display_name"`
+ Status string `q:"status"`
+ VolumeID string `q:"volume_id"`
+}
+
+func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
+ url := listURL(client)
+ if opts != nil {
+ query, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query.String()
+ }
+
+ createPage := func(r pagination.LastHTTPResponse) pagination.Page {
+ return ListResult{pagination.SinglePageBase(r)}
+ }
+ return pagination.NewPager(client, url, createPage)
+}
+
+type UpdateOpts struct {
+ Description string
+ Name string
+}
+
+func Update(client *gophercloud.ServiceClient, id string, opts *UpdateOpts) UpdateResult {
+ type update struct {
+ Description *string `json:"display_description,omitempty"`
+ Name *string `json:"display_name,omitempty"`
+ }
+
+ type request struct {
+ Volume update `json:"snapshot"`
+ }
+
+ reqBody := request{
+ Volume: update{},
+ }
+
+ reqBody.Volume.Description = utils.MaybeString(opts.Description)
+ reqBody.Volume.Name = utils.MaybeString(opts.Name)
+
+ var res UpdateResult
+
+ _, res.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
+ MoreHeaders: client.Provider.AuthenticatedHeaders(),
+ OkCodes: []int{200},
+ ReqBody: &reqBody,
+ Results: &res.Resp,
+ })
+ return res
}
diff --git a/openstack/blockstorage/v1/snapshots/requests_test.go b/openstack/blockstorage/v1/snapshots/requests_test.go
index 1e77fbd..dae53fd 100644
--- a/openstack/blockstorage/v1/snapshots/requests_test.go
+++ b/openstack/blockstorage/v1/snapshots/requests_test.go
@@ -1 +1,160 @@
package snapshots
+
+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("/snapshots", 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, `
+ {
+ "snapshots": [
+ {
+ "id": "289da7f8-6440-407c-9fb4-7db01ec49164",
+ "display_name": "snapshot-001"
+ },
+ {
+ "id": "96c3bda7-c82a-4f50-be73-ca7621794835",
+ "display_name": "snapshot-002"
+ }
+ ]
+ }
+ `)
+ })
+
+ client := ServiceClient()
+ count := 0
+
+ List(client, &ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractSnapshots(page)
+ if err != nil {
+ t.Errorf("Failed to extract snapshots: %v", err)
+ return false, err
+ }
+
+ expected := []Snapshot{
+ Snapshot{
+ ID: "289da7f8-6440-407c-9fb4-7db01ec49164",
+ Name: "snapshot-001",
+ },
+ Snapshot{
+ ID: "96c3bda7-c82a-4f50-be73-ca7621794835",
+ Name: "snapshot-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("/snapshots/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, `
+{
+ "snapshot": {
+ "display_name": "snapshot-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, "snapshot-001")
+ th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
+}
+
+func TestCreate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/snapshots", 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, `
+{
+ "snapshot": {
+ "display_name": "snapshot-001"
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+{
+ "snapshot": {
+ "display_name": "snapshot-001",
+ "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+ }
+}
+ `)
+ })
+
+ options := &CreateOpts{Name: "snapshot-001"}
+ n, err := Create(ServiceClient(), options).Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, n.Name, "snapshot-001")
+ th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
+}
+
+func TestDelete(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/snapshots/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/snapshots/results.go b/openstack/blockstorage/v1/snapshots/results.go
index d8178c1..cc3a419 100644
--- a/openstack/blockstorage/v1/snapshots/results.go
+++ b/openstack/blockstorage/v1/snapshots/results.go
@@ -3,37 +3,82 @@
import (
"fmt"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+
"github.com/mitchellh/mapstructure"
)
type Snapshot struct {
- CreatedAt string
- Description string
- ID string
- Metadata map[string]interface{}
- Name string
- Size int
- Status string
- VolumeID string
+ Status string `mapstructure:"status"`
+ Name string `mapstructure:"display_name"`
+ Attachments []string `mapstructure:"attachments"`
+ AvailabilityZone string `mapstructure:"availability_zone"`
+ Bootable string `mapstructure:"bootable"`
+ CreatedAt string `mapstructure:"created_at"`
+ Description string `mapstructure:"display_discription"`
+ VolumeType string `mapstructure:"volume_type"`
+ SnapshotID string `mapstructure:"snapshot_id"`
+ SourceVolID string `mapstructure:"source_volid"`
+ Metadata map[string]string `mapstructure:"metadata"`
+ ID string `mapstructure:"id"`
+ Size int `mapstructure:"size"`
}
-type GetResult struct {
- err error
- r map[string]interface{}
+// ListResult is a *http.Response that is returned from a call to the List function.
+type ListResult struct {
+ pagination.SinglePageBase
}
-func (gr GetResult) ExtractSnapshot() (*Snapshot, error) {
- if gr.err != nil {
- return nil, gr.err
+// IsEmpty returns true if a ListResult contains no container names.
+func (r ListResult) IsEmpty() (bool, error) {
+ volumes, err := ExtractSnapshots(r)
+ if err != nil {
+ return true, err
+ }
+ return len(volumes) == 0, nil
+}
+
+// ExtractSnapshots extracts and returns the Volumes from a 'List' request.
+func ExtractSnapshots(page pagination.Page) ([]Snapshot, error) {
+ var response struct {
+ Snapshots []Snapshot `json:"snapshots"`
}
- var response struct {
+ err := mapstructure.Decode(page.(ListResult).Body, &response)
+ return response.Snapshots, err
+}
+
+type commonResult struct {
+ gophercloud.CommonResult
+}
+
+// Extract returns a pointer to the Snapshot from a commonResult.Resp.
+func (r commonResult) Extract() (*Snapshot, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
Snapshot *Snapshot `json:"snapshot"`
}
- err := mapstructure.Decode(gr.r, &response)
+ err := mapstructure.Decode(r.Resp, &res)
if err != nil {
- return nil, fmt.Errorf("snapshots: Error decoding snapshot.GetResult: %v", err)
+ return nil, fmt.Errorf("snapshots: Error decoding snapshots.commonResult: %v", err)
}
- return response.Snapshot, nil
+ return res.Snapshot, nil
}
+
+type GetResult struct {
+ commonResult
+}
+
+type CreateResult struct {
+ commonResult
+}
+type UpdateResult struct {
+ commonResult
+}
+
+type DeleteResult commonResult
diff --git a/openstack/blockstorage/v1/snapshots/urls.go b/openstack/blockstorage/v1/snapshots/urls.go
index c129185..fb324bb 100644
--- a/openstack/blockstorage/v1/snapshots/urls.go
+++ b/openstack/blockstorage/v1/snapshots/urls.go
@@ -2,10 +2,26 @@
import "github.com/rackspace/gophercloud"
-func snapshotsURL(c *gophercloud.ServiceClient) string {
+func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("snapshots")
}
-func snapshotURL(c *gophercloud.ServiceClient, id string) string {
+func deleteURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("snapshots", id)
}
+
+func getURL(c *gophercloud.ServiceClient, id string) string {
+ return deleteURL(c, id)
+}
+
+func listURL(c *gophercloud.ServiceClient) string {
+ return createURL(c)
+}
+
+func metadataURL(c *gophercloud.ServiceClient, id string) string {
+ return c.ServiceURL("snapshots", id, "metadata")
+}
+
+func updateURL(c *gophercloud.ServiceClient, id string) string {
+ return metadataURL(c, id)
+}
diff --git a/openstack/blockstorage/v1/snapshots/urls_test.go b/openstack/blockstorage/v1/snapshots/urls_test.go
index c36227f..8877580 100644
--- a/openstack/blockstorage/v1/snapshots/urls_test.go
+++ b/openstack/blockstorage/v1/snapshots/urls_test.go
@@ -13,14 +13,38 @@
return &gophercloud.ServiceClient{Endpoint: endpoint}
}
-func TestSnapshotsURL(t *testing.T) {
- actual := snapshotsURL(endpointClient())
+func TestCreateURL(t *testing.T) {
+ actual := createURL(endpointClient())
expected := endpoint + "snapshots"
th.AssertEquals(t, expected, actual)
}
-func TestSnapshotURL(t *testing.T) {
- actual := snapshotURL(endpointClient(), "foo")
+func TestDeleteURL(t *testing.T) {
+ actual := deleteURL(endpointClient(), "foo")
expected := endpoint + "snapshots/foo"
th.AssertEquals(t, expected, actual)
}
+
+func TestGetURL(t *testing.T) {
+ actual := getURL(endpointClient(), "foo")
+ expected := endpoint + "snapshots/foo"
+ th.AssertEquals(t, expected, actual)
+}
+
+func TestListURL(t *testing.T) {
+ actual := listURL(endpointClient())
+ expected := endpoint + "snapshots"
+ th.AssertEquals(t, expected, actual)
+}
+
+func TestMetadataURL(t *testing.T) {
+ actual := metadataURL(endpointClient(), "foo")
+ expected := endpoint + "snapshots/foo/metadata"
+ th.AssertEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+ actual := updateURL(endpointClient(), "foo")
+ expected := endpoint + "snapshots/foo/metadata"
+ th.AssertEquals(t, expected, actual)
+}
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)
}