Move to vendor

As we walk away from 1.4, godep no longer requires to have stuff into workspace and we no longer need the symlink.

Signed-off-by: Olivier Gambier <olivier@docker.com>
This commit is contained in:
Olivier Gambier
2015-11-03 21:14:39 -08:00
parent b3e83da3e6
commit c9117af080
1059 changed files with 0 additions and 1 deletions

View File

@@ -0,0 +1,3 @@
// Package apiversions provides information and interaction with the different
// API versions for the OpenStack Block Storage service, code-named Cinder.
package apiversions

View File

@@ -0,0 +1,21 @@
package apiversions
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// List lists all the Cinder API versions available to end-users.
func List(c *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(c, listURL(c), func(r pagination.PageResult) pagination.Page {
return APIVersionPage{pagination.SinglePageBase(r)}
})
}
// Get will retrieve the volume type with the provided ID. To extract the volume
// type from the result, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, v string) GetResult {
var res GetResult
_, res.Err = client.Get(getURL(client, v), &res.Body, nil)
return res
}

View File

@@ -0,0 +1,145 @@
package apiversions
import (
"fmt"
"net/http"
"testing"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
"github.com/rackspace/gophercloud/testhelper/client"
)
func TestListVersions(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{
"versions": [
{
"status": "CURRENT",
"updated": "2012-01-04T11:33:21Z",
"id": "v1.0",
"links": [
{
"href": "http://23.253.228.211:8776/v1/",
"rel": "self"
}
]
},
{
"status": "CURRENT",
"updated": "2012-11-21T11:33:21Z",
"id": "v2.0",
"links": [
{
"href": "http://23.253.228.211:8776/v2/",
"rel": "self"
}
]
}
]
}`)
})
count := 0
List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
count++
actual, err := ExtractAPIVersions(page)
if err != nil {
t.Errorf("Failed to extract API versions: %v", err)
return false, err
}
expected := []APIVersion{
APIVersion{
ID: "v1.0",
Status: "CURRENT",
Updated: "2012-01-04T11:33:21Z",
},
APIVersion{
ID: "v2.0",
Status: "CURRENT",
Updated: "2012-11-21T11:33:21Z",
},
}
th.AssertDeepEquals(t, expected, actual)
return true, nil
})
if count != 1 {
t.Errorf("Expected 1 page, got %d", count)
}
}
func TestAPIInfo(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
th.Mux.HandleFunc("/v1/", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{
"version": {
"status": "CURRENT",
"updated": "2012-01-04T11:33:21Z",
"media-types": [
{
"base": "application/xml",
"type": "application/vnd.openstack.volume+xml;version=1"
},
{
"base": "application/json",
"type": "application/vnd.openstack.volume+json;version=1"
}
],
"id": "v1.0",
"links": [
{
"href": "http://23.253.228.211:8776/v1/",
"rel": "self"
},
{
"href": "http://jorgew.github.com/block-storage-api/content/os-block-storage-1.0.pdf",
"type": "application/pdf",
"rel": "describedby"
},
{
"href": "http://docs.rackspacecloud.com/servers/api/v1.1/application.wadl",
"type": "application/vnd.sun.wadl+xml",
"rel": "describedby"
}
]
}
}`)
})
actual, err := Get(client.ServiceClient(), "v1").Extract()
if err != nil {
t.Errorf("Failed to extract version: %v", err)
}
expected := APIVersion{
ID: "v1.0",
Status: "CURRENT",
Updated: "2012-01-04T11:33:21Z",
}
th.AssertEquals(t, actual.ID, expected.ID)
th.AssertEquals(t, actual.Status, expected.Status)
th.AssertEquals(t, actual.Updated, expected.Updated)
}

View File

@@ -0,0 +1,58 @@
package apiversions
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
)
// APIVersion represents an API version for Cinder.
type APIVersion struct {
ID string `json:"id" mapstructure:"id"` // unique identifier
Status string `json:"status" mapstructure:"status"` // current status
Updated string `json:"updated" mapstructure:"updated"` // date last updated
}
// APIVersionPage is the page returned by a pager when traversing over a
// collection of API versions.
type APIVersionPage struct {
pagination.SinglePageBase
}
// IsEmpty checks whether an APIVersionPage struct is empty.
func (r APIVersionPage) IsEmpty() (bool, error) {
is, err := ExtractAPIVersions(r)
if err != nil {
return true, err
}
return len(is) == 0, nil
}
// ExtractAPIVersions takes a collection page, extracts all of the elements,
// and returns them a slice of APIVersion structs. It is effectively a cast.
func ExtractAPIVersions(page pagination.Page) ([]APIVersion, error) {
var resp struct {
Versions []APIVersion `mapstructure:"versions"`
}
err := mapstructure.Decode(page.(APIVersionPage).Body, &resp)
return resp.Versions, err
}
// GetResult represents the result of a get operation.
type GetResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts an API version resource.
func (r GetResult) Extract() (*APIVersion, error) {
var resp struct {
Version *APIVersion `mapstructure:"version"`
}
err := mapstructure.Decode(r.Body, &resp)
return resp.Version, err
}

View File

@@ -0,0 +1,15 @@
package apiversions
import (
"strings"
"github.com/rackspace/gophercloud"
)
func getURL(c *gophercloud.ServiceClient, version string) string {
return c.ServiceURL(strings.TrimRight(version, "/") + "/")
}
func listURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("")
}

View File

@@ -0,0 +1,26 @@
package apiversions
import (
"testing"
"github.com/rackspace/gophercloud"
th "github.com/rackspace/gophercloud/testhelper"
)
const endpoint = "http://localhost:57909/"
func endpointClient() *gophercloud.ServiceClient {
return &gophercloud.ServiceClient{Endpoint: endpoint}
}
func TestGetURL(t *testing.T) {
actual := getURL(endpointClient(), "v1")
expected := endpoint + "v1/"
th.AssertEquals(t, expected, actual)
}
func TestListURL(t *testing.T) {
actual := listURL(endpointClient())
expected := endpoint
th.AssertEquals(t, expected, actual)
}

View File

@@ -0,0 +1,5 @@
// Package snapshots provides information and interaction with snapshots in the
// OpenStack Block Storage service. A snapshot is a point in time copy of the
// data contained in an external storage volume, and can be controlled
// programmatically.
package snapshots

View File

@@ -0,0 +1,114 @@
package snapshots
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func MockListResponse(t *testing.T) {
th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.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"
}
]
}
`)
})
}
func MockGetResponse(t *testing.T) {
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", fake.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"
}
}
`)
})
}
func MockCreateResponse(t *testing.T) {
th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Content-Type", "application/json")
th.TestHeader(t, r, "Accept", "application/json")
th.TestJSONRequest(t, r, `
{
"snapshot": {
"volume_id": "1234",
"display_name": "snapshot-001"
}
}
`)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, `
{
"snapshot": {
"volume_id": "1234",
"display_name": "snapshot-001",
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
}
}
`)
})
}
func MockUpdateMetadataResponse(t *testing.T) {
th.Mux.HandleFunc("/snapshots/123/metadata", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Content-Type", "application/json")
th.TestJSONRequest(t, r, `
{
"metadata": {
"key": "v1"
}
}
`)
fmt.Fprintf(w, `
{
"metadata": {
"key": "v1"
}
}
`)
})
}
func MockDeleteResponse(t *testing.T) {
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", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
}

View File

@@ -0,0 +1,173 @@
package snapshots
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToSnapshotCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains options for creating a Snapshot. This object is passed to
// the snapshots.Create function. For more information about these parameters,
// see the Snapshot object.
type CreateOpts struct {
// OPTIONAL
Description string
// OPTIONAL
Force bool
// OPTIONAL
Metadata map[string]interface{}
// OPTIONAL
Name string
// REQUIRED
VolumeID string
}
// ToSnapshotCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToSnapshotCreateMap() (map[string]interface{}, error) {
s := make(map[string]interface{})
if opts.VolumeID == "" {
return nil, fmt.Errorf("Required CreateOpts field 'VolumeID' not set.")
}
s["volume_id"] = opts.VolumeID
if opts.Description != "" {
s["display_description"] = opts.Description
}
if opts.Force == true {
s["force"] = opts.Force
}
if opts.Metadata != nil {
s["metadata"] = opts.Metadata
}
if opts.Name != "" {
s["display_name"] = opts.Name
}
return map[string]interface{}{"snapshot": s}, nil
}
// Create will create a new Snapshot based on the values in CreateOpts. To
// extract the Snapshot object from the response, call the Extract method on the
// CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToSnapshotCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return res
}
// Delete will delete the existing Snapshot with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
var res DeleteResult
_, res.Err = client.Delete(deleteURL(client, id), nil)
return res
}
// Get retrieves the Snapshot with the provided ID. To extract the Snapshot
// object from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
return res
}
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToSnapshotListQuery() (string, error)
}
// ListOpts hold options for listing Snapshots. It is passed to the
// snapshots.List function.
type ListOpts struct {
Name string `q:"display_name"`
Status string `q:"status"`
VolumeID string `q:"volume_id"`
}
// ToSnapshotListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToSnapshotListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// List returns Snapshots optionally limited by the conditions provided in
// ListOpts.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToSnapshotListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
createPage := func(r pagination.PageResult) pagination.Page {
return ListResult{pagination.SinglePageBase(r)}
}
return pagination.NewPager(client, url, createPage)
}
// UpdateMetadataOptsBuilder allows extensions to add additional parameters to
// the Update request.
type UpdateMetadataOptsBuilder interface {
ToSnapshotUpdateMetadataMap() (map[string]interface{}, error)
}
// UpdateMetadataOpts contain options for updating an existing Snapshot. This
// object is passed to the snapshots.Update function. For more information
// about the parameters, see the Snapshot object.
type UpdateMetadataOpts struct {
Metadata map[string]interface{}
}
// ToSnapshotUpdateMetadataMap assembles a request body based on the contents of
// an UpdateMetadataOpts.
func (opts UpdateMetadataOpts) ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) {
v := make(map[string]interface{})
if opts.Metadata != nil {
v["metadata"] = opts.Metadata
}
return v, nil
}
// UpdateMetadata will update the Snapshot with provided information. To
// extract the updated Snapshot from the response, call the ExtractMetadata
// method on the UpdateMetadataResult.
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) UpdateMetadataResult {
var res UpdateMetadataResult
reqBody, err := opts.ToSnapshotUpdateMetadataMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Put(updateMetadataURL(client, id), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}

View File

@@ -0,0 +1,104 @@
package snapshots
import (
"testing"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
"github.com/rackspace/gophercloud/testhelper/client"
)
func TestList(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
MockListResponse(t)
count := 0
List(client.ServiceClient(), &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()
MockGetResponse(t)
v, err := Get(client.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()
MockCreateResponse(t)
options := CreateOpts{VolumeID: "1234", Name: "snapshot-001"}
n, err := Create(client.ServiceClient(), options).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, n.VolumeID, "1234")
th.AssertEquals(t, n.Name, "snapshot-001")
th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
}
func TestUpdateMetadata(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
MockUpdateMetadataResponse(t)
expected := map[string]interface{}{"key": "v1"}
options := &UpdateMetadataOpts{
Metadata: map[string]interface{}{
"key": "v1",
},
}
actual, err := UpdateMetadata(client.ServiceClient(), "123", options).ExtractMetadata()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, actual, expected)
}
func TestDelete(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
MockDeleteResponse(t)
res := Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22")
th.AssertNoErr(t, res.Err)
}

View File

@@ -0,0 +1,123 @@
package snapshots
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
)
// Snapshot contains all the information associated with an OpenStack Snapshot.
type Snapshot struct {
// Currect status of the Snapshot.
Status string `mapstructure:"status"`
// Display name.
Name string `mapstructure:"display_name"`
// Instances onto which the Snapshot is attached.
Attachments []string `mapstructure:"attachments"`
// Logical group.
AvailabilityZone string `mapstructure:"availability_zone"`
// Is the Snapshot bootable?
Bootable string `mapstructure:"bootable"`
// Date created.
CreatedAt string `mapstructure:"created_at"`
// Display description.
Description string `mapstructure:"display_discription"`
// See VolumeType object for more information.
VolumeType string `mapstructure:"volume_type"`
// ID of the Snapshot from which this Snapshot was created.
SnapshotID string `mapstructure:"snapshot_id"`
// ID of the Volume from which this Snapshot was created.
VolumeID string `mapstructure:"volume_id"`
// User-defined key-value pairs.
Metadata map[string]string `mapstructure:"metadata"`
// Unique identifier.
ID string `mapstructure:"id"`
// Size of the Snapshot, in GB.
Size int `mapstructure:"size"`
}
// CreateResult contains the response body and error from a Create request.
type CreateResult struct {
commonResult
}
// GetResult contains the response body and error from a Get request.
type GetResult struct {
commonResult
}
// DeleteResult contains the response body and error from a Delete request.
type DeleteResult struct {
gophercloud.ErrResult
}
// ListResult is a pagination.Pager that is returned from a call to the List function.
type ListResult struct {
pagination.SinglePageBase
}
// IsEmpty returns true if a ListResult contains no Snapshots.
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 Snapshots. It is used while iterating over a snapshots.List call.
func ExtractSnapshots(page pagination.Page) ([]Snapshot, error) {
var response struct {
Snapshots []Snapshot `json:"snapshots"`
}
err := mapstructure.Decode(page.(ListResult).Body, &response)
return response.Snapshots, err
}
// UpdateMetadataResult contains the response body and error from an UpdateMetadata request.
type UpdateMetadataResult struct {
commonResult
}
// ExtractMetadata returns the metadata from a response from snapshots.UpdateMetadata.
func (r UpdateMetadataResult) ExtractMetadata() (map[string]interface{}, error) {
if r.Err != nil {
return nil, r.Err
}
m := r.Body.(map[string]interface{})["metadata"]
return m.(map[string]interface{}), nil
}
type commonResult struct {
gophercloud.Result
}
// Extract will get the Snapshot object out of the commonResult object.
func (r commonResult) Extract() (*Snapshot, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
Snapshot *Snapshot `json:"snapshot"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Snapshot, err
}

View File

@@ -0,0 +1,27 @@
package snapshots
import "github.com/rackspace/gophercloud"
func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("snapshots")
}
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 updateMetadataURL(c *gophercloud.ServiceClient, id string) string {
return metadataURL(c, id)
}

View File

@@ -0,0 +1,50 @@
package snapshots
import (
"testing"
"github.com/rackspace/gophercloud"
th "github.com/rackspace/gophercloud/testhelper"
)
const endpoint = "http://localhost:57909"
func endpointClient() *gophercloud.ServiceClient {
return &gophercloud.ServiceClient{Endpoint: endpoint}
}
func TestCreateURL(t *testing.T) {
actual := createURL(endpointClient())
expected := endpoint + "snapshots"
th.AssertEquals(t, expected, actual)
}
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 TestUpdateMetadataURL(t *testing.T) {
actual := updateMetadataURL(endpointClient(), "foo")
expected := endpoint + "snapshots/foo/metadata"
th.AssertEquals(t, expected, actual)
}

View File

@@ -0,0 +1,22 @@
package snapshots
import (
"github.com/rackspace/gophercloud"
)
// WaitForStatus will continually poll the resource, checking for a particular
// status. It will do this for the amount of seconds defined.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@@ -0,0 +1,5 @@
// Package volumes provides information and interaction with volumes in the
// OpenStack Block Storage service. A volume is a detachable block storage
// device, akin to a USB hard drive. It can only be attached to one instance at
// a time.
package volumes

View File

@@ -0,0 +1,113 @@
package volumes
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func MockListResponse(t *testing.T) {
th.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.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"
}
]
}
`)
})
}
func MockGetResponse(t *testing.T) {
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", fake.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",
"attachments": [
{
"device": "/dev/vde",
"server_id": "a740d24b-dc5b-4d59-ac75-53971c2920ba",
"id": "d6da11e5-2ed3-413e-88d8-b772ba62193d",
"volume_id": "d6da11e5-2ed3-413e-88d8-b772ba62193d"
}
]
}
}
`)
})
}
func MockCreateResponse(t *testing.T) {
th.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Content-Type", "application/json")
th.TestHeader(t, r, "Accept", "application/json")
th.TestJSONRequest(t, r, `
{
"volume": {
"size": 75
}
}
`)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, `
{
"volume": {
"size": 4,
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
}
}
`)
})
}
func MockDeleteResponse(t *testing.T) {
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", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
}
func MockUpdateResponse(t *testing.T) {
th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"volume": {
"display_name": "vol-002",
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
}
}
`)
})
}

View File

@@ -0,0 +1,203 @@
package volumes
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToVolumeCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains options for creating a Volume. This object is passed to
// the volumes.Create function. For more information about these parameters,
// see the Volume object.
type CreateOpts struct {
// OPTIONAL
Availability string
// OPTIONAL
Description string
// OPTIONAL
Metadata map[string]string
// OPTIONAL
Name string
// REQUIRED
Size int
// OPTIONAL
SnapshotID, SourceVolID, ImageID string
// OPTIONAL
VolumeType string
}
// ToVolumeCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
v := make(map[string]interface{})
if opts.Size == 0 {
return nil, fmt.Errorf("Required CreateOpts field 'Size' not set.")
}
v["size"] = opts.Size
if opts.Availability != "" {
v["availability_zone"] = opts.Availability
}
if opts.Description != "" {
v["display_description"] = opts.Description
}
if opts.ImageID != "" {
v["imageRef"] = opts.ImageID
}
if opts.Metadata != nil {
v["metadata"] = opts.Metadata
}
if opts.Name != "" {
v["display_name"] = opts.Name
}
if opts.SourceVolID != "" {
v["source_volid"] = opts.SourceVolID
}
if opts.SnapshotID != "" {
v["snapshot_id"] = opts.SnapshotID
}
if opts.VolumeType != "" {
v["volume_type"] = opts.VolumeType
}
return map[string]interface{}{"volume": v}, nil
}
// Create will create a new Volume based on the values in CreateOpts. To extract
// the Volume object from the response, call the Extract method on the
// CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToVolumeCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return res
}
// Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
var res DeleteResult
_, res.Err = client.Delete(deleteURL(client, id), nil)
return res
}
// Get retrieves the Volume with the provided ID. To extract the Volume object
// from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
return res
}
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToVolumeListQuery() (string, error)
}
// ListOpts holds options for listing Volumes. It is passed to the volumes.List
// function.
type ListOpts struct {
// admin-only option. Set it to true to see all tenant volumes.
AllTenants bool `q:"all_tenants"`
// List only volumes that contain Metadata.
Metadata map[string]string `q:"metadata"`
// List only volumes that have Name as the display name.
Name string `q:"name"`
// List only volumes that have a status of Status.
Status string `q:"status"`
}
// ToVolumeListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToVolumeListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// List returns Volumes optionally limited by the conditions provided in ListOpts.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToVolumeListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
createPage := func(r pagination.PageResult) pagination.Page {
return ListResult{pagination.SinglePageBase(r)}
}
return pagination.NewPager(client, url, createPage)
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToVolumeUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contain options for updating an existing Volume. This object is passed
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
// OPTIONAL
Name string
// OPTIONAL
Description string
// OPTIONAL
Metadata map[string]string
}
// ToVolumeUpdateMap assembles a request body based on the contents of an
// UpdateOpts.
func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
v := make(map[string]interface{})
if opts.Description != "" {
v["display_description"] = opts.Description
}
if opts.Metadata != nil {
v["metadata"] = opts.Metadata
}
if opts.Name != "" {
v["display_name"] = opts.Name
}
return map[string]interface{}{"volume": v}, nil
}
// Update will update the Volume with provided information. To extract the updated
// Volume from the response, call the Extract method on the UpdateResult.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToVolumeUpdateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Put(updateURL(client, id), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}

View File

@@ -0,0 +1,122 @@
package volumes
import (
"testing"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
"github.com/rackspace/gophercloud/testhelper/client"
)
func TestList(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
MockListResponse(t)
count := 0
List(client.ServiceClient(), &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 TestListAll(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
MockListResponse(t)
allPages, err := List(client.ServiceClient(), &ListOpts{}).AllPages()
th.AssertNoErr(t, err)
actual, err := ExtractVolumes(allPages)
th.AssertNoErr(t, 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)
}
func TestGet(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
MockGetResponse(t)
v, err := Get(client.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")
th.AssertEquals(t, v.Attachments[0]["device"], "/dev/vde")
}
func TestCreate(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
MockCreateResponse(t)
options := &CreateOpts{Size: 75}
n, err := Create(client.ServiceClient(), options).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Size, 4)
th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
}
func TestDelete(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
MockDeleteResponse(t)
res := Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22")
th.AssertNoErr(t, res.Err)
}
func TestUpdate(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
MockUpdateResponse(t)
options := UpdateOpts{Name: "vol-002"}
v, err := Update(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract()
th.AssertNoErr(t, err)
th.CheckEquals(t, "vol-002", v.Name)
}

View File

@@ -0,0 +1,113 @@
package volumes
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
)
// Volume contains all the information associated with an OpenStack Volume.
type Volume struct {
// Current status of the volume.
Status string `mapstructure:"status"`
// Human-readable display name for the volume.
Name string `mapstructure:"display_name"`
// Instances onto which the volume is attached.
Attachments []map[string]interface{} `mapstructure:"attachments"`
// This parameter is no longer used.
AvailabilityZone string `mapstructure:"availability_zone"`
// Indicates whether this is a bootable volume.
Bootable string `mapstructure:"bootable"`
// The date when this volume was created.
CreatedAt string `mapstructure:"created_at"`
// Human-readable description for the volume.
Description string `mapstructure:"display_description"`
// The type of volume to create, either SATA or SSD.
VolumeType string `mapstructure:"volume_type"`
// The ID of the snapshot from which the volume was created
SnapshotID string `mapstructure:"snapshot_id"`
// The ID of another block storage volume from which the current volume was created
SourceVolID string `mapstructure:"source_volid"`
// Arbitrary key-value pairs defined by the user.
Metadata map[string]string `mapstructure:"metadata"`
// Unique identifier for the volume.
ID string `mapstructure:"id"`
// Size of the volume in GB.
Size int `mapstructure:"size"`
}
// CreateResult contains the response body and error from a Create request.
type CreateResult struct {
commonResult
}
// GetResult contains the response body and error from a Get request.
type GetResult struct {
commonResult
}
// DeleteResult contains the response body and error from a Delete request.
type DeleteResult struct {
gophercloud.ErrResult
}
// ListResult is a pagination.pager that is returned from a call to the List function.
type ListResult struct {
pagination.SinglePageBase
}
// IsEmpty returns true if a ListResult contains no Volumes.
func (r ListResult) IsEmpty() (bool, error) {
volumes, err := ExtractVolumes(r)
if err != nil {
return true, err
}
return len(volumes) == 0, nil
}
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
func ExtractVolumes(page pagination.Page) ([]Volume, error) {
var response struct {
Volumes []Volume `json:"volumes"`
}
err := mapstructure.Decode(page.(ListResult).Body, &response)
return response.Volumes, err
}
// UpdateResult contains the response body and error from an Update request.
type UpdateResult struct {
commonResult
}
type commonResult struct {
gophercloud.Result
}
// Extract will get the Volume object out of the commonResult object.
func (r commonResult) Extract() (*Volume, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
Volume *Volume `json:"volume"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Volume, err
}

View File

@@ -0,0 +1,23 @@
package volumes
import "github.com/rackspace/gophercloud"
func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("volumes")
}
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)
}

View File

@@ -0,0 +1,44 @@
package volumes
import (
"testing"
"github.com/rackspace/gophercloud"
th "github.com/rackspace/gophercloud/testhelper"
)
const endpoint = "http://localhost:57909"
func endpointClient() *gophercloud.ServiceClient {
return &gophercloud.ServiceClient{Endpoint: endpoint}
}
func TestCreateURL(t *testing.T) {
actual := createURL(endpointClient())
expected := endpoint + "volumes"
th.AssertEquals(t, expected, actual)
}
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)
}

View File

@@ -0,0 +1,22 @@
package volumes
import (
"github.com/rackspace/gophercloud"
)
// WaitForStatus will continually poll the resource, checking for a particular
// status. It will do this for the amount of seconds defined.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@@ -0,0 +1,9 @@
// Package volumetypes provides information and interaction with volume types
// in the OpenStack Block Storage service. A volume type indicates the type of
// a block storage volume, such as SATA, SCSCI, SSD, etc. These can be
// customized or defined by the OpenStack admin.
//
// You can also define extra_specs associated with your volume types. For
// instance, you could have a VolumeType=SATA, with extra_specs (RPM=10000,
// RAID-Level=5) . Extra_specs are defined and customized by the admin.
package volumetypes

View File

@@ -0,0 +1,60 @@
package volumetypes
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func MockListResponse(t *testing.T) {
th.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"volume_types": [
{
"id": "289da7f8-6440-407c-9fb4-7db01ec49164",
"name": "vol-type-001",
"extra_specs": {
"capabilities": "gpu"
}
},
{
"id": "96c3bda7-c82a-4f50-be73-ca7621794835",
"name": "vol-type-002",
"extra_specs": {}
}
]
}
`)
})
}
func MockGetResponse(t *testing.T) {
th.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"volume_type": {
"name": "vol-type-001",
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
"extra_specs": {
"serverNumber": "2"
}
}
}
`)
})
}

View File

@@ -0,0 +1,76 @@
package volumetypes
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToVolumeTypeCreateMap() (map[string]interface{}, error)
}
// CreateOpts are options for creating a volume type.
type CreateOpts struct {
// OPTIONAL. See VolumeType.
ExtraSpecs map[string]interface{}
// OPTIONAL. See VolumeType.
Name string
}
// ToVolumeTypeCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToVolumeTypeCreateMap() (map[string]interface{}, error) {
vt := make(map[string]interface{})
if opts.ExtraSpecs != nil {
vt["extra_specs"] = opts.ExtraSpecs
}
if opts.Name != "" {
vt["name"] = opts.Name
}
return map[string]interface{}{"volume_type": vt}, nil
}
// Create will create a new volume. To extract the created volume type object,
// call the Extract method on the CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToVolumeTypeCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return res
}
// Delete will delete the volume type with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
var res DeleteResult
_, res.Err = client.Delete(deleteURL(client, id), nil)
return res
}
// Get will retrieve the volume type with the provided ID. To extract the volume
// type from the result, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, err := client.Get(getURL(client, id), &res.Body, nil)
res.Err = err
return res
}
// List returns all volume types.
func List(client *gophercloud.ServiceClient) pagination.Pager {
createPage := func(r pagination.PageResult) pagination.Page {
return ListResult{pagination.SinglePageBase(r)}
}
return pagination.NewPager(client, listURL(client), createPage)
}

View File

@@ -0,0 +1,118 @@
package volumetypes
import (
"fmt"
"net/http"
"testing"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
"github.com/rackspace/gophercloud/testhelper/client"
)
func TestList(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
MockListResponse(t)
count := 0
List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
count++
actual, err := ExtractVolumeTypes(page)
if err != nil {
t.Errorf("Failed to extract volume types: %v", err)
return false, err
}
expected := []VolumeType{
VolumeType{
ID: "289da7f8-6440-407c-9fb4-7db01ec49164",
Name: "vol-type-001",
ExtraSpecs: map[string]interface{}{
"capabilities": "gpu",
},
},
VolumeType{
ID: "96c3bda7-c82a-4f50-be73-ca7621794835",
Name: "vol-type-002",
ExtraSpecs: map[string]interface{}{},
},
}
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()
MockGetResponse(t)
vt, err := Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, vt.ExtraSpecs, map[string]interface{}{"serverNumber": "2"})
th.AssertEquals(t, vt.Name, "vol-type-001")
th.AssertEquals(t, vt.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
}
func TestCreate(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
th.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestHeader(t, r, "Content-Type", "application/json")
th.TestHeader(t, r, "Accept", "application/json")
th.TestJSONRequest(t, r, `
{
"volume_type": {
"name": "vol-type-001"
}
}
`)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, `
{
"volume_type": {
"name": "vol-type-001",
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
}
}
`)
})
options := &CreateOpts{Name: "vol-type-001"}
n, err := Create(client.ServiceClient(), options).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Name, "vol-type-001")
th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
}
func TestDelete(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
th.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
w.WriteHeader(http.StatusAccepted)
})
err := Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractErr()
th.AssertNoErr(t, err)
}

View File

@@ -0,0 +1,72 @@
package volumetypes
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// VolumeType contains all information associated with an OpenStack Volume Type.
type VolumeType struct {
ExtraSpecs map[string]interface{} `json:"extra_specs" mapstructure:"extra_specs"` // user-defined metadata
ID string `json:"id" mapstructure:"id"` // unique identifier
Name string `json:"name" mapstructure:"name"` // display name
}
// CreateResult contains the response body and error from a Create request.
type CreateResult struct {
commonResult
}
// GetResult contains the response body and error from a Get request.
type GetResult struct {
commonResult
}
// DeleteResult contains the response error from a Delete request.
type DeleteResult struct {
gophercloud.ErrResult
}
// ListResult is a pagination.Pager that is returned from a call to the List function.
type ListResult struct {
pagination.SinglePageBase
}
// IsEmpty returns true if a ListResult contains no Volume Types.
func (r ListResult) IsEmpty() (bool, error) {
volumeTypes, err := ExtractVolumeTypes(r)
if err != nil {
return true, err
}
return len(volumeTypes) == 0, nil
}
// ExtractVolumeTypes extracts and returns Volume Types.
func ExtractVolumeTypes(page pagination.Page) ([]VolumeType, error) {
var response struct {
VolumeTypes []VolumeType `mapstructure:"volume_types"`
}
err := mapstructure.Decode(page.(ListResult).Body, &response)
return response.VolumeTypes, err
}
type commonResult struct {
gophercloud.Result
}
// Extract will get the Volume Type object out of the commonResult object.
func (r commonResult) Extract() (*VolumeType, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
VolumeType *VolumeType `json:"volume_type" mapstructure:"volume_type"`
}
err := mapstructure.Decode(r.Body, &res)
return res.VolumeType, err
}

View File

@@ -0,0 +1,19 @@
package volumetypes
import "github.com/rackspace/gophercloud"
func listURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("types")
}
func createURL(c *gophercloud.ServiceClient) string {
return listURL(c)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("types", id)
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return getURL(c, id)
}

View File

@@ -0,0 +1,38 @@
package volumetypes
import (
"testing"
"github.com/rackspace/gophercloud"
th "github.com/rackspace/gophercloud/testhelper"
)
const endpoint = "http://localhost:57909"
func endpointClient() *gophercloud.ServiceClient {
return &gophercloud.ServiceClient{Endpoint: endpoint}
}
func TestListURL(t *testing.T) {
actual := listURL(endpointClient())
expected := endpoint + "types"
th.AssertEquals(t, expected, actual)
}
func TestCreateURL(t *testing.T) {
actual := createURL(endpointClient())
expected := endpoint + "types"
th.AssertEquals(t, expected, actual)
}
func TestGetURL(t *testing.T) {
actual := getURL(endpointClient(), "foo")
expected := endpoint + "types/foo"
th.AssertEquals(t, expected, actual)
}
func TestDeleteURL(t *testing.T) {
actual := deleteURL(endpointClient(), "foo")
expected := endpoint + "types/foo"
th.AssertEquals(t, expected, actual)
}