Add support for OS flavors
diff --git a/openstack/db/v1/flavors/doc.go b/openstack/db/v1/flavors/doc.go
new file mode 100644
index 0000000..5822e1b
--- /dev/null
+++ b/openstack/db/v1/flavors/doc.go
@@ -0,0 +1,7 @@
+// Package flavors provides information and interaction with the flavor API
+// resource in the OpenStack Compute service.
+//
+// A flavor is an available hardware configuration for a server. Each flavor
+// has a unique combination of disk space, memory capacity and priority for CPU
+// time.
+package flavors
diff --git a/openstack/db/v1/flavors/fixtures.go b/openstack/db/v1/flavors/fixtures.go
new file mode 100644
index 0000000..653b5c8
--- /dev/null
+++ b/openstack/db/v1/flavors/fixtures.go
@@ -0,0 +1,113 @@
+package flavors
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ th "github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func HandleListFlavorsSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/flavors", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "flavors": [
+ {
+ "id": 1,
+ "links": [
+ {
+ "href": "https://openstack.example.com/v1.0/1234/flavors/1",
+ "rel": "self"
+ },
+ {
+ "href": "https://openstack.example.com/flavors/1",
+ "rel": "bookmark"
+ }
+ ],
+ "name": "m1.tiny",
+ "ram": 512
+ },
+ {
+ "id": 2,
+ "links": [
+ {
+ "href": "https://openstack.example.com/v1.0/1234/flavors/2",
+ "rel": "self"
+ },
+ {
+ "href": "https://openstack.example.com/flavors/2",
+ "rel": "bookmark"
+ }
+ ],
+ "name": "m1.small",
+ "ram": 1024
+ },
+ {
+ "id": 3,
+ "links": [
+ {
+ "href": "https://openstack.example.com/v1.0/1234/flavors/3",
+ "rel": "self"
+ },
+ {
+ "href": "https://openstack.example.com/flavors/3",
+ "rel": "bookmark"
+ }
+ ],
+ "name": "m1.medium",
+ "ram": 2048
+ },
+ {
+ "id": 4,
+ "links": [
+ {
+ "href": "https://openstack.example.com/v1.0/1234/flavors/4",
+ "rel": "self"
+ },
+ {
+ "href": "https://openstack.example.com/flavors/4",
+ "rel": "bookmark"
+ }
+ ],
+ "name": "m1.large",
+ "ram": 4096
+ }
+ ]
+}
+`)
+ })
+}
+
+func HandleGetFlavorSuccessfully(t *testing.T, flavorID string) {
+ th.Mux.HandleFunc("/flavors/"+flavorID, func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "flavor": {
+ "id": 1,
+ "links": [
+ {
+ "href": "https://openstack.example.com/v1.0/1234/flavors/1",
+ "rel": "self"
+ }
+ ],
+ "name": "m1.tiny",
+ "ram": 512
+ }
+}
+`)
+ })
+}
diff --git a/openstack/db/v1/flavors/requests.go b/openstack/db/v1/flavors/requests.go
new file mode 100644
index 0000000..3799469
--- /dev/null
+++ b/openstack/db/v1/flavors/requests.go
@@ -0,0 +1,24 @@
+package flavors
+
+import (
+ "github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+func List(client *gophercloud.ServiceClient) pagination.Pager {
+ createPage := func(r pagination.PageResult) pagination.Page {
+ return FlavorPage{pagination.LinkedPageBase{PageResult: r}}
+ }
+
+ return pagination.NewPager(client, listURL(client), createPage)
+}
+
+func Get(client *gophercloud.ServiceClient, id string) GetResult {
+ var gr GetResult
+ gr.Err = perigee.Get(getURL(client, id), perigee.Options{
+ Results: &gr.Body,
+ MoreHeaders: client.AuthenticatedHeaders(),
+ })
+ return gr
+}
diff --git a/openstack/db/v1/flavors/requests_test.go b/openstack/db/v1/flavors/requests_test.go
new file mode 100644
index 0000000..9ed9e59
--- /dev/null
+++ b/openstack/db/v1/flavors/requests_test.go
@@ -0,0 +1,96 @@
+package flavors
+
+import (
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestListFlavors(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleListFlavorsSuccessfully(t)
+
+ pages := 0
+ err := List(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
+ pages++
+
+ actual, err := ExtractFlavors(page)
+ if err != nil {
+ return false, err
+ }
+
+ expected := []Flavor{
+ Flavor{
+ ID: 1,
+ Name: "m1.tiny",
+ RAM: 512,
+ Links: []gophercloud.Link{
+ gophercloud.Link{Href: "https://openstack.example.com/v1.0/1234/flavors/1", Rel: "self"},
+ gophercloud.Link{Href: "https://openstack.example.com/flavors/1", Rel: "bookmark"},
+ },
+ },
+ Flavor{
+ ID: 2,
+ Name: "m1.small",
+ RAM: 1024,
+ Links: []gophercloud.Link{
+ gophercloud.Link{Href: "https://openstack.example.com/v1.0/1234/flavors/2", Rel: "self"},
+ gophercloud.Link{Href: "https://openstack.example.com/flavors/2", Rel: "bookmark"},
+ },
+ },
+ Flavor{
+ ID: 3,
+ Name: "m1.medium",
+ RAM: 2048,
+ Links: []gophercloud.Link{
+ gophercloud.Link{Href: "https://openstack.example.com/v1.0/1234/flavors/3", Rel: "self"},
+ gophercloud.Link{Href: "https://openstack.example.com/flavors/3", Rel: "bookmark"},
+ },
+ },
+ Flavor{
+ ID: 4,
+ Name: "m1.large",
+ RAM: 4096,
+ Links: []gophercloud.Link{
+ gophercloud.Link{Href: "https://openstack.example.com/v1.0/1234/flavors/4", Rel: "self"},
+ gophercloud.Link{Href: "https://openstack.example.com/flavors/4", Rel: "bookmark"},
+ },
+ },
+ }
+
+ th.AssertDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ th.AssertNoErr(t, err)
+ if pages != 1 {
+ t.Errorf("Expected one page, got %d", pages)
+ }
+}
+
+func TestGetFlavor(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleGetFlavorSuccessfully(t, "12345")
+
+ actual, err := Get(fake.ServiceClient(), "12345").Extract()
+ th.AssertNoErr(t, err)
+
+ expected := &Flavor{
+ ID: 1,
+ Name: "m1.tiny",
+ RAM: 512,
+ Links: []gophercloud.Link{
+ gophercloud.Link{Href: "https://openstack.example.com/v1.0/1234/flavors/1", Rel: "self"},
+ },
+ }
+
+ th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/openstack/db/v1/flavors/results.go b/openstack/db/v1/flavors/results.go
new file mode 100644
index 0000000..5aac5ce
--- /dev/null
+++ b/openstack/db/v1/flavors/results.go
@@ -0,0 +1,85 @@
+package flavors
+
+import (
+ "errors"
+
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// ErrCannotInterpret is returned by an Extract call if the response body doesn't have the expected structure.
+var ErrCannotInterpet = errors.New("Unable to interpret a response body.")
+
+// GetResult temporarily holds the response from a Get call.
+type GetResult struct {
+ gophercloud.Result
+}
+
+// Extract provides access to the individual Flavor returned by the Get function.
+func (gr GetResult) Extract() (*Flavor, error) {
+ if gr.Err != nil {
+ return nil, gr.Err
+ }
+
+ var result struct {
+ Flavor Flavor `mapstructure:"flavor"`
+ }
+
+ err := mapstructure.Decode(gr.Body, &result)
+ return &result.Flavor, err
+}
+
+// Flavor records represent (virtual) hardware configurations for server resources in a region.
+type Flavor struct {
+ // The Id field contains the flavor's unique identifier.
+ // For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
+ ID int `mapstructure:"id"`
+
+ RAM int `mapstructure:"ram"`
+
+ // The Name field provides a human-readable moniker for the flavor.
+ Name string `mapstructure:"name"`
+
+ Links []gophercloud.Link
+}
+
+// FlavorPage contains a single page of the response from a List call.
+type FlavorPage struct {
+ pagination.LinkedPageBase
+}
+
+// IsEmpty determines if a page contains any results.
+func (p FlavorPage) IsEmpty() (bool, error) {
+ flavors, err := ExtractFlavors(p)
+ if err != nil {
+ return true, err
+ }
+ return len(flavors) == 0, nil
+}
+
+// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
+func (p FlavorPage) NextPageURL() (string, error) {
+ type resp struct {
+ Links []gophercloud.Link `mapstructure:"flavors_links"`
+ }
+
+ var r resp
+ err := mapstructure.Decode(p.Body, &r)
+ if err != nil {
+ return "", err
+ }
+
+ return gophercloud.ExtractNextURL(r.Links)
+}
+
+// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
+func ExtractFlavors(page pagination.Page) ([]Flavor, error) {
+ casted := page.(FlavorPage).Body
+ var container struct {
+ Flavors []Flavor `mapstructure:"flavors"`
+ }
+
+ err := mapstructure.Decode(casted, &container)
+ return container.Flavors, err
+}
diff --git a/openstack/db/v1/flavors/urls.go b/openstack/db/v1/flavors/urls.go
new file mode 100644
index 0000000..a1b6b32
--- /dev/null
+++ b/openstack/db/v1/flavors/urls.go
@@ -0,0 +1,13 @@
+package flavors
+
+import (
+ "github.com/rackspace/gophercloud"
+)
+
+func getURL(client *gophercloud.ServiceClient, id string) string {
+ return client.ServiceURL("flavors", id)
+}
+
+func listURL(client *gophercloud.ServiceClient) string {
+ return client.ServiceURL("flavors")
+}