Add create subnet operation
diff --git a/openstack/networking/v2/subnets/errors.go b/openstack/networking/v2/subnets/errors.go
index b15101b..9299927 100644
--- a/openstack/networking/v2/subnets/errors.go
+++ b/openstack/networking/v2/subnets/errors.go
@@ -1 +1,13 @@
 package subnets
+
+import "fmt"
+
+func err(str string) error {
+	return fmt.Errorf("%s", str)
+}
+
+var (
+	ErrNetworkIDRequired = err("A network ID is required")
+	ErrCIDRRequired      = err("A valid CIDR is required")
+	ErrInvalidIPType     = err("An IP type must either be 4 or 6")
+)
diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go
index 9d23e56..93641d9 100644
--- a/openstack/networking/v2/subnets/requests.go
+++ b/openstack/networking/v2/subnets/requests.go
@@ -68,3 +68,94 @@
 	}
 	return &s, nil
 }
+
+const (
+	IPv4 = 4
+	IPv6 = 6
+)
+
+type SubnetOpts struct {
+	// Required
+	NetworkID string
+	CIDR      string
+	// Optional
+	Name            string
+	TenantID        string
+	AllocationPools []AllocationPool
+	GatewayIP       string
+	IPVersion       int
+	ID              string
+	EnableDHCP      *bool
+}
+
+// maybeString returns nil for empty strings and nil for empty.
+func maybeString(original string) *string {
+	if original != "" {
+		return &original
+	}
+	return nil
+}
+
+func Create(c *gophercloud.ServiceClient, opts SubnetOpts) (*Subnet, error) {
+	// Validate required options
+	if opts.NetworkID == "" {
+		return nil, ErrNetworkIDRequired
+	}
+	if opts.CIDR == "" {
+		return nil, ErrCIDRRequired
+	}
+	if opts.IPVersion != 0 && opts.IPVersion != IPv4 && opts.IPVersion != IPv6 {
+		return nil, ErrInvalidIPType
+	}
+
+	type subnet struct {
+		NetworkID       string           `json:"network_id"`
+		CIDR            string           `json:"cidr"`
+		Name            *string          `json:"name,omitempty"`
+		TenantID        *string          `json:"tenant_id,omitempty"`
+		AllocationPools []AllocationPool `json:"allocation_pools,omitempty"`
+		GatewayIP       *string          `json:"gateway_ip,omitempty"`
+		IPVersion       int              `json:"ip_version,omitempty"`
+		ID              *string          `json:"id,omitempty"`
+		EnableDHCP      *bool            `json:"enable_dhcp,omitempty"`
+	}
+	type request struct {
+		Subnet subnet `json:"subnet"`
+	}
+
+	reqBody := request{Subnet: subnet{
+		NetworkID: opts.NetworkID,
+		CIDR:      opts.CIDR,
+	}}
+
+	reqBody.Subnet.Name = maybeString(opts.Name)
+	reqBody.Subnet.TenantID = maybeString(opts.TenantID)
+	reqBody.Subnet.GatewayIP = maybeString(opts.GatewayIP)
+	reqBody.Subnet.ID = maybeString(opts.ID)
+	reqBody.Subnet.EnableDHCP = opts.EnableDHCP
+
+	if opts.IPVersion != 0 {
+		reqBody.Subnet.IPVersion = opts.IPVersion
+	}
+
+	if len(opts.AllocationPools) != 0 {
+		reqBody.Subnet.AllocationPools = opts.AllocationPools
+	}
+
+	type response struct {
+		Subnet *Subnet `json:"subnet"`
+	}
+
+	var res response
+	_, err := perigee.Request("POST", CreateURL(c), perigee.Options{
+		MoreHeaders: c.Provider.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		Results:     &res,
+		OkCodes:     []int{201},
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return res.Subnet, nil
+}
diff --git a/openstack/networking/v2/subnets/requests_test.go b/openstack/networking/v2/subnets/requests_test.go
index fb21ff3..f5716cf 100644
--- a/openstack/networking/v2/subnets/requests_test.go
+++ b/openstack/networking/v2/subnets/requests_test.go
@@ -190,3 +190,71 @@
 	th.AssertEquals(t, s.CIDR, "192.0.0.0/8")
 	th.AssertEquals(t, s.ID, "54d6f61d-db07-451c-9ab3-b9609b6b6f0b")
 }
+
+func TestCreate(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, "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, `
+{
+    "subnet": {
+        "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+        "ip_version": 4,
+        "cidr": "192.168.199.0/24"
+    }
+}
+			`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusCreated)
+
+		fmt.Fprintf(w, `
+{
+    "subnet": {
+        "name": "",
+        "enable_dhcp": true,
+        "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+        "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+        "dns_nameservers": [],
+        "allocation_pools": [
+            {
+                "start": "192.168.199.2",
+                "end": "192.168.199.254"
+            }
+        ],
+        "host_routes": [],
+        "ip_version": 4,
+        "gateway_ip": "192.168.199.1",
+        "cidr": "192.168.199.0/24",
+        "id": "3b80198d-4f7b-4f77-9ef5-774d54e17126"
+    }
+}
+		`)
+	})
+
+	opts := SubnetOpts{NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", IPVersion: 4, CIDR: "192.168.199.0/24"}
+	s, err := Create(ServiceClient(), opts)
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, s.Name, "")
+	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.168.199.2",
+			End:   "192.168.199.254",
+		},
+	})
+	th.AssertDeepEquals(t, s.HostRoutes, []interface{}{})
+	th.AssertEquals(t, s.IPVersion, 4)
+	th.AssertEquals(t, s.GatewayIP, "192.168.199.1")
+	th.AssertEquals(t, s.CIDR, "192.168.199.0/24")
+	th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126")
+}
diff --git a/openstack/networking/v2/subnets/results.go b/openstack/networking/v2/subnets/results.go
index 1790028..3de2668 100644
--- a/openstack/networking/v2/subnets/results.go
+++ b/openstack/networking/v2/subnets/results.go
@@ -6,8 +6,8 @@
 )
 
 type AllocationPool struct {
-	Start string
-	End   string
+	Start string `json:"start"`
+	End   string `json:"end"`
 }
 
 type Subnet struct {
diff --git a/openstack/networking/v2/subnets/urls.go b/openstack/networking/v2/subnets/urls.go
index 2cf128b..485f97a 100644
--- a/openstack/networking/v2/subnets/urls.go
+++ b/openstack/networking/v2/subnets/urls.go
@@ -19,3 +19,7 @@
 func GetURL(c *gophercloud.ServiceClient, id string) string {
 	return ResourceURL(c, id)
 }
+
+func CreateURL(c *gophercloud.ServiceClient) string {
+	return RootURL(c)
+}
diff --git a/openstack/networking/v2/subnets/urls_tests.go b/openstack/networking/v2/subnets/urls_tests.go
index 95b179f..336e2fe 100644
--- a/openstack/networking/v2/subnets/urls_tests.go
+++ b/openstack/networking/v2/subnets/urls_tests.go
@@ -24,3 +24,9 @@
 	expected := Endpoint + "v2.0/subnets/foo"
 	th.AssertEquals(t, expected, actual)
 }
+
+func TestCreateURL(t *testing.T) {
+	actual := CreateURL(EndpointClient())
+	expected := Endpoint + "v2.0/subnets"
+	th.AssertEquals(t, expected, actual)
+}