Compute Limits (#121)

* Compute Limits

This commit adds support for the limits API. It includes the ability
to query limits for the currently scoped user as well as to query the
limits for a specific tenant.

* Clarifying RAM measurement

* Removing ExtractAbsolute. Renaming ExtractLimits to Extract
diff --git a/openstack/compute/v2/extensions/limits/requests.go b/openstack/compute/v2/extensions/limits/requests.go
new file mode 100644
index 0000000..70324b8
--- /dev/null
+++ b/openstack/compute/v2/extensions/limits/requests.go
@@ -0,0 +1,39 @@
+package limits
+
+import (
+	"github.com/gophercloud/gophercloud"
+)
+
+// GetOptsBuilder allows extensions to add additional parameters to the
+// Get request.
+type GetOptsBuilder interface {
+	ToLimitsQuery() (string, error)
+}
+
+// GetOpts enables retrieving limits by a specific tenant.
+type GetOpts struct {
+	// The tenant ID to retrieve limits for
+	TenantID string `q:"tenant_id"`
+}
+
+// ToLimitsQuery formats a GetOpts into a query string.
+func (opts GetOpts) ToLimitsQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	return q.String(), err
+}
+
+// Get returns the limits about the currently scoped tenant.
+func Get(client *gophercloud.ServiceClient, opts GetOptsBuilder) (r GetResult) {
+	url := getURL(client)
+	if opts != nil {
+		query, err := opts.ToLimitsQuery()
+		if err != nil {
+			r.Err = err
+			return
+		}
+		url += query
+	}
+
+	_, r.Err = client.Get(url, &r.Body, nil)
+	return
+}
diff --git a/openstack/compute/v2/extensions/limits/results.go b/openstack/compute/v2/extensions/limits/results.go
new file mode 100644
index 0000000..95b794d
--- /dev/null
+++ b/openstack/compute/v2/extensions/limits/results.go
@@ -0,0 +1,90 @@
+package limits
+
+import (
+	"github.com/gophercloud/gophercloud"
+)
+
+// Limits is a struct that contains the response of a limit query.
+type Limits struct {
+	// Absolute contains the limits and usage information.
+	Absolute Absolute `json:"absolute"`
+}
+
+// Usage is a struct that contains the current resource usage and limits
+// of a tenant.
+type Absolute struct {
+	// MaxTotalCores is the number of cores available to a tenant.
+	MaxTotalCores int `json:"maxTotalCores"`
+
+	// MaxImageMeta is the amount of image metadata available to a tenant.
+	MaxImageMeta int `json:"maxImageMeta"`
+
+	// MaxServerMeta is the amount of server metadata available to a tenant.
+	MaxServerMeta int `json:"maxServerMeta"`
+
+	// MaxPersonality is the amount of personality/files available to a tenant.
+	MaxPersonality int `json:"maxPersonality"`
+
+	// MaxPersonalitySize is the personality file size available to a tenant.
+	MaxPersonalitySize int `json:"maxPersonalitySize"`
+
+	// MaxTotalKeypairs is the total keypairs available to a tenant.
+	MaxTotalKeypairs int `json:maxTotalKeypairs"`
+
+	// MaxSecurityGroups is the number of security groups available to a tenant.
+	MaxSecurityGroups int `json:"maxSecurityGroups"`
+
+	// MaxSecurityGroupRules is the number of security group rules available to
+	// a tenant.
+	MaxSecurityGroupRules int `json:"maxSecurityGroupRules"`
+
+	// MaxServerGroups is the number of server groups available to a tenant.
+	MaxServerGroups int `json:"maxServerGroups"`
+
+	// MaxServerGroupMembers is the number of server group members available
+	// to a tenant.
+	MaxServerGroupMembers int `json:"maxServerGroupMembers"`
+
+	// MaxTotalFloatingIps is the number of floating IPs available to a tenant.
+	MaxTotalFloatingIps int `json:"maxTotalFloatingIps"`
+
+	// MaxTotalInstances is the number of instances/servers available to a tenant.
+	MaxTotalInstances int `json:"maxTotalInstances"`
+
+	// MaxTotalRAMSize is the total amount of RAM available to a tenant measured
+	// in megabytes (MB).
+	MaxTotalRAMSize int `json:"maxTotalRAMSize"`
+
+	// TotalCoresUsed is the number of cores currently in use.
+	TotalCoresUsed int `json:"totalCoresUsed"`
+
+	// TotalInstancesUsed is the number of instances/servers in use.
+	TotalInstancesUsed int `json:"totalInstancesUsed"`
+
+	// TotalFloatingIpsUsed is the number of floating IPs in use.
+	TotalFloatingIpsUsed int `json:"totalFloatingIpsUsed"`
+
+	// TotalRAMUsed is the total RAM/memory in use measured in megabytes (MB).
+	TotalRAMUsed int `json:"totalRAMUsed"`
+
+	// TotalSecurityGroupsUsed is the total number of security groups in use.
+	TotalSecurityGroupsUsed int `json:"totalSecurityGroupsUsed"`
+
+	// TotalServerGroupsUsed is the total number of server groups in use.
+	TotalServerGroupsUsed int `json:"totalServerGroupsUsed"`
+}
+
+// Extract interprets a limits result as a Limits.
+func (r GetResult) Extract() (*Limits, error) {
+	var s struct {
+		Limits *Limits `json:"limits"`
+	}
+	err := r.ExtractInto(&s)
+	return s.Limits, err
+}
+
+// GetResult is the response from a Get operation. Call its ExtractAbsolute
+// method to interpret it as an Absolute.
+type GetResult struct {
+	gophercloud.Result
+}
diff --git a/openstack/compute/v2/extensions/limits/testing/fixtures.go b/openstack/compute/v2/extensions/limits/testing/fixtures.go
new file mode 100644
index 0000000..9ec24a9
--- /dev/null
+++ b/openstack/compute/v2/extensions/limits/testing/fixtures.go
@@ -0,0 +1,80 @@
+package testing
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/limits"
+	th "github.com/gophercloud/gophercloud/testhelper"
+	"github.com/gophercloud/gophercloud/testhelper/client"
+)
+
+// GetOutput is a sample response to a Get call.
+const GetOutput = `
+{
+    "limits": {
+        "rate": [],
+        "absolute": {
+            "maxServerMeta": 128,
+            "maxPersonality": 5,
+            "totalServerGroupsUsed": 0,
+            "maxImageMeta": 128,
+            "maxPersonalitySize": 10240,
+            "maxTotalKeypairs": 100,
+            "maxSecurityGroupRules": 20,
+            "maxServerGroups": 10,
+            "totalCoresUsed": 1,
+            "totalRAMUsed": 2048,
+            "totalInstancesUsed": 1,
+            "maxSecurityGroups": 10,
+            "totalFloatingIpsUsed": 0,
+            "maxTotalCores": 20,
+            "maxServerGroupMembers": 10,
+            "maxTotalFloatingIps": 10,
+            "totalSecurityGroupsUsed": 1,
+            "maxTotalInstances": 10,
+            "maxTotalRAMSize": 51200
+        }
+    }
+}
+`
+
+// LimitsResult is the result of the limits in GetOutput.
+var LimitsResult = limits.Limits{
+	limits.Absolute{
+		MaxServerMeta:           128,
+		MaxPersonality:          5,
+		TotalServerGroupsUsed:   0,
+		MaxImageMeta:            128,
+		MaxPersonalitySize:      10240,
+		MaxTotalKeypairs:        100,
+		MaxSecurityGroupRules:   20,
+		MaxServerGroups:         10,
+		TotalCoresUsed:          1,
+		TotalRAMUsed:            2048,
+		TotalInstancesUsed:      1,
+		MaxSecurityGroups:       10,
+		TotalFloatingIpsUsed:    0,
+		MaxTotalCores:           20,
+		MaxServerGroupMembers:   10,
+		MaxTotalFloatingIps:     10,
+		TotalSecurityGroupsUsed: 1,
+		MaxTotalInstances:       10,
+		MaxTotalRAMSize:         51200,
+	},
+}
+
+const TenantID = "555544443333222211110000ffffeeee"
+
+// HandleGetSuccessfully configures the test server to respond to a Get request
+// for a limit.
+func HandleGetSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/limits", 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")
+		fmt.Fprintf(w, GetOutput)
+	})
+}
diff --git a/openstack/compute/v2/extensions/limits/testing/requests_test.go b/openstack/compute/v2/extensions/limits/testing/requests_test.go
new file mode 100644
index 0000000..9c8456c
--- /dev/null
+++ b/openstack/compute/v2/extensions/limits/testing/requests_test.go
@@ -0,0 +1,23 @@
+package testing
+
+import (
+	"testing"
+
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/limits"
+	th "github.com/gophercloud/gophercloud/testhelper"
+	"github.com/gophercloud/gophercloud/testhelper/client"
+)
+
+func TestGet(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetSuccessfully(t)
+
+	getOpts := limits.GetOpts{
+		TenantID: TenantID,
+	}
+
+	actual, err := limits.Get(client.ServiceClient(), getOpts).Extract()
+	th.AssertNoErr(t, err)
+	th.CheckDeepEquals(t, &LimitsResult, actual)
+}
diff --git a/openstack/compute/v2/extensions/limits/urls.go b/openstack/compute/v2/extensions/limits/urls.go
new file mode 100644
index 0000000..edd97e4
--- /dev/null
+++ b/openstack/compute/v2/extensions/limits/urls.go
@@ -0,0 +1,11 @@
+package limits
+
+import (
+	"github.com/gophercloud/gophercloud"
+)
+
+const resourcePath = "limits"
+
+func getURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL(resourcePath)
+}