Adding list and get operations for subnets
diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go
index b15101b..9d23e56 100644
--- a/openstack/networking/v2/subnets/requests.go
+++ b/openstack/networking/v2/subnets/requests.go
@@ -1 +1,70 @@
 package subnets
+
+import (
+	"strconv"
+
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/openstack/utils"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+type ListOpts struct {
+	Name       string
+	EnableDHCP *bool
+	NetworkID  string
+	TenantID   string
+	IPVersion  int
+	GatewayIP  string
+	CIDR       string
+	ID         string
+}
+
+func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
+	// Build query parameters
+	q := make(map[string]string)
+	if opts.Name != "" {
+		q["name"] = opts.Name
+	}
+	if opts.EnableDHCP != nil {
+		q["enable_dhcp"] = strconv.FormatBool(*opts.EnableDHCP)
+	}
+	if opts.NetworkID != "" {
+		q["network_id"] = opts.NetworkID
+	}
+	if opts.TenantID != "" {
+		q["tenant_id"] = opts.TenantID
+	}
+	if opts.IPVersion != 0 {
+		q["ip_version"] = strconv.Itoa(opts.IPVersion)
+	}
+	if opts.GatewayIP != "" {
+		q["gateway_ip"] = opts.GatewayIP
+	}
+	if opts.CIDR != "" {
+		q["cidr"] = opts.CIDR
+	}
+	if opts.ID != "" {
+		q["id"] = opts.ID
+	}
+
+	u := ListURL(c) + utils.BuildQuery(q)
+	return pagination.NewPager(c, u, func(r pagination.LastHTTPResponse) pagination.Page {
+		return SubnetPage{pagination.LinkedPageBase(r)}
+	})
+}
+
+func Get(c *gophercloud.ServiceClient, id string) (*Subnet, error) {
+	var s Subnet
+	_, err := perigee.Request("GET", GetURL(c, id), perigee.Options{
+		MoreHeaders: c.Provider.AuthenticatedHeaders(),
+		Results: &struct {
+			Subnet *Subnet `json:"subnet"`
+		}{&s},
+		OkCodes: []int{200},
+	})
+	if err != nil {
+		return nil, err
+	}
+	return &s, nil
+}
diff --git a/openstack/networking/v2/subnets/requests_test.go b/openstack/networking/v2/subnets/requests_test.go
index 48d241f..fb21ff3 100644
--- a/openstack/networking/v2/subnets/requests_test.go
+++ b/openstack/networking/v2/subnets/requests_test.go
@@ -1,7 +1,192 @@
 package subnets
 
-import "testing"
+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("/v2.0/subnets", 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, `
+{
+    "subnets": [
+        {
+            "name": "private-subnet",
+            "enable_dhcp": true,
+            "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+            "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+            "dns_nameservers": [],
+            "allocation_pools": [
+                {
+                    "start": "10.0.0.2",
+                    "end": "10.0.0.254"
+                }
+            ],
+            "host_routes": [],
+            "ip_version": 4,
+            "gateway_ip": "10.0.0.1",
+            "cidr": "10.0.0.0/24",
+            "id": "08eae331-0402-425a-923c-34f7cfe39c1b"
+        },
+        {
+            "name": "my_subnet",
+            "enable_dhcp": true,
+            "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+            "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+            "dns_nameservers": [],
+            "allocation_pools": [
+                {
+                    "start": "192.0.0.2",
+                    "end": "192.255.255.254"
+                }
+            ],
+            "host_routes": [],
+            "ip_version": 4,
+            "gateway_ip": "192.0.0.1",
+            "cidr": "192.0.0.0/8",
+            "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+        }
+    ]
+}
+      `)
+	})
+
+	count := 0
+
+	List(ServiceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractSubnets(page)
+		if err != nil {
+			t.Errorf("Failed to extract subnets: %v", err)
+			return false, nil
+		}
+
+		expected := []Subnet{
+			Subnet{
+				Name:           "private-subnet",
+				EnableDHCP:     true,
+				NetworkID:      "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+				TenantID:       "26a7980765d0414dbc1fc1f88cdb7e6e",
+				DNSNameservers: []interface{}{},
+				AllocationPools: []AllocationPool{
+					AllocationPool{
+						Start: "10.0.0.2",
+						End:   "10.0.0.254",
+					},
+				},
+				HostRoutes: []interface{}{},
+				IPVersion:  4,
+				GatewayIP:  "10.0.0.1",
+				CIDR:       "10.0.0.0/24",
+				ID:         "08eae331-0402-425a-923c-34f7cfe39c1b",
+			},
+			Subnet{
+				Name:           "my_subnet",
+				EnableDHCP:     true,
+				NetworkID:      "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+				TenantID:       "4fd44f30292945e481c7b8a0c8908869",
+				DNSNameservers: []interface{}{},
+				AllocationPools: []AllocationPool{
+					AllocationPool{
+						Start: "192.0.0.2",
+						End:   "192.255.255.254",
+					},
+				},
+				HostRoutes: []interface{}{},
+				IPVersion:  4,
+				GatewayIP:  "192.0.0.1",
+				CIDR:       "192.0.0.0/8",
+				ID:         "54d6f61d-db07-451c-9ab3-b9609b6b6f0b",
+			},
+		}
+
+		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("/v2.0/subnets/54d6f61d-db07-451c-9ab3-b9609b6b6f0b", 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, `
+{
+    "subnet": {
+        "name": "my_subnet",
+        "enable_dhcp": true,
+        "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+        "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+        "dns_nameservers": [],
+        "allocation_pools": [
+            {
+                "start": "192.0.0.2",
+                "end": "192.255.255.254"
+            }
+        ],
+        "host_routes": [],
+        "ip_version": 4,
+        "gateway_ip": "192.0.0.1",
+        "cidr": "192.0.0.0/8",
+        "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+    }
+}
+			`)
+	})
+
+	s, err := Get(ServiceClient(), "54d6f61d-db07-451c-9ab3-b9609b6b6f0b")
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, s.Name, "my_subnet")
+	th.AssertEquals(t, s.EnableDHCP, true)
+	th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
+	th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869")
+	th.AssertDeepEquals(t, s.DNSNameservers, []interface{}{})
+	th.AssertDeepEquals(t, s.AllocationPools, []AllocationPool{
+		AllocationPool{
+			Start: "192.0.0.2",
+			End:   "192.255.255.254",
+		},
+	})
+	th.AssertDeepEquals(t, s.HostRoutes, []interface{}{})
+	th.AssertEquals(t, s.IPVersion, 4)
+	th.AssertEquals(t, s.GatewayIP, "192.0.0.1")
+	th.AssertEquals(t, s.CIDR, "192.0.0.0/8")
+	th.AssertEquals(t, s.ID, "54d6f61d-db07-451c-9ab3-b9609b6b6f0b")
 }
diff --git a/openstack/networking/v2/subnets/results.go b/openstack/networking/v2/subnets/results.go
index b15101b..1790028 100644
--- a/openstack/networking/v2/subnets/results.go
+++ b/openstack/networking/v2/subnets/results.go
@@ -1 +1,78 @@
 package subnets
+
+import (
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+type AllocationPool struct {
+	Start string
+	End   string
+}
+
+type Subnet struct {
+	Name            string           `mapstructure:"name" json:"name"`
+	EnableDHCP      bool             `mapstructure:"enable_dhcp" json:"enable_dhcp"`
+	NetworkID       string           `mapstructure:"network_id" json:"network_id"`
+	TenantID        string           `mapstructure:"tenant_id" json:"tenant_id"`
+	DNSNameservers  []interface{}    `mapstructure:"dns_nameservers" json:"dns_nameservers"`
+	AllocationPools []AllocationPool `mapstructure:"allocation_pools" json:"allocation_pools"`
+	HostRoutes      []interface{}    `mapstructure:"host_routes" json:"host_routes"`
+	IPVersion       int              `mapstructure:"ip_version" json:"ip_version"`
+	GatewayIP       string           `mapstructure:"gateway_ip" json:"gateway_ip"`
+	CIDR            string           `mapstructure:"cidr" json:"cidr"`
+	ID              string           `mapstructure:"id" json:"id"`
+}
+
+type SubnetPage struct {
+	pagination.LinkedPageBase
+}
+
+func (current SubnetPage) NextPageURL() (string, error) {
+	type link struct {
+		Href string `mapstructure:"href"`
+		Rel  string `mapstructure:"rel"`
+	}
+	type resp struct {
+		Links []link `mapstructure:"subnets_links"`
+	}
+
+	var r resp
+	err := mapstructure.Decode(current.Body, &r)
+	if err != nil {
+		return "", err
+	}
+
+	var url string
+	for _, l := range r.Links {
+		if l.Rel == "next" {
+			url = l.Href
+		}
+	}
+	if url == "" {
+		return "", nil
+	}
+
+	return url, nil
+}
+
+func (r SubnetPage) IsEmpty() (bool, error) {
+	is, err := ExtractSubnets(r)
+	if err != nil {
+		return true, nil
+	}
+	return len(is) == 0, nil
+}
+
+func ExtractSubnets(page pagination.Page) ([]Subnet, error) {
+	var resp struct {
+		Subnets []Subnet `mapstructure:"subnets" json:"subnets"`
+	}
+
+	err := mapstructure.Decode(page.(SubnetPage).Body, &resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp.Subnets, nil
+}
diff --git a/openstack/networking/v2/subnets/urls.go b/openstack/networking/v2/subnets/urls.go
index b15101b..2cf128b 100644
--- a/openstack/networking/v2/subnets/urls.go
+++ b/openstack/networking/v2/subnets/urls.go
@@ -1 +1,21 @@
 package subnets
+
+import "github.com/rackspace/gophercloud"
+
+const Version = "v2.0"
+
+func ResourceURL(c *gophercloud.ServiceClient, id string) string {
+	return c.ServiceURL(Version, "subnets", id)
+}
+
+func RootURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL(Version, "subnets")
+}
+
+func ListURL(c *gophercloud.ServiceClient) string {
+	return RootURL(c)
+}
+
+func GetURL(c *gophercloud.ServiceClient, id string) string {
+	return ResourceURL(c, id)
+}
diff --git a/openstack/networking/v2/subnets/urls_tests.go b/openstack/networking/v2/subnets/urls_tests.go
index b15101b..95b179f 100644
--- a/openstack/networking/v2/subnets/urls_tests.go
+++ b/openstack/networking/v2/subnets/urls_tests.go
@@ -1 +1,26 @@
 package subnets
+
+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 + "v2.0/subnets"
+	th.AssertEquals(t, expected, actual)
+}
+
+func TestGetURL(t *testing.T) {
+	actual := GetURL(EndpointClient(), "foo")
+	expected := Endpoint + "v2.0/subnets/foo"
+	th.AssertEquals(t, expected, actual)
+}